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