2023-04-10 09:26:54

by Mubashshir

[permalink] [raw]
Subject: [PATCH] staging: HID: Add ShanWan USB WirelessGamepad driver

This device has a quirky initialization process.
Depending on how it was initialized, behaves completely differently.
In default mode, it behaves as expected, but in fallback it disables
force-feedback, analog stick configurations and L3/R3.

Signed-off-by: Huseyin BIYIK <[email protected]>
Signed-off-by: Mubashshir <[email protected]>
---
drivers/hid/Kconfig | 19 +++
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 3 +
drivers/hid/hid-shanwan.c | 256 ++++++++++++++++++++++++++++++++++++++
4 files changed, 279 insertions(+)
create mode 100644 drivers/hid/hid-shanwan.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 82f64fb31fda..a17db9c9694c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -990,6 +990,25 @@ config HID_SEMITEK
- Woo-dy
- X-Bows Nature/Knight

+config HID_SHANWAN
+ tristate "ShanWan USB WirelessGamepad"
+ depends on USB_HID
+ help
+ Support for Shanwan USB WirelessGamepad (and clones).
+
+ This device has a quirky initialization process.
+ Depending on how it was initialized, it behaves completely differently.
+ In default mode, it behaves as expected, but in fallback it disables
+ force-feedback, analog stick configurations and L3/R3.
+
+config SHANWAN_FF
+ bool "ShanWan USB WirelessGamepad force feedback support"
+ depends on HID_SHANWAN
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you have a ShanWan USB WirelessGamepad and want to enable
+ force feedback support for it.
+
config HID_SIGMAMICRO
tristate "SiGma Micro-based keyboards"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 5d37cacbde33..52878455fc10 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o
+obj-$(CONFIG_HID_SHANWAN) += hid-shanwan.o
obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 63545cd307e5..278914e37eb7 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -623,6 +623,9 @@
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a

+#define USB_VENDOR_ID_SHANWAN 0x2563
+#define USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD 0x0575
+
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
#define USB_DEVICE_ID_HUION_TABLET2 0x006d
diff --git a/drivers/hid/hid-shanwan.c b/drivers/hid/hid-shanwan.c
new file mode 100644
index 000000000000..6c91a4d79036
--- /dev/null
+++ b/drivers/hid/hid-shanwan.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Force feedback support for Shanwan USB WirelessGamepad
+ *
+ * Copyright (c) 2022-2023 Huseyin BIYIK <[email protected]>
+ * Copyright (c) 2023 Ahmad Hasan Mubashshir <[email protected]>
+ *
+ * mapping according to Gamepad Protocol
+ *
+ * Button 01: BTN_SOUTH (CROSS)
+ * Button 02: BTN_EAST(CIRCLE)
+ * Button 03: BTN_NORTH (TRIANGLE)
+ * Button 04: BTN_WEST (SQUARE)
+ * Button 05: BTL_TL (L1)
+ * Button 06: BTM_TR (R1)
+ * Button 07: BTN_TL2 (L2)
+ * Button 08: BTN_TR2 (R2)
+ * Button 09: BTN_SELECT
+ * Button 10: BTN_START
+ * Button 11: BTN_MODE
+ * Button 12: BTN_THUMBL (LS1)
+ * Button 13: BTN_THUMBR (LS1)
+ * LS1: X/Y AXIS
+ * LS2: Rx/Ry AXIS
+ * R2/L2 Touch Sensors: R/Rz
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/moduleparam.h>
+
+#include "hid-ids.h"
+
+#define PID0575_RDESC_ORIG_SIZE 137
+
+static bool swap_motors;
+module_param_named(swap, swap_motors, bool, 0);
+MODULE_PARM_DESC(swap, "Swap Weak/Strong Feedback motors");
+
+static __u8 pid0575_rdesc_fixed[] = {
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x05, // Usage (Game Pad)
+ 0xA1, 0x01, // Collection (Application)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x45, 0x01, // Physical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x0D, // Report Count (13)
+ 0x05, 0x09, // Usage Page (Button)
+ 0x09, 0x03, // Usage (BTN_NORTH)
+ 0x09, 0x02, // Usage (BTN_EAST)
+ 0x09, 0x01, // Usage (BTN_SOUTH)
+ 0x09, 0x04, // Usage (BTN_WEST)
+ 0x09, 0x05, // Usage (BTN_TL)
+ 0x09, 0x06, // Usage (BTN_TR)
+ 0x09, 0x07, // Usage (BTN_TL2)
+ 0x09, 0x08, // Usage (BTN_TR2)
+ 0x09, 0x09, // Usage (BTN_SELECT)
+ 0x09, 0x10, // Usage (BTN_START)
+ 0x09, 0x12, // Usage (BTN_THUMBL)
+ 0x09, 0x13, // Usage (BTN_THUMBR)
+ 0x09, 0x11, // Usage (BTN_MODE)
+ 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x03, // Report Count (3)
+ 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x25, 0x07, // Logical Maximum (7)
+ 0x46, 0x3B, 0x01, // Physical Maximum (315)
+ 0x75, 0x04, // Report Size (4)
+ 0x95, 0x01, // Report Count (1)
+ 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter)
+ 0x09, 0x39, // Usage (Hat switch)
+ 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NullState)
+ 0x65, 0x00, // Unit (None)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x01, // Input(Const,Array,Abs,NoWrap,Linear,PreferredState,NoNullPosition)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x46, 0xFF, 0x00, // Physical Maximum (255)
+ 0x09, 0x30, // Usage (X)
+ 0x09, 0x31, // Usage (Y)
+ 0x09, 0x33, // Usage (Rx)
+ 0x09, 0x34, // Usage (Ry)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x04, // Report Count (4)
+ 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x95, 0x0A, // Report Count (10)
+ 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x46, 0xFF, 0x00, // Physical Maximum (255)
+ 0x09, 0x32, // Usage (Z)
+ 0x09, 0x35, // Usage (Rz)
+ 0x95, 0x02, // Report Count (2)
+ 0x81, 0x02, // Input(Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x01, // Input(Const,Array,Abs,NoWrap,Linear,PreferredState,NoNullPosition)
+ 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
+ 0xB1, 0x02, // Feature(Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition,!volatile)
+ 0x0A, 0x21, 0x26, // Usage (0x2621)
+ 0x95, 0x08, // Report Count (8)
+ 0x91, 0x02, // Output(Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition,!volatile)
+ 0x0A, 0x21, 0x26, // Usage (0x2621)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x02, // Input(Data,Var,Abs,No Wrap,Linear,Preferred State,NoNullPosition)
+ 0xC0, // End Collection
+};
+
+struct shanwan_device {
+ struct hid_report *report;
+};
+
+#ifdef CONFIG_SHANWAN_FF
+static int shanwan_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct shanwan_device *shanwan = data;
+ struct hid_report *report = shanwan->report;
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ report->field[0]->value[0] = 0x02; /* 2 = rumble effect message */
+ report->field[0]->value[1] = 0x08; /* reserved value, always 8 */
+ if (swap_motors) {
+ /* weak rumble / strong rumble */
+ report->field[0]->value[2] = effect->u.rumble.strong_magnitude / 256;
+ report->field[0]->value[3] = effect->u.rumble.weak_magnitude / 256;
+ } else {
+ /* strong rumble / weak rumble */
+ report->field[0]->value[2] = effect->u.rumble.weak_magnitude / 256;
+ report->field[0]->value[3] = effect->u.rumble.strong_magnitude / 256;
+ }
+ report->field[0]->value[4] = 0xff; /* duration 0-254 (255 = nonstop) */
+ report->field[0]->value[5] = 0x00; /* padding */
+ report->field[0]->value[6] = 0x00; /* padding */
+ report->field[0]->value[7] = 0x00; /* padding */
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
+ return 0;
+}
+
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ struct shanwan_device *shanwan;
+ struct hid_report *report;
+ struct hid_input *hidinput;
+ struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct input_dev *dev;
+
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ dev = hidinput->input;
+
+ if (list_empty(report_list)) {
+ hid_err(hid, "no output reports found\n");
+ return -ENODEV;
+ }
+
+ report = list_first_entry(report_list, struct hid_report, list);
+
+ shanwan = kzalloc(sizeof(*shanwan), GFP_KERNEL);
+ if (!shanwan)
+ return -ENOMEM;
+
+ set_bit(FF_RUMBLE, dev->ffbit);
+
+ if (input_ff_create_memless(dev, shanwan, shanwan_play_effect)) {
+ kfree(shanwan);
+ return -ENODEV;
+ }
+
+ shanwan->report = report;
+
+ return 0;
+}
+#else
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ return 0
+}
+#endif
+
+static int shanwan_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int error;
+
+ error = hid_parse(hdev);
+ if (error) {
+ hid_err(hdev, "parse failed\n");
+ return error;
+ }
+
+ error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ if (error) {
+ hid_err(hdev, "hw start failed\n");
+ return error;
+ }
+
+ error = shanwan_init_ff(hdev);
+ if (error)
+ hid_warn(hdev, "Failed to enable force feedback support, error: %d\n", error);
+
+ error = hid_hw_open(hdev);
+ if (error) {
+ dev_err(&hdev->dev, "hw open failed\n");
+ hid_hw_stop(hdev);
+ return error;
+ }
+
+ return 0;
+}
+
+static void shanwan_remove(struct hid_device *hdev)
+{
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static __u8 *shanwan_report_fixup(struct hid_device *hid, __u8 *rdesc, unsigned int *rsize)
+{
+ if (*rsize == PID0575_RDESC_ORIG_SIZE) {
+ rdesc = pid0575_rdesc_fixed;
+ *rsize = sizeof(pid0575_rdesc_fixed);
+ } else {
+ hid_warn(hid, "unexpected rdesc, please submit for review\n");
+ }
+ return rdesc;
+}
+
+static const struct hid_device_id shanwan_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_SHANWAN, USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, shanwan_devices);
+
+static struct hid_driver shanwan_driver = {
+ .name = "shanwan",
+ .id_table = shanwan_devices,
+ .probe = shanwan_probe,
+ .report_fixup = shanwan_report_fixup,
+ .remove = shanwan_remove,
+};
+module_hid_driver(shanwan_driver);
+
+MODULE_AUTHOR("Huseyin BIYIK <[email protected]>");
+MODULE_AUTHOR("Ahmad Hasan Mubashshir <[email protected]>")
+MODULE_DESCRIPTION("Force feedback support for Shanwan USB WirelessGamepad");
+MODULE_LICENSE("GPL");
--
2.40.0


2023-04-10 12:06:27

by Mubashshir

[permalink] [raw]
Subject: [PATCH v2] staging: HID: Add ShanWan USB WirelessGamepad driver

This device has a quirky initialization process.
Depending on how it was initialized, behaves completely differently.
In default mode, it behaves as expected, but in fallback it disables
force-feedback, analog stick configurations and L3/R3.

Signed-off-by: Huseyin BIYIK <[email protected]>
Signed-off-by: Mubashshir <[email protected]>
---
drivers/hid/Kconfig | 19 +++
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 3 +
drivers/hid/hid-shanwan.c | 256 ++++++++++++++++++++++++++++++++++++++
4 files changed, 279 insertions(+)
create mode 100644 drivers/hid/hid-shanwan.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 82f64fb31fda..a17db9c9694c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -990,6 +990,25 @@ config HID_SEMITEK
- Woo-dy
- X-Bows Nature/Knight

+config HID_SHANWAN
+ tristate "ShanWan USB WirelessGamepad"
+ depends on USB_HID
+ help
+ Support for Shanwan USB WirelessGamepad (and clones).
+
+ This device has a quirky initialization process.
+ Depending on how it was initialized, it behaves completely differently.
+ In default mode, it behaves as expected, but in fallback it disables
+ force-feedback, analog stick configurations and L3/R3.
+
+config SHANWAN_FF
+ bool "ShanWan USB WirelessGamepad force feedback support"
+ depends on HID_SHANWAN
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you have a ShanWan USB WirelessGamepad and want to enable
+ force feedback support for it.
+
config HID_SIGMAMICRO
tristate "SiGma Micro-based keyboards"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 5d37cacbde33..52878455fc10 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o
+obj-$(CONFIG_HID_SHANWAN) += hid-shanwan.o
obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 63545cd307e5..278914e37eb7 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -623,6 +623,9 @@
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a

+#define USB_VENDOR_ID_SHANWAN 0x2563
+#define USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD 0x0575
+
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
#define USB_DEVICE_ID_HUION_TABLET2 0x006d
diff --git a/drivers/hid/hid-shanwan.c b/drivers/hid/hid-shanwan.c
new file mode 100644
index 000000000000..dfe9ec26b31c
--- /dev/null
+++ b/drivers/hid/hid-shanwan.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Force feedback support for Shanwan USB WirelessGamepad
+ *
+ * Copyright (c) 2022-2023 Huseyin BIYIK <[email protected]>
+ * Copyright (c) 2023 Ahmad Hasan Mubashshir <[email protected]>
+ *
+ * mapping according to Gamepad Protocol
+ *
+ * Button 01: BTN_SOUTH (CROSS)
+ * Button 02: BTN_EAST(CIRCLE)
+ * Button 03: BTN_NORTH (TRIANGLE)
+ * Button 04: BTN_WEST (SQUARE)
+ * Button 05: BTL_TL (L1)
+ * Button 06: BTM_TR (R1)
+ * Button 07: BTN_TL2 (L2)
+ * Button 08: BTN_TR2 (R2)
+ * Button 09: BTN_SELECT
+ * Button 10: BTN_START
+ * Button 11: BTN_MODE
+ * Button 12: BTN_THUMBL (LS1)
+ * Button 13: BTN_THUMBR (LS1)
+ * LS1: X/Y AXIS
+ * LS2: Rx/Ry AXIS
+ * R2/L2 Touch Sensors: R/Rz
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/moduleparam.h>
+
+#include "hid-ids.h"
+
+#define PID0575_RDESC_ORIG_SIZE 137
+
+static bool swap_motors;
+module_param_named(swap, swap_motors, bool, 0);
+MODULE_PARM_DESC(swap, "Swap Weak/Strong Feedback motors");
+
+static __u8 pid0575_rdesc_fixed[] = {
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x05, // Usage (Game Pad)
+ 0xA1, 0x01, // Collection (Application)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x45, 0x01, // Physical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x0D, // Report Count (13)
+ 0x05, 0x09, // Usage Page (Button)
+ 0x09, 0x03, // Usage (BTN_NORTH)
+ 0x09, 0x02, // Usage (BTN_EAST)
+ 0x09, 0x01, // Usage (BTN_SOUTH)
+ 0x09, 0x04, // Usage (BTN_WEST)
+ 0x09, 0x05, // Usage (BTN_TL)
+ 0x09, 0x06, // Usage (BTN_TR)
+ 0x09, 0x07, // Usage (BTN_TL2)
+ 0x09, 0x08, // Usage (BTN_TR2)
+ 0x09, 0x09, // Usage (BTN_SELECT)
+ 0x09, 0x10, // Usage (BTN_START)
+ 0x09, 0x12, // Usage (BTN_THUMBL)
+ 0x09, 0x13, // Usage (BTN_THUMBR)
+ 0x09, 0x11, // Usage (BTN_MODE)
+ 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x03, // Report Count (3)
+ 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x25, 0x07, // Logical Maximum (7)
+ 0x46, 0x3B, 0x01, // Physical Maximum (315)
+ 0x75, 0x04, // Report Size (4)
+ 0x95, 0x01, // Report Count (1)
+ 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter)
+ 0x09, 0x39, // Usage (Hat switch)
+ 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NullState)
+ 0x65, 0x00, // Unit (None)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x01, // Input(Const,Array,Abs,NoWrap,Linear,PreferredState,NoNullPosition)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x46, 0xFF, 0x00, // Physical Maximum (255)
+ 0x09, 0x30, // Usage (X)
+ 0x09, 0x31, // Usage (Y)
+ 0x09, 0x33, // Usage (Rx)
+ 0x09, 0x34, // Usage (Ry)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x04, // Report Count (4)
+ 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x95, 0x0A, // Report Count (10)
+ 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x46, 0xFF, 0x00, // Physical Maximum (255)
+ 0x09, 0x32, // Usage (Z)
+ 0x09, 0x35, // Usage (Rz)
+ 0x95, 0x02, // Report Count (2)
+ 0x81, 0x02, // Input(Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x01, // Input(Const,Array,Abs,NoWrap,Linear,PreferredState,NoNullPosition)
+ 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
+ 0xB1, 0x02, // Feature(Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition,!volatile)
+ 0x0A, 0x21, 0x26, // Usage (0x2621)
+ 0x95, 0x08, // Report Count (8)
+ 0x91, 0x02, // Output(Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition,!volatile)
+ 0x0A, 0x21, 0x26, // Usage (0x2621)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x02, // Input(Data,Var,Abs,No Wrap,Linear,Preferred State,NoNullPosition)
+ 0xC0, // End Collection
+};
+
+struct shanwan_device {
+ struct hid_report *report;
+};
+
+#ifdef CONFIG_SHANWAN_FF
+static int shanwan_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct shanwan_device *shanwan = data;
+ struct hid_report *report = shanwan->report;
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ report->field[0]->value[0] = 0x02; /* 2 = rumble effect message */
+ report->field[0]->value[1] = 0x08; /* reserved value, always 8 */
+ if (swap_motors) {
+ /* weak rumble / strong rumble */
+ report->field[0]->value[2] = effect->u.rumble.strong_magnitude / 256;
+ report->field[0]->value[3] = effect->u.rumble.weak_magnitude / 256;
+ } else {
+ /* strong rumble / weak rumble */
+ report->field[0]->value[2] = effect->u.rumble.weak_magnitude / 256;
+ report->field[0]->value[3] = effect->u.rumble.strong_magnitude / 256;
+ }
+ report->field[0]->value[4] = 0xff; /* duration 0-254 (255 = nonstop) */
+ report->field[0]->value[5] = 0x00; /* padding */
+ report->field[0]->value[6] = 0x00; /* padding */
+ report->field[0]->value[7] = 0x00; /* padding */
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
+ return 0;
+}
+
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ struct shanwan_device *shanwan;
+ struct hid_report *report;
+ struct hid_input *hidinput;
+ struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct input_dev *dev;
+
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ dev = hidinput->input;
+
+ if (list_empty(report_list)) {
+ hid_err(hid, "no output reports found\n");
+ return -ENODEV;
+ }
+
+ report = list_first_entry(report_list, struct hid_report, list);
+
+ shanwan = kzalloc(sizeof(*shanwan), GFP_KERNEL);
+ if (!shanwan)
+ return -ENOMEM;
+
+ set_bit(FF_RUMBLE, dev->ffbit);
+
+ if (input_ff_create_memless(dev, shanwan, shanwan_play_effect)) {
+ kfree(shanwan);
+ return -ENODEV;
+ }
+
+ shanwan->report = report;
+
+ return 0;
+}
+#else
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ return 0
+}
+#endif
+
+static int shanwan_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int error;
+
+ error = hid_parse(hdev);
+ if (error) {
+ hid_err(hdev, "parse failed\n");
+ return error;
+ }
+
+ error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ if (error) {
+ hid_err(hdev, "hw start failed\n");
+ return error;
+ }
+
+ error = shanwan_init_ff(hdev);
+ if (error)
+ hid_warn(hdev, "Failed to enable force feedback support, error: %d\n", error);
+
+ error = hid_hw_open(hdev);
+ if (error) {
+ dev_err(&hdev->dev, "hw open failed\n");
+ hid_hw_stop(hdev);
+ return error;
+ }
+
+ return 0;
+}
+
+static void shanwan_remove(struct hid_device *hdev)
+{
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static __u8 *shanwan_report_fixup(struct hid_device *hid, __u8 *rdesc, unsigned int *rsize)
+{
+ if (*rsize == PID0575_RDESC_ORIG_SIZE) {
+ rdesc = pid0575_rdesc_fixed;
+ *rsize = sizeof(pid0575_rdesc_fixed);
+ } else {
+ hid_warn(hid, "unexpected rdesc, please submit for review\n");
+ }
+ return rdesc;
+}
+
+static const struct hid_device_id shanwan_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_SHANWAN, USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, shanwan_devices);
+
+static struct hid_driver shanwan_driver = {
+ .name = "shanwan",
+ .id_table = shanwan_devices,
+ .probe = shanwan_probe,
+ .report_fixup = shanwan_report_fixup,
+ .remove = shanwan_remove,
+};
+module_hid_driver(shanwan_driver);
+
+MODULE_AUTHOR("Huseyin BIYIK <[email protected]>");
+MODULE_AUTHOR("Ahmad Hasan Mubashshir <[email protected]>");
+MODULE_DESCRIPTION("Force feedback support for Shanwan USB WirelessGamepad");
+MODULE_LICENSE("GPL");
--
2.40.0

2023-04-10 12:07:45

by Mubashshir

[permalink] [raw]
Subject: Re: [PATCH v2] staging: HID: Add ShanWan USB WirelessGamepad driver

On Mon, Apr 10 2023 at 05:59:30 PM +06:00:00, Mubashshir
<[email protected]> wrote:
> +MODULE_AUTHOR("Ahmad Hasan Mubashshir <[email protected]>");

Sorry, I missed the semicolon here on my previous patch.


2023-04-10 12:32:46

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH] staging: HID: Add ShanWan USB WirelessGamepad driver

Hi Mubashshir,

kernel test robot noticed the following build errors:

[auto build test ERROR on staging/staging-testing]

url: https://github.com/intel-lab-lkp/linux/commits/Mubashshir/staging-HID-Add-ShanWan-USB-WirelessGamepad-driver/20230410-172120
patch link: https://lore.kernel.org/r/39b44678dc54b519fa469b69d80757b36ab3cf25.1681118245.git.ahmubashshir%40gmail.com
patch subject: [PATCH] staging: HID: Add ShanWan USB WirelessGamepad driver
config: sh-allmodconfig (https://download.01.org/0day-ci/archive/20230410/[email protected]/config)
compiler: sh4-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/bdfd2a839da337ac583bfa8ae2cb19f0388619b8
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Mubashshir/staging-HID-Add-ShanWan-USB-WirelessGamepad-driver/20230410-172120
git checkout bdfd2a839da337ac583bfa8ae2cb19f0388619b8
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sh olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sh SHELL=/bin/bash drivers/hid/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

In file included from include/linux/module.h:22,
from include/linux/device/driver.h:21,
from include/linux/device.h:32,
from include/linux/input.h:19,
from drivers/hid/hid-shanwan.c:28:
>> include/linux/moduleparam.h:24:9: error: expected ',' or ';' before 'static'
24 | static const char __UNIQUE_ID(name)[] \
| ^~~~~~
include/linux/module.h:165:32: note: in expansion of macro '__MODULE_INFO'
165 | #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
| ^~~~~~~~~~~~~
include/linux/module.h:238:42: note: in expansion of macro 'MODULE_INFO'
238 | #define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
| ^~~~~~~~~~~
drivers/hid/hid-shanwan.c:255:1: note: in expansion of macro 'MODULE_DESCRIPTION'
255 | MODULE_DESCRIPTION("Force feedback support for Shanwan USB WirelessGamepad");
| ^~~~~~~~~~~~~~~~~~


vim +24 include/linux/moduleparam.h

730b69d2252595 Rusty Russell 2008-10-22 22
^1da177e4c3f41 Linus Torvalds 2005-04-16 23 #define __MODULE_INFO(tag, name, info) \
34182eea36fc1d Rusty Russell 2012-11-22 @24 static const char __UNIQUE_ID(name)[] \
2aec389e19150e Johan Hovold 2020-11-23 25 __used __section(".modinfo") __aligned(1) \
898490c010b5d2 Alexey Gladkov 2019-04-29 26 = __MODULE_INFO_PREFIX __stringify(tag) "=" info
898490c010b5d2 Alexey Gladkov 2019-04-29 27

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests

2023-04-10 13:06:37

by Mubashshir

[permalink] [raw]
Subject: [PATCH v3] staging: HID: Add ShanWan USB WirelessGamepad driver

This device has a quirky initialization process.
Depending on how it was initialized, behaves completely differently.
In default mode, it behaves as expected, but in fallback it disables
force-feedback, analog stick configurations and L3/R3.

Signed-off-by: Huseyin BIYIK <[email protected]>
Signed-off-by: Mubashshir <[email protected]>
---
v3: Another missed semicolon

drivers/hid/Kconfig | 19 +++
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 3 +
drivers/hid/hid-shanwan.c | 256 ++++++++++++++++++++++++++++++++++++++
4 files changed, 279 insertions(+)
create mode 100644 drivers/hid/hid-shanwan.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 82f64fb31fda..a17db9c9694c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -990,6 +990,25 @@ config HID_SEMITEK
- Woo-dy
- X-Bows Nature/Knight

+config HID_SHANWAN
+ tristate "ShanWan USB WirelessGamepad"
+ depends on USB_HID
+ help
+ Support for Shanwan USB WirelessGamepad (and clones).
+
+ This device has a quirky initialization process.
+ Depending on how it was initialized, it behaves completely differently.
+ In default mode, it behaves as expected, but in fallback it disables
+ force-feedback, analog stick configurations and L3/R3.
+
+config SHANWAN_FF
+ bool "ShanWan USB WirelessGamepad force feedback support"
+ depends on HID_SHANWAN
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you have a ShanWan USB WirelessGamepad and want to enable
+ force feedback support for it.
+
config HID_SIGMAMICRO
tristate "SiGma Micro-based keyboards"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 5d37cacbde33..52878455fc10 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o
+obj-$(CONFIG_HID_SHANWAN) += hid-shanwan.o
obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 63545cd307e5..278914e37eb7 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -623,6 +623,9 @@
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a

+#define USB_VENDOR_ID_SHANWAN 0x2563
+#define USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD 0x0575
+
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
#define USB_DEVICE_ID_HUION_TABLET2 0x006d
diff --git a/drivers/hid/hid-shanwan.c b/drivers/hid/hid-shanwan.c
new file mode 100644
index 000000000000..39c1bb6f40c6
--- /dev/null
+++ b/drivers/hid/hid-shanwan.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Force feedback support for Shanwan USB WirelessGamepad
+ *
+ * Copyright (c) 2022-2023 Huseyin BIYIK <[email protected]>
+ * Copyright (c) 2023 Ahmad Hasan Mubashshir <[email protected]>
+ *
+ * mapping according to Gamepad Protocol
+ *
+ * Button 01: BTN_SOUTH (CROSS)
+ * Button 02: BTN_EAST(CIRCLE)
+ * Button 03: BTN_NORTH (TRIANGLE)
+ * Button 04: BTN_WEST (SQUARE)
+ * Button 05: BTL_TL (L1)
+ * Button 06: BTM_TR (R1)
+ * Button 07: BTN_TL2 (L2)
+ * Button 08: BTN_TR2 (R2)
+ * Button 09: BTN_SELECT
+ * Button 10: BTN_START
+ * Button 11: BTN_MODE
+ * Button 12: BTN_THUMBL (LS1)
+ * Button 13: BTN_THUMBR (LS1)
+ * LS1: X/Y AXIS
+ * LS2: Rx/Ry AXIS
+ * R2/L2 Touch Sensors: R/Rz
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/moduleparam.h>
+
+#include "hid-ids.h"
+
+#define PID0575_RDESC_ORIG_SIZE 137
+
+static bool swap_motors;
+module_param_named(swap, swap_motors, bool, 0);
+MODULE_PARM_DESC(swap, "Swap Weak/Strong Feedback motors");
+
+static __u8 pid0575_rdesc_fixed[] = {
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x05, // Usage (Game Pad)
+ 0xA1, 0x01, // Collection (Application)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x35, 0x00, // Physical Minimum (0)
+ 0x45, 0x01, // Physical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x0D, // Report Count (13)
+ 0x05, 0x09, // Usage Page (Button)
+ 0x09, 0x03, // Usage (BTN_NORTH)
+ 0x09, 0x02, // Usage (BTN_EAST)
+ 0x09, 0x01, // Usage (BTN_SOUTH)
+ 0x09, 0x04, // Usage (BTN_WEST)
+ 0x09, 0x05, // Usage (BTN_TL)
+ 0x09, 0x06, // Usage (BTN_TR)
+ 0x09, 0x07, // Usage (BTN_TL2)
+ 0x09, 0x08, // Usage (BTN_TR2)
+ 0x09, 0x09, // Usage (BTN_SELECT)
+ 0x09, 0x10, // Usage (BTN_START)
+ 0x09, 0x12, // Usage (BTN_THUMBL)
+ 0x09, 0x13, // Usage (BTN_THUMBR)
+ 0x09, 0x11, // Usage (BTN_MODE)
+ 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x03, // Report Count (3)
+ 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x25, 0x07, // Logical Maximum (7)
+ 0x46, 0x3B, 0x01, // Physical Maximum (315)
+ 0x75, 0x04, // Report Size (4)
+ 0x95, 0x01, // Report Count (1)
+ 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter)
+ 0x09, 0x39, // Usage (Hat switch)
+ 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NullState)
+ 0x65, 0x00, // Unit (None)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x01, // Input(Const,Array,Abs,NoWrap,Linear,PreferredState,NoNullPosition)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x46, 0xFF, 0x00, // Physical Maximum (255)
+ 0x09, 0x30, // Usage (X)
+ 0x09, 0x31, // Usage (Y)
+ 0x09, 0x33, // Usage (Rx)
+ 0x09, 0x34, // Usage (Ry)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x04, // Report Count (4)
+ 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x95, 0x0A, // Report Count (10)
+ 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x46, 0xFF, 0x00, // Physical Maximum (255)
+ 0x09, 0x32, // Usage (Z)
+ 0x09, 0x35, // Usage (Rz)
+ 0x95, 0x02, // Report Count (2)
+ 0x81, 0x02, // Input(Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x01, // Input(Const,Array,Abs,NoWrap,Linear,PreferredState,NoNullPosition)
+ 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
+ 0xB1, 0x02, // Feature(Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition,!volatile)
+ 0x0A, 0x21, 0x26, // Usage (0x2621)
+ 0x95, 0x08, // Report Count (8)
+ 0x91, 0x02, // Output(Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition,!volatile)
+ 0x0A, 0x21, 0x26, // Usage (0x2621)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x02, // Input(Data,Var,Abs,No Wrap,Linear,Preferred State,NoNullPosition)
+ 0xC0, // End Collection
+};
+
+struct shanwan_device {
+ struct hid_report *report;
+};
+
+#ifdef CONFIG_SHANWAN_FF
+static int shanwan_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct shanwan_device *shanwan = data;
+ struct hid_report *report = shanwan->report;
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ report->field[0]->value[0] = 0x02; /* 2 = rumble effect message */
+ report->field[0]->value[1] = 0x08; /* reserved value, always 8 */
+ if (swap_motors) {
+ /* weak rumble / strong rumble */
+ report->field[0]->value[2] = effect->u.rumble.strong_magnitude / 256;
+ report->field[0]->value[3] = effect->u.rumble.weak_magnitude / 256;
+ } else {
+ /* strong rumble / weak rumble */
+ report->field[0]->value[2] = effect->u.rumble.weak_magnitude / 256;
+ report->field[0]->value[3] = effect->u.rumble.strong_magnitude / 256;
+ }
+ report->field[0]->value[4] = 0xff; /* duration 0-254 (255 = nonstop) */
+ report->field[0]->value[5] = 0x00; /* padding */
+ report->field[0]->value[6] = 0x00; /* padding */
+ report->field[0]->value[7] = 0x00; /* padding */
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
+ return 0;
+}
+
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ struct shanwan_device *shanwan;
+ struct hid_report *report;
+ struct hid_input *hidinput;
+ struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct input_dev *dev;
+
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ dev = hidinput->input;
+
+ if (list_empty(report_list)) {
+ hid_err(hid, "no output reports found\n");
+ return -ENODEV;
+ }
+
+ report = list_first_entry(report_list, struct hid_report, list);
+
+ shanwan = kzalloc(sizeof(*shanwan), GFP_KERNEL);
+ if (!shanwan)
+ return -ENOMEM;
+
+ set_bit(FF_RUMBLE, dev->ffbit);
+
+ if (input_ff_create_memless(dev, shanwan, shanwan_play_effect)) {
+ kfree(shanwan);
+ return -ENODEV;
+ }
+
+ shanwan->report = report;
+
+ return 0;
+}
+#else
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ return 0;
+}
+#endif
+
+static int shanwan_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int error;
+
+ error = hid_parse(hdev);
+ if (error) {
+ hid_err(hdev, "parse failed\n");
+ return error;
+ }
+
+ error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ if (error) {
+ hid_err(hdev, "hw start failed\n");
+ return error;
+ }
+
+ error = shanwan_init_ff(hdev);
+ if (error)
+ hid_warn(hdev, "Failed to enable force feedback support, error: %d\n", error);
+
+ error = hid_hw_open(hdev);
+ if (error) {
+ dev_err(&hdev->dev, "hw open failed\n");
+ hid_hw_stop(hdev);
+ return error;
+ }
+
+ return 0;
+}
+
+static void shanwan_remove(struct hid_device *hdev)
+{
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static __u8 *shanwan_report_fixup(struct hid_device *hid, __u8 *rdesc, unsigned int *rsize)
+{
+ if (*rsize == PID0575_RDESC_ORIG_SIZE) {
+ rdesc = pid0575_rdesc_fixed;
+ *rsize = sizeof(pid0575_rdesc_fixed);
+ } else {
+ hid_warn(hid, "unexpected rdesc, please submit for review\n");
+ }
+ return rdesc;
+}
+
+static const struct hid_device_id shanwan_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_SHANWAN, USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, shanwan_devices);
+
+static struct hid_driver shanwan_driver = {
+ .name = "shanwan",
+ .id_table = shanwan_devices,
+ .probe = shanwan_probe,
+ .report_fixup = shanwan_report_fixup,
+ .remove = shanwan_remove,
+};
+module_hid_driver(shanwan_driver);
+
+MODULE_AUTHOR("Huseyin BIYIK <[email protected]>");
+MODULE_AUTHOR("Ahmad Hasan Mubashshir <[email protected]>");
+MODULE_DESCRIPTION("Force feedback support for Shanwan USB WirelessGamepad");
+MODULE_LICENSE("GPL");
--
2.40.0

2023-04-13 16:15:35

by Benjamin Tissoires

[permalink] [raw]
Subject: Re: [PATCH v3] staging: HID: Add ShanWan USB WirelessGamepad driver

On Apr 10 2023, Mubashshir wrote:
> This device has a quirky initialization process.
> Depending on how it was initialized, behaves completely differently.
> In default mode, it behaves as expected, but in fallback it disables
> force-feedback, analog stick configurations and L3/R3.
>
> Signed-off-by: Huseyin BIYIK <[email protected]>
> Signed-off-by: Mubashshir <[email protected]>
> ---
> v3: Another missed semicolon
>
> drivers/hid/Kconfig | 19 +++
> drivers/hid/Makefile | 1 +
> drivers/hid/hid-ids.h | 3 +
> drivers/hid/hid-shanwan.c | 256 ++++++++++++++++++++++++++++++++++++++
> 4 files changed, 279 insertions(+)
> create mode 100644 drivers/hid/hid-shanwan.c
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 82f64fb31fda..a17db9c9694c 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -990,6 +990,25 @@ config HID_SEMITEK
> - Woo-dy
> - X-Bows Nature/Knight
>
> +config HID_SHANWAN
> + tristate "ShanWan USB WirelessGamepad"
> + depends on USB_HID
> + help
> + Support for Shanwan USB WirelessGamepad (and clones).
> +
> + This device has a quirky initialization process.
> + Depending on how it was initialized, it behaves completely differently.
> + In default mode, it behaves as expected, but in fallback it disables
> + force-feedback, analog stick configurations and L3/R3.
> +
> +config SHANWAN_FF
> + bool "ShanWan USB WirelessGamepad force feedback support"
> + depends on HID_SHANWAN
> + select INPUT_FF_MEMLESS
> + help
> + Say Y here if you have a ShanWan USB WirelessGamepad and want to enable
> + force feedback support for it.
> +
> config HID_SIGMAMICRO
> tristate "SiGma Micro-based keyboards"
> depends on USB_HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 5d37cacbde33..52878455fc10 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -116,6 +116,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o
> obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
> obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
> obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o
> +obj-$(CONFIG_HID_SHANWAN) += hid-shanwan.o
> obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o
> obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
> obj-$(CONFIG_HID_SONY) += hid-sony.o
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 63545cd307e5..278914e37eb7 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -623,6 +623,9 @@
> #define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
> #define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a
>
> +#define USB_VENDOR_ID_SHANWAN 0x2563
> +#define USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD 0x0575
> +
> #define USB_VENDOR_ID_HUION 0x256c
> #define USB_DEVICE_ID_HUION_TABLET 0x006e
> #define USB_DEVICE_ID_HUION_TABLET2 0x006d
> diff --git a/drivers/hid/hid-shanwan.c b/drivers/hid/hid-shanwan.c
> new file mode 100644
> index 000000000000..39c1bb6f40c6
> --- /dev/null
> +++ b/drivers/hid/hid-shanwan.c
> @@ -0,0 +1,256 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Force feedback support for Shanwan USB WirelessGamepad
> + *
> + * Copyright (c) 2022-2023 Huseyin BIYIK <[email protected]>
> + * Copyright (c) 2023 Ahmad Hasan Mubashshir <[email protected]>
> + *
> + * mapping according to Gamepad Protocol
> + *
> + * Button 01: BTN_SOUTH (CROSS)
> + * Button 02: BTN_EAST(CIRCLE)
> + * Button 03: BTN_NORTH (TRIANGLE)
> + * Button 04: BTN_WEST (SQUARE)
> + * Button 05: BTL_TL (L1)
> + * Button 06: BTM_TR (R1)
> + * Button 07: BTN_TL2 (L2)
> + * Button 08: BTN_TR2 (R2)
> + * Button 09: BTN_SELECT
> + * Button 10: BTN_START
> + * Button 11: BTN_MODE
> + * Button 12: BTN_THUMBL (LS1)
> + * Button 13: BTN_THUMBR (LS1)
> + * LS1: X/Y AXIS
> + * LS2: Rx/Ry AXIS
> + * R2/L2 Touch Sensors: R/Rz
> + */
> +
> +#include <linux/input.h>
> +#include <linux/slab.h>
> +#include <linux/hid.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <linux/moduleparam.h>
> +
> +#include "hid-ids.h"
> +
> +#define PID0575_RDESC_ORIG_SIZE 137
> +
> +static bool swap_motors;
> +module_param_named(swap, swap_motors, bool, 0);
> +MODULE_PARM_DESC(swap, "Swap Weak/Strong Feedback motors");
> +
> +static __u8 pid0575_rdesc_fixed[] = {
> + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
> + 0x09, 0x05, // Usage (Game Pad)
> + 0xA1, 0x01, // Collection (Application)
> + 0x15, 0x00, // Logical Minimum (0)
> + 0x25, 0x01, // Logical Maximum (1)
> + 0x35, 0x00, // Physical Minimum (0)
> + 0x45, 0x01, // Physical Maximum (1)
> + 0x75, 0x01, // Report Size (1)
> + 0x95, 0x0D, // Report Count (13)
> + 0x05, 0x09, // Usage Page (Button)
> + 0x09, 0x03, // Usage (BTN_NORTH)
> + 0x09, 0x02, // Usage (BTN_EAST)
> + 0x09, 0x01, // Usage (BTN_SOUTH)
> + 0x09, 0x04, // Usage (BTN_WEST)
> + 0x09, 0x05, // Usage (BTN_TL)
> + 0x09, 0x06, // Usage (BTN_TR)
> + 0x09, 0x07, // Usage (BTN_TL2)
> + 0x09, 0x08, // Usage (BTN_TR2)
> + 0x09, 0x09, // Usage (BTN_SELECT)
> + 0x09, 0x10, // Usage (BTN_START)
> + 0x09, 0x12, // Usage (BTN_THUMBL)
> + 0x09, 0x13, // Usage (BTN_THUMBR)
> + 0x09, 0x11, // Usage (BTN_MODE)
> + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
> + 0x75, 0x01, // Report Size (1)
> + 0x95, 0x03, // Report Count (3)
> + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
> + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
> + 0x25, 0x07, // Logical Maximum (7)
> + 0x46, 0x3B, 0x01, // Physical Maximum (315)
> + 0x75, 0x04, // Report Size (4)
> + 0x95, 0x01, // Report Count (1)
> + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter)
> + 0x09, 0x39, // Usage (Hat switch)
> + 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NullState)
> + 0x65, 0x00, // Unit (None)
> + 0x95, 0x01, // Report Count (1)
> + 0x81, 0x01, // Input(Const,Array,Abs,NoWrap,Linear,PreferredState,NoNullPosition)
> + 0x26, 0xFF, 0x00, // Logical Maximum (255)
> + 0x46, 0xFF, 0x00, // Physical Maximum (255)
> + 0x09, 0x30, // Usage (X)
> + 0x09, 0x31, // Usage (Y)
> + 0x09, 0x33, // Usage (Rx)
> + 0x09, 0x34, // Usage (Ry)
> + 0x75, 0x08, // Report Size (8)
> + 0x95, 0x04, // Report Count (4)
> + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
> + 0x95, 0x0A, // Report Count (10)
> + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,PreferredState,NoNullPosition)
> + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
> + 0x26, 0xFF, 0x00, // Logical Maximum (255)
> + 0x46, 0xFF, 0x00, // Physical Maximum (255)
> + 0x09, 0x32, // Usage (Z)
> + 0x09, 0x35, // Usage (Rz)
> + 0x95, 0x02, // Report Count (2)
> + 0x81, 0x02, // Input(Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
> + 0x95, 0x08, // Report Count (8)
> + 0x81, 0x01, // Input(Const,Array,Abs,NoWrap,Linear,PreferredState,NoNullPosition)
> + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
> + 0xB1, 0x02, // Feature(Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition,!volatile)
> + 0x0A, 0x21, 0x26, // Usage (0x2621)
> + 0x95, 0x08, // Report Count (8)
> + 0x91, 0x02, // Output(Data,Var,Abs,No Wrap,Linear,PreferredState,NoNullPosition,!volatile)
> + 0x0A, 0x21, 0x26, // Usage (0x2621)
> + 0x95, 0x08, // Report Count (8)
> + 0x81, 0x02, // Input(Data,Var,Abs,No Wrap,Linear,Preferred State,NoNullPosition)
> + 0xC0, // End Collection
> +};
> +
> +struct shanwan_device {
> + struct hid_report *report;
> +};
> +
> +#ifdef CONFIG_SHANWAN_FF
> +static int shanwan_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
> +{
> + struct hid_device *hid = input_get_drvdata(dev);
> + struct shanwan_device *shanwan = data;
> + struct hid_report *report = shanwan->report;
> +
> + if (effect->type != FF_RUMBLE)
> + return 0;
> +
> + report->field[0]->value[0] = 0x02; /* 2 = rumble effect message */
> + report->field[0]->value[1] = 0x08; /* reserved value, always 8 */
> + if (swap_motors) {
> + /* weak rumble / strong rumble */
> + report->field[0]->value[2] = effect->u.rumble.strong_magnitude / 256;
> + report->field[0]->value[3] = effect->u.rumble.weak_magnitude / 256;
> + } else {
> + /* strong rumble / weak rumble */
> + report->field[0]->value[2] = effect->u.rumble.weak_magnitude / 256;
> + report->field[0]->value[3] = effect->u.rumble.strong_magnitude / 256;
> + }
> + report->field[0]->value[4] = 0xff; /* duration 0-254 (255 = nonstop) */
> + report->field[0]->value[5] = 0x00; /* padding */
> + report->field[0]->value[6] = 0x00; /* padding */
> + report->field[0]->value[7] = 0x00; /* padding */
> + hid_hw_request(hid, report, HID_REQ_SET_REPORT);

I see some other drivers using a workqueue here because ->play() can be
called in atomic context. Are you sure you can sleep here?

> +
> + return 0;
> +}
> +
> +static int shanwan_init_ff(struct hid_device *hid)
> +{
> + struct shanwan_device *shanwan;
> + struct hid_report *report;
> + struct hid_input *hidinput;
> + struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
> + struct input_dev *dev;
> +
> + if (list_empty(&hid->inputs)) {
> + hid_err(hid, "no inputs found\n");
> + return -ENODEV;
> + }
> + hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
> + dev = hidinput->input;
> +
> + if (list_empty(report_list)) {
> + hid_err(hid, "no output reports found\n");
> + return -ENODEV;
> + }
> +
> + report = list_first_entry(report_list, struct hid_report, list);
> +
> + shanwan = kzalloc(sizeof(*shanwan), GFP_KERNEL);
> + if (!shanwan)
> + return -ENOMEM;
> +
> + set_bit(FF_RUMBLE, dev->ffbit);
> +
> + if (input_ff_create_memless(dev, shanwan, shanwan_play_effect)) {
> + kfree(shanwan);
> + return -ENODEV;
> + }
> +
> + shanwan->report = report;
> +
> + return 0;
> +}
> +#else
> +static int shanwan_init_ff(struct hid_device *hid)
> +{
> + return 0;
> +}
> +#endif
> +
> +static int shanwan_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> + int error;
> +
> + error = hid_parse(hdev);
> + if (error) {
> + hid_err(hdev, "parse failed\n");
> + return error;
> + }
> +
> + error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
> + if (error) {
> + hid_err(hdev, "hw start failed\n");
> + return error;
> + }
> +
> + error = shanwan_init_ff(hdev);
> + if (error)
> + hid_warn(hdev, "Failed to enable force feedback support, error: %d\n", error);
> +
> + error = hid_hw_open(hdev);

What's the point of keeping it opened for the lifetime of the device? Do
you really need this?

> + if (error) {
> + dev_err(&hdev->dev, "hw open failed\n");
> + hid_hw_stop(hdev);
> + return error;
> + }
> +
> + return 0;
> +}
> +
> +static void shanwan_remove(struct hid_device *hdev)
> +{
> + hid_hw_close(hdev);

If you can drop the last hid_hw_open/close, then you can entirely skip
the ->remove().

Cheers,
Benjamin

> + hid_hw_stop(hdev);
> +}
> +
> +static __u8 *shanwan_report_fixup(struct hid_device *hid, __u8 *rdesc, unsigned int *rsize)
> +{
> + if (*rsize == PID0575_RDESC_ORIG_SIZE) {
> + rdesc = pid0575_rdesc_fixed;
> + *rsize = sizeof(pid0575_rdesc_fixed);
> + } else {
> + hid_warn(hid, "unexpected rdesc, please submit for review\n");
> + }
> + return rdesc;
> +}
> +
> +static const struct hid_device_id shanwan_devices[] = {
> + { HID_USB_DEVICE(USB_VENDOR_ID_SHANWAN, USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD) },
> + { }
> +};
> +MODULE_DEVICE_TABLE(hid, shanwan_devices);
> +
> +static struct hid_driver shanwan_driver = {
> + .name = "shanwan",
> + .id_table = shanwan_devices,
> + .probe = shanwan_probe,
> + .report_fixup = shanwan_report_fixup,
> + .remove = shanwan_remove,
> +};
> +module_hid_driver(shanwan_driver);
> +
> +MODULE_AUTHOR("Huseyin BIYIK <[email protected]>");
> +MODULE_AUTHOR("Ahmad Hasan Mubashshir <[email protected]>");
> +MODULE_DESCRIPTION("Force feedback support for Shanwan USB WirelessGamepad");
> +MODULE_LICENSE("GPL");
> --
> 2.40.0
>

