467 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * HID driver for THQ PS3 uDraw tablet4 *5 * Copyright (C) 2016 Red Hat Inc. All Rights Reserved6 */7 8#include <linux/device.h>9#include <linux/hid.h>10#include <linux/module.h>11#include "hid-ids.h"12 13MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");14MODULE_DESCRIPTION("PS3 uDraw tablet driver");15MODULE_LICENSE("GPL");16 17/*18 * Protocol information from:19 * https://brandonw.net/udraw/20 * and the source code of:21 * https://vvvv.org/contribution/udraw-hid22 */23 24/*25 * The device is setup with multiple input devices:26 * - the touch area which works as a touchpad27 * - the tablet area which works as a touchpad/drawing tablet28 * - a joypad with a d-pad, and 7 buttons29 * - an accelerometer device30 */31 32enum {33 TOUCH_NONE,34 TOUCH_PEN,35 TOUCH_FINGER,36 TOUCH_TWOFINGER37};38 39enum {40 AXIS_X,41 AXIS_Y,42 AXIS_Z43};44 45/*46 * Accelerometer min/max values47 * in order, X, Y and Z48 */49static struct {50 int min;51 int max;52} accel_limits[] = {53 [AXIS_X] = { 490, 534 },54 [AXIS_Y] = { 490, 534 },55 [AXIS_Z] = { 492, 536 }56};57 58#define DEVICE_NAME "THQ uDraw Game Tablet for PS3"59/* resolution in pixels */60#define RES_X 192061#define RES_Y 108062/* size in mm */63#define WIDTH 16064#define HEIGHT 9065#define PRESSURE_OFFSET 11366#define MAX_PRESSURE (255 - PRESSURE_OFFSET)67 68struct udraw {69 struct input_dev *joy_input_dev;70 struct input_dev *touch_input_dev;71 struct input_dev *pen_input_dev;72 struct input_dev *accel_input_dev;73 struct hid_device *hdev;74 75 /*76 * The device's two-finger support is pretty unreliable, as77 * the device could report a single touch when the two fingers78 * are too close together, and the distance between fingers, even79 * though reported is not in the same unit as the touches.80 *81 * We'll make do without it, and try to report the first touch82 * as reliably as possible.83 */84 int last_one_finger_x;85 int last_one_finger_y;86 int last_two_finger_x;87 int last_two_finger_y;88};89 90static int clamp_accel(int axis, int offset)91{92 axis = clamp(axis,93 accel_limits[offset].min,94 accel_limits[offset].max);95 axis = (axis - accel_limits[offset].min) /96 ((accel_limits[offset].max -97 accel_limits[offset].min) * 0xFF);98 return axis;99}100 101static int udraw_raw_event(struct hid_device *hdev, struct hid_report *report,102 u8 *data, int len)103{104 struct udraw *udraw = hid_get_drvdata(hdev);105 int touch;106 int x, y, z;107 108 if (len != 27)109 return 0;110 111 if (data[11] == 0x00)112 touch = TOUCH_NONE;113 else if (data[11] == 0x40)114 touch = TOUCH_PEN;115 else if (data[11] == 0x80)116 touch = TOUCH_FINGER;117 else118 touch = TOUCH_TWOFINGER;119 120 /* joypad */121 input_report_key(udraw->joy_input_dev, BTN_WEST, data[0] & 1);122 input_report_key(udraw->joy_input_dev, BTN_SOUTH, !!(data[0] & 2));123 input_report_key(udraw->joy_input_dev, BTN_EAST, !!(data[0] & 4));124 input_report_key(udraw->joy_input_dev, BTN_NORTH, !!(data[0] & 8));125 126 input_report_key(udraw->joy_input_dev, BTN_SELECT, !!(data[1] & 1));127 input_report_key(udraw->joy_input_dev, BTN_START, !!(data[1] & 2));128 input_report_key(udraw->joy_input_dev, BTN_MODE, !!(data[1] & 16));129 130 x = y = 0;131 switch (data[2]) {132 case 0x0:133 y = -127;134 break;135 case 0x1:136 y = -127;137 x = 127;138 break;139 case 0x2:140 x = 127;141 break;142 case 0x3:143 y = 127;144 x = 127;145 break;146 case 0x4:147 y = 127;148 break;149 case 0x5:150 y = 127;151 x = -127;152 break;153 case 0x6:154 x = -127;155 break;156 case 0x7:157 y = -127;158 x = -127;159 break;160 default:161 break;162 }163 164 input_report_abs(udraw->joy_input_dev, ABS_X, x);165 input_report_abs(udraw->joy_input_dev, ABS_Y, y);166 167 input_sync(udraw->joy_input_dev);168 169 /* For pen and touchpad */170 x = y = 0;171 if (touch != TOUCH_NONE) {172 if (data[15] != 0x0F)173 x = data[15] * 256 + data[17];174 if (data[16] != 0x0F)175 y = data[16] * 256 + data[18];176 }177 178 if (touch == TOUCH_FINGER) {179 /* Save the last one-finger touch */180 udraw->last_one_finger_x = x;181 udraw->last_one_finger_y = y;182 udraw->last_two_finger_x = -1;183 udraw->last_two_finger_y = -1;184 } else if (touch == TOUCH_TWOFINGER) {185 /*186 * We have a problem because x/y is the one for the187 * second finger but we want the first finger given188 * to user-space otherwise it'll look as if it jumped.189 *190 * See the udraw struct definition for why this was191 * implemented this way.192 */193 if (udraw->last_two_finger_x == -1) {194 /* Save the position of the 2nd finger */195 udraw->last_two_finger_x = x;196 udraw->last_two_finger_y = y;197 198 x = udraw->last_one_finger_x;199 y = udraw->last_one_finger_y;200 } else {201 /*202 * Offset the 2-finger coords using the203 * saved data from the first finger204 */205 x = x - (udraw->last_two_finger_x206 - udraw->last_one_finger_x);207 y = y - (udraw->last_two_finger_y208 - udraw->last_one_finger_y);209 }210 }211 212 /* touchpad */213 if (touch == TOUCH_FINGER || touch == TOUCH_TWOFINGER) {214 input_report_key(udraw->touch_input_dev, BTN_TOUCH, 1);215 input_report_key(udraw->touch_input_dev, BTN_TOOL_FINGER,216 touch == TOUCH_FINGER);217 input_report_key(udraw->touch_input_dev, BTN_TOOL_DOUBLETAP,218 touch == TOUCH_TWOFINGER);219 220 input_report_abs(udraw->touch_input_dev, ABS_X, x);221 input_report_abs(udraw->touch_input_dev, ABS_Y, y);222 } else {223 input_report_key(udraw->touch_input_dev, BTN_TOUCH, 0);224 input_report_key(udraw->touch_input_dev, BTN_TOOL_FINGER, 0);225 input_report_key(udraw->touch_input_dev, BTN_TOOL_DOUBLETAP, 0);226 }227 input_sync(udraw->touch_input_dev);228 229 /* pen */230 if (touch == TOUCH_PEN) {231 int level;232 233 level = clamp(data[13] - PRESSURE_OFFSET,234 0, MAX_PRESSURE);235 236 input_report_key(udraw->pen_input_dev, BTN_TOUCH, (level != 0));237 input_report_key(udraw->pen_input_dev, BTN_TOOL_PEN, 1);238 input_report_abs(udraw->pen_input_dev, ABS_PRESSURE, level);239 input_report_abs(udraw->pen_input_dev, ABS_X, x);240 input_report_abs(udraw->pen_input_dev, ABS_Y, y);241 } else {242 input_report_key(udraw->pen_input_dev, BTN_TOUCH, 0);243 input_report_key(udraw->pen_input_dev, BTN_TOOL_PEN, 0);244 input_report_abs(udraw->pen_input_dev, ABS_PRESSURE, 0);245 }246 input_sync(udraw->pen_input_dev);247 248 /* accel */249 x = (data[19] + (data[20] << 8));250 x = clamp_accel(x, AXIS_X);251 y = (data[21] + (data[22] << 8));252 y = clamp_accel(y, AXIS_Y);253 z = (data[23] + (data[24] << 8));254 z = clamp_accel(z, AXIS_Z);255 input_report_abs(udraw->accel_input_dev, ABS_X, x);256 input_report_abs(udraw->accel_input_dev, ABS_Y, y);257 input_report_abs(udraw->accel_input_dev, ABS_Z, z);258 input_sync(udraw->accel_input_dev);259 260 /* let hidraw and hiddev handle the report */261 return 0;262}263 264static int udraw_open(struct input_dev *dev)265{266 struct udraw *udraw = input_get_drvdata(dev);267 268 return hid_hw_open(udraw->hdev);269}270 271static void udraw_close(struct input_dev *dev)272{273 struct udraw *udraw = input_get_drvdata(dev);274 275 hid_hw_close(udraw->hdev);276}277 278static struct input_dev *allocate_and_setup(struct hid_device *hdev,279 const char *name)280{281 struct input_dev *input_dev;282 283 input_dev = devm_input_allocate_device(&hdev->dev);284 if (!input_dev)285 return NULL;286 287 input_dev->name = name;288 input_dev->phys = hdev->phys;289 input_dev->dev.parent = &hdev->dev;290 input_dev->open = udraw_open;291 input_dev->close = udraw_close;292 input_dev->uniq = hdev->uniq;293 input_dev->id.bustype = hdev->bus;294 input_dev->id.vendor = hdev->vendor;295 input_dev->id.product = hdev->product;296 input_dev->id.version = hdev->version;297 input_set_drvdata(input_dev, hid_get_drvdata(hdev));298 299 return input_dev;300}301 302static bool udraw_setup_touch(struct udraw *udraw,303 struct hid_device *hdev)304{305 struct input_dev *input_dev;306 307 input_dev = allocate_and_setup(hdev, DEVICE_NAME " Touchpad");308 if (!input_dev)309 return false;310 311 input_dev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);312 313 input_set_abs_params(input_dev, ABS_X, 0, RES_X, 1, 0);314 input_abs_set_res(input_dev, ABS_X, RES_X / WIDTH);315 input_set_abs_params(input_dev, ABS_Y, 0, RES_Y, 1, 0);316 input_abs_set_res(input_dev, ABS_Y, RES_Y / HEIGHT);317 318 set_bit(BTN_TOUCH, input_dev->keybit);319 set_bit(BTN_TOOL_FINGER, input_dev->keybit);320 set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit);321 322 set_bit(INPUT_PROP_POINTER, input_dev->propbit);323 324 udraw->touch_input_dev = input_dev;325 326 return true;327}328 329static bool udraw_setup_pen(struct udraw *udraw,330 struct hid_device *hdev)331{332 struct input_dev *input_dev;333 334 input_dev = allocate_and_setup(hdev, DEVICE_NAME " Pen");335 if (!input_dev)336 return false;337 338 input_dev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);339 340 input_set_abs_params(input_dev, ABS_X, 0, RES_X, 1, 0);341 input_abs_set_res(input_dev, ABS_X, RES_X / WIDTH);342 input_set_abs_params(input_dev, ABS_Y, 0, RES_Y, 1, 0);343 input_abs_set_res(input_dev, ABS_Y, RES_Y / HEIGHT);344 input_set_abs_params(input_dev, ABS_PRESSURE,345 0, MAX_PRESSURE, 0, 0);346 347 set_bit(BTN_TOUCH, input_dev->keybit);348 set_bit(BTN_TOOL_PEN, input_dev->keybit);349 350 set_bit(INPUT_PROP_POINTER, input_dev->propbit);351 352 udraw->pen_input_dev = input_dev;353 354 return true;355}356 357static bool udraw_setup_accel(struct udraw *udraw,358 struct hid_device *hdev)359{360 struct input_dev *input_dev;361 362 input_dev = allocate_and_setup(hdev, DEVICE_NAME " Accelerometer");363 if (!input_dev)364 return false;365 366 input_dev->evbit[0] = BIT(EV_ABS);367 368 /* 1G accel is reported as ~256, so clamp to 2G */369 input_set_abs_params(input_dev, ABS_X, -512, 512, 0, 0);370 input_set_abs_params(input_dev, ABS_Y, -512, 512, 0, 0);371 input_set_abs_params(input_dev, ABS_Z, -512, 512, 0, 0);372 373 set_bit(INPUT_PROP_ACCELEROMETER, input_dev->propbit);374 375 udraw->accel_input_dev = input_dev;376 377 return true;378}379 380static bool udraw_setup_joypad(struct udraw *udraw,381 struct hid_device *hdev)382{383 struct input_dev *input_dev;384 385 input_dev = allocate_and_setup(hdev, DEVICE_NAME " Joypad");386 if (!input_dev)387 return false;388 389 input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);390 391 set_bit(BTN_SOUTH, input_dev->keybit);392 set_bit(BTN_NORTH, input_dev->keybit);393 set_bit(BTN_EAST, input_dev->keybit);394 set_bit(BTN_WEST, input_dev->keybit);395 set_bit(BTN_SELECT, input_dev->keybit);396 set_bit(BTN_START, input_dev->keybit);397 set_bit(BTN_MODE, input_dev->keybit);398 399 input_set_abs_params(input_dev, ABS_X, -127, 127, 0, 0);400 input_set_abs_params(input_dev, ABS_Y, -127, 127, 0, 0);401 402 udraw->joy_input_dev = input_dev;403 404 return true;405}406 407static int udraw_probe(struct hid_device *hdev, const struct hid_device_id *id)408{409 struct udraw *udraw;410 int ret;411 412 udraw = devm_kzalloc(&hdev->dev, sizeof(struct udraw), GFP_KERNEL);413 if (!udraw)414 return -ENOMEM;415 416 udraw->hdev = hdev;417 udraw->last_two_finger_x = -1;418 udraw->last_two_finger_y = -1;419 420 hid_set_drvdata(hdev, udraw);421 422 ret = hid_parse(hdev);423 if (ret) {424 hid_err(hdev, "parse failed\n");425 return ret;426 }427 428 if (!udraw_setup_joypad(udraw, hdev) ||429 !udraw_setup_touch(udraw, hdev) ||430 !udraw_setup_pen(udraw, hdev) ||431 !udraw_setup_accel(udraw, hdev)) {432 hid_err(hdev, "could not allocate interfaces\n");433 return -ENOMEM;434 }435 436 ret = input_register_device(udraw->joy_input_dev) ||437 input_register_device(udraw->touch_input_dev) ||438 input_register_device(udraw->pen_input_dev) ||439 input_register_device(udraw->accel_input_dev);440 if (ret) {441 hid_err(hdev, "failed to register interfaces\n");442 return ret;443 }444 445 ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW | HID_CONNECT_DRIVER);446 if (ret) {447 hid_err(hdev, "hw start failed\n");448 return ret;449 }450 451 return 0;452}453 454static const struct hid_device_id udraw_devices[] = {455 { HID_USB_DEVICE(USB_VENDOR_ID_THQ, USB_DEVICE_ID_THQ_PS3_UDRAW) },456 { }457};458MODULE_DEVICE_TABLE(hid, udraw_devices);459 460static struct hid_driver udraw_driver = {461 .name = "hid-udraw",462 .id_table = udraw_devices,463 .raw_event = udraw_raw_event,464 .probe = udraw_probe,465};466module_hid_driver(udraw_driver);467