input: touchpanel: Add Synaptics latest version 2.6 touchpanel driver
This is the reference driver source code for synaptics touch driver, the version is 2.6. All the new files are copied from the git commit: 7b6b9d126aca1f371e8abf3ae56d7a5a4e538f13 drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_active_pen.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_active_pen.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_core.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.h is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_core.h; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_fw_update.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_fw_update.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_gesture.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_gesture.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_i2c.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_i2c.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_proximity.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_proximity.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_dev.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_rmi_dev.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_hid_i2c.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_rmi_hid_i2c.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_spi.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_spi.c; drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_test_reporting.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_test_reporting.c drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_video.c is copied from kernel/drivers/input/touchscreen/synaptics_dsx/synaptics_dsx_video.c include/linux/input/synaptics_dsx_v2_6.h is copied from kernel/include/linux/input/synaptics_dsx.h Change-Id: Ifda2c38ca5b4a82c3363d8c59ae9da3839568e82 Signed-off-by: Mao Li <maol@codeaurora.org>
This commit is contained in:
parent
4101688009
commit
1420512d46
15 changed files with 21046 additions and 0 deletions
117
drivers/input/touchscreen/synaptics_dsx_2.6/Kconfig
Normal file
117
drivers/input/touchscreen/synaptics_dsx_2.6/Kconfig
Normal file
|
@ -0,0 +1,117 @@
|
|||
#
|
||||
# Synaptics DSX touchscreen driver configuration
|
||||
#
|
||||
menuconfig TOUCHSCREEN_SYNAPTICS_DSX
|
||||
bool "Synaptics DSX touchscreen"
|
||||
default N
|
||||
help
|
||||
Say Y here if you have a Synaptics DSX touchscreen connected
|
||||
to your system.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
if TOUCHSCREEN_SYNAPTICS_DSX
|
||||
|
||||
choice
|
||||
default TOUCHSCREEN_SYNAPTICS_DSX_I2C
|
||||
prompt "Synaptics DSX bus interface"
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_I2C
|
||||
bool "RMI over I2C"
|
||||
depends on I2C
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_SPI
|
||||
bool "RMI over SPI"
|
||||
depends on SPI_MASTER
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_RMI_HID_I2C
|
||||
bool "HID over I2C"
|
||||
depends on I2C
|
||||
endchoice
|
||||
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_CORE
|
||||
tristate "Synaptics DSX core driver module"
|
||||
depends on I2C || SPI_MASTER
|
||||
help
|
||||
Say Y here to enable basic touch reporting functionality.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called synaptics_dsx_core.
|
||||
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_RMI_DEV
|
||||
tristate "Synaptics DSX RMI device module"
|
||||
depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE
|
||||
help
|
||||
Say Y here to enable support for direct RMI register access.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called synaptics_dsx_rmi_dev.
|
||||
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE
|
||||
tristate "Synaptics DSX firmware update module"
|
||||
depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE
|
||||
help
|
||||
Say Y here to enable support for doing firmware update.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called synaptics_dsx_fw_update.
|
||||
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_TEST_REPORTING
|
||||
tristate "Synaptics DSX test reporting module"
|
||||
depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE
|
||||
help
|
||||
Say Y here to enable support for retrieving production test reports.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called synaptics_dsx_test_reporting.
|
||||
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_PROXIMITY
|
||||
tristate "Synaptics DSX proximity module"
|
||||
depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE
|
||||
help
|
||||
Say Y here to enable support for proximity functionality.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called synaptics_dsx_proximity.
|
||||
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_ACTIVE_PEN
|
||||
tristate "Synaptics DSX active pen module"
|
||||
depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE
|
||||
help
|
||||
Say Y here to enable support for active pen functionality.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called synaptics_dsx_active_pen.
|
||||
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_GESTURE
|
||||
tristate "Synaptics DSX user defined gesture module"
|
||||
depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE
|
||||
help
|
||||
Say Y here to enable support for user defined gesture functionality.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called synaptics_dsx_gesture.
|
||||
|
||||
config TOUCHSCREEN_SYNAPTICS_DSX_VIDEO
|
||||
tristate "Synaptics DSX video module"
|
||||
depends on TOUCHSCREEN_SYNAPTICS_DSX_CORE
|
||||
help
|
||||
Say Y here to enable support for video communication functionality.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called synaptics_dsx_video.
|
||||
|
||||
endif
|
17
drivers/input/touchscreen/synaptics_dsx_2.6/Makefile
Normal file
17
drivers/input/touchscreen/synaptics_dsx_2.6/Makefile
Normal file
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# Makefile for the Synaptics DSX touchscreen driver.
|
||||
#
|
||||
|
||||
# Each configuration option enables a list of files.
|
||||
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_I2C) += synaptics_dsx_i2c.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_SPI) += synaptics_dsx_spi.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_RMI_HID_I2C) += synaptics_dsx_rmi_hid_i2c.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_CORE) += synaptics_dsx_core.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_RMI_DEV) += synaptics_dsx_rmi_dev.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_FW_UPDATE) += synaptics_dsx_fw_update.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_TEST_REPORTING) += synaptics_dsx_test_reporting.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_PROXIMITY) += synaptics_dsx_proximity.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_ACTIVE_PEN) += synaptics_dsx_active_pen.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_GESTURE) += synaptics_dsx_gesture.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_DSX_VIDEO) += synaptics_dsx_video.o
|
|
@ -0,0 +1,624 @@
|
|||
/*
|
||||
* Synaptics DSX touchscreen driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
|
||||
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
|
||||
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
|
||||
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
||||
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
|
||||
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
|
||||
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
|
||||
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
|
||||
* DOLLARS.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/input/synaptics_dsx.h>
|
||||
#include "synaptics_dsx_core.h"
|
||||
|
||||
#define APEN_PHYS_NAME "synaptics_dsx/active_pen"
|
||||
|
||||
#define ACTIVE_PEN_MAX_PRESSURE_16BIT 65535
|
||||
#define ACTIVE_PEN_MAX_PRESSURE_8BIT 255
|
||||
|
||||
struct synaptics_rmi4_f12_query_8 {
|
||||
union {
|
||||
struct {
|
||||
unsigned char size_of_query9;
|
||||
struct {
|
||||
unsigned char data0_is_present:1;
|
||||
unsigned char data1_is_present:1;
|
||||
unsigned char data2_is_present:1;
|
||||
unsigned char data3_is_present:1;
|
||||
unsigned char data4_is_present:1;
|
||||
unsigned char data5_is_present:1;
|
||||
unsigned char data6_is_present:1;
|
||||
unsigned char data7_is_present:1;
|
||||
} __packed;
|
||||
};
|
||||
unsigned char data[2];
|
||||
};
|
||||
};
|
||||
|
||||
struct apen_data_8b_pressure {
|
||||
union {
|
||||
struct {
|
||||
unsigned char status_pen:1;
|
||||
unsigned char status_invert:1;
|
||||
unsigned char status_barrel:1;
|
||||
unsigned char status_reserved:5;
|
||||
unsigned char x_lsb;
|
||||
unsigned char x_msb;
|
||||
unsigned char y_lsb;
|
||||
unsigned char y_msb;
|
||||
unsigned char pressure_msb;
|
||||
unsigned char battery_state;
|
||||
unsigned char pen_id_0_7;
|
||||
unsigned char pen_id_8_15;
|
||||
unsigned char pen_id_16_23;
|
||||
unsigned char pen_id_24_31;
|
||||
} __packed;
|
||||
unsigned char data[11];
|
||||
};
|
||||
};
|
||||
|
||||
struct apen_data {
|
||||
union {
|
||||
struct {
|
||||
unsigned char status_pen:1;
|
||||
unsigned char status_invert:1;
|
||||
unsigned char status_barrel:1;
|
||||
unsigned char status_reserved:5;
|
||||
unsigned char x_lsb;
|
||||
unsigned char x_msb;
|
||||
unsigned char y_lsb;
|
||||
unsigned char y_msb;
|
||||
unsigned char pressure_lsb;
|
||||
unsigned char pressure_msb;
|
||||
unsigned char battery_state;
|
||||
unsigned char pen_id_0_7;
|
||||
unsigned char pen_id_8_15;
|
||||
unsigned char pen_id_16_23;
|
||||
unsigned char pen_id_24_31;
|
||||
} __packed;
|
||||
unsigned char data[12];
|
||||
};
|
||||
};
|
||||
|
||||
struct synaptics_rmi4_apen_handle {
|
||||
bool apen_present;
|
||||
unsigned char intr_mask;
|
||||
unsigned char battery_state;
|
||||
unsigned short query_base_addr;
|
||||
unsigned short control_base_addr;
|
||||
unsigned short data_base_addr;
|
||||
unsigned short command_base_addr;
|
||||
unsigned short apen_data_addr;
|
||||
unsigned short max_pressure;
|
||||
unsigned int pen_id;
|
||||
struct input_dev *apen_dev;
|
||||
struct apen_data *apen_data;
|
||||
struct synaptics_rmi4_data *rmi4_data;
|
||||
};
|
||||
|
||||
static struct synaptics_rmi4_apen_handle *apen;
|
||||
|
||||
DECLARE_COMPLETION(apen_remove_complete);
|
||||
|
||||
static void apen_lift(void)
|
||||
{
|
||||
input_report_key(apen->apen_dev, BTN_TOUCH, 0);
|
||||
input_report_key(apen->apen_dev, BTN_TOOL_PEN, 0);
|
||||
input_report_key(apen->apen_dev, BTN_TOOL_RUBBER, 0);
|
||||
input_sync(apen->apen_dev);
|
||||
apen->apen_present = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void apen_report(void)
|
||||
{
|
||||
int retval;
|
||||
int x;
|
||||
int y;
|
||||
int pressure;
|
||||
static int invert = -1;
|
||||
struct apen_data_8b_pressure *apen_data_8b;
|
||||
struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data;
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
apen->apen_data_addr,
|
||||
apen->apen_data->data,
|
||||
sizeof(apen->apen_data->data));
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to read active pen data\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (apen->apen_data->status_pen == 0) {
|
||||
if (apen->apen_present)
|
||||
apen_lift();
|
||||
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: No active pen data\n",
|
||||
__func__);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
x = (apen->apen_data->x_msb << 8) | (apen->apen_data->x_lsb);
|
||||
y = (apen->apen_data->y_msb << 8) | (apen->apen_data->y_lsb);
|
||||
|
||||
if ((x == -1) && (y == -1)) {
|
||||
if (apen->apen_present)
|
||||
apen_lift();
|
||||
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: Active pen in range but no valid x & y\n",
|
||||
__func__);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apen->apen_present)
|
||||
invert = -1;
|
||||
|
||||
if (invert != -1 && invert != apen->apen_data->status_invert)
|
||||
apen_lift();
|
||||
|
||||
invert = apen->apen_data->status_invert;
|
||||
|
||||
if (apen->max_pressure == ACTIVE_PEN_MAX_PRESSURE_16BIT) {
|
||||
pressure = (apen->apen_data->pressure_msb << 8) |
|
||||
apen->apen_data->pressure_lsb;
|
||||
apen->battery_state = apen->apen_data->battery_state;
|
||||
apen->pen_id = (apen->apen_data->pen_id_24_31 << 24) |
|
||||
(apen->apen_data->pen_id_16_23 << 16) |
|
||||
(apen->apen_data->pen_id_8_15 << 8) |
|
||||
apen->apen_data->pen_id_0_7;
|
||||
} else {
|
||||
apen_data_8b = (struct apen_data_8b_pressure *)apen->apen_data;
|
||||
pressure = apen_data_8b->pressure_msb;
|
||||
apen->battery_state = apen_data_8b->battery_state;
|
||||
apen->pen_id = (apen_data_8b->pen_id_24_31 << 24) |
|
||||
(apen_data_8b->pen_id_16_23 << 16) |
|
||||
(apen_data_8b->pen_id_8_15 << 8) |
|
||||
apen_data_8b->pen_id_0_7;
|
||||
}
|
||||
|
||||
input_report_key(apen->apen_dev, BTN_TOUCH, pressure > 0 ? 1 : 0);
|
||||
input_report_key(apen->apen_dev,
|
||||
apen->apen_data->status_invert > 0 ?
|
||||
BTN_TOOL_RUBBER : BTN_TOOL_PEN, 1);
|
||||
input_report_key(apen->apen_dev,
|
||||
BTN_STYLUS, apen->apen_data->status_barrel > 0 ?
|
||||
1 : 0);
|
||||
input_report_abs(apen->apen_dev, ABS_X, x);
|
||||
input_report_abs(apen->apen_dev, ABS_Y, y);
|
||||
input_report_abs(apen->apen_dev, ABS_PRESSURE, pressure);
|
||||
|
||||
input_sync(apen->apen_dev);
|
||||
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: Active pen: status = %d, invert = %d, barrel = %d, x = %d, y = %d, pressure = %d\n",
|
||||
__func__,
|
||||
apen->apen_data->status_pen,
|
||||
apen->apen_data->status_invert,
|
||||
apen->apen_data->status_barrel,
|
||||
x, y, pressure);
|
||||
|
||||
apen->apen_present = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void apen_set_params(void)
|
||||
{
|
||||
input_set_abs_params(apen->apen_dev, ABS_X, 0,
|
||||
apen->rmi4_data->sensor_max_x, 0, 0);
|
||||
input_set_abs_params(apen->apen_dev, ABS_Y, 0,
|
||||
apen->rmi4_data->sensor_max_y, 0, 0);
|
||||
input_set_abs_params(apen->apen_dev, ABS_PRESSURE, 0,
|
||||
apen->max_pressure, 0, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int apen_pressure(struct synaptics_rmi4_f12_query_8 *query_8)
|
||||
{
|
||||
int retval;
|
||||
unsigned char ii;
|
||||
unsigned char data_reg_presence;
|
||||
unsigned char size_of_query_9;
|
||||
unsigned char *query_9;
|
||||
unsigned char *data_desc;
|
||||
struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data;
|
||||
|
||||
data_reg_presence = query_8->data[1];
|
||||
|
||||
size_of_query_9 = query_8->size_of_query9;
|
||||
query_9 = kmalloc(size_of_query_9, GFP_KERNEL);
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
apen->query_base_addr + 9,
|
||||
query_9,
|
||||
size_of_query_9);
|
||||
if (retval < 0)
|
||||
goto exit;
|
||||
|
||||
data_desc = query_9;
|
||||
|
||||
for (ii = 0; ii < 6; ii++) {
|
||||
if (!(data_reg_presence & (1 << ii)))
|
||||
continue; /* The data register is not present */
|
||||
data_desc++; /* Jump over the size entry */
|
||||
while (*data_desc & (1 << 7))
|
||||
data_desc++;
|
||||
data_desc++; /* Go to the next descriptor */
|
||||
}
|
||||
|
||||
data_desc++; /* Jump over the size entry */
|
||||
/* Check for the presence of subpackets 1 and 2 */
|
||||
if ((*data_desc & (3 << 1)) == (3 << 1))
|
||||
apen->max_pressure = ACTIVE_PEN_MAX_PRESSURE_16BIT;
|
||||
else
|
||||
apen->max_pressure = ACTIVE_PEN_MAX_PRESSURE_8BIT;
|
||||
|
||||
exit:
|
||||
kfree(query_9);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int apen_reg_init(void)
|
||||
{
|
||||
int retval;
|
||||
unsigned char data_offset;
|
||||
unsigned char size_of_query8;
|
||||
struct synaptics_rmi4_f12_query_8 query_8;
|
||||
struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data;
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
apen->query_base_addr + 7,
|
||||
&size_of_query8,
|
||||
sizeof(size_of_query8));
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
apen->query_base_addr + 8,
|
||||
query_8.data,
|
||||
sizeof(query_8.data));
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
if ((size_of_query8 >= 2) && (query_8.data6_is_present)) {
|
||||
data_offset = query_8.data0_is_present +
|
||||
query_8.data1_is_present +
|
||||
query_8.data2_is_present +
|
||||
query_8.data3_is_present +
|
||||
query_8.data4_is_present +
|
||||
query_8.data5_is_present;
|
||||
apen->apen_data_addr = apen->data_base_addr + data_offset;
|
||||
retval = apen_pressure(&query_8);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
} else {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Active pen support unavailable\n",
|
||||
__func__);
|
||||
retval = -ENODEV;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int apen_scan_pdt(void)
|
||||
{
|
||||
int retval;
|
||||
unsigned char ii;
|
||||
unsigned char page;
|
||||
unsigned char intr_count = 0;
|
||||
unsigned char intr_off;
|
||||
unsigned char intr_src;
|
||||
unsigned short addr;
|
||||
struct synaptics_rmi4_fn_desc fd;
|
||||
struct synaptics_rmi4_data *rmi4_data = apen->rmi4_data;
|
||||
|
||||
for (page = 0; page < PAGES_TO_SERVICE; page++) {
|
||||
for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) {
|
||||
addr |= (page << 8);
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
addr,
|
||||
(unsigned char *)&fd,
|
||||
sizeof(fd));
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
addr &= ~(MASK_8BIT << 8);
|
||||
|
||||
if (fd.fn_number) {
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: Found F%02x\n",
|
||||
__func__, fd.fn_number);
|
||||
switch (fd.fn_number) {
|
||||
case SYNAPTICS_RMI4_F12:
|
||||
goto f12_found;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
intr_count += fd.intr_src_count;
|
||||
}
|
||||
}
|
||||
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to find F12\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
|
||||
f12_found:
|
||||
apen->query_base_addr = fd.query_base_addr | (page << 8);
|
||||
apen->control_base_addr = fd.ctrl_base_addr | (page << 8);
|
||||
apen->data_base_addr = fd.data_base_addr | (page << 8);
|
||||
apen->command_base_addr = fd.cmd_base_addr | (page << 8);
|
||||
|
||||
retval = apen_reg_init();
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to initialize active pen registers\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
apen->intr_mask = 0;
|
||||
intr_src = fd.intr_src_count;
|
||||
intr_off = intr_count % 8;
|
||||
for (ii = intr_off;
|
||||
ii < (intr_src + intr_off);
|
||||
ii++) {
|
||||
apen->intr_mask |= 1 << ii;
|
||||
}
|
||||
|
||||
rmi4_data->intr_mask[0] |= apen->intr_mask;
|
||||
|
||||
addr = rmi4_data->f01_ctrl_base_addr + 1;
|
||||
|
||||
retval = synaptics_rmi4_reg_write(rmi4_data,
|
||||
addr,
|
||||
&(rmi4_data->intr_mask[0]),
|
||||
sizeof(rmi4_data->intr_mask[0]));
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to set interrupt enable bit\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_apen_attn(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned char intr_mask)
|
||||
{
|
||||
if (!apen)
|
||||
return;
|
||||
|
||||
if (apen->intr_mask & intr_mask)
|
||||
apen_report();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_apen_init(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
int retval;
|
||||
|
||||
if (apen) {
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: Handle already exists\n",
|
||||
__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
apen = kzalloc(sizeof(*apen), GFP_KERNEL);
|
||||
if (!apen) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to alloc mem for apen\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
apen->apen_data = kzalloc(sizeof(*(apen->apen_data)), GFP_KERNEL);
|
||||
if (!apen->apen_data) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to alloc mem for apen_data\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit_free_apen;
|
||||
}
|
||||
|
||||
apen->rmi4_data = rmi4_data;
|
||||
|
||||
retval = apen_scan_pdt();
|
||||
if (retval < 0)
|
||||
goto exit_free_apen_data;
|
||||
|
||||
apen->apen_dev = input_allocate_device();
|
||||
if (apen->apen_dev == NULL) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to allocate active pen device\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit_free_apen_data;
|
||||
}
|
||||
|
||||
apen->apen_dev->name = ACTIVE_PEN_DRIVER_NAME;
|
||||
apen->apen_dev->phys = APEN_PHYS_NAME;
|
||||
apen->apen_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT;
|
||||
apen->apen_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION;
|
||||
apen->apen_dev->dev.parent = rmi4_data->pdev->dev.parent;
|
||||
input_set_drvdata(apen->apen_dev, rmi4_data);
|
||||
|
||||
set_bit(EV_KEY, apen->apen_dev->evbit);
|
||||
set_bit(EV_ABS, apen->apen_dev->evbit);
|
||||
set_bit(BTN_TOUCH, apen->apen_dev->keybit);
|
||||
set_bit(BTN_TOOL_PEN, apen->apen_dev->keybit);
|
||||
set_bit(BTN_TOOL_RUBBER, apen->apen_dev->keybit);
|
||||
set_bit(BTN_STYLUS, apen->apen_dev->keybit);
|
||||
#ifdef INPUT_PROP_DIRECT
|
||||
set_bit(INPUT_PROP_DIRECT, apen->apen_dev->propbit);
|
||||
#endif
|
||||
|
||||
apen_set_params();
|
||||
|
||||
retval = input_register_device(apen->apen_dev);
|
||||
if (retval) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to register active pen device\n",
|
||||
__func__);
|
||||
goto exit_free_input_device;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free_input_device:
|
||||
input_free_device(apen->apen_dev);
|
||||
|
||||
exit_free_apen_data:
|
||||
kfree(apen->apen_data);
|
||||
|
||||
exit_free_apen:
|
||||
kfree(apen);
|
||||
apen = NULL;
|
||||
|
||||
exit:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_apen_remove(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!apen)
|
||||
goto exit;
|
||||
|
||||
input_unregister_device(apen->apen_dev);
|
||||
kfree(apen->apen_data);
|
||||
kfree(apen);
|
||||
apen = NULL;
|
||||
|
||||
exit:
|
||||
complete(&apen_remove_complete);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_apen_reset(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!apen) {
|
||||
synaptics_rmi4_apen_init(rmi4_data);
|
||||
return;
|
||||
}
|
||||
|
||||
apen_lift();
|
||||
|
||||
apen_scan_pdt();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_apen_reinit(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!apen)
|
||||
return;
|
||||
|
||||
apen_lift();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_apen_e_suspend(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!apen)
|
||||
return;
|
||||
|
||||
apen_lift();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_apen_suspend(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!apen)
|
||||
return;
|
||||
|
||||
apen_lift();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static struct synaptics_rmi4_exp_fn active_pen_module = {
|
||||
.fn_type = RMI_ACTIVE_PEN,
|
||||
.init = synaptics_rmi4_apen_init,
|
||||
.remove = synaptics_rmi4_apen_remove,
|
||||
.reset = synaptics_rmi4_apen_reset,
|
||||
.reinit = synaptics_rmi4_apen_reinit,
|
||||
.early_suspend = synaptics_rmi4_apen_e_suspend,
|
||||
.suspend = synaptics_rmi4_apen_suspend,
|
||||
.resume = NULL,
|
||||
.late_resume = NULL,
|
||||
.attn = synaptics_rmi4_apen_attn,
|
||||
};
|
||||
|
||||
static int __init rmi4_active_pen_module_init(void)
|
||||
{
|
||||
synaptics_rmi4_new_function(&active_pen_module, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rmi4_active_pen_module_exit(void)
|
||||
{
|
||||
synaptics_rmi4_new_function(&active_pen_module, false);
|
||||
|
||||
wait_for_completion(&apen_remove_complete);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
module_init(rmi4_active_pen_module_init);
|
||||
module_exit(rmi4_active_pen_module_exit);
|
||||
|
||||
MODULE_AUTHOR("Synaptics, Inc.");
|
||||
MODULE_DESCRIPTION("Synaptics DSX Active Pen Module");
|
||||
MODULE_LICENSE("GPL v2");
|
4330
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.c
Normal file
4330
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.c
Normal file
File diff suppressed because it is too large
Load diff
477
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.h
Normal file
477
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_core.h
Normal file
|
@ -0,0 +1,477 @@
|
|||
/*
|
||||
* Synaptics DSX touchscreen driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
|
||||
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
|
||||
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
|
||||
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
||||
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
|
||||
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
|
||||
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
|
||||
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
|
||||
* DOLLARS.
|
||||
*/
|
||||
|
||||
#ifndef _SYNAPTICS_DSX_RMI4_H_
|
||||
#define _SYNAPTICS_DSX_RMI4_H_
|
||||
|
||||
#define SYNAPTICS_DS4 (1 << 0)
|
||||
#define SYNAPTICS_DS5 (1 << 1)
|
||||
#define SYNAPTICS_DSX_DRIVER_PRODUCT (SYNAPTICS_DS4 | SYNAPTICS_DS5)
|
||||
#define SYNAPTICS_DSX_DRIVER_VERSION 0x2061
|
||||
|
||||
#include <linux/version.h>
|
||||
#ifdef CONFIG_FB
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/fb.h>
|
||||
#endif
|
||||
#ifdef CONFIG_HAS_EARLYSUSPEND
|
||||
#include <linux/earlysuspend.h>
|
||||
#endif
|
||||
|
||||
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 38))
|
||||
#define KERNEL_ABOVE_2_6_38
|
||||
#endif
|
||||
|
||||
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0))
|
||||
#define KERNEL_ABOVE_3_6
|
||||
#endif
|
||||
|
||||
#ifdef KERNEL_ABOVE_2_6_38
|
||||
#define sstrtoul(...) kstrtoul(__VA_ARGS__)
|
||||
#else
|
||||
#define sstrtoul(...) strict_strtoul(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define PDT_PROPS (0X00EF)
|
||||
#define PDT_START (0x00E9)
|
||||
#define PDT_END (0x00D0)
|
||||
#define PDT_ENTRY_SIZE (0x0006)
|
||||
#define PAGES_TO_SERVICE (10)
|
||||
#define PAGE_SELECT_LEN (2)
|
||||
#define ADDRESS_WORD_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 PRODUCT_INFO_SIZE 2
|
||||
#define PRODUCT_ID_SIZE 10
|
||||
#define BUILD_ID_SIZE 3
|
||||
|
||||
#define F12_FINGERS_TO_SUPPORT 10
|
||||
#define F12_NO_OBJECT_STATUS 0x00
|
||||
#define F12_FINGER_STATUS 0x01
|
||||
#define F12_ACTIVE_STYLUS_STATUS 0x02
|
||||
#define F12_PALM_STATUS 0x03
|
||||
#define F12_HOVERING_FINGER_STATUS 0x05
|
||||
#define F12_GLOVED_FINGER_STATUS 0x06
|
||||
#define F12_NARROW_OBJECT_STATUS 0x07
|
||||
#define F12_HAND_EDGE_STATUS 0x08
|
||||
#define F12_COVER_STATUS 0x0A
|
||||
#define F12_STYLUS_STATUS 0x0B
|
||||
#define F12_ERASER_STATUS 0x0C
|
||||
#define F12_SMALL_OBJECT_STATUS 0x0D
|
||||
|
||||
#define F12_GESTURE_DETECTION_LEN 5
|
||||
|
||||
#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
|
||||
|
||||
enum exp_fn {
|
||||
RMI_DEV = 0,
|
||||
RMI_FW_UPDATER,
|
||||
RMI_TEST_REPORTING,
|
||||
RMI_PROXIMITY,
|
||||
RMI_ACTIVE_PEN,
|
||||
RMI_GESTURE,
|
||||
RMI_VIDEO,
|
||||
RMI_DEBUG,
|
||||
RMI_LAST,
|
||||
};
|
||||
|
||||
/*
|
||||
* struct synaptics_rmi4_fn_desc - function descriptor fields in PDT entry
|
||||
* @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_version: version of function
|
||||
* @fn_number: function number
|
||||
*/
|
||||
struct synaptics_rmi4_fn_desc {
|
||||
union {
|
||||
struct {
|
||||
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;
|
||||
} __packed;
|
||||
unsigned char data[6];
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 command registers
|
||||
* @ctrl_base: 16-bit base address for control registers
|
||||
* @data_base: 16-bit base address for data 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_f11_extra_data - extra data of F$11
|
||||
* @data38_offset: offset to F11_2D_DATA38 register
|
||||
*/
|
||||
struct synaptics_rmi4_f11_extra_data {
|
||||
unsigned char data38_offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct synaptics_rmi4_f12_extra_data - extra data of F$12
|
||||
* @data1_offset: offset to F12_2D_DATA01 register
|
||||
* @data4_offset: offset to F12_2D_DATA04 register
|
||||
* @data15_offset: offset to F12_2D_DATA15 register
|
||||
* @data15_size: size of F12_2D_DATA15 register
|
||||
* @data15_data: buffer for reading F12_2D_DATA15 register
|
||||
* @data23_offset: offset to F12_2D_DATA23 register
|
||||
* @data23_size: size of F12_2D_DATA23 register
|
||||
* @data23_data: buffer for reading F12_2D_DATA23 register
|
||||
* @ctrl20_offset: offset to F12_2D_CTRL20 register
|
||||
*/
|
||||
struct synaptics_rmi4_f12_extra_data {
|
||||
unsigned char data1_offset;
|
||||
unsigned char data4_offset;
|
||||
unsigned char data15_offset;
|
||||
unsigned char data15_size;
|
||||
unsigned char data15_data[(F12_FINGERS_TO_SUPPORT + 7) / 8];
|
||||
unsigned char data23_offset;
|
||||
unsigned char data23_size;
|
||||
unsigned char data23_data[F12_FINGERS_TO_SUPPORT];
|
||||
unsigned char ctrl20_offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct synaptics_rmi4_fn - RMI function handler
|
||||
* @fn_number: function number
|
||||
* @num_of_data_sources: number of data sources
|
||||
* @num_of_data_points: maximum number of fingers supported
|
||||
* @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
|
||||
* @extra: pointer to extra data
|
||||
*/
|
||||
struct synaptics_rmi4_fn {
|
||||
unsigned char fn_number;
|
||||
unsigned char num_of_data_sources;
|
||||
unsigned char num_of_data_points;
|
||||
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;
|
||||
void *extra;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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
|
||||
* @product_info: product information
|
||||
* @product_id_string: product ID
|
||||
* @build_id: firmware build 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[PRODUCT_INFO_SIZE];
|
||||
unsigned char product_id_string[PRODUCT_ID_SIZE + 1];
|
||||
unsigned char build_id[BUILD_ID_SIZE];
|
||||
struct list_head support_fn_list;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct synaptics_rmi4_data - RMI4 device instance data
|
||||
* @pdev: pointer to platform device
|
||||
* @input_dev: pointer to associated input device
|
||||
* @stylus_dev: pointer to associated stylus device
|
||||
* @hw_if: pointer to hardware interface data
|
||||
* @rmi4_mod_info: device information
|
||||
* @board_prop_dir: /sys/board_properties directory for virtual key map file
|
||||
* @pwr_reg: pointer to regulator for power control
|
||||
* @bus_reg: pointer to regulator for bus pullup control
|
||||
* @rmi4_reset_mutex: mutex for software reset
|
||||
* @rmi4_report_mutex: mutex for input event reporting
|
||||
* @rmi4_io_ctrl_mutex: mutex for communication interface I/O
|
||||
* @rmi4_exp_init_mutex: mutex for expansion function module initialization
|
||||
* @rb_work: work for rebuilding input device
|
||||
* @rb_workqueue: workqueue for rebuilding input device
|
||||
* @fb_notifier: framebuffer notifier client
|
||||
* @reset_work: work for issuing reset after display framebuffer ready
|
||||
* @reset_workqueue: workqueue for issuing reset after display framebuffer ready
|
||||
* @early_suspend: early suspend power management
|
||||
* @current_page: current RMI page for register access
|
||||
* @button_0d_enabled: switch for enabling 0d button support
|
||||
* @num_of_tx: number of Tx channels for 2D touch
|
||||
* @num_of_rx: number of Rx channels for 2D touch
|
||||
* @num_of_fingers: maximum number of fingers for 2D touch
|
||||
* @max_touch_width: maximum touch width
|
||||
* @report_enable: input data to report for F$12
|
||||
* @no_sleep_setting: default setting of NoSleep in F01_RMI_CTRL00 register
|
||||
* @gesture_detection: detected gesture type and properties
|
||||
* @intr_mask: interrupt enable mask
|
||||
* @button_txrx_mapping: Tx Rx mapping of 0D buttons
|
||||
* @num_of_intr_regs: number of interrupt registers
|
||||
* @f01_query_base_addr: query base address for f$01
|
||||
* @f01_cmd_base_addr: command base address for f$01
|
||||
* @f01_ctrl_base_addr: control base address for f$01
|
||||
* @f01_data_base_addr: data base address for f$01
|
||||
* @firmware_id: firmware build ID
|
||||
* @irq: attention interrupt
|
||||
* @sensor_max_x: maximum x coordinate for 2D touch
|
||||
* @sensor_max_y: maximum y coordinate for 2D touch
|
||||
* @flash_prog_mode: flag to indicate flash programming mode status
|
||||
* @irq_enabled: flag to indicate attention interrupt enable status
|
||||
* @fingers_on_2d: flag to indicate presence of fingers in 2D area
|
||||
* @suspend: flag to indicate whether in suspend state
|
||||
* @sensor_sleep: flag to indicate sleep state of sensor
|
||||
* @stay_awake: flag to indicate whether to stay awake during suspend
|
||||
* @fb_ready: flag to indicate whether display framebuffer in ready state
|
||||
* @f11_wakeup_gesture: flag to indicate support for wakeup gestures in F$11
|
||||
* @f12_wakeup_gesture: flag to indicate support for wakeup gestures in F$12
|
||||
* @enable_wakeup_gesture: flag to indicate usage of wakeup gestures
|
||||
* @wedge_sensor: flag to indicate use of wedge sensor
|
||||
* @report_pressure: flag to indicate reporting of pressure data
|
||||
* @stylus_enable: flag to indicate reporting of stylus data
|
||||
* @eraser_enable: flag to indicate reporting of eraser data
|
||||
* @external_afe_buttons: flag to indicate presence of external AFE buttons
|
||||
* @reset_device: pointer to device reset function
|
||||
* @irq_enable: pointer to interrupt enable function
|
||||
* @sleep_enable: pointer to sleep enable function
|
||||
* @report_touch: pointer to touch reporting function
|
||||
*/
|
||||
struct synaptics_rmi4_data {
|
||||
struct platform_device *pdev;
|
||||
struct input_dev *input_dev;
|
||||
struct input_dev *stylus_dev;
|
||||
const struct synaptics_dsx_hw_interface *hw_if;
|
||||
struct synaptics_rmi4_device_info rmi4_mod_info;
|
||||
struct kobject *board_prop_dir;
|
||||
struct regulator *pwr_reg;
|
||||
struct regulator *bus_reg;
|
||||
struct mutex rmi4_reset_mutex;
|
||||
struct mutex rmi4_report_mutex;
|
||||
struct mutex rmi4_io_ctrl_mutex;
|
||||
struct mutex rmi4_exp_init_mutex;
|
||||
struct delayed_work rb_work;
|
||||
struct workqueue_struct *rb_workqueue;
|
||||
#ifdef CONFIG_FB
|
||||
struct notifier_block fb_notifier;
|
||||
struct work_struct reset_work;
|
||||
struct workqueue_struct *reset_workqueue;
|
||||
#endif
|
||||
#ifdef CONFIG_HAS_EARLYSUSPEND
|
||||
struct early_suspend early_suspend;
|
||||
#endif
|
||||
unsigned char current_page;
|
||||
unsigned char button_0d_enabled;
|
||||
unsigned char num_of_tx;
|
||||
unsigned char num_of_rx;
|
||||
unsigned char num_of_fingers;
|
||||
unsigned char max_touch_width;
|
||||
unsigned char report_enable;
|
||||
unsigned char no_sleep_setting;
|
||||
unsigned char gesture_detection[F12_GESTURE_DETECTION_LEN];
|
||||
unsigned char intr_mask[MAX_INTR_REGISTERS];
|
||||
unsigned char *button_txrx_mapping;
|
||||
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 firmware_id;
|
||||
int irq;
|
||||
int sensor_max_x;
|
||||
int sensor_max_y;
|
||||
bool flash_prog_mode;
|
||||
bool irq_enabled;
|
||||
bool fingers_on_2d;
|
||||
bool suspend;
|
||||
bool sensor_sleep;
|
||||
bool stay_awake;
|
||||
bool fb_ready;
|
||||
bool f11_wakeup_gesture;
|
||||
bool f12_wakeup_gesture;
|
||||
bool enable_wakeup_gesture;
|
||||
bool wedge_sensor;
|
||||
bool report_pressure;
|
||||
bool stylus_enable;
|
||||
bool eraser_enable;
|
||||
bool external_afe_buttons;
|
||||
int (*reset_device)(struct synaptics_rmi4_data *rmi4_data,
|
||||
bool rebuild);
|
||||
int (*irq_enable)(struct synaptics_rmi4_data *rmi4_data, bool enable,
|
||||
bool attn_only);
|
||||
void (*sleep_enable)(struct synaptics_rmi4_data *rmi4_data,
|
||||
bool enable);
|
||||
void (*report_touch)(struct synaptics_rmi4_data *rmi4_data,
|
||||
struct synaptics_rmi4_fn *fhandler);
|
||||
};
|
||||
|
||||
struct synaptics_dsx_bus_access {
|
||||
unsigned char type;
|
||||
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);
|
||||
};
|
||||
|
||||
struct synaptics_dsx_hw_interface {
|
||||
struct synaptics_dsx_board_data *board_data;
|
||||
const struct synaptics_dsx_bus_access *bus_access;
|
||||
int (*bl_hw_init)(struct synaptics_rmi4_data *rmi4_data);
|
||||
int (*ui_hw_init)(struct synaptics_rmi4_data *rmi4_data);
|
||||
};
|
||||
|
||||
struct synaptics_rmi4_exp_fn {
|
||||
enum exp_fn fn_type;
|
||||
int (*init)(struct synaptics_rmi4_data *rmi4_data);
|
||||
void (*remove)(struct synaptics_rmi4_data *rmi4_data);
|
||||
void (*reset)(struct synaptics_rmi4_data *rmi4_data);
|
||||
void (*reinit)(struct synaptics_rmi4_data *rmi4_data);
|
||||
void (*early_suspend)(struct synaptics_rmi4_data *rmi4_data);
|
||||
void (*suspend)(struct synaptics_rmi4_data *rmi4_data);
|
||||
void (*resume)(struct synaptics_rmi4_data *rmi4_data);
|
||||
void (*late_resume)(struct synaptics_rmi4_data *rmi4_data);
|
||||
void (*attn)(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned char intr_mask);
|
||||
};
|
||||
|
||||
int synaptics_rmi4_bus_init(void);
|
||||
|
||||
void synaptics_rmi4_bus_exit(void);
|
||||
|
||||
void synaptics_rmi4_new_function(struct synaptics_rmi4_exp_fn *exp_fn_module,
|
||||
bool insert);
|
||||
|
||||
int synaptics_fw_updater(const unsigned char *fw_data);
|
||||
|
||||
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->hw_if->bus_access->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->hw_if->bus_access->write(rmi4_data, addr, data, len);
|
||||
}
|
||||
|
||||
static inline ssize_t synaptics_rmi4_show_error(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
dev_warn(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)
|
||||
{
|
||||
dev_warn(dev, "%s Attempted to write to read-only attribute %s\n",
|
||||
__func__, attr->attr.name);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load diff
2308
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_gesture.c
Normal file
2308
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_gesture.c
Normal file
File diff suppressed because it is too large
Load diff
646
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_i2c.c
Normal file
646
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_i2c.c
Normal file
|
@ -0,0 +1,646 @@
|
|||
/*
|
||||
* Synaptics DSX touchscreen driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
|
||||
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
|
||||
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
|
||||
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
||||
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
|
||||
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
|
||||
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
|
||||
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
|
||||
* DOLLARS.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/input/synaptics_dsx.h>
|
||||
#include "synaptics_dsx_core.h"
|
||||
|
||||
#define SYN_I2C_RETRY_TIMES 10
|
||||
|
||||
/*
|
||||
#define I2C_BURST_LIMIT 255
|
||||
*/
|
||||
/*
|
||||
#define XFER_MSGS_LIMIT 8
|
||||
*/
|
||||
|
||||
static unsigned char *wr_buf;
|
||||
|
||||
static struct synaptics_dsx_hw_interface hw_if;
|
||||
|
||||
static struct platform_device *synaptics_dsx_i2c_device;
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata)
|
||||
{
|
||||
int retval;
|
||||
u32 value;
|
||||
const char *name;
|
||||
struct property *prop;
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
bdata->irq_gpio = of_get_named_gpio_flags(np,
|
||||
"synaptics,irq-gpio", 0,
|
||||
(enum of_gpio_flags *)&bdata->irq_flags);
|
||||
|
||||
retval = of_property_read_u32(np, "synaptics,irq-on-state",
|
||||
&value);
|
||||
if (retval < 0)
|
||||
bdata->irq_on_state = 0;
|
||||
else
|
||||
bdata->irq_on_state = value;
|
||||
|
||||
retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name);
|
||||
if (retval < 0)
|
||||
bdata->pwr_reg_name = NULL;
|
||||
else
|
||||
bdata->pwr_reg_name = name;
|
||||
|
||||
retval = of_property_read_string(np, "synaptics,bus-reg-name", &name);
|
||||
if (retval < 0)
|
||||
bdata->bus_reg_name = NULL;
|
||||
else
|
||||
bdata->bus_reg_name = name;
|
||||
|
||||
prop = of_find_property(np, "synaptics,power-gpio", NULL);
|
||||
if (prop && prop->length) {
|
||||
bdata->power_gpio = of_get_named_gpio_flags(np,
|
||||
"synaptics,power-gpio", 0, NULL);
|
||||
retval = of_property_read_u32(np, "synaptics,power-on-state",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->power_on_state = value;
|
||||
}
|
||||
} else {
|
||||
bdata->power_gpio = -1;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,power-delay-ms", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,power-delay-ms",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->power_delay_ms = value;
|
||||
}
|
||||
} else {
|
||||
bdata->power_delay_ms = 0;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,reset-gpio", NULL);
|
||||
if (prop && prop->length) {
|
||||
bdata->reset_gpio = of_get_named_gpio_flags(np,
|
||||
"synaptics,reset-gpio", 0, NULL);
|
||||
retval = of_property_read_u32(np, "synaptics,reset-on-state",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->reset_on_state = value;
|
||||
}
|
||||
retval = of_property_read_u32(np, "synaptics,reset-active-ms",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->reset_active_ms = value;
|
||||
}
|
||||
} else {
|
||||
bdata->reset_gpio = -1;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,reset-delay-ms", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,reset-delay-ms",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->reset_delay_ms = value;
|
||||
}
|
||||
} else {
|
||||
bdata->reset_delay_ms = 0;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,max-y-for-2d", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,max-y-for-2d",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->max_y_for_2d = value;
|
||||
}
|
||||
} else {
|
||||
bdata->max_y_for_2d = -1;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,swap-axes", NULL);
|
||||
bdata->swap_axes = prop > 0 ? true : false;
|
||||
|
||||
prop = of_find_property(np, "synaptics,x-flip", NULL);
|
||||
bdata->x_flip = prop > 0 ? true : false;
|
||||
|
||||
prop = of_find_property(np, "synaptics,y-flip", NULL);
|
||||
bdata->y_flip = prop > 0 ? true : false;
|
||||
|
||||
prop = of_find_property(np, "synaptics,ub-i2c-addr", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,ub-i2c-addr",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->ub_i2c_addr = (unsigned short)value;
|
||||
}
|
||||
} else {
|
||||
bdata->ub_i2c_addr = -1;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,cap-button-codes", NULL);
|
||||
if (prop && prop->length) {
|
||||
bdata->cap_button_map->map = devm_kzalloc(dev,
|
||||
prop->length,
|
||||
GFP_KERNEL);
|
||||
if (!bdata->cap_button_map->map)
|
||||
return -ENOMEM;
|
||||
bdata->cap_button_map->nbuttons = prop->length / sizeof(u32);
|
||||
retval = of_property_read_u32_array(np,
|
||||
"synaptics,cap-button-codes",
|
||||
bdata->cap_button_map->map,
|
||||
bdata->cap_button_map->nbuttons);
|
||||
if (retval < 0) {
|
||||
bdata->cap_button_map->nbuttons = 0;
|
||||
bdata->cap_button_map->map = NULL;
|
||||
}
|
||||
} else {
|
||||
bdata->cap_button_map->nbuttons = 0;
|
||||
bdata->cap_button_map->map = NULL;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,vir-button-codes", NULL);
|
||||
if (prop && prop->length) {
|
||||
bdata->vir_button_map->map = devm_kzalloc(dev,
|
||||
prop->length,
|
||||
GFP_KERNEL);
|
||||
if (!bdata->vir_button_map->map)
|
||||
return -ENOMEM;
|
||||
bdata->vir_button_map->nbuttons = prop->length / sizeof(u32);
|
||||
bdata->vir_button_map->nbuttons /= 5;
|
||||
retval = of_property_read_u32_array(np,
|
||||
"synaptics,vir-button-codes",
|
||||
bdata->vir_button_map->map,
|
||||
bdata->vir_button_map->nbuttons * 5);
|
||||
if (retval < 0) {
|
||||
bdata->vir_button_map->nbuttons = 0;
|
||||
bdata->vir_button_map->map = NULL;
|
||||
}
|
||||
} else {
|
||||
bdata->vir_button_map->nbuttons = 0;
|
||||
bdata->vir_button_map->map = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int synaptics_rmi4_i2c_alloc_buf(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned int count)
|
||||
{
|
||||
static unsigned int buf_size;
|
||||
|
||||
if (count > buf_size) {
|
||||
if (buf_size)
|
||||
kfree(wr_buf);
|
||||
wr_buf = kzalloc(count, GFP_KERNEL);
|
||||
if (!wr_buf) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to alloc mem for buffer\n",
|
||||
__func__);
|
||||
buf_size = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
buf_size = count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_i2c_check_addr(struct synaptics_rmi4_data *rmi4_data,
|
||||
struct i2c_client *i2c)
|
||||
{
|
||||
if (hw_if.board_data->ub_i2c_addr == -1)
|
||||
return;
|
||||
|
||||
if (hw_if.board_data->i2c_addr == i2c->addr)
|
||||
hw_if.board_data->i2c_addr = hw_if.board_data->ub_i2c_addr;
|
||||
else
|
||||
hw_if.board_data->i2c_addr = i2c->addr;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_i2c_set_page(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned short addr)
|
||||
{
|
||||
int retval;
|
||||
unsigned char retry;
|
||||
unsigned char buf[PAGE_SELECT_LEN];
|
||||
unsigned char page;
|
||||
struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent);
|
||||
struct i2c_msg msg[1];
|
||||
|
||||
msg[0].addr = hw_if.board_data->i2c_addr;
|
||||
msg[0].flags = 0;
|
||||
msg[0].len = PAGE_SELECT_LEN;
|
||||
msg[0].buf = buf;
|
||||
|
||||
page = ((addr >> 8) & MASK_8BIT);
|
||||
buf[0] = MASK_8BIT;
|
||||
buf[1] = page;
|
||||
|
||||
if (page != rmi4_data->current_page) {
|
||||
for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) {
|
||||
if (i2c_transfer(i2c->adapter, msg, 1) == 1) {
|
||||
rmi4_data->current_page = page;
|
||||
retval = PAGE_SELECT_LEN;
|
||||
break;
|
||||
}
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: I2C retry %d\n",
|
||||
__func__, retry + 1);
|
||||
msleep(20);
|
||||
|
||||
if (retry == SYN_I2C_RETRY_TIMES / 2) {
|
||||
synaptics_rmi4_i2c_check_addr(rmi4_data, i2c);
|
||||
msg[0].addr = hw_if.board_data->i2c_addr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
retval = PAGE_SELECT_LEN;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_i2c_read(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned short addr, unsigned char *data, unsigned short length)
|
||||
{
|
||||
int retval;
|
||||
unsigned char retry;
|
||||
unsigned char buf;
|
||||
#ifdef I2C_BURST_LIMIT
|
||||
unsigned char ii;
|
||||
unsigned char rd_msgs = ((length - 1) / I2C_BURST_LIMIT) + 1;
|
||||
#else
|
||||
unsigned char rd_msgs = 1;
|
||||
#endif
|
||||
unsigned char index = 0;
|
||||
unsigned char xfer_msgs;
|
||||
unsigned char remaining_msgs;
|
||||
unsigned short i2c_addr;
|
||||
unsigned short data_offset = 0;
|
||||
unsigned short remaining_length = length;
|
||||
struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent);
|
||||
struct i2c_adapter *adap = i2c->adapter;
|
||||
struct i2c_msg msg[rd_msgs + 1];
|
||||
|
||||
mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
|
||||
retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr);
|
||||
if (retval != PAGE_SELECT_LEN) {
|
||||
retval = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
msg[0].addr = hw_if.board_data->i2c_addr;
|
||||
msg[0].flags = 0;
|
||||
msg[0].len = 1;
|
||||
msg[0].buf = &buf;
|
||||
|
||||
#ifdef I2C_BURST_LIMIT
|
||||
for (ii = 0; ii < (rd_msgs - 1); ii++) {
|
||||
msg[ii + 1].addr = hw_if.board_data->i2c_addr;
|
||||
msg[ii + 1].flags = I2C_M_RD;
|
||||
msg[ii + 1].len = I2C_BURST_LIMIT;
|
||||
msg[ii + 1].buf = &data[data_offset];
|
||||
data_offset += I2C_BURST_LIMIT;
|
||||
remaining_length -= I2C_BURST_LIMIT;
|
||||
}
|
||||
#endif
|
||||
|
||||
msg[rd_msgs].addr = hw_if.board_data->i2c_addr;
|
||||
msg[rd_msgs].flags = I2C_M_RD;
|
||||
msg[rd_msgs].len = remaining_length;
|
||||
msg[rd_msgs].buf = &data[data_offset];
|
||||
|
||||
buf = addr & MASK_8BIT;
|
||||
|
||||
remaining_msgs = rd_msgs + 1;
|
||||
|
||||
while (remaining_msgs) {
|
||||
#ifdef XFER_MSGS_LIMIT
|
||||
if (remaining_msgs > XFER_MSGS_LIMIT)
|
||||
xfer_msgs = XFER_MSGS_LIMIT;
|
||||
else
|
||||
xfer_msgs = remaining_msgs;
|
||||
#else
|
||||
xfer_msgs = remaining_msgs;
|
||||
#endif
|
||||
for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) {
|
||||
retval = i2c_transfer(adap, &msg[index], xfer_msgs);
|
||||
if (retval == xfer_msgs)
|
||||
break;
|
||||
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: I2C retry %d\n",
|
||||
__func__, retry + 1);
|
||||
msleep(20);
|
||||
|
||||
if (retry == SYN_I2C_RETRY_TIMES / 2) {
|
||||
synaptics_rmi4_i2c_check_addr(rmi4_data, i2c);
|
||||
i2c_addr = hw_if.board_data->i2c_addr;
|
||||
msg[0].addr = i2c_addr;
|
||||
#ifdef I2C_BURST_LIMIT
|
||||
for (ii = 0; ii < (rd_msgs - 1); ii++)
|
||||
msg[ii + 1].addr = i2c_addr;
|
||||
#endif
|
||||
msg[rd_msgs].addr = i2c_addr;
|
||||
}
|
||||
}
|
||||
|
||||
if (retry == SYN_I2C_RETRY_TIMES) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: I2C read over retry limit\n",
|
||||
__func__);
|
||||
retval = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
remaining_msgs -= xfer_msgs;
|
||||
index += xfer_msgs;
|
||||
}
|
||||
|
||||
retval = length;
|
||||
|
||||
exit:
|
||||
mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_i2c_write(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned short addr, unsigned char *data, unsigned short length)
|
||||
{
|
||||
int retval;
|
||||
unsigned char retry;
|
||||
struct i2c_client *i2c = to_i2c_client(rmi4_data->pdev->dev.parent);
|
||||
struct i2c_msg msg[1];
|
||||
|
||||
retval = synaptics_rmi4_i2c_alloc_buf(rmi4_data, length + 1);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
|
||||
retval = synaptics_rmi4_i2c_set_page(rmi4_data, addr);
|
||||
if (retval != PAGE_SELECT_LEN) {
|
||||
retval = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
msg[0].addr = hw_if.board_data->i2c_addr;
|
||||
msg[0].flags = 0;
|
||||
msg[0].len = length + 1;
|
||||
msg[0].buf = wr_buf;
|
||||
|
||||
wr_buf[0] = addr & MASK_8BIT;
|
||||
retval = secure_memcpy(&wr_buf[1], length, &data[0], length, length);
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to copy data\n",
|
||||
__func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
for (retry = 0; retry < SYN_I2C_RETRY_TIMES; retry++) {
|
||||
if (i2c_transfer(i2c->adapter, msg, 1) == 1) {
|
||||
retval = length;
|
||||
break;
|
||||
}
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: I2C retry %d\n",
|
||||
__func__, retry + 1);
|
||||
msleep(20);
|
||||
|
||||
if (retry == SYN_I2C_RETRY_TIMES / 2) {
|
||||
synaptics_rmi4_i2c_check_addr(rmi4_data, i2c);
|
||||
msg[0].addr = hw_if.board_data->i2c_addr;
|
||||
}
|
||||
}
|
||||
|
||||
if (retry == SYN_I2C_RETRY_TIMES) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: I2C write over retry limit\n",
|
||||
__func__);
|
||||
retval = -EIO;
|
||||
}
|
||||
|
||||
exit:
|
||||
mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct synaptics_dsx_bus_access bus_access = {
|
||||
.type = BUS_I2C,
|
||||
.read = synaptics_rmi4_i2c_read,
|
||||
.write = synaptics_rmi4_i2c_write,
|
||||
};
|
||||
|
||||
static void synaptics_rmi4_i2c_dev_release(struct device *dev)
|
||||
{
|
||||
kfree(synaptics_dsx_i2c_device);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *dev_id)
|
||||
{
|
||||
int retval;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
dev_err(&client->dev,
|
||||
"%s: SMBus byte data commands not supported by host\n",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
synaptics_dsx_i2c_device = kzalloc(
|
||||
sizeof(struct platform_device),
|
||||
GFP_KERNEL);
|
||||
if (!synaptics_dsx_i2c_device) {
|
||||
dev_err(&client->dev,
|
||||
"%s: Failed to allocate memory for synaptics_dsx_i2c_device\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
if (client->dev.of_node) {
|
||||
hw_if.board_data = devm_kzalloc(&client->dev,
|
||||
sizeof(struct synaptics_dsx_board_data),
|
||||
GFP_KERNEL);
|
||||
if (!hw_if.board_data) {
|
||||
dev_err(&client->dev,
|
||||
"%s: Failed to allocate memory for board data\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
hw_if.board_data->cap_button_map = devm_kzalloc(&client->dev,
|
||||
sizeof(struct synaptics_dsx_button_map),
|
||||
GFP_KERNEL);
|
||||
if (!hw_if.board_data->cap_button_map) {
|
||||
dev_err(&client->dev,
|
||||
"%s: Failed to allocate memory for 0D button map\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
hw_if.board_data->vir_button_map = devm_kzalloc(&client->dev,
|
||||
sizeof(struct synaptics_dsx_button_map),
|
||||
GFP_KERNEL);
|
||||
if (!hw_if.board_data->vir_button_map) {
|
||||
dev_err(&client->dev,
|
||||
"%s: Failed to allocate memory for virtual button map\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
parse_dt(&client->dev, hw_if.board_data);
|
||||
}
|
||||
#else
|
||||
hw_if.board_data = client->dev.platform_data;
|
||||
#endif
|
||||
|
||||
hw_if.bus_access = &bus_access;
|
||||
hw_if.board_data->i2c_addr = client->addr;
|
||||
|
||||
synaptics_dsx_i2c_device->name = PLATFORM_DRIVER_NAME;
|
||||
synaptics_dsx_i2c_device->id = 0;
|
||||
synaptics_dsx_i2c_device->num_resources = 0;
|
||||
synaptics_dsx_i2c_device->dev.parent = &client->dev;
|
||||
synaptics_dsx_i2c_device->dev.platform_data = &hw_if;
|
||||
synaptics_dsx_i2c_device->dev.release = synaptics_rmi4_i2c_dev_release;
|
||||
|
||||
retval = platform_device_register(synaptics_dsx_i2c_device);
|
||||
if (retval) {
|
||||
dev_err(&client->dev,
|
||||
"%s: Failed to register platform device\n",
|
||||
__func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
platform_device_unregister(synaptics_dsx_i2c_device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id synaptics_rmi4_id_table[] = {
|
||||
{I2C_DRIVER_NAME, 0},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static struct of_device_id synaptics_rmi4_of_match_table[] = {
|
||||
{
|
||||
.compatible = "synaptics,dsx-i2c",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table);
|
||||
#else
|
||||
#define synaptics_rmi4_of_match_table NULL
|
||||
#endif
|
||||
|
||||
static struct i2c_driver synaptics_rmi4_i2c_driver = {
|
||||
.driver = {
|
||||
.name = I2C_DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = synaptics_rmi4_of_match_table,
|
||||
},
|
||||
.probe = synaptics_rmi4_i2c_probe,
|
||||
.remove = synaptics_rmi4_i2c_remove,
|
||||
.id_table = synaptics_rmi4_id_table,
|
||||
};
|
||||
|
||||
int synaptics_rmi4_bus_init(void)
|
||||
{
|
||||
return i2c_add_driver(&synaptics_rmi4_i2c_driver);
|
||||
}
|
||||
EXPORT_SYMBOL(synaptics_rmi4_bus_init);
|
||||
|
||||
void synaptics_rmi4_bus_exit(void)
|
||||
{
|
||||
kfree(wr_buf);
|
||||
|
||||
i2c_del_driver(&synaptics_rmi4_i2c_driver);
|
||||
|
||||
return;
|
||||
}
|
||||
EXPORT_SYMBOL(synaptics_rmi4_bus_exit);
|
||||
|
||||
MODULE_AUTHOR("Synaptics, Inc.");
|
||||
MODULE_DESCRIPTION("Synaptics DSX I2C Bus Support Module");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,692 @@
|
|||
/*
|
||||
* Synaptics DSX touchscreen driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
|
||||
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
|
||||
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
|
||||
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
||||
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
|
||||
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
|
||||
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
|
||||
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
|
||||
* DOLLARS.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/input/synaptics_dsx.h>
|
||||
#include "synaptics_dsx_core.h"
|
||||
|
||||
#define PROX_PHYS_NAME "synaptics_dsx/proximity"
|
||||
|
||||
#define HOVER_Z_MAX (255)
|
||||
|
||||
#define HOVERING_FINGER_EN (1 << 4)
|
||||
|
||||
static ssize_t synaptics_rmi4_hover_finger_en_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf);
|
||||
|
||||
static ssize_t synaptics_rmi4_hover_finger_en_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count);
|
||||
|
||||
static struct device_attribute attrs[] = {
|
||||
__ATTR(hover_finger_en, (S_IRUGO | S_IWUGO),
|
||||
synaptics_rmi4_hover_finger_en_show,
|
||||
synaptics_rmi4_hover_finger_en_store),
|
||||
};
|
||||
|
||||
struct synaptics_rmi4_f12_query_5 {
|
||||
union {
|
||||
struct {
|
||||
unsigned char size_of_query6;
|
||||
struct {
|
||||
unsigned char ctrl0_is_present:1;
|
||||
unsigned char ctrl1_is_present:1;
|
||||
unsigned char ctrl2_is_present:1;
|
||||
unsigned char ctrl3_is_present:1;
|
||||
unsigned char ctrl4_is_present:1;
|
||||
unsigned char ctrl5_is_present:1;
|
||||
unsigned char ctrl6_is_present:1;
|
||||
unsigned char ctrl7_is_present:1;
|
||||
} __packed;
|
||||
struct {
|
||||
unsigned char ctrl8_is_present:1;
|
||||
unsigned char ctrl9_is_present:1;
|
||||
unsigned char ctrl10_is_present:1;
|
||||
unsigned char ctrl11_is_present:1;
|
||||
unsigned char ctrl12_is_present:1;
|
||||
unsigned char ctrl13_is_present:1;
|
||||
unsigned char ctrl14_is_present:1;
|
||||
unsigned char ctrl15_is_present:1;
|
||||
} __packed;
|
||||
struct {
|
||||
unsigned char ctrl16_is_present:1;
|
||||
unsigned char ctrl17_is_present:1;
|
||||
unsigned char ctrl18_is_present:1;
|
||||
unsigned char ctrl19_is_present:1;
|
||||
unsigned char ctrl20_is_present:1;
|
||||
unsigned char ctrl21_is_present:1;
|
||||
unsigned char ctrl22_is_present:1;
|
||||
unsigned char ctrl23_is_present:1;
|
||||
} __packed;
|
||||
};
|
||||
unsigned char data[4];
|
||||
};
|
||||
};
|
||||
|
||||
struct synaptics_rmi4_f12_query_8 {
|
||||
union {
|
||||
struct {
|
||||
unsigned char size_of_query9;
|
||||
struct {
|
||||
unsigned char data0_is_present:1;
|
||||
unsigned char data1_is_present:1;
|
||||
unsigned char data2_is_present:1;
|
||||
unsigned char data3_is_present:1;
|
||||
unsigned char data4_is_present:1;
|
||||
unsigned char data5_is_present:1;
|
||||
unsigned char data6_is_present:1;
|
||||
unsigned char data7_is_present:1;
|
||||
} __packed;
|
||||
};
|
||||
unsigned char data[2];
|
||||
};
|
||||
};
|
||||
|
||||
struct prox_finger_data {
|
||||
union {
|
||||
struct {
|
||||
unsigned char object_type_and_status;
|
||||
unsigned char x_lsb;
|
||||
unsigned char x_msb;
|
||||
unsigned char y_lsb;
|
||||
unsigned char y_msb;
|
||||
unsigned char z;
|
||||
} __packed;
|
||||
unsigned char proximity_data[6];
|
||||
};
|
||||
};
|
||||
|
||||
struct synaptics_rmi4_prox_handle {
|
||||
bool hover_finger_present;
|
||||
bool hover_finger_en;
|
||||
unsigned char intr_mask;
|
||||
unsigned short query_base_addr;
|
||||
unsigned short control_base_addr;
|
||||
unsigned short data_base_addr;
|
||||
unsigned short command_base_addr;
|
||||
unsigned short hover_finger_en_addr;
|
||||
unsigned short hover_finger_data_addr;
|
||||
struct input_dev *prox_dev;
|
||||
struct prox_finger_data *finger_data;
|
||||
struct synaptics_rmi4_data *rmi4_data;
|
||||
};
|
||||
|
||||
static struct synaptics_rmi4_prox_handle *prox;
|
||||
|
||||
DECLARE_COMPLETION(prox_remove_complete);
|
||||
|
||||
static void prox_hover_finger_lift(void)
|
||||
{
|
||||
input_report_key(prox->prox_dev, BTN_TOUCH, 0);
|
||||
input_report_key(prox->prox_dev, BTN_TOOL_FINGER, 0);
|
||||
input_sync(prox->prox_dev);
|
||||
prox->hover_finger_present = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void prox_hover_finger_report(void)
|
||||
{
|
||||
int retval;
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
struct prox_finger_data *data;
|
||||
struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data;
|
||||
|
||||
data = prox->finger_data;
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
prox->hover_finger_data_addr,
|
||||
data->proximity_data,
|
||||
sizeof(data->proximity_data));
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to read hovering finger data\n",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->object_type_and_status != F12_HOVERING_FINGER_STATUS) {
|
||||
if (prox->hover_finger_present)
|
||||
prox_hover_finger_lift();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
x = (data->x_msb << 8) | (data->x_lsb);
|
||||
y = (data->y_msb << 8) | (data->y_lsb);
|
||||
z = HOVER_Z_MAX - data->z;
|
||||
|
||||
input_report_key(prox->prox_dev, BTN_TOUCH, 0);
|
||||
input_report_key(prox->prox_dev, BTN_TOOL_FINGER, 1);
|
||||
input_report_abs(prox->prox_dev, ABS_X, x);
|
||||
input_report_abs(prox->prox_dev, ABS_Y, y);
|
||||
input_report_abs(prox->prox_dev, ABS_DISTANCE, z);
|
||||
|
||||
input_sync(prox->prox_dev);
|
||||
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: x = %d y = %d z = %d\n",
|
||||
__func__, x, y, z);
|
||||
|
||||
prox->hover_finger_present = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int prox_set_hover_finger_en(void)
|
||||
{
|
||||
int retval;
|
||||
unsigned char object_report_enable;
|
||||
struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data;
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
prox->hover_finger_en_addr,
|
||||
&object_report_enable,
|
||||
sizeof(object_report_enable));
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to read from object report enable register\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (prox->hover_finger_en)
|
||||
object_report_enable |= HOVERING_FINGER_EN;
|
||||
else
|
||||
object_report_enable &= ~HOVERING_FINGER_EN;
|
||||
|
||||
retval = synaptics_rmi4_reg_write(rmi4_data,
|
||||
prox->hover_finger_en_addr,
|
||||
&object_report_enable,
|
||||
sizeof(object_report_enable));
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to write to object report enable register\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void prox_set_params(void)
|
||||
{
|
||||
input_set_abs_params(prox->prox_dev, ABS_X, 0,
|
||||
prox->rmi4_data->sensor_max_x, 0, 0);
|
||||
input_set_abs_params(prox->prox_dev, ABS_Y, 0,
|
||||
prox->rmi4_data->sensor_max_y, 0, 0);
|
||||
input_set_abs_params(prox->prox_dev, ABS_DISTANCE, 0,
|
||||
HOVER_Z_MAX, 0, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int prox_reg_init(void)
|
||||
{
|
||||
int retval;
|
||||
unsigned char ctrl_23_offset;
|
||||
unsigned char data_1_offset;
|
||||
struct synaptics_rmi4_f12_query_5 query_5;
|
||||
struct synaptics_rmi4_f12_query_8 query_8;
|
||||
struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data;
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
prox->query_base_addr + 5,
|
||||
query_5.data,
|
||||
sizeof(query_5.data));
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
ctrl_23_offset = query_5.ctrl0_is_present +
|
||||
query_5.ctrl1_is_present +
|
||||
query_5.ctrl2_is_present +
|
||||
query_5.ctrl3_is_present +
|
||||
query_5.ctrl4_is_present +
|
||||
query_5.ctrl5_is_present +
|
||||
query_5.ctrl6_is_present +
|
||||
query_5.ctrl7_is_present +
|
||||
query_5.ctrl8_is_present +
|
||||
query_5.ctrl9_is_present +
|
||||
query_5.ctrl10_is_present +
|
||||
query_5.ctrl11_is_present +
|
||||
query_5.ctrl12_is_present +
|
||||
query_5.ctrl13_is_present +
|
||||
query_5.ctrl14_is_present +
|
||||
query_5.ctrl15_is_present +
|
||||
query_5.ctrl16_is_present +
|
||||
query_5.ctrl17_is_present +
|
||||
query_5.ctrl18_is_present +
|
||||
query_5.ctrl19_is_present +
|
||||
query_5.ctrl20_is_present +
|
||||
query_5.ctrl21_is_present +
|
||||
query_5.ctrl22_is_present;
|
||||
|
||||
prox->hover_finger_en_addr = prox->control_base_addr + ctrl_23_offset;
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
prox->query_base_addr + 8,
|
||||
query_8.data,
|
||||
sizeof(query_8.data));
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
data_1_offset = query_8.data0_is_present;
|
||||
prox->hover_finger_data_addr = prox->data_base_addr + data_1_offset;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int prox_scan_pdt(void)
|
||||
{
|
||||
int retval;
|
||||
unsigned char ii;
|
||||
unsigned char page;
|
||||
unsigned char intr_count = 0;
|
||||
unsigned char intr_off;
|
||||
unsigned char intr_src;
|
||||
unsigned short addr;
|
||||
struct synaptics_rmi4_fn_desc fd;
|
||||
struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data;
|
||||
|
||||
for (page = 0; page < PAGES_TO_SERVICE; page++) {
|
||||
for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) {
|
||||
addr |= (page << 8);
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
addr,
|
||||
(unsigned char *)&fd,
|
||||
sizeof(fd));
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
addr &= ~(MASK_8BIT << 8);
|
||||
|
||||
if (fd.fn_number) {
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: Found F%02x\n",
|
||||
__func__, fd.fn_number);
|
||||
switch (fd.fn_number) {
|
||||
case SYNAPTICS_RMI4_F12:
|
||||
goto f12_found;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
intr_count += fd.intr_src_count;
|
||||
}
|
||||
}
|
||||
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to find F12\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
|
||||
f12_found:
|
||||
prox->query_base_addr = fd.query_base_addr | (page << 8);
|
||||
prox->control_base_addr = fd.ctrl_base_addr | (page << 8);
|
||||
prox->data_base_addr = fd.data_base_addr | (page << 8);
|
||||
prox->command_base_addr = fd.cmd_base_addr | (page << 8);
|
||||
|
||||
retval = prox_reg_init();
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to initialize proximity registers\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
prox->intr_mask = 0;
|
||||
intr_src = fd.intr_src_count;
|
||||
intr_off = intr_count % 8;
|
||||
for (ii = intr_off;
|
||||
ii < (intr_src + intr_off);
|
||||
ii++) {
|
||||
prox->intr_mask |= 1 << ii;
|
||||
}
|
||||
|
||||
rmi4_data->intr_mask[0] |= prox->intr_mask;
|
||||
|
||||
addr = rmi4_data->f01_ctrl_base_addr + 1;
|
||||
|
||||
retval = synaptics_rmi4_reg_write(rmi4_data,
|
||||
addr,
|
||||
&(rmi4_data->intr_mask[0]),
|
||||
sizeof(rmi4_data->intr_mask[0]));
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to set interrupt enable bit\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t synaptics_rmi4_hover_finger_en_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
if (!prox)
|
||||
return -ENODEV;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
prox->hover_finger_en);
|
||||
}
|
||||
|
||||
static ssize_t synaptics_rmi4_hover_finger_en_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int retval;
|
||||
unsigned int input;
|
||||
struct synaptics_rmi4_data *rmi4_data = prox->rmi4_data;
|
||||
|
||||
if (!prox)
|
||||
return -ENODEV;
|
||||
|
||||
if (sscanf(buf, "%x", &input) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (input == 1)
|
||||
prox->hover_finger_en = true;
|
||||
else if (input == 0)
|
||||
prox->hover_finger_en = false;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
retval = prox_set_hover_finger_en();
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to change hovering finger enable setting\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int synaptics_rmi4_prox_hover_finger_en(bool enable)
|
||||
{
|
||||
int retval;
|
||||
|
||||
if (!prox)
|
||||
return -ENODEV;
|
||||
|
||||
prox->hover_finger_en = enable;
|
||||
|
||||
retval = prox_set_hover_finger_en();
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(synaptics_rmi4_prox_hover_finger_en);
|
||||
|
||||
static void synaptics_rmi4_prox_attn(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned char intr_mask)
|
||||
{
|
||||
if (!prox)
|
||||
return;
|
||||
|
||||
if (prox->intr_mask & intr_mask)
|
||||
prox_hover_finger_report();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_prox_init(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
int retval;
|
||||
unsigned char attr_count;
|
||||
|
||||
if (prox) {
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: Handle already exists\n",
|
||||
__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
prox = kzalloc(sizeof(*prox), GFP_KERNEL);
|
||||
if (!prox) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to alloc mem for prox\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
prox->finger_data = kzalloc(sizeof(*(prox->finger_data)), GFP_KERNEL);
|
||||
if (!prox->finger_data) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to alloc mem for finger_data\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit_free_prox;
|
||||
}
|
||||
|
||||
prox->rmi4_data = rmi4_data;
|
||||
|
||||
retval = prox_scan_pdt();
|
||||
if (retval < 0)
|
||||
goto exit_free_finger_data;
|
||||
|
||||
prox->hover_finger_en = true;
|
||||
|
||||
retval = prox_set_hover_finger_en();
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
prox->prox_dev = input_allocate_device();
|
||||
if (prox->prox_dev == NULL) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to allocate proximity device\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit_free_finger_data;
|
||||
}
|
||||
|
||||
prox->prox_dev->name = PROXIMITY_DRIVER_NAME;
|
||||
prox->prox_dev->phys = PROX_PHYS_NAME;
|
||||
prox->prox_dev->id.product = SYNAPTICS_DSX_DRIVER_PRODUCT;
|
||||
prox->prox_dev->id.version = SYNAPTICS_DSX_DRIVER_VERSION;
|
||||
prox->prox_dev->dev.parent = rmi4_data->pdev->dev.parent;
|
||||
input_set_drvdata(prox->prox_dev, rmi4_data);
|
||||
|
||||
set_bit(EV_KEY, prox->prox_dev->evbit);
|
||||
set_bit(EV_ABS, prox->prox_dev->evbit);
|
||||
set_bit(BTN_TOUCH, prox->prox_dev->keybit);
|
||||
set_bit(BTN_TOOL_FINGER, prox->prox_dev->keybit);
|
||||
#ifdef INPUT_PROP_DIRECT
|
||||
set_bit(INPUT_PROP_DIRECT, prox->prox_dev->propbit);
|
||||
#endif
|
||||
|
||||
prox_set_params();
|
||||
|
||||
retval = input_register_device(prox->prox_dev);
|
||||
if (retval) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to register proximity device\n",
|
||||
__func__);
|
||||
goto exit_free_input_device;
|
||||
}
|
||||
|
||||
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
|
||||
retval = sysfs_create_file(&rmi4_data->input_dev->dev.kobj,
|
||||
&attrs[attr_count].attr);
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to create sysfs attributes\n",
|
||||
__func__);
|
||||
goto exit_free_sysfs;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_free_sysfs:
|
||||
for (attr_count--; attr_count >= 0; attr_count--) {
|
||||
sysfs_remove_file(&rmi4_data->input_dev->dev.kobj,
|
||||
&attrs[attr_count].attr);
|
||||
}
|
||||
|
||||
input_unregister_device(prox->prox_dev);
|
||||
prox->prox_dev = NULL;
|
||||
|
||||
exit_free_input_device:
|
||||
if (prox->prox_dev)
|
||||
input_free_device(prox->prox_dev);
|
||||
|
||||
exit_free_finger_data:
|
||||
kfree(prox->finger_data);
|
||||
|
||||
exit_free_prox:
|
||||
kfree(prox);
|
||||
prox = NULL;
|
||||
|
||||
exit:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_prox_remove(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
unsigned char attr_count;
|
||||
|
||||
if (!prox)
|
||||
goto exit;
|
||||
|
||||
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
|
||||
sysfs_remove_file(&rmi4_data->input_dev->dev.kobj,
|
||||
&attrs[attr_count].attr);
|
||||
}
|
||||
|
||||
input_unregister_device(prox->prox_dev);
|
||||
kfree(prox->finger_data);
|
||||
kfree(prox);
|
||||
prox = NULL;
|
||||
|
||||
exit:
|
||||
complete(&prox_remove_complete);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_prox_reset(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!prox) {
|
||||
synaptics_rmi4_prox_init(rmi4_data);
|
||||
return;
|
||||
}
|
||||
|
||||
prox_hover_finger_lift();
|
||||
|
||||
prox_scan_pdt();
|
||||
|
||||
prox_set_hover_finger_en();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_prox_reinit(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!prox)
|
||||
return;
|
||||
|
||||
prox_hover_finger_lift();
|
||||
|
||||
prox_set_hover_finger_en();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_prox_e_suspend(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!prox)
|
||||
return;
|
||||
|
||||
prox_hover_finger_lift();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_prox_suspend(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!prox)
|
||||
return;
|
||||
|
||||
prox_hover_finger_lift();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static struct synaptics_rmi4_exp_fn proximity_module = {
|
||||
.fn_type = RMI_PROXIMITY,
|
||||
.init = synaptics_rmi4_prox_init,
|
||||
.remove = synaptics_rmi4_prox_remove,
|
||||
.reset = synaptics_rmi4_prox_reset,
|
||||
.reinit = synaptics_rmi4_prox_reinit,
|
||||
.early_suspend = synaptics_rmi4_prox_e_suspend,
|
||||
.suspend = synaptics_rmi4_prox_suspend,
|
||||
.resume = NULL,
|
||||
.late_resume = NULL,
|
||||
.attn = synaptics_rmi4_prox_attn,
|
||||
};
|
||||
|
||||
static int __init rmi4_proximity_module_init(void)
|
||||
{
|
||||
synaptics_rmi4_new_function(&proximity_module, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rmi4_proximity_module_exit(void)
|
||||
{
|
||||
synaptics_rmi4_new_function(&proximity_module, false);
|
||||
|
||||
wait_for_completion(&prox_remove_complete);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
module_init(rmi4_proximity_module_init);
|
||||
module_exit(rmi4_proximity_module_exit);
|
||||
|
||||
MODULE_AUTHOR("Synaptics, Inc.");
|
||||
MODULE_DESCRIPTION("Synaptics DSX Proximity Module");
|
||||
MODULE_LICENSE("GPL v2");
|
1058
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_dev.c
Normal file
1058
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_rmi_dev.c
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
634
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_spi.c
Normal file
634
drivers/input/touchscreen/synaptics_dsx_2.6/synaptics_dsx_spi.c
Normal file
|
@ -0,0 +1,634 @@
|
|||
/*
|
||||
* Synaptics DSX touchscreen driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
|
||||
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
|
||||
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
|
||||
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
||||
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
|
||||
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
|
||||
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
|
||||
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
|
||||
* DOLLARS.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/input/synaptics_dsx.h>
|
||||
#include "synaptics_dsx_core.h"
|
||||
|
||||
#define SPI_READ 0x80
|
||||
#define SPI_WRITE 0x00
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata)
|
||||
{
|
||||
int retval;
|
||||
u32 value;
|
||||
const char *name;
|
||||
struct property *prop;
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
bdata->irq_gpio = of_get_named_gpio_flags(np,
|
||||
"synaptics,irq-gpio", 0,
|
||||
(enum of_gpio_flags *)&bdata->irq_flags);
|
||||
|
||||
retval = of_property_read_u32(np, "synaptics,irq-on-state",
|
||||
&value);
|
||||
if (retval < 0)
|
||||
bdata->irq_on_state = 0;
|
||||
else
|
||||
bdata->irq_on_state = value;
|
||||
|
||||
retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name);
|
||||
if (retval < 0)
|
||||
bdata->pwr_reg_name = NULL;
|
||||
else
|
||||
bdata->pwr_reg_name = name;
|
||||
|
||||
retval = of_property_read_string(np, "synaptics,bus-reg-name", &name);
|
||||
if (retval < 0)
|
||||
bdata->bus_reg_name = NULL;
|
||||
else
|
||||
bdata->bus_reg_name = name;
|
||||
|
||||
prop = of_find_property(np, "synaptics,power-gpio", NULL);
|
||||
if (prop && prop->length) {
|
||||
bdata->power_gpio = of_get_named_gpio_flags(np,
|
||||
"synaptics,power-gpio", 0, NULL);
|
||||
retval = of_property_read_u32(np, "synaptics,power-on-state",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,power-on-state property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->power_on_state = value;
|
||||
}
|
||||
} else {
|
||||
bdata->power_gpio = -1;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,power-delay-ms", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,power-delay-ms",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,power-delay-ms property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->power_delay_ms = value;
|
||||
}
|
||||
} else {
|
||||
bdata->power_delay_ms = 0;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,reset-gpio", NULL);
|
||||
if (prop && prop->length) {
|
||||
bdata->reset_gpio = of_get_named_gpio_flags(np,
|
||||
"synaptics,reset-gpio", 0, NULL);
|
||||
retval = of_property_read_u32(np, "synaptics,reset-on-state",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,reset-on-state property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->reset_on_state = value;
|
||||
}
|
||||
retval = of_property_read_u32(np, "synaptics,reset-active-ms",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,reset-active-ms property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->reset_active_ms = value;
|
||||
}
|
||||
} else {
|
||||
bdata->reset_gpio = -1;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,reset-delay-ms", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,reset-delay-ms",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,reset-delay-ms property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->reset_delay_ms = value;
|
||||
}
|
||||
} else {
|
||||
bdata->reset_delay_ms = 0;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,byte-delay-us", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,byte-delay-us",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,byte-delay-us property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->byte_delay_us = value;
|
||||
}
|
||||
} else {
|
||||
bdata->byte_delay_us = 0;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,block-delay-us", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,block-delay-us",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,block-delay-us property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->block_delay_us = value;
|
||||
}
|
||||
} else {
|
||||
bdata->block_delay_us = 0;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,max-y-for-2d", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,max-y-for-2d",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,max-y-for-2d property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->max_y_for_2d = value;
|
||||
}
|
||||
} else {
|
||||
bdata->max_y_for_2d = -1;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,swap-axes", NULL);
|
||||
bdata->swap_axes = prop > 0 ? true : false;
|
||||
|
||||
prop = of_find_property(np, "synaptics,x-flip", NULL);
|
||||
bdata->x_flip = prop > 0 ? true : false;
|
||||
|
||||
prop = of_find_property(np, "synaptics,y-flip", NULL);
|
||||
bdata->y_flip = prop > 0 ? true : false;
|
||||
|
||||
prop = of_find_property(np, "synaptics,ub-i2c-addr", NULL);
|
||||
if (prop && prop->length) {
|
||||
retval = of_property_read_u32(np, "synaptics,ub-i2c-addr",
|
||||
&value);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "%s: Unable to read synaptics,ub-i2c-addr property\n",
|
||||
__func__);
|
||||
return retval;
|
||||
} else {
|
||||
bdata->ub_i2c_addr = (unsigned short)value;
|
||||
}
|
||||
} else {
|
||||
bdata->ub_i2c_addr = -1;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,cap-button-codes", NULL);
|
||||
if (prop && prop->length) {
|
||||
bdata->cap_button_map->map = devm_kzalloc(dev,
|
||||
prop->length,
|
||||
GFP_KERNEL);
|
||||
if (!bdata->cap_button_map->map)
|
||||
return -ENOMEM;
|
||||
bdata->cap_button_map->nbuttons = prop->length / sizeof(u32);
|
||||
retval = of_property_read_u32_array(np,
|
||||
"synaptics,cap-button-codes",
|
||||
bdata->cap_button_map->map,
|
||||
bdata->cap_button_map->nbuttons);
|
||||
if (retval < 0) {
|
||||
bdata->cap_button_map->nbuttons = 0;
|
||||
bdata->cap_button_map->map = NULL;
|
||||
}
|
||||
} else {
|
||||
bdata->cap_button_map->nbuttons = 0;
|
||||
bdata->cap_button_map->map = NULL;
|
||||
}
|
||||
|
||||
prop = of_find_property(np, "synaptics,vir-button-codes", NULL);
|
||||
if (prop && prop->length) {
|
||||
bdata->vir_button_map->map = devm_kzalloc(dev,
|
||||
prop->length,
|
||||
GFP_KERNEL);
|
||||
if (!bdata->vir_button_map->map)
|
||||
return -ENOMEM;
|
||||
bdata->vir_button_map->nbuttons = prop->length / sizeof(u32);
|
||||
bdata->vir_button_map->nbuttons /= 5;
|
||||
retval = of_property_read_u32_array(np,
|
||||
"synaptics,vir-button-codes",
|
||||
bdata->vir_button_map->map,
|
||||
bdata->vir_button_map->nbuttons * 5);
|
||||
if (retval < 0) {
|
||||
bdata->vir_button_map->nbuttons = 0;
|
||||
bdata->vir_button_map->map = NULL;
|
||||
}
|
||||
} else {
|
||||
bdata->vir_button_map->nbuttons = 0;
|
||||
bdata->vir_button_map->map = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int synaptics_rmi4_spi_set_page(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned short addr)
|
||||
{
|
||||
int retval;
|
||||
unsigned int index;
|
||||
unsigned int xfer_count = PAGE_SELECT_LEN + 1;
|
||||
unsigned char txbuf[xfer_count];
|
||||
unsigned char page;
|
||||
struct spi_message msg;
|
||||
struct spi_transfer xfers[xfer_count];
|
||||
struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent);
|
||||
const struct synaptics_dsx_board_data *bdata =
|
||||
rmi4_data->hw_if->board_data;
|
||||
|
||||
page = ((addr >> 8) & ~MASK_7BIT);
|
||||
if (page != rmi4_data->current_page) {
|
||||
spi_message_init(&msg);
|
||||
|
||||
txbuf[0] = SPI_WRITE;
|
||||
txbuf[1] = MASK_8BIT;
|
||||
txbuf[2] = page;
|
||||
|
||||
for (index = 0; index < xfer_count; index++) {
|
||||
memset(&xfers[index], 0, sizeof(struct spi_transfer));
|
||||
xfers[index].len = 1;
|
||||
xfers[index].delay_usecs = bdata->byte_delay_us;
|
||||
xfers[index].tx_buf = &txbuf[index];
|
||||
spi_message_add_tail(&xfers[index], &msg);
|
||||
}
|
||||
|
||||
if (bdata->block_delay_us)
|
||||
xfers[index - 1].delay_usecs = bdata->block_delay_us;
|
||||
|
||||
retval = spi_sync(spi, &msg);
|
||||
if (retval == 0) {
|
||||
rmi4_data->current_page = page;
|
||||
retval = PAGE_SELECT_LEN;
|
||||
} else {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to complete SPI transfer, error = %d\n",
|
||||
__func__, retval);
|
||||
}
|
||||
} else {
|
||||
retval = PAGE_SELECT_LEN;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_spi_read(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned short addr, unsigned char *data, unsigned short length)
|
||||
{
|
||||
int retval;
|
||||
unsigned int index;
|
||||
unsigned int xfer_count = length + ADDRESS_WORD_LEN;
|
||||
unsigned char txbuf[ADDRESS_WORD_LEN];
|
||||
unsigned char *rxbuf = NULL;
|
||||
struct spi_message msg;
|
||||
struct spi_transfer *xfers = NULL;
|
||||
struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent);
|
||||
const struct synaptics_dsx_board_data *bdata =
|
||||
rmi4_data->hw_if->board_data;
|
||||
|
||||
spi_message_init(&msg);
|
||||
|
||||
xfers = kcalloc(xfer_count, sizeof(struct spi_transfer), GFP_KERNEL);
|
||||
if (!xfers) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to allocate memory for xfers\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
txbuf[0] = (addr >> 8) | SPI_READ;
|
||||
txbuf[1] = addr & MASK_8BIT;
|
||||
|
||||
rxbuf = kmalloc(length, GFP_KERNEL);
|
||||
if (!rxbuf) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to allocate memory for rxbuf\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
|
||||
retval = synaptics_rmi4_spi_set_page(rmi4_data, addr);
|
||||
if (retval != PAGE_SELECT_LEN) {
|
||||
mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
retval = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
for (index = 0; index < xfer_count; index++) {
|
||||
xfers[index].len = 1;
|
||||
xfers[index].delay_usecs = bdata->byte_delay_us;
|
||||
if (index < ADDRESS_WORD_LEN)
|
||||
xfers[index].tx_buf = &txbuf[index];
|
||||
else
|
||||
xfers[index].rx_buf = &rxbuf[index - ADDRESS_WORD_LEN];
|
||||
spi_message_add_tail(&xfers[index], &msg);
|
||||
}
|
||||
|
||||
if (bdata->block_delay_us)
|
||||
xfers[index - 1].delay_usecs = bdata->block_delay_us;
|
||||
|
||||
retval = spi_sync(spi, &msg);
|
||||
if (retval == 0) {
|
||||
retval = secure_memcpy(data, length, rxbuf, length, length);
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to copy data\n",
|
||||
__func__);
|
||||
} else {
|
||||
retval = length;
|
||||
}
|
||||
} else {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to complete SPI transfer, error = %d\n",
|
||||
__func__, retval);
|
||||
}
|
||||
|
||||
mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
|
||||
exit:
|
||||
kfree(rxbuf);
|
||||
kfree(xfers);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_spi_write(struct synaptics_rmi4_data *rmi4_data,
|
||||
unsigned short addr, unsigned char *data, unsigned short length)
|
||||
{
|
||||
int retval;
|
||||
unsigned int index;
|
||||
unsigned int xfer_count = length + ADDRESS_WORD_LEN;
|
||||
unsigned char *txbuf = NULL;
|
||||
struct spi_message msg;
|
||||
struct spi_transfer *xfers = NULL;
|
||||
struct spi_device *spi = to_spi_device(rmi4_data->pdev->dev.parent);
|
||||
const struct synaptics_dsx_board_data *bdata =
|
||||
rmi4_data->hw_if->board_data;
|
||||
|
||||
spi_message_init(&msg);
|
||||
|
||||
xfers = kcalloc(xfer_count, sizeof(struct spi_transfer), GFP_KERNEL);
|
||||
if (!xfers) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to allocate memory for xfers\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
txbuf = kmalloc(xfer_count, GFP_KERNEL);
|
||||
if (!txbuf) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to allocate memory for txbuf\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
txbuf[0] = (addr >> 8) & ~SPI_READ;
|
||||
txbuf[1] = addr & MASK_8BIT;
|
||||
retval = secure_memcpy(&txbuf[ADDRESS_WORD_LEN],
|
||||
xfer_count - ADDRESS_WORD_LEN, data, length, length);
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to copy data\n",
|
||||
__func__);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mutex_lock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
|
||||
retval = synaptics_rmi4_spi_set_page(rmi4_data, addr);
|
||||
if (retval != PAGE_SELECT_LEN) {
|
||||
mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
retval = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
for (index = 0; index < xfer_count; index++) {
|
||||
xfers[index].len = 1;
|
||||
xfers[index].delay_usecs = bdata->byte_delay_us;
|
||||
xfers[index].tx_buf = &txbuf[index];
|
||||
spi_message_add_tail(&xfers[index], &msg);
|
||||
}
|
||||
|
||||
if (bdata->block_delay_us)
|
||||
xfers[index - 1].delay_usecs = bdata->block_delay_us;
|
||||
|
||||
retval = spi_sync(spi, &msg);
|
||||
if (retval == 0) {
|
||||
retval = length;
|
||||
} else {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to complete SPI transfer, error = %d\n",
|
||||
__func__, retval);
|
||||
}
|
||||
|
||||
mutex_unlock(&rmi4_data->rmi4_io_ctrl_mutex);
|
||||
|
||||
exit:
|
||||
kfree(txbuf);
|
||||
kfree(xfers);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct synaptics_dsx_bus_access bus_access = {
|
||||
.type = BUS_SPI,
|
||||
.read = synaptics_rmi4_spi_read,
|
||||
.write = synaptics_rmi4_spi_write,
|
||||
};
|
||||
|
||||
static struct synaptics_dsx_hw_interface hw_if;
|
||||
|
||||
static struct platform_device *synaptics_dsx_spi_device;
|
||||
|
||||
static void synaptics_rmi4_spi_dev_release(struct device *dev)
|
||||
{
|
||||
kfree(synaptics_dsx_spi_device);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
int retval;
|
||||
|
||||
if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
|
||||
dev_err(&spi->dev,
|
||||
"%s: Full duplex not supported by host\n",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
synaptics_dsx_spi_device = kzalloc(
|
||||
sizeof(struct platform_device),
|
||||
GFP_KERNEL);
|
||||
if (!synaptics_dsx_spi_device) {
|
||||
dev_err(&spi->dev,
|
||||
"%s: Failed to allocate memory for synaptics_dsx_spi_device\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
if (spi->dev.of_node) {
|
||||
hw_if.board_data = devm_kzalloc(&spi->dev,
|
||||
sizeof(struct synaptics_dsx_board_data),
|
||||
GFP_KERNEL);
|
||||
if (!hw_if.board_data) {
|
||||
dev_err(&spi->dev,
|
||||
"%s: Failed to allocate memory for board data\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
hw_if.board_data->cap_button_map = devm_kzalloc(&spi->dev,
|
||||
sizeof(struct synaptics_dsx_button_map),
|
||||
GFP_KERNEL);
|
||||
if (!hw_if.board_data->cap_button_map) {
|
||||
dev_err(&spi->dev,
|
||||
"%s: Failed to allocate memory for 0D button map\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
hw_if.board_data->vir_button_map = devm_kzalloc(&spi->dev,
|
||||
sizeof(struct synaptics_dsx_button_map),
|
||||
GFP_KERNEL);
|
||||
if (!hw_if.board_data->vir_button_map) {
|
||||
dev_err(&spi->dev,
|
||||
"%s: Failed to allocate memory for virtual button map\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
parse_dt(&spi->dev, hw_if.board_data);
|
||||
}
|
||||
#else
|
||||
hw_if.board_data = spi->dev.platform_data;
|
||||
#endif
|
||||
|
||||
hw_if.bus_access = &bus_access;
|
||||
|
||||
spi->bits_per_word = 8;
|
||||
spi->mode = SPI_MODE_3;
|
||||
|
||||
retval = spi_setup(spi);
|
||||
if (retval < 0) {
|
||||
dev_err(&spi->dev,
|
||||
"%s: Failed to perform SPI setup\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
synaptics_dsx_spi_device->name = PLATFORM_DRIVER_NAME;
|
||||
synaptics_dsx_spi_device->id = 0;
|
||||
synaptics_dsx_spi_device->num_resources = 0;
|
||||
synaptics_dsx_spi_device->dev.parent = &spi->dev;
|
||||
synaptics_dsx_spi_device->dev.platform_data = &hw_if;
|
||||
synaptics_dsx_spi_device->dev.release = synaptics_rmi4_spi_dev_release;
|
||||
|
||||
retval = platform_device_register(synaptics_dsx_spi_device);
|
||||
if (retval) {
|
||||
dev_err(&spi->dev,
|
||||
"%s: Failed to register platform device\n",
|
||||
__func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
platform_device_unregister(synaptics_dsx_spi_device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static struct of_device_id synaptics_rmi4_of_match_table[] = {
|
||||
{
|
||||
.compatible = "synaptics,dsx-spi",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table);
|
||||
#else
|
||||
#define synaptics_rmi4_of_match_table NULL
|
||||
#endif
|
||||
|
||||
static struct spi_driver synaptics_rmi4_spi_driver = {
|
||||
.driver = {
|
||||
.name = SPI_DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = synaptics_rmi4_of_match_table,
|
||||
},
|
||||
.probe = synaptics_rmi4_spi_probe,
|
||||
.remove = synaptics_rmi4_spi_remove,
|
||||
};
|
||||
|
||||
|
||||
int synaptics_rmi4_bus_init(void)
|
||||
{
|
||||
return spi_register_driver(&synaptics_rmi4_spi_driver);
|
||||
}
|
||||
EXPORT_SYMBOL(synaptics_rmi4_bus_init);
|
||||
|
||||
void synaptics_rmi4_bus_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&synaptics_rmi4_spi_driver);
|
||||
|
||||
return;
|
||||
}
|
||||
EXPORT_SYMBOL(synaptics_rmi4_bus_exit);
|
||||
|
||||
MODULE_AUTHOR("Synaptics, Inc.");
|
||||
MODULE_DESCRIPTION("Synaptics DSX SPI Bus Support Module");
|
||||
MODULE_LICENSE("GPL v2");
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
* Synaptics DSX touchscreen driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
|
||||
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
|
||||
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
|
||||
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
||||
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
|
||||
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
|
||||
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
|
||||
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
|
||||
* DOLLARS.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/input/synaptics_dsx.h>
|
||||
#include "synaptics_dsx_core.h"
|
||||
|
||||
#define SYSFS_FOLDER_NAME "video"
|
||||
|
||||
/*
|
||||
#define RMI_DCS_SUSPEND_RESUME
|
||||
*/
|
||||
|
||||
static ssize_t video_sysfs_dcs_write_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count);
|
||||
|
||||
static ssize_t video_sysfs_param_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count);
|
||||
|
||||
static int video_send_dcs_command(unsigned char command_opcode);
|
||||
|
||||
struct f38_command {
|
||||
union {
|
||||
struct {
|
||||
unsigned char command_opcode;
|
||||
unsigned char register_access:1;
|
||||
unsigned char gamma_page:1;
|
||||
unsigned char f38_control1_b2__7:6;
|
||||
unsigned char parameter_field_1;
|
||||
unsigned char parameter_field_2;
|
||||
unsigned char parameter_field_3;
|
||||
unsigned char parameter_field_4;
|
||||
unsigned char send_to_dcs:1;
|
||||
unsigned char f38_command6_b1__7:7;
|
||||
} __packed;
|
||||
unsigned char data[7];
|
||||
};
|
||||
};
|
||||
|
||||
struct synaptics_rmi4_video_handle {
|
||||
unsigned char param;
|
||||
unsigned short query_base_addr;
|
||||
unsigned short control_base_addr;
|
||||
unsigned short data_base_addr;
|
||||
unsigned short command_base_addr;
|
||||
struct synaptics_rmi4_data *rmi4_data;
|
||||
struct kobject *sysfs_dir;
|
||||
};
|
||||
|
||||
#ifdef RMI_DCS_SUSPEND_RESUME
|
||||
struct dcs_command {
|
||||
unsigned char command;
|
||||
unsigned int wait_time;
|
||||
};
|
||||
|
||||
static struct dcs_command suspend_sequence[] = {
|
||||
{
|
||||
.command = 0x28,
|
||||
.wait_time = 200,
|
||||
},
|
||||
{
|
||||
.command = 0x10,
|
||||
.wait_time = 200,
|
||||
},
|
||||
};
|
||||
|
||||
static struct dcs_command resume_sequence[] = {
|
||||
{
|
||||
.command = 0x11,
|
||||
.wait_time = 200,
|
||||
},
|
||||
{
|
||||
.command = 0x29,
|
||||
.wait_time = 200,
|
||||
},
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct device_attribute attrs[] = {
|
||||
__ATTR(dcs_write, S_IWUGO,
|
||||
synaptics_rmi4_show_error,
|
||||
video_sysfs_dcs_write_store),
|
||||
__ATTR(param, S_IWUGO,
|
||||
synaptics_rmi4_show_error,
|
||||
video_sysfs_param_store),
|
||||
};
|
||||
|
||||
static struct synaptics_rmi4_video_handle *video;
|
||||
|
||||
DECLARE_COMPLETION(video_remove_complete);
|
||||
|
||||
static ssize_t video_sysfs_dcs_write_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int retval;
|
||||
unsigned int input;
|
||||
|
||||
if (sscanf(buf, "%x", &input) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
retval = video_send_dcs_command((unsigned char)input);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t video_sysfs_param_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
unsigned int input;
|
||||
|
||||
if (sscanf(buf, "%x", &input) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
video->param = (unsigned char)input;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int video_send_dcs_command(unsigned char command_opcode)
|
||||
{
|
||||
int retval;
|
||||
struct f38_command command;
|
||||
struct synaptics_rmi4_data *rmi4_data = video->rmi4_data;
|
||||
|
||||
memset(&command, 0x00, sizeof(command));
|
||||
|
||||
command.command_opcode = command_opcode;
|
||||
command.parameter_field_1 = video->param;
|
||||
command.send_to_dcs = 1;
|
||||
|
||||
video->param = 0;
|
||||
|
||||
retval = synaptics_rmi4_reg_write(rmi4_data,
|
||||
video->command_base_addr,
|
||||
command.data,
|
||||
sizeof(command.data));
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to send DCS command\n",
|
||||
__func__);
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int video_scan_pdt(void)
|
||||
{
|
||||
int retval;
|
||||
unsigned char page;
|
||||
unsigned short addr;
|
||||
bool f38_found = false;
|
||||
struct synaptics_rmi4_fn_desc rmi_fd;
|
||||
struct synaptics_rmi4_data *rmi4_data = video->rmi4_data;
|
||||
|
||||
for (page = 0; page < PAGES_TO_SERVICE; page++) {
|
||||
for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) {
|
||||
addr |= (page << 8);
|
||||
|
||||
retval = synaptics_rmi4_reg_read(rmi4_data,
|
||||
addr,
|
||||
(unsigned char *)&rmi_fd,
|
||||
sizeof(rmi_fd));
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
addr &= ~(MASK_8BIT << 8);
|
||||
|
||||
if (!rmi_fd.fn_number)
|
||||
break;
|
||||
|
||||
if (rmi_fd.fn_number == SYNAPTICS_RMI4_F38) {
|
||||
f38_found = true;
|
||||
goto f38_found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!f38_found) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to find F38\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
f38_found:
|
||||
video->query_base_addr = rmi_fd.query_base_addr | (page << 8);
|
||||
video->control_base_addr = rmi_fd.ctrl_base_addr | (page << 8);
|
||||
video->data_base_addr = rmi_fd.data_base_addr | (page << 8);
|
||||
video->command_base_addr = rmi_fd.cmd_base_addr | (page << 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int synaptics_rmi4_video_init(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
int retval;
|
||||
unsigned char attr_count;
|
||||
|
||||
if (video) {
|
||||
dev_dbg(rmi4_data->pdev->dev.parent,
|
||||
"%s: Handle already exists\n",
|
||||
__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
video = kzalloc(sizeof(*video), GFP_KERNEL);
|
||||
if (!video) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to alloc mem for video\n",
|
||||
__func__);
|
||||
retval = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
video->rmi4_data = rmi4_data;
|
||||
|
||||
retval = video_scan_pdt();
|
||||
if (retval < 0) {
|
||||
retval = 0;
|
||||
goto exit_scan_pdt;
|
||||
}
|
||||
|
||||
video->sysfs_dir = kobject_create_and_add(SYSFS_FOLDER_NAME,
|
||||
&rmi4_data->input_dev->dev.kobj);
|
||||
if (!video->sysfs_dir) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to create sysfs directory\n",
|
||||
__func__);
|
||||
retval = -ENODEV;
|
||||
goto exit_sysfs_dir;
|
||||
}
|
||||
|
||||
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
|
||||
retval = sysfs_create_file(video->sysfs_dir,
|
||||
&attrs[attr_count].attr);
|
||||
if (retval < 0) {
|
||||
dev_err(rmi4_data->pdev->dev.parent,
|
||||
"%s: Failed to create sysfs attributes\n",
|
||||
__func__);
|
||||
retval = -ENODEV;
|
||||
goto exit_sysfs_attrs;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_sysfs_attrs:
|
||||
for (attr_count--; attr_count >= 0; attr_count--)
|
||||
sysfs_remove_file(video->sysfs_dir, &attrs[attr_count].attr);
|
||||
|
||||
kobject_put(video->sysfs_dir);
|
||||
|
||||
exit_sysfs_dir:
|
||||
exit_scan_pdt:
|
||||
kfree(video);
|
||||
video = NULL;
|
||||
|
||||
exit:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_video_remove(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
unsigned char attr_count;
|
||||
|
||||
if (!video)
|
||||
goto exit;
|
||||
|
||||
for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++)
|
||||
sysfs_remove_file(video->sysfs_dir, &attrs[attr_count].attr);
|
||||
|
||||
kobject_put(video->sysfs_dir);
|
||||
|
||||
kfree(video);
|
||||
video = NULL;
|
||||
|
||||
exit:
|
||||
complete(&video_remove_complete);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_video_reset(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
if (!video)
|
||||
synaptics_rmi4_video_init(rmi4_data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef RMI_DCS_SUSPEND_RESUME
|
||||
static void synaptics_rmi4_video_suspend(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
int retval;
|
||||
unsigned char ii;
|
||||
unsigned char command;
|
||||
unsigned char num_of_cmds;
|
||||
|
||||
if (!video)
|
||||
return;
|
||||
|
||||
num_of_cmds = ARRAY_SIZE(suspend_sequence);
|
||||
|
||||
for (ii = 0; ii < num_of_cmds; ii++) {
|
||||
command = suspend_sequence[ii].command;
|
||||
retval = video_send_dcs_command(command);
|
||||
if (retval < 0)
|
||||
return;
|
||||
msleep(suspend_sequence[ii].wait_time);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void synaptics_rmi4_video_resume(struct synaptics_rmi4_data *rmi4_data)
|
||||
{
|
||||
int retval;
|
||||
unsigned char ii;
|
||||
unsigned char command;
|
||||
unsigned char num_of_cmds;
|
||||
|
||||
if (!video)
|
||||
return;
|
||||
|
||||
num_of_cmds = ARRAY_SIZE(resume_sequence);
|
||||
|
||||
for (ii = 0; ii < num_of_cmds; ii++) {
|
||||
command = resume_sequence[ii].command;
|
||||
retval = video_send_dcs_command(command);
|
||||
if (retval < 0)
|
||||
return;
|
||||
msleep(resume_sequence[ii].wait_time);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct synaptics_rmi4_exp_fn video_module = {
|
||||
.fn_type = RMI_VIDEO,
|
||||
.init = synaptics_rmi4_video_init,
|
||||
.remove = synaptics_rmi4_video_remove,
|
||||
.reset = synaptics_rmi4_video_reset,
|
||||
.reinit = NULL,
|
||||
.early_suspend = NULL,
|
||||
#ifdef RMI_DCS_SUSPEND_RESUME
|
||||
.suspend = synaptics_rmi4_video_suspend,
|
||||
.resume = synaptics_rmi4_video_resume,
|
||||
#else
|
||||
.suspend = NULL,
|
||||
.resume = NULL,
|
||||
#endif
|
||||
.late_resume = NULL,
|
||||
.attn = NULL,
|
||||
};
|
||||
|
||||
static int __init rmi4_video_module_init(void)
|
||||
{
|
||||
synaptics_rmi4_new_function(&video_module, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rmi4_video_module_exit(void)
|
||||
{
|
||||
synaptics_rmi4_new_function(&video_module, false);
|
||||
|
||||
wait_for_completion(&video_remove_complete);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
module_init(rmi4_video_module_init);
|
||||
module_exit(rmi4_video_module_exit);
|
||||
|
||||
MODULE_AUTHOR("Synaptics, Inc.");
|
||||
MODULE_DESCRIPTION("Synaptics DSX Video Module");
|
||||
MODULE_LICENSE("GPL v2");
|
111
include/linux/input/synaptics_dsx_v2_6.h
Normal file
111
include/linux/input/synaptics_dsx_v2_6.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Synaptics DSX touchscreen driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Synaptics Incorporated. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
|
||||
* EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
|
||||
* AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
|
||||
* IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
||||
* WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
|
||||
* AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
|
||||
* NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
|
||||
* TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
|
||||
* DOLLARS.
|
||||
*/
|
||||
|
||||
#ifndef _SYNAPTICS_DSX_H_
|
||||
#define _SYNAPTICS_DSX_H_
|
||||
|
||||
#define PLATFORM_DRIVER_NAME "synaptics_dsx"
|
||||
#define STYLUS_DRIVER_NAME "synaptics_dsx_stylus"
|
||||
#define ACTIVE_PEN_DRIVER_NAME "synaptics_dsx_active_pen"
|
||||
#define PROXIMITY_DRIVER_NAME "synaptics_dsx_proximity"
|
||||
#define GESTURE_DRIVER_NAME "synaptics_dsx_gesture"
|
||||
#define I2C_DRIVER_NAME "synaptics_dsx_i2c"
|
||||
#define SPI_DRIVER_NAME "synaptics_dsx_spi"
|
||||
|
||||
/*
|
||||
* struct synaptics_dsx_button_map - button map
|
||||
* @nbuttons: number of buttons
|
||||
* @map: pointer to array of button codes
|
||||
*/
|
||||
struct synaptics_dsx_button_map {
|
||||
unsigned char nbuttons;
|
||||
unsigned int *map;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct synaptics_dsx_board_data - DSX board data
|
||||
* @x_flip: x flip flag
|
||||
* @y_flip: y flip flag
|
||||
* @swap_axes: swap axes flag
|
||||
* @irq_gpio: attention interrupt GPIO
|
||||
* @irq_on_state: attention interrupt active state
|
||||
* @power_gpio: power switch GPIO
|
||||
* @power_on_state: power switch active state
|
||||
* @reset_gpio: reset GPIO
|
||||
* @reset_on_state: reset active state
|
||||
* @max_y_for_2d: maximum y value for 2D area when virtual buttons are present
|
||||
* @irq_flags: IRQ flags
|
||||
* @i2c_addr: I2C slave address
|
||||
* @ub_i2c_addr: microbootloader mode I2C slave address
|
||||
* @device_descriptor_addr: HID device descriptor address
|
||||
* @panel_x: x-axis resolution of display panel
|
||||
* @panel_y: y-axis resolution of display panel
|
||||
* @power_delay_ms: delay time to wait after powering up device
|
||||
* @reset_delay_ms: delay time to wait after resetting device
|
||||
* @reset_active_ms: reset active time
|
||||
* @byte_delay_us: delay time between two bytes of SPI data
|
||||
* @block_delay_us: delay time between two SPI transfers
|
||||
* @pwr_reg_name: pointer to name of regulator for power control
|
||||
* @bus_reg_name: pointer to name of regulator for bus pullup control
|
||||
* @cap_button_map: pointer to 0D button map
|
||||
* @vir_button_map: pointer to virtual button map
|
||||
*/
|
||||
struct synaptics_dsx_board_data {
|
||||
bool x_flip;
|
||||
bool y_flip;
|
||||
bool swap_axes;
|
||||
int irq_gpio;
|
||||
int irq_on_state;
|
||||
int power_gpio;
|
||||
int power_on_state;
|
||||
int reset_gpio;
|
||||
int reset_on_state;
|
||||
int max_y_for_2d;
|
||||
unsigned long irq_flags;
|
||||
unsigned short i2c_addr;
|
||||
unsigned short ub_i2c_addr;
|
||||
unsigned short device_descriptor_addr;
|
||||
unsigned int panel_x;
|
||||
unsigned int panel_y;
|
||||
unsigned int power_delay_ms;
|
||||
unsigned int reset_delay_ms;
|
||||
unsigned int reset_active_ms;
|
||||
unsigned int byte_delay_us;
|
||||
unsigned int block_delay_us;
|
||||
const char *pwr_reg_name;
|
||||
const char *bus_reg_name;
|
||||
struct synaptics_dsx_button_map *cap_button_map;
|
||||
struct synaptics_dsx_button_map *vir_button_map;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue