364 lines
10 KiB
C
364 lines
10 KiB
C
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/hid.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/input.h>
|
|
#include <linux/input/mt.h>
|
|
|
|
#include "hid-ids.h"
|
|
|
|
#define MAX_NUM_OF_FINGERS 5
|
|
#define FINGER_DATA_SIZE 8
|
|
#define DEFAULT_MAX_ABS_MT_PRESSURE 255
|
|
|
|
static int debug = 0;
|
|
module_param(debug, int, 0644);
|
|
MODULE_PARM_DESC(debug, "Activate debugging output");
|
|
|
|
struct syntp_finger {
|
|
int x;
|
|
int y;
|
|
int z;
|
|
int w;
|
|
int dribble_count;
|
|
};
|
|
|
|
struct hid_synaptics_data {
|
|
char phys[64];
|
|
struct input_dev *input; /* input dev */
|
|
struct syntp_finger fingers[MAX_NUM_OF_FINGERS];
|
|
int button_down;
|
|
};
|
|
|
|
#define samsung_kbd_mouse_map_key_clear(c) \
|
|
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
|
|
|
|
static int hid_synaptics_raw_event(struct hid_device *hdev,
|
|
struct hid_report *report, u8 *data, int size)
|
|
{
|
|
struct hid_synaptics_data *syntp_data = hid_get_drvdata(hdev);
|
|
struct input_dev *input = syntp_data->input;
|
|
int raw_z, raw_w, raw_x, raw_y, i = 0;
|
|
int inverted_y;
|
|
int send_packet;
|
|
|
|
/* make sure this report has the right report id */
|
|
if (data[0] != 9 || ((size - 1) < (MAX_NUM_OF_FINGERS * FINGER_DATA_SIZE)))
|
|
return 0;
|
|
|
|
++data;
|
|
for (i = 0; i < MAX_NUM_OF_FINGERS; ++i, data += FINGER_DATA_SIZE) {
|
|
raw_w = data[0] & 0x0f;
|
|
raw_x = be16_to_cpup((__be16 *)&data[2]);
|
|
raw_y = be16_to_cpup((__be16 *)&data[4]);
|
|
raw_z = data[6];
|
|
|
|
send_packet = 0;
|
|
|
|
if (raw_z == 0 && raw_w == 0) {
|
|
/* dribble packet */
|
|
if (syntp_data->fingers[i].dribble_count == 0) {
|
|
raw_x = syntp_data->fingers[i].x;
|
|
raw_y = syntp_data->fingers[i].y;
|
|
raw_z = 0;
|
|
raw_w = 0;
|
|
++syntp_data->fingers[i].dribble_count;
|
|
send_packet = 1;
|
|
}
|
|
} else {
|
|
/* real packet */
|
|
syntp_data->fingers[i].dribble_count = 0;
|
|
send_packet = 1;
|
|
|
|
/* update with last real packet */
|
|
syntp_data->fingers[i].x = raw_x;
|
|
syntp_data->fingers[i].y = raw_y;
|
|
syntp_data->fingers[i].z = raw_z;
|
|
syntp_data->fingers[i].w = raw_w;
|
|
}
|
|
|
|
if (send_packet) {
|
|
inverted_y = (5888 - 1024) - raw_y;
|
|
|
|
dev_dbg(&hdev->dev, "Finger Count = %d\n", data[7] >> 4);
|
|
dev_dbg(&hdev->dev, "ID=%d X=%d Y=%d W=%d Z=%d "
|
|
"button=%d\n", i, raw_x, raw_y,
|
|
raw_w, raw_z, data[1]);
|
|
|
|
input_mt_slot(input, i);
|
|
input_mt_report_slot_state(input, MT_TOOL_FINGER, 1);
|
|
input_report_abs(input, ABS_MT_PRESSURE, raw_z);
|
|
input_report_abs(input, ABS_MT_TOUCH_MAJOR, raw_w);
|
|
input_report_abs(input, ABS_MT_TOUCH_MINOR, raw_w);
|
|
input_report_abs(input, ABS_MT_ORIENTATION, 0);
|
|
input_report_abs(input, ABS_MT_POSITION_X, raw_x);
|
|
input_report_abs(input, ABS_MT_POSITION_Y, inverted_y);
|
|
|
|
input_mt_report_pointer_emulation(input, true);
|
|
}
|
|
|
|
if (data[1] & 0x02) {
|
|
if (!syntp_data->button_down) {
|
|
dev_dbg(&hdev->dev, "Button Down!\n");
|
|
syntp_data->button_down = 1;
|
|
input_report_key(input, BTN_LEFT, 1);
|
|
}
|
|
} else if (syntp_data->button_down) {
|
|
dev_dbg(&hdev->dev, "Button Up!\n");
|
|
syntp_data->button_down = 0;
|
|
input_report_key(input, BTN_LEFT, 0);
|
|
}
|
|
}
|
|
|
|
input_sync(input);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hid_synaptics_event(struct hid_device *hdev, struct hid_field *field,
|
|
struct hid_usage *usage, __s32 value)
|
|
{
|
|
if (field->report->id == 9)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hid_synaptics_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|
{
|
|
struct hid_synaptics_data *syntp_data;
|
|
struct input_dev * input_dev;
|
|
unsigned int connect_mask = HID_CONNECT_DEFAULT;
|
|
int ret;
|
|
|
|
dev_dbg(&hdev->dev, "%s\n", __func__);
|
|
|
|
ret = hid_parse(hdev);
|
|
if (ret) {
|
|
hid_err(hdev, "parse failed\n");
|
|
goto err_free;
|
|
}
|
|
|
|
ret = hid_hw_start(hdev, connect_mask);
|
|
if (ret) {
|
|
hid_err(hdev, "hw start failed\n");
|
|
goto err_free;
|
|
}
|
|
|
|
syntp_data = devm_kzalloc(&hdev->dev, sizeof(struct hid_synaptics_data), GFP_KERNEL);
|
|
if (!syntp_data) {
|
|
ret =-ENOMEM;
|
|
goto hid_stop;
|
|
}
|
|
|
|
input_dev = input_allocate_device();
|
|
if (!input_dev) {
|
|
ret = -ENOMEM;
|
|
kfree(syntp_data);
|
|
goto hid_stop;
|
|
}
|
|
|
|
input_dev->name = "Synaptics HID TouchPad";
|
|
snprintf(syntp_data->phys, 64, "%s/input0", dev_name(&hdev->dev));
|
|
input_dev->phys = syntp_data->phys;
|
|
|
|
/* Setup Events to Report */
|
|
set_bit(EV_SYN, input_dev->evbit);
|
|
set_bit(EV_KEY, input_dev->evbit);
|
|
set_bit(EV_ABS, input_dev->evbit);
|
|
set_bit(BTN_LEFT, input_dev->keybit);
|
|
|
|
set_bit(INPUT_PROP_POINTER, input_dev->propbit);
|
|
|
|
input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
|
|
|
|
set_bit(BTN_TOOL_FINGER, input_dev->keybit);
|
|
set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit);
|
|
set_bit(BTN_TOOL_TRIPLETAP, input_dev->keybit);
|
|
set_bit(BTN_TOOL_QUADTAP, input_dev->keybit);
|
|
|
|
input_set_abs_params(input_dev, ABS_X, 1024, 5888, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_Y, 1024, 5888, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_PRESSURE, 0, DEFAULT_MAX_ABS_MT_PRESSURE, 0, 0);
|
|
|
|
input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
|
|
0, 15, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR,
|
|
0, 15, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_ORIENTATION,
|
|
0, 1, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_TRACKING_ID,
|
|
1, 5, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_POSITION_X,
|
|
1024, 5888, 0, 0);
|
|
input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
|
|
1024, 5888, 0, 0);
|
|
|
|
input_mt_init_slots(input_dev, 5, 0);
|
|
|
|
syntp_data->input = input_dev;
|
|
ret = input_register_device(syntp_data->input);
|
|
if (ret)
|
|
{
|
|
input_free_device(syntp_data->input);
|
|
goto hid_init_failed;
|
|
}
|
|
|
|
hid_set_drvdata(hdev, syntp_data);
|
|
|
|
dev_dbg(&hdev->dev, "Opening low level driver\n");
|
|
hdev->ll_driver->open(hdev);
|
|
|
|
dev_info(&hdev->dev, "registered rmi hid driver for %s\n", hdev->phys);
|
|
return 0;
|
|
|
|
hid_init_failed:
|
|
hdev->ll_driver->close(hdev);
|
|
hid_stop:
|
|
hid_hw_stop(hdev);
|
|
|
|
err_free:
|
|
return ret;
|
|
}
|
|
|
|
static void hid_synaptics_remove(struct hid_device *hdev)
|
|
{
|
|
hdev->ll_driver->close(hdev);
|
|
hid_hw_stop(hdev);
|
|
}
|
|
|
|
static int samsung_bookcover_input_mapping(struct hid_device *hdev,
|
|
struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
|
|
unsigned long **bit, int *max)
|
|
{
|
|
if (!(HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE) ||
|
|
HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)))
|
|
return 0;
|
|
|
|
dbg_hid("samsung wireless keyboard input mapping event [0x%x]\n",
|
|
usage->hid & HID_USAGE);
|
|
|
|
if (HID_UP_KEYBOARD == (usage->hid & HID_USAGE_PAGE)) {
|
|
switch (usage->hid & HID_USAGE) {
|
|
set_bit(EV_REP, hi->input->evbit);
|
|
/* Only for UK keyboard */
|
|
/* key found */
|
|
#ifdef CONFIG_HID_KK_UPGRADE
|
|
case 0x32: samsung_kbd_mouse_map_key_clear(KEY_KBDILLUMTOGGLE); break;
|
|
case 0x64: samsung_kbd_mouse_map_key_clear(KEY_BACKSLASH); break;
|
|
#else
|
|
case 0x32: samsung_kbd_mouse_map_key_clear(KEY_BACKSLASH); break;
|
|
case 0x64: samsung_kbd_mouse_map_key_clear(KEY_102ND); break;
|
|
#endif
|
|
/* Only for BR keyboard */
|
|
case 0x87: samsung_kbd_mouse_map_key_clear(KEY_RO); break;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (HID_UP_CONSUMER == (usage->hid & HID_USAGE_PAGE)) {
|
|
switch (usage->hid & HID_USAGE) {
|
|
/* report 2 */
|
|
/* MENU */
|
|
case 0x040: samsung_kbd_mouse_map_key_clear(KEY_MENU); break;
|
|
case 0x18a: samsung_kbd_mouse_map_key_clear(KEY_MAIL); break;
|
|
case 0x196: samsung_kbd_mouse_map_key_clear(KEY_WWW); break;
|
|
case 0x19e: samsung_kbd_mouse_map_key_clear(KEY_SCREENLOCK); break;
|
|
case 0x221: samsung_kbd_mouse_map_key_clear(KEY_SEARCH); break;
|
|
case 0x223: samsung_kbd_mouse_map_key_clear(KEY_HOMEPAGE); break;
|
|
/* RECENTAPPS */
|
|
case 0x301: samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY1); break;
|
|
/* APPLICATION */
|
|
case 0x302: samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY2); break;
|
|
/* Voice search */
|
|
case 0x305: samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY4); break;
|
|
/* QPANEL on/off */
|
|
case 0x306: samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY5); break;
|
|
/* SIP on/off */
|
|
case 0x307: samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY3); break;
|
|
/* LANG */
|
|
case 0x308: samsung_kbd_mouse_map_key_clear(KEY_LANGUAGE); break;
|
|
case 0x30a: samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSDOWN); break;
|
|
case 0x070: samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSDOWN); break;
|
|
case 0x30b: samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSUP); break;
|
|
case 0x06f: samsung_kbd_mouse_map_key_clear(KEY_BRIGHTNESSUP); break;
|
|
/* S-Finder */
|
|
case 0x304: samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY7); break;
|
|
/* Screen Capture */
|
|
case 0x303: samsung_kbd_mouse_map_key_clear(KEY_SYSRQ); break;
|
|
/* Multi Window */
|
|
case 0x309: samsung_kbd_mouse_map_key_clear(BTN_TRIGGER_HAPPY9); break;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int hid_synaptics_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|
struct hid_field *field, struct hid_usage *usage,
|
|
unsigned long **bit, int *max)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (USB_DEVICE_ID_SAMSUNG_WIRELESS_BOOKCOVER == hdev->product)
|
|
ret = samsung_bookcover_input_mapping(hdev,
|
|
hi, field, usage, bit, max);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct hid_device_id hid_synaptics_id[] = {
|
|
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SAMSUNG_ELECTRONICS, USB_DEVICE_ID_SAMSUNG_WIRELESS_BOOKCOVER),
|
|
.driver_data = 0 },
|
|
{ HID_USB_DEVICE(0x06cb, 0x5555),
|
|
.driver_data = 0 },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(hid, hid_synaptics_id);
|
|
|
|
static struct hid_driver hid_synaptics_driver = {
|
|
.name = "hid-synaptics-bt",
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "hid-synaptics-bt",
|
|
},
|
|
.id_table = hid_synaptics_id,
|
|
.probe = hid_synaptics_probe,
|
|
.remove = hid_synaptics_remove,
|
|
.raw_event = hid_synaptics_raw_event,
|
|
.event = hid_synaptics_event,
|
|
.input_mapping = hid_synaptics_mapping,
|
|
};
|
|
|
|
static int __init hid_synaptics_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = hid_register_driver(&hid_synaptics_driver);
|
|
if (ret)
|
|
pr_err("Failed to register hid_synaptics_driver (%d)\n", ret);
|
|
else
|
|
pr_info("Successfully registered hid_synaptics_driver\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit hid_synaptics_exit(void)
|
|
{
|
|
hid_unregister_driver(&hid_synaptics_driver);
|
|
}
|
|
|
|
module_init(hid_synaptics_init);
|
|
module_exit(hid_synaptics_exit);
|
|
|
|
MODULE_AUTHOR("Andrew Duggan");
|
|
MODULE_DESCRIPTION("Synaptics HID Bluetooth Dock Driver");
|
|
MODULE_LICENSE("GPL");
|