162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Force feedback support for Holtek On Line Grip based gamepads
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  These include at least a Brazilian "Clone Joypad Super Power Fire"
662306a36Sopenharmony_ci *  which uses vendor ID 0x1241 and identifies as "HOLTEK On Line Grip".
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci *  Copyright (c) 2011 Anssi Hannula <anssi.hannula@iki.fi>
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci/*
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/hid.h>
1562306a36Sopenharmony_ci#include <linux/input.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "hid-ids.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#ifdef CONFIG_HOLTEK_FF
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/*
2462306a36Sopenharmony_ci * These commands and parameters are currently known:
2562306a36Sopenharmony_ci *
2662306a36Sopenharmony_ci * byte 0: command id:
2762306a36Sopenharmony_ci * 	01  set effect parameters
2862306a36Sopenharmony_ci * 	02  play specified effect
2962306a36Sopenharmony_ci * 	03  stop specified effect
3062306a36Sopenharmony_ci * 	04  stop all effects
3162306a36Sopenharmony_ci * 	06  stop all effects
3262306a36Sopenharmony_ci * 	(the difference between 04 and 06 isn't known; win driver
3362306a36Sopenharmony_ci * 	 sends 06,04 on application init, and 06 otherwise)
3462306a36Sopenharmony_ci *
3562306a36Sopenharmony_ci * Commands 01 and 02 need to be sent as pairs, i.e. you need to send 01
3662306a36Sopenharmony_ci * before each 02.
3762306a36Sopenharmony_ci *
3862306a36Sopenharmony_ci * The rest of the bytes are parameters. Command 01 takes all of them, and
3962306a36Sopenharmony_ci * commands 02,03 take only the effect id.
4062306a36Sopenharmony_ci *
4162306a36Sopenharmony_ci * byte 1:
4262306a36Sopenharmony_ci *	bits 0-3: effect id:
4362306a36Sopenharmony_ci * 		1: very strong rumble
4462306a36Sopenharmony_ci * 		2: periodic rumble, short intervals
4562306a36Sopenharmony_ci * 		3: very strong rumble
4662306a36Sopenharmony_ci * 		4: periodic rumble, long intervals
4762306a36Sopenharmony_ci * 		5: weak periodic rumble, long intervals
4862306a36Sopenharmony_ci * 		6: weak periodic rumble, short intervals
4962306a36Sopenharmony_ci * 		7: periodic rumble, short intervals
5062306a36Sopenharmony_ci * 		8: strong periodic rumble, short intervals
5162306a36Sopenharmony_ci * 		9: very strong rumble
5262306a36Sopenharmony_ci * 		a: causes an error
5362306a36Sopenharmony_ci * 		b: very strong periodic rumble, very short intervals
5462306a36Sopenharmony_ci * 		c-f: nothing
5562306a36Sopenharmony_ci *	bit 6: right (weak) motor enabled
5662306a36Sopenharmony_ci *	bit 7: left (strong) motor enabled
5762306a36Sopenharmony_ci *
5862306a36Sopenharmony_ci * bytes 2-3:  time in milliseconds, big-endian
5962306a36Sopenharmony_ci * bytes 5-6:  unknown (win driver seems to use at least 10e0 with effect 1
6062306a36Sopenharmony_ci * 		       and 0014 with effect 6)
6162306a36Sopenharmony_ci * byte 7:
6262306a36Sopenharmony_ci *	bits 0-3: effect magnitude
6362306a36Sopenharmony_ci */
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci#define HOLTEKFF_MSG_LENGTH     7
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic const u8 start_effect_1[] = { 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
6862306a36Sopenharmony_cistatic const u8 stop_all4[] =	   { 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
6962306a36Sopenharmony_cistatic const u8 stop_all6[] =	   { 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistruct holtekff_device {
7262306a36Sopenharmony_ci	struct hid_field *field;
7362306a36Sopenharmony_ci};
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic void holtekff_send(struct holtekff_device *holtekff,
7662306a36Sopenharmony_ci			  struct hid_device *hid,
7762306a36Sopenharmony_ci			  const u8 data[HOLTEKFF_MSG_LENGTH])
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	int i;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	for (i = 0; i < HOLTEKFF_MSG_LENGTH; i++) {
8262306a36Sopenharmony_ci		holtekff->field->value[i] = data[i];
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	dbg_hid("sending %7ph\n", data);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	hid_hw_request(hid, holtekff->field->report, HID_REQ_SET_REPORT);
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int holtekff_play(struct input_dev *dev, void *data,
9162306a36Sopenharmony_ci			 struct ff_effect *effect)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct hid_device *hid = input_get_drvdata(dev);
9462306a36Sopenharmony_ci	struct holtekff_device *holtekff = data;
9562306a36Sopenharmony_ci	int left, right;
9662306a36Sopenharmony_ci	/* effect type 1, length 65535 msec */
9762306a36Sopenharmony_ci	u8 buf[HOLTEKFF_MSG_LENGTH] =
9862306a36Sopenharmony_ci		{ 0x01, 0x01, 0xff, 0xff, 0x10, 0xe0, 0x00 };
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	left = effect->u.rumble.strong_magnitude;
10162306a36Sopenharmony_ci	right = effect->u.rumble.weak_magnitude;
10262306a36Sopenharmony_ci	dbg_hid("called with 0x%04x 0x%04x\n", left, right);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (!left && !right) {
10562306a36Sopenharmony_ci		holtekff_send(holtekff, hid, stop_all6);
10662306a36Sopenharmony_ci		return 0;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (left)
11062306a36Sopenharmony_ci		buf[1] |= 0x80;
11162306a36Sopenharmony_ci	if (right)
11262306a36Sopenharmony_ci		buf[1] |= 0x40;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* The device takes a single magnitude, so we just sum them up. */
11562306a36Sopenharmony_ci	buf[6] = min(0xf, (left >> 12) + (right >> 12));
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	holtekff_send(holtekff, hid, buf);
11862306a36Sopenharmony_ci	holtekff_send(holtekff, hid, start_effect_1);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	return 0;
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic int holtekff_init(struct hid_device *hid)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct holtekff_device *holtekff;
12662306a36Sopenharmony_ci	struct hid_report *report;
12762306a36Sopenharmony_ci	struct hid_input *hidinput;
12862306a36Sopenharmony_ci	struct list_head *report_list =
12962306a36Sopenharmony_ci			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
13062306a36Sopenharmony_ci	struct input_dev *dev;
13162306a36Sopenharmony_ci	int error;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (list_empty(&hid->inputs)) {
13462306a36Sopenharmony_ci		hid_err(hid, "no inputs found\n");
13562306a36Sopenharmony_ci		return -ENODEV;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci	hidinput = list_entry(hid->inputs.next, struct hid_input, list);
13862306a36Sopenharmony_ci	dev = hidinput->input;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (list_empty(report_list)) {
14162306a36Sopenharmony_ci		hid_err(hid, "no output report found\n");
14262306a36Sopenharmony_ci		return -ENODEV;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	report = list_entry(report_list->next, struct hid_report, list);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (report->maxfield < 1 || report->field[0]->report_count != 7) {
14862306a36Sopenharmony_ci		hid_err(hid, "unexpected output report layout\n");
14962306a36Sopenharmony_ci		return -ENODEV;
15062306a36Sopenharmony_ci	}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	holtekff = kzalloc(sizeof(*holtekff), GFP_KERNEL);
15362306a36Sopenharmony_ci	if (!holtekff)
15462306a36Sopenharmony_ci		return -ENOMEM;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	set_bit(FF_RUMBLE, dev->ffbit);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	holtekff->field = report->field[0];
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	/* initialize the same way as win driver does */
16162306a36Sopenharmony_ci	holtekff_send(holtekff, hid, stop_all4);
16262306a36Sopenharmony_ci	holtekff_send(holtekff, hid, stop_all6);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	error = input_ff_create_memless(dev, holtekff, holtekff_play);
16562306a36Sopenharmony_ci	if (error) {
16662306a36Sopenharmony_ci		kfree(holtekff);
16762306a36Sopenharmony_ci		return error;
16862306a36Sopenharmony_ci	}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	hid_info(hid, "Force feedback for Holtek On Line Grip based devices by Anssi Hannula <anssi.hannula@iki.fi>\n");
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	return 0;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci#else
17562306a36Sopenharmony_cistatic inline int holtekff_init(struct hid_device *hid)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	return 0;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci#endif
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	int ret;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	ret = hid_parse(hdev);
18662306a36Sopenharmony_ci	if (ret) {
18762306a36Sopenharmony_ci		hid_err(hdev, "parse failed\n");
18862306a36Sopenharmony_ci		goto err;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
19262306a36Sopenharmony_ci	if (ret) {
19362306a36Sopenharmony_ci		hid_err(hdev, "hw start failed\n");
19462306a36Sopenharmony_ci		goto err;
19562306a36Sopenharmony_ci	}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	holtekff_init(hdev);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	return 0;
20062306a36Sopenharmony_cierr:
20162306a36Sopenharmony_ci	return ret;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic const struct hid_device_id holtek_devices[] = {
20562306a36Sopenharmony_ci	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
20662306a36Sopenharmony_ci	{ }
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hid, holtek_devices);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic struct hid_driver holtek_driver = {
21162306a36Sopenharmony_ci	.name = "holtek",
21262306a36Sopenharmony_ci	.id_table = holtek_devices,
21362306a36Sopenharmony_ci	.probe = holtek_probe,
21462306a36Sopenharmony_ci};
21562306a36Sopenharmony_cimodule_hid_driver(holtek_driver);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
21862306a36Sopenharmony_ciMODULE_AUTHOR("Anssi Hannula <anssi.hannula@iki.fi>");
21962306a36Sopenharmony_ciMODULE_DESCRIPTION("Force feedback support for Holtek On Line Grip based devices");
220