/* Recalculates X axis value accordingly to currently selected range */ static s32 lg4ff_adjust_dfp_x_axis(s32 value, u16 range)
{
u16 max_range;
s32 new_value;
/* Sends command to set range compatible with G25/G27/Driving Force GT */ staticvoid lg4ff_set_range_g25(struct hid_device *hid, u16 range)
{ struct lg4ff_device_entry *entry; struct lg_drv_data *drv_data; unsignedlong flags;
s32 *value;
drv_data = hid_get_drvdata(hid); if (!drv_data) {
hid_err(hid, "Private driver data not found!\n"); return;
}
entry = drv_data->device_props; if (!entry) {
hid_err(hid, "Device properties not found!\n"); return;
}
value = entry->report->field[0]->value;
dbg_hid("G25/G27/DFGT: setting range to %u\n", range);
/* Sends commands to set range compatible with Driving Force Pro wheel */ staticvoid lg4ff_set_range_dfp(struct hid_device *hid, u16 range)
{ struct lg4ff_device_entry *entry; struct lg_drv_data *drv_data; unsignedlong flags; int start_left, start_right, full_range;
s32 *value;
drv_data = hid_get_drvdata(hid); if (!drv_data) {
hid_err(hid, "Private driver data not found!\n"); return;
}
entry = drv_data->device_props; if (!entry) {
hid_err(hid, "Device properties not found!\n"); return;
}
value = entry->report->field[0]->value;
dbg_hid("Driving Force Pro: setting range to %u\n", range);
if (range == 200 || range == 900) { /* Do not apply any fine limit */
hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
spin_unlock_irqrestore(&entry->report_lock, flags); return;
}
staticconststruct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
{ switch (real_product_id) { case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: switch (target_product_id) { case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: return &lg4ff_mode_switch_ext01_dfp; /* DFP can only be switched to its native mode */ default: return NULL;
} break; case USB_DEVICE_ID_LOGITECH_G25_WHEEL: switch (target_product_id) { case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: return &lg4ff_mode_switch_ext01_dfp; case USB_DEVICE_ID_LOGITECH_G25_WHEEL: return &lg4ff_mode_switch_ext16_g25; /* G25 can only be switched to DFP mode or its native mode */ default: return NULL;
} break; case USB_DEVICE_ID_LOGITECH_G27_WHEEL: switch (target_product_id) { case USB_DEVICE_ID_LOGITECH_WHEEL: return &lg4ff_mode_switch_ext09_dfex; case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: return &lg4ff_mode_switch_ext09_dfp; case USB_DEVICE_ID_LOGITECH_G25_WHEEL: return &lg4ff_mode_switch_ext09_g25; case USB_DEVICE_ID_LOGITECH_G27_WHEEL: return &lg4ff_mode_switch_ext09_g27; /* G27 can only be switched to DF-EX, DFP, G25 or its native mode */ default: return NULL;
} break; case USB_DEVICE_ID_LOGITECH_G29_WHEEL: switch (target_product_id) { case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: return &lg4ff_mode_switch_ext09_dfp; case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: return &lg4ff_mode_switch_ext09_dfgt; case USB_DEVICE_ID_LOGITECH_G25_WHEEL: return &lg4ff_mode_switch_ext09_g25; case USB_DEVICE_ID_LOGITECH_G27_WHEEL: return &lg4ff_mode_switch_ext09_g27; case USB_DEVICE_ID_LOGITECH_G29_WHEEL: return &lg4ff_mode_switch_ext09_g29; /* G29 can only be switched to DF-EX, DFP, DFGT, G25, G27 or its native mode */ default: return NULL;
} break; case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: switch (target_product_id) { case USB_DEVICE_ID_LOGITECH_WHEEL: return &lg4ff_mode_switch_ext09_dfex; case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: return &lg4ff_mode_switch_ext09_dfp; case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: return &lg4ff_mode_switch_ext09_dfgt; /* DFGT can only be switched to DF-EX, DFP or its native mode */ default: return NULL;
} break; /* No other wheels have multiple modes */ default: return NULL;
}
}
drv_data = hid_get_drvdata(hid); if (!drv_data) {
hid_err(hid, "Private driver data not found!\n"); return 0;
}
entry = drv_data->device_props; if (!entry) {
hid_err(hid, "Device properties not found!\n"); return 0;
}
if (!entry->wdata.real_name) {
hid_err(hid, "NULL pointer to string\n"); return 0;
}
for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) { if (entry->wdata.alternate_modes & BIT(i)) { /* Print tag and full name */
count += sysfs_emit_at(buf, count, "%s: %s",
lg4ff_alternate_modes[i].tag,
!lg4ff_alternate_modes[i].product_id ? entry->wdata.real_name : lg4ff_alternate_modes[i].name); if (count >= PAGE_SIZE - 1) return count;
/* Mark the currently active mode with an asterisk */ if (lg4ff_alternate_modes[i].product_id == entry->wdata.product_id ||
(lg4ff_alternate_modes[i].product_id == 0 && entry->wdata.product_id == entry->wdata.real_product_id))
count += sysfs_emit_at(buf, count, " *\n"); else
count += sysfs_emit_at(buf, count, "\n");
drv_data = hid_get_drvdata(hid); if (!drv_data) {
hid_err(hid, "Private driver data not found!\n"); return -EINVAL;
}
entry = drv_data->device_props; if (!entry) {
hid_err(hid, "Device properties not found!\n"); return -EINVAL;
}
/* Allow \n at the end of the input parameter */
lbuf = kasprintf(GFP_KERNEL, "%s", buf); if (!lbuf) return -ENOMEM;
i = strlen(lbuf);
if (i == 0) {
kfree(lbuf); return -EINVAL;
}
if (lbuf[i-1] == '\n') { if (i == 1) {
kfree(lbuf); return -EINVAL;
}
lbuf[i-1] = '\0';
}
for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) { const u16 mode_product_id = lg4ff_alternate_modes[i].product_id; constchar *tag = lg4ff_alternate_modes[i].tag;
if (entry->wdata.alternate_modes & BIT(i)) { if (!strcmp(tag, lbuf)) { if (!mode_product_id)
target_product_id = entry->wdata.real_product_id; else
target_product_id = mode_product_id; break;
}
}
}
if (i == LG4FF_MODE_MAX_IDX) {
hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf);
kfree(lbuf); return -EINVAL;
}
kfree(lbuf); /* Not needed anymore */
if (target_product_id == entry->wdata.product_id) /* Nothing to do */ return count;
/* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */ if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
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",
entry->wdata.real_name); return -EINVAL;
}
/* Take care of hardware limitations */ if ((entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
entry->wdata.product_id > target_product_id) {
hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->wdata.real_name, lg4ff_alternate_modes[i].name); return -EINVAL;
}
s = lg4ff_get_mode_switch_command(entry->wdata.real_product_id, target_product_id); if (!s) {
hid_err(hid, "Invalid target product ID %X\n", target_product_id); return -EINVAL;
}
/* Set range to user specified value, call appropriate function
* according to the type of the wheel */ static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr, constchar *buf, size_t count)
{ struct hid_device *hid = to_hid_device(dev); struct lg4ff_device_entry *entry; struct lg_drv_data *drv_data;
u16 range = simple_strtoul(buf, NULL, 10);
drv_data = hid_get_drvdata(hid); if (!drv_data) {
hid_err(hid, "Private driver data not found!\n"); return -EINVAL;
}
entry = drv_data->device_props; if (!entry) {
hid_err(hid, "Device properties not found!\n"); return -EINVAL;
}
if (range == 0)
range = entry->wdata.max_range;
/* Check if the wheel supports range setting
* and that the range is within limits for the wheel */ if (entry->wdata.set_range && range >= entry->wdata.min_range && range <= entry->wdata.max_range) {
entry->wdata.set_range(hid, range);
entry->wdata.range = range;
}
/* identify current mode from USB PID */ for (i = 1; i < ARRAY_SIZE(lg4ff_alternate_modes); i++) {
dbg_hid("Testing whether PID is %X\n", lg4ff_alternate_modes[i].product_id); if (reported_product_id == lg4ff_alternate_modes[i].product_id) break;
}
if (i == ARRAY_SIZE(lg4ff_alternate_modes)) return 0;
current_mode = BIT(i);
for (i = 0; i < ARRAY_SIZE(lg4ff_main_checklist); i++) { const u16 mask = lg4ff_main_checklist[i]->mask; const u16 result = lg4ff_main_checklist[i]->result; const u16 real_product_id = lg4ff_main_checklist[i]->real_product_id;
if ((current_mode & lg4ff_main_checklist[i]->modes) && \
(bcdDevice & mask) == result) {
dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id); return real_product_id;
}
}
/* No match found. This is either Driving Force or an unknown
* wheel model, do not touch it */
dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice); return 0;
}
*real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice); /* Probed wheel is not a multimode wheel */ if (!*real_product_id) {
*real_product_id = reported_product_id;
dbg_hid("Wheel is not a multimode wheel\n"); return LG4FF_MMODE_NOT_MULTIMODE;
}
/* Switch from "Driving Force" mode to native mode automatically.
* Otherwise keep the wheel in its current mode */ if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
reported_product_id != *real_product_id &&
!lg4ff_no_autoswitch) { conststruct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
if (!s) {
hid_err(hid, "Invalid product id %X\n", *real_product_id); return LG4FF_MMODE_NOT_MULTIMODE;
}
ret = lg4ff_switch_compatibility_mode(hid, s); if (ret) { /* Wheel could not have been switched to native mode,
* leave it in "Driving Force" mode and continue */
hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret); return LG4FF_MMODE_IS_MULTIMODE;
} return LG4FF_MMODE_SWITCHED;
}
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n"); return -ENODEV;
}
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
dev = hidinput->input;
/* Check that the report looks ok */ if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) return -1;
drv_data = hid_get_drvdata(hid); if (!drv_data) {
hid_err(hid, "Cannot add device, private driver data not allocated\n"); return -1;
}
entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return -ENOMEM;
spin_lock_init(&entry->report_lock);
entry->report = report;
drv_data->device_props = entry;
/* Check if a multimode wheel has been connected and
* handle it appropriately */
mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
/* Wheel has been told to switch to native mode. There is no point in going on * with the initialization as the wheel will do a USB reset when it switches mode
*/ if (mmode_ret == LG4FF_MMODE_SWITCHED) return 0; elseif (mmode_ret < 0) {
hid_err(hid, "Unable to switch device mode during initialization, errno %d\n", mmode_ret);
error = mmode_ret; goto err_init;
}
/* Check what wheel has been connected */ for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { if (hid->product == lg4ff_devices[i].product_id) {
dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id); break;
}
}
if (i == ARRAY_SIZE(lg4ff_devices)) {
hid_err(hid, "This device is flagged to be handled by the lg4ff module but this module does not know how to handle it. " "Please report this as a bug to LKML, Simon Wood or " "Michal Maly \n");
error = -1; goto err_init;
}
if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) { for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) { if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id) break;
}
if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) {
hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id);
error = -1; goto err_init;
}
}
/* Set supported force feedback capabilities */ for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
/* Check if autocentering is available and
* set the centering force to zero by default */ if (test_bit(FF_AUTOCENTER, dev->ffbit)) { /* Formula Force EX expects different autocentering command */ if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
(bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
dev->ff->set_autocenter = lg4ff_set_autocenter_ffex; else
dev->ff->set_autocenter = lg4ff_set_autocenter_default;
dev->ff->set_autocenter(dev, 0);
}
/* Create sysfs interface */
error = device_create_file(&hid->dev, &dev_attr_combine_pedals); if (error)
hid_warn(hid, "Unable to create sysfs interface for \"combine\", errno %d\n", error);
error = device_create_file(&hid->dev, &dev_attr_range); if (error)
hid_warn(hid, "Unable to create sysfs interface for \"range\", errno %d\n", error); if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
error = device_create_file(&hid->dev, &dev_attr_real_id); if (error)
hid_warn(hid, "Unable to create sysfs interface for \"real_id\", errno %d\n", error);
error = device_create_file(&hid->dev, &dev_attr_alternate_modes); if (error)
hid_warn(hid, "Unable to create sysfs interface for \"alternate_modes\", errno %d\n", error);
}
dbg_hid("sysfs interface created\n");
/* Set the maximum range to start with */
entry->wdata.range = entry->wdata.max_range; if (entry->wdata.set_range)
entry->wdata.set_range(hid, entry->wdata.range);
#ifdef CONFIG_LEDS_CLASS /* register led subsystem - G27/G29 only */
entry->wdata.led_state = 0; for (j = 0; j < 5; j++)
entry->wdata.led[j] = NULL;
drv_data = hid_get_drvdata(hid); if (!drv_data) {
hid_err(hid, "Error while deinitializing device, no private driver data.\n"); return -1;
}
entry = drv_data->device_props; if (!entry) goto out; /* Nothing more to do */
/* Multimode devices will have at least the "MODE_NATIVE" bit set */ if (entry->wdata.alternate_modes) {
device_remove_file(&hid->dev, &dev_attr_real_id);
device_remove_file(&hid->dev, &dev_attr_alternate_modes);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.