Jetpack/kernel/nvidia/drivers/hid/hid-nvidia-blake.c

515 lines
12 KiB
C

/*
* HID driver for NVIDIA Shield Wireless Joystick
*
* Copyright (c) 2013-2016, NVIDIA Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include "hid-ids.h"
#define JOYSTICK_FUZZ 64
#define TRIGGER_FUZZ 64
#define JOYSTICK_FLAT 64
#define TRIGGER_FLAT 0
#define MAX_CHAR 255
#define TOUCHPAD_DEFAULT_X 128
#define TOUCHPAD_DEFAULT_Y 128
#define TOUCH_HID_REPORT_SIZE 4
#define TOUCH_ACTION_MASK 0x8
#define TOUCH_ACTION_SHFT 3
#define TOUCH_ACTION_UP 0
#define TOUCH_ACTION_DOWN 1
#define TOUCH_REPORT_ID 2
#define MAX_SPEED 10
#define DEFAULT_SPEED 7
#define SCALE_LEN 6
#define MOUSE_MODE_STR "mouse"
#define GESTURE_MODE_STR "gesture"
#define ABSOLUTE_MODE_STR "absolute"
#define DISABLED_MODE_STR "disabled"
#define UNKNOWN_MODE_STR "unknown"
#define MAX_REL 255
#define MAX_ABS 65535
#define MAX_DPAD_MOVE 4
#define XSCALE 1
#define YSCALE 0
struct nvidia_tp_loc {
u8 x;
u8 y;
u8 action;
u8 speed; /* Not used for now but keep as an parameter */
u8 mode; /* Trackpad mode */
u8 release;
u8 tp_size;
struct hid_report *ff_report;
};
enum {
MOUSE_MODE = 0,
GESTURE_MODE,
ABSOLUTE_MODE,
DISABLED_MODE,
};
/*
* Scale mapping is a table based on quadratic function
*/
static const s8 blake_touch_scale_table_x[SCALE_LEN] = {
0, 1, 4, 8, 13, 15};
static const s8 blake_touch_scale_table_y[SCALE_LEN] = {
0, 3, 10, 17, 22, 25};
static signed short scale_rel(u8 rel, u8 coeff, u8 xy)
{
s8 s_rel = (s8)rel;
s8 sign = (s_rel >= 0) ? 1 : -1;
s8 abs_rel = s_rel * sign;
if (abs_rel >= SCALE_LEN)
abs_rel = SCALE_LEN - 1;
/* If Direction is X or Y */
if (xy) {
return (signed short)((s_rel +
sign*blake_touch_scale_table_x[abs_rel]));
} else {
return (signed short)((s_rel +
sign*blake_touch_scale_table_y[abs_rel]));
}
}
static __s32 scale_rel_to_abs(__s32 rel)
{
__s32 val;
if (rel < 0)
val = MAX_REL + rel;
else
val = rel;
val = val * MAX_ABS / MAX_REL;
return val;
}
static int nvidia_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size) {
unsigned id;
struct nvidia_tp_loc *loc =
(struct nvidia_tp_loc *)hid_get_drvdata(hdev);
u8 action;
u16 x, y;
int press = 0;
int release = 0;
signed short relx, rely;
signed short relx_raw, rely_raw;
if (!report)
return -EINVAL;
id = report->id;
if (!loc)
return -EINVAL;
/* If not valid touch events, let generic driver to handle this */
if (id != TOUCH_REPORT_ID)
return 0;
/* If driver is disabled, don't report anything to generic driver */
if (loc->mode == DISABLED_MODE)
return 1;
if (!data)
return -EINVAL;
action = (data[1] & TOUCH_ACTION_MASK) >> TOUCH_ACTION_SHFT;
x = data[2];
y = data[4];
if (!loc->action && action)
press = 1;
else if (loc->action && !action)
release = 1;
else if (!loc->action && !action)
return 1; /* Double release, don't do anything */
relx_raw = x - loc->x;
rely_raw = y - loc->y;
relx = scale_rel(relx_raw, loc->speed, XSCALE);
rely = scale_rel(rely_raw, (loc->speed), YSCALE);
loc->action = action;
dbg_hid("%u %u %u rel %d %d\n", action, x, y, (s8)relx, (s8)rely);
loc->x = x;
loc->y = y;
/* If it's a press event, don't report. */
if (press)
return 1;
/* Not a press event, we need to report it to input subsystem,
* set values and pass to generic driver */
if (loc->mode == ABSOLUTE_MODE) {
/* Report raw absolute data to generic driver */
if (loc->tp_size == 8) {
data[2] = x & 0xff;
data[3] = y & 0xff;
}
} else if (loc->mode == GESTURE_MODE) {
/* Report raw relative data to generic driver */
if (loc->tp_size == 8) {
data[2] = relx_raw & 0xff;
data[3] = rely_raw & 0xff;
} else {
data[2] = relx_raw & 0xff;
data[3] = relx_raw >> 8;
data[4] = rely_raw & 0xff;
data[5] = rely_raw >> 8;
}
if (release)
loc->release = 1;
else
loc->release = 0;
} else {
/* MOUSE_MODE */
if (loc->tp_size == 8) {
data[2] = relx & 0xff;
data[3] = rely & 0xff;
} else {
data[2] = relx & 0xff;
data[3] = relx >> 8;
data[4] = rely & 0xff;
data[5] = rely >> 8;
}
}
return 0;
}
static int nvidia_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value) {
struct nvidia_tp_loc *loc =
(struct nvidia_tp_loc *)hid_get_drvdata(hdev);
__u16 keycode = ABS_HAT0X;
if (!loc)
return 0;
/* If not in absolute mode or gesture mode, we pass as it is */
if (loc->mode != ABSOLUTE_MODE && loc->mode != GESTURE_MODE)
return 0;
/* If not mouse event, we pass as it is */
if (field->physical != HID_GD_MOUSE
&& field->application != HID_GD_MOUSE)
return 0;
/* If not relative event, we pass as it is */
if (usage->type != EV_REL)
return 0;
if (loc->mode == ABSOLUTE_MODE) {
value = scale_rel_to_abs(value);
input_event(field->hidinput->input, EV_ABS, usage->code,
value);
} else {
/* GESTURE_MODE */
value = (value > 1) ? 1 : ((value < -1) ? -1 : 0);
if (usage->code == REL_X)
keycode = ABS_HAT0X;
else if (usage->code == REL_Y)
keycode = ABS_HAT0Y;
if (!loc->release)
input_event(field->hidinput->input, EV_ABS, keycode,
value);
else
input_event(field->hidinput->input, EV_ABS, keycode,
0);
}
return 1;
}
static ssize_t blake_show_mode(struct device *dev,
struct device_attribute *attr,
char *buf) {
struct hid_device *hdev =
container_of(dev, struct hid_device, dev);
struct nvidia_tp_loc *loc =
(struct nvidia_tp_loc *)hid_get_drvdata(hdev);
if (!loc)
return snprintf(buf, MAX_CHAR, UNKNOWN_MODE_STR);
switch (loc->mode) {
case MOUSE_MODE:
return snprintf(buf, MAX_CHAR, MOUSE_MODE_STR);
case GESTURE_MODE:
return snprintf(buf, MAX_CHAR, GESTURE_MODE_STR);
case ABSOLUTE_MODE:
return snprintf(buf, MAX_CHAR, ABSOLUTE_MODE_STR);
case DISABLED_MODE:
return snprintf(buf, MAX_CHAR, DISABLED_MODE_STR);
default:
return snprintf(buf, MAX_CHAR, UNKNOWN_MODE_STR);
}
}
static ssize_t blake_store_mode(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count) {
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct nvidia_tp_loc *loc =
(struct nvidia_tp_loc *)hid_get_drvdata(hdev);
size_t buflen;
char blake_mode[MAX_CHAR];
if (!loc)
return count;
blake_mode[sizeof(blake_mode) - 1] = '\0';
strncpy(blake_mode, buf, sizeof(blake_mode) - 1);
buflen = strlen(blake_mode);
if (buflen && blake_mode[buflen - 1] == '\n')
blake_mode[buflen - 1] = '\0';
if (!strcmp(blake_mode, MOUSE_MODE_STR))
loc->mode = MOUSE_MODE;
else if (!strcmp(blake_mode, GESTURE_MODE_STR))
loc->mode = GESTURE_MODE;
else if (!strcmp(blake_mode, ABSOLUTE_MODE_STR))
loc->mode = ABSOLUTE_MODE;
else if (!strcmp(blake_mode, DISABLED_MODE_STR))
loc->mode = DISABLED_MODE;
return count;
}
static ssize_t blake_show_speed(struct device *dev,
struct device_attribute *attr,
char *buf) {
struct hid_device *hdev =
container_of(dev, struct hid_device, dev);
struct nvidia_tp_loc *loc =
(struct nvidia_tp_loc *)hid_get_drvdata(hdev);
return snprintf(buf, MAX_CHAR, "%d\n", loc->speed);
}
static ssize_t blake_store_speed(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count) {
struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct nvidia_tp_loc *loc =
(struct nvidia_tp_loc *)hid_get_drvdata(hdev);
unsigned long speed_val;
if (!loc)
return count;
if (!buf)
return count;
if (!kstrtoul(buf, 10, &speed_val)) {
if (speed_val > MAX_SPEED)
return count;
loc->speed = (u8)speed_val;
}
return count;
}
static int nvidia_find_tp_len(struct hid_device *hdev,
struct nvidia_tp_loc *loc)
{
struct hid_report *report;
struct list_head *report_list =
&hdev->report_enum[HID_INPUT_REPORT].report_list;
u8 i;
if (list_empty(report_list)) {
hid_err(hdev, "no output reports found\n");
return -ENODEV;
}
/* set default */
loc->tp_size = 8;
list_for_each_entry(report, report_list, list) {
if (report->id == TOUCH_REPORT_ID) {
/* check if we are reporting u8 or u16
* for x/y size need to handle FW change
*/
for (i = 0; i < report->maxfield; i++) {
if (report->field[i]->application
== HID_GD_MOUSE) {
if (report->field[i]->report_size
== 16) {
loc->tp_size = 16;
break;
}
}
}
}
}
return 0;
}
static DEVICE_ATTR(speed, S_IRUGO | S_IWUSR,
blake_show_speed, blake_store_speed);
static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR,
blake_show_mode, blake_store_mode);
static int nvidia_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
struct nvidia_tp_loc *loc;
loc = devm_kzalloc(&hdev->dev, sizeof(*loc), GFP_KERNEL);
if (!loc)
return -ENOMEM;
loc->x = TOUCHPAD_DEFAULT_X;
loc->y = TOUCHPAD_DEFAULT_Y;
loc->action = 0;
loc->speed = DEFAULT_SPEED;
loc->mode = DISABLED_MODE;
hid_set_drvdata(hdev, loc);
/* Parse the HID report now */
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
goto err_parse;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
if (ret)
goto err_parse;
nvidia_find_tp_len(hdev, loc);
ret = device_create_file(&hdev->dev, &dev_attr_speed);
if (ret) {
hid_warn(hdev,
"cannot create sysfs speed attribute err: %d\n", ret);
}
ret = device_create_file(&hdev->dev, &dev_attr_mode);
if (ret)
hid_warn(hdev,
"cannot create sysfs mode attribute err: %d\n", ret);
kobject_uevent(&hdev->dev.kobj, KOBJ_CHANGE);
return 0;
err_parse:
return ret;
}
static void nvidia_remove(struct hid_device *hdev)
{
struct nvidia_tp_loc *loc = hid_get_drvdata(hdev);
if (!loc)
return;
device_remove_file(&hdev->dev, &dev_attr_speed);
device_remove_file(&hdev->dev, &dev_attr_mode);
hid_hw_stop(hdev);
}
static int nvidia_input_mapped(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
int a = field->logical_minimum;
int b = field->logical_maximum;
int fuzz;
int flat;
if ((usage->type == EV_ABS) && (field->application == HID_GD_GAMEPAD
|| field->application == HID_GD_JOYSTICK)) {
switch (usage->hid) {
case HID_GD_X:
case HID_GD_Y:
case HID_GD_RX:
case HID_GD_RY:
fuzz = JOYSTICK_FUZZ;
flat = JOYSTICK_FLAT;
break;
case HID_GD_Z:
case HID_GD_RZ:
fuzz = TRIGGER_FUZZ;
flat = TRIGGER_FLAT;
break;
default: return 0;/*Use generic mapping for HatX, HatY*/
}
set_bit(usage->type, hi->input->evbit);
set_bit(usage->code, *bit);
input_set_abs_params(hi->input, usage->code, a, b, fuzz, flat);
input_abs_set_res(hi->input, usage->code,
hidinput_calc_abs_res(field, usage->code));
return -1;
}
return 0;
}
static const struct hid_device_id nvidia_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_NVIDIA, USB_DEVICE_ID_NVIDIA_BLAKE) },
{ }
};
MODULE_DEVICE_TABLE(hid, nvidia_devices);
static struct hid_driver nvidia_driver = {
.name = "hid-nvidia-blake",
.id_table = nvidia_devices,
.input_mapped = nvidia_input_mapped,
.raw_event = nvidia_raw_event,
.event = nvidia_event,
.probe = nvidia_probe,
.remove = nvidia_remove,
};
module_hid_driver(nvidia_driver);
MODULE_AUTHOR("Jun Yan <juyan@nvidia.com>");
MODULE_LICENSE("GPL");