blob: b363d88267c2e0f1ea9d3e2c36ca9a63c1b58675 [file] [log] [blame]
Simon Wood32c88cb2010-09-22 13:19:42 +02001/*
Simon Wood64013802012-04-21 05:41:16 -07002 * Force feedback support for Logitech Gaming Wheels
Simon Wood32c88cb2010-09-22 13:19:42 +02003 *
Simon Wood64013802012-04-21 05:41:16 -07004 * Including G27, G25, DFP, DFGT, FFEX, Momo, Momo2 &
5 * Speed Force Wireless (WiiWheel)
Simon Wood32c88cb2010-09-22 13:19:42 +02006 *
7 * Copyright (c) 2010 Simon Wood <simon@mungewell.org>
8 */
9
10/*
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25
26
27#include <linux/input.h>
28#include <linux/usb.h>
29#include <linux/hid.h>
30
31#include "usbhid/usbhid.h"
32#include "hid-lg.h"
Michal Malýa54dc7792015-02-18 17:59:22 +010033#include "hid-lg4ff.h"
Michal Malý7362cd22011-08-04 16:16:09 +020034#include "hid-ids.h"
Simon Wood32c88cb2010-09-22 13:19:42 +020035
Michal Malý30bb75d2011-08-04 16:20:40 +020036#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
37
Michal Malýb96d23e2015-02-18 17:59:21 +010038#define LG4FF_MMODE_IS_MULTIMODE 0
Michal Malýe7c23442015-02-18 17:59:20 +010039#define LG4FF_MMODE_SWITCHED 1
40#define LG4FF_MMODE_NOT_MULTIMODE 2
41
Michal Malýb96d23e2015-02-18 17:59:21 +010042#define LG4FF_MODE_NATIVE_IDX 0
43#define LG4FF_MODE_DFEX_IDX 1
44#define LG4FF_MODE_DFP_IDX 2
45#define LG4FF_MODE_G25_IDX 3
46#define LG4FF_MODE_DFGT_IDX 4
47#define LG4FF_MODE_G27_IDX 5
48#define LG4FF_MODE_MAX_IDX 6
49
50#define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX)
51#define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX)
52#define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX)
53#define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX)
54#define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX)
55#define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX)
56
57#define LG4FF_DFEX_TAG "DF-EX"
58#define LG4FF_DFEX_NAME "Driving Force / Formula EX"
59#define LG4FF_DFP_TAG "DFP"
60#define LG4FF_DFP_NAME "Driving Force Pro"
61#define LG4FF_G25_TAG "G25"
62#define LG4FF_G25_NAME "G25 Racing Wheel"
63#define LG4FF_G27_TAG "G27"
64#define LG4FF_G27_NAME "G27 Racing Wheel"
65#define LG4FF_DFGT_TAG "DFGT"
66#define LG4FF_DFGT_NAME "Driving Force GT"
67
Michal Malýe7c23442015-02-18 17:59:20 +010068#define LG4FF_FFEX_REV_MAJ 0x21
69#define LG4FF_FFEX_REV_MIN 0x00
70
Michal Malýd0afd842015-04-08 22:56:40 +020071static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
72static void lg4ff_set_range_g25(struct hid_device *hid, u16 range);
Michal Malý30bb75d2011-08-04 16:20:40 +020073
Michal Malý72529c62015-04-08 22:56:46 +020074struct lg4ff_wheel_data {
Michal Malý5d9d60a2015-04-08 22:56:50 +020075 const u32 product_id;
Michal Malý2a552c32015-04-08 22:56:39 +020076 u16 range;
Michal Malý5d9d60a2015-04-08 22:56:50 +020077 const u16 min_range;
78 const u16 max_range;
Simon Wood22bcefd2012-04-21 05:41:15 -070079#ifdef CONFIG_LEDS_CLASS
Michal Malý2a552c32015-04-08 22:56:39 +020080 u8 led_state;
Simon Wood22bcefd2012-04-21 05:41:15 -070081 struct led_classdev *led[5];
82#endif
Michal Malý5d9d60a2015-04-08 22:56:50 +020083 const u32 alternate_modes;
84 const char * const real_tag;
85 const char * const real_name;
86 const u16 real_product_id;
Michal Malý72529c62015-04-08 22:56:46 +020087
Michal Malý30bb75d2011-08-04 16:20:40 +020088 void (*set_range)(struct hid_device *hid, u16 range);
89};
90
Michal Malý72529c62015-04-08 22:56:46 +020091struct lg4ff_device_entry {
Michal Malýc918fe72015-04-08 22:56:48 +020092 spinlock_t report_lock; /* Protect output HID report */
Michal Malýc28abd82015-04-08 22:56:49 +020093 struct hid_report *report;
Michal Malý72529c62015-04-08 22:56:46 +020094 struct lg4ff_wheel_data wdata;
95};
96
Michal Malý7362cd22011-08-04 16:16:09 +020097static const signed short lg4ff_wheel_effects[] = {
Simon Wood32c88cb2010-09-22 13:19:42 +020098 FF_CONSTANT,
99 FF_AUTOCENTER,
100 -1
101};
102
Michal Malý7362cd22011-08-04 16:16:09 +0200103struct lg4ff_wheel {
Michal Malý2a552c32015-04-08 22:56:39 +0200104 const u32 product_id;
Michal Malý7362cd22011-08-04 16:16:09 +0200105 const signed short *ff_effects;
Michal Malý2a552c32015-04-08 22:56:39 +0200106 const u16 min_range;
107 const u16 max_range;
Michal Malý30bb75d2011-08-04 16:20:40 +0200108 void (*set_range)(struct hid_device *hid, u16 range);
Michal Malý7362cd22011-08-04 16:16:09 +0200109};
110
Michal Malýe7c23442015-02-18 17:59:20 +0100111struct lg4ff_compat_mode_switch {
Michal Malý2a552c32015-04-08 22:56:39 +0200112 const u8 cmd_count; /* Number of commands to send */
113 const u8 cmd[];
Michal Malýe7c23442015-02-18 17:59:20 +0100114};
115
116struct lg4ff_wheel_ident_info {
Simon Woodbbec1bd2015-11-02 07:56:51 -0700117 const u32 modes;
Michal Malýe7c23442015-02-18 17:59:20 +0100118 const u16 mask;
119 const u16 result;
120 const u16 real_product_id;
121};
122
Michal Malýb96d23e2015-02-18 17:59:21 +0100123struct lg4ff_multimode_wheel {
124 const u16 product_id;
125 const u32 alternate_modes;
126 const char *real_tag;
127 const char *real_name;
128};
129
130struct lg4ff_alternate_mode {
131 const u16 product_id;
132 const char *tag;
133 const char *name;
134};
135
Michal Malý7362cd22011-08-04 16:16:09 +0200136static const struct lg4ff_wheel lg4ff_devices[] = {
Michal Malý30bb75d2011-08-04 16:20:40 +0200137 {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
138 {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL},
Michal Malýd0afd842015-04-08 22:56:40 +0200139 {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_dfp},
140 {USB_DEVICE_ID_LOGITECH_G25_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
141 {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
142 {USB_DEVICE_ID_LOGITECH_G27_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
Michal Malý30bb75d2011-08-04 16:20:40 +0200143 {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL},
144 {USB_DEVICE_ID_LOGITECH_WII_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}
Michal Malý7362cd22011-08-04 16:16:09 +0200145};
146
Michal Malýb96d23e2015-02-18 17:59:21 +0100147static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = {
148 {USB_DEVICE_ID_LOGITECH_DFP_WHEEL,
149 LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
150 LG4FF_DFP_TAG, LG4FF_DFP_NAME},
151 {USB_DEVICE_ID_LOGITECH_G25_WHEEL,
152 LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
153 LG4FF_G25_TAG, LG4FF_G25_NAME},
154 {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,
155 LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
156 LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
157 {USB_DEVICE_ID_LOGITECH_G27_WHEEL,
158 LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
159 LG4FF_G27_TAG, LG4FF_G27_NAME},
160};
161
162static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
163 [LG4FF_MODE_NATIVE_IDX] = {0, "native", ""},
164 [LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME},
165 [LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME},
166 [LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME},
167 [LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
168 [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME}
169};
170
Michal Malýe7c23442015-02-18 17:59:20 +0100171/* Multimode wheel identificators */
172static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
Simon Woodbbec1bd2015-11-02 07:56:51 -0700173 LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
Michal Malýe7c23442015-02-18 17:59:20 +0100174 0xf000,
175 0x1000,
176 USB_DEVICE_ID_LOGITECH_DFP_WHEEL
Michal Malý96440c82011-08-04 16:18:11 +0200177};
178
Michal Malýe7c23442015-02-18 17:59:20 +0100179static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
Simon Woodbbec1bd2015-11-02 07:56:51 -0700180 LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
Michal Malýe7c23442015-02-18 17:59:20 +0100181 0xff00,
182 0x1200,
183 USB_DEVICE_ID_LOGITECH_G25_WHEEL
Michal Malý96440c82011-08-04 16:18:11 +0200184};
185
Michal Malýe7c23442015-02-18 17:59:20 +0100186static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
Simon Woodbbec1bd2015-11-02 07:56:51 -0700187 LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
Michal Malýe7c23442015-02-18 17:59:20 +0100188 0xfff0,
189 0x1230,
190 USB_DEVICE_ID_LOGITECH_G27_WHEEL
191};
192
193static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
Simon Woodbbec1bd2015-11-02 07:56:51 -0700194 LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
Michal Malýe7c23442015-02-18 17:59:20 +0100195 0xff00,
196 0x1300,
197 USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
198};
199
200/* Multimode wheel identification checklists */
Simon Woodbbec1bd2015-11-02 07:56:51 -0700201static const struct lg4ff_wheel_ident_info *lg4ff_main_checklist[] = {
202 &lg4ff_dfgt_ident_info,
203 &lg4ff_g27_ident_info,
204 &lg4ff_g25_ident_info,
205 &lg4ff_dfp_ident_info
Michal Malýe7c23442015-02-18 17:59:20 +0100206};
207
208/* Compatibility mode switching commands */
Michal Malýf31a2de2015-02-18 17:59:23 +0100209/* EXT_CMD9 - Understood by G27 and DFGT */
210static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = {
211 2,
212 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
213 0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DF-EX with detach */
214};
215
216static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = {
217 2,
218 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
219 0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFP with detach */
220};
221
222static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = {
223 2,
224 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
225 0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G25 with detach */
226};
227
228static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
229 2,
230 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
231 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00} /* Switch mode to DFGT with detach */
232};
233
234static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
235 2,
236 {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, /* Revert mode upon USB reset */
237 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00} /* Switch mode to G27 with detach */
238};
239
240/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
241static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
Michal Malý96440c82011-08-04 16:18:11 +0200242 1,
243 {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
244};
245
Michal Malýf31a2de2015-02-18 17:59:23 +0100246/* EXT_CMD16 - Understood by G25 and G27 */
247static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
Michal Malý96440c82011-08-04 16:18:11 +0200248 1,
249 {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
250};
251
Michal Malý2b24a962012-09-23 22:41:08 +0200252/* Recalculates X axis value accordingly to currently selected range */
Michal Malý2a552c32015-04-08 22:56:39 +0200253static s32 lg4ff_adjust_dfp_x_axis(s32 value, u16 range)
Michal Malý2b24a962012-09-23 22:41:08 +0200254{
Michal Malý2a552c32015-04-08 22:56:39 +0200255 u16 max_range;
256 s32 new_value;
Michal Malý2b24a962012-09-23 22:41:08 +0200257
258 if (range == 900)
259 return value;
260 else if (range == 200)
261 return value;
262 else if (range < 200)
263 max_range = 200;
264 else
265 max_range = 900;
266
267 new_value = 8192 + mult_frac(value - 8192, max_range, range);
268 if (new_value < 0)
269 return 0;
270 else if (new_value > 16383)
271 return 16383;
272 else
273 return new_value;
274}
275
276int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
Michal Malý2a552c32015-04-08 22:56:39 +0200277 struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data)
Michal Malý2b24a962012-09-23 22:41:08 +0200278{
279 struct lg4ff_device_entry *entry = drv_data->device_props;
Michal Malý2a552c32015-04-08 22:56:39 +0200280 s32 new_value = 0;
Michal Malý2b24a962012-09-23 22:41:08 +0200281
282 if (!entry) {
283 hid_err(hid, "Device properties not found");
284 return 0;
285 }
286
Michal Malý72529c62015-04-08 22:56:46 +0200287 switch (entry->wdata.product_id) {
Michal Malý2b24a962012-09-23 22:41:08 +0200288 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
289 switch (usage->code) {
290 case ABS_X:
Michal Malý72529c62015-04-08 22:56:46 +0200291 new_value = lg4ff_adjust_dfp_x_axis(value, entry->wdata.range);
Michal Malý2b24a962012-09-23 22:41:08 +0200292 input_event(field->hidinput->input, usage->type, usage->code, new_value);
293 return 1;
294 default:
295 return 0;
296 }
297 default:
298 return 0;
299 }
300}
301
Michal Malý5d9d60a2015-04-08 22:56:50 +0200302static void lg4ff_init_wheel_data(struct lg4ff_wheel_data * const wdata, const struct lg4ff_wheel *wheel,
303 const struct lg4ff_multimode_wheel *mmode_wheel,
304 const u16 real_product_id)
305{
306 u32 alternate_modes = 0;
307 const char *real_tag = NULL;
308 const char *real_name = NULL;
309
310 if (mmode_wheel) {
311 alternate_modes = mmode_wheel->alternate_modes;
312 real_tag = mmode_wheel->real_tag;
313 real_name = mmode_wheel->real_name;
314 }
315
316 {
317 struct lg4ff_wheel_data t_wdata = { .product_id = wheel->product_id,
318 .real_product_id = real_product_id,
319 .min_range = wheel->min_range,
320 .max_range = wheel->max_range,
321 .set_range = wheel->set_range,
322 .alternate_modes = alternate_modes,
323 .real_tag = real_tag,
324 .real_name = real_name };
325
326 memcpy(wdata, &t_wdata, sizeof(t_wdata));
327 }
328}
329
Michal Malýd0afd842015-04-08 22:56:40 +0200330static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
Simon Wood32c88cb2010-09-22 13:19:42 +0200331{
332 struct hid_device *hid = input_get_drvdata(dev);
Michal Malýc918fe72015-04-08 22:56:48 +0200333 struct lg4ff_device_entry *entry;
334 struct lg_drv_data *drv_data;
335 unsigned long flags;
Michal Malýc28abd82015-04-08 22:56:49 +0200336 s32 *value;
Simon Wood32c88cb2010-09-22 13:19:42 +0200337 int x;
338
Michal Malýc918fe72015-04-08 22:56:48 +0200339 drv_data = hid_get_drvdata(hid);
340 if (!drv_data) {
341 hid_err(hid, "Private driver data not found!\n");
342 return -EINVAL;
343 }
344
345 entry = drv_data->device_props;
346 if (!entry) {
347 hid_err(hid, "Device properties not found!\n");
348 return -EINVAL;
349 }
Michal Malýc28abd82015-04-08 22:56:49 +0200350 value = entry->report->field[0]->value;
Michal Malýc918fe72015-04-08 22:56:48 +0200351
Michal Malýa80fe5d2012-09-24 01:13:17 +0200352#define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0)
Simon Wood32c88cb2010-09-22 13:19:42 +0200353
354 switch (effect->type) {
355 case FF_CONSTANT:
356 x = effect->u.ramp.start_level + 0x80; /* 0x80 is no force */
357 CLAMP(x);
Simon Wood56930e72013-11-06 12:30:42 -0700358
Michal Malýc918fe72015-04-08 22:56:48 +0200359 spin_lock_irqsave(&entry->report_lock, flags);
Simon Wood56930e72013-11-06 12:30:42 -0700360 if (x == 0x80) {
361 /* De-activate force in slot-1*/
362 value[0] = 0x13;
363 value[1] = 0x00;
364 value[2] = 0x00;
365 value[3] = 0x00;
366 value[4] = 0x00;
367 value[5] = 0x00;
368 value[6] = 0x00;
369
Michal Malýc28abd82015-04-08 22:56:49 +0200370 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200371 spin_unlock_irqrestore(&entry->report_lock, flags);
Simon Wood56930e72013-11-06 12:30:42 -0700372 return 0;
373 }
374
Michal Malý74479ba2012-09-24 01:09:30 +0200375 value[0] = 0x11; /* Slot 1 */
376 value[1] = 0x08;
377 value[2] = x;
378 value[3] = 0x80;
379 value[4] = 0x00;
380 value[5] = 0x00;
381 value[6] = 0x00;
Simon Wood32c88cb2010-09-22 13:19:42 +0200382
Michal Malýc28abd82015-04-08 22:56:49 +0200383 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200384 spin_unlock_irqrestore(&entry->report_lock, flags);
Simon Wood32c88cb2010-09-22 13:19:42 +0200385 break;
386 }
387 return 0;
388}
389
Michal Malý6e2de8e2011-08-04 16:22:07 +0200390/* Sends default autocentering command compatible with
391 * all wheels except Formula Force EX */
Michal Malýd0afd842015-04-08 22:56:40 +0200392static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude)
Simon Wood32c88cb2010-09-22 13:19:42 +0200393{
394 struct hid_device *hid = input_get_drvdata(dev);
395 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
396 struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
Michal Malý2a552c32015-04-08 22:56:39 +0200397 s32 *value = report->field[0]->value;
398 u32 expand_a, expand_b;
Simon Wood18597622013-11-06 12:30:44 -0700399 struct lg4ff_device_entry *entry;
400 struct lg_drv_data *drv_data;
Michal Malýc918fe72015-04-08 22:56:48 +0200401 unsigned long flags;
Simon Wood18597622013-11-06 12:30:44 -0700402
403 drv_data = hid_get_drvdata(hid);
404 if (!drv_data) {
405 hid_err(hid, "Private driver data not found!\n");
406 return;
407 }
408
409 entry = drv_data->device_props;
410 if (!entry) {
411 hid_err(hid, "Device properties not found!\n");
412 return;
413 }
Michal Malýc28abd82015-04-08 22:56:49 +0200414 value = entry->report->field[0]->value;
Simon Woodf8c23152013-11-06 12:30:40 -0700415
Simon Woodd2c02da2013-11-06 12:30:41 -0700416 /* De-activate Auto-Center */
Michal Malýc918fe72015-04-08 22:56:48 +0200417 spin_lock_irqsave(&entry->report_lock, flags);
Simon Woodd2c02da2013-11-06 12:30:41 -0700418 if (magnitude == 0) {
419 value[0] = 0xf5;
420 value[1] = 0x00;
421 value[2] = 0x00;
422 value[3] = 0x00;
423 value[4] = 0x00;
424 value[5] = 0x00;
425 value[6] = 0x00;
426
Michal Malýc28abd82015-04-08 22:56:49 +0200427 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200428 spin_unlock_irqrestore(&entry->report_lock, flags);
Simon Woodd2c02da2013-11-06 12:30:41 -0700429 return;
430 }
431
Simon Woodf8c23152013-11-06 12:30:40 -0700432 if (magnitude <= 0xaaaa) {
433 expand_a = 0x0c * magnitude;
434 expand_b = 0x80 * magnitude;
435 } else {
436 expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa);
437 expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa);
438 }
Simon Wood32c88cb2010-09-22 13:19:42 +0200439
Simon Wood18597622013-11-06 12:30:44 -0700440 /* Adjust for non-MOMO wheels */
Michal Malý72529c62015-04-08 22:56:46 +0200441 switch (entry->wdata.product_id) {
Simon Wood18597622013-11-06 12:30:44 -0700442 case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
443 case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
444 break;
445 default:
446 expand_a = expand_a >> 1;
447 break;
448 }
449
Michal Malý74479ba2012-09-24 01:09:30 +0200450 value[0] = 0xfe;
451 value[1] = 0x0d;
Simon Woodf8c23152013-11-06 12:30:40 -0700452 value[2] = expand_a / 0xaaaa;
453 value[3] = expand_a / 0xaaaa;
454 value[4] = expand_b / 0xaaaa;
Michal Malý74479ba2012-09-24 01:09:30 +0200455 value[5] = 0x00;
456 value[6] = 0x00;
Simon Wood32c88cb2010-09-22 13:19:42 +0200457
Michal Malýc28abd82015-04-08 22:56:49 +0200458 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Simon Woodd2c02da2013-11-06 12:30:41 -0700459
460 /* Activate Auto-Center */
461 value[0] = 0x14;
462 value[1] = 0x00;
463 value[2] = 0x00;
464 value[3] = 0x00;
465 value[4] = 0x00;
466 value[5] = 0x00;
467 value[6] = 0x00;
468
Michal Malýc28abd82015-04-08 22:56:49 +0200469 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200470 spin_unlock_irqrestore(&entry->report_lock, flags);
Simon Wood32c88cb2010-09-22 13:19:42 +0200471}
472
Michal Malý6e2de8e2011-08-04 16:22:07 +0200473/* Sends autocentering command compatible with Formula Force EX */
Michal Malýd0afd842015-04-08 22:56:40 +0200474static void lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude)
Michal Malý6e2de8e2011-08-04 16:22:07 +0200475{
476 struct hid_device *hid = input_get_drvdata(dev);
Michal Malýc918fe72015-04-08 22:56:48 +0200477 struct lg4ff_device_entry *entry;
478 struct lg_drv_data *drv_data;
479 unsigned long flags;
Michal Malýc28abd82015-04-08 22:56:49 +0200480 s32 *value;
Michal Malý6e2de8e2011-08-04 16:22:07 +0200481 magnitude = magnitude * 90 / 65535;
Michal Malý6e2de8e2011-08-04 16:22:07 +0200482
Michal Malýc918fe72015-04-08 22:56:48 +0200483 drv_data = hid_get_drvdata(hid);
484 if (!drv_data) {
485 hid_err(hid, "Private driver data not found!\n");
486 return;
487 }
488
489 entry = drv_data->device_props;
490 if (!entry) {
491 hid_err(hid, "Device properties not found!\n");
492 return;
493 }
Michal Malýc28abd82015-04-08 22:56:49 +0200494 value = entry->report->field[0]->value;
Michal Malýc918fe72015-04-08 22:56:48 +0200495
496 spin_lock_irqsave(&entry->report_lock, flags);
Michal Malý74479ba2012-09-24 01:09:30 +0200497 value[0] = 0xfe;
498 value[1] = 0x03;
499 value[2] = magnitude >> 14;
500 value[3] = magnitude >> 14;
501 value[4] = magnitude;
502 value[5] = 0x00;
503 value[6] = 0x00;
Michal Malý6e2de8e2011-08-04 16:22:07 +0200504
Michal Malýc28abd82015-04-08 22:56:49 +0200505 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200506 spin_unlock_irqrestore(&entry->report_lock, flags);
Michal Malý6e2de8e2011-08-04 16:22:07 +0200507}
508
Michal Malý30bb75d2011-08-04 16:20:40 +0200509/* Sends command to set range compatible with G25/G27/Driving Force GT */
Michal Malýd0afd842015-04-08 22:56:40 +0200510static void lg4ff_set_range_g25(struct hid_device *hid, u16 range)
Michal Malý30bb75d2011-08-04 16:20:40 +0200511{
Michal Malýc918fe72015-04-08 22:56:48 +0200512 struct lg4ff_device_entry *entry;
513 struct lg_drv_data *drv_data;
514 unsigned long flags;
Michal Malýc28abd82015-04-08 22:56:49 +0200515 s32 *value;
Michal Malý74479ba2012-09-24 01:09:30 +0200516
Michal Malýc918fe72015-04-08 22:56:48 +0200517 drv_data = hid_get_drvdata(hid);
518 if (!drv_data) {
519 hid_err(hid, "Private driver data not found!\n");
520 return;
521 }
522
523 entry = drv_data->device_props;
524 if (!entry) {
525 hid_err(hid, "Device properties not found!\n");
526 return;
527 }
Michal Malýc28abd82015-04-08 22:56:49 +0200528 value = entry->report->field[0]->value;
Michal Malý30bb75d2011-08-04 16:20:40 +0200529 dbg_hid("G25/G27/DFGT: setting range to %u\n", range);
530
Michal Malýc918fe72015-04-08 22:56:48 +0200531 spin_lock_irqsave(&entry->report_lock, flags);
Michal Malý74479ba2012-09-24 01:09:30 +0200532 value[0] = 0xf8;
533 value[1] = 0x81;
534 value[2] = range & 0x00ff;
535 value[3] = (range & 0xff00) >> 8;
536 value[4] = 0x00;
537 value[5] = 0x00;
538 value[6] = 0x00;
Michal Malý30bb75d2011-08-04 16:20:40 +0200539
Michal Malýc28abd82015-04-08 22:56:49 +0200540 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200541 spin_unlock_irqrestore(&entry->report_lock, flags);
Michal Malý30bb75d2011-08-04 16:20:40 +0200542}
543
544/* Sends commands to set range compatible with Driving Force Pro wheel */
Michal Malýd0afd842015-04-08 22:56:40 +0200545static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range)
Michal Malý30bb75d2011-08-04 16:20:40 +0200546{
Michal Malýc918fe72015-04-08 22:56:48 +0200547 struct lg4ff_device_entry *entry;
548 struct lg_drv_data *drv_data;
549 unsigned long flags;
Michal Malýc28abd82015-04-08 22:56:49 +0200550 int start_left, start_right, full_range;
551 s32 *value;
Michal Malý74479ba2012-09-24 01:09:30 +0200552
Michal Malýc918fe72015-04-08 22:56:48 +0200553 drv_data = hid_get_drvdata(hid);
554 if (!drv_data) {
555 hid_err(hid, "Private driver data not found!\n");
556 return;
557 }
558
559 entry = drv_data->device_props;
560 if (!entry) {
561 hid_err(hid, "Device properties not found!\n");
562 return;
563 }
Michal Malýc28abd82015-04-08 22:56:49 +0200564 value = entry->report->field[0]->value;
Michal Malý30bb75d2011-08-04 16:20:40 +0200565 dbg_hid("Driving Force Pro: setting range to %u\n", range);
566
567 /* Prepare "coarse" limit command */
Michal Malýc918fe72015-04-08 22:56:48 +0200568 spin_lock_irqsave(&entry->report_lock, flags);
Michal Malý74479ba2012-09-24 01:09:30 +0200569 value[0] = 0xf8;
Michal Malýa80fe5d2012-09-24 01:13:17 +0200570 value[1] = 0x00; /* Set later */
Michal Malý74479ba2012-09-24 01:09:30 +0200571 value[2] = 0x00;
572 value[3] = 0x00;
573 value[4] = 0x00;
574 value[5] = 0x00;
575 value[6] = 0x00;
Michal Malý30bb75d2011-08-04 16:20:40 +0200576
577 if (range > 200) {
Michal Malýc28abd82015-04-08 22:56:49 +0200578 value[1] = 0x03;
Michal Malý30bb75d2011-08-04 16:20:40 +0200579 full_range = 900;
580 } else {
Michal Malýc28abd82015-04-08 22:56:49 +0200581 value[1] = 0x02;
Michal Malý30bb75d2011-08-04 16:20:40 +0200582 full_range = 200;
583 }
Michal Malýc28abd82015-04-08 22:56:49 +0200584 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malý30bb75d2011-08-04 16:20:40 +0200585
586 /* Prepare "fine" limit command */
Michal Malý74479ba2012-09-24 01:09:30 +0200587 value[0] = 0x81;
588 value[1] = 0x0b;
589 value[2] = 0x00;
590 value[3] = 0x00;
591 value[4] = 0x00;
592 value[5] = 0x00;
593 value[6] = 0x00;
Michal Malý30bb75d2011-08-04 16:20:40 +0200594
595 if (range == 200 || range == 900) { /* Do not apply any fine limit */
Michal Malýc28abd82015-04-08 22:56:49 +0200596 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200597 spin_unlock_irqrestore(&entry->report_lock, flags);
Michal Malý30bb75d2011-08-04 16:20:40 +0200598 return;
599 }
600
601 /* Construct fine limit command */
602 start_left = (((full_range - range + 1) * 2047) / full_range);
603 start_right = 0xfff - start_left;
604
Michal Malý74479ba2012-09-24 01:09:30 +0200605 value[2] = start_left >> 4;
606 value[3] = start_right >> 4;
607 value[4] = 0xff;
608 value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
609 value[6] = 0xff;
Michal Malý30bb75d2011-08-04 16:20:40 +0200610
Michal Malýc28abd82015-04-08 22:56:49 +0200611 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200612 spin_unlock_irqrestore(&entry->report_lock, flags);
Michal Malý30bb75d2011-08-04 16:20:40 +0200613}
614
Michal Malýf31a2de2015-02-18 17:59:23 +0100615static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
616{
617 switch (real_product_id) {
618 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
619 switch (target_product_id) {
620 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
621 return &lg4ff_mode_switch_ext01_dfp;
622 /* DFP can only be switched to its native mode */
623 default:
624 return NULL;
625 }
626 break;
627 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
628 switch (target_product_id) {
629 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
630 return &lg4ff_mode_switch_ext01_dfp;
631 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
632 return &lg4ff_mode_switch_ext16_g25;
633 /* G25 can only be switched to DFP mode or its native mode */
634 default:
635 return NULL;
636 }
637 break;
638 case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
639 switch (target_product_id) {
640 case USB_DEVICE_ID_LOGITECH_WHEEL:
641 return &lg4ff_mode_switch_ext09_dfex;
642 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
643 return &lg4ff_mode_switch_ext09_dfp;
644 case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
645 return &lg4ff_mode_switch_ext09_g25;
646 case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
647 return &lg4ff_mode_switch_ext09_g27;
648 /* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
649 default:
650 return NULL;
651 }
652 break;
653 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
654 switch (target_product_id) {
655 case USB_DEVICE_ID_LOGITECH_WHEEL:
656 return &lg4ff_mode_switch_ext09_dfex;
657 case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
658 return &lg4ff_mode_switch_ext09_dfp;
659 case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
660 return &lg4ff_mode_switch_ext09_dfgt;
661 /* DFGT can only be switched to DF-EX, DFP or its native mode */
662 default:
663 return NULL;
664 }
665 break;
666 /* No other wheels have multiple modes */
667 default:
668 return NULL;
669 }
670}
671
Michal Malýe7c23442015-02-18 17:59:20 +0100672static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
Michal Malý96440c82011-08-04 16:18:11 +0200673{
Michal Malýc918fe72015-04-08 22:56:48 +0200674 struct lg4ff_device_entry *entry;
675 struct lg_drv_data *drv_data;
676 unsigned long flags;
Michal Malýc28abd82015-04-08 22:56:49 +0200677 s32 *value;
Michal Malýe7c23442015-02-18 17:59:20 +0100678 u8 i;
Michal Malý96440c82011-08-04 16:18:11 +0200679
Michal Malýc918fe72015-04-08 22:56:48 +0200680 drv_data = hid_get_drvdata(hid);
681 if (!drv_data) {
682 hid_err(hid, "Private driver data not found!\n");
683 return -EINVAL;
684 }
685
686 entry = drv_data->device_props;
687 if (!entry) {
688 hid_err(hid, "Device properties not found!\n");
689 return -EINVAL;
690 }
Michal Malýc28abd82015-04-08 22:56:49 +0200691 value = entry->report->field[0]->value;
Michal Malýc918fe72015-04-08 22:56:48 +0200692
693 spin_lock_irqsave(&entry->report_lock, flags);
Michal Malýe7c23442015-02-18 17:59:20 +0100694 for (i = 0; i < s->cmd_count; i++) {
Michal Malýc1740d12015-02-18 22:49:33 +0100695 u8 j;
Michal Malý96440c82011-08-04 16:18:11 +0200696
Michal Malýc1740d12015-02-18 22:49:33 +0100697 for (j = 0; j < 7; j++)
698 value[j] = s->cmd[j + (7*i)];
699
Michal Malýc28abd82015-04-08 22:56:49 +0200700 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malý96440c82011-08-04 16:18:11 +0200701 }
Michal Malýc918fe72015-04-08 22:56:48 +0200702 spin_unlock_irqrestore(&entry->report_lock, flags);
Michal Malýc1740d12015-02-18 22:49:33 +0100703 hid_hw_wait(hid);
Michal Malýe7c23442015-02-18 17:59:20 +0100704 return 0;
Michal Malý96440c82011-08-04 16:18:11 +0200705}
Simon Wood32c88cb2010-09-22 13:19:42 +0200706
Michal Malýb96d23e2015-02-18 17:59:21 +0100707static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
708{
709 struct hid_device *hid = to_hid_device(dev);
710 struct lg4ff_device_entry *entry;
711 struct lg_drv_data *drv_data;
712 ssize_t count = 0;
713 int i;
714
715 drv_data = hid_get_drvdata(hid);
716 if (!drv_data) {
717 hid_err(hid, "Private driver data not found!\n");
718 return 0;
719 }
720
721 entry = drv_data->device_props;
722 if (!entry) {
723 hid_err(hid, "Device properties not found!\n");
724 return 0;
725 }
726
Michal Malý72529c62015-04-08 22:56:46 +0200727 if (!entry->wdata.real_name) {
Michal Malýb96d23e2015-02-18 17:59:21 +0100728 hid_err(hid, "NULL pointer to string\n");
729 return 0;
730 }
731
732 for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
Michal Malý72529c62015-04-08 22:56:46 +0200733 if (entry->wdata.alternate_modes & BIT(i)) {
Michal Malýb96d23e2015-02-18 17:59:21 +0100734 /* Print tag and full name */
735 count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s",
736 lg4ff_alternate_modes[i].tag,
Michal Malý72529c62015-04-08 22:56:46 +0200737 !lg4ff_alternate_modes[i].product_id ? entry->wdata.real_name : lg4ff_alternate_modes[i].name);
Michal Malýb96d23e2015-02-18 17:59:21 +0100738 if (count >= PAGE_SIZE - 1)
739 return count;
740
741 /* Mark the currently active mode with an asterisk */
Michal Malý72529c62015-04-08 22:56:46 +0200742 if (lg4ff_alternate_modes[i].product_id == entry->wdata.product_id ||
743 (lg4ff_alternate_modes[i].product_id == 0 && entry->wdata.product_id == entry->wdata.real_product_id))
Michal Malýb96d23e2015-02-18 17:59:21 +0100744 count += scnprintf(buf + count, PAGE_SIZE - count, " *\n");
745 else
746 count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
747
748 if (count >= PAGE_SIZE - 1)
749 return count;
750 }
751 }
752
753 return count;
754}
755
756static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
757{
Michal Malýf31a2de2015-02-18 17:59:23 +0100758 struct hid_device *hid = to_hid_device(dev);
759 struct lg4ff_device_entry *entry;
760 struct lg_drv_data *drv_data;
761 const struct lg4ff_compat_mode_switch *s;
762 u16 target_product_id = 0;
763 int i, ret;
764 char *lbuf;
765
766 drv_data = hid_get_drvdata(hid);
767 if (!drv_data) {
768 hid_err(hid, "Private driver data not found!\n");
769 return -EINVAL;
770 }
771
772 entry = drv_data->device_props;
773 if (!entry) {
774 hid_err(hid, "Device properties not found!\n");
775 return -EINVAL;
776 }
777
778 /* Allow \n at the end of the input parameter */
779 lbuf = kasprintf(GFP_KERNEL, "%s", buf);
780 if (!lbuf)
781 return -ENOMEM;
782
783 i = strlen(lbuf);
784 if (lbuf[i-1] == '\n') {
785 if (i == 1) {
786 kfree(lbuf);
787 return -EINVAL;
788 }
789 lbuf[i-1] = '\0';
790 }
791
792 for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
793 const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
794 const char *tag = lg4ff_alternate_modes[i].tag;
795
Michal Malý72529c62015-04-08 22:56:46 +0200796 if (entry->wdata.alternate_modes & BIT(i)) {
Michal Malýf31a2de2015-02-18 17:59:23 +0100797 if (!strcmp(tag, lbuf)) {
798 if (!mode_product_id)
Michal Malý72529c62015-04-08 22:56:46 +0200799 target_product_id = entry->wdata.real_product_id;
Michal Malýf31a2de2015-02-18 17:59:23 +0100800 else
801 target_product_id = mode_product_id;
802 break;
803 }
804 }
805 }
806
807 if (i == LG4FF_MODE_MAX_IDX) {
808 hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf);
809 kfree(lbuf);
810 return -EINVAL;
811 }
812 kfree(lbuf); /* Not needed anymore */
813
Michal Malý72529c62015-04-08 22:56:46 +0200814 if (target_product_id == entry->wdata.product_id) /* Nothing to do */
Michal Malýf31a2de2015-02-18 17:59:23 +0100815 return count;
816
817 /* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
818 if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
819 hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again\n",
Michal Malý72529c62015-04-08 22:56:46 +0200820 entry->wdata.real_name);
Michal Malýf31a2de2015-02-18 17:59:23 +0100821 return -EINVAL;
822 }
823
824 /* Take care of hardware limitations */
Michal Malý72529c62015-04-08 22:56:46 +0200825 if ((entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
826 entry->wdata.product_id > target_product_id) {
827 hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->wdata.real_name, lg4ff_alternate_modes[i].name);
Michal Malýf31a2de2015-02-18 17:59:23 +0100828 return -EINVAL;
829 }
830
Michal Malý72529c62015-04-08 22:56:46 +0200831 s = lg4ff_get_mode_switch_command(entry->wdata.real_product_id, target_product_id);
Michal Malýf31a2de2015-02-18 17:59:23 +0100832 if (!s) {
833 hid_err(hid, "Invalid target product ID %X\n", target_product_id);
834 return -EINVAL;
835 }
836
837 ret = lg4ff_switch_compatibility_mode(hid, s);
838 return (ret == 0 ? count : ret);
Michal Malýb96d23e2015-02-18 17:59:21 +0100839}
840static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
841
Michal Malýfbf85e22015-04-08 22:56:41 +0200842/* Export the currently set range of the wheel */
843static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr,
844 char *buf)
Michal Malý30bb75d2011-08-04 16:20:40 +0200845{
Michal Malý30bb75d2011-08-04 16:20:40 +0200846 struct hid_device *hid = to_hid_device(dev);
Michal Malý3b6b17b2012-04-09 09:08:49 +0200847 struct lg4ff_device_entry *entry;
848 struct lg_drv_data *drv_data;
Michal Malý30bb75d2011-08-04 16:20:40 +0200849 size_t count;
850
Michal Malý3b6b17b2012-04-09 09:08:49 +0200851 drv_data = hid_get_drvdata(hid);
852 if (!drv_data) {
853 hid_err(hid, "Private driver data not found!\n");
854 return 0;
Michal Malý30bb75d2011-08-04 16:20:40 +0200855 }
Michal Malý3b6b17b2012-04-09 09:08:49 +0200856
857 entry = drv_data->device_props;
858 if (!entry) {
859 hid_err(hid, "Device properties not found!\n");
Michal Malý30bb75d2011-08-04 16:20:40 +0200860 return 0;
861 }
862
Michal Malý72529c62015-04-08 22:56:46 +0200863 count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.range);
Michal Malý30bb75d2011-08-04 16:20:40 +0200864 return count;
865}
866
867/* Set range to user specified value, call appropriate function
868 * according to the type of the wheel */
Michal Malýfbf85e22015-04-08 22:56:41 +0200869static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr,
870 const char *buf, size_t count)
Michal Malý30bb75d2011-08-04 16:20:40 +0200871{
Michal Malý30bb75d2011-08-04 16:20:40 +0200872 struct hid_device *hid = to_hid_device(dev);
Michal Malý3b6b17b2012-04-09 09:08:49 +0200873 struct lg4ff_device_entry *entry;
874 struct lg_drv_data *drv_data;
Michal Malý2a552c32015-04-08 22:56:39 +0200875 u16 range = simple_strtoul(buf, NULL, 10);
Michal Malý30bb75d2011-08-04 16:20:40 +0200876
Michal Malý3b6b17b2012-04-09 09:08:49 +0200877 drv_data = hid_get_drvdata(hid);
878 if (!drv_data) {
879 hid_err(hid, "Private driver data not found!\n");
Simon Wood29ff6652014-08-14 20:43:01 -0600880 return -EINVAL;
Michal Malý30bb75d2011-08-04 16:20:40 +0200881 }
Michal Malý3b6b17b2012-04-09 09:08:49 +0200882
883 entry = drv_data->device_props;
884 if (!entry) {
885 hid_err(hid, "Device properties not found!\n");
Simon Wood29ff6652014-08-14 20:43:01 -0600886 return -EINVAL;
Michal Malý30bb75d2011-08-04 16:20:40 +0200887 }
888
889 if (range == 0)
Michal Malý72529c62015-04-08 22:56:46 +0200890 range = entry->wdata.max_range;
Michal Malý30bb75d2011-08-04 16:20:40 +0200891
892 /* Check if the wheel supports range setting
893 * and that the range is within limits for the wheel */
Michal Malý72529c62015-04-08 22:56:46 +0200894 if (entry->wdata.set_range && range >= entry->wdata.min_range && range <= entry->wdata.max_range) {
895 entry->wdata.set_range(hid, range);
896 entry->wdata.range = range;
Michal Malý30bb75d2011-08-04 16:20:40 +0200897 }
898
899 return count;
900}
Michal Malýfbf85e22015-04-08 22:56:41 +0200901static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_range_show, lg4ff_range_store);
Michal Malý30bb75d2011-08-04 16:20:40 +0200902
Michal Malýb96d23e2015-02-18 17:59:21 +0100903static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf)
904{
905 struct hid_device *hid = to_hid_device(dev);
906 struct lg4ff_device_entry *entry;
907 struct lg_drv_data *drv_data;
908 size_t count;
909
910 drv_data = hid_get_drvdata(hid);
911 if (!drv_data) {
912 hid_err(hid, "Private driver data not found!\n");
913 return 0;
914 }
915
916 entry = drv_data->device_props;
917 if (!entry) {
918 hid_err(hid, "Device properties not found!\n");
919 return 0;
920 }
921
Michal Malý72529c62015-04-08 22:56:46 +0200922 if (!entry->wdata.real_tag || !entry->wdata.real_name) {
Michal Malýb96d23e2015-02-18 17:59:21 +0100923 hid_err(hid, "NULL pointer to string\n");
924 return 0;
925 }
926
Michal Malý72529c62015-04-08 22:56:46 +0200927 count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->wdata.real_tag, entry->wdata.real_name);
Michal Malýb96d23e2015-02-18 17:59:21 +0100928 return count;
929}
930
931static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
932{
933 /* Real ID is a read-only value */
934 return -EPERM;
935}
936static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store);
937
Simon Wood22bcefd2012-04-21 05:41:15 -0700938#ifdef CONFIG_LEDS_CLASS
Michal Malý2a552c32015-04-08 22:56:39 +0200939static void lg4ff_set_leds(struct hid_device *hid, u8 leds)
Simon Wood22bcefd2012-04-21 05:41:15 -0700940{
Michal Malýc918fe72015-04-08 22:56:48 +0200941 struct lg_drv_data *drv_data;
942 struct lg4ff_device_entry *entry;
943 unsigned long flags;
Michal Malýc28abd82015-04-08 22:56:49 +0200944 s32 *value;
Simon Wood22bcefd2012-04-21 05:41:15 -0700945
Michal Malýc918fe72015-04-08 22:56:48 +0200946 drv_data = hid_get_drvdata(hid);
947 if (!drv_data) {
948 hid_err(hid, "Private driver data not found!\n");
949 return;
950 }
951
952 entry = drv_data->device_props;
953 if (!entry) {
954 hid_err(hid, "Device properties not found!\n");
955 return;
956 }
Michal Malýc28abd82015-04-08 22:56:49 +0200957 value = entry->report->field[0]->value;
Michal Malýc918fe72015-04-08 22:56:48 +0200958
959 spin_lock_irqsave(&entry->report_lock, flags);
Michal Malý74479ba2012-09-24 01:09:30 +0200960 value[0] = 0xf8;
961 value[1] = 0x12;
962 value[2] = leds;
963 value[3] = 0x00;
964 value[4] = 0x00;
965 value[5] = 0x00;
966 value[6] = 0x00;
Michal Malýc28abd82015-04-08 22:56:49 +0200967 hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
Michal Malýc918fe72015-04-08 22:56:48 +0200968 spin_unlock_irqrestore(&entry->report_lock, flags);
Simon Wood22bcefd2012-04-21 05:41:15 -0700969}
970
971static void lg4ff_led_set_brightness(struct led_classdev *led_cdev,
972 enum led_brightness value)
973{
974 struct device *dev = led_cdev->dev->parent;
975 struct hid_device *hid = container_of(dev, struct hid_device, dev);
Axel Lin4629fd12012-09-13 14:09:33 +0800976 struct lg_drv_data *drv_data = hid_get_drvdata(hid);
Simon Wood22bcefd2012-04-21 05:41:15 -0700977 struct lg4ff_device_entry *entry;
978 int i, state = 0;
979
980 if (!drv_data) {
981 hid_err(hid, "Device data not found.");
982 return;
983 }
984
Michal Malý371a1d92015-04-08 22:56:43 +0200985 entry = drv_data->device_props;
Simon Wood22bcefd2012-04-21 05:41:15 -0700986
987 if (!entry) {
988 hid_err(hid, "Device properties not found.");
989 return;
990 }
991
992 for (i = 0; i < 5; i++) {
Michal Malý72529c62015-04-08 22:56:46 +0200993 if (led_cdev != entry->wdata.led[i])
Simon Wood22bcefd2012-04-21 05:41:15 -0700994 continue;
Michal Malý72529c62015-04-08 22:56:46 +0200995 state = (entry->wdata.led_state >> i) & 1;
Simon Wood22bcefd2012-04-21 05:41:15 -0700996 if (value == LED_OFF && state) {
Michal Malý72529c62015-04-08 22:56:46 +0200997 entry->wdata.led_state &= ~(1 << i);
998 lg4ff_set_leds(hid, entry->wdata.led_state);
Simon Wood22bcefd2012-04-21 05:41:15 -0700999 } else if (value != LED_OFF && !state) {
Michal Malý72529c62015-04-08 22:56:46 +02001000 entry->wdata.led_state |= 1 << i;
1001 lg4ff_set_leds(hid, entry->wdata.led_state);
Simon Wood22bcefd2012-04-21 05:41:15 -07001002 }
1003 break;
1004 }
1005}
1006
1007static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cdev)
1008{
1009 struct device *dev = led_cdev->dev->parent;
1010 struct hid_device *hid = container_of(dev, struct hid_device, dev);
Axel Lin4629fd12012-09-13 14:09:33 +08001011 struct lg_drv_data *drv_data = hid_get_drvdata(hid);
Simon Wood22bcefd2012-04-21 05:41:15 -07001012 struct lg4ff_device_entry *entry;
1013 int i, value = 0;
1014
1015 if (!drv_data) {
1016 hid_err(hid, "Device data not found.");
1017 return LED_OFF;
1018 }
1019
Michal Malý371a1d92015-04-08 22:56:43 +02001020 entry = drv_data->device_props;
Simon Wood22bcefd2012-04-21 05:41:15 -07001021
1022 if (!entry) {
1023 hid_err(hid, "Device properties not found.");
1024 return LED_OFF;
1025 }
1026
1027 for (i = 0; i < 5; i++)
Michal Malý72529c62015-04-08 22:56:46 +02001028 if (led_cdev == entry->wdata.led[i]) {
1029 value = (entry->wdata.led_state >> i) & 1;
Simon Wood22bcefd2012-04-21 05:41:15 -07001030 break;
1031 }
1032
1033 return value ? LED_FULL : LED_OFF;
1034}
1035#endif
1036
Michal Malýe7c23442015-02-18 17:59:20 +01001037static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
1038{
Simon Woodbbec1bd2015-11-02 07:56:51 -07001039 u32 current_mode;
1040 int i;
Michal Malýe7c23442015-02-18 17:59:20 +01001041
Simon Woodbbec1bd2015-11-02 07:56:51 -07001042 /* identify current mode from USB PID */
1043 for (i = 1; i < ARRAY_SIZE(lg4ff_alternate_modes); i++) {
1044 dbg_hid("Testing whether PID is %X\n", lg4ff_alternate_modes[i].product_id);
1045 if (reported_product_id == lg4ff_alternate_modes[i].product_id)
1046 break;
Michal Malýe7c23442015-02-18 17:59:20 +01001047 }
1048
Simon Woodbbec1bd2015-11-02 07:56:51 -07001049 if (i == ARRAY_SIZE(lg4ff_alternate_modes))
1050 return 0;
Michal Malýe7c23442015-02-18 17:59:20 +01001051
Simon Woodbbec1bd2015-11-02 07:56:51 -07001052 current_mode = BIT(i);
1053
1054 for (i = 0; i < ARRAY_SIZE(lg4ff_main_checklist); i++) {
1055 const u16 mask = lg4ff_main_checklist[i]->mask;
1056 const u16 result = lg4ff_main_checklist[i]->result;
1057 const u16 real_product_id = lg4ff_main_checklist[i]->real_product_id;
1058
1059 if ((current_mode & lg4ff_main_checklist[i]->modes) && \
1060 (bcdDevice & mask) == result) {
Michal Malýe7c23442015-02-18 17:59:20 +01001061 dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
1062 return real_product_id;
1063 }
1064 }
1065
Michal Malýf31a2de2015-02-18 17:59:23 +01001066 /* No match found. This is either Driving Force or an unknown
1067 * wheel model, do not touch it */
Michal Malýe7c23442015-02-18 17:59:20 +01001068 dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
1069 return 0;
1070}
1071
1072static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)
1073{
1074 const u16 reported_product_id = hid->product;
1075 int ret;
1076
1077 *real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice);
1078 /* Probed wheel is not a multimode wheel */
1079 if (!*real_product_id) {
1080 *real_product_id = reported_product_id;
1081 dbg_hid("Wheel is not a multimode wheel\n");
1082 return LG4FF_MMODE_NOT_MULTIMODE;
1083 }
1084
1085 /* Switch from "Driving Force" mode to native mode automatically.
1086 * Otherwise keep the wheel in its current mode */
1087 if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
Michal Malýa54dc7792015-02-18 17:59:22 +01001088 reported_product_id != *real_product_id &&
1089 !lg4ff_no_autoswitch) {
Michal Malýf31a2de2015-02-18 17:59:23 +01001090 const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
Michal Malýe7c23442015-02-18 17:59:20 +01001091
Michal Malýf31a2de2015-02-18 17:59:23 +01001092 if (!s) {
Michal Malýe7c23442015-02-18 17:59:20 +01001093 hid_err(hid, "Invalid product id %X\n", *real_product_id);
Michal Malýb96d23e2015-02-18 17:59:21 +01001094 return LG4FF_MMODE_NOT_MULTIMODE;
Michal Malýe7c23442015-02-18 17:59:20 +01001095 }
1096
1097 ret = lg4ff_switch_compatibility_mode(hid, s);
1098 if (ret) {
1099 /* Wheel could not have been switched to native mode,
1100 * leave it in "Driving Force" mode and continue */
1101 hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret);
Michal Malýb96d23e2015-02-18 17:59:21 +01001102 return LG4FF_MMODE_IS_MULTIMODE;
Michal Malýe7c23442015-02-18 17:59:20 +01001103 }
1104 return LG4FF_MMODE_SWITCHED;
1105 }
1106
Michal Malýb96d23e2015-02-18 17:59:21 +01001107 return LG4FF_MMODE_IS_MULTIMODE;
Michal Malýe7c23442015-02-18 17:59:20 +01001108}
1109
1110
Simon Wood32c88cb2010-09-22 13:19:42 +02001111int lg4ff_init(struct hid_device *hid)
1112{
1113 struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
Simon Wood32c88cb2010-09-22 13:19:42 +02001114 struct input_dev *dev = hidinput->input;
Michal Malýc28abd82015-04-08 22:56:49 +02001115 struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
1116 struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
Michal Malýe7c23442015-02-18 17:59:20 +01001117 const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
1118 const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
Michal Malý5d9d60a2015-04-08 22:56:50 +02001119 const struct lg4ff_multimode_wheel *mmode_wheel = NULL;
Michal Malý30bb75d2011-08-04 16:20:40 +02001120 struct lg4ff_device_entry *entry;
Michal Malý3b6b17b2012-04-09 09:08:49 +02001121 struct lg_drv_data *drv_data;
Michal Malýb96d23e2015-02-18 17:59:21 +01001122 int error, i, j;
1123 int mmode_ret, mmode_idx = -1;
Michal Malýe7c23442015-02-18 17:59:20 +01001124 u16 real_product_id;
Simon Wood32c88cb2010-09-22 13:19:42 +02001125
Simon Wood32c88cb2010-09-22 13:19:42 +02001126 /* Check that the report looks ok */
Kees Cook0fb6bd02013-09-11 21:56:54 +02001127 if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
Simon Wood32c88cb2010-09-22 13:19:42 +02001128 return -1;
Michal Malý30bb75d2011-08-04 16:20:40 +02001129
Michal Malý72529c62015-04-08 22:56:46 +02001130 drv_data = hid_get_drvdata(hid);
1131 if (!drv_data) {
1132 hid_err(hid, "Cannot add device, private driver data not allocated\n");
1133 return -1;
1134 }
1135 entry = kzalloc(sizeof(*entry), GFP_KERNEL);
1136 if (!entry)
1137 return -ENOMEM;
Michal Malýc918fe72015-04-08 22:56:48 +02001138 spin_lock_init(&entry->report_lock);
Michal Malýc28abd82015-04-08 22:56:49 +02001139 entry->report = report;
Michal Malý72529c62015-04-08 22:56:46 +02001140 drv_data->device_props = entry;
1141
Michal Malýe7c23442015-02-18 17:59:20 +01001142 /* Check if a multimode wheel has been connected and
1143 * handle it appropriately */
Michal Malýb96d23e2015-02-18 17:59:21 +01001144 mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
Michal Malýe7c23442015-02-18 17:59:20 +01001145
1146 /* Wheel has been told to switch to native mode. There is no point in going on
1147 * with the initialization as the wheel will do a USB reset when it switches mode
1148 */
Michal Malýb96d23e2015-02-18 17:59:21 +01001149 if (mmode_ret == LG4FF_MMODE_SWITCHED)
Michal Malýe7c23442015-02-18 17:59:20 +01001150 return 0;
Michal Malý72529c62015-04-08 22:56:46 +02001151 else if (mmode_ret < 0) {
1152 hid_err(hid, "Unable to switch device mode during initialization, errno %d\n", mmode_ret);
1153 error = mmode_ret;
1154 goto err_init;
1155 }
Michal Malýe7c23442015-02-18 17:59:20 +01001156
Michal Malý7362cd22011-08-04 16:16:09 +02001157 /* Check what wheel has been connected */
1158 for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
1159 if (hid->product == lg4ff_devices[i].product_id) {
1160 dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id);
1161 break;
1162 }
1163 }
Simon Wood32c88cb2010-09-22 13:19:42 +02001164
Michal Malý7362cd22011-08-04 16:16:09 +02001165 if (i == ARRAY_SIZE(lg4ff_devices)) {
Michal Malý9c2a6bd2015-04-08 22:56:44 +02001166 hid_err(hid, "This device is flagged to be handled by the lg4ff module but this module does not know how to handle it. "
1167 "Please report this as a bug to LKML, Simon Wood <simon@mungewell.org> or "
1168 "Michal Maly <madcatxster@devoid-pointer.net>\n");
Michal Malý72529c62015-04-08 22:56:46 +02001169 error = -1;
1170 goto err_init;
Michal Malý7362cd22011-08-04 16:16:09 +02001171 }
1172
Michal Malýb96d23e2015-02-18 17:59:21 +01001173 if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
1174 for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) {
1175 if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id)
1176 break;
1177 }
1178
1179 if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) {
1180 hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id);
Michal Malý72529c62015-04-08 22:56:46 +02001181 error = -1;
1182 goto err_init;
Michal Malýb96d23e2015-02-18 17:59:21 +01001183 }
1184 }
1185
Michal Malý7362cd22011-08-04 16:16:09 +02001186 /* Set supported force feedback capabilities */
1187 for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
1188 set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
Simon Wood32c88cb2010-09-22 13:19:42 +02001189
Michal Malýd0afd842015-04-08 22:56:40 +02001190 error = input_ff_create_memless(dev, NULL, lg4ff_play);
Simon Wood32c88cb2010-09-22 13:19:42 +02001191
1192 if (error)
Michal Malý72529c62015-04-08 22:56:46 +02001193 goto err_init;
Simon Wood32c88cb2010-09-22 13:19:42 +02001194
Michal Malý5d9d60a2015-04-08 22:56:50 +02001195 /* Initialize device properties */
Michal Malýb96d23e2015-02-18 17:59:21 +01001196 if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
1197 BUG_ON(mmode_idx == -1);
Michal Malý5d9d60a2015-04-08 22:56:50 +02001198 mmode_wheel = &lg4ff_multimode_wheels[mmode_idx];
Michal Malýb96d23e2015-02-18 17:59:21 +01001199 }
Michal Malý5d9d60a2015-04-08 22:56:50 +02001200 lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);
Michal Malý30bb75d2011-08-04 16:20:40 +02001201
Simon Wood114a55c2013-11-06 12:30:43 -07001202 /* Check if autocentering is available and
1203 * set the centering force to zero by default */
1204 if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
Michal Malýe7c23442015-02-18 17:59:20 +01001205 /* Formula Force EX expects different autocentering command */
1206 if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
1207 (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
Michal Malýd0afd842015-04-08 22:56:40 +02001208 dev->ff->set_autocenter = lg4ff_set_autocenter_ffex;
Simon Wood114a55c2013-11-06 12:30:43 -07001209 else
Michal Malýd0afd842015-04-08 22:56:40 +02001210 dev->ff->set_autocenter = lg4ff_set_autocenter_default;
Simon Wood114a55c2013-11-06 12:30:43 -07001211
1212 dev->ff->set_autocenter(dev, 0);
1213 }
1214
Michal Malý30bb75d2011-08-04 16:20:40 +02001215 /* Create sysfs interface */
1216 error = device_create_file(&hid->dev, &dev_attr_range);
1217 if (error)
Michal Malýd61a70ec2015-04-08 22:56:51 +02001218 hid_warn(hid, "Unable to create sysfs interface for \"range\", errno %d\n", error);
Michal Malýb96d23e2015-02-18 17:59:21 +01001219 if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
1220 error = device_create_file(&hid->dev, &dev_attr_real_id);
1221 if (error)
Michal Malýd61a70ec2015-04-08 22:56:51 +02001222 hid_warn(hid, "Unable to create sysfs interface for \"real_id\", errno %d\n", error);
Michal Malýb96d23e2015-02-18 17:59:21 +01001223 error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
1224 if (error)
Michal Malýd61a70ec2015-04-08 22:56:51 +02001225 hid_warn(hid, "Unable to create sysfs interface for \"alternate_modes\", errno %d\n", error);
Michal Malýb96d23e2015-02-18 17:59:21 +01001226 }
Michal Malý30bb75d2011-08-04 16:20:40 +02001227 dbg_hid("sysfs interface created\n");
1228
1229 /* Set the maximum range to start with */
Michal Malý72529c62015-04-08 22:56:46 +02001230 entry->wdata.range = entry->wdata.max_range;
1231 if (entry->wdata.set_range)
1232 entry->wdata.set_range(hid, entry->wdata.range);
Michal Malý30bb75d2011-08-04 16:20:40 +02001233
Simon Wood22bcefd2012-04-21 05:41:15 -07001234#ifdef CONFIG_LEDS_CLASS
1235 /* register led subsystem - G27 only */
Michal Malý72529c62015-04-08 22:56:46 +02001236 entry->wdata.led_state = 0;
Simon Wood22bcefd2012-04-21 05:41:15 -07001237 for (j = 0; j < 5; j++)
Michal Malý72529c62015-04-08 22:56:46 +02001238 entry->wdata.led[j] = NULL;
Simon Wood22bcefd2012-04-21 05:41:15 -07001239
1240 if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL) {
1241 struct led_classdev *led;
1242 size_t name_sz;
1243 char *name;
1244
1245 lg4ff_set_leds(hid, 0);
1246
1247 name_sz = strlen(dev_name(&hid->dev)) + 8;
1248
1249 for (j = 0; j < 5; j++) {
1250 led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
1251 if (!led) {
1252 hid_err(hid, "can't allocate memory for LED %d\n", j);
Michal Malý72529c62015-04-08 22:56:46 +02001253 goto err_leds;
Simon Wood22bcefd2012-04-21 05:41:15 -07001254 }
1255
1256 name = (void *)(&led[1]);
1257 snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1);
1258 led->name = name;
1259 led->brightness = 0;
1260 led->max_brightness = 1;
1261 led->brightness_get = lg4ff_led_get_brightness;
1262 led->brightness_set = lg4ff_led_set_brightness;
1263
Michal Malý72529c62015-04-08 22:56:46 +02001264 entry->wdata.led[j] = led;
Simon Wood22bcefd2012-04-21 05:41:15 -07001265 error = led_classdev_register(&hid->dev, led);
1266
1267 if (error) {
1268 hid_err(hid, "failed to register LED %d. Aborting.\n", j);
Michal Malý72529c62015-04-08 22:56:46 +02001269err_leds:
Simon Wood22bcefd2012-04-21 05:41:15 -07001270 /* Deregister LEDs (if any) */
1271 for (j = 0; j < 5; j++) {
Michal Malý72529c62015-04-08 22:56:46 +02001272 led = entry->wdata.led[j];
1273 entry->wdata.led[j] = NULL;
Simon Wood22bcefd2012-04-21 05:41:15 -07001274 if (!led)
1275 continue;
1276 led_classdev_unregister(led);
1277 kfree(led);
1278 }
1279 goto out; /* Let the driver continue without LEDs */
1280 }
1281 }
1282 }
Simon Wood22bcefd2012-04-21 05:41:15 -07001283out:
Jiri Kosinac6e6dc82012-04-23 21:08:15 +02001284#endif
Simon Wood64013802012-04-21 05:41:16 -07001285 hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n");
Simon Wood32c88cb2010-09-22 13:19:42 +02001286 return 0;
Michal Malý72529c62015-04-08 22:56:46 +02001287
1288err_init:
1289 drv_data->device_props = NULL;
1290 kfree(entry);
1291 return error;
Simon Wood32c88cb2010-09-22 13:19:42 +02001292}
1293
Michal Malý30bb75d2011-08-04 16:20:40 +02001294int lg4ff_deinit(struct hid_device *hid)
1295{
Michal Malý30bb75d2011-08-04 16:20:40 +02001296 struct lg4ff_device_entry *entry;
Michal Malý3b6b17b2012-04-09 09:08:49 +02001297 struct lg_drv_data *drv_data;
1298
Michal Malý3b6b17b2012-04-09 09:08:49 +02001299 drv_data = hid_get_drvdata(hid);
1300 if (!drv_data) {
1301 hid_err(hid, "Error while deinitializing device, no private driver data.\n");
Michal Malý30bb75d2011-08-04 16:20:40 +02001302 return -1;
1303 }
Michal Malý3b6b17b2012-04-09 09:08:49 +02001304 entry = drv_data->device_props;
Michal Malýe7c23442015-02-18 17:59:20 +01001305 if (!entry)
1306 goto out; /* Nothing more to do */
1307
Michal Malýb96d23e2015-02-18 17:59:21 +01001308 /* Multimode devices will have at least the "MODE_NATIVE" bit set */
Michal Malý72529c62015-04-08 22:56:46 +02001309 if (entry->wdata.alternate_modes) {
Michal Malýb96d23e2015-02-18 17:59:21 +01001310 device_remove_file(&hid->dev, &dev_attr_real_id);
1311 device_remove_file(&hid->dev, &dev_attr_alternate_modes);
1312 }
1313
Michal Malý72529c62015-04-08 22:56:46 +02001314 device_remove_file(&hid->dev, &dev_attr_range);
Simon Wood22bcefd2012-04-21 05:41:15 -07001315#ifdef CONFIG_LEDS_CLASS
1316 {
1317 int j;
1318 struct led_classdev *led;
1319
1320 /* Deregister LEDs (if any) */
1321 for (j = 0; j < 5; j++) {
1322
Michal Malý72529c62015-04-08 22:56:46 +02001323 led = entry->wdata.led[j];
1324 entry->wdata.led[j] = NULL;
Simon Wood22bcefd2012-04-21 05:41:15 -07001325 if (!led)
1326 continue;
1327 led_classdev_unregister(led);
1328 kfree(led);
1329 }
1330 }
1331#endif
Michal Malýb211a632015-04-08 22:56:47 +02001332 hid_hw_stop(hid);
1333 drv_data->device_props = NULL;
Simon Wood22bcefd2012-04-21 05:41:15 -07001334
Michal Malý3b6b17b2012-04-09 09:08:49 +02001335 kfree(entry);
Michal Malýe7c23442015-02-18 17:59:20 +01001336out:
Michal Malý30bb75d2011-08-04 16:20:40 +02001337 dbg_hid("Device successfully unregistered\n");
1338 return 0;
1339}