Input: elantech - add support for SMBus devices
Many of the Elantech devices are connected through PS/2 and a different bus (SMBus or plain I2C). To not break any existing device, we only enable SMBus based on a module parameter. If some laptops require the quirk to be set, we will have to rely on a list of PNPIds or MDI matching to individually expose those hardware over SMBus. the parameter mentioned above is elantech_smbus from the psmouse module. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Acked-by: KT Liao <kt.liao@emc.com.tw> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
This commit is contained in:
parent
80212ed743
commit
21c48dbde0
@ -133,6 +133,18 @@ config MOUSE_PS2_ELANTECH
|
|||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
|
config MOUSE_PS2_ELANTECH_SMBUS
|
||||||
|
bool "Elantech PS/2 SMbus companion" if EXPERT
|
||||||
|
default y
|
||||||
|
depends on MOUSE_PS2 && MOUSE_PS2_ELANTECH
|
||||||
|
depends on I2C=y || I2C=MOUSE_PS2
|
||||||
|
select MOUSE_PS2_SMBUS
|
||||||
|
help
|
||||||
|
Say Y here if you have a Elantech touchpad connected to
|
||||||
|
to an SMBus, but enumerated through PS/2.
|
||||||
|
|
||||||
|
If unsure, say Y.
|
||||||
|
|
||||||
config MOUSE_PS2_SENTELIC
|
config MOUSE_PS2_SENTELIC
|
||||||
bool "Sentelic Finger Sensing Pad PS/2 protocol extension"
|
bool "Sentelic Finger Sensing Pad PS/2 protocol extension"
|
||||||
depends on MOUSE_PS2
|
depends on MOUSE_PS2
|
||||||
|
@ -14,13 +14,16 @@
|
|||||||
#include <linux/dmi.h>
|
#include <linux/dmi.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
#include <linux/input.h>
|
#include <linux/input.h>
|
||||||
#include <linux/input/mt.h>
|
#include <linux/input/mt.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
#include <linux/serio.h>
|
#include <linux/serio.h>
|
||||||
#include <linux/libps2.h>
|
#include <linux/libps2.h>
|
||||||
#include <asm/unaligned.h>
|
#include <asm/unaligned.h>
|
||||||
#include "psmouse.h"
|
#include "psmouse.h"
|
||||||
#include "elantech.h"
|
#include "elantech.h"
|
||||||
|
#include "elan_i2c.h"
|
||||||
|
|
||||||
#define elantech_debug(fmt, ...) \
|
#define elantech_debug(fmt, ...) \
|
||||||
do { \
|
do { \
|
||||||
@ -1084,7 +1087,8 @@ static unsigned int elantech_convert_res(unsigned int val)
|
|||||||
|
|
||||||
static int elantech_get_resolution_v4(struct psmouse *psmouse,
|
static int elantech_get_resolution_v4(struct psmouse *psmouse,
|
||||||
unsigned int *x_res,
|
unsigned int *x_res,
|
||||||
unsigned int *y_res)
|
unsigned int *y_res,
|
||||||
|
unsigned int *bus)
|
||||||
{
|
{
|
||||||
unsigned char param[3];
|
unsigned char param[3];
|
||||||
|
|
||||||
@ -1093,6 +1097,7 @@ static int elantech_get_resolution_v4(struct psmouse *psmouse,
|
|||||||
|
|
||||||
*x_res = elantech_convert_res(param[1] & 0x0f);
|
*x_res = elantech_convert_res(param[1] & 0x0f);
|
||||||
*y_res = elantech_convert_res((param[1] & 0xf0) >> 4);
|
*y_res = elantech_convert_res((param[1] & 0xf0) >> 4);
|
||||||
|
*bus = param[2];
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1474,6 +1479,12 @@ static void elantech_disconnect(struct psmouse *psmouse)
|
|||||||
{
|
{
|
||||||
struct elantech_data *etd = psmouse->private;
|
struct elantech_data *etd = psmouse->private;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We might have left a breadcrumb when trying to
|
||||||
|
* set up SMbus companion.
|
||||||
|
*/
|
||||||
|
psmouse_smbus_cleanup(psmouse);
|
||||||
|
|
||||||
if (etd->tp_dev)
|
if (etd->tp_dev)
|
||||||
input_unregister_device(etd->tp_dev);
|
input_unregister_device(etd->tp_dev);
|
||||||
sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj,
|
sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj,
|
||||||
@ -1659,6 +1670,8 @@ static int elantech_query_info(struct psmouse *psmouse,
|
|||||||
{
|
{
|
||||||
unsigned char param[3];
|
unsigned char param[3];
|
||||||
|
|
||||||
|
memset(info, 0, sizeof(*info));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Do the version query again so we can store the result
|
* Do the version query again so we can store the result
|
||||||
*/
|
*/
|
||||||
@ -1717,7 +1730,8 @@ static int elantech_query_info(struct psmouse *psmouse,
|
|||||||
if (info->hw_version == 4) {
|
if (info->hw_version == 4) {
|
||||||
if (elantech_get_resolution_v4(psmouse,
|
if (elantech_get_resolution_v4(psmouse,
|
||||||
&info->x_res,
|
&info->x_res,
|
||||||
&info->y_res)) {
|
&info->y_res,
|
||||||
|
&info->bus)) {
|
||||||
psmouse_warn(psmouse,
|
psmouse_warn(psmouse,
|
||||||
"failed to query resolution data.\n");
|
"failed to query resolution data.\n");
|
||||||
}
|
}
|
||||||
@ -1726,6 +1740,129 @@ static int elantech_query_info(struct psmouse *psmouse,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The newest Elantech device can use a secondary bus (over SMBus) which
|
||||||
|
* provides a better bandwidth and allow a better control of the touchpads.
|
||||||
|
* This is used to decide if we need to use this bus or not.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
ELANTECH_SMBUS_NOT_SET = -1,
|
||||||
|
ELANTECH_SMBUS_OFF,
|
||||||
|
ELANTECH_SMBUS_ON,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int elantech_smbus = IS_ENABLED(CONFIG_MOUSE_ELAN_I2C_SMBUS) ?
|
||||||
|
ELANTECH_SMBUS_NOT_SET : ELANTECH_SMBUS_OFF;
|
||||||
|
module_param_named(elantech_smbus, elantech_smbus, int, 0644);
|
||||||
|
MODULE_PARM_DESC(elantech_smbus, "Use a secondary bus for the Elantech device.");
|
||||||
|
|
||||||
|
static int elantech_create_smbus(struct psmouse *psmouse,
|
||||||
|
struct elantech_device_info *info,
|
||||||
|
bool leave_breadcrumbs)
|
||||||
|
{
|
||||||
|
const struct property_entry i2c_properties[] = {
|
||||||
|
PROPERTY_ENTRY_BOOL("elan,trackpoint"),
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
struct i2c_board_info smbus_board = {
|
||||||
|
I2C_BOARD_INFO("elan_i2c", 0x15),
|
||||||
|
.flags = I2C_CLIENT_HOST_NOTIFY,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (info->has_trackpoint)
|
||||||
|
smbus_board.properties = i2c_properties;
|
||||||
|
|
||||||
|
return psmouse_smbus_init(psmouse, &smbus_board, NULL, 0,
|
||||||
|
leave_breadcrumbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* elantech_setup_smbus - called once the PS/2 devices are enumerated
|
||||||
|
* and decides to instantiate a SMBus InterTouch device.
|
||||||
|
*/
|
||||||
|
static int elantech_setup_smbus(struct psmouse *psmouse,
|
||||||
|
struct elantech_device_info *info,
|
||||||
|
bool leave_breadcrumbs)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (elantech_smbus == ELANTECH_SMBUS_OFF)
|
||||||
|
return -ENXIO;
|
||||||
|
|
||||||
|
if (elantech_smbus == ELANTECH_SMBUS_NOT_SET) {
|
||||||
|
/*
|
||||||
|
* FIXME:
|
||||||
|
* constraint the I2C capable devices by using FW version,
|
||||||
|
* board version, or by using DMI matching
|
||||||
|
*/
|
||||||
|
return -ENXIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
psmouse_info(psmouse, "Trying to set up SMBus access\n");
|
||||||
|
|
||||||
|
error = elantech_create_smbus(psmouse, info, leave_breadcrumbs);
|
||||||
|
if (error) {
|
||||||
|
if (error == -EAGAIN)
|
||||||
|
psmouse_info(psmouse, "SMbus companion is not ready yet\n");
|
||||||
|
else
|
||||||
|
psmouse_err(psmouse, "unable to create intertouch device\n");
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool elantech_use_host_notify(struct psmouse *psmouse,
|
||||||
|
struct elantech_device_info *info)
|
||||||
|
{
|
||||||
|
switch (info->bus) {
|
||||||
|
case ETP_BUS_PS2_ONLY:
|
||||||
|
/* expected case */
|
||||||
|
break;
|
||||||
|
case ETP_BUS_SMB_ALERT_ONLY:
|
||||||
|
/* fall-through */
|
||||||
|
case ETP_BUS_PS2_SMB_ALERT:
|
||||||
|
psmouse_dbg(psmouse, "Ignoring SMBus provider through alert protocol.\n");
|
||||||
|
break;
|
||||||
|
case ETP_BUS_SMB_HST_NTFY_ONLY:
|
||||||
|
/* fall-through */
|
||||||
|
case ETP_BUS_PS2_SMB_HST_NTFY:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
psmouse_dbg(psmouse,
|
||||||
|
"Ignoring SMBus bus provider %d.\n",
|
||||||
|
info->bus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int elantech_init_smbus(struct psmouse *psmouse)
|
||||||
|
{
|
||||||
|
struct elantech_device_info info;
|
||||||
|
int error = -EINVAL;
|
||||||
|
|
||||||
|
psmouse_reset(psmouse);
|
||||||
|
|
||||||
|
error = elantech_query_info(psmouse, &info);
|
||||||
|
if (error)
|
||||||
|
goto init_fail;
|
||||||
|
|
||||||
|
if (info.hw_version < 4) {
|
||||||
|
error = -ENXIO;
|
||||||
|
goto init_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elantech_create_smbus(psmouse, &info, false);
|
||||||
|
init_fail:
|
||||||
|
psmouse_reset(psmouse);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize the touchpad and create sysfs entries
|
* Initialize the touchpad and create sysfs entries
|
||||||
*/
|
*/
|
||||||
@ -1734,7 +1871,7 @@ static int elantech_setup_ps2(struct psmouse *psmouse,
|
|||||||
{
|
{
|
||||||
struct elantech_data *etd;
|
struct elantech_data *etd;
|
||||||
int i;
|
int i;
|
||||||
int error;
|
int error = -EINVAL;
|
||||||
struct input_dev *tp_dev;
|
struct input_dev *tp_dev;
|
||||||
|
|
||||||
psmouse->private = etd = kzalloc(sizeof(*etd), GFP_KERNEL);
|
psmouse->private = etd = kzalloc(sizeof(*etd), GFP_KERNEL);
|
||||||
@ -1821,7 +1958,7 @@ static int elantech_setup_ps2(struct psmouse *psmouse,
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
int elantech_init(struct psmouse *psmouse)
|
int elantech_init_ps2(struct psmouse *psmouse)
|
||||||
{
|
{
|
||||||
struct elantech_device_info info;
|
struct elantech_device_info info;
|
||||||
int error = -EINVAL;
|
int error = -EINVAL;
|
||||||
@ -1841,3 +1978,46 @@ int elantech_init(struct psmouse *psmouse)
|
|||||||
psmouse_reset(psmouse);
|
psmouse_reset(psmouse);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int elantech_init(struct psmouse *psmouse)
|
||||||
|
{
|
||||||
|
struct elantech_device_info info;
|
||||||
|
int error = -EINVAL;
|
||||||
|
|
||||||
|
psmouse_reset(psmouse);
|
||||||
|
|
||||||
|
error = elantech_query_info(psmouse, &info);
|
||||||
|
if (error)
|
||||||
|
goto init_fail;
|
||||||
|
|
||||||
|
#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS)
|
||||||
|
|
||||||
|
if (elantech_use_host_notify(psmouse, &info)) {
|
||||||
|
if (!IS_ENABLED(CONFIG_MOUSE_ELAN_I2C_SMBUS) ||
|
||||||
|
!IS_ENABLED(CONFIG_MOUSE_PS2_ELANTECH_SMBUS)) {
|
||||||
|
psmouse_warn(psmouse,
|
||||||
|
"The touchpad can support a better bus than the too old PS/2 protocol. "
|
||||||
|
"Make sure MOUSE_PS2_ELANTECH_SMBUS and MOUSE_ELAN_I2C_SMBUS are enabled to get a better touchpad experience.\n");
|
||||||
|
}
|
||||||
|
error = elantech_setup_smbus(psmouse, &info, true);
|
||||||
|
if (!error)
|
||||||
|
return PSMOUSE_ELANTECH_SMBUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */
|
||||||
|
|
||||||
|
error = elantech_setup_ps2(psmouse, &info);
|
||||||
|
if (error < 0) {
|
||||||
|
/*
|
||||||
|
* Not using any flavor of Elantech support, so clean up
|
||||||
|
* SMbus breadcrumbs, if any.
|
||||||
|
*/
|
||||||
|
psmouse_smbus_cleanup(psmouse);
|
||||||
|
goto init_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PSMOUSE_ELANTECH;
|
||||||
|
init_fail:
|
||||||
|
psmouse_reset(psmouse);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
@ -106,6 +106,15 @@
|
|||||||
*/
|
*/
|
||||||
#define ETP_WEIGHT_VALUE 5
|
#define ETP_WEIGHT_VALUE 5
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bus information on 3rd byte of query ETP_RESOLUTION_QUERY(0x04)
|
||||||
|
*/
|
||||||
|
#define ETP_BUS_PS2_ONLY 0
|
||||||
|
#define ETP_BUS_SMB_ALERT_ONLY 1
|
||||||
|
#define ETP_BUS_SMB_HST_NTFY_ONLY 2
|
||||||
|
#define ETP_BUS_PS2_SMB_ALERT 3
|
||||||
|
#define ETP_BUS_PS2_SMB_HST_NTFY 4
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The base position for one finger, v4 hardware
|
* The base position for one finger, v4 hardware
|
||||||
*/
|
*/
|
||||||
@ -122,6 +131,7 @@ struct elantech_device_info {
|
|||||||
unsigned int fw_version;
|
unsigned int fw_version;
|
||||||
unsigned int x_res;
|
unsigned int x_res;
|
||||||
unsigned int y_res;
|
unsigned int y_res;
|
||||||
|
unsigned int bus;
|
||||||
bool paritycheck;
|
bool paritycheck;
|
||||||
bool jumpy_cursor;
|
bool jumpy_cursor;
|
||||||
bool reports_pressure;
|
bool reports_pressure;
|
||||||
@ -156,6 +166,7 @@ struct elantech_data {
|
|||||||
|
|
||||||
#ifdef CONFIG_MOUSE_PS2_ELANTECH
|
#ifdef CONFIG_MOUSE_PS2_ELANTECH
|
||||||
int elantech_detect(struct psmouse *psmouse, bool set_properties);
|
int elantech_detect(struct psmouse *psmouse, bool set_properties);
|
||||||
|
int elantech_init_ps2(struct psmouse *psmouse);
|
||||||
int elantech_init(struct psmouse *psmouse);
|
int elantech_init(struct psmouse *psmouse);
|
||||||
#else
|
#else
|
||||||
static inline int elantech_detect(struct psmouse *psmouse, bool set_properties)
|
static inline int elantech_detect(struct psmouse *psmouse, bool set_properties)
|
||||||
@ -166,6 +177,19 @@ static inline int elantech_init(struct psmouse *psmouse)
|
|||||||
{
|
{
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
}
|
}
|
||||||
|
static inline int elantech_init_ps2(struct psmouse *psmouse)
|
||||||
|
{
|
||||||
|
return -ENOSYS;
|
||||||
|
}
|
||||||
#endif /* CONFIG_MOUSE_PS2_ELANTECH */
|
#endif /* CONFIG_MOUSE_PS2_ELANTECH */
|
||||||
|
|
||||||
|
#if defined(CONFIG_MOUSE_PS2_ELANTECH_SMBUS)
|
||||||
|
int elantech_init_smbus(struct psmouse *psmouse);
|
||||||
|
#else
|
||||||
|
static inline int elantech_init_smbus(struct psmouse *psmouse)
|
||||||
|
{
|
||||||
|
return -ENOSYS;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_MOUSE_PS2_ELANTECH_SMBUS */
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -856,7 +856,17 @@ static const struct psmouse_protocol psmouse_protocols[] = {
|
|||||||
.name = "ETPS/2",
|
.name = "ETPS/2",
|
||||||
.alias = "elantech",
|
.alias = "elantech",
|
||||||
.detect = elantech_detect,
|
.detect = elantech_detect,
|
||||||
.init = elantech_init,
|
.init = elantech_init_ps2,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_MOUSE_PS2_ELANTECH_SMBUS
|
||||||
|
{
|
||||||
|
.type = PSMOUSE_ELANTECH_SMBUS,
|
||||||
|
.name = "ETSMBus",
|
||||||
|
.alias = "elantech-smbus",
|
||||||
|
.detect = elantech_detect,
|
||||||
|
.init = elantech_init_smbus,
|
||||||
|
.smbus_companion = true,
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_MOUSE_PS2_SENTELIC
|
#ifdef CONFIG_MOUSE_PS2_SENTELIC
|
||||||
@ -1158,8 +1168,13 @@ static int psmouse_extensions(struct psmouse *psmouse,
|
|||||||
/* Try Elantech touchpad */
|
/* Try Elantech touchpad */
|
||||||
if (max_proto > PSMOUSE_IMEX &&
|
if (max_proto > PSMOUSE_IMEX &&
|
||||||
psmouse_try_protocol(psmouse, PSMOUSE_ELANTECH,
|
psmouse_try_protocol(psmouse, PSMOUSE_ELANTECH,
|
||||||
&max_proto, set_properties, true)) {
|
&max_proto, set_properties, false)) {
|
||||||
return PSMOUSE_ELANTECH;
|
if (!set_properties)
|
||||||
|
return PSMOUSE_ELANTECH;
|
||||||
|
|
||||||
|
ret = elantech_init(psmouse);
|
||||||
|
if (ret >= 0)
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (max_proto > PSMOUSE_IMEX) {
|
if (max_proto > PSMOUSE_IMEX) {
|
||||||
|
@ -237,10 +237,13 @@ int psmouse_smbus_init(struct psmouse *psmouse,
|
|||||||
smbdev->psmouse = psmouse;
|
smbdev->psmouse = psmouse;
|
||||||
smbdev->board = *board;
|
smbdev->board = *board;
|
||||||
|
|
||||||
smbdev->board.platform_data = kmemdup(pdata, pdata_size, GFP_KERNEL);
|
if (pdata) {
|
||||||
if (!smbdev->board.platform_data) {
|
smbdev->board.platform_data = kmemdup(pdata, pdata_size,
|
||||||
kfree(smbdev);
|
GFP_KERNEL);
|
||||||
return -ENOMEM;
|
if (!smbdev->board.platform_data) {
|
||||||
|
kfree(smbdev);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
psmouse->private = smbdev;
|
psmouse->private = smbdev;
|
||||||
|
@ -68,6 +68,7 @@ enum psmouse_type {
|
|||||||
PSMOUSE_VMMOUSE,
|
PSMOUSE_VMMOUSE,
|
||||||
PSMOUSE_BYD,
|
PSMOUSE_BYD,
|
||||||
PSMOUSE_SYNAPTICS_SMBUS,
|
PSMOUSE_SYNAPTICS_SMBUS,
|
||||||
|
PSMOUSE_ELANTECH_SMBUS,
|
||||||
PSMOUSE_AUTO /* This one should always be last */
|
PSMOUSE_AUTO /* This one should always be last */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user