18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  HID driver for UC-Logic devices not fully compliant with HID standard
48c2ecf20Sopenharmony_ci *  - tablet initialization and parameter retrieval
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci *  Copyright (c) 2018 Nikolai Kondrashov
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci/*
108c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify it
118c2ecf20Sopenharmony_ci * under the terms of the GNU General Public License as published by the Free
128c2ecf20Sopenharmony_ci * Software Foundation; either version 2 of the License, or (at your option)
138c2ecf20Sopenharmony_ci * any later version.
148c2ecf20Sopenharmony_ci */
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include "hid-uclogic-params.h"
178c2ecf20Sopenharmony_ci#include "hid-uclogic-rdesc.h"
188c2ecf20Sopenharmony_ci#include "usbhid/usbhid.h"
198c2ecf20Sopenharmony_ci#include "hid-ids.h"
208c2ecf20Sopenharmony_ci#include <linux/ctype.h>
218c2ecf20Sopenharmony_ci#include <asm/unaligned.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci/**
248c2ecf20Sopenharmony_ci * Convert a pen in-range reporting type to a string.
258c2ecf20Sopenharmony_ci *
268c2ecf20Sopenharmony_ci * @inrange:	The in-range reporting type to convert.
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci * Returns:
298c2ecf20Sopenharmony_ci *	The string representing the type, or NULL if the type is unknown.
308c2ecf20Sopenharmony_ci */
318c2ecf20Sopenharmony_ciconst char *uclogic_params_pen_inrange_to_str(
328c2ecf20Sopenharmony_ci			enum uclogic_params_pen_inrange inrange)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	switch (inrange) {
358c2ecf20Sopenharmony_ci	case UCLOGIC_PARAMS_PEN_INRANGE_NORMAL:
368c2ecf20Sopenharmony_ci		return "normal";
378c2ecf20Sopenharmony_ci	case UCLOGIC_PARAMS_PEN_INRANGE_INVERTED:
388c2ecf20Sopenharmony_ci		return "inverted";
398c2ecf20Sopenharmony_ci	case UCLOGIC_PARAMS_PEN_INRANGE_NONE:
408c2ecf20Sopenharmony_ci		return "none";
418c2ecf20Sopenharmony_ci	default:
428c2ecf20Sopenharmony_ci		return NULL;
438c2ecf20Sopenharmony_ci	}
448c2ecf20Sopenharmony_ci}
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci/**
478c2ecf20Sopenharmony_ci * uclogic_params_get_str_desc - retrieve a string descriptor from a HID
488c2ecf20Sopenharmony_ci * device interface, putting it into a kmalloc-allocated buffer as is, without
498c2ecf20Sopenharmony_ci * character encoding conversion.
508c2ecf20Sopenharmony_ci *
518c2ecf20Sopenharmony_ci * @pbuf:	Location for the kmalloc-allocated buffer pointer containing
528c2ecf20Sopenharmony_ci *		the retrieved descriptor. Not modified in case of error.
538c2ecf20Sopenharmony_ci *		Can be NULL to have retrieved descriptor discarded.
548c2ecf20Sopenharmony_ci * @hdev:	The HID device of the tablet interface to retrieve the string
558c2ecf20Sopenharmony_ci *		descriptor from. Cannot be NULL.
568c2ecf20Sopenharmony_ci * @idx:	Index of the string descriptor to request from the device.
578c2ecf20Sopenharmony_ci * @len:	Length of the buffer to allocate and the data to retrieve.
588c2ecf20Sopenharmony_ci *
598c2ecf20Sopenharmony_ci * Returns:
608c2ecf20Sopenharmony_ci *	number of bytes retrieved (<= len),
618c2ecf20Sopenharmony_ci *	-EPIPE, if the descriptor was not found, or
628c2ecf20Sopenharmony_ci *	another negative errno code in case of other error.
638c2ecf20Sopenharmony_ci */
648c2ecf20Sopenharmony_cistatic int uclogic_params_get_str_desc(__u8 **pbuf, struct hid_device *hdev,
658c2ecf20Sopenharmony_ci					__u8 idx, size_t len)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	int rc;
688c2ecf20Sopenharmony_ci	struct usb_device *udev;
698c2ecf20Sopenharmony_ci	__u8 *buf = NULL;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	/* Check arguments */
728c2ecf20Sopenharmony_ci	if (hdev == NULL) {
738c2ecf20Sopenharmony_ci		rc = -EINVAL;
748c2ecf20Sopenharmony_ci		goto cleanup;
758c2ecf20Sopenharmony_ci	}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	udev = hid_to_usb_dev(hdev);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	buf = kmalloc(len, GFP_KERNEL);
808c2ecf20Sopenharmony_ci	if (buf == NULL) {
818c2ecf20Sopenharmony_ci		rc = -ENOMEM;
828c2ecf20Sopenharmony_ci		goto cleanup;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
868c2ecf20Sopenharmony_ci				USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
878c2ecf20Sopenharmony_ci				(USB_DT_STRING << 8) + idx,
888c2ecf20Sopenharmony_ci				0x0409, buf, len,
898c2ecf20Sopenharmony_ci				USB_CTRL_GET_TIMEOUT);
908c2ecf20Sopenharmony_ci	if (rc == -EPIPE) {
918c2ecf20Sopenharmony_ci		hid_dbg(hdev, "string descriptor #%hhu not found\n", idx);
928c2ecf20Sopenharmony_ci		goto cleanup;
938c2ecf20Sopenharmony_ci	} else if (rc < 0) {
948c2ecf20Sopenharmony_ci		hid_err(hdev,
958c2ecf20Sopenharmony_ci			"failed retrieving string descriptor #%hhu: %d\n",
968c2ecf20Sopenharmony_ci			idx, rc);
978c2ecf20Sopenharmony_ci		goto cleanup;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	if (pbuf != NULL) {
1018c2ecf20Sopenharmony_ci		*pbuf = buf;
1028c2ecf20Sopenharmony_ci		buf = NULL;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cicleanup:
1068c2ecf20Sopenharmony_ci	kfree(buf);
1078c2ecf20Sopenharmony_ci	return rc;
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci/**
1118c2ecf20Sopenharmony_ci * uclogic_params_pen_cleanup - free resources used by struct
1128c2ecf20Sopenharmony_ci * uclogic_params_pen (tablet interface's pen input parameters).
1138c2ecf20Sopenharmony_ci * Can be called repeatedly.
1148c2ecf20Sopenharmony_ci *
1158c2ecf20Sopenharmony_ci * @pen:	Pen input parameters to cleanup. Cannot be NULL.
1168c2ecf20Sopenharmony_ci */
1178c2ecf20Sopenharmony_cistatic void uclogic_params_pen_cleanup(struct uclogic_params_pen *pen)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	kfree(pen->desc_ptr);
1208c2ecf20Sopenharmony_ci	memset(pen, 0, sizeof(*pen));
1218c2ecf20Sopenharmony_ci}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci/**
1248c2ecf20Sopenharmony_ci * uclogic_params_pen_init_v1() - initialize tablet interface pen
1258c2ecf20Sopenharmony_ci * input and retrieve its parameters from the device, using v1 protocol.
1268c2ecf20Sopenharmony_ci *
1278c2ecf20Sopenharmony_ci * @pen:	Pointer to the pen parameters to initialize (to be
1288c2ecf20Sopenharmony_ci *		cleaned up with uclogic_params_pen_cleanup()). Not modified in
1298c2ecf20Sopenharmony_ci *		case of error, or if parameters are not found. Cannot be NULL.
1308c2ecf20Sopenharmony_ci * @pfound:	Location for a flag which is set to true if the parameters
1318c2ecf20Sopenharmony_ci *		were found, and to false if not (e.g. device was
1328c2ecf20Sopenharmony_ci *		incompatible). Not modified in case of error. Cannot be NULL.
1338c2ecf20Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
1348c2ecf20Sopenharmony_ci *		parameters from. Cannot be NULL.
1358c2ecf20Sopenharmony_ci *
1368c2ecf20Sopenharmony_ci * Returns:
1378c2ecf20Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
1388c2ecf20Sopenharmony_ci */
1398c2ecf20Sopenharmony_cistatic int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
1408c2ecf20Sopenharmony_ci				      bool *pfound,
1418c2ecf20Sopenharmony_ci				      struct hid_device *hdev)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	int rc;
1448c2ecf20Sopenharmony_ci	bool found = false;
1458c2ecf20Sopenharmony_ci	/* Buffer for (part of) the string descriptor */
1468c2ecf20Sopenharmony_ci	__u8 *buf = NULL;
1478c2ecf20Sopenharmony_ci	/* Minimum descriptor length required, maximum seen so far is 18 */
1488c2ecf20Sopenharmony_ci	const int len = 12;
1498c2ecf20Sopenharmony_ci	s32 resolution;
1508c2ecf20Sopenharmony_ci	/* Pen report descriptor template parameters */
1518c2ecf20Sopenharmony_ci	s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM];
1528c2ecf20Sopenharmony_ci	__u8 *desc_ptr = NULL;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	/* Check arguments */
1558c2ecf20Sopenharmony_ci	if (pen == NULL || pfound == NULL || hdev == NULL) {
1568c2ecf20Sopenharmony_ci		rc = -EINVAL;
1578c2ecf20Sopenharmony_ci		goto cleanup;
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	/*
1618c2ecf20Sopenharmony_ci	 * Read string descriptor containing pen input parameters.
1628c2ecf20Sopenharmony_ci	 * The specific string descriptor and data were discovered by sniffing
1638c2ecf20Sopenharmony_ci	 * the Windows driver traffic.
1648c2ecf20Sopenharmony_ci	 * NOTE: This enables fully-functional tablet mode.
1658c2ecf20Sopenharmony_ci	 */
1668c2ecf20Sopenharmony_ci	rc = uclogic_params_get_str_desc(&buf, hdev, 100, len);
1678c2ecf20Sopenharmony_ci	if (rc == -EPIPE) {
1688c2ecf20Sopenharmony_ci		hid_dbg(hdev,
1698c2ecf20Sopenharmony_ci			"string descriptor with pen parameters not found, assuming not compatible\n");
1708c2ecf20Sopenharmony_ci		goto finish;
1718c2ecf20Sopenharmony_ci	} else if (rc < 0) {
1728c2ecf20Sopenharmony_ci		hid_err(hdev, "failed retrieving pen parameters: %d\n", rc);
1738c2ecf20Sopenharmony_ci		goto cleanup;
1748c2ecf20Sopenharmony_ci	} else if (rc != len) {
1758c2ecf20Sopenharmony_ci		hid_dbg(hdev,
1768c2ecf20Sopenharmony_ci			"string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n",
1778c2ecf20Sopenharmony_ci			rc, len);
1788c2ecf20Sopenharmony_ci		goto finish;
1798c2ecf20Sopenharmony_ci	}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	/*
1828c2ecf20Sopenharmony_ci	 * Fill report descriptor parameters from the string descriptor
1838c2ecf20Sopenharmony_ci	 */
1848c2ecf20Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
1858c2ecf20Sopenharmony_ci		get_unaligned_le16(buf + 2);
1868c2ecf20Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
1878c2ecf20Sopenharmony_ci		get_unaligned_le16(buf + 4);
1888c2ecf20Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
1898c2ecf20Sopenharmony_ci		get_unaligned_le16(buf + 8);
1908c2ecf20Sopenharmony_ci	resolution = get_unaligned_le16(buf + 10);
1918c2ecf20Sopenharmony_ci	if (resolution == 0) {
1928c2ecf20Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
1938c2ecf20Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
1948c2ecf20Sopenharmony_ci	} else {
1958c2ecf20Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
1968c2ecf20Sopenharmony_ci			desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
1978c2ecf20Sopenharmony_ci			resolution;
1988c2ecf20Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
1998c2ecf20Sopenharmony_ci			desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
2008c2ecf20Sopenharmony_ci			resolution;
2018c2ecf20Sopenharmony_ci	}
2028c2ecf20Sopenharmony_ci	kfree(buf);
2038c2ecf20Sopenharmony_ci	buf = NULL;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	/*
2068c2ecf20Sopenharmony_ci	 * Generate pen report descriptor
2078c2ecf20Sopenharmony_ci	 */
2088c2ecf20Sopenharmony_ci	desc_ptr = uclogic_rdesc_template_apply(
2098c2ecf20Sopenharmony_ci				uclogic_rdesc_pen_v1_template_arr,
2108c2ecf20Sopenharmony_ci				uclogic_rdesc_pen_v1_template_size,
2118c2ecf20Sopenharmony_ci				desc_params, ARRAY_SIZE(desc_params));
2128c2ecf20Sopenharmony_ci	if (desc_ptr == NULL) {
2138c2ecf20Sopenharmony_ci		rc = -ENOMEM;
2148c2ecf20Sopenharmony_ci		goto cleanup;
2158c2ecf20Sopenharmony_ci	}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	/*
2188c2ecf20Sopenharmony_ci	 * Fill-in the parameters
2198c2ecf20Sopenharmony_ci	 */
2208c2ecf20Sopenharmony_ci	memset(pen, 0, sizeof(*pen));
2218c2ecf20Sopenharmony_ci	pen->desc_ptr = desc_ptr;
2228c2ecf20Sopenharmony_ci	desc_ptr = NULL;
2238c2ecf20Sopenharmony_ci	pen->desc_size = uclogic_rdesc_pen_v1_template_size;
2248c2ecf20Sopenharmony_ci	pen->id = UCLOGIC_RDESC_PEN_V1_ID;
2258c2ecf20Sopenharmony_ci	pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED;
2268c2ecf20Sopenharmony_ci	found = true;
2278c2ecf20Sopenharmony_cifinish:
2288c2ecf20Sopenharmony_ci	*pfound = found;
2298c2ecf20Sopenharmony_ci	rc = 0;
2308c2ecf20Sopenharmony_cicleanup:
2318c2ecf20Sopenharmony_ci	kfree(desc_ptr);
2328c2ecf20Sopenharmony_ci	kfree(buf);
2338c2ecf20Sopenharmony_ci	return rc;
2348c2ecf20Sopenharmony_ci}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci/**
2378c2ecf20Sopenharmony_ci * uclogic_params_get_le24() - get a 24-bit little-endian number from a
2388c2ecf20Sopenharmony_ci * buffer.
2398c2ecf20Sopenharmony_ci *
2408c2ecf20Sopenharmony_ci * @p:	The pointer to the number buffer.
2418c2ecf20Sopenharmony_ci *
2428c2ecf20Sopenharmony_ci * Returns:
2438c2ecf20Sopenharmony_ci *	The retrieved number
2448c2ecf20Sopenharmony_ci */
2458c2ecf20Sopenharmony_cistatic s32 uclogic_params_get_le24(const void *p)
2468c2ecf20Sopenharmony_ci{
2478c2ecf20Sopenharmony_ci	const __u8 *b = p;
2488c2ecf20Sopenharmony_ci	return b[0] | (b[1] << 8UL) | (b[2] << 16UL);
2498c2ecf20Sopenharmony_ci}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci/**
2528c2ecf20Sopenharmony_ci * uclogic_params_pen_init_v2() - initialize tablet interface pen
2538c2ecf20Sopenharmony_ci * input and retrieve its parameters from the device, using v2 protocol.
2548c2ecf20Sopenharmony_ci *
2558c2ecf20Sopenharmony_ci * @pen:	Pointer to the pen parameters to initialize (to be
2568c2ecf20Sopenharmony_ci *		cleaned up with uclogic_params_pen_cleanup()). Not modified in
2578c2ecf20Sopenharmony_ci *		case of error, or if parameters are not found. Cannot be NULL.
2588c2ecf20Sopenharmony_ci * @pfound:	Location for a flag which is set to true if the parameters
2598c2ecf20Sopenharmony_ci *		were found, and to false if not (e.g. device was
2608c2ecf20Sopenharmony_ci *		incompatible). Not modified in case of error. Cannot be NULL.
2618c2ecf20Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
2628c2ecf20Sopenharmony_ci *		parameters from. Cannot be NULL.
2638c2ecf20Sopenharmony_ci *
2648c2ecf20Sopenharmony_ci * Returns:
2658c2ecf20Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
2668c2ecf20Sopenharmony_ci */
2678c2ecf20Sopenharmony_cistatic int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen,
2688c2ecf20Sopenharmony_ci					bool *pfound,
2698c2ecf20Sopenharmony_ci					struct hid_device *hdev)
2708c2ecf20Sopenharmony_ci{
2718c2ecf20Sopenharmony_ci	int rc;
2728c2ecf20Sopenharmony_ci	bool found = false;
2738c2ecf20Sopenharmony_ci	/* Buffer for (part of) the string descriptor */
2748c2ecf20Sopenharmony_ci	__u8 *buf = NULL;
2758c2ecf20Sopenharmony_ci	/* Descriptor length required */
2768c2ecf20Sopenharmony_ci	const int len = 18;
2778c2ecf20Sopenharmony_ci	s32 resolution;
2788c2ecf20Sopenharmony_ci	/* Pen report descriptor template parameters */
2798c2ecf20Sopenharmony_ci	s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM];
2808c2ecf20Sopenharmony_ci	__u8 *desc_ptr = NULL;
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	/* Check arguments */
2838c2ecf20Sopenharmony_ci	if (pen == NULL || pfound == NULL || hdev == NULL) {
2848c2ecf20Sopenharmony_ci		rc = -EINVAL;
2858c2ecf20Sopenharmony_ci		goto cleanup;
2868c2ecf20Sopenharmony_ci	}
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	/*
2898c2ecf20Sopenharmony_ci	 * Read string descriptor containing pen input parameters.
2908c2ecf20Sopenharmony_ci	 * The specific string descriptor and data were discovered by sniffing
2918c2ecf20Sopenharmony_ci	 * the Windows driver traffic.
2928c2ecf20Sopenharmony_ci	 * NOTE: This enables fully-functional tablet mode.
2938c2ecf20Sopenharmony_ci	 */
2948c2ecf20Sopenharmony_ci	rc = uclogic_params_get_str_desc(&buf, hdev, 200, len);
2958c2ecf20Sopenharmony_ci	if (rc == -EPIPE) {
2968c2ecf20Sopenharmony_ci		hid_dbg(hdev,
2978c2ecf20Sopenharmony_ci			"string descriptor with pen parameters not found, assuming not compatible\n");
2988c2ecf20Sopenharmony_ci		goto finish;
2998c2ecf20Sopenharmony_ci	} else if (rc < 0) {
3008c2ecf20Sopenharmony_ci		hid_err(hdev, "failed retrieving pen parameters: %d\n", rc);
3018c2ecf20Sopenharmony_ci		goto cleanup;
3028c2ecf20Sopenharmony_ci	} else if (rc != len) {
3038c2ecf20Sopenharmony_ci		hid_dbg(hdev,
3048c2ecf20Sopenharmony_ci			"string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n",
3058c2ecf20Sopenharmony_ci			rc, len);
3068c2ecf20Sopenharmony_ci		goto finish;
3078c2ecf20Sopenharmony_ci	} else {
3088c2ecf20Sopenharmony_ci		size_t i;
3098c2ecf20Sopenharmony_ci		/*
3108c2ecf20Sopenharmony_ci		 * Check it's not just a catch-all UTF-16LE-encoded ASCII
3118c2ecf20Sopenharmony_ci		 * string (such as the model name) some tablets put into all
3128c2ecf20Sopenharmony_ci		 * unknown string descriptors.
3138c2ecf20Sopenharmony_ci		 */
3148c2ecf20Sopenharmony_ci		for (i = 2;
3158c2ecf20Sopenharmony_ci		     i < len &&
3168c2ecf20Sopenharmony_ci			(buf[i] >= 0x20 && buf[i] < 0x7f && buf[i + 1] == 0);
3178c2ecf20Sopenharmony_ci		     i += 2);
3188c2ecf20Sopenharmony_ci		if (i >= len) {
3198c2ecf20Sopenharmony_ci			hid_dbg(hdev,
3208c2ecf20Sopenharmony_ci				"string descriptor with pen parameters seems to contain only text, assuming not compatible\n");
3218c2ecf20Sopenharmony_ci			goto finish;
3228c2ecf20Sopenharmony_ci		}
3238c2ecf20Sopenharmony_ci	}
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	/*
3268c2ecf20Sopenharmony_ci	 * Fill report descriptor parameters from the string descriptor
3278c2ecf20Sopenharmony_ci	 */
3288c2ecf20Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
3298c2ecf20Sopenharmony_ci		uclogic_params_get_le24(buf + 2);
3308c2ecf20Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
3318c2ecf20Sopenharmony_ci		uclogic_params_get_le24(buf + 5);
3328c2ecf20Sopenharmony_ci	desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
3338c2ecf20Sopenharmony_ci		get_unaligned_le16(buf + 8);
3348c2ecf20Sopenharmony_ci	resolution = get_unaligned_le16(buf + 10);
3358c2ecf20Sopenharmony_ci	if (resolution == 0) {
3368c2ecf20Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
3378c2ecf20Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
3388c2ecf20Sopenharmony_ci	} else {
3398c2ecf20Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
3408c2ecf20Sopenharmony_ci			desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
3418c2ecf20Sopenharmony_ci			resolution;
3428c2ecf20Sopenharmony_ci		desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
3438c2ecf20Sopenharmony_ci			desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
3448c2ecf20Sopenharmony_ci			resolution;
3458c2ecf20Sopenharmony_ci	}
3468c2ecf20Sopenharmony_ci	kfree(buf);
3478c2ecf20Sopenharmony_ci	buf = NULL;
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	/*
3508c2ecf20Sopenharmony_ci	 * Generate pen report descriptor
3518c2ecf20Sopenharmony_ci	 */
3528c2ecf20Sopenharmony_ci	desc_ptr = uclogic_rdesc_template_apply(
3538c2ecf20Sopenharmony_ci				uclogic_rdesc_pen_v2_template_arr,
3548c2ecf20Sopenharmony_ci				uclogic_rdesc_pen_v2_template_size,
3558c2ecf20Sopenharmony_ci				desc_params, ARRAY_SIZE(desc_params));
3568c2ecf20Sopenharmony_ci	if (desc_ptr == NULL) {
3578c2ecf20Sopenharmony_ci		rc = -ENOMEM;
3588c2ecf20Sopenharmony_ci		goto cleanup;
3598c2ecf20Sopenharmony_ci	}
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	/*
3628c2ecf20Sopenharmony_ci	 * Fill-in the parameters
3638c2ecf20Sopenharmony_ci	 */
3648c2ecf20Sopenharmony_ci	memset(pen, 0, sizeof(*pen));
3658c2ecf20Sopenharmony_ci	pen->desc_ptr = desc_ptr;
3668c2ecf20Sopenharmony_ci	desc_ptr = NULL;
3678c2ecf20Sopenharmony_ci	pen->desc_size = uclogic_rdesc_pen_v2_template_size;
3688c2ecf20Sopenharmony_ci	pen->id = UCLOGIC_RDESC_PEN_V2_ID;
3698c2ecf20Sopenharmony_ci	pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE;
3708c2ecf20Sopenharmony_ci	pen->fragmented_hires = true;
3718c2ecf20Sopenharmony_ci	found = true;
3728c2ecf20Sopenharmony_cifinish:
3738c2ecf20Sopenharmony_ci	*pfound = found;
3748c2ecf20Sopenharmony_ci	rc = 0;
3758c2ecf20Sopenharmony_cicleanup:
3768c2ecf20Sopenharmony_ci	kfree(desc_ptr);
3778c2ecf20Sopenharmony_ci	kfree(buf);
3788c2ecf20Sopenharmony_ci	return rc;
3798c2ecf20Sopenharmony_ci}
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci/**
3828c2ecf20Sopenharmony_ci * uclogic_params_frame_cleanup - free resources used by struct
3838c2ecf20Sopenharmony_ci * uclogic_params_frame (tablet interface's frame controls input parameters).
3848c2ecf20Sopenharmony_ci * Can be called repeatedly.
3858c2ecf20Sopenharmony_ci *
3868c2ecf20Sopenharmony_ci * @frame:	Frame controls input parameters to cleanup. Cannot be NULL.
3878c2ecf20Sopenharmony_ci */
3888c2ecf20Sopenharmony_cistatic void uclogic_params_frame_cleanup(struct uclogic_params_frame *frame)
3898c2ecf20Sopenharmony_ci{
3908c2ecf20Sopenharmony_ci	kfree(frame->desc_ptr);
3918c2ecf20Sopenharmony_ci	memset(frame, 0, sizeof(*frame));
3928c2ecf20Sopenharmony_ci}
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci/**
3958c2ecf20Sopenharmony_ci * uclogic_params_frame_init_with_desc() - initialize tablet's frame control
3968c2ecf20Sopenharmony_ci * parameters with a static report descriptor.
3978c2ecf20Sopenharmony_ci *
3988c2ecf20Sopenharmony_ci * @frame:	Pointer to the frame parameters to initialize (to be cleaned
3998c2ecf20Sopenharmony_ci *		up with uclogic_params_frame_cleanup()). Not modified in case
4008c2ecf20Sopenharmony_ci *		of error. Cannot be NULL.
4018c2ecf20Sopenharmony_ci * @desc_ptr:	Report descriptor pointer. Can be NULL, if desc_size is zero.
4028c2ecf20Sopenharmony_ci * @desc_size:	Report descriptor size.
4038c2ecf20Sopenharmony_ci * @id:		Report ID used for frame reports, if they should be tweaked,
4048c2ecf20Sopenharmony_ci *		zero if not.
4058c2ecf20Sopenharmony_ci *
4068c2ecf20Sopenharmony_ci * Returns:
4078c2ecf20Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
4088c2ecf20Sopenharmony_ci */
4098c2ecf20Sopenharmony_cistatic int uclogic_params_frame_init_with_desc(
4108c2ecf20Sopenharmony_ci					struct uclogic_params_frame *frame,
4118c2ecf20Sopenharmony_ci					const __u8 *desc_ptr,
4128c2ecf20Sopenharmony_ci					size_t desc_size,
4138c2ecf20Sopenharmony_ci					unsigned int id)
4148c2ecf20Sopenharmony_ci{
4158c2ecf20Sopenharmony_ci	__u8 *copy_desc_ptr;
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	if (frame == NULL || (desc_ptr == NULL && desc_size != 0))
4188c2ecf20Sopenharmony_ci		return -EINVAL;
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	copy_desc_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL);
4218c2ecf20Sopenharmony_ci	if (copy_desc_ptr == NULL)
4228c2ecf20Sopenharmony_ci		return -ENOMEM;
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	memset(frame, 0, sizeof(*frame));
4258c2ecf20Sopenharmony_ci	frame->desc_ptr = copy_desc_ptr;
4268c2ecf20Sopenharmony_ci	frame->desc_size = desc_size;
4278c2ecf20Sopenharmony_ci	frame->id = id;
4288c2ecf20Sopenharmony_ci	return 0;
4298c2ecf20Sopenharmony_ci}
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci/**
4328c2ecf20Sopenharmony_ci * uclogic_params_frame_init_v1_buttonpad() - initialize abstract buttonpad
4338c2ecf20Sopenharmony_ci * on a v1 tablet interface.
4348c2ecf20Sopenharmony_ci *
4358c2ecf20Sopenharmony_ci * @frame:	Pointer to the frame parameters to initialize (to be cleaned
4368c2ecf20Sopenharmony_ci *		up with uclogic_params_frame_cleanup()). Not modified in case
4378c2ecf20Sopenharmony_ci *		of error, or if parameters are not found. Cannot be NULL.
4388c2ecf20Sopenharmony_ci * @pfound:	Location for a flag which is set to true if the parameters
4398c2ecf20Sopenharmony_ci *		were found, and to false if not (e.g. device was
4408c2ecf20Sopenharmony_ci *		incompatible). Not modified in case of error. Cannot be NULL.
4418c2ecf20Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
4428c2ecf20Sopenharmony_ci *		parameters from. Cannot be NULL.
4438c2ecf20Sopenharmony_ci *
4448c2ecf20Sopenharmony_ci * Returns:
4458c2ecf20Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
4468c2ecf20Sopenharmony_ci */
4478c2ecf20Sopenharmony_cistatic int uclogic_params_frame_init_v1_buttonpad(
4488c2ecf20Sopenharmony_ci					struct uclogic_params_frame *frame,
4498c2ecf20Sopenharmony_ci					bool *pfound,
4508c2ecf20Sopenharmony_ci					struct hid_device *hdev)
4518c2ecf20Sopenharmony_ci{
4528c2ecf20Sopenharmony_ci	int rc;
4538c2ecf20Sopenharmony_ci	bool found = false;
4548c2ecf20Sopenharmony_ci	struct usb_device *usb_dev;
4558c2ecf20Sopenharmony_ci	char *str_buf = NULL;
4568c2ecf20Sopenharmony_ci	const size_t str_len = 16;
4578c2ecf20Sopenharmony_ci
4588c2ecf20Sopenharmony_ci	/* Check arguments */
4598c2ecf20Sopenharmony_ci	if (frame == NULL || pfound == NULL || hdev == NULL) {
4608c2ecf20Sopenharmony_ci		rc = -EINVAL;
4618c2ecf20Sopenharmony_ci		goto cleanup;
4628c2ecf20Sopenharmony_ci	}
4638c2ecf20Sopenharmony_ci
4648c2ecf20Sopenharmony_ci	usb_dev = hid_to_usb_dev(hdev);
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ci	/*
4678c2ecf20Sopenharmony_ci	 * Enable generic button mode
4688c2ecf20Sopenharmony_ci	 */
4698c2ecf20Sopenharmony_ci	str_buf = kzalloc(str_len, GFP_KERNEL);
4708c2ecf20Sopenharmony_ci	if (str_buf == NULL) {
4718c2ecf20Sopenharmony_ci		rc = -ENOMEM;
4728c2ecf20Sopenharmony_ci		goto cleanup;
4738c2ecf20Sopenharmony_ci	}
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	rc = usb_string(usb_dev, 123, str_buf, str_len);
4768c2ecf20Sopenharmony_ci	if (rc == -EPIPE) {
4778c2ecf20Sopenharmony_ci		hid_dbg(hdev,
4788c2ecf20Sopenharmony_ci			"generic button -enabling string descriptor not found\n");
4798c2ecf20Sopenharmony_ci	} else if (rc < 0) {
4808c2ecf20Sopenharmony_ci		goto cleanup;
4818c2ecf20Sopenharmony_ci	} else if (strncmp(str_buf, "HK On", rc) != 0) {
4828c2ecf20Sopenharmony_ci		hid_dbg(hdev,
4838c2ecf20Sopenharmony_ci			"invalid response to enabling generic buttons: \"%s\"\n",
4848c2ecf20Sopenharmony_ci			str_buf);
4858c2ecf20Sopenharmony_ci	} else {
4868c2ecf20Sopenharmony_ci		hid_dbg(hdev, "generic buttons enabled\n");
4878c2ecf20Sopenharmony_ci		rc = uclogic_params_frame_init_with_desc(
4888c2ecf20Sopenharmony_ci				frame,
4898c2ecf20Sopenharmony_ci				uclogic_rdesc_buttonpad_v1_arr,
4908c2ecf20Sopenharmony_ci				uclogic_rdesc_buttonpad_v1_size,
4918c2ecf20Sopenharmony_ci				UCLOGIC_RDESC_BUTTONPAD_V1_ID);
4928c2ecf20Sopenharmony_ci		if (rc != 0)
4938c2ecf20Sopenharmony_ci			goto cleanup;
4948c2ecf20Sopenharmony_ci		found = true;
4958c2ecf20Sopenharmony_ci	}
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci	*pfound = found;
4988c2ecf20Sopenharmony_ci	rc = 0;
4998c2ecf20Sopenharmony_cicleanup:
5008c2ecf20Sopenharmony_ci	kfree(str_buf);
5018c2ecf20Sopenharmony_ci	return rc;
5028c2ecf20Sopenharmony_ci}
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci/**
5058c2ecf20Sopenharmony_ci * uclogic_params_cleanup - free resources used by struct uclogic_params
5068c2ecf20Sopenharmony_ci * (tablet interface's parameters).
5078c2ecf20Sopenharmony_ci * Can be called repeatedly.
5088c2ecf20Sopenharmony_ci *
5098c2ecf20Sopenharmony_ci * @params:	Input parameters to cleanup. Cannot be NULL.
5108c2ecf20Sopenharmony_ci */
5118c2ecf20Sopenharmony_civoid uclogic_params_cleanup(struct uclogic_params *params)
5128c2ecf20Sopenharmony_ci{
5138c2ecf20Sopenharmony_ci	if (!params->invalid) {
5148c2ecf20Sopenharmony_ci		kfree(params->desc_ptr);
5158c2ecf20Sopenharmony_ci		if (!params->pen_unused)
5168c2ecf20Sopenharmony_ci			uclogic_params_pen_cleanup(&params->pen);
5178c2ecf20Sopenharmony_ci		uclogic_params_frame_cleanup(&params->frame);
5188c2ecf20Sopenharmony_ci		memset(params, 0, sizeof(*params));
5198c2ecf20Sopenharmony_ci	}
5208c2ecf20Sopenharmony_ci}
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci/**
5238c2ecf20Sopenharmony_ci * Get a replacement report descriptor for a tablet's interface.
5248c2ecf20Sopenharmony_ci *
5258c2ecf20Sopenharmony_ci * @params:	The parameters of a tablet interface to get report
5268c2ecf20Sopenharmony_ci *		descriptor for. Cannot be NULL.
5278c2ecf20Sopenharmony_ci * @pdesc:	Location for the resulting, kmalloc-allocated report
5288c2ecf20Sopenharmony_ci *		descriptor pointer, or for NULL, if there's no replacement
5298c2ecf20Sopenharmony_ci *		report descriptor. Not modified in case of error. Cannot be
5308c2ecf20Sopenharmony_ci *		NULL.
5318c2ecf20Sopenharmony_ci * @psize:	Location for the resulting report descriptor size, not set if
5328c2ecf20Sopenharmony_ci *		there's no replacement report descriptor. Not modified in case
5338c2ecf20Sopenharmony_ci *		of error. Cannot be NULL.
5348c2ecf20Sopenharmony_ci *
5358c2ecf20Sopenharmony_ci * Returns:
5368c2ecf20Sopenharmony_ci *	Zero, if successful.
5378c2ecf20Sopenharmony_ci *	-EINVAL, if invalid arguments are supplied.
5388c2ecf20Sopenharmony_ci *	-ENOMEM, if failed to allocate memory.
5398c2ecf20Sopenharmony_ci */
5408c2ecf20Sopenharmony_ciint uclogic_params_get_desc(const struct uclogic_params *params,
5418c2ecf20Sopenharmony_ci				__u8 **pdesc,
5428c2ecf20Sopenharmony_ci				unsigned int *psize)
5438c2ecf20Sopenharmony_ci{
5448c2ecf20Sopenharmony_ci	bool common_present;
5458c2ecf20Sopenharmony_ci	bool pen_present;
5468c2ecf20Sopenharmony_ci	bool frame_present;
5478c2ecf20Sopenharmony_ci	unsigned int size;
5488c2ecf20Sopenharmony_ci	__u8 *desc = NULL;
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci	/* Check arguments */
5518c2ecf20Sopenharmony_ci	if (params == NULL || pdesc == NULL || psize == NULL)
5528c2ecf20Sopenharmony_ci		return -EINVAL;
5538c2ecf20Sopenharmony_ci
5548c2ecf20Sopenharmony_ci	size = 0;
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	common_present = (params->desc_ptr != NULL);
5578c2ecf20Sopenharmony_ci	pen_present = (!params->pen_unused && params->pen.desc_ptr != NULL);
5588c2ecf20Sopenharmony_ci	frame_present = (params->frame.desc_ptr != NULL);
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci	if (common_present)
5618c2ecf20Sopenharmony_ci		size += params->desc_size;
5628c2ecf20Sopenharmony_ci	if (pen_present)
5638c2ecf20Sopenharmony_ci		size += params->pen.desc_size;
5648c2ecf20Sopenharmony_ci	if (frame_present)
5658c2ecf20Sopenharmony_ci		size += params->frame.desc_size;
5668c2ecf20Sopenharmony_ci
5678c2ecf20Sopenharmony_ci	if (common_present || pen_present || frame_present) {
5688c2ecf20Sopenharmony_ci		__u8 *p;
5698c2ecf20Sopenharmony_ci
5708c2ecf20Sopenharmony_ci		desc = kmalloc(size, GFP_KERNEL);
5718c2ecf20Sopenharmony_ci		if (desc == NULL)
5728c2ecf20Sopenharmony_ci			return -ENOMEM;
5738c2ecf20Sopenharmony_ci		p = desc;
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_ci		if (common_present) {
5768c2ecf20Sopenharmony_ci			memcpy(p, params->desc_ptr,
5778c2ecf20Sopenharmony_ci				params->desc_size);
5788c2ecf20Sopenharmony_ci			p += params->desc_size;
5798c2ecf20Sopenharmony_ci		}
5808c2ecf20Sopenharmony_ci		if (pen_present) {
5818c2ecf20Sopenharmony_ci			memcpy(p, params->pen.desc_ptr,
5828c2ecf20Sopenharmony_ci				params->pen.desc_size);
5838c2ecf20Sopenharmony_ci			p += params->pen.desc_size;
5848c2ecf20Sopenharmony_ci		}
5858c2ecf20Sopenharmony_ci		if (frame_present) {
5868c2ecf20Sopenharmony_ci			memcpy(p, params->frame.desc_ptr,
5878c2ecf20Sopenharmony_ci				params->frame.desc_size);
5888c2ecf20Sopenharmony_ci			p += params->frame.desc_size;
5898c2ecf20Sopenharmony_ci		}
5908c2ecf20Sopenharmony_ci
5918c2ecf20Sopenharmony_ci		WARN_ON(p != desc + size);
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci		*psize = size;
5948c2ecf20Sopenharmony_ci	}
5958c2ecf20Sopenharmony_ci
5968c2ecf20Sopenharmony_ci	*pdesc = desc;
5978c2ecf20Sopenharmony_ci	return 0;
5988c2ecf20Sopenharmony_ci}
5998c2ecf20Sopenharmony_ci
6008c2ecf20Sopenharmony_ci/**
6018c2ecf20Sopenharmony_ci * uclogic_params_init_invalid() - initialize tablet interface parameters,
6028c2ecf20Sopenharmony_ci * specifying the interface is invalid.
6038c2ecf20Sopenharmony_ci *
6048c2ecf20Sopenharmony_ci * @params:		Parameters to initialize (to be cleaned with
6058c2ecf20Sopenharmony_ci *			uclogic_params_cleanup()). Cannot be NULL.
6068c2ecf20Sopenharmony_ci */
6078c2ecf20Sopenharmony_cistatic void uclogic_params_init_invalid(struct uclogic_params *params)
6088c2ecf20Sopenharmony_ci{
6098c2ecf20Sopenharmony_ci	params->invalid = true;
6108c2ecf20Sopenharmony_ci}
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_ci/**
6138c2ecf20Sopenharmony_ci * uclogic_params_init_with_opt_desc() - initialize tablet interface
6148c2ecf20Sopenharmony_ci * parameters with an optional replacement report descriptor. Only modify
6158c2ecf20Sopenharmony_ci * report descriptor, if the original report descriptor matches the expected
6168c2ecf20Sopenharmony_ci * size.
6178c2ecf20Sopenharmony_ci *
6188c2ecf20Sopenharmony_ci * @params:		Parameters to initialize (to be cleaned with
6198c2ecf20Sopenharmony_ci *			uclogic_params_cleanup()). Not modified in case of
6208c2ecf20Sopenharmony_ci *			error. Cannot be NULL.
6218c2ecf20Sopenharmony_ci * @hdev:		The HID device of the tablet interface create the
6228c2ecf20Sopenharmony_ci *			parameters for. Cannot be NULL.
6238c2ecf20Sopenharmony_ci * @orig_desc_size:	Expected size of the original report descriptor to
6248c2ecf20Sopenharmony_ci *			be replaced.
6258c2ecf20Sopenharmony_ci * @desc_ptr:		Pointer to the replacement report descriptor.
6268c2ecf20Sopenharmony_ci *			Can be NULL, if desc_size is zero.
6278c2ecf20Sopenharmony_ci * @desc_size:		Size of the replacement report descriptor.
6288c2ecf20Sopenharmony_ci *
6298c2ecf20Sopenharmony_ci * Returns:
6308c2ecf20Sopenharmony_ci *	Zero, if successful. -EINVAL if an invalid argument was passed.
6318c2ecf20Sopenharmony_ci *	-ENOMEM, if failed to allocate memory.
6328c2ecf20Sopenharmony_ci */
6338c2ecf20Sopenharmony_cistatic int uclogic_params_init_with_opt_desc(struct uclogic_params *params,
6348c2ecf20Sopenharmony_ci					     struct hid_device *hdev,
6358c2ecf20Sopenharmony_ci					     unsigned int orig_desc_size,
6368c2ecf20Sopenharmony_ci					     __u8 *desc_ptr,
6378c2ecf20Sopenharmony_ci					     unsigned int desc_size)
6388c2ecf20Sopenharmony_ci{
6398c2ecf20Sopenharmony_ci	__u8 *desc_copy_ptr = NULL;
6408c2ecf20Sopenharmony_ci	unsigned int desc_copy_size;
6418c2ecf20Sopenharmony_ci	int rc;
6428c2ecf20Sopenharmony_ci
6438c2ecf20Sopenharmony_ci	/* Check arguments */
6448c2ecf20Sopenharmony_ci	if (params == NULL || hdev == NULL ||
6458c2ecf20Sopenharmony_ci	    (desc_ptr == NULL && desc_size != 0)) {
6468c2ecf20Sopenharmony_ci		rc = -EINVAL;
6478c2ecf20Sopenharmony_ci		goto cleanup;
6488c2ecf20Sopenharmony_ci	}
6498c2ecf20Sopenharmony_ci
6508c2ecf20Sopenharmony_ci	/* Replace report descriptor, if it matches */
6518c2ecf20Sopenharmony_ci	if (hdev->dev_rsize == orig_desc_size) {
6528c2ecf20Sopenharmony_ci		hid_dbg(hdev,
6538c2ecf20Sopenharmony_ci			"device report descriptor matches the expected size, replacing\n");
6548c2ecf20Sopenharmony_ci		desc_copy_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL);
6558c2ecf20Sopenharmony_ci		if (desc_copy_ptr == NULL) {
6568c2ecf20Sopenharmony_ci			rc = -ENOMEM;
6578c2ecf20Sopenharmony_ci			goto cleanup;
6588c2ecf20Sopenharmony_ci		}
6598c2ecf20Sopenharmony_ci		desc_copy_size = desc_size;
6608c2ecf20Sopenharmony_ci	} else {
6618c2ecf20Sopenharmony_ci		hid_dbg(hdev,
6628c2ecf20Sopenharmony_ci			"device report descriptor doesn't match the expected size (%u != %u), preserving\n",
6638c2ecf20Sopenharmony_ci			hdev->dev_rsize, orig_desc_size);
6648c2ecf20Sopenharmony_ci		desc_copy_ptr = NULL;
6658c2ecf20Sopenharmony_ci		desc_copy_size = 0;
6668c2ecf20Sopenharmony_ci	}
6678c2ecf20Sopenharmony_ci
6688c2ecf20Sopenharmony_ci	/* Output parameters */
6698c2ecf20Sopenharmony_ci	memset(params, 0, sizeof(*params));
6708c2ecf20Sopenharmony_ci	params->desc_ptr = desc_copy_ptr;
6718c2ecf20Sopenharmony_ci	desc_copy_ptr = NULL;
6728c2ecf20Sopenharmony_ci	params->desc_size = desc_copy_size;
6738c2ecf20Sopenharmony_ci
6748c2ecf20Sopenharmony_ci	rc = 0;
6758c2ecf20Sopenharmony_cicleanup:
6768c2ecf20Sopenharmony_ci	kfree(desc_copy_ptr);
6778c2ecf20Sopenharmony_ci	return rc;
6788c2ecf20Sopenharmony_ci}
6798c2ecf20Sopenharmony_ci
6808c2ecf20Sopenharmony_ci/**
6818c2ecf20Sopenharmony_ci * uclogic_params_init_with_pen_unused() - initialize tablet interface
6828c2ecf20Sopenharmony_ci * parameters preserving original reports and generic HID processing, but
6838c2ecf20Sopenharmony_ci * disabling pen usage.
6848c2ecf20Sopenharmony_ci *
6858c2ecf20Sopenharmony_ci * @params:		Parameters to initialize (to be cleaned with
6868c2ecf20Sopenharmony_ci *			uclogic_params_cleanup()). Not modified in case of
6878c2ecf20Sopenharmony_ci *			error. Cannot be NULL.
6888c2ecf20Sopenharmony_ci */
6898c2ecf20Sopenharmony_cistatic void uclogic_params_init_with_pen_unused(struct uclogic_params *params)
6908c2ecf20Sopenharmony_ci{
6918c2ecf20Sopenharmony_ci	memset(params, 0, sizeof(*params));
6928c2ecf20Sopenharmony_ci	params->pen_unused = true;
6938c2ecf20Sopenharmony_ci}
6948c2ecf20Sopenharmony_ci
6958c2ecf20Sopenharmony_ci/**
6968c2ecf20Sopenharmony_ci * uclogic_params_init() - initialize a Huion tablet interface and discover
6978c2ecf20Sopenharmony_ci * its parameters.
6988c2ecf20Sopenharmony_ci *
6998c2ecf20Sopenharmony_ci * @params:	Parameters to fill in (to be cleaned with
7008c2ecf20Sopenharmony_ci *		uclogic_params_cleanup()). Not modified in case of error.
7018c2ecf20Sopenharmony_ci *		Cannot be NULL.
7028c2ecf20Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
7038c2ecf20Sopenharmony_ci *		parameters from. Cannot be NULL.
7048c2ecf20Sopenharmony_ci *
7058c2ecf20Sopenharmony_ci * Returns:
7068c2ecf20Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
7078c2ecf20Sopenharmony_ci */
7088c2ecf20Sopenharmony_cistatic int uclogic_params_huion_init(struct uclogic_params *params,
7098c2ecf20Sopenharmony_ci				     struct hid_device *hdev)
7108c2ecf20Sopenharmony_ci{
7118c2ecf20Sopenharmony_ci	int rc;
7128c2ecf20Sopenharmony_ci	struct usb_device *udev;
7138c2ecf20Sopenharmony_ci	struct usb_interface *iface;
7148c2ecf20Sopenharmony_ci	__u8 bInterfaceNumber;
7158c2ecf20Sopenharmony_ci	bool found;
7168c2ecf20Sopenharmony_ci	/* The resulting parameters (noop) */
7178c2ecf20Sopenharmony_ci	struct uclogic_params p = {0, };
7188c2ecf20Sopenharmony_ci	static const char transition_ver[] = "HUION_T153_160607";
7198c2ecf20Sopenharmony_ci	char *ver_ptr = NULL;
7208c2ecf20Sopenharmony_ci	const size_t ver_len = sizeof(transition_ver) + 1;
7218c2ecf20Sopenharmony_ci
7228c2ecf20Sopenharmony_ci	/* Check arguments */
7238c2ecf20Sopenharmony_ci	if (params == NULL || hdev == NULL) {
7248c2ecf20Sopenharmony_ci		rc = -EINVAL;
7258c2ecf20Sopenharmony_ci		goto cleanup;
7268c2ecf20Sopenharmony_ci	}
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_ci	udev = hid_to_usb_dev(hdev);
7298c2ecf20Sopenharmony_ci	iface = to_usb_interface(hdev->dev.parent);
7308c2ecf20Sopenharmony_ci	bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
7318c2ecf20Sopenharmony_ci
7328c2ecf20Sopenharmony_ci	/* If it's not a pen interface */
7338c2ecf20Sopenharmony_ci	if (bInterfaceNumber != 0) {
7348c2ecf20Sopenharmony_ci		/* TODO: Consider marking the interface invalid */
7358c2ecf20Sopenharmony_ci		uclogic_params_init_with_pen_unused(&p);
7368c2ecf20Sopenharmony_ci		goto output;
7378c2ecf20Sopenharmony_ci	}
7388c2ecf20Sopenharmony_ci
7398c2ecf20Sopenharmony_ci	/* Try to get firmware version */
7408c2ecf20Sopenharmony_ci	ver_ptr = kzalloc(ver_len, GFP_KERNEL);
7418c2ecf20Sopenharmony_ci	if (ver_ptr == NULL) {
7428c2ecf20Sopenharmony_ci		rc = -ENOMEM;
7438c2ecf20Sopenharmony_ci		goto cleanup;
7448c2ecf20Sopenharmony_ci	}
7458c2ecf20Sopenharmony_ci	rc = usb_string(udev, 201, ver_ptr, ver_len);
7468c2ecf20Sopenharmony_ci	if (rc == -EPIPE) {
7478c2ecf20Sopenharmony_ci		*ver_ptr = '\0';
7488c2ecf20Sopenharmony_ci	} else if (rc < 0) {
7498c2ecf20Sopenharmony_ci		hid_err(hdev,
7508c2ecf20Sopenharmony_ci			"failed retrieving Huion firmware version: %d\n", rc);
7518c2ecf20Sopenharmony_ci		goto cleanup;
7528c2ecf20Sopenharmony_ci	}
7538c2ecf20Sopenharmony_ci
7548c2ecf20Sopenharmony_ci	/* If this is a transition firmware */
7558c2ecf20Sopenharmony_ci	if (strcmp(ver_ptr, transition_ver) == 0) {
7568c2ecf20Sopenharmony_ci		hid_dbg(hdev,
7578c2ecf20Sopenharmony_ci			"transition firmware detected, not probing pen v2 parameters\n");
7588c2ecf20Sopenharmony_ci	} else {
7598c2ecf20Sopenharmony_ci		/* Try to probe v2 pen parameters */
7608c2ecf20Sopenharmony_ci		rc = uclogic_params_pen_init_v2(&p.pen, &found, hdev);
7618c2ecf20Sopenharmony_ci		if (rc != 0) {
7628c2ecf20Sopenharmony_ci			hid_err(hdev,
7638c2ecf20Sopenharmony_ci				"failed probing pen v2 parameters: %d\n", rc);
7648c2ecf20Sopenharmony_ci			goto cleanup;
7658c2ecf20Sopenharmony_ci		} else if (found) {
7668c2ecf20Sopenharmony_ci			hid_dbg(hdev, "pen v2 parameters found\n");
7678c2ecf20Sopenharmony_ci			/* Create v2 buttonpad parameters */
7688c2ecf20Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
7698c2ecf20Sopenharmony_ci					&p.frame,
7708c2ecf20Sopenharmony_ci					uclogic_rdesc_buttonpad_v2_arr,
7718c2ecf20Sopenharmony_ci					uclogic_rdesc_buttonpad_v2_size,
7728c2ecf20Sopenharmony_ci					UCLOGIC_RDESC_BUTTONPAD_V2_ID);
7738c2ecf20Sopenharmony_ci			if (rc != 0) {
7748c2ecf20Sopenharmony_ci				hid_err(hdev,
7758c2ecf20Sopenharmony_ci					"failed creating v2 buttonpad parameters: %d\n",
7768c2ecf20Sopenharmony_ci					rc);
7778c2ecf20Sopenharmony_ci				goto cleanup;
7788c2ecf20Sopenharmony_ci			}
7798c2ecf20Sopenharmony_ci			/* Set bitmask marking frame reports in pen reports */
7808c2ecf20Sopenharmony_ci			p.pen_frame_flag = 0x20;
7818c2ecf20Sopenharmony_ci			goto output;
7828c2ecf20Sopenharmony_ci		}
7838c2ecf20Sopenharmony_ci		hid_dbg(hdev, "pen v2 parameters not found\n");
7848c2ecf20Sopenharmony_ci	}
7858c2ecf20Sopenharmony_ci
7868c2ecf20Sopenharmony_ci	/* Try to probe v1 pen parameters */
7878c2ecf20Sopenharmony_ci	rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
7888c2ecf20Sopenharmony_ci	if (rc != 0) {
7898c2ecf20Sopenharmony_ci		hid_err(hdev,
7908c2ecf20Sopenharmony_ci			"failed probing pen v1 parameters: %d\n", rc);
7918c2ecf20Sopenharmony_ci		goto cleanup;
7928c2ecf20Sopenharmony_ci	} else if (found) {
7938c2ecf20Sopenharmony_ci		hid_dbg(hdev, "pen v1 parameters found\n");
7948c2ecf20Sopenharmony_ci		/* Try to probe v1 buttonpad */
7958c2ecf20Sopenharmony_ci		rc = uclogic_params_frame_init_v1_buttonpad(
7968c2ecf20Sopenharmony_ci						&p.frame,
7978c2ecf20Sopenharmony_ci						&found, hdev);
7988c2ecf20Sopenharmony_ci		if (rc != 0) {
7998c2ecf20Sopenharmony_ci			hid_err(hdev, "v1 buttonpad probing failed: %d\n", rc);
8008c2ecf20Sopenharmony_ci			goto cleanup;
8018c2ecf20Sopenharmony_ci		}
8028c2ecf20Sopenharmony_ci		hid_dbg(hdev, "buttonpad v1 parameters%s found\n",
8038c2ecf20Sopenharmony_ci			(found ? "" : " not"));
8048c2ecf20Sopenharmony_ci		if (found) {
8058c2ecf20Sopenharmony_ci			/* Set bitmask marking frame reports */
8068c2ecf20Sopenharmony_ci			p.pen_frame_flag = 0x20;
8078c2ecf20Sopenharmony_ci		}
8088c2ecf20Sopenharmony_ci		goto output;
8098c2ecf20Sopenharmony_ci	}
8108c2ecf20Sopenharmony_ci	hid_dbg(hdev, "pen v1 parameters not found\n");
8118c2ecf20Sopenharmony_ci
8128c2ecf20Sopenharmony_ci	uclogic_params_init_invalid(&p);
8138c2ecf20Sopenharmony_ci
8148c2ecf20Sopenharmony_cioutput:
8158c2ecf20Sopenharmony_ci	/* Output parameters */
8168c2ecf20Sopenharmony_ci	memcpy(params, &p, sizeof(*params));
8178c2ecf20Sopenharmony_ci	memset(&p, 0, sizeof(p));
8188c2ecf20Sopenharmony_ci	rc = 0;
8198c2ecf20Sopenharmony_cicleanup:
8208c2ecf20Sopenharmony_ci	kfree(ver_ptr);
8218c2ecf20Sopenharmony_ci	uclogic_params_cleanup(&p);
8228c2ecf20Sopenharmony_ci	return rc;
8238c2ecf20Sopenharmony_ci}
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_ci/**
8268c2ecf20Sopenharmony_ci * uclogic_params_init() - initialize a tablet interface and discover its
8278c2ecf20Sopenharmony_ci * parameters.
8288c2ecf20Sopenharmony_ci *
8298c2ecf20Sopenharmony_ci * @params:	Parameters to fill in (to be cleaned with
8308c2ecf20Sopenharmony_ci *		uclogic_params_cleanup()). Not modified in case of error.
8318c2ecf20Sopenharmony_ci *		Cannot be NULL.
8328c2ecf20Sopenharmony_ci * @hdev:	The HID device of the tablet interface to initialize and get
8338c2ecf20Sopenharmony_ci *		parameters from. Cannot be NULL. Must be using the USB low-level
8348c2ecf20Sopenharmony_ci *		driver, i.e. be an actual USB tablet.
8358c2ecf20Sopenharmony_ci *
8368c2ecf20Sopenharmony_ci * Returns:
8378c2ecf20Sopenharmony_ci *	Zero, if successful. A negative errno code on error.
8388c2ecf20Sopenharmony_ci */
8398c2ecf20Sopenharmony_ciint uclogic_params_init(struct uclogic_params *params,
8408c2ecf20Sopenharmony_ci			struct hid_device *hdev)
8418c2ecf20Sopenharmony_ci{
8428c2ecf20Sopenharmony_ci	int rc;
8438c2ecf20Sopenharmony_ci	struct usb_device *udev;
8448c2ecf20Sopenharmony_ci	__u8  bNumInterfaces;
8458c2ecf20Sopenharmony_ci	struct usb_interface *iface;
8468c2ecf20Sopenharmony_ci	__u8 bInterfaceNumber;
8478c2ecf20Sopenharmony_ci	bool found;
8488c2ecf20Sopenharmony_ci	/* The resulting parameters (noop) */
8498c2ecf20Sopenharmony_ci	struct uclogic_params p = {0, };
8508c2ecf20Sopenharmony_ci
8518c2ecf20Sopenharmony_ci	/* Check arguments */
8528c2ecf20Sopenharmony_ci	if (params == NULL || hdev == NULL || !hid_is_usb(hdev)) {
8538c2ecf20Sopenharmony_ci		rc = -EINVAL;
8548c2ecf20Sopenharmony_ci		goto cleanup;
8558c2ecf20Sopenharmony_ci	}
8568c2ecf20Sopenharmony_ci
8578c2ecf20Sopenharmony_ci	udev = hid_to_usb_dev(hdev);
8588c2ecf20Sopenharmony_ci	bNumInterfaces = udev->config->desc.bNumInterfaces;
8598c2ecf20Sopenharmony_ci	iface = to_usb_interface(hdev->dev.parent);
8608c2ecf20Sopenharmony_ci	bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
8618c2ecf20Sopenharmony_ci
8628c2ecf20Sopenharmony_ci	/*
8638c2ecf20Sopenharmony_ci	 * Set replacement report descriptor if the original matches the
8648c2ecf20Sopenharmony_ci	 * specified size. Otherwise keep interface unchanged.
8658c2ecf20Sopenharmony_ci	 */
8668c2ecf20Sopenharmony_ci#define WITH_OPT_DESC(_orig_desc_token, _new_desc_token) \
8678c2ecf20Sopenharmony_ci	uclogic_params_init_with_opt_desc(                  \
8688c2ecf20Sopenharmony_ci		&p, hdev,                                   \
8698c2ecf20Sopenharmony_ci		UCLOGIC_RDESC_##_orig_desc_token##_SIZE,    \
8708c2ecf20Sopenharmony_ci		uclogic_rdesc_##_new_desc_token##_arr,      \
8718c2ecf20Sopenharmony_ci		uclogic_rdesc_##_new_desc_token##_size)
8728c2ecf20Sopenharmony_ci
8738c2ecf20Sopenharmony_ci#define VID_PID(_vid, _pid) \
8748c2ecf20Sopenharmony_ci	(((__u32)(_vid) << 16) | ((__u32)(_pid) & U16_MAX))
8758c2ecf20Sopenharmony_ci
8768c2ecf20Sopenharmony_ci	/*
8778c2ecf20Sopenharmony_ci	 * Handle specific interfaces for specific tablets.
8788c2ecf20Sopenharmony_ci	 *
8798c2ecf20Sopenharmony_ci	 * Observe the following logic:
8808c2ecf20Sopenharmony_ci	 *
8818c2ecf20Sopenharmony_ci	 * If the interface is recognized as producing certain useful input:
8828c2ecf20Sopenharmony_ci	 *	Mark interface as valid.
8838c2ecf20Sopenharmony_ci	 *	Output interface parameters.
8848c2ecf20Sopenharmony_ci	 * Else, if the interface is recognized as *not* producing any useful
8858c2ecf20Sopenharmony_ci	 * input:
8868c2ecf20Sopenharmony_ci	 *	Mark interface as invalid.
8878c2ecf20Sopenharmony_ci	 * Else:
8888c2ecf20Sopenharmony_ci	 *	Mark interface as valid.
8898c2ecf20Sopenharmony_ci	 *	Output noop parameters.
8908c2ecf20Sopenharmony_ci	 *
8918c2ecf20Sopenharmony_ci	 * Rule of thumb: it is better to disable a broken interface than let
8928c2ecf20Sopenharmony_ci	 *		  it spew garbage input.
8938c2ecf20Sopenharmony_ci	 */
8948c2ecf20Sopenharmony_ci
8958c2ecf20Sopenharmony_ci	switch (VID_PID(hdev->vendor, hdev->product)) {
8968c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
8978c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_PF1209):
8988c2ecf20Sopenharmony_ci		rc = WITH_OPT_DESC(PF1209_ORIG, pf1209_fixed);
8998c2ecf20Sopenharmony_ci		if (rc != 0)
9008c2ecf20Sopenharmony_ci			goto cleanup;
9018c2ecf20Sopenharmony_ci		break;
9028c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9038c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U):
9048c2ecf20Sopenharmony_ci		rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp4030u_fixed);
9058c2ecf20Sopenharmony_ci		if (rc != 0)
9068c2ecf20Sopenharmony_ci			goto cleanup;
9078c2ecf20Sopenharmony_ci		break;
9088c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9098c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U):
9108c2ecf20Sopenharmony_ci		if (hdev->dev_rsize == UCLOGIC_RDESC_WP5540U_V2_ORIG_SIZE) {
9118c2ecf20Sopenharmony_ci			if (bInterfaceNumber == 0) {
9128c2ecf20Sopenharmony_ci				/* Try to probe v1 pen parameters */
9138c2ecf20Sopenharmony_ci				rc = uclogic_params_pen_init_v1(&p.pen,
9148c2ecf20Sopenharmony_ci								&found, hdev);
9158c2ecf20Sopenharmony_ci				if (rc != 0) {
9168c2ecf20Sopenharmony_ci					hid_err(hdev,
9178c2ecf20Sopenharmony_ci						"pen probing failed: %d\n",
9188c2ecf20Sopenharmony_ci						rc);
9198c2ecf20Sopenharmony_ci					goto cleanup;
9208c2ecf20Sopenharmony_ci				}
9218c2ecf20Sopenharmony_ci				if (!found) {
9228c2ecf20Sopenharmony_ci					hid_warn(hdev,
9238c2ecf20Sopenharmony_ci						 "pen parameters not found");
9248c2ecf20Sopenharmony_ci				}
9258c2ecf20Sopenharmony_ci			} else {
9268c2ecf20Sopenharmony_ci				uclogic_params_init_invalid(&p);
9278c2ecf20Sopenharmony_ci			}
9288c2ecf20Sopenharmony_ci		} else {
9298c2ecf20Sopenharmony_ci			rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp5540u_fixed);
9308c2ecf20Sopenharmony_ci			if (rc != 0)
9318c2ecf20Sopenharmony_ci				goto cleanup;
9328c2ecf20Sopenharmony_ci		}
9338c2ecf20Sopenharmony_ci		break;
9348c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9358c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U):
9368c2ecf20Sopenharmony_ci		rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp8060u_fixed);
9378c2ecf20Sopenharmony_ci		if (rc != 0)
9388c2ecf20Sopenharmony_ci			goto cleanup;
9398c2ecf20Sopenharmony_ci		break;
9408c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9418c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_WP1062):
9428c2ecf20Sopenharmony_ci		rc = WITH_OPT_DESC(WP1062_ORIG, wp1062_fixed);
9438c2ecf20Sopenharmony_ci		if (rc != 0)
9448c2ecf20Sopenharmony_ci			goto cleanup;
9458c2ecf20Sopenharmony_ci		break;
9468c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9478c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850):
9488c2ecf20Sopenharmony_ci		switch (bInterfaceNumber) {
9498c2ecf20Sopenharmony_ci		case 0:
9508c2ecf20Sopenharmony_ci			rc = WITH_OPT_DESC(TWHL850_ORIG0, twhl850_fixed0);
9518c2ecf20Sopenharmony_ci			if (rc != 0)
9528c2ecf20Sopenharmony_ci				goto cleanup;
9538c2ecf20Sopenharmony_ci			break;
9548c2ecf20Sopenharmony_ci		case 1:
9558c2ecf20Sopenharmony_ci			rc = WITH_OPT_DESC(TWHL850_ORIG1, twhl850_fixed1);
9568c2ecf20Sopenharmony_ci			if (rc != 0)
9578c2ecf20Sopenharmony_ci				goto cleanup;
9588c2ecf20Sopenharmony_ci			break;
9598c2ecf20Sopenharmony_ci		case 2:
9608c2ecf20Sopenharmony_ci			rc = WITH_OPT_DESC(TWHL850_ORIG2, twhl850_fixed2);
9618c2ecf20Sopenharmony_ci			if (rc != 0)
9628c2ecf20Sopenharmony_ci				goto cleanup;
9638c2ecf20Sopenharmony_ci			break;
9648c2ecf20Sopenharmony_ci		}
9658c2ecf20Sopenharmony_ci		break;
9668c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9678c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60):
9688c2ecf20Sopenharmony_ci		/*
9698c2ecf20Sopenharmony_ci		 * If it is not a three-interface version, which is known to
9708c2ecf20Sopenharmony_ci		 * respond to initialization.
9718c2ecf20Sopenharmony_ci		 */
9728c2ecf20Sopenharmony_ci		if (bNumInterfaces != 3) {
9738c2ecf20Sopenharmony_ci			switch (bInterfaceNumber) {
9748c2ecf20Sopenharmony_ci			case 0:
9758c2ecf20Sopenharmony_ci				rc = WITH_OPT_DESC(TWHA60_ORIG0,
9768c2ecf20Sopenharmony_ci							twha60_fixed0);
9778c2ecf20Sopenharmony_ci				if (rc != 0)
9788c2ecf20Sopenharmony_ci					goto cleanup;
9798c2ecf20Sopenharmony_ci				break;
9808c2ecf20Sopenharmony_ci			case 1:
9818c2ecf20Sopenharmony_ci				rc = WITH_OPT_DESC(TWHA60_ORIG1,
9828c2ecf20Sopenharmony_ci							twha60_fixed1);
9838c2ecf20Sopenharmony_ci				if (rc != 0)
9848c2ecf20Sopenharmony_ci					goto cleanup;
9858c2ecf20Sopenharmony_ci				break;
9868c2ecf20Sopenharmony_ci			}
9878c2ecf20Sopenharmony_ci			break;
9888c2ecf20Sopenharmony_ci		}
9898c2ecf20Sopenharmony_ci		fallthrough;
9908c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_HUION,
9918c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_HUION_TABLET):
9928c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_HUION,
9938c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_HUION_HS64):
9948c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9958c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_HUION_TABLET):
9968c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9978c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_YIYNOVA_TABLET):
9988c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
9998c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81):
10008c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
10018c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3):
10028c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
10038c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45):
10048c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UCLOGIC,
10058c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_47):
10068c2ecf20Sopenharmony_ci		rc = uclogic_params_huion_init(&p, hdev);
10078c2ecf20Sopenharmony_ci		if (rc != 0)
10088c2ecf20Sopenharmony_ci			goto cleanup;
10098c2ecf20Sopenharmony_ci		break;
10108c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGTIZER,
10118c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UGTIZER_TABLET_GP0610):
10128c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGTIZER,
10138c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UGTIZER_TABLET_GT5040):
10148c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
10158c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540):
10168c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
10178c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640):
10188c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
10198c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720):
10208c2ecf20Sopenharmony_ci		/* If this is the pen interface */
10218c2ecf20Sopenharmony_ci		if (bInterfaceNumber == 1) {
10228c2ecf20Sopenharmony_ci			/* Probe v1 pen parameters */
10238c2ecf20Sopenharmony_ci			rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
10248c2ecf20Sopenharmony_ci			if (rc != 0) {
10258c2ecf20Sopenharmony_ci				hid_err(hdev, "pen probing failed: %d\n", rc);
10268c2ecf20Sopenharmony_ci				goto cleanup;
10278c2ecf20Sopenharmony_ci			}
10288c2ecf20Sopenharmony_ci			if (!found) {
10298c2ecf20Sopenharmony_ci				hid_warn(hdev, "pen parameters not found");
10308c2ecf20Sopenharmony_ci				uclogic_params_init_invalid(&p);
10318c2ecf20Sopenharmony_ci			}
10328c2ecf20Sopenharmony_ci		} else {
10338c2ecf20Sopenharmony_ci			/* TODO: Consider marking the interface invalid */
10348c2ecf20Sopenharmony_ci			uclogic_params_init_with_pen_unused(&p);
10358c2ecf20Sopenharmony_ci		}
10368c2ecf20Sopenharmony_ci		break;
10378c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
10388c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01):
10398c2ecf20Sopenharmony_ci		/* If this is the pen and frame interface */
10408c2ecf20Sopenharmony_ci		if (bInterfaceNumber == 1) {
10418c2ecf20Sopenharmony_ci			/* Probe v1 pen parameters */
10428c2ecf20Sopenharmony_ci			rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
10438c2ecf20Sopenharmony_ci			if (rc != 0) {
10448c2ecf20Sopenharmony_ci				hid_err(hdev, "pen probing failed: %d\n", rc);
10458c2ecf20Sopenharmony_ci				goto cleanup;
10468c2ecf20Sopenharmony_ci			}
10478c2ecf20Sopenharmony_ci			/* Initialize frame parameters */
10488c2ecf20Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
10498c2ecf20Sopenharmony_ci				&p.frame,
10508c2ecf20Sopenharmony_ci				uclogic_rdesc_xppen_deco01_frame_arr,
10518c2ecf20Sopenharmony_ci				uclogic_rdesc_xppen_deco01_frame_size,
10528c2ecf20Sopenharmony_ci				0);
10538c2ecf20Sopenharmony_ci			if (rc != 0)
10548c2ecf20Sopenharmony_ci				goto cleanup;
10558c2ecf20Sopenharmony_ci		} else {
10568c2ecf20Sopenharmony_ci			/* TODO: Consider marking the interface invalid */
10578c2ecf20Sopenharmony_ci			uclogic_params_init_with_pen_unused(&p);
10588c2ecf20Sopenharmony_ci		}
10598c2ecf20Sopenharmony_ci		break;
10608c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
10618c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UGEE_TABLET_G5):
10628c2ecf20Sopenharmony_ci		/* Ignore non-pen interfaces */
10638c2ecf20Sopenharmony_ci		if (bInterfaceNumber != 1) {
10648c2ecf20Sopenharmony_ci			uclogic_params_init_invalid(&p);
10658c2ecf20Sopenharmony_ci			break;
10668c2ecf20Sopenharmony_ci		}
10678c2ecf20Sopenharmony_ci
10688c2ecf20Sopenharmony_ci		rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
10698c2ecf20Sopenharmony_ci		if (rc != 0) {
10708c2ecf20Sopenharmony_ci			hid_err(hdev, "pen probing failed: %d\n", rc);
10718c2ecf20Sopenharmony_ci			goto cleanup;
10728c2ecf20Sopenharmony_ci		} else if (found) {
10738c2ecf20Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
10748c2ecf20Sopenharmony_ci				&p.frame,
10758c2ecf20Sopenharmony_ci				uclogic_rdesc_ugee_g5_frame_arr,
10768c2ecf20Sopenharmony_ci				uclogic_rdesc_ugee_g5_frame_size,
10778c2ecf20Sopenharmony_ci				UCLOGIC_RDESC_UGEE_G5_FRAME_ID);
10788c2ecf20Sopenharmony_ci			if (rc != 0) {
10798c2ecf20Sopenharmony_ci				hid_err(hdev,
10808c2ecf20Sopenharmony_ci					"failed creating buttonpad parameters: %d\n",
10818c2ecf20Sopenharmony_ci					rc);
10828c2ecf20Sopenharmony_ci				goto cleanup;
10838c2ecf20Sopenharmony_ci			}
10848c2ecf20Sopenharmony_ci			p.frame.re_lsb =
10858c2ecf20Sopenharmony_ci				UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB;
10868c2ecf20Sopenharmony_ci			p.frame.dev_id_byte =
10878c2ecf20Sopenharmony_ci				UCLOGIC_RDESC_UGEE_G5_FRAME_DEV_ID_BYTE;
10888c2ecf20Sopenharmony_ci		} else {
10898c2ecf20Sopenharmony_ci			hid_warn(hdev, "pen parameters not found");
10908c2ecf20Sopenharmony_ci			uclogic_params_init_invalid(&p);
10918c2ecf20Sopenharmony_ci		}
10928c2ecf20Sopenharmony_ci
10938c2ecf20Sopenharmony_ci		break;
10948c2ecf20Sopenharmony_ci	case VID_PID(USB_VENDOR_ID_UGEE,
10958c2ecf20Sopenharmony_ci		     USB_DEVICE_ID_UGEE_TABLET_EX07S):
10968c2ecf20Sopenharmony_ci		/* Ignore non-pen interfaces */
10978c2ecf20Sopenharmony_ci		if (bInterfaceNumber != 1) {
10988c2ecf20Sopenharmony_ci			uclogic_params_init_invalid(&p);
10998c2ecf20Sopenharmony_ci			break;
11008c2ecf20Sopenharmony_ci		}
11018c2ecf20Sopenharmony_ci
11028c2ecf20Sopenharmony_ci		rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
11038c2ecf20Sopenharmony_ci		if (rc != 0) {
11048c2ecf20Sopenharmony_ci			hid_err(hdev, "pen probing failed: %d\n", rc);
11058c2ecf20Sopenharmony_ci			goto cleanup;
11068c2ecf20Sopenharmony_ci		} else if (found) {
11078c2ecf20Sopenharmony_ci			rc = uclogic_params_frame_init_with_desc(
11088c2ecf20Sopenharmony_ci				&p.frame,
11098c2ecf20Sopenharmony_ci				uclogic_rdesc_ugee_ex07_buttonpad_arr,
11108c2ecf20Sopenharmony_ci				uclogic_rdesc_ugee_ex07_buttonpad_size,
11118c2ecf20Sopenharmony_ci				0);
11128c2ecf20Sopenharmony_ci			if (rc != 0) {
11138c2ecf20Sopenharmony_ci				hid_err(hdev,
11148c2ecf20Sopenharmony_ci					"failed creating buttonpad parameters: %d\n",
11158c2ecf20Sopenharmony_ci					rc);
11168c2ecf20Sopenharmony_ci				goto cleanup;
11178c2ecf20Sopenharmony_ci			}
11188c2ecf20Sopenharmony_ci		} else {
11198c2ecf20Sopenharmony_ci			hid_warn(hdev, "pen parameters not found");
11208c2ecf20Sopenharmony_ci			uclogic_params_init_invalid(&p);
11218c2ecf20Sopenharmony_ci		}
11228c2ecf20Sopenharmony_ci
11238c2ecf20Sopenharmony_ci		break;
11248c2ecf20Sopenharmony_ci	}
11258c2ecf20Sopenharmony_ci
11268c2ecf20Sopenharmony_ci#undef VID_PID
11278c2ecf20Sopenharmony_ci#undef WITH_OPT_DESC
11288c2ecf20Sopenharmony_ci
11298c2ecf20Sopenharmony_ci	/* Output parameters */
11308c2ecf20Sopenharmony_ci	memcpy(params, &p, sizeof(*params));
11318c2ecf20Sopenharmony_ci	memset(&p, 0, sizeof(p));
11328c2ecf20Sopenharmony_ci	rc = 0;
11338c2ecf20Sopenharmony_cicleanup:
11348c2ecf20Sopenharmony_ci	uclogic_params_cleanup(&p);
11358c2ecf20Sopenharmony_ci	return rc;
11368c2ecf20Sopenharmony_ci}
1137