162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  HID driver for UC-Logic devices not fully compliant with HID standard
462306a36Sopenharmony_ci *  - tablet initialization and parameter retrieval
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *  Copyright (c) 2018 Nikolai Kondrashov
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci/*
1062306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or modify it
1162306a36Sopenharmony_ci * under the terms of the GNU General Public License as published by the Free
1262306a36Sopenharmony_ci * Software Foundation; either version 2 of the License, or (at your option)
1362306a36Sopenharmony_ci * any later version.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "hid-uclogic-params.h"
1762306a36Sopenharmony_ci#include "hid-uclogic-rdesc.h"
1862306a36Sopenharmony_ci#include "usbhid/usbhid.h"
1962306a36Sopenharmony_ci#include "hid-ids.h"
2062306a36Sopenharmony_ci#include <linux/ctype.h>
2162306a36Sopenharmony_ci#include <linux/string.h>
2262306a36Sopenharmony_ci#include <asm/unaligned.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/**
2562306a36Sopenharmony_ci * uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
2662306a36Sopenharmony_ci *                                       to a string.
2762306a36Sopenharmony_ci * @inrange:	The in-range reporting type to convert.
2862306a36Sopenharmony_ci *
2962306a36Sopenharmony_ci * Return:
3062306a36Sopenharmony_ci * * The string representing the type, or
3162306a36Sopenharmony_ci * * %NULL if the type is unknown.
3262306a36Sopenharmony_ci */
3362306a36Sopenharmony_cistatic const char *uclogic_params_pen_inrange_to_str(
3462306a36Sopenharmony_ci				enum uclogic_params_pen_inrange inrange)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	switch (inrange) {
3762306a36Sopenharmony_ci	case UCLOGIC_PARAMS_PEN_INRANGE_NORMAL:
3862306a36Sopenharmony_ci		return "normal";
3962306a36Sopenharmony_ci	case UCLOGIC_PARAMS_PEN_INRANGE_INVERTED:
4062306a36Sopenharmony_ci		return "inverted";
4162306a36Sopenharmony_ci	case UCLOGIC_PARAMS_PEN_INRANGE_NONE:
4262306a36Sopenharmony_ci		return "none";
4362306a36Sopenharmony_ci	default:
4462306a36Sopenharmony_ci		return NULL;
4562306a36Sopenharmony_ci	}
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/**
4962306a36Sopenharmony_ci * uclogic_params_pen_hid_dbg() - Dump tablet interface pen parameters
5062306a36Sopenharmony_ci * @hdev:	The HID device the pen parameters describe.
5162306a36Sopenharmony_ci * @pen:	The pen parameters to dump.
5262306a36Sopenharmony_ci *
5362306a36Sopenharmony_ci * Dump tablet interface pen parameters with hid_dbg(). The dump is indented
5462306a36Sopenharmony_ci * with a tab.
5562306a36Sopenharmony_ci */
5662306a36Sopenharmony_cistatic void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
5762306a36Sopenharmony_ci					const struct uclogic_params_pen *pen)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	size_t i;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	hid_dbg(hdev, "\t.usage_invalid = %s\n",
6262306a36Sopenharmony_ci		(pen->usage_invalid ? "true" : "false"));
6362306a36Sopenharmony_ci	hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr);
6462306a36Sopenharmony_ci	hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size);
6562306a36Sopenharmony_ci	hid_dbg(hdev, "\t.id = %u\n", pen->id);
6662306a36Sopenharmony_ci	hid_dbg(hdev, "\t.subreport_list = {\n");
6762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(pen->subreport_list); i++) {
6862306a36Sopenharmony_ci		hid_dbg(hdev, "\t\t{0x%02hhx, %hhu}%s\n",
6962306a36Sopenharmony_ci			pen->subreport_list[i].value,
7062306a36Sopenharmony_ci			pen->subreport_list[i].id,
7162306a36Sopenharmony_ci			i < (ARRAY_SIZE(pen->subreport_list) - 1) ? "," : "");
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci	hid_dbg(hdev, "\t}\n");
7462306a36Sopenharmony_ci	hid_dbg(hdev, "\t.inrange = %s\n",
7562306a36Sopenharmony_ci		uclogic_params_pen_inrange_to_str(pen->inrange));
7662306a36Sopenharmony_ci	hid_dbg(hdev, "\t.fragmented_hires = %s\n",
7762306a36Sopenharmony_ci		(pen->fragmented_hires ? "true" : "false"));
7862306a36Sopenharmony_ci	hid_dbg(hdev, "\t.tilt_y_flipped = %s\n",
7962306a36Sopenharmony_ci		(pen->tilt_y_flipped ? "true" : "false"));
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci/**
8362306a36Sopenharmony_ci * uclogic_params_frame_hid_dbg() - Dump tablet interface frame parameters
8462306a36Sopenharmony_ci * @hdev:	The HID device the pen parameters describe.
8562306a36Sopenharmony_ci * @frame:	The frame parameters to dump.
8662306a36Sopenharmony_ci *
8762306a36Sopenharmony_ci * Dump tablet interface frame parameters with hid_dbg(). The dump is
8862306a36Sopenharmony_ci * indented with two tabs.
8962306a36Sopenharmony_ci */
9062306a36Sopenharmony_cistatic void uclogic_params_frame_hid_dbg(
9162306a36Sopenharmony_ci				const struct hid_device *hdev,
9262306a36Sopenharmony_ci				const struct uclogic_params_frame *frame)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.desc_ptr = %p\n", frame->desc_ptr);
9562306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.desc_size = %u\n", frame->desc_size);
9662306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.id = %u\n", frame->id);
9762306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.suffix = %s\n", frame->suffix);
9862306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.re_lsb = %u\n", frame->re_lsb);
9962306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.dev_id_byte = %u\n", frame->dev_id_byte);
10062306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.touch_byte = %u\n", frame->touch_byte);
10162306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.touch_max = %hhd\n", frame->touch_max);
10262306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.touch_flip_at = %hhd\n",
10362306a36Sopenharmony_ci		frame->touch_flip_at);
10462306a36Sopenharmony_ci	hid_dbg(hdev, "\t\t.bitmap_dial_byte = %u\n",
10562306a36Sopenharmony_ci		frame->bitmap_dial_byte);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/**
10962306a36Sopenharmony_ci * uclogic_params_hid_dbg() - Dump tablet interface parameters
11062306a36Sopenharmony_ci * @hdev:	The HID device the parameters describe.
11162306a36Sopenharmony_ci * @params:	The parameters to dump.
11262306a36Sopenharmony_ci *
11362306a36Sopenharmony_ci * Dump tablet interface parameters with hid_dbg().
11462306a36Sopenharmony_ci */
11562306a36Sopenharmony_civoid uclogic_params_hid_dbg(const struct hid_device *hdev,
11662306a36Sopenharmony_ci				const struct uclogic_params *params)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	size_t i;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	hid_dbg(hdev, ".invalid = %s\n",
12162306a36Sopenharmony_ci		params->invalid ? "true" : "false");
12262306a36Sopenharmony_ci	hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr);
12362306a36Sopenharmony_ci	hid_dbg(hdev, ".desc_size = %u\n", params->desc_size);
12462306a36Sopenharmony_ci	hid_dbg(hdev, ".pen = {\n");
12562306a36Sopenharmony_ci	uclogic_params_pen_hid_dbg(hdev, &params->pen);
12662306a36Sopenharmony_ci	hid_dbg(hdev, "\t}\n");
12762306a36Sopenharmony_ci	hid_dbg(hdev, ".frame_list = {\n");
12862306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) {
12962306a36Sopenharmony_ci		hid_dbg(hdev, "\t{\n");
13062306a36Sopenharmony_ci		uclogic_params_frame_hid_dbg(hdev, &params->frame_list[i]);
13162306a36Sopenharmony_ci		hid_dbg(hdev, "\t}%s\n",
13262306a36Sopenharmony_ci			i < (ARRAY_SIZE(params->frame_list) - 1) ? "," : "");
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci	hid_dbg(hdev, "}\n");
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci/**
13862306a36Sopenharmony_ci * uclogic_params_get_str_desc - retrieve a string descriptor from a HID
13962306a36Sopenharmony_ci * device interface, putting it into a kmalloc-allocated buffer as is, without
14062306a36Sopenharmony_ci * character encoding conversion.
14162306a36Sopenharmony_ci *
14262306a36Sopenharmony_ci * @pbuf:	Location for the kmalloc-allocated buffer pointer containing
14362306a36Sopenharmony_ci *		the retrieved descriptor. Not modified in case of error.
14462306a36Sopenharmony_ci *		Can be NULL to have retrieved descriptor discarded.
14562306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface to retrieve the string
14662306a36Sopenharmony_ci *		descriptor from. Cannot be NULL.
14762306a36Sopenharmony_ci * @idx:	Index of the string descriptor to request from the device.
14862306a36Sopenharmony_ci * @len:	Length of the buffer to allocate and the data to retrieve.
14962306a36Sopenharmony_ci *
15062306a36Sopenharmony_ci * Returns:
15162306a36Sopenharmony_ci *	number of bytes retrieved (<= len),
15262306a36Sopenharmony_ci *	-EPIPE, if the descriptor was not found, or
15362306a36Sopenharmony_ci *	another negative errno code in case of other error.
15462306a36Sopenharmony_ci */
15562306a36Sopenharmony_cistatic int uclogic_params_get_str_desc(__u8 **pbuf, struct hid_device *hdev,
15662306a36Sopenharmony_ci					__u8 idx, size_t len)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	int rc;
15962306a36Sopenharmony_ci	struct usb_device *udev;
16062306a36Sopenharmony_ci	__u8 *buf = NULL;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	/* Check arguments */
16362306a36Sopenharmony_ci	if (hdev == NULL) {
16462306a36Sopenharmony_ci		rc = -EINVAL;
16562306a36Sopenharmony_ci		goto cleanup;
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	udev = hid_to_usb_dev(hdev);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	buf = kmalloc(len, GFP_KERNEL);
17162306a36Sopenharmony_ci	if (buf == NULL) {
17262306a36Sopenharmony_ci		rc = -ENOMEM;
17362306a36Sopenharmony_ci		goto cleanup;
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
17762306a36Sopenharmony_ci				USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
17862306a36Sopenharmony_ci				(USB_DT_STRING << 8) + idx,
17962306a36Sopenharmony_ci				0x0409, buf, len,
18062306a36Sopenharmony_ci				USB_CTRL_GET_TIMEOUT);
18162306a36Sopenharmony_ci	if (rc == -EPIPE) {
18262306a36Sopenharmony_ci		hid_dbg(hdev, "string descriptor #%hhu not found\n", idx);
18362306a36Sopenharmony_ci		goto cleanup;
18462306a36Sopenharmony_ci	} else if (rc < 0) {
18562306a36Sopenharmony_ci		hid_err(hdev,
18662306a36Sopenharmony_ci			"failed retrieving string descriptor #%u: %d\n",
18762306a36Sopenharmony_ci			idx, rc);
18862306a36Sopenharmony_ci		goto cleanup;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (pbuf != NULL) {
19262306a36Sopenharmony_ci		*pbuf = buf;
19362306a36Sopenharmony_ci		buf = NULL;
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cicleanup:
19762306a36Sopenharmony_ci	kfree(buf);
19862306a36Sopenharmony_ci	return rc;
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci/**
20262306a36Sopenharmony_ci * uclogic_params_pen_cleanup - free resources used by struct
20362306a36Sopenharmony_ci * uclogic_params_pen (tablet interface's pen input parameters).
20462306a36Sopenharmony_ci * Can be called repeatedly.
20562306a36Sopenharmony_ci *
20662306a36Sopenharmony_ci * @pen:	Pen input parameters to cleanup. Cannot be NULL.
20762306a36Sopenharmony_ci */
20862306a36Sopenharmony_cistatic void uclogic_params_pen_cleanup(struct uclogic_params_pen *pen)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	kfree(pen->desc_ptr);
21162306a36Sopenharmony_ci	memset(pen, 0, sizeof(*pen));
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci/**
21562306a36Sopenharmony_ci * uclogic_params_pen_init_v1() - initialize tablet interface pen
21662306a36Sopenharmony_ci * input and retrieve its parameters from the device, using v1 protocol.
21762306a36Sopenharmony_ci *
21862306a36Sopenharmony_ci * @pen:	Pointer to the pen parameters to initialize (to be
21962306a36Sopenharmony_ci *		cleaned up with uclogic_params_pen_cleanup()). Not modified in
22062306a36Sopenharmony_ci *		case of error, or if parameters are not found. Cannot be NULL.
22162306a36Sopenharmony_ci * @pfound:	Location for a flag which is set to true if the parameters
22262306a36Sopenharmony_ci *		were found, and to false if not (e.g. device was
22362306a36Sopenharmony_ci *		incompatible). Not modified in case of error. Cannot be NULL.
22462306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
22562306a36Sopenharmony_ci *		parameters from. Cannot be NULL.
22662306a36Sopenharmony_ci *
22762306a36Sopenharmony_ci * Returns:
22862306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
22962306a36Sopenharmony_ci */
23062306a36Sopenharmony_cistatic int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
23162306a36Sopenharmony_ci				      bool *pfound,
23262306a36Sopenharmony_ci				      struct hid_device *hdev)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	int rc;
23562306a36Sopenharmony_ci	bool found = false;
23662306a36Sopenharmony_ci	/* Buffer for (part of) the string descriptor */
23762306a36Sopenharmony_ci	__u8 *buf = NULL;
23862306a36Sopenharmony_ci	/* Minimum descriptor length required, maximum seen so far is 18 */
23962306a36Sopenharmony_ci	const int len = 12;
24062306a36Sopenharmony_ci	s32 resolution;
24162306a36Sopenharmony_ci	/* Pen report descriptor template parameters */
24262306a36Sopenharmony_ci	s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
24362306a36Sopenharmony_ci	__u8 *desc_ptr = NULL;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	/* Check arguments */
24662306a36Sopenharmony_ci	if (pen == NULL || pfound == NULL || hdev == NULL) {
24762306a36Sopenharmony_ci		rc = -EINVAL;
24862306a36Sopenharmony_ci		goto cleanup;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	/*
25262306a36Sopenharmony_ci	 * Read string descriptor containing pen input parameters.
25362306a36Sopenharmony_ci	 * The specific string descriptor and data were discovered by sniffing
25462306a36Sopenharmony_ci	 * the Windows driver traffic.
25562306a36Sopenharmony_ci	 * NOTE: This enables fully-functional tablet mode.
25662306a36Sopenharmony_ci	 */
25762306a36Sopenharmony_ci	rc = uclogic_params_get_str_desc(&buf, hdev, 100, len);
25862306a36Sopenharmony_ci	if (rc == -EPIPE) {
25962306a36Sopenharmony_ci		hid_dbg(hdev,
26062306a36Sopenharmony_ci			"string descriptor with pen parameters not found, assuming not compatible\n");
26162306a36Sopenharmony_ci		goto finish;
26262306a36Sopenharmony_ci	} else if (rc < 0) {
26362306a36Sopenharmony_ci		hid_err(hdev, "failed retrieving pen parameters: %d\n", rc);
26462306a36Sopenharmony_ci		goto cleanup;
26562306a36Sopenharmony_ci	} else if (rc != len) {
26662306a36Sopenharmony_ci		hid_dbg(hdev,
26762306a36Sopenharmony_ci			"string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n",
26862306a36Sopenharmony_ci			rc, len);
26962306a36Sopenharmony_ci		goto finish;
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	/*
27362306a36Sopenharmony_ci	 * Fill report descriptor parameters from the string descriptor
27462306a36Sopenharmony_ci	 */
27562306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
27662306a36Sopenharmony_ci		get_unaligned_le16(buf + 2);
27762306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
27862306a36Sopenharmony_ci		get_unaligned_le16(buf + 4);
27962306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
28062306a36Sopenharmony_ci		get_unaligned_le16(buf + 8);
28162306a36Sopenharmony_ci	resolution = get_unaligned_le16(buf + 10);
28262306a36Sopenharmony_ci	if (resolution == 0) {
28362306a36Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
28462306a36Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
28562306a36Sopenharmony_ci	} else {
28662306a36Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
28762306a36Sopenharmony_ci			desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
28862306a36Sopenharmony_ci			resolution;
28962306a36Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
29062306a36Sopenharmony_ci			desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
29162306a36Sopenharmony_ci			resolution;
29262306a36Sopenharmony_ci	}
29362306a36Sopenharmony_ci	kfree(buf);
29462306a36Sopenharmony_ci	buf = NULL;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	/*
29762306a36Sopenharmony_ci	 * Generate pen report descriptor
29862306a36Sopenharmony_ci	 */
29962306a36Sopenharmony_ci	desc_ptr = uclogic_rdesc_template_apply(
30062306a36Sopenharmony_ci				uclogic_rdesc_v1_pen_template_arr,
30162306a36Sopenharmony_ci				uclogic_rdesc_v1_pen_template_size,
30262306a36Sopenharmony_ci				desc_params, ARRAY_SIZE(desc_params));
30362306a36Sopenharmony_ci	if (desc_ptr == NULL) {
30462306a36Sopenharmony_ci		rc = -ENOMEM;
30562306a36Sopenharmony_ci		goto cleanup;
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	/*
30962306a36Sopenharmony_ci	 * Fill-in the parameters
31062306a36Sopenharmony_ci	 */
31162306a36Sopenharmony_ci	memset(pen, 0, sizeof(*pen));
31262306a36Sopenharmony_ci	pen->desc_ptr = desc_ptr;
31362306a36Sopenharmony_ci	desc_ptr = NULL;
31462306a36Sopenharmony_ci	pen->desc_size = uclogic_rdesc_v1_pen_template_size;
31562306a36Sopenharmony_ci	pen->id = UCLOGIC_RDESC_V1_PEN_ID;
31662306a36Sopenharmony_ci	pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED;
31762306a36Sopenharmony_ci	found = true;
31862306a36Sopenharmony_cifinish:
31962306a36Sopenharmony_ci	*pfound = found;
32062306a36Sopenharmony_ci	rc = 0;
32162306a36Sopenharmony_cicleanup:
32262306a36Sopenharmony_ci	kfree(desc_ptr);
32362306a36Sopenharmony_ci	kfree(buf);
32462306a36Sopenharmony_ci	return rc;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci/**
32862306a36Sopenharmony_ci * uclogic_params_get_le24() - get a 24-bit little-endian number from a
32962306a36Sopenharmony_ci * buffer.
33062306a36Sopenharmony_ci *
33162306a36Sopenharmony_ci * @p:	The pointer to the number buffer.
33262306a36Sopenharmony_ci *
33362306a36Sopenharmony_ci * Returns:
33462306a36Sopenharmony_ci *	The retrieved number
33562306a36Sopenharmony_ci */
33662306a36Sopenharmony_cistatic s32 uclogic_params_get_le24(const void *p)
33762306a36Sopenharmony_ci{
33862306a36Sopenharmony_ci	const __u8 *b = p;
33962306a36Sopenharmony_ci	return b[0] | (b[1] << 8UL) | (b[2] << 16UL);
34062306a36Sopenharmony_ci}
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci/**
34362306a36Sopenharmony_ci * uclogic_params_pen_init_v2() - initialize tablet interface pen
34462306a36Sopenharmony_ci * input and retrieve its parameters from the device, using v2 protocol.
34562306a36Sopenharmony_ci *
34662306a36Sopenharmony_ci * @pen:		Pointer to the pen parameters to initialize (to be
34762306a36Sopenharmony_ci *			cleaned up with uclogic_params_pen_cleanup()). Not
34862306a36Sopenharmony_ci *			modified in case of error, or if parameters are not
34962306a36Sopenharmony_ci *			found. Cannot be NULL.
35062306a36Sopenharmony_ci * @pfound:		Location for a flag which is set to true if the
35162306a36Sopenharmony_ci *			parameters were found, and to false if not (e.g.
35262306a36Sopenharmony_ci *			device was incompatible). Not modified in case of
35362306a36Sopenharmony_ci *			error. Cannot be NULL.
35462306a36Sopenharmony_ci * @pparams_ptr:	Location for a kmalloc'ed pointer to the retrieved raw
35562306a36Sopenharmony_ci *			parameters, which could be used to identify the tablet
35662306a36Sopenharmony_ci *			to some extent. Should be freed with kfree after use.
35762306a36Sopenharmony_ci *			NULL, if not needed. Not modified in case of error.
35862306a36Sopenharmony_ci *			Only set if *pfound is set to true.
35962306a36Sopenharmony_ci * @pparams_len:	Location for the length of the retrieved raw
36062306a36Sopenharmony_ci *			parameters. NULL, if not needed. Not modified in case
36162306a36Sopenharmony_ci *			of error. Only set if *pfound is set to true.
36262306a36Sopenharmony_ci * @hdev:		The HID device of the tablet interface to initialize
36362306a36Sopenharmony_ci *			and get parameters from. Cannot be NULL.
36462306a36Sopenharmony_ci *
36562306a36Sopenharmony_ci * Returns:
36662306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
36762306a36Sopenharmony_ci */
36862306a36Sopenharmony_cistatic int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen,
36962306a36Sopenharmony_ci					bool *pfound,
37062306a36Sopenharmony_ci					__u8 **pparams_ptr,
37162306a36Sopenharmony_ci					size_t *pparams_len,
37262306a36Sopenharmony_ci					struct hid_device *hdev)
37362306a36Sopenharmony_ci{
37462306a36Sopenharmony_ci	int rc;
37562306a36Sopenharmony_ci	bool found = false;
37662306a36Sopenharmony_ci	/* Buffer for (part of) the parameter string descriptor */
37762306a36Sopenharmony_ci	__u8 *buf = NULL;
37862306a36Sopenharmony_ci	/* Parameter string descriptor required length */
37962306a36Sopenharmony_ci	const int params_len_min = 18;
38062306a36Sopenharmony_ci	/* Parameter string descriptor accepted length */
38162306a36Sopenharmony_ci	const int params_len_max = 32;
38262306a36Sopenharmony_ci	/* Parameter string descriptor received length */
38362306a36Sopenharmony_ci	int params_len;
38462306a36Sopenharmony_ci	size_t i;
38562306a36Sopenharmony_ci	s32 resolution;
38662306a36Sopenharmony_ci	/* Pen report descriptor template parameters */
38762306a36Sopenharmony_ci	s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
38862306a36Sopenharmony_ci	__u8 *desc_ptr = NULL;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	/* Check arguments */
39162306a36Sopenharmony_ci	if (pen == NULL || pfound == NULL || hdev == NULL) {
39262306a36Sopenharmony_ci		rc = -EINVAL;
39362306a36Sopenharmony_ci		goto cleanup;
39462306a36Sopenharmony_ci	}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	/*
39762306a36Sopenharmony_ci	 * Read string descriptor containing pen input parameters.
39862306a36Sopenharmony_ci	 * The specific string descriptor and data were discovered by sniffing
39962306a36Sopenharmony_ci	 * the Windows driver traffic.
40062306a36Sopenharmony_ci	 * NOTE: This enables fully-functional tablet mode.
40162306a36Sopenharmony_ci	 */
40262306a36Sopenharmony_ci	rc = uclogic_params_get_str_desc(&buf, hdev, 200, params_len_max);
40362306a36Sopenharmony_ci	if (rc == -EPIPE) {
40462306a36Sopenharmony_ci		hid_dbg(hdev,
40562306a36Sopenharmony_ci			"string descriptor with pen parameters not found, assuming not compatible\n");
40662306a36Sopenharmony_ci		goto finish;
40762306a36Sopenharmony_ci	} else if (rc < 0) {
40862306a36Sopenharmony_ci		hid_err(hdev, "failed retrieving pen parameters: %d\n", rc);
40962306a36Sopenharmony_ci		goto cleanup;
41062306a36Sopenharmony_ci	} else if (rc < params_len_min) {
41162306a36Sopenharmony_ci		hid_dbg(hdev,
41262306a36Sopenharmony_ci			"string descriptor with pen parameters is too short (got %d, expected at least %d), assuming not compatible\n",
41362306a36Sopenharmony_ci			rc, params_len_min);
41462306a36Sopenharmony_ci		goto finish;
41562306a36Sopenharmony_ci	}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	params_len = rc;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	/*
42062306a36Sopenharmony_ci	 * Check it's not just a catch-all UTF-16LE-encoded ASCII
42162306a36Sopenharmony_ci	 * string (such as the model name) some tablets put into all
42262306a36Sopenharmony_ci	 * unknown string descriptors.
42362306a36Sopenharmony_ci	 */
42462306a36Sopenharmony_ci	for (i = 2;
42562306a36Sopenharmony_ci	     i < params_len &&
42662306a36Sopenharmony_ci		(buf[i] >= 0x20 && buf[i] < 0x7f && buf[i + 1] == 0);
42762306a36Sopenharmony_ci	     i += 2);
42862306a36Sopenharmony_ci	if (i >= params_len) {
42962306a36Sopenharmony_ci		hid_dbg(hdev,
43062306a36Sopenharmony_ci			"string descriptor with pen parameters seems to contain only text, assuming not compatible\n");
43162306a36Sopenharmony_ci		goto finish;
43262306a36Sopenharmony_ci	}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	/*
43562306a36Sopenharmony_ci	 * Fill report descriptor parameters from the string descriptor
43662306a36Sopenharmony_ci	 */
43762306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
43862306a36Sopenharmony_ci		uclogic_params_get_le24(buf + 2);
43962306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
44062306a36Sopenharmony_ci		uclogic_params_get_le24(buf + 5);
44162306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
44262306a36Sopenharmony_ci		get_unaligned_le16(buf + 8);
44362306a36Sopenharmony_ci	resolution = get_unaligned_le16(buf + 10);
44462306a36Sopenharmony_ci	if (resolution == 0) {
44562306a36Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
44662306a36Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
44762306a36Sopenharmony_ci	} else {
44862306a36Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
44962306a36Sopenharmony_ci			desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
45062306a36Sopenharmony_ci			resolution;
45162306a36Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
45262306a36Sopenharmony_ci			desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
45362306a36Sopenharmony_ci			resolution;
45462306a36Sopenharmony_ci	}
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	/*
45762306a36Sopenharmony_ci	 * Generate pen report descriptor
45862306a36Sopenharmony_ci	 */
45962306a36Sopenharmony_ci	desc_ptr = uclogic_rdesc_template_apply(
46062306a36Sopenharmony_ci				uclogic_rdesc_v2_pen_template_arr,
46162306a36Sopenharmony_ci				uclogic_rdesc_v2_pen_template_size,
46262306a36Sopenharmony_ci				desc_params, ARRAY_SIZE(desc_params));
46362306a36Sopenharmony_ci	if (desc_ptr == NULL) {
46462306a36Sopenharmony_ci		rc = -ENOMEM;
46562306a36Sopenharmony_ci		goto cleanup;
46662306a36Sopenharmony_ci	}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	/*
46962306a36Sopenharmony_ci	 * Fill-in the parameters
47062306a36Sopenharmony_ci	 */
47162306a36Sopenharmony_ci	memset(pen, 0, sizeof(*pen));
47262306a36Sopenharmony_ci	pen->desc_ptr = desc_ptr;
47362306a36Sopenharmony_ci	desc_ptr = NULL;
47462306a36Sopenharmony_ci	pen->desc_size = uclogic_rdesc_v2_pen_template_size;
47562306a36Sopenharmony_ci	pen->id = UCLOGIC_RDESC_V2_PEN_ID;
47662306a36Sopenharmony_ci	pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE;
47762306a36Sopenharmony_ci	pen->fragmented_hires = true;
47862306a36Sopenharmony_ci	pen->tilt_y_flipped = true;
47962306a36Sopenharmony_ci	found = true;
48062306a36Sopenharmony_ci	if (pparams_ptr != NULL) {
48162306a36Sopenharmony_ci		*pparams_ptr = buf;
48262306a36Sopenharmony_ci		buf = NULL;
48362306a36Sopenharmony_ci	}
48462306a36Sopenharmony_ci	if (pparams_len != NULL)
48562306a36Sopenharmony_ci		*pparams_len = params_len;
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_cifinish:
48862306a36Sopenharmony_ci	*pfound = found;
48962306a36Sopenharmony_ci	rc = 0;
49062306a36Sopenharmony_cicleanup:
49162306a36Sopenharmony_ci	kfree(desc_ptr);
49262306a36Sopenharmony_ci	kfree(buf);
49362306a36Sopenharmony_ci	return rc;
49462306a36Sopenharmony_ci}
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci/**
49762306a36Sopenharmony_ci * uclogic_params_frame_cleanup - free resources used by struct
49862306a36Sopenharmony_ci * uclogic_params_frame (tablet interface's frame controls input parameters).
49962306a36Sopenharmony_ci * Can be called repeatedly.
50062306a36Sopenharmony_ci *
50162306a36Sopenharmony_ci * @frame:	Frame controls input parameters to cleanup. Cannot be NULL.
50262306a36Sopenharmony_ci */
50362306a36Sopenharmony_cistatic void uclogic_params_frame_cleanup(struct uclogic_params_frame *frame)
50462306a36Sopenharmony_ci{
50562306a36Sopenharmony_ci	kfree(frame->desc_ptr);
50662306a36Sopenharmony_ci	memset(frame, 0, sizeof(*frame));
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci/**
51062306a36Sopenharmony_ci * uclogic_params_frame_init_with_desc() - initialize tablet's frame control
51162306a36Sopenharmony_ci * parameters with a static report descriptor.
51262306a36Sopenharmony_ci *
51362306a36Sopenharmony_ci * @frame:	Pointer to the frame parameters to initialize (to be cleaned
51462306a36Sopenharmony_ci *		up with uclogic_params_frame_cleanup()). Not modified in case
51562306a36Sopenharmony_ci *		of error. Cannot be NULL.
51662306a36Sopenharmony_ci * @desc_ptr:	Report descriptor pointer. Can be NULL, if desc_size is zero.
51762306a36Sopenharmony_ci * @desc_size:	Report descriptor size.
51862306a36Sopenharmony_ci * @id:		Report ID used for frame reports, if they should be tweaked,
51962306a36Sopenharmony_ci *		zero if not.
52062306a36Sopenharmony_ci *
52162306a36Sopenharmony_ci * Returns:
52262306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
52362306a36Sopenharmony_ci */
52462306a36Sopenharmony_cistatic int uclogic_params_frame_init_with_desc(
52562306a36Sopenharmony_ci					struct uclogic_params_frame *frame,
52662306a36Sopenharmony_ci					const __u8 *desc_ptr,
52762306a36Sopenharmony_ci					size_t desc_size,
52862306a36Sopenharmony_ci					unsigned int id)
52962306a36Sopenharmony_ci{
53062306a36Sopenharmony_ci	__u8 *copy_desc_ptr;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	if (frame == NULL || (desc_ptr == NULL && desc_size != 0))
53362306a36Sopenharmony_ci		return -EINVAL;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	copy_desc_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL);
53662306a36Sopenharmony_ci	if (copy_desc_ptr == NULL)
53762306a36Sopenharmony_ci		return -ENOMEM;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	memset(frame, 0, sizeof(*frame));
54062306a36Sopenharmony_ci	frame->desc_ptr = copy_desc_ptr;
54162306a36Sopenharmony_ci	frame->desc_size = desc_size;
54262306a36Sopenharmony_ci	frame->id = id;
54362306a36Sopenharmony_ci	return 0;
54462306a36Sopenharmony_ci}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci/**
54762306a36Sopenharmony_ci * uclogic_params_frame_init_v1() - initialize v1 tablet interface frame
54862306a36Sopenharmony_ci * controls.
54962306a36Sopenharmony_ci *
55062306a36Sopenharmony_ci * @frame:	Pointer to the frame parameters to initialize (to be cleaned
55162306a36Sopenharmony_ci *		up with uclogic_params_frame_cleanup()). Not modified in case
55262306a36Sopenharmony_ci *		of error, or if parameters are not found. Cannot be NULL.
55362306a36Sopenharmony_ci * @pfound:	Location for a flag which is set to true if the parameters
55462306a36Sopenharmony_ci *		were found, and to false if not (e.g. device was
55562306a36Sopenharmony_ci *		incompatible). Not modified in case of error. Cannot be NULL.
55662306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
55762306a36Sopenharmony_ci *		parameters from. Cannot be NULL.
55862306a36Sopenharmony_ci *
55962306a36Sopenharmony_ci * Returns:
56062306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
56162306a36Sopenharmony_ci */
56262306a36Sopenharmony_cistatic int uclogic_params_frame_init_v1(struct uclogic_params_frame *frame,
56362306a36Sopenharmony_ci					bool *pfound,
56462306a36Sopenharmony_ci					struct hid_device *hdev)
56562306a36Sopenharmony_ci{
56662306a36Sopenharmony_ci	int rc;
56762306a36Sopenharmony_ci	bool found = false;
56862306a36Sopenharmony_ci	struct usb_device *usb_dev;
56962306a36Sopenharmony_ci	char *str_buf = NULL;
57062306a36Sopenharmony_ci	const size_t str_len = 16;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	/* Check arguments */
57362306a36Sopenharmony_ci	if (frame == NULL || pfound == NULL || hdev == NULL) {
57462306a36Sopenharmony_ci		rc = -EINVAL;
57562306a36Sopenharmony_ci		goto cleanup;
57662306a36Sopenharmony_ci	}
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci	usb_dev = hid_to_usb_dev(hdev);
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	/*
58162306a36Sopenharmony_ci	 * Enable generic button mode
58262306a36Sopenharmony_ci	 */
58362306a36Sopenharmony_ci	str_buf = kzalloc(str_len, GFP_KERNEL);
58462306a36Sopenharmony_ci	if (str_buf == NULL) {
58562306a36Sopenharmony_ci		rc = -ENOMEM;
58662306a36Sopenharmony_ci		goto cleanup;
58762306a36Sopenharmony_ci	}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	rc = usb_string(usb_dev, 123, str_buf, str_len);
59062306a36Sopenharmony_ci	if (rc == -EPIPE) {
59162306a36Sopenharmony_ci		hid_dbg(hdev,
59262306a36Sopenharmony_ci			"generic button -enabling string descriptor not found\n");
59362306a36Sopenharmony_ci	} else if (rc < 0) {
59462306a36Sopenharmony_ci		goto cleanup;
59562306a36Sopenharmony_ci	} else if (strncmp(str_buf, "HK On", rc) != 0) {
59662306a36Sopenharmony_ci		hid_dbg(hdev,
59762306a36Sopenharmony_ci			"invalid response to enabling generic buttons: \"%s\"\n",
59862306a36Sopenharmony_ci			str_buf);
59962306a36Sopenharmony_ci	} else {
60062306a36Sopenharmony_ci		hid_dbg(hdev, "generic buttons enabled\n");
60162306a36Sopenharmony_ci		rc = uclogic_params_frame_init_with_desc(
60262306a36Sopenharmony_ci				frame,
60362306a36Sopenharmony_ci				uclogic_rdesc_v1_frame_arr,
60462306a36Sopenharmony_ci				uclogic_rdesc_v1_frame_size,
60562306a36Sopenharmony_ci				UCLOGIC_RDESC_V1_FRAME_ID);
60662306a36Sopenharmony_ci		if (rc != 0)
60762306a36Sopenharmony_ci			goto cleanup;
60862306a36Sopenharmony_ci		found = true;
60962306a36Sopenharmony_ci	}
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	*pfound = found;
61262306a36Sopenharmony_ci	rc = 0;
61362306a36Sopenharmony_cicleanup:
61462306a36Sopenharmony_ci	kfree(str_buf);
61562306a36Sopenharmony_ci	return rc;
61662306a36Sopenharmony_ci}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci/**
61962306a36Sopenharmony_ci * uclogic_params_cleanup_event_hooks - free resources used by the list of raw
62062306a36Sopenharmony_ci * event hooks.
62162306a36Sopenharmony_ci * Can be called repeatedly.
62262306a36Sopenharmony_ci *
62362306a36Sopenharmony_ci * @params: Input parameters to cleanup. Cannot be NULL.
62462306a36Sopenharmony_ci */
62562306a36Sopenharmony_cistatic void uclogic_params_cleanup_event_hooks(struct uclogic_params *params)
62662306a36Sopenharmony_ci{
62762306a36Sopenharmony_ci	struct uclogic_raw_event_hook *curr, *n;
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	if (!params || !params->event_hooks)
63062306a36Sopenharmony_ci		return;
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	list_for_each_entry_safe(curr, n, &params->event_hooks->list, list) {
63362306a36Sopenharmony_ci		cancel_work_sync(&curr->work);
63462306a36Sopenharmony_ci		list_del(&curr->list);
63562306a36Sopenharmony_ci		kfree(curr->event);
63662306a36Sopenharmony_ci		kfree(curr);
63762306a36Sopenharmony_ci	}
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	kfree(params->event_hooks);
64062306a36Sopenharmony_ci	params->event_hooks = NULL;
64162306a36Sopenharmony_ci}
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci/**
64462306a36Sopenharmony_ci * uclogic_params_cleanup - free resources used by struct uclogic_params
64562306a36Sopenharmony_ci * (tablet interface's parameters).
64662306a36Sopenharmony_ci * Can be called repeatedly.
64762306a36Sopenharmony_ci *
64862306a36Sopenharmony_ci * @params:	Input parameters to cleanup. Cannot be NULL.
64962306a36Sopenharmony_ci */
65062306a36Sopenharmony_civoid uclogic_params_cleanup(struct uclogic_params *params)
65162306a36Sopenharmony_ci{
65262306a36Sopenharmony_ci	if (!params->invalid) {
65362306a36Sopenharmony_ci		size_t i;
65462306a36Sopenharmony_ci		kfree(params->desc_ptr);
65562306a36Sopenharmony_ci		uclogic_params_pen_cleanup(&params->pen);
65662306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
65762306a36Sopenharmony_ci			uclogic_params_frame_cleanup(&params->frame_list[i]);
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci		uclogic_params_cleanup_event_hooks(params);
66062306a36Sopenharmony_ci		memset(params, 0, sizeof(*params));
66162306a36Sopenharmony_ci	}
66262306a36Sopenharmony_ci}
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci/**
66562306a36Sopenharmony_ci * uclogic_params_get_desc() - Get a replacement report descriptor for a
66662306a36Sopenharmony_ci *                             tablet's interface.
66762306a36Sopenharmony_ci *
66862306a36Sopenharmony_ci * @params:	The parameters of a tablet interface to get report
66962306a36Sopenharmony_ci *		descriptor for. Cannot be NULL.
67062306a36Sopenharmony_ci * @pdesc:	Location for the resulting, kmalloc-allocated report
67162306a36Sopenharmony_ci *		descriptor pointer, or for NULL, if there's no replacement
67262306a36Sopenharmony_ci *		report descriptor. Not modified in case of error. Cannot be
67362306a36Sopenharmony_ci *		NULL.
67462306a36Sopenharmony_ci * @psize:	Location for the resulting report descriptor size, not set if
67562306a36Sopenharmony_ci *		there's no replacement report descriptor. Not modified in case
67662306a36Sopenharmony_ci *		of error. Cannot be NULL.
67762306a36Sopenharmony_ci *
67862306a36Sopenharmony_ci * Returns:
67962306a36Sopenharmony_ci *	Zero, if successful.
68062306a36Sopenharmony_ci *	-EINVAL, if invalid arguments are supplied.
68162306a36Sopenharmony_ci *	-ENOMEM, if failed to allocate memory.
68262306a36Sopenharmony_ci */
68362306a36Sopenharmony_ciint uclogic_params_get_desc(const struct uclogic_params *params,
68462306a36Sopenharmony_ci				__u8 **pdesc,
68562306a36Sopenharmony_ci				unsigned int *psize)
68662306a36Sopenharmony_ci{
68762306a36Sopenharmony_ci	int rc = -ENOMEM;
68862306a36Sopenharmony_ci	bool present = false;
68962306a36Sopenharmony_ci	unsigned int size = 0;
69062306a36Sopenharmony_ci	__u8 *desc = NULL;
69162306a36Sopenharmony_ci	size_t i;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	/* Check arguments */
69462306a36Sopenharmony_ci	if (params == NULL || pdesc == NULL || psize == NULL)
69562306a36Sopenharmony_ci		return -EINVAL;
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	/* Concatenate descriptors */
69862306a36Sopenharmony_ci#define ADD_DESC(_desc_ptr, _desc_size) \
69962306a36Sopenharmony_ci	do {                                                        \
70062306a36Sopenharmony_ci		unsigned int new_size;                              \
70162306a36Sopenharmony_ci		__u8 *new_desc;                                     \
70262306a36Sopenharmony_ci		if ((_desc_ptr) == NULL) {                          \
70362306a36Sopenharmony_ci			break;                                      \
70462306a36Sopenharmony_ci		}                                                   \
70562306a36Sopenharmony_ci		new_size = size + (_desc_size);                     \
70662306a36Sopenharmony_ci		new_desc = krealloc(desc, new_size, GFP_KERNEL);    \
70762306a36Sopenharmony_ci		if (new_desc == NULL) {                             \
70862306a36Sopenharmony_ci			goto cleanup;                               \
70962306a36Sopenharmony_ci		}                                                   \
71062306a36Sopenharmony_ci		memcpy(new_desc + size, (_desc_ptr), (_desc_size)); \
71162306a36Sopenharmony_ci		desc = new_desc;                                    \
71262306a36Sopenharmony_ci		size = new_size;                                    \
71362306a36Sopenharmony_ci		present = true;                                     \
71462306a36Sopenharmony_ci	} while (0)
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_ci	ADD_DESC(params->desc_ptr, params->desc_size);
71762306a36Sopenharmony_ci	ADD_DESC(params->pen.desc_ptr, params->pen.desc_size);
71862306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) {
71962306a36Sopenharmony_ci		ADD_DESC(params->frame_list[i].desc_ptr,
72062306a36Sopenharmony_ci				params->frame_list[i].desc_size);
72162306a36Sopenharmony_ci	}
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci#undef ADD_DESC
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci	if (present) {
72662306a36Sopenharmony_ci		*pdesc = desc;
72762306a36Sopenharmony_ci		*psize = size;
72862306a36Sopenharmony_ci		desc = NULL;
72962306a36Sopenharmony_ci	}
73062306a36Sopenharmony_ci	rc = 0;
73162306a36Sopenharmony_cicleanup:
73262306a36Sopenharmony_ci	kfree(desc);
73362306a36Sopenharmony_ci	return rc;
73462306a36Sopenharmony_ci}
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci/**
73762306a36Sopenharmony_ci * uclogic_params_init_invalid() - initialize tablet interface parameters,
73862306a36Sopenharmony_ci * specifying the interface is invalid.
73962306a36Sopenharmony_ci *
74062306a36Sopenharmony_ci * @params:		Parameters to initialize (to be cleaned with
74162306a36Sopenharmony_ci *			uclogic_params_cleanup()). Cannot be NULL.
74262306a36Sopenharmony_ci */
74362306a36Sopenharmony_cistatic void uclogic_params_init_invalid(struct uclogic_params *params)
74462306a36Sopenharmony_ci{
74562306a36Sopenharmony_ci	params->invalid = true;
74662306a36Sopenharmony_ci}
74762306a36Sopenharmony_ci
74862306a36Sopenharmony_ci/**
74962306a36Sopenharmony_ci * uclogic_params_init_with_opt_desc() - initialize tablet interface
75062306a36Sopenharmony_ci * parameters with an optional replacement report descriptor. Only modify
75162306a36Sopenharmony_ci * report descriptor, if the original report descriptor matches the expected
75262306a36Sopenharmony_ci * size.
75362306a36Sopenharmony_ci *
75462306a36Sopenharmony_ci * @params:		Parameters to initialize (to be cleaned with
75562306a36Sopenharmony_ci *			uclogic_params_cleanup()). Not modified in case of
75662306a36Sopenharmony_ci *			error. Cannot be NULL.
75762306a36Sopenharmony_ci * @hdev:		The HID device of the tablet interface create the
75862306a36Sopenharmony_ci *			parameters for. Cannot be NULL.
75962306a36Sopenharmony_ci * @orig_desc_size:	Expected size of the original report descriptor to
76062306a36Sopenharmony_ci *			be replaced.
76162306a36Sopenharmony_ci * @desc_ptr:		Pointer to the replacement report descriptor.
76262306a36Sopenharmony_ci *			Can be NULL, if desc_size is zero.
76362306a36Sopenharmony_ci * @desc_size:		Size of the replacement report descriptor.
76462306a36Sopenharmony_ci *
76562306a36Sopenharmony_ci * Returns:
76662306a36Sopenharmony_ci *	Zero, if successful. -EINVAL if an invalid argument was passed.
76762306a36Sopenharmony_ci *	-ENOMEM, if failed to allocate memory.
76862306a36Sopenharmony_ci */
76962306a36Sopenharmony_cistatic int uclogic_params_init_with_opt_desc(struct uclogic_params *params,
77062306a36Sopenharmony_ci					     struct hid_device *hdev,
77162306a36Sopenharmony_ci					     unsigned int orig_desc_size,
77262306a36Sopenharmony_ci					     __u8 *desc_ptr,
77362306a36Sopenharmony_ci					     unsigned int desc_size)
77462306a36Sopenharmony_ci{
77562306a36Sopenharmony_ci	__u8 *desc_copy_ptr = NULL;
77662306a36Sopenharmony_ci	unsigned int desc_copy_size;
77762306a36Sopenharmony_ci	int rc;
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	/* Check arguments */
78062306a36Sopenharmony_ci	if (params == NULL || hdev == NULL ||
78162306a36Sopenharmony_ci	    (desc_ptr == NULL && desc_size != 0)) {
78262306a36Sopenharmony_ci		rc = -EINVAL;
78362306a36Sopenharmony_ci		goto cleanup;
78462306a36Sopenharmony_ci	}
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_ci	/* Replace report descriptor, if it matches */
78762306a36Sopenharmony_ci	if (hdev->dev_rsize == orig_desc_size) {
78862306a36Sopenharmony_ci		hid_dbg(hdev,
78962306a36Sopenharmony_ci			"device report descriptor matches the expected size, replacing\n");
79062306a36Sopenharmony_ci		desc_copy_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL);
79162306a36Sopenharmony_ci		if (desc_copy_ptr == NULL) {
79262306a36Sopenharmony_ci			rc = -ENOMEM;
79362306a36Sopenharmony_ci			goto cleanup;
79462306a36Sopenharmony_ci		}
79562306a36Sopenharmony_ci		desc_copy_size = desc_size;
79662306a36Sopenharmony_ci	} else {
79762306a36Sopenharmony_ci		hid_dbg(hdev,
79862306a36Sopenharmony_ci			"device report descriptor doesn't match the expected size (%u != %u), preserving\n",
79962306a36Sopenharmony_ci			hdev->dev_rsize, orig_desc_size);
80062306a36Sopenharmony_ci		desc_copy_ptr = NULL;
80162306a36Sopenharmony_ci		desc_copy_size = 0;
80262306a36Sopenharmony_ci	}
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci	/* Output parameters */
80562306a36Sopenharmony_ci	memset(params, 0, sizeof(*params));
80662306a36Sopenharmony_ci	params->desc_ptr = desc_copy_ptr;
80762306a36Sopenharmony_ci	desc_copy_ptr = NULL;
80862306a36Sopenharmony_ci	params->desc_size = desc_copy_size;
80962306a36Sopenharmony_ci
81062306a36Sopenharmony_ci	rc = 0;
81162306a36Sopenharmony_cicleanup:
81262306a36Sopenharmony_ci	kfree(desc_copy_ptr);
81362306a36Sopenharmony_ci	return rc;
81462306a36Sopenharmony_ci}
81562306a36Sopenharmony_ci
81662306a36Sopenharmony_ci/**
81762306a36Sopenharmony_ci * uclogic_params_huion_init() - initialize a Huion tablet interface and discover
81862306a36Sopenharmony_ci * its parameters.
81962306a36Sopenharmony_ci *
82062306a36Sopenharmony_ci * @params:	Parameters to fill in (to be cleaned with
82162306a36Sopenharmony_ci *		uclogic_params_cleanup()). Not modified in case of error.
82262306a36Sopenharmony_ci *		Cannot be NULL.
82362306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
82462306a36Sopenharmony_ci *		parameters from. Cannot be NULL.
82562306a36Sopenharmony_ci *
82662306a36Sopenharmony_ci * Returns:
82762306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
82862306a36Sopenharmony_ci */
82962306a36Sopenharmony_cistatic int uclogic_params_huion_init(struct uclogic_params *params,
83062306a36Sopenharmony_ci				     struct hid_device *hdev)
83162306a36Sopenharmony_ci{
83262306a36Sopenharmony_ci	int rc;
83362306a36Sopenharmony_ci	struct usb_device *udev;
83462306a36Sopenharmony_ci	struct usb_interface *iface;
83562306a36Sopenharmony_ci	__u8 bInterfaceNumber;
83662306a36Sopenharmony_ci	bool found;
83762306a36Sopenharmony_ci	/* The resulting parameters (noop) */
83862306a36Sopenharmony_ci	struct uclogic_params p = {0, };
83962306a36Sopenharmony_ci	static const char transition_ver[] = "HUION_T153_160607";
84062306a36Sopenharmony_ci	char *ver_ptr = NULL;
84162306a36Sopenharmony_ci	const size_t ver_len = sizeof(transition_ver) + 1;
84262306a36Sopenharmony_ci	__u8 *params_ptr = NULL;
84362306a36Sopenharmony_ci	size_t params_len = 0;
84462306a36Sopenharmony_ci	/* Parameters string descriptor of a model with touch ring (HS610) */
84562306a36Sopenharmony_ci	const __u8 touch_ring_model_params_buf[] = {
84662306a36Sopenharmony_ci		0x13, 0x03, 0x70, 0xC6, 0x00, 0x06, 0x7C, 0x00,
84762306a36Sopenharmony_ci		0xFF, 0x1F, 0xD8, 0x13, 0x03, 0x0D, 0x10, 0x01,
84862306a36Sopenharmony_ci		0x04, 0x3C, 0x3E
84962306a36Sopenharmony_ci	};
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	/* Check arguments */
85262306a36Sopenharmony_ci	if (params == NULL || hdev == NULL) {
85362306a36Sopenharmony_ci		rc = -EINVAL;
85462306a36Sopenharmony_ci		goto cleanup;
85562306a36Sopenharmony_ci	}
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	udev = hid_to_usb_dev(hdev);
85862306a36Sopenharmony_ci	iface = to_usb_interface(hdev->dev.parent);
85962306a36Sopenharmony_ci	bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	/* If it's a custom keyboard interface */
86262306a36Sopenharmony_ci	if (bInterfaceNumber == 1) {
86362306a36Sopenharmony_ci		/* Keep everything intact, but mark pen usage invalid */
86462306a36Sopenharmony_ci		p.pen.usage_invalid = true;
86562306a36Sopenharmony_ci		goto output;
86662306a36Sopenharmony_ci	/* Else, if it's not a pen interface */
86762306a36Sopenharmony_ci	} else if (bInterfaceNumber != 0) {
86862306a36Sopenharmony_ci		uclogic_params_init_invalid(&p);
86962306a36Sopenharmony_ci		goto output;
87062306a36Sopenharmony_ci	}
87162306a36Sopenharmony_ci
87262306a36Sopenharmony_ci	/* Try to get firmware version */
87362306a36Sopenharmony_ci	ver_ptr = kzalloc(ver_len, GFP_KERNEL);
87462306a36Sopenharmony_ci	if (ver_ptr == NULL) {
87562306a36Sopenharmony_ci		rc = -ENOMEM;
87662306a36Sopenharmony_ci		goto cleanup;
87762306a36Sopenharmony_ci	}
87862306a36Sopenharmony_ci	rc = usb_string(udev, 201, ver_ptr, ver_len);
87962306a36Sopenharmony_ci	if (rc == -EPIPE) {
88062306a36Sopenharmony_ci		*ver_ptr = '\0';
88162306a36Sopenharmony_ci	} else if (rc < 0) {
88262306a36Sopenharmony_ci		hid_err(hdev,
88362306a36Sopenharmony_ci			"failed retrieving Huion firmware version: %d\n", rc);
88462306a36Sopenharmony_ci		goto cleanup;
88562306a36Sopenharmony_ci	}
88662306a36Sopenharmony_ci
88762306a36Sopenharmony_ci	/* If this is a transition firmware */
88862306a36Sopenharmony_ci	if (strcmp(ver_ptr, transition_ver) == 0) {
88962306a36Sopenharmony_ci		hid_dbg(hdev,
89062306a36Sopenharmony_ci			"transition firmware detected, not probing pen v2 parameters\n");
89162306a36Sopenharmony_ci	} else {
89262306a36Sopenharmony_ci		/* Try to probe v2 pen parameters */
89362306a36Sopenharmony_ci		rc = uclogic_params_pen_init_v2(&p.pen, &found,
89462306a36Sopenharmony_ci						&params_ptr, &params_len,
89562306a36Sopenharmony_ci						hdev);
89662306a36Sopenharmony_ci		if (rc != 0) {
89762306a36Sopenharmony_ci			hid_err(hdev,
89862306a36Sopenharmony_ci				"failed probing pen v2 parameters: %d\n", rc);
89962306a36Sopenharmony_ci			goto cleanup;
90062306a36Sopenharmony_ci		} else if (found) {
90162306a36Sopenharmony_ci			hid_dbg(hdev, "pen v2 parameters found\n");
90262306a36Sopenharmony_ci			/* Create v2 frame button parameters */
90362306a36Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
90462306a36Sopenharmony_ci					&p.frame_list[0],
90562306a36Sopenharmony_ci					uclogic_rdesc_v2_frame_buttons_arr,
90662306a36Sopenharmony_ci					uclogic_rdesc_v2_frame_buttons_size,
90762306a36Sopenharmony_ci					UCLOGIC_RDESC_V2_FRAME_BUTTONS_ID);
90862306a36Sopenharmony_ci			if (rc != 0) {
90962306a36Sopenharmony_ci				hid_err(hdev,
91062306a36Sopenharmony_ci					"failed creating v2 frame button parameters: %d\n",
91162306a36Sopenharmony_ci					rc);
91262306a36Sopenharmony_ci				goto cleanup;
91362306a36Sopenharmony_ci			}
91462306a36Sopenharmony_ci
91562306a36Sopenharmony_ci			/* Link from pen sub-report */
91662306a36Sopenharmony_ci			p.pen.subreport_list[0].value = 0xe0;
91762306a36Sopenharmony_ci			p.pen.subreport_list[0].id =
91862306a36Sopenharmony_ci				UCLOGIC_RDESC_V2_FRAME_BUTTONS_ID;
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci			/* If this is the model with touch ring */
92162306a36Sopenharmony_ci			if (params_ptr != NULL &&
92262306a36Sopenharmony_ci			    params_len == sizeof(touch_ring_model_params_buf) &&
92362306a36Sopenharmony_ci			    memcmp(params_ptr, touch_ring_model_params_buf,
92462306a36Sopenharmony_ci				   params_len) == 0) {
92562306a36Sopenharmony_ci				/* Create touch ring parameters */
92662306a36Sopenharmony_ci				rc = uclogic_params_frame_init_with_desc(
92762306a36Sopenharmony_ci					&p.frame_list[1],
92862306a36Sopenharmony_ci					uclogic_rdesc_v2_frame_touch_ring_arr,
92962306a36Sopenharmony_ci					uclogic_rdesc_v2_frame_touch_ring_size,
93062306a36Sopenharmony_ci					UCLOGIC_RDESC_V2_FRAME_TOUCH_ID);
93162306a36Sopenharmony_ci				if (rc != 0) {
93262306a36Sopenharmony_ci					hid_err(hdev,
93362306a36Sopenharmony_ci						"failed creating v2 frame touch ring parameters: %d\n",
93462306a36Sopenharmony_ci						rc);
93562306a36Sopenharmony_ci					goto cleanup;
93662306a36Sopenharmony_ci				}
93762306a36Sopenharmony_ci				p.frame_list[1].suffix = "Touch Ring";
93862306a36Sopenharmony_ci				p.frame_list[1].dev_id_byte =
93962306a36Sopenharmony_ci					UCLOGIC_RDESC_V2_FRAME_TOUCH_DEV_ID_BYTE;
94062306a36Sopenharmony_ci				p.frame_list[1].touch_byte = 5;
94162306a36Sopenharmony_ci				p.frame_list[1].touch_max = 12;
94262306a36Sopenharmony_ci				p.frame_list[1].touch_flip_at = 7;
94362306a36Sopenharmony_ci			} else {
94462306a36Sopenharmony_ci				/* Create touch strip parameters */
94562306a36Sopenharmony_ci				rc = uclogic_params_frame_init_with_desc(
94662306a36Sopenharmony_ci					&p.frame_list[1],
94762306a36Sopenharmony_ci					uclogic_rdesc_v2_frame_touch_strip_arr,
94862306a36Sopenharmony_ci					uclogic_rdesc_v2_frame_touch_strip_size,
94962306a36Sopenharmony_ci					UCLOGIC_RDESC_V2_FRAME_TOUCH_ID);
95062306a36Sopenharmony_ci				if (rc != 0) {
95162306a36Sopenharmony_ci					hid_err(hdev,
95262306a36Sopenharmony_ci						"failed creating v2 frame touch strip parameters: %d\n",
95362306a36Sopenharmony_ci						rc);
95462306a36Sopenharmony_ci					goto cleanup;
95562306a36Sopenharmony_ci				}
95662306a36Sopenharmony_ci				p.frame_list[1].suffix = "Touch Strip";
95762306a36Sopenharmony_ci				p.frame_list[1].dev_id_byte =
95862306a36Sopenharmony_ci					UCLOGIC_RDESC_V2_FRAME_TOUCH_DEV_ID_BYTE;
95962306a36Sopenharmony_ci				p.frame_list[1].touch_byte = 5;
96062306a36Sopenharmony_ci				p.frame_list[1].touch_max = 8;
96162306a36Sopenharmony_ci			}
96262306a36Sopenharmony_ci
96362306a36Sopenharmony_ci			/* Link from pen sub-report */
96462306a36Sopenharmony_ci			p.pen.subreport_list[1].value = 0xf0;
96562306a36Sopenharmony_ci			p.pen.subreport_list[1].id =
96662306a36Sopenharmony_ci				UCLOGIC_RDESC_V2_FRAME_TOUCH_ID;
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_ci			/* Create v2 frame dial parameters */
96962306a36Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
97062306a36Sopenharmony_ci					&p.frame_list[2],
97162306a36Sopenharmony_ci					uclogic_rdesc_v2_frame_dial_arr,
97262306a36Sopenharmony_ci					uclogic_rdesc_v2_frame_dial_size,
97362306a36Sopenharmony_ci					UCLOGIC_RDESC_V2_FRAME_DIAL_ID);
97462306a36Sopenharmony_ci			if (rc != 0) {
97562306a36Sopenharmony_ci				hid_err(hdev,
97662306a36Sopenharmony_ci					"failed creating v2 frame dial parameters: %d\n",
97762306a36Sopenharmony_ci					rc);
97862306a36Sopenharmony_ci				goto cleanup;
97962306a36Sopenharmony_ci			}
98062306a36Sopenharmony_ci			p.frame_list[2].suffix = "Dial";
98162306a36Sopenharmony_ci			p.frame_list[2].dev_id_byte =
98262306a36Sopenharmony_ci				UCLOGIC_RDESC_V2_FRAME_DIAL_DEV_ID_BYTE;
98362306a36Sopenharmony_ci			p.frame_list[2].bitmap_dial_byte = 5;
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci			/* Link from pen sub-report */
98662306a36Sopenharmony_ci			p.pen.subreport_list[2].value = 0xf1;
98762306a36Sopenharmony_ci			p.pen.subreport_list[2].id =
98862306a36Sopenharmony_ci				UCLOGIC_RDESC_V2_FRAME_DIAL_ID;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci			goto output;
99162306a36Sopenharmony_ci		}
99262306a36Sopenharmony_ci		hid_dbg(hdev, "pen v2 parameters not found\n");
99362306a36Sopenharmony_ci	}
99462306a36Sopenharmony_ci
99562306a36Sopenharmony_ci	/* Try to probe v1 pen parameters */
99662306a36Sopenharmony_ci	rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
99762306a36Sopenharmony_ci	if (rc != 0) {
99862306a36Sopenharmony_ci		hid_err(hdev,
99962306a36Sopenharmony_ci			"failed probing pen v1 parameters: %d\n", rc);
100062306a36Sopenharmony_ci		goto cleanup;
100162306a36Sopenharmony_ci	} else if (found) {
100262306a36Sopenharmony_ci		hid_dbg(hdev, "pen v1 parameters found\n");
100362306a36Sopenharmony_ci		/* Try to probe v1 frame */
100462306a36Sopenharmony_ci		rc = uclogic_params_frame_init_v1(&p.frame_list[0],
100562306a36Sopenharmony_ci						  &found, hdev);
100662306a36Sopenharmony_ci		if (rc != 0) {
100762306a36Sopenharmony_ci			hid_err(hdev, "v1 frame probing failed: %d\n", rc);
100862306a36Sopenharmony_ci			goto cleanup;
100962306a36Sopenharmony_ci		}
101062306a36Sopenharmony_ci		hid_dbg(hdev, "frame v1 parameters%s found\n",
101162306a36Sopenharmony_ci			(found ? "" : " not"));
101262306a36Sopenharmony_ci		if (found) {
101362306a36Sopenharmony_ci			/* Link frame button subreports from pen reports */
101462306a36Sopenharmony_ci			p.pen.subreport_list[0].value = 0xe0;
101562306a36Sopenharmony_ci			p.pen.subreport_list[0].id =
101662306a36Sopenharmony_ci				UCLOGIC_RDESC_V1_FRAME_ID;
101762306a36Sopenharmony_ci		}
101862306a36Sopenharmony_ci		goto output;
101962306a36Sopenharmony_ci	}
102062306a36Sopenharmony_ci	hid_dbg(hdev, "pen v1 parameters not found\n");
102162306a36Sopenharmony_ci
102262306a36Sopenharmony_ci	uclogic_params_init_invalid(&p);
102362306a36Sopenharmony_ci
102462306a36Sopenharmony_cioutput:
102562306a36Sopenharmony_ci	/* Output parameters */
102662306a36Sopenharmony_ci	memcpy(params, &p, sizeof(*params));
102762306a36Sopenharmony_ci	memset(&p, 0, sizeof(p));
102862306a36Sopenharmony_ci	rc = 0;
102962306a36Sopenharmony_cicleanup:
103062306a36Sopenharmony_ci	kfree(params_ptr);
103162306a36Sopenharmony_ci	kfree(ver_ptr);
103262306a36Sopenharmony_ci	uclogic_params_cleanup(&p);
103362306a36Sopenharmony_ci	return rc;
103462306a36Sopenharmony_ci}
103562306a36Sopenharmony_ci
103662306a36Sopenharmony_ci/**
103762306a36Sopenharmony_ci * uclogic_probe_interface() - some tablets, like the Parblo A610 PLUS V2 or
103862306a36Sopenharmony_ci * the XP-PEN Deco Mini 7, need to be initialized by sending them magic data.
103962306a36Sopenharmony_ci *
104062306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
104162306a36Sopenharmony_ci *		parameters from. Cannot be NULL.
104262306a36Sopenharmony_ci * @magic_arr:	The magic data that should be sent to probe the interface.
104362306a36Sopenharmony_ci *		Cannot be NULL.
104462306a36Sopenharmony_ci * @magic_size:	Size of the magic data.
104562306a36Sopenharmony_ci * @endpoint:	Endpoint where the magic data should be sent.
104662306a36Sopenharmony_ci *
104762306a36Sopenharmony_ci * Returns:
104862306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
104962306a36Sopenharmony_ci */
105062306a36Sopenharmony_cistatic int uclogic_probe_interface(struct hid_device *hdev, const u8 *magic_arr,
105162306a36Sopenharmony_ci				   size_t magic_size, int endpoint)
105262306a36Sopenharmony_ci{
105362306a36Sopenharmony_ci	struct usb_device *udev;
105462306a36Sopenharmony_ci	unsigned int pipe = 0;
105562306a36Sopenharmony_ci	int sent;
105662306a36Sopenharmony_ci	u8 *buf = NULL;
105762306a36Sopenharmony_ci	int rc = 0;
105862306a36Sopenharmony_ci
105962306a36Sopenharmony_ci	if (!hdev || !magic_arr) {
106062306a36Sopenharmony_ci		rc = -EINVAL;
106162306a36Sopenharmony_ci		goto cleanup;
106262306a36Sopenharmony_ci	}
106362306a36Sopenharmony_ci
106462306a36Sopenharmony_ci	buf = kmemdup(magic_arr, magic_size, GFP_KERNEL);
106562306a36Sopenharmony_ci	if (!buf) {
106662306a36Sopenharmony_ci		rc = -ENOMEM;
106762306a36Sopenharmony_ci		goto cleanup;
106862306a36Sopenharmony_ci	}
106962306a36Sopenharmony_ci
107062306a36Sopenharmony_ci	udev = hid_to_usb_dev(hdev);
107162306a36Sopenharmony_ci	pipe = usb_sndintpipe(udev, endpoint);
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci	rc = usb_interrupt_msg(udev, pipe, buf, magic_size, &sent, 1000);
107462306a36Sopenharmony_ci	if (rc || sent != magic_size) {
107562306a36Sopenharmony_ci		hid_err(hdev, "Interface probing failed: %d\n", rc);
107662306a36Sopenharmony_ci		rc = -1;
107762306a36Sopenharmony_ci		goto cleanup;
107862306a36Sopenharmony_ci	}
107962306a36Sopenharmony_ci
108062306a36Sopenharmony_ci	rc = 0;
108162306a36Sopenharmony_cicleanup:
108262306a36Sopenharmony_ci	kfree(buf);
108362306a36Sopenharmony_ci	return rc;
108462306a36Sopenharmony_ci}
108562306a36Sopenharmony_ci
108662306a36Sopenharmony_ci/**
108762306a36Sopenharmony_ci * uclogic_params_parse_ugee_v2_desc - parse the string descriptor containing
108862306a36Sopenharmony_ci * pen and frame parameters returned by UGEE v2 devices.
108962306a36Sopenharmony_ci *
109062306a36Sopenharmony_ci * @str_desc:		String descriptor, cannot be NULL.
109162306a36Sopenharmony_ci * @str_desc_size:	Size of the string descriptor.
109262306a36Sopenharmony_ci * @desc_params:	Output description params list.
109362306a36Sopenharmony_ci * @desc_params_size:	Size of the output description params list.
109462306a36Sopenharmony_ci * @frame_type:		Output frame type.
109562306a36Sopenharmony_ci *
109662306a36Sopenharmony_ci * Returns:
109762306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
109862306a36Sopenharmony_ci */
109962306a36Sopenharmony_cistatic int uclogic_params_parse_ugee_v2_desc(const __u8 *str_desc,
110062306a36Sopenharmony_ci					     size_t str_desc_size,
110162306a36Sopenharmony_ci					     s32 *desc_params,
110262306a36Sopenharmony_ci					     size_t desc_params_size,
110362306a36Sopenharmony_ci					     enum uclogic_params_frame_type *frame_type)
110462306a36Sopenharmony_ci{
110562306a36Sopenharmony_ci	s32 pen_x_lm, pen_y_lm;
110662306a36Sopenharmony_ci	s32 pen_x_pm, pen_y_pm;
110762306a36Sopenharmony_ci	s32 pen_pressure_lm;
110862306a36Sopenharmony_ci	s32 frame_num_buttons;
110962306a36Sopenharmony_ci	s32 resolution;
111062306a36Sopenharmony_ci
111162306a36Sopenharmony_ci	/* Minimum descriptor length required, maximum seen so far is 14 */
111262306a36Sopenharmony_ci	const int min_str_desc_size = 12;
111362306a36Sopenharmony_ci
111462306a36Sopenharmony_ci	if (!str_desc || str_desc_size < min_str_desc_size)
111562306a36Sopenharmony_ci		return -EINVAL;
111662306a36Sopenharmony_ci
111762306a36Sopenharmony_ci	if (desc_params_size != UCLOGIC_RDESC_PH_ID_NUM)
111862306a36Sopenharmony_ci		return -EINVAL;
111962306a36Sopenharmony_ci
112062306a36Sopenharmony_ci	pen_x_lm = get_unaligned_le16(str_desc + 2);
112162306a36Sopenharmony_ci	pen_y_lm = get_unaligned_le16(str_desc + 4);
112262306a36Sopenharmony_ci	frame_num_buttons = str_desc[6];
112362306a36Sopenharmony_ci	*frame_type = str_desc[7];
112462306a36Sopenharmony_ci	pen_pressure_lm = get_unaligned_le16(str_desc + 8);
112562306a36Sopenharmony_ci
112662306a36Sopenharmony_ci	resolution = get_unaligned_le16(str_desc + 10);
112762306a36Sopenharmony_ci	if (resolution == 0) {
112862306a36Sopenharmony_ci		pen_x_pm = 0;
112962306a36Sopenharmony_ci		pen_y_pm = 0;
113062306a36Sopenharmony_ci	} else {
113162306a36Sopenharmony_ci		pen_x_pm = pen_x_lm * 1000 / resolution;
113262306a36Sopenharmony_ci		pen_y_pm = pen_y_lm * 1000 / resolution;
113362306a36Sopenharmony_ci	}
113462306a36Sopenharmony_ci
113562306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] = pen_x_lm;
113662306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = pen_x_pm;
113762306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] = pen_y_lm;
113862306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = pen_y_pm;
113962306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] = pen_pressure_lm;
114062306a36Sopenharmony_ci	desc_params[UCLOGIC_RDESC_FRAME_PH_ID_UM] = frame_num_buttons;
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	return 0;
114362306a36Sopenharmony_ci}
114462306a36Sopenharmony_ci
114562306a36Sopenharmony_ci/**
114662306a36Sopenharmony_ci * uclogic_params_ugee_v2_init_frame_buttons() - initialize a UGEE v2 frame with
114762306a36Sopenharmony_ci * buttons.
114862306a36Sopenharmony_ci * @p:			Parameters to fill in, cannot be NULL.
114962306a36Sopenharmony_ci * @desc_params:	Device description params list.
115062306a36Sopenharmony_ci * @desc_params_size:	Size of the description params list.
115162306a36Sopenharmony_ci *
115262306a36Sopenharmony_ci * Returns:
115362306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
115462306a36Sopenharmony_ci */
115562306a36Sopenharmony_cistatic int uclogic_params_ugee_v2_init_frame_buttons(struct uclogic_params *p,
115662306a36Sopenharmony_ci						     const s32 *desc_params,
115762306a36Sopenharmony_ci						     size_t desc_params_size)
115862306a36Sopenharmony_ci{
115962306a36Sopenharmony_ci	__u8 *rdesc_frame = NULL;
116062306a36Sopenharmony_ci	int rc = 0;
116162306a36Sopenharmony_ci
116262306a36Sopenharmony_ci	if (!p || desc_params_size != UCLOGIC_RDESC_PH_ID_NUM)
116362306a36Sopenharmony_ci		return -EINVAL;
116462306a36Sopenharmony_ci
116562306a36Sopenharmony_ci	rdesc_frame = uclogic_rdesc_template_apply(
116662306a36Sopenharmony_ci				uclogic_rdesc_ugee_v2_frame_btn_template_arr,
116762306a36Sopenharmony_ci				uclogic_rdesc_ugee_v2_frame_btn_template_size,
116862306a36Sopenharmony_ci				desc_params, UCLOGIC_RDESC_PH_ID_NUM);
116962306a36Sopenharmony_ci	if (!rdesc_frame)
117062306a36Sopenharmony_ci		return -ENOMEM;
117162306a36Sopenharmony_ci
117262306a36Sopenharmony_ci	rc = uclogic_params_frame_init_with_desc(&p->frame_list[0],
117362306a36Sopenharmony_ci						 rdesc_frame,
117462306a36Sopenharmony_ci						 uclogic_rdesc_ugee_v2_frame_btn_template_size,
117562306a36Sopenharmony_ci						 UCLOGIC_RDESC_V1_FRAME_ID);
117662306a36Sopenharmony_ci	kfree(rdesc_frame);
117762306a36Sopenharmony_ci	return rc;
117862306a36Sopenharmony_ci}
117962306a36Sopenharmony_ci
118062306a36Sopenharmony_ci/**
118162306a36Sopenharmony_ci * uclogic_params_ugee_v2_init_frame_dial() - initialize a UGEE v2 frame with a
118262306a36Sopenharmony_ci * bitmap dial.
118362306a36Sopenharmony_ci * @p:			Parameters to fill in, cannot be NULL.
118462306a36Sopenharmony_ci * @desc_params:	Device description params list.
118562306a36Sopenharmony_ci * @desc_params_size:	Size of the description params list.
118662306a36Sopenharmony_ci *
118762306a36Sopenharmony_ci * Returns:
118862306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
118962306a36Sopenharmony_ci */
119062306a36Sopenharmony_cistatic int uclogic_params_ugee_v2_init_frame_dial(struct uclogic_params *p,
119162306a36Sopenharmony_ci						  const s32 *desc_params,
119262306a36Sopenharmony_ci						  size_t desc_params_size)
119362306a36Sopenharmony_ci{
119462306a36Sopenharmony_ci	__u8 *rdesc_frame = NULL;
119562306a36Sopenharmony_ci	int rc = 0;
119662306a36Sopenharmony_ci
119762306a36Sopenharmony_ci	if (!p || desc_params_size != UCLOGIC_RDESC_PH_ID_NUM)
119862306a36Sopenharmony_ci		return -EINVAL;
119962306a36Sopenharmony_ci
120062306a36Sopenharmony_ci	rdesc_frame = uclogic_rdesc_template_apply(
120162306a36Sopenharmony_ci				uclogic_rdesc_ugee_v2_frame_dial_template_arr,
120262306a36Sopenharmony_ci				uclogic_rdesc_ugee_v2_frame_dial_template_size,
120362306a36Sopenharmony_ci				desc_params, UCLOGIC_RDESC_PH_ID_NUM);
120462306a36Sopenharmony_ci	if (!rdesc_frame)
120562306a36Sopenharmony_ci		return -ENOMEM;
120662306a36Sopenharmony_ci
120762306a36Sopenharmony_ci	rc = uclogic_params_frame_init_with_desc(&p->frame_list[0],
120862306a36Sopenharmony_ci						 rdesc_frame,
120962306a36Sopenharmony_ci						 uclogic_rdesc_ugee_v2_frame_dial_template_size,
121062306a36Sopenharmony_ci						 UCLOGIC_RDESC_V1_FRAME_ID);
121162306a36Sopenharmony_ci	kfree(rdesc_frame);
121262306a36Sopenharmony_ci	if (rc)
121362306a36Sopenharmony_ci		return rc;
121462306a36Sopenharmony_ci
121562306a36Sopenharmony_ci	p->frame_list[0].bitmap_dial_byte = 7;
121662306a36Sopenharmony_ci	return 0;
121762306a36Sopenharmony_ci}
121862306a36Sopenharmony_ci
121962306a36Sopenharmony_ci/**
122062306a36Sopenharmony_ci * uclogic_params_ugee_v2_init_frame_mouse() - initialize a UGEE v2 frame with a
122162306a36Sopenharmony_ci * mouse.
122262306a36Sopenharmony_ci * @p:			Parameters to fill in, cannot be NULL.
122362306a36Sopenharmony_ci *
122462306a36Sopenharmony_ci * Returns:
122562306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
122662306a36Sopenharmony_ci */
122762306a36Sopenharmony_cistatic int uclogic_params_ugee_v2_init_frame_mouse(struct uclogic_params *p)
122862306a36Sopenharmony_ci{
122962306a36Sopenharmony_ci	int rc = 0;
123062306a36Sopenharmony_ci
123162306a36Sopenharmony_ci	if (!p)
123262306a36Sopenharmony_ci		return -EINVAL;
123362306a36Sopenharmony_ci
123462306a36Sopenharmony_ci	rc = uclogic_params_frame_init_with_desc(&p->frame_list[1],
123562306a36Sopenharmony_ci						 uclogic_rdesc_ugee_v2_frame_mouse_template_arr,
123662306a36Sopenharmony_ci						 uclogic_rdesc_ugee_v2_frame_mouse_template_size,
123762306a36Sopenharmony_ci						 UCLOGIC_RDESC_V1_FRAME_ID);
123862306a36Sopenharmony_ci	return rc;
123962306a36Sopenharmony_ci}
124062306a36Sopenharmony_ci
124162306a36Sopenharmony_ci/**
124262306a36Sopenharmony_ci * uclogic_params_ugee_v2_has_battery() - check whether a UGEE v2 device has
124362306a36Sopenharmony_ci * battery or not.
124462306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface.
124562306a36Sopenharmony_ci *
124662306a36Sopenharmony_ci * Returns:
124762306a36Sopenharmony_ci *	True if the device has battery, false otherwise.
124862306a36Sopenharmony_ci */
124962306a36Sopenharmony_cistatic bool uclogic_params_ugee_v2_has_battery(struct hid_device *hdev)
125062306a36Sopenharmony_ci{
125162306a36Sopenharmony_ci	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
125262306a36Sopenharmony_ci
125362306a36Sopenharmony_ci	if (drvdata->quirks & UCLOGIC_BATTERY_QUIRK)
125462306a36Sopenharmony_ci		return true;
125562306a36Sopenharmony_ci
125662306a36Sopenharmony_ci	/* The XP-PEN Deco LW vendor, product and version are identical to the
125762306a36Sopenharmony_ci	 * Deco L. The only difference reported by their firmware is the product
125862306a36Sopenharmony_ci	 * name. Add a quirk to support battery reporting on the wireless
125962306a36Sopenharmony_ci	 * version.
126062306a36Sopenharmony_ci	 */
126162306a36Sopenharmony_ci	if (hdev->vendor == USB_VENDOR_ID_UGEE &&
126262306a36Sopenharmony_ci	    hdev->product == USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L) {
126362306a36Sopenharmony_ci		struct usb_device *udev = hid_to_usb_dev(hdev);
126462306a36Sopenharmony_ci
126562306a36Sopenharmony_ci		if (strstarts(udev->product, "Deco LW"))
126662306a36Sopenharmony_ci			return true;
126762306a36Sopenharmony_ci	}
126862306a36Sopenharmony_ci
126962306a36Sopenharmony_ci	return false;
127062306a36Sopenharmony_ci}
127162306a36Sopenharmony_ci
127262306a36Sopenharmony_ci/**
127362306a36Sopenharmony_ci * uclogic_params_ugee_v2_init_battery() - initialize UGEE v2 battery reporting.
127462306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface, cannot be NULL.
127562306a36Sopenharmony_ci * @p:		Parameters to fill in, cannot be NULL.
127662306a36Sopenharmony_ci *
127762306a36Sopenharmony_ci * Returns:
127862306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
127962306a36Sopenharmony_ci */
128062306a36Sopenharmony_cistatic int uclogic_params_ugee_v2_init_battery(struct hid_device *hdev,
128162306a36Sopenharmony_ci					       struct uclogic_params *p)
128262306a36Sopenharmony_ci{
128362306a36Sopenharmony_ci	int rc = 0;
128462306a36Sopenharmony_ci
128562306a36Sopenharmony_ci	if (!hdev || !p)
128662306a36Sopenharmony_ci		return -EINVAL;
128762306a36Sopenharmony_ci
128862306a36Sopenharmony_ci	/* Some tablets contain invalid characters in hdev->uniq, throwing a
128962306a36Sopenharmony_ci	 * "hwmon: '<name>' is not a valid name attribute, please fix" error.
129062306a36Sopenharmony_ci	 * Use the device vendor and product IDs instead.
129162306a36Sopenharmony_ci	 */
129262306a36Sopenharmony_ci	snprintf(hdev->uniq, sizeof(hdev->uniq), "%x-%x", hdev->vendor,
129362306a36Sopenharmony_ci		 hdev->product);
129462306a36Sopenharmony_ci
129562306a36Sopenharmony_ci	rc = uclogic_params_frame_init_with_desc(&p->frame_list[1],
129662306a36Sopenharmony_ci						 uclogic_rdesc_ugee_v2_battery_template_arr,
129762306a36Sopenharmony_ci						 uclogic_rdesc_ugee_v2_battery_template_size,
129862306a36Sopenharmony_ci						 UCLOGIC_RDESC_UGEE_V2_BATTERY_ID);
129962306a36Sopenharmony_ci	if (rc)
130062306a36Sopenharmony_ci		return rc;
130162306a36Sopenharmony_ci
130262306a36Sopenharmony_ci	p->frame_list[1].suffix = "Battery";
130362306a36Sopenharmony_ci	p->pen.subreport_list[1].value = 0xf2;
130462306a36Sopenharmony_ci	p->pen.subreport_list[1].id = UCLOGIC_RDESC_UGEE_V2_BATTERY_ID;
130562306a36Sopenharmony_ci
130662306a36Sopenharmony_ci	return rc;
130762306a36Sopenharmony_ci}
130862306a36Sopenharmony_ci
130962306a36Sopenharmony_ci/**
131062306a36Sopenharmony_ci * uclogic_params_ugee_v2_reconnect_work() - When a wireless tablet looses
131162306a36Sopenharmony_ci * connection to the USB dongle and reconnects, either because of its physical
131262306a36Sopenharmony_ci * distance or because it was switches off and on using the frame's switch,
131362306a36Sopenharmony_ci * uclogic_probe_interface() needs to be called again to enable the tablet.
131462306a36Sopenharmony_ci *
131562306a36Sopenharmony_ci * @work: The work that triggered this function.
131662306a36Sopenharmony_ci */
131762306a36Sopenharmony_cistatic void uclogic_params_ugee_v2_reconnect_work(struct work_struct *work)
131862306a36Sopenharmony_ci{
131962306a36Sopenharmony_ci	struct uclogic_raw_event_hook *event_hook;
132062306a36Sopenharmony_ci
132162306a36Sopenharmony_ci	event_hook = container_of(work, struct uclogic_raw_event_hook, work);
132262306a36Sopenharmony_ci	uclogic_probe_interface(event_hook->hdev, uclogic_ugee_v2_probe_arr,
132362306a36Sopenharmony_ci				uclogic_ugee_v2_probe_size,
132462306a36Sopenharmony_ci				uclogic_ugee_v2_probe_endpoint);
132562306a36Sopenharmony_ci}
132662306a36Sopenharmony_ci
132762306a36Sopenharmony_ci/**
132862306a36Sopenharmony_ci * uclogic_params_ugee_v2_init_event_hooks() - initialize the list of events
132962306a36Sopenharmony_ci * to be hooked for UGEE v2 devices.
133062306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
133162306a36Sopenharmony_ci *		parameters from.
133262306a36Sopenharmony_ci * @p:		Parameters to fill in, cannot be NULL.
133362306a36Sopenharmony_ci *
133462306a36Sopenharmony_ci * Returns:
133562306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
133662306a36Sopenharmony_ci */
133762306a36Sopenharmony_cistatic int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev,
133862306a36Sopenharmony_ci						   struct uclogic_params *p)
133962306a36Sopenharmony_ci{
134062306a36Sopenharmony_ci	struct uclogic_raw_event_hook *event_hook;
134162306a36Sopenharmony_ci	__u8 reconnect_event[] = {
134262306a36Sopenharmony_ci		/* Event received on wireless tablet reconnection */
134362306a36Sopenharmony_ci		0x02, 0xF8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
134462306a36Sopenharmony_ci	};
134562306a36Sopenharmony_ci
134662306a36Sopenharmony_ci	if (!p)
134762306a36Sopenharmony_ci		return -EINVAL;
134862306a36Sopenharmony_ci
134962306a36Sopenharmony_ci	/* The reconnection event is only received if the tablet has battery */
135062306a36Sopenharmony_ci	if (!uclogic_params_ugee_v2_has_battery(hdev))
135162306a36Sopenharmony_ci		return 0;
135262306a36Sopenharmony_ci
135362306a36Sopenharmony_ci	p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL);
135462306a36Sopenharmony_ci	if (!p->event_hooks)
135562306a36Sopenharmony_ci		return -ENOMEM;
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_ci	INIT_LIST_HEAD(&p->event_hooks->list);
135862306a36Sopenharmony_ci
135962306a36Sopenharmony_ci	event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL);
136062306a36Sopenharmony_ci	if (!event_hook)
136162306a36Sopenharmony_ci		return -ENOMEM;
136262306a36Sopenharmony_ci
136362306a36Sopenharmony_ci	INIT_WORK(&event_hook->work, uclogic_params_ugee_v2_reconnect_work);
136462306a36Sopenharmony_ci	event_hook->hdev = hdev;
136562306a36Sopenharmony_ci	event_hook->size = ARRAY_SIZE(reconnect_event);
136662306a36Sopenharmony_ci	event_hook->event = kmemdup(reconnect_event, event_hook->size, GFP_KERNEL);
136762306a36Sopenharmony_ci	if (!event_hook->event)
136862306a36Sopenharmony_ci		return -ENOMEM;
136962306a36Sopenharmony_ci
137062306a36Sopenharmony_ci	list_add_tail(&event_hook->list, &p->event_hooks->list);
137162306a36Sopenharmony_ci
137262306a36Sopenharmony_ci	return 0;
137362306a36Sopenharmony_ci}
137462306a36Sopenharmony_ci
137562306a36Sopenharmony_ci/**
137662306a36Sopenharmony_ci * uclogic_params_ugee_v2_init() - initialize a UGEE graphics tablets by
137762306a36Sopenharmony_ci * discovering their parameters.
137862306a36Sopenharmony_ci *
137962306a36Sopenharmony_ci * These tables, internally designed as v2 to differentiate them from older
138062306a36Sopenharmony_ci * models, expect a payload of magic data in orther to be switched to the fully
138162306a36Sopenharmony_ci * functional mode and expose their parameters in a similar way to the
138262306a36Sopenharmony_ci * information present in uclogic_params_pen_init_v1() but with some
138362306a36Sopenharmony_ci * differences.
138462306a36Sopenharmony_ci *
138562306a36Sopenharmony_ci * @params:	Parameters to fill in (to be cleaned with
138662306a36Sopenharmony_ci *		uclogic_params_cleanup()). Not modified in case of error.
138762306a36Sopenharmony_ci *		Cannot be NULL.
138862306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
138962306a36Sopenharmony_ci *		parameters from. Cannot be NULL.
139062306a36Sopenharmony_ci *
139162306a36Sopenharmony_ci * Returns:
139262306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
139362306a36Sopenharmony_ci */
139462306a36Sopenharmony_cistatic int uclogic_params_ugee_v2_init(struct uclogic_params *params,
139562306a36Sopenharmony_ci				       struct hid_device *hdev)
139662306a36Sopenharmony_ci{
139762306a36Sopenharmony_ci	int rc = 0;
139862306a36Sopenharmony_ci	struct uclogic_drvdata *drvdata;
139962306a36Sopenharmony_ci	struct usb_interface *iface;
140062306a36Sopenharmony_ci	__u8 bInterfaceNumber;
140162306a36Sopenharmony_ci	const int str_desc_len = 12;
140262306a36Sopenharmony_ci	__u8 *str_desc = NULL;
140362306a36Sopenharmony_ci	__u8 *rdesc_pen = NULL;
140462306a36Sopenharmony_ci	s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
140562306a36Sopenharmony_ci	enum uclogic_params_frame_type frame_type;
140662306a36Sopenharmony_ci	/* The resulting parameters (noop) */
140762306a36Sopenharmony_ci	struct uclogic_params p = {0, };
140862306a36Sopenharmony_ci
140962306a36Sopenharmony_ci	if (!params || !hdev) {
141062306a36Sopenharmony_ci		rc = -EINVAL;
141162306a36Sopenharmony_ci		goto cleanup;
141262306a36Sopenharmony_ci	}
141362306a36Sopenharmony_ci
141462306a36Sopenharmony_ci	drvdata = hid_get_drvdata(hdev);
141562306a36Sopenharmony_ci	iface = to_usb_interface(hdev->dev.parent);
141662306a36Sopenharmony_ci	bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
141762306a36Sopenharmony_ci
141862306a36Sopenharmony_ci	if (bInterfaceNumber == 0) {
141962306a36Sopenharmony_ci		rc = uclogic_params_ugee_v2_init_frame_mouse(&p);
142062306a36Sopenharmony_ci		if (rc)
142162306a36Sopenharmony_ci			goto cleanup;
142262306a36Sopenharmony_ci
142362306a36Sopenharmony_ci		goto output;
142462306a36Sopenharmony_ci	}
142562306a36Sopenharmony_ci
142662306a36Sopenharmony_ci	if (bInterfaceNumber != 2) {
142762306a36Sopenharmony_ci		uclogic_params_init_invalid(&p);
142862306a36Sopenharmony_ci		goto output;
142962306a36Sopenharmony_ci	}
143062306a36Sopenharmony_ci
143162306a36Sopenharmony_ci	/*
143262306a36Sopenharmony_ci	 * Initialize the interface by sending magic data.
143362306a36Sopenharmony_ci	 * The specific data was discovered by sniffing the Windows driver
143462306a36Sopenharmony_ci	 * traffic.
143562306a36Sopenharmony_ci	 */
143662306a36Sopenharmony_ci	rc = uclogic_probe_interface(hdev, uclogic_ugee_v2_probe_arr,
143762306a36Sopenharmony_ci				     uclogic_ugee_v2_probe_size,
143862306a36Sopenharmony_ci				     uclogic_ugee_v2_probe_endpoint);
143962306a36Sopenharmony_ci	if (rc) {
144062306a36Sopenharmony_ci		uclogic_params_init_invalid(&p);
144162306a36Sopenharmony_ci		goto output;
144262306a36Sopenharmony_ci	}
144362306a36Sopenharmony_ci
144462306a36Sopenharmony_ci	/*
144562306a36Sopenharmony_ci	 * Read the string descriptor containing pen and frame parameters.
144662306a36Sopenharmony_ci	 * The specific string descriptor and data were discovered by sniffing
144762306a36Sopenharmony_ci	 * the Windows driver traffic.
144862306a36Sopenharmony_ci	 */
144962306a36Sopenharmony_ci	rc = uclogic_params_get_str_desc(&str_desc, hdev, 100, str_desc_len);
145062306a36Sopenharmony_ci	if (rc != str_desc_len) {
145162306a36Sopenharmony_ci		hid_err(hdev, "failed retrieving pen and frame parameters: %d\n", rc);
145262306a36Sopenharmony_ci		uclogic_params_init_invalid(&p);
145362306a36Sopenharmony_ci		goto output;
145462306a36Sopenharmony_ci	}
145562306a36Sopenharmony_ci
145662306a36Sopenharmony_ci	rc = uclogic_params_parse_ugee_v2_desc(str_desc, str_desc_len,
145762306a36Sopenharmony_ci					       desc_params,
145862306a36Sopenharmony_ci					       ARRAY_SIZE(desc_params),
145962306a36Sopenharmony_ci					       &frame_type);
146062306a36Sopenharmony_ci	if (rc)
146162306a36Sopenharmony_ci		goto cleanup;
146262306a36Sopenharmony_ci
146362306a36Sopenharmony_ci	kfree(str_desc);
146462306a36Sopenharmony_ci	str_desc = NULL;
146562306a36Sopenharmony_ci
146662306a36Sopenharmony_ci	/* Initialize the pen interface */
146762306a36Sopenharmony_ci	rdesc_pen = uclogic_rdesc_template_apply(
146862306a36Sopenharmony_ci				uclogic_rdesc_ugee_v2_pen_template_arr,
146962306a36Sopenharmony_ci				uclogic_rdesc_ugee_v2_pen_template_size,
147062306a36Sopenharmony_ci				desc_params, ARRAY_SIZE(desc_params));
147162306a36Sopenharmony_ci	if (!rdesc_pen) {
147262306a36Sopenharmony_ci		rc = -ENOMEM;
147362306a36Sopenharmony_ci		goto cleanup;
147462306a36Sopenharmony_ci	}
147562306a36Sopenharmony_ci
147662306a36Sopenharmony_ci	p.pen.desc_ptr = rdesc_pen;
147762306a36Sopenharmony_ci	p.pen.desc_size = uclogic_rdesc_ugee_v2_pen_template_size;
147862306a36Sopenharmony_ci	p.pen.id = 0x02;
147962306a36Sopenharmony_ci	p.pen.subreport_list[0].value = 0xf0;
148062306a36Sopenharmony_ci	p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID;
148162306a36Sopenharmony_ci
148262306a36Sopenharmony_ci	/* Initialize the frame interface */
148362306a36Sopenharmony_ci	if (drvdata->quirks & UCLOGIC_MOUSE_FRAME_QUIRK)
148462306a36Sopenharmony_ci		frame_type = UCLOGIC_PARAMS_FRAME_MOUSE;
148562306a36Sopenharmony_ci
148662306a36Sopenharmony_ci	switch (frame_type) {
148762306a36Sopenharmony_ci	case UCLOGIC_PARAMS_FRAME_DIAL:
148862306a36Sopenharmony_ci	case UCLOGIC_PARAMS_FRAME_MOUSE:
148962306a36Sopenharmony_ci		rc = uclogic_params_ugee_v2_init_frame_dial(&p, desc_params,
149062306a36Sopenharmony_ci							    ARRAY_SIZE(desc_params));
149162306a36Sopenharmony_ci		break;
149262306a36Sopenharmony_ci	case UCLOGIC_PARAMS_FRAME_BUTTONS:
149362306a36Sopenharmony_ci	default:
149462306a36Sopenharmony_ci		rc = uclogic_params_ugee_v2_init_frame_buttons(&p, desc_params,
149562306a36Sopenharmony_ci							       ARRAY_SIZE(desc_params));
149662306a36Sopenharmony_ci		break;
149762306a36Sopenharmony_ci	}
149862306a36Sopenharmony_ci
149962306a36Sopenharmony_ci	if (rc)
150062306a36Sopenharmony_ci		goto cleanup;
150162306a36Sopenharmony_ci
150262306a36Sopenharmony_ci	/* Initialize the battery interface*/
150362306a36Sopenharmony_ci	if (uclogic_params_ugee_v2_has_battery(hdev)) {
150462306a36Sopenharmony_ci		rc = uclogic_params_ugee_v2_init_battery(hdev, &p);
150562306a36Sopenharmony_ci		if (rc) {
150662306a36Sopenharmony_ci			hid_err(hdev, "error initializing battery: %d\n", rc);
150762306a36Sopenharmony_ci			goto cleanup;
150862306a36Sopenharmony_ci		}
150962306a36Sopenharmony_ci	}
151062306a36Sopenharmony_ci
151162306a36Sopenharmony_ci	/* Create a list of raw events to be ignored */
151262306a36Sopenharmony_ci	rc = uclogic_params_ugee_v2_init_event_hooks(hdev, &p);
151362306a36Sopenharmony_ci	if (rc) {
151462306a36Sopenharmony_ci		hid_err(hdev, "error initializing event hook list: %d\n", rc);
151562306a36Sopenharmony_ci		goto cleanup;
151662306a36Sopenharmony_ci	}
151762306a36Sopenharmony_ci
151862306a36Sopenharmony_cioutput:
151962306a36Sopenharmony_ci	/* Output parameters */
152062306a36Sopenharmony_ci	memcpy(params, &p, sizeof(*params));
152162306a36Sopenharmony_ci	memset(&p, 0, sizeof(p));
152262306a36Sopenharmony_ci	rc = 0;
152362306a36Sopenharmony_cicleanup:
152462306a36Sopenharmony_ci	kfree(str_desc);
152562306a36Sopenharmony_ci	uclogic_params_cleanup(&p);
152662306a36Sopenharmony_ci	return rc;
152762306a36Sopenharmony_ci}
152862306a36Sopenharmony_ci
152962306a36Sopenharmony_ci/**
153062306a36Sopenharmony_ci * uclogic_params_init() - initialize a tablet interface and discover its
153162306a36Sopenharmony_ci * parameters.
153262306a36Sopenharmony_ci *
153362306a36Sopenharmony_ci * @params:	Parameters to fill in (to be cleaned with
153462306a36Sopenharmony_ci *		uclogic_params_cleanup()). Not modified in case of error.
153562306a36Sopenharmony_ci *		Cannot be NULL.
153662306a36Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
153762306a36Sopenharmony_ci *		parameters from. Cannot be NULL. Must be using the USB low-level
153862306a36Sopenharmony_ci *		driver, i.e. be an actual USB tablet.
153962306a36Sopenharmony_ci *
154062306a36Sopenharmony_ci * Returns:
154162306a36Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
154262306a36Sopenharmony_ci */
154362306a36Sopenharmony_ciint uclogic_params_init(struct uclogic_params *params,
154462306a36Sopenharmony_ci			struct hid_device *hdev)
154562306a36Sopenharmony_ci{
154662306a36Sopenharmony_ci	int rc;
154762306a36Sopenharmony_ci	struct usb_device *udev;
154862306a36Sopenharmony_ci	__u8  bNumInterfaces;
154962306a36Sopenharmony_ci	struct usb_interface *iface;
155062306a36Sopenharmony_ci	__u8 bInterfaceNumber;
155162306a36Sopenharmony_ci	bool found;
155262306a36Sopenharmony_ci	/* The resulting parameters (noop) */
155362306a36Sopenharmony_ci	struct uclogic_params p = {0, };
155462306a36Sopenharmony_ci
155562306a36Sopenharmony_ci	/* Check arguments */
155662306a36Sopenharmony_ci	if (params == NULL || hdev == NULL || !hid_is_usb(hdev)) {
155762306a36Sopenharmony_ci		rc = -EINVAL;
155862306a36Sopenharmony_ci		goto cleanup;
155962306a36Sopenharmony_ci	}
156062306a36Sopenharmony_ci
156162306a36Sopenharmony_ci	udev = hid_to_usb_dev(hdev);
156262306a36Sopenharmony_ci	bNumInterfaces = udev->config->desc.bNumInterfaces;
156362306a36Sopenharmony_ci	iface = to_usb_interface(hdev->dev.parent);
156462306a36Sopenharmony_ci	bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
156562306a36Sopenharmony_ci
156662306a36Sopenharmony_ci	/*
156762306a36Sopenharmony_ci	 * Set replacement report descriptor if the original matches the
156862306a36Sopenharmony_ci	 * specified size. Otherwise keep interface unchanged.
156962306a36Sopenharmony_ci	 */
157062306a36Sopenharmony_ci#define WITH_OPT_DESC(_orig_desc_token, _new_desc_token) \
157162306a36Sopenharmony_ci	uclogic_params_init_with_opt_desc(                  \
157262306a36Sopenharmony_ci		&p, hdev,                                   \
157362306a36Sopenharmony_ci		UCLOGIC_RDESC_##_orig_desc_token##_SIZE,    \
157462306a36Sopenharmony_ci		uclogic_rdesc_##_new_desc_token##_arr,      \
157562306a36Sopenharmony_ci		uclogic_rdesc_##_new_desc_token##_size)
157662306a36Sopenharmony_ci
157762306a36Sopenharmony_ci#define VID_PID(_vid, _pid) \
157862306a36Sopenharmony_ci	(((__u32)(_vid) << 16) | ((__u32)(_pid) & U16_MAX))
157962306a36Sopenharmony_ci
158062306a36Sopenharmony_ci	/*
158162306a36Sopenharmony_ci	 * Handle specific interfaces for specific tablets.
158262306a36Sopenharmony_ci	 *
158362306a36Sopenharmony_ci	 * Observe the following logic:
158462306a36Sopenharmony_ci	 *
158562306a36Sopenharmony_ci	 * If the interface is recognized as producing certain useful input:
158662306a36Sopenharmony_ci	 *	Mark interface as valid.
158762306a36Sopenharmony_ci	 *	Output interface parameters.
158862306a36Sopenharmony_ci	 * Else, if the interface is recognized as *not* producing any useful
158962306a36Sopenharmony_ci	 * input:
159062306a36Sopenharmony_ci	 *	Mark interface as invalid.
159162306a36Sopenharmony_ci	 * Else:
159262306a36Sopenharmony_ci	 *	Mark interface as valid.
159362306a36Sopenharmony_ci	 *	Output noop parameters.
159462306a36Sopenharmony_ci	 *
159562306a36Sopenharmony_ci	 * Rule of thumb: it is better to disable a broken interface than let
159662306a36Sopenharmony_ci	 *		  it spew garbage input.
159762306a36Sopenharmony_ci	 */
159862306a36Sopenharmony_ci
159962306a36Sopenharmony_ci	switch (VID_PID(hdev->vendor, hdev->product)) {
160062306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
160162306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_PF1209):
160262306a36Sopenharmony_ci		rc = WITH_OPT_DESC(PF1209_ORIG, pf1209_fixed);
160362306a36Sopenharmony_ci		if (rc != 0)
160462306a36Sopenharmony_ci			goto cleanup;
160562306a36Sopenharmony_ci		break;
160662306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
160762306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U):
160862306a36Sopenharmony_ci		rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp4030u_fixed);
160962306a36Sopenharmony_ci		if (rc != 0)
161062306a36Sopenharmony_ci			goto cleanup;
161162306a36Sopenharmony_ci		break;
161262306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
161362306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U):
161462306a36Sopenharmony_ci		if (hdev->dev_rsize == UCLOGIC_RDESC_WP5540U_V2_ORIG_SIZE) {
161562306a36Sopenharmony_ci			if (bInterfaceNumber == 0) {
161662306a36Sopenharmony_ci				/* Try to probe v1 pen parameters */
161762306a36Sopenharmony_ci				rc = uclogic_params_pen_init_v1(&p.pen,
161862306a36Sopenharmony_ci								&found, hdev);
161962306a36Sopenharmony_ci				if (rc != 0) {
162062306a36Sopenharmony_ci					hid_err(hdev,
162162306a36Sopenharmony_ci						"pen probing failed: %d\n",
162262306a36Sopenharmony_ci						rc);
162362306a36Sopenharmony_ci					goto cleanup;
162462306a36Sopenharmony_ci				}
162562306a36Sopenharmony_ci				if (!found) {
162662306a36Sopenharmony_ci					hid_warn(hdev,
162762306a36Sopenharmony_ci						 "pen parameters not found");
162862306a36Sopenharmony_ci				}
162962306a36Sopenharmony_ci			} else {
163062306a36Sopenharmony_ci				uclogic_params_init_invalid(&p);
163162306a36Sopenharmony_ci			}
163262306a36Sopenharmony_ci		} else {
163362306a36Sopenharmony_ci			rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp5540u_fixed);
163462306a36Sopenharmony_ci			if (rc != 0)
163562306a36Sopenharmony_ci				goto cleanup;
163662306a36Sopenharmony_ci		}
163762306a36Sopenharmony_ci		break;
163862306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
163962306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U):
164062306a36Sopenharmony_ci		rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp8060u_fixed);
164162306a36Sopenharmony_ci		if (rc != 0)
164262306a36Sopenharmony_ci			goto cleanup;
164362306a36Sopenharmony_ci		break;
164462306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
164562306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_WP1062):
164662306a36Sopenharmony_ci		rc = WITH_OPT_DESC(WP1062_ORIG, wp1062_fixed);
164762306a36Sopenharmony_ci		if (rc != 0)
164862306a36Sopenharmony_ci			goto cleanup;
164962306a36Sopenharmony_ci		break;
165062306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
165162306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850):
165262306a36Sopenharmony_ci		switch (bInterfaceNumber) {
165362306a36Sopenharmony_ci		case 0:
165462306a36Sopenharmony_ci			rc = WITH_OPT_DESC(TWHL850_ORIG0, twhl850_fixed0);
165562306a36Sopenharmony_ci			if (rc != 0)
165662306a36Sopenharmony_ci				goto cleanup;
165762306a36Sopenharmony_ci			break;
165862306a36Sopenharmony_ci		case 1:
165962306a36Sopenharmony_ci			rc = WITH_OPT_DESC(TWHL850_ORIG1, twhl850_fixed1);
166062306a36Sopenharmony_ci			if (rc != 0)
166162306a36Sopenharmony_ci				goto cleanup;
166262306a36Sopenharmony_ci			break;
166362306a36Sopenharmony_ci		case 2:
166462306a36Sopenharmony_ci			rc = WITH_OPT_DESC(TWHL850_ORIG2, twhl850_fixed2);
166562306a36Sopenharmony_ci			if (rc != 0)
166662306a36Sopenharmony_ci				goto cleanup;
166762306a36Sopenharmony_ci			break;
166862306a36Sopenharmony_ci		}
166962306a36Sopenharmony_ci		break;
167062306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
167162306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60):
167262306a36Sopenharmony_ci		/*
167362306a36Sopenharmony_ci		 * If it is not a three-interface version, which is known to
167462306a36Sopenharmony_ci		 * respond to initialization.
167562306a36Sopenharmony_ci		 */
167662306a36Sopenharmony_ci		if (bNumInterfaces != 3) {
167762306a36Sopenharmony_ci			switch (bInterfaceNumber) {
167862306a36Sopenharmony_ci			case 0:
167962306a36Sopenharmony_ci				rc = WITH_OPT_DESC(TWHA60_ORIG0,
168062306a36Sopenharmony_ci							twha60_fixed0);
168162306a36Sopenharmony_ci				if (rc != 0)
168262306a36Sopenharmony_ci					goto cleanup;
168362306a36Sopenharmony_ci				break;
168462306a36Sopenharmony_ci			case 1:
168562306a36Sopenharmony_ci				rc = WITH_OPT_DESC(TWHA60_ORIG1,
168662306a36Sopenharmony_ci							twha60_fixed1);
168762306a36Sopenharmony_ci				if (rc != 0)
168862306a36Sopenharmony_ci					goto cleanup;
168962306a36Sopenharmony_ci				break;
169062306a36Sopenharmony_ci			}
169162306a36Sopenharmony_ci			break;
169262306a36Sopenharmony_ci		}
169362306a36Sopenharmony_ci		fallthrough;
169462306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_HUION,
169562306a36Sopenharmony_ci		     USB_DEVICE_ID_HUION_TABLET):
169662306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_HUION,
169762306a36Sopenharmony_ci		     USB_DEVICE_ID_HUION_TABLET2):
169862306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
169962306a36Sopenharmony_ci		     USB_DEVICE_ID_HUION_TABLET):
170062306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
170162306a36Sopenharmony_ci		     USB_DEVICE_ID_YIYNOVA_TABLET):
170262306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
170362306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81):
170462306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
170562306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3):
170662306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
170762306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45):
170862306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
170962306a36Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_47):
171062306a36Sopenharmony_ci		rc = uclogic_params_huion_init(&p, hdev);
171162306a36Sopenharmony_ci		if (rc != 0)
171262306a36Sopenharmony_ci			goto cleanup;
171362306a36Sopenharmony_ci		break;
171462306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGTIZER,
171562306a36Sopenharmony_ci		     USB_DEVICE_ID_UGTIZER_TABLET_GP0610):
171662306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGTIZER,
171762306a36Sopenharmony_ci		     USB_DEVICE_ID_UGTIZER_TABLET_GT5040):
171862306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
171962306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540):
172062306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
172162306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640):
172262306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
172362306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06):
172462306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
172562306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720):
172662306a36Sopenharmony_ci		/* If this is the pen interface */
172762306a36Sopenharmony_ci		if (bInterfaceNumber == 1) {
172862306a36Sopenharmony_ci			/* Probe v1 pen parameters */
172962306a36Sopenharmony_ci			rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
173062306a36Sopenharmony_ci			if (rc != 0) {
173162306a36Sopenharmony_ci				hid_err(hdev, "pen probing failed: %d\n", rc);
173262306a36Sopenharmony_ci				goto cleanup;
173362306a36Sopenharmony_ci			}
173462306a36Sopenharmony_ci			if (!found) {
173562306a36Sopenharmony_ci				hid_warn(hdev, "pen parameters not found");
173662306a36Sopenharmony_ci				uclogic_params_init_invalid(&p);
173762306a36Sopenharmony_ci			}
173862306a36Sopenharmony_ci		} else {
173962306a36Sopenharmony_ci			uclogic_params_init_invalid(&p);
174062306a36Sopenharmony_ci		}
174162306a36Sopenharmony_ci		break;
174262306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
174362306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01):
174462306a36Sopenharmony_ci		/* If this is the pen and frame interface */
174562306a36Sopenharmony_ci		if (bInterfaceNumber == 1) {
174662306a36Sopenharmony_ci			/* Probe v1 pen parameters */
174762306a36Sopenharmony_ci			rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
174862306a36Sopenharmony_ci			if (rc != 0) {
174962306a36Sopenharmony_ci				hid_err(hdev, "pen probing failed: %d\n", rc);
175062306a36Sopenharmony_ci				goto cleanup;
175162306a36Sopenharmony_ci			}
175262306a36Sopenharmony_ci			/* Initialize frame parameters */
175362306a36Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
175462306a36Sopenharmony_ci				&p.frame_list[0],
175562306a36Sopenharmony_ci				uclogic_rdesc_xppen_deco01_frame_arr,
175662306a36Sopenharmony_ci				uclogic_rdesc_xppen_deco01_frame_size,
175762306a36Sopenharmony_ci				0);
175862306a36Sopenharmony_ci			if (rc != 0)
175962306a36Sopenharmony_ci				goto cleanup;
176062306a36Sopenharmony_ci		} else {
176162306a36Sopenharmony_ci			uclogic_params_init_invalid(&p);
176262306a36Sopenharmony_ci		}
176362306a36Sopenharmony_ci		break;
176462306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
176562306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_PARBLO_A610_PRO):
176662306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
176762306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2):
176862306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
176962306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L):
177062306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
177162306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW):
177262306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
177362306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S):
177462306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
177562306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW):
177662306a36Sopenharmony_ci		rc = uclogic_params_ugee_v2_init(&p, hdev);
177762306a36Sopenharmony_ci		if (rc != 0)
177862306a36Sopenharmony_ci			goto cleanup;
177962306a36Sopenharmony_ci		break;
178062306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_TRUST,
178162306a36Sopenharmony_ci		     USB_DEVICE_ID_TRUST_PANORA_TABLET):
178262306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
178362306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_TABLET_G5):
178462306a36Sopenharmony_ci		/* Ignore non-pen interfaces */
178562306a36Sopenharmony_ci		if (bInterfaceNumber != 1) {
178662306a36Sopenharmony_ci			uclogic_params_init_invalid(&p);
178762306a36Sopenharmony_ci			break;
178862306a36Sopenharmony_ci		}
178962306a36Sopenharmony_ci
179062306a36Sopenharmony_ci		rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
179162306a36Sopenharmony_ci		if (rc != 0) {
179262306a36Sopenharmony_ci			hid_err(hdev, "pen probing failed: %d\n", rc);
179362306a36Sopenharmony_ci			goto cleanup;
179462306a36Sopenharmony_ci		} else if (found) {
179562306a36Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
179662306a36Sopenharmony_ci				&p.frame_list[0],
179762306a36Sopenharmony_ci				uclogic_rdesc_ugee_g5_frame_arr,
179862306a36Sopenharmony_ci				uclogic_rdesc_ugee_g5_frame_size,
179962306a36Sopenharmony_ci				UCLOGIC_RDESC_UGEE_G5_FRAME_ID);
180062306a36Sopenharmony_ci			if (rc != 0) {
180162306a36Sopenharmony_ci				hid_err(hdev,
180262306a36Sopenharmony_ci					"failed creating frame parameters: %d\n",
180362306a36Sopenharmony_ci					rc);
180462306a36Sopenharmony_ci				goto cleanup;
180562306a36Sopenharmony_ci			}
180662306a36Sopenharmony_ci			p.frame_list[0].re_lsb =
180762306a36Sopenharmony_ci				UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB;
180862306a36Sopenharmony_ci			p.frame_list[0].dev_id_byte =
180962306a36Sopenharmony_ci				UCLOGIC_RDESC_UGEE_G5_FRAME_DEV_ID_BYTE;
181062306a36Sopenharmony_ci		} else {
181162306a36Sopenharmony_ci			hid_warn(hdev, "pen parameters not found");
181262306a36Sopenharmony_ci			uclogic_params_init_invalid(&p);
181362306a36Sopenharmony_ci		}
181462306a36Sopenharmony_ci
181562306a36Sopenharmony_ci		break;
181662306a36Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
181762306a36Sopenharmony_ci		     USB_DEVICE_ID_UGEE_TABLET_EX07S):
181862306a36Sopenharmony_ci		/* Ignore non-pen interfaces */
181962306a36Sopenharmony_ci		if (bInterfaceNumber != 1) {
182062306a36Sopenharmony_ci			uclogic_params_init_invalid(&p);
182162306a36Sopenharmony_ci			break;
182262306a36Sopenharmony_ci		}
182362306a36Sopenharmony_ci
182462306a36Sopenharmony_ci		rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
182562306a36Sopenharmony_ci		if (rc != 0) {
182662306a36Sopenharmony_ci			hid_err(hdev, "pen probing failed: %d\n", rc);
182762306a36Sopenharmony_ci			goto cleanup;
182862306a36Sopenharmony_ci		} else if (found) {
182962306a36Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
183062306a36Sopenharmony_ci				&p.frame_list[0],
183162306a36Sopenharmony_ci				uclogic_rdesc_ugee_ex07_frame_arr,
183262306a36Sopenharmony_ci				uclogic_rdesc_ugee_ex07_frame_size,
183362306a36Sopenharmony_ci				0);
183462306a36Sopenharmony_ci			if (rc != 0) {
183562306a36Sopenharmony_ci				hid_err(hdev,
183662306a36Sopenharmony_ci					"failed creating frame parameters: %d\n",
183762306a36Sopenharmony_ci					rc);
183862306a36Sopenharmony_ci				goto cleanup;
183962306a36Sopenharmony_ci			}
184062306a36Sopenharmony_ci		} else {
184162306a36Sopenharmony_ci			hid_warn(hdev, "pen parameters not found");
184262306a36Sopenharmony_ci			uclogic_params_init_invalid(&p);
184362306a36Sopenharmony_ci		}
184462306a36Sopenharmony_ci
184562306a36Sopenharmony_ci		break;
184662306a36Sopenharmony_ci	}
184762306a36Sopenharmony_ci
184862306a36Sopenharmony_ci#undef VID_PID
184962306a36Sopenharmony_ci#undef WITH_OPT_DESC
185062306a36Sopenharmony_ci
185162306a36Sopenharmony_ci	/* Output parameters */
185262306a36Sopenharmony_ci	memcpy(params, &p, sizeof(*params));
185362306a36Sopenharmony_ci	memset(&p, 0, sizeof(p));
185462306a36Sopenharmony_ci	rc = 0;
185562306a36Sopenharmony_cicleanup:
185662306a36Sopenharmony_ci	uclogic_params_cleanup(&p);
185762306a36Sopenharmony_ci	return rc;
185862306a36Sopenharmony_ci}
185962306a36Sopenharmony_ci
186062306a36Sopenharmony_ci#ifdef CONFIG_HID_KUNIT_TEST
186162306a36Sopenharmony_ci#include "hid-uclogic-params-test.c"
186262306a36Sopenharmony_ci#endif
1863