blob: 693269d12dbe335b8eea6113e6d18ac1a3e2d9e0 [file] [log] [blame]
#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)) {
set_bit(EV_REP, hi->input->evbit);
switch (usage->hid & HID_USAGE) {
/* 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");