123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * HID driver for THQ PS3 uDraw tablet
- *
- * Copyright (C) 2016 Red Hat Inc. All Rights Reserved
- */
- #include <linux/device.h>
- #include <linux/hid.h>
- #include <linux/module.h>
- #include "hid-ids.h"
- MODULE_AUTHOR("Bastien Nocera <[email protected]>");
- MODULE_DESCRIPTION("PS3 uDraw tablet driver");
- MODULE_LICENSE("GPL");
- /*
- * Protocol information from:
- * https://brandonw.net/udraw/
- * and the source code of:
- * https://vvvv.org/contribution/udraw-hid
- */
- /*
- * The device is setup with multiple input devices:
- * - the touch area which works as a touchpad
- * - the tablet area which works as a touchpad/drawing tablet
- * - a joypad with a d-pad, and 7 buttons
- * - an accelerometer device
- */
- enum {
- TOUCH_NONE,
- TOUCH_PEN,
- TOUCH_FINGER,
- TOUCH_TWOFINGER
- };
- enum {
- AXIS_X,
- AXIS_Y,
- AXIS_Z
- };
- /*
- * Accelerometer min/max values
- * in order, X, Y and Z
- */
- static struct {
- int min;
- int max;
- } accel_limits[] = {
- [AXIS_X] = { 490, 534 },
- [AXIS_Y] = { 490, 534 },
- [AXIS_Z] = { 492, 536 }
- };
- #define DEVICE_NAME "THQ uDraw Game Tablet for PS3"
- /* resolution in pixels */
- #define RES_X 1920
- #define RES_Y 1080
- /* size in mm */
- #define WIDTH 160
- #define HEIGHT 90
- #define PRESSURE_OFFSET 113
- #define MAX_PRESSURE (255 - PRESSURE_OFFSET)
- struct udraw {
- struct input_dev *joy_input_dev;
- struct input_dev *touch_input_dev;
- struct input_dev *pen_input_dev;
- struct input_dev *accel_input_dev;
- struct hid_device *hdev;
- /*
- * The device's two-finger support is pretty unreliable, as
- * the device could report a single touch when the two fingers
- * are too close together, and the distance between fingers, even
- * though reported is not in the same unit as the touches.
- *
- * We'll make do without it, and try to report the first touch
- * as reliably as possible.
- */
- int last_one_finger_x;
- int last_one_finger_y;
- int last_two_finger_x;
- int last_two_finger_y;
- };
- static int clamp_accel(int axis, int offset)
- {
- axis = clamp(axis,
- accel_limits[offset].min,
- accel_limits[offset].max);
- axis = (axis - accel_limits[offset].min) /
- ((accel_limits[offset].max -
- accel_limits[offset].min) * 0xFF);
- return axis;
- }
- static int udraw_raw_event(struct hid_device *hdev, struct hid_report *report,
- u8 *data, int len)
- {
- struct udraw *udraw = hid_get_drvdata(hdev);
- int touch;
- int x, y, z;
- if (len != 27)
- return 0;
- if (data[11] == 0x00)
- touch = TOUCH_NONE;
- else if (data[11] == 0x40)
- touch = TOUCH_PEN;
- else if (data[11] == 0x80)
- touch = TOUCH_FINGER;
- else
- touch = TOUCH_TWOFINGER;
- /* joypad */
- input_report_key(udraw->joy_input_dev, BTN_WEST, data[0] & 1);
- input_report_key(udraw->joy_input_dev, BTN_SOUTH, !!(data[0] & 2));
- input_report_key(udraw->joy_input_dev, BTN_EAST, !!(data[0] & 4));
- input_report_key(udraw->joy_input_dev, BTN_NORTH, !!(data[0] & 8));
- input_report_key(udraw->joy_input_dev, BTN_SELECT, !!(data[1] & 1));
- input_report_key(udraw->joy_input_dev, BTN_START, !!(data[1] & 2));
- input_report_key(udraw->joy_input_dev, BTN_MODE, !!(data[1] & 16));
- x = y = 0;
- switch (data[2]) {
- case 0x0:
- y = -127;
- break;
- case 0x1:
- y = -127;
- x = 127;
- break;
- case 0x2:
- x = 127;
- break;
- case 0x3:
- y = 127;
- x = 127;
- break;
- case 0x4:
- y = 127;
- break;
- case 0x5:
- y = 127;
- x = -127;
- break;
- case 0x6:
- x = -127;
- break;
- case 0x7:
- y = -127;
- x = -127;
- break;
- default:
- break;
- }
- input_report_abs(udraw->joy_input_dev, ABS_X, x);
- input_report_abs(udraw->joy_input_dev, ABS_Y, y);
- input_sync(udraw->joy_input_dev);
- /* For pen and touchpad */
- x = y = 0;
- if (touch != TOUCH_NONE) {
- if (data[15] != 0x0F)
- x = data[15] * 256 + data[17];
- if (data[16] != 0x0F)
- y = data[16] * 256 + data[18];
- }
- if (touch == TOUCH_FINGER) {
- /* Save the last one-finger touch */
- udraw->last_one_finger_x = x;
- udraw->last_one_finger_y = y;
- udraw->last_two_finger_x = -1;
- udraw->last_two_finger_y = -1;
- } else if (touch == TOUCH_TWOFINGER) {
- /*
- * We have a problem because x/y is the one for the
- * second finger but we want the first finger given
- * to user-space otherwise it'll look as if it jumped.
- *
- * See the udraw struct definition for why this was
- * implemented this way.
- */
- if (udraw->last_two_finger_x == -1) {
- /* Save the position of the 2nd finger */
- udraw->last_two_finger_x = x;
- udraw->last_two_finger_y = y;
- x = udraw->last_one_finger_x;
- y = udraw->last_one_finger_y;
- } else {
- /*
- * Offset the 2-finger coords using the
- * saved data from the first finger
- */
- x = x - (udraw->last_two_finger_x
- - udraw->last_one_finger_x);
- y = y - (udraw->last_two_finger_y
- - udraw->last_one_finger_y);
- }
- }
- /* touchpad */
- if (touch == TOUCH_FINGER || touch == TOUCH_TWOFINGER) {
- input_report_key(udraw->touch_input_dev, BTN_TOUCH, 1);
- input_report_key(udraw->touch_input_dev, BTN_TOOL_FINGER,
- touch == TOUCH_FINGER);
- input_report_key(udraw->touch_input_dev, BTN_TOOL_DOUBLETAP,
- touch == TOUCH_TWOFINGER);
- input_report_abs(udraw->touch_input_dev, ABS_X, x);
- input_report_abs(udraw->touch_input_dev, ABS_Y, y);
- } else {
- input_report_key(udraw->touch_input_dev, BTN_TOUCH, 0);
- input_report_key(udraw->touch_input_dev, BTN_TOOL_FINGER, 0);
- input_report_key(udraw->touch_input_dev, BTN_TOOL_DOUBLETAP, 0);
- }
- input_sync(udraw->touch_input_dev);
- /* pen */
- if (touch == TOUCH_PEN) {
- int level;
- level = clamp(data[13] - PRESSURE_OFFSET,
- 0, MAX_PRESSURE);
- input_report_key(udraw->pen_input_dev, BTN_TOUCH, (level != 0));
- input_report_key(udraw->pen_input_dev, BTN_TOOL_PEN, 1);
- input_report_abs(udraw->pen_input_dev, ABS_PRESSURE, level);
- input_report_abs(udraw->pen_input_dev, ABS_X, x);
- input_report_abs(udraw->pen_input_dev, ABS_Y, y);
- } else {
- input_report_key(udraw->pen_input_dev, BTN_TOUCH, 0);
- input_report_key(udraw->pen_input_dev, BTN_TOOL_PEN, 0);
- input_report_abs(udraw->pen_input_dev, ABS_PRESSURE, 0);
- }
- input_sync(udraw->pen_input_dev);
- /* accel */
- x = (data[19] + (data[20] << 8));
- x = clamp_accel(x, AXIS_X);
- y = (data[21] + (data[22] << 8));
- y = clamp_accel(y, AXIS_Y);
- z = (data[23] + (data[24] << 8));
- z = clamp_accel(z, AXIS_Z);
- input_report_abs(udraw->accel_input_dev, ABS_X, x);
- input_report_abs(udraw->accel_input_dev, ABS_Y, y);
- input_report_abs(udraw->accel_input_dev, ABS_Z, z);
- input_sync(udraw->accel_input_dev);
- /* let hidraw and hiddev handle the report */
- return 0;
- }
- static int udraw_open(struct input_dev *dev)
- {
- struct udraw *udraw = input_get_drvdata(dev);
- return hid_hw_open(udraw->hdev);
- }
- static void udraw_close(struct input_dev *dev)
- {
- struct udraw *udraw = input_get_drvdata(dev);
- hid_hw_close(udraw->hdev);
- }
- static struct input_dev *allocate_and_setup(struct hid_device *hdev,
- const char *name)
- {
- struct input_dev *input_dev;
- input_dev = devm_input_allocate_device(&hdev->dev);
- if (!input_dev)
- return NULL;
- input_dev->name = name;
- input_dev->phys = hdev->phys;
- input_dev->dev.parent = &hdev->dev;
- input_dev->open = udraw_open;
- input_dev->close = udraw_close;
- input_dev->uniq = hdev->uniq;
- input_dev->id.bustype = hdev->bus;
- input_dev->id.vendor = hdev->vendor;
- input_dev->id.product = hdev->product;
- input_dev->id.version = hdev->version;
- input_set_drvdata(input_dev, hid_get_drvdata(hdev));
- return input_dev;
- }
- static bool udraw_setup_touch(struct udraw *udraw,
- struct hid_device *hdev)
- {
- struct input_dev *input_dev;
- input_dev = allocate_and_setup(hdev, DEVICE_NAME " Touchpad");
- if (!input_dev)
- return false;
- input_dev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);
- input_set_abs_params(input_dev, ABS_X, 0, RES_X, 1, 0);
- input_abs_set_res(input_dev, ABS_X, RES_X / WIDTH);
- input_set_abs_params(input_dev, ABS_Y, 0, RES_Y, 1, 0);
- input_abs_set_res(input_dev, ABS_Y, RES_Y / HEIGHT);
- set_bit(BTN_TOUCH, input_dev->keybit);
- set_bit(BTN_TOOL_FINGER, input_dev->keybit);
- set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit);
- set_bit(INPUT_PROP_POINTER, input_dev->propbit);
- udraw->touch_input_dev = input_dev;
- return true;
- }
- static bool udraw_setup_pen(struct udraw *udraw,
- struct hid_device *hdev)
- {
- struct input_dev *input_dev;
- input_dev = allocate_and_setup(hdev, DEVICE_NAME " Pen");
- if (!input_dev)
- return false;
- input_dev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);
- input_set_abs_params(input_dev, ABS_X, 0, RES_X, 1, 0);
- input_abs_set_res(input_dev, ABS_X, RES_X / WIDTH);
- input_set_abs_params(input_dev, ABS_Y, 0, RES_Y, 1, 0);
- input_abs_set_res(input_dev, ABS_Y, RES_Y / HEIGHT);
- input_set_abs_params(input_dev, ABS_PRESSURE,
- 0, MAX_PRESSURE, 0, 0);
- set_bit(BTN_TOUCH, input_dev->keybit);
- set_bit(BTN_TOOL_PEN, input_dev->keybit);
- set_bit(INPUT_PROP_POINTER, input_dev->propbit);
- udraw->pen_input_dev = input_dev;
- return true;
- }
- static bool udraw_setup_accel(struct udraw *udraw,
- struct hid_device *hdev)
- {
- struct input_dev *input_dev;
- input_dev = allocate_and_setup(hdev, DEVICE_NAME " Accelerometer");
- if (!input_dev)
- return false;
- input_dev->evbit[0] = BIT(EV_ABS);
- /* 1G accel is reported as ~256, so clamp to 2G */
- input_set_abs_params(input_dev, ABS_X, -512, 512, 0, 0);
- input_set_abs_params(input_dev, ABS_Y, -512, 512, 0, 0);
- input_set_abs_params(input_dev, ABS_Z, -512, 512, 0, 0);
- set_bit(INPUT_PROP_ACCELEROMETER, input_dev->propbit);
- udraw->accel_input_dev = input_dev;
- return true;
- }
- static bool udraw_setup_joypad(struct udraw *udraw,
- struct hid_device *hdev)
- {
- struct input_dev *input_dev;
- input_dev = allocate_and_setup(hdev, DEVICE_NAME " Joypad");
- if (!input_dev)
- return false;
- input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
- set_bit(BTN_SOUTH, input_dev->keybit);
- set_bit(BTN_NORTH, input_dev->keybit);
- set_bit(BTN_EAST, input_dev->keybit);
- set_bit(BTN_WEST, input_dev->keybit);
- set_bit(BTN_SELECT, input_dev->keybit);
- set_bit(BTN_START, input_dev->keybit);
- set_bit(BTN_MODE, input_dev->keybit);
- input_set_abs_params(input_dev, ABS_X, -127, 127, 0, 0);
- input_set_abs_params(input_dev, ABS_Y, -127, 127, 0, 0);
- udraw->joy_input_dev = input_dev;
- return true;
- }
- static int udraw_probe(struct hid_device *hdev, const struct hid_device_id *id)
- {
- struct udraw *udraw;
- int ret;
- udraw = devm_kzalloc(&hdev->dev, sizeof(struct udraw), GFP_KERNEL);
- if (!udraw)
- return -ENOMEM;
- udraw->hdev = hdev;
- udraw->last_two_finger_x = -1;
- udraw->last_two_finger_y = -1;
- hid_set_drvdata(hdev, udraw);
- ret = hid_parse(hdev);
- if (ret) {
- hid_err(hdev, "parse failed\n");
- return ret;
- }
- if (!udraw_setup_joypad(udraw, hdev) ||
- !udraw_setup_touch(udraw, hdev) ||
- !udraw_setup_pen(udraw, hdev) ||
- !udraw_setup_accel(udraw, hdev)) {
- hid_err(hdev, "could not allocate interfaces\n");
- return -ENOMEM;
- }
- ret = input_register_device(udraw->joy_input_dev) ||
- input_register_device(udraw->touch_input_dev) ||
- input_register_device(udraw->pen_input_dev) ||
- input_register_device(udraw->accel_input_dev);
- if (ret) {
- hid_err(hdev, "failed to register interfaces\n");
- return ret;
- }
- ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW | HID_CONNECT_DRIVER);
- if (ret) {
- hid_err(hdev, "hw start failed\n");
- return ret;
- }
- return 0;
- }
- static const struct hid_device_id udraw_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_THQ, USB_DEVICE_ID_THQ_PS3_UDRAW) },
- { }
- };
- MODULE_DEVICE_TABLE(hid, udraw_devices);
- static struct hid_driver udraw_driver = {
- .name = "hid-udraw",
- .id_table = udraw_devices,
- .raw_event = udraw_raw_event,
- .probe = udraw_probe,
- };
- module_hid_driver(udraw_driver);
|