Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   1) // SPDX-License-Identifier: GPL-2.0-or-later
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   3)  *  Force feedback support for Holtek On Line Grip based gamepads
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   4)  *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   5)  *  These include at least a Brazilian "Clone Joypad Super Power Fire"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   6)  *  which uses vendor ID 0x1241 and identifies as "HOLTEK On Line Grip".
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   7)  *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   8)  *  Copyright (c) 2011 Anssi Hannula <anssi.hannula@iki.fi>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300   9)  */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  10) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  11) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  12)  */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  13) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  14) #include <linux/hid.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  15) #include <linux/input.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  16) #include <linux/module.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  17) #include <linux/slab.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  18) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  19) #include "hid-ids.h"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  20) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  21) #ifdef CONFIG_HOLTEK_FF
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  22) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  23) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  24)  * These commands and parameters are currently known:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  25)  *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  26)  * byte 0: command id:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  27)  * 	01  set effect parameters
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  28)  * 	02  play specified effect
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  29)  * 	03  stop specified effect
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  30)  * 	04  stop all effects
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  31)  * 	06  stop all effects
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  32)  * 	(the difference between 04 and 06 isn't known; win driver
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  33)  * 	 sends 06,04 on application init, and 06 otherwise)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  34)  * 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  35)  * Commands 01 and 02 need to be sent as pairs, i.e. you need to send 01
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  36)  * before each 02.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  37)  *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  38)  * The rest of the bytes are parameters. Command 01 takes all of them, and
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  39)  * commands 02,03 take only the effect id.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  40)  *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  41)  * byte 1:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  42)  *	bits 0-3: effect id:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  43)  * 		1: very strong rumble
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  44)  * 		2: periodic rumble, short intervals
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  45)  * 		3: very strong rumble
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  46)  * 		4: periodic rumble, long intervals
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  47)  * 		5: weak periodic rumble, long intervals
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  48)  * 		6: weak periodic rumble, short intervals
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  49)  * 		7: periodic rumble, short intervals
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  50)  * 		8: strong periodic rumble, short intervals
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  51)  * 		9: very strong rumble
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  52)  * 		a: causes an error
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  53)  * 		b: very strong periodic rumble, very short intervals
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  54)  * 		c-f: nothing
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  55)  *	bit 6: right (weak) motor enabled
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  56)  *	bit 7: left (strong) motor enabled
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  57)  *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  58)  * bytes 2-3:  time in milliseconds, big-endian
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  59)  * bytes 5-6:  unknown (win driver seems to use at least 10e0 with effect 1
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  60)  * 		       and 0014 with effect 6)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  61)  * byte 7:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  62)  *	bits 0-3: effect magnitude
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  63)  */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  64) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  65) #define HOLTEKFF_MSG_LENGTH     7
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  66) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  67) static const u8 start_effect_1[] = { 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  68) static const u8 stop_all4[] =	   { 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  69) static const u8 stop_all6[] =	   { 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  70) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  71) struct holtekff_device {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  72) 	struct hid_field *field;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  73) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  74) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  75) static void holtekff_send(struct holtekff_device *holtekff,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  76) 			  struct hid_device *hid,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  77) 			  const u8 data[HOLTEKFF_MSG_LENGTH])
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  78) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  79) 	int i;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  80) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  81) 	for (i = 0; i < HOLTEKFF_MSG_LENGTH; i++) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  82) 		holtekff->field->value[i] = data[i];
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  83) 	}
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  84) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  85) 	dbg_hid("sending %7ph\n", data);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  86) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  87) 	hid_hw_request(hid, holtekff->field->report, HID_REQ_SET_REPORT);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  88) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  89) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  90) static int holtekff_play(struct input_dev *dev, void *data,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  91) 			 struct ff_effect *effect)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  92) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  93) 	struct hid_device *hid = input_get_drvdata(dev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  94) 	struct holtekff_device *holtekff = data;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  95) 	int left, right;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  96) 	/* effect type 1, length 65535 msec */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  97) 	u8 buf[HOLTEKFF_MSG_LENGTH] =
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  98) 		{ 0x01, 0x01, 0xff, 0xff, 0x10, 0xe0, 0x00 };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300  99) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) 	left = effect->u.rumble.strong_magnitude;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) 	right = effect->u.rumble.weak_magnitude;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) 	dbg_hid("called with 0x%04x 0x%04x\n", left, right);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) 	if (!left && !right) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) 		holtekff_send(holtekff, hid, stop_all6);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) 		return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) 	}
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) 	if (left)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) 		buf[1] |= 0x80;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) 	if (right)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) 		buf[1] |= 0x40;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) 	/* The device takes a single magnitude, so we just sum them up. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) 	buf[6] = min(0xf, (left >> 12) + (right >> 12));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) 	holtekff_send(holtekff, hid, buf);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) 	holtekff_send(holtekff, hid, start_effect_1);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) 	return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) static int holtekff_init(struct hid_device *hid)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) 	struct holtekff_device *holtekff;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126) 	struct hid_report *report;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) 	struct hid_input *hidinput;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) 	struct list_head *report_list =
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) 			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) 	struct input_dev *dev;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) 	int error;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) 	if (list_empty(&hid->inputs)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) 		hid_err(hid, "no inputs found\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) 		return -ENODEV;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) 	}
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) 	hidinput = list_entry(hid->inputs.next, struct hid_input, list);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) 	dev = hidinput->input;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) 	if (list_empty(report_list)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) 		hid_err(hid, "no output report found\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) 		return -ENODEV;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) 	}
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) 	report = list_entry(report_list->next, struct hid_report, list);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147) 	if (report->maxfield < 1 || report->field[0]->report_count != 7) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) 		hid_err(hid, "unexpected output report layout\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) 		return -ENODEV;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150) 	}
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) 	holtekff = kzalloc(sizeof(*holtekff), GFP_KERNEL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) 	if (!holtekff)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) 		return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156) 	set_bit(FF_RUMBLE, dev->ffbit);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) 	holtekff->field = report->field[0];
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160) 	/* initialize the same way as win driver does */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) 	holtekff_send(holtekff, hid, stop_all4);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162) 	holtekff_send(holtekff, hid, stop_all6);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) 	error = input_ff_create_memless(dev, holtekff, holtekff_play);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) 	if (error) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) 		kfree(holtekff);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167) 		return error;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) 	}
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170) 	hid_info(hid, "Force feedback for Holtek On Line Grip based devices by Anssi Hannula <anssi.hannula@iki.fi>\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) 	return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174) #else
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) static inline int holtekff_init(struct hid_device *hid)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177) 	return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) #endif
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181) static int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) 	int ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185) 	ret = hid_parse(hdev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) 	if (ret) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187) 		hid_err(hdev, "parse failed\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) 		goto err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189) 	}
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191) 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192) 	if (ret) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) 		hid_err(hdev, "hw start failed\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) 		goto err;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195) 	}
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197) 	holtekff_init(hdev);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199) 	return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200) err:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201) 	return ret;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 204) static const struct hid_device_id holtek_devices[] = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 205) 	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 206) 	{ }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 207) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 208) MODULE_DEVICE_TABLE(hid, holtek_devices);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 209) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 210) static struct hid_driver holtek_driver = {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 211) 	.name = "holtek",
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 212) 	.id_table = holtek_devices,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 213) 	.probe = holtek_probe,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 214) };
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 215) module_hid_driver(holtek_driver);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 216) 
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 217) MODULE_LICENSE("GPL");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 218) MODULE_AUTHOR("Anssi Hannula <anssi.hannula@iki.fi>");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 219) MODULE_DESCRIPTION("Force feedback support for Holtek On Line Grip based devices");