2023-04-19 06:43:36

by Mubashshir

[permalink] [raw]
Subject: Re: [PATCH v3] staging: HID: Add ShanWan USB WirelessGamepad driver

On Thu, Apr 13 2023 at 06:13:05 PM +02:00:00, Benjamin Tissoires
<[email protected]> wrote:
> What's the point of keeping it opened for the lifetime of the device?
> Do
> you really need this?
>
>> + if (error) {
>> + dev_err(&hdev->dev, "hw open failed\n");
>> + hid_hw_stop(hdev);
>> + return error;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void shanwan_remove(struct hid_device *hdev)
>> +{
>> + hid_hw_close(hdev);
>
> If you can drop the last hid_hw_open/close, then you can entirely skip
> the ->remove().

Would hid_hw_request work without open device?


2023-04-25 09:11:04

by Benjamin Tissoires

[permalink] [raw]
Subject: Re: [PATCH v3] staging: HID: Add ShanWan USB WirelessGamepad driver

On Apr 19 2023, Mubashshir wrote:
> On Thu, Apr 13 2023 at 06:13:05 PM +02:00:00, Benjamin Tissoires
> <[email protected]> wrote:
> > What's the point of keeping it opened for the lifetime of the device? Do
> > you really need this?
> >
> > > + if (error) {
> > > + dev_err(&hdev->dev, "hw open failed\n");
> > > + hid_hw_stop(hdev);
> > > + return error;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void shanwan_remove(struct hid_device *hdev)
> > > +{
> > > + hid_hw_close(hdev);
> >
> > If you can drop the last hid_hw_open/close, then you can entirely skip
> > the ->remove().
>
> Would hid_hw_request work without open device?

No, it won't work if the device is not opened. But what's the point of
sending rumble data to the joystick if the device is not opened (so used)
by anybody? open() is called whenever a client opens the device, by
accessing the evdev node, so I think it should be fine.

Cheers,
Benjamin

2023-04-25 09:19:45

by Mubashshir

[permalink] [raw]
Subject: Re: [PATCH v3] staging: HID: Add ShanWan USB WirelessGamepad driver

On Tue, Apr 25 2023 at 11:04:43 AM +02:00:00, Benjamin Tissoires
<[email protected]> wrote:
> No, it won't work if the device is not opened. But what's the point of
> sending rumble data to the joystick if the device is not opened (so
> used)
> by anybody? open() is called whenever a client opens the device, by
> accessing the evdev node, so I think it should be fine.

I'll test it tonight...


2023-05-15 12:51:43

by Mubashshir

[permalink] [raw]
Subject: [PATCH v4] staging: HID: Add ShanWan USB WirelessGamepad driver

This device has a quirky initialization process.
Depending on how it was initialized, behaves completely differently.
In default mode, it behaves as expected, but in fallback it disables
force-feedback, analog stick configurations and L3/R3.

Different OEMs manufactures joypads with same vid:pid but different
axis/button mapping[1], and I don't know which one has which layout,
so, we'll let hid-core figure that out, and handle only FF here.

* The one I have has different axis layout than the one of Huseyin.

Signed-off-by: Huseyin BIYIK <[email protected]>
Signed-off-by: Mubashshir <[email protected]>
---
drivers/hid/Kconfig | 19 +++++
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 3 +
drivers/hid/hid-shanwan.c | 155 ++++++++++++++++++++++++++++++++++++++
4 files changed, 178 insertions(+)
create mode 100644 drivers/hid/hid-shanwan.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 82f64fb31fda..a17db9c9694c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -990,6 +990,25 @@ config HID_SEMITEK
- Woo-dy
- X-Bows Nature/Knight

+config HID_SHANWAN
+ tristate "ShanWan USB WirelessGamepad"
+ depends on USB_HID
+ help
+ Support for Shanwan USB WirelessGamepad (and clones).
+
+ This device has a quirky initialization process.
+ Depending on how it was initialized, it behaves completely differently.
+ In default mode, it behaves as expected, but in fallback it disables
+ force-feedback, analog stick configurations and L3/R3.
+
+config SHANWAN_FF
+ bool "ShanWan USB WirelessGamepad force feedback support"
+ depends on HID_SHANWAN
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you have a ShanWan USB WirelessGamepad and want to enable
+ force feedback support for it.
+
config HID_SIGMAMICRO
tristate "SiGma Micro-based keyboards"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 5d37cacbde33..52878455fc10 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o
+obj-$(CONFIG_HID_SHANWAN) += hid-shanwan.o
obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 63545cd307e5..278914e37eb7 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -623,6 +623,9 @@
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a

+#define USB_VENDOR_ID_SHANWAN 0x2563
+#define USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD 0x0575
+
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
#define USB_DEVICE_ID_HUION_TABLET2 0x006d
diff --git a/drivers/hid/hid-shanwan.c b/drivers/hid/hid-shanwan.c
new file mode 100644
index 000000000000..dba969d553ff
--- /dev/null
+++ b/drivers/hid/hid-shanwan.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Force feedback support for Shanwan USB WirelessGamepad
+ *
+ * Copyright (c) 2022-2023 Huseyin BIYIK <[email protected]>
+ * Copyright (c) 2023 Ahmad Hasan Mubashshir <[email protected]>
+ *
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+
+#include "hid-ids.h"
+
+#define SHANWAN_PAYLOAD_LEN(x) (sizeof(s32) * x)
+
+static bool swap_motors;
+module_param_named(swap, swap_motors, bool, 0);
+MODULE_PARM_DESC(swap, "Swap Weak/Strong Feedback motors");
+
+#ifdef CONFIG_SHANWAN_FF
+struct shanwan_device {
+ struct hid_report *report;
+};
+
+static int shanwan_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct shanwan_device *shanwan = data;
+ struct hid_report *report = shanwan->report;
+ struct hid_field *field0 = report->field[0];
+ s32 payload_template[] = {
+ 0x02, // 2 = rumble effect message
+ 0x08, // reserved value, always 8
+ 0x00, // rumble value
+ 0x00, // rumble value
+ 0xff // duration 0-254 (255 = nonstop)
+ };
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ memcpy_and_pad(field0->value, SHANWAN_PAYLOAD_LEN(8), payload_template, SHANWAN_PAYLOAD_LEN(4), 0x00);
+
+ if (swap_motors) {
+ /* weak rumble / strong rumble */
+ field0->value[2] = effect->u.rumble.strong_magnitude / 256;
+ field0->value[3] = effect->u.rumble.weak_magnitude / 256;
+ } else {
+ /* strong rumble / weak rumble */
+ field0->value[2] = effect->u.rumble.weak_magnitude / 256;
+ field0->value[3] = effect->u.rumble.strong_magnitude / 256;
+ }
+
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
+ return 0;
+}
+
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ struct shanwan_device *shanwan;
+ struct hid_report *report;
+ struct hid_input *hidinput;
+ struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct input_dev *dev;
+
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ dev = hidinput->input;
+
+ if (list_empty(report_list)) {
+ hid_err(hid, "no output reports found\n");
+ return -ENODEV;
+ }
+
+ report = list_first_entry(report_list, struct hid_report, list);
+
+ shanwan = kzalloc(sizeof(*shanwan), GFP_KERNEL);
+ if (!shanwan)
+ return -ENOMEM;
+
+ set_bit(FF_RUMBLE, dev->ffbit);
+
+ if (input_ff_create_memless(dev, shanwan, shanwan_play_effect)) {
+ kfree(shanwan);
+ return -ENODEV;
+ }
+
+ shanwan->report = report;
+
+ return 0;
+}
+#else
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ return 0;
+}
+#endif
+
+static int shanwan_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ ret = shanwan_init_ff(hdev);
+ if (ret)
+ hid_warn(hdev, "Failed to enable force feedback support, error: %d\n", ret);
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ dev_err(&hdev->dev, "hw open failed\n");
+ hid_hw_stop(hdev);
+ return ret;
+ }
+
+ hid_hw_close(hdev);
+ return ret;
+}
+
+static const struct hid_device_id shanwan_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_SHANWAN, USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, shanwan_devices);
+
+static struct hid_driver shanwan_driver = {
+ .name = "shanwan",
+ .id_table = shanwan_devices,
+ .probe = shanwan_probe,
+};
+module_hid_driver(shanwan_driver);
+
+MODULE_AUTHOR("Huseyin BIYIK <[email protected]>");
+MODULE_AUTHOR("Ahmad Hasan Mubashshir <[email protected]>");
+MODULE_DESCRIPTION("Force feedback support for Shanwan USB WirelessGamepad");
+MODULE_LICENSE("GPL");
--
2.40.1


2023-05-15 14:10:32

by Mubashshir

[permalink] [raw]
Subject: [PATCH v5] staging: HID: Add ShanWan USB WirelessGamepad driver

This device has a quirky initialization process.
Depending on how it was initialized, behaves completely differently.
In default mode, it behaves as expected, but in fallback it disables
force-feedback, analog stick configurations and L3/R3.

Different OEMs manufactures joypads with same vid:pid but different
axis/button mapping[1], and I don't know which one has which layout,
so, we'll let hid-core figure that out, and handle only FF here.

* The one I have has different axis layout than the one of Huseyin.

Signed-off-by: Huseyin BIYIK <[email protected]>
Signed-off-by: Mubashshir <[email protected]>
---
v5: Use hid_{get,set}_drvdata to pass data to `->play()`

drivers/hid/Kconfig | 19 +++++
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 3 +
drivers/hid/hid-shanwan.c | 145 ++++++++++++++++++++++++++++++++++++++
4 files changed, 168 insertions(+)
create mode 100644 drivers/hid/hid-shanwan.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 4ce012f83253..e6c8aa855252 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -990,6 +990,25 @@ config HID_SEMITEK
- Woo-dy
- X-Bows Nature/Knight

+config HID_SHANWAN
+ tristate "ShanWan USB WirelessGamepad"
+ depends on USB_HID
+ help
+ Support for Shanwan USB WirelessGamepad (and clones).
+
+ This device has a quirky initialization process.
+ Depending on how it was initialized, it behaves completely differently.
+ In default mode, it behaves as expected, but in fallback it disables
+ force-feedback, analog stick configurations and L3/R3.
+
+config SHANWAN_FF
+ bool "ShanWan USB WirelessGamepad force feedback support"
+ depends on HID_SHANWAN
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you have a ShanWan USB WirelessGamepad and want to enable
+ force feedback support for it.
+
config HID_SIGMAMICRO
tristate "SiGma Micro-based keyboards"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 5d37cacbde33..52878455fc10 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o
obj-$(CONFIG_HID_SAITEK) += hid-saitek.o
obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o
obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o
+obj-$(CONFIG_HID_SHANWAN) += hid-shanwan.o
obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o
obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
obj-$(CONFIG_HID_SONY) += hid-sony.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index d79e946acdcb..04c3324dc453 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -627,6 +627,9 @@
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a

+#define USB_VENDOR_ID_SHANWAN 0x2563
+#define USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD 0x0575
+
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
#define USB_DEVICE_ID_HUION_TABLET2 0x006d
diff --git a/drivers/hid/hid-shanwan.c b/drivers/hid/hid-shanwan.c
new file mode 100644
index 000000000000..c80bfcac5dc7
--- /dev/null
+++ b/drivers/hid/hid-shanwan.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Force feedback support for Shanwan USB WirelessGamepad
+ *
+ * Copyright (c) 2022-2023 Huseyin BIYIK <[email protected]>
+ * Copyright (c) 2023 Ahmad Hasan Mubashshir <[email protected]>
+ *
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+
+#include "hid-ids.h"
+
+static bool swap_motors;
+module_param_named(swap, swap_motors, bool, 0);
+MODULE_PARM_DESC(swap, "Swap Weak/Strong Feedback motors");
+
+#ifdef CONFIG_SHANWAN_FF
+static int shanwan_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct hid_device *hid = input_get_drvdata(dev);
+ struct hid_report *report = hid_get_drvdata(hid);
+ struct hid_field *field0 = report->field[0];
+ s32 payload_template[] = {
+ 0x02, // 2 = rumble effect message
+ 0x08, // reserved value, always 8
+ 0x00, // rumble value
+ 0x00, // rumble value
+ 0xff // duration 0-254 (255 = nonstop)
+ };
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ memcpy_and_pad(field0->value,
+ (sizeof(s32) * 8),
+ payload_template,
+ (sizeof(s32) * 4),
+ 0x00);
+
+ if (swap_motors) {
+ /* weak rumble / strong rumble */
+ field0->value[2] = effect->u.rumble.strong_magnitude / 256;
+ field0->value[3] = effect->u.rumble.weak_magnitude / 256;
+ } else {
+ /* strong rumble / weak rumble */
+ field0->value[2] = effect->u.rumble.weak_magnitude / 256;
+ field0->value[3] = effect->u.rumble.strong_magnitude / 256;
+ }
+
+ hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
+ return 0;
+}
+
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ struct hid_report *report;
+ struct hid_input *hidinput;
+ struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+ struct input_dev *dev;
+
+ if (list_empty(&hid->inputs)) {
+ hid_err(hid, "no inputs found\n");
+ return -ENODEV;
+ }
+ hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+ dev = hidinput->input;
+
+ if (list_empty(report_list)) {
+ hid_err(hid, "no output reports found\n");
+ return -ENODEV;
+ }
+
+ report = list_first_entry(report_list, struct hid_report, list);
+ hid_set_drvdata(hid, report);
+
+ set_bit(FF_RUMBLE, dev->ffbit);
+ if (input_ff_create_memless(dev, NULL, shanwan_play_effect))
+ return -ENODEV;
+
+ return 0;
+}
+#else
+static int shanwan_init_ff(struct hid_device *hid)
+{
+ return 0;
+}
+#endif
+
+static int shanwan_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ ret = shanwan_init_ff(hdev);
+ if (ret)
+ hid_warn(hdev, "Failed to enable force feedback support, error: %d\n", ret);
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ dev_err(&hdev->dev, "hw open failed\n");
+ hid_hw_stop(hdev);
+ return ret;
+ }
+
+ hid_hw_close(hdev);
+ return ret;
+}
+
+static const struct hid_device_id shanwan_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_SHANWAN, USB_PRODUCT_ID_SHANWAN_USB_WIRELESSGAMEPAD) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, shanwan_devices);
+
+static struct hid_driver shanwan_driver = {
+ .name = "shanwan",
+ .id_table = shanwan_devices,
+ .probe = shanwan_probe,
+};
+module_hid_driver(shanwan_driver);
+
+MODULE_AUTHOR("Huseyin BIYIK <[email protected]>");
+MODULE_AUTHOR("Ahmad Hasan Mubashshir <[email protected]>");
+MODULE_DESCRIPTION("Force feedback support for Shanwan USB WirelessGamepad");
+MODULE_LICENSE("GPL");
+
+// vim: ts=8:noet
--
2.40.1