Skip to content

Commit

Permalink
HID: uclogic: Add support for XP-PEN Artist 22R Pro
Browse files Browse the repository at this point in the history
Adds support for the  XP-PEN Artist 22R Pro in uclogic, including the stylus,
frame and pen pressure support.

I did not do the research for this hardware, or the original patch - that work
has been done by Aren Villanueva. For some reason they decided not to merge
it. My changes include remapping the stupid amount of tablet frame buttons,
cleaning up the code to match kernel style, and other small stuff.

The tablet is (almost) fully functional even when uclogic doesn't handle it.
Without initialization, the tablet has some sort of "basic driverless mode"
that allows the tablet frame buttons to have some default keys associated with
them (CTRL-S, CTRL-Z, that kind of stuff), but unfortunately the stylus pen
semi-works. While pressure sensitivity works, only one stylus button functions
correctly. Since the initialization process differs for Pro series tablets, the
new function uclogic_params_init_ugee_xppen_pro had to be introduced. I also
added USB HID IDs for this tablet too, but it's classified under the UGEE
vendor ID.

One of the more strange things I had to do is figure out a way to remap the
buttons since there are 20 of them in total, and of course there are more
buttons than there are BTN constants defined for us. When running without
uclogic, it starts at BTN_0, ends at BTN_8 and the tablet starts reporting
nonsensical keycodes so just leaving it alone isn't an option. I'm testing
this under a libinput system, which has a list of buttons it considers "tablet
pad buttons" which are notably BTN_0, BTN_1, so on and some
gamepad/joystick buttons. So I created a new array called
uclogic_extra_input_mapping for 20 working inputs.

Another weird feature of this tablet is the second dial, which the original
patchset introduced a new uclogic_frame param to handle since it seems it
throws both dials values into one byte. The left wheel is considered EV_WHEEL
and the other, EV_HWHEEL which seems fine to me. I also added the new param to
the debug messages too.

Link: DIGImend/digimend-kernel-drivers#557
Signed-off-by: Joshua Goins <[email protected]>
  • Loading branch information
kurikaesu authored and intel-lab-lkp committed Dec 26, 2022
1 parent 8ac9a8f commit 51d8c9b
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 4 deletions.
1 change: 1 addition & 0 deletions drivers/hid/hid-ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,7 @@
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L 0x0935
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S 0x0909
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO 0x091b
#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071
#define USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720 0x0055
Expand Down
68 changes: 64 additions & 4 deletions drivers/hid/hid-uclogic-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,30 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
return rdesc;
}

/* Buttons considered valid tablet pad inputs. */
const unsigned int uclogic_extra_input_mapping[] = {
BTN_0,
BTN_1,
BTN_2,
BTN_3,
BTN_4,
BTN_5,
BTN_6,
BTN_7,
BTN_8,
BTN_RIGHT,
BTN_MIDDLE,
BTN_SIDE,
BTN_EXTRA,
BTN_FORWARD,
BTN_BACK,
BTN_B,
BTN_A,
BTN_BASE,
BTN_BASE2,
BTN_X
};

static int uclogic_input_mapping(struct hid_device *hdev,
struct hid_input *hi,
struct hid_field *field,
Expand All @@ -91,9 +115,27 @@ static int uclogic_input_mapping(struct hid_device *hdev,
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
struct uclogic_params *params = &drvdata->params;

/* Discard invalid pen usages */
if (params->pen.usage_invalid && (field->application == HID_DG_PEN))
return -1;
if (field->application == HID_GD_KEYPAD) {
/*
* Remap input buttons to sensible ones that are not invalid.
* This only affects previous behavior for devices with more than ten or so buttons.
*/
const int key = (usage->hid & HID_USAGE) - 1;

if (key > 0 && key < ARRAY_SIZE(uclogic_extra_input_mapping)) {
hid_map_usage(hi,
usage,
bit,
max,
EV_KEY,
uclogic_extra_input_mapping[key]);
return 1;
}
} else if (field->application == HID_DG_PEN) {
/* Discard invalid pen usages */
if (params->pen.usage_invalid)
return -1;
}

/* Let hid-core decide what to do */
return 0;
Expand Down Expand Up @@ -403,8 +445,24 @@ static int uclogic_raw_event_frame(

/* If need to, and can, transform the bitmap dial reports */
if (frame->bitmap_dial_byte > 0 && frame->bitmap_dial_byte < size) {
if (data[frame->bitmap_dial_byte] == 2)
switch (data[frame->bitmap_dial_byte]) {
case 2:
data[frame->bitmap_dial_byte] = -1;
break;

/* Everything below here is for tablets that shove multiple dials into 1 byte */
case 4:
case 0x10:
data[frame->bitmap_dial_byte] = 0;
data[frame->bitmap_second_dial_destination_byte] = 1;
break;

case 8:
case 0x20:
data[frame->bitmap_dial_byte] = 0;
data[frame->bitmap_second_dial_destination_byte] = -1;
break;
}
}

return 0;
Expand Down Expand Up @@ -531,6 +589,8 @@ static const struct hid_device_id uclogic_devices[] = {
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO) },
{ }
};
MODULE_DEVICE_TABLE(hid, uclogic_devices);
Expand Down
147 changes: 147 additions & 0 deletions drivers/hid/hid-uclogic-params.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ static void uclogic_params_frame_hid_dbg(
frame->touch_flip_at);
hid_dbg(hdev, "\t\t.bitmap_dial_byte = %u\n",
frame->bitmap_dial_byte);
hid_dbg(hdev, "\t\t.bitmap_second_dial_destination_byte = %u\n",
frame->bitmap_second_dial_destination_byte);
}

/**
Expand Down Expand Up @@ -1418,6 +1420,126 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
return rc;
}


/*
* uclogic_params_init_ugee_xppen_pro() - Initializes a UGEE XP-Pen Pro tablet device.
*
* @hdev: The HID device of the tablet interface to initialize and get
* parameters from. Cannot be NULL.
* @params: Parameters to fill in (to be cleaned with
* uclogic_params_cleanup()). Not modified in case of error.
* Cannot be NULL.
*
* Returns:
* Zero, if successful. A negative errno code on error.
*/
static int uclogic_params_init_ugee_xppen_pro(struct hid_device *hdev,
struct uclogic_params *p,
const u8 probe_endpoint,
const u8 rdesc_init_packet[],
const size_t rdesc_init_size,
const u8 rdesc_tablet_arr[],
const size_t rdesc_tablet_size,
const u8 rdesc_frame_arr[],
const size_t rdesc_frame_size)
{
const size_t str_desc_len = 12;
struct usb_device *udev = hid_to_usb_dev(hdev);
u8 *buf = kmemdup(rdesc_init_packet, rdesc_init_size, GFP_KERNEL);
s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
int actual_len, rc;
u16 resolution;

if (hdev == NULL || p == NULL)
return -EINVAL;

rc = usb_interrupt_msg(
udev,
usb_sndintpipe(udev, probe_endpoint),
buf,
rdesc_init_size,
&actual_len,
USB_CTRL_SET_TIMEOUT);
kfree(buf);
if (rc == -EPIPE) {
hid_err(hdev, "broken pipe sending init packet\n");
return rc;
} else if (rc < 0) {
hid_err(hdev, "failed sending init packet: %d\n", rc);
return rc;
} else if (actual_len != rdesc_init_size) {
hid_err(hdev,
"failed to transfer complete init packet, only %d bytes sent\n",
actual_len);
return -1;
}

rc = uclogic_params_get_str_desc(&buf, hdev, 100, str_desc_len);
if (rc != str_desc_len) {
if (rc == -EPIPE) {
hid_err(hdev,
"string descriptor with pen parameters not found\n");
} else if (rc < 0) {
hid_err(hdev,
"failed retrieving pen parameters: %d\n", rc);
} else {
hid_err(hdev,
"string descriptor with pen parameters has invalid length (got %d, expected %lu)\n",
rc,
str_desc_len);
rc = -1;
}
kfree(buf);
return rc;
}

desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] = get_unaligned_le16(buf + 2);
desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] = get_unaligned_le16(buf + 4);
/* buf + 6 is the number of pad buttons? Its 0x0008 */
desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
get_unaligned_le16(buf + 8);
resolution = get_unaligned_le16(buf + 10);
kfree(buf);
if (resolution == 0) {
hid_err(hdev, "resolution of 0 in descriptor string\n");
return -1;
}
desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 / resolution;
desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 / resolution;

hid_dbg(hdev,
"Received parameters: X: %d Y: %d Pressure: %d Resolution: %u\n",
desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM],
desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM],
desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM],
resolution);

p->pen.desc_ptr = uclogic_rdesc_template_apply(
rdesc_tablet_arr,
rdesc_tablet_size,
desc_params,
ARRAY_SIZE(desc_params));
p->pen.desc_size = rdesc_tablet_size;
p->pen.id = 0x02;

rc = uclogic_params_frame_init_with_desc(
&p->frame_list[0],
rdesc_frame_arr,
rdesc_frame_size,
UCLOGIC_RDESC_V1_FRAME_ID);
if (rc < 0) {
hid_err(hdev, "initializing frame params failed: %d\n", rc);
return rc;
}

p->pen.subreport_list[0].value = 0xf0;
p->pen.subreport_list[0].id = p->frame_list[0].id;

return 0;
}

/**
* uclogic_params_init() - initialize a tablet interface and discover its
* parameters.
Expand Down Expand Up @@ -1728,6 +1850,31 @@ int uclogic_params_init(struct uclogic_params *params,
uclogic_params_init_invalid(&p);
}

break;
case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO):
/* Ignore non-pen interfaces */
if (bInterfaceNumber != 2) {
uclogic_params_init_invalid(&p);
break;
}

rc = uclogic_params_init_ugee_xppen_pro(
hdev, &p, UCLOGIC_RDESC_UGEE_XPPEN_PROBE_ENDPOINT_TYPE1,
uclogic_rdesc_xppen_init_packet_type1_arr,
uclogic_rdesc_xppen_init_packet_type1_size,
uclogic_rdesc_xppen_pro_stylus_type1_arr,
uclogic_rdesc_xppen_pro_stylus_type1_size,
uclogic_rdesc_xppen_artist_22r_pro_frame_arr,
uclogic_rdesc_xppen_artist_22r_pro_frame_size);
if (rc != 0) {
hid_err(hdev, "failed creating frame parameters: %d\n", rc);
goto cleanup;
}

p.frame_list[0].bitmap_dial_byte = 7;
p.frame_list[0].bitmap_second_dial_destination_byte = 8;

break;
}

Expand Down
5 changes: 5 additions & 0 deletions drivers/hid/hid-uclogic-params.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ struct uclogic_params_frame {
* counterclockwise, as opposed to the normal 1 and -1.
*/
unsigned int bitmap_dial_byte;
/*
* Destination offset for the second bitmap dial byte, if the tablet
* supports a second dial at all.
*/
unsigned int bitmap_second_dial_destination_byte;
};

/*
Expand Down
Loading

0 comments on commit 51d8c9b

Please sign in to comment.