/* These are just aliases for now */ #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS #define HIDPP_QUIRK_KBD_ZOOM_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
/* Convenience constant to check for any high-res support. */ #define HIDPP_CAPABILITY_HI_RES_SCROLL (HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL | \
HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL | \
HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL)
/* * There are two hidpp protocols in use, the first version hidpp10 is known * as register access protocol or RAP, the second version hidpp20 is known as * feature access protocol or FAP * * Most older devices (including the Unifying usb receiver) use the RAP protocol * where as most newer devices use the FAP protocol. Both protocols are * compatible with the underlying transport, which could be usb, Unifiying, or * bluetooth. The message lengths are defined by the hid vendor specific report * descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and * the HIDPP_LONG report type (total message length 20 bytes) * * The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG * messages. The Unifying receiver itself responds to RAP messages (device index * is 0xFF for the receiver), and all messages (short or long) with a device * index between 1 and 6 are passed untouched to the corresponding paired * Unifying device. * * The paired device can be RAP or FAP, it will receive the message untouched * from the Unifiying receiver.
*/
struct hidpp_battery {
u8 feature_index;
u8 solar_feature_index;
u8 voltage_feature_index;
u8 adc_measurement_feature_index; struct power_supply_desc desc; struct power_supply *ps; char name[64]; int status; int capacity; int level; int voltage; int charge_type; bool online;
u8 supported_levels_1004;
};
/** * struct hidpp_scroll_counter - Utility class for processing high-resolution * scroll events. * @dev: the input device for which events should be reported. * @wheel_multiplier: the scalar multiplier to be applied to each wheel event * @remainder: counts the number of high-resolution units moved since the last * low-resolution event (REL_WHEEL or REL_HWHEEL) was sent. Should * only be used by class methods. * @direction: direction of last movement (1 or -1) * @last_time: last event time, used to reset remainder after inactivity
*/ struct hidpp_scroll_counter { int wheel_multiplier; int remainder; int direction; unsignedlonglong last_time;
};
struct hidpp_device { struct hid_device *hid_dev; struct input_dev *input; struct mutex send_mutex; void *send_receive_buf; char *name; /* will never be NULL and should not be freed */
wait_queue_head_t wait; int very_long_report_length; bool answer_available;
u8 protocol_major;
u8 protocol_minor;
switch (hidpp_report->report_id) { case REPORT_ID_HIDPP_SHORT:
fields_count = HIDPP_REPORT_SHORT_LENGTH; break; case REPORT_ID_HIDPP_LONG:
fields_count = HIDPP_REPORT_LONG_LENGTH; break; case REPORT_ID_HIDPP_VERY_LONG:
fields_count = hidpp->very_long_report_length; break; default: return -ENODEV;
}
/* * set the device_index as the receiver, it will be overwritten by * hid_hw_request if needed
*/
hidpp_report->device_index = 0xff;
if (hidpp->quirks & HIDPP_QUIRK_FORCE_OUTPUT_REPORTS) {
ret = hid_hw_output_report(hdev, (u8 *)hidpp_report, fields_count);
} else {
ret = hid_hw_raw_request(hdev, hidpp_report->report_id,
(u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT,
HID_REQ_SET_REPORT);
}
return ret == fields_count ? 0 : -1;
}
/* * Effectively send the message to the device, waiting for its answer. * * Must be called with hidpp->send_mutex locked * * Same return protocol than hidpp_send_message_sync(): * - success on 0 * - negative error means transport error * - positive value means protocol error
*/ staticint __do_hidpp_send_message_sync(struct hidpp_device *hidpp, struct hidpp_report *message, struct hidpp_report *response)
{ int ret;
/* * hidpp_send_message_sync() returns 0 in case of success, and something else * in case of a failure. * * See __do_hidpp_send_message_sync() for a detailed explanation of the returned * value.
*/ staticint hidpp_send_message_sync(struct hidpp_device *hidpp, struct hidpp_report *message, struct hidpp_report *response)
{ int ret; int max_retries = 3;
mutex_lock(&hidpp->send_mutex);
do {
ret = __do_hidpp_send_message_sync(hidpp, message, response); if (ret != HIDPP20_ERROR_BUSY) break;
/* * hidpp_send_fap_command_sync() returns 0 in case of success, and something else * in case of a failure. * * See __do_hidpp_send_message_sync() for a detailed explanation of the returned * value.
*/ staticint hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count, struct hidpp_report *response)
{ struct hidpp_report *message; int ret;
if (param_count > sizeof(message->fap.params)) {
hid_dbg(hidpp->hid_dev, "Invalid number of parameters passed to command (%d != %llu)\n",
param_count,
(unsignedlonglong) sizeof(message->fap.params)); return -EINVAL;
}
message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); if (!message) return -ENOMEM;
ret = hidpp_send_message_sync(hidpp, message, response);
kfree(message); return ret;
}
/* * hidpp_send_rap_command_sync() returns 0 in case of success, and something else * in case of a failure. * * See __do_hidpp_send_message_sync() for a detailed explanation of the returned * value.
*/ staticint hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count, struct hidpp_report *response)
{ struct hidpp_report *message; int ret, max_count;
/* Send as long report if short reports are not supported. */ if (report_id == REPORT_ID_HIDPP_SHORT &&
!(hidpp_dev->supported_reports & HIDPP_REPORT_SHORT_SUPPORTED))
report_id = REPORT_ID_HIDPP_LONG;
/* * hidpp_prefix_name() prefixes the current given name with "Logitech ".
*/ staticvoid hidpp_prefix_name(char **name, int name_length)
{ #define PREFIX_LENGTH 9/* "Logitech " */
int new_length; char *new_name;
if (name_length > PREFIX_LENGTH &&
strncmp(*name, "Logitech ", PREFIX_LENGTH) == 0) /* The prefix has is already in the name */ return;
/* * Updates the USB wireless_status based on whether the headset * is turned on and reachable.
*/ staticvoid hidpp_update_usb_wireless_status(struct hidpp_device *hidpp)
{ struct hid_device *hdev = hidpp->hid_dev; struct usb_interface *intf;
if (!(hidpp->quirks & HIDPP_QUIRK_WIRELESS_STATUS)) return; if (!hid_is_usb(hdev)) return;
/** * hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll * events given a high-resolution wheel * movement. * @input_dev: Pointer to the input device * @counter: a hid_scroll_counter struct describing the wheel. * @hi_res_value: the movement of the wheel, in the mouse's high-resolution * units. * * Given a high-resolution movement, this function converts the movement into * fractions of 120 and emits high-resolution scroll events for the input * device. It also uses the multiplier from &struct hid_scroll_counter to * emit low-resolution scroll events when appropriate for * backwards-compatibility with userspace input libraries.
*/ staticvoid hidpp_scroll_counter_handle_scroll(struct input_dev *input_dev, struct hidpp_scroll_counter *counter, int hi_res_value)
{ int low_res_value, remainder, direction; unsignedlonglong now, previous;
now = sched_clock();
previous = counter->last_time;
counter->last_time = now; /* * Reset the remainder after a period of inactivity or when the * direction changes. This prevents the REL_WHEEL emulation point * from sliding for devices that don't always provide the same * number of movements per detent.
*/ if (now - previous > 1000000000 || direction != counter->direction)
remainder = 0;
/* Some wheels will rest 7/8ths of a detent from the previous detent * after slow movement, so we want the threshold for low-res events to * be in the middle between two detents (e.g. after 4/8ths) as * opposed to on the detents themselves (8/8ths).
*/ if (abs(remainder) >= 60) { /* Add (or subtract) 1 because we want to trigger when the wheel * is half-way to the next detent (i.e. scroll 1 detent after a * 1/2 detent movement, 2 detents after a 1 1/2 detent movement, * etc.).
*/
low_res_value = remainder / 120; if (low_res_value == 0)
low_res_value = (hi_res_value > 0 ? 1 : -1);
input_report_rel(input_dev, REL_WHEEL, low_res_value);
remainder -= low_res_value * 120;
}
counter->remainder = remainder;
}
/** * hidpp10_set_register - Modify a HID++ 1.0 register. * @hidpp_dev: the device to set the register on. * @register_address: the address of the register to modify. * @byte: the byte of the register to modify. Should be less than 3. * @mask: mask of the bits to modify * @value: new values for the bits in mask * Return: 0 if successful, otherwise a negative error code.
*/ staticint hidpp10_set_register(struct hidpp_device *hidpp_dev,
u8 register_address, u8 byte, u8 mask, u8 value)
{ struct hidpp_report response; int ret;
u8 params[3] = { 0 };
ret = hidpp_send_rap_command_sync(hidpp_dev,
REPORT_ID_HIDPP_SHORT,
HIDPP_GET_REGISTER,
register_address,
NULL, 0, &response); if (ret) return ret;
memcpy(params, response.rap.params, 3);
params[byte] &= ~mask;
params[byte] |= value & mask;
ret = hidpp_send_rap_command_sync(hidpp,
REPORT_ID_HIDPP_SHORT,
HIDPP_GET_REGISTER,
HIDPP_REG_BATTERY_STATUS,
NULL, 0, &response); if (ret) return ret;
hidpp->battery.level =
hidpp10_battery_status_map_level(response.rap.params[0]);
status = hidpp10_battery_status_map_status(response.rap.params[1]);
hidpp->battery.status = status; /* the capacity is only available when discharging or full */
hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
status == POWER_SUPPLY_STATUS_FULL;
return0;
}
#define HIDPP_REG_BATTERY_MILEAGE 0x0D
staticint hidpp10_battery_mileage_map_status(u8 param)
{ int status;
switch (param >> 6) { case0x00: /* discharging (in use) */
status = POWER_SUPPLY_STATUS_DISCHARGING; break; case0x01: /* charging */
status = POWER_SUPPLY_STATUS_CHARGING; break; case0x02: /* charge complete */
status = POWER_SUPPLY_STATUS_FULL; break; /* * 0x03 = charging error
*/ default:
status = POWER_SUPPLY_STATUS_NOT_CHARGING; break;
}
ret = hidpp_send_rap_command_sync(hidpp,
REPORT_ID_HIDPP_SHORT,
HIDPP_GET_REGISTER,
HIDPP_REG_BATTERY_MILEAGE,
NULL, 0, &response); if (ret) return ret;
hidpp->battery.capacity = response.rap.params[0];
status = hidpp10_battery_mileage_map_status(response.rap.params[2]);
hidpp->battery.status = status; /* the capacity is only available when discharging or full */
hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
status == POWER_SUPPLY_STATUS_FULL;
/* the capacity is only available when discharging or full */
hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
status == POWER_SUPPLY_STATUS_FULL;
if (changed) {
hidpp->battery.level = level;
hidpp->battery.status = status; if (hidpp->battery.ps)
power_supply_changed(hidpp->battery.ps);
}
ret = hidpp_send_rap_command_sync(hidpp,
REPORT_ID_HIDPP_SHORT,
HIDPP_GET_LONG_REGISTER,
HIDPP_REG_PAIRING_INFORMATION,
params, 1, &response); if (ret) return ret;
/* * We don't care about LE or BE, we will output it as a string * with %4phD, so we need to keep the order.
*/
*serial = *((u32 *)&response.rap.params[1]); return0;
}
staticint hidpp_map_battery_level(int capacity)
{ if (capacity < 11) return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; /* * The spec says this should be < 31 but some devices report 30 * with brand new batteries and Windows reports 30 as "Good".
*/ elseif (capacity < 30) return POWER_SUPPLY_CAPACITY_LEVEL_LOW; elseif (capacity < 81) return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
}
staticint hidpp20_batterylevel_map_status_capacity(u8 data[3], int *capacity, int *next_capacity, int *level)
{ int status;
/* When discharging, we can rely on the device reported capacity. * For all other states the device reports 0 (unknown).
*/ switch (data[2]) { case0: /* discharging (in use) */
status = POWER_SUPPLY_STATUS_DISCHARGING;
*level = hidpp_map_battery_level(*capacity); break; case1: /* recharging */
status = POWER_SUPPLY_STATUS_CHARGING; break; case2: /* charge in final stage */
status = POWER_SUPPLY_STATUS_CHARGING; break; case3: /* charge complete */
status = POWER_SUPPLY_STATUS_FULL;
*level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
*capacity = 100; break; case4: /* recharging below optimal speed */
status = POWER_SUPPLY_STATUS_CHARGING; break; /* 5 = invalid battery type 6 = thermal error
7 = other charging error */ default:
status = POWER_SUPPLY_STATUS_NOT_CHARGING; break;
}
return status;
}
staticint hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp,
u8 feature_index, int *status, int *capacity, int *next_capacity, int *level)
{ struct hidpp_report response; int ret;
u8 *params = (u8 *)response.fap.params;
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS,
NULL, 0, &response); /* Ignore these intermittent errors */ if (ret == HIDPP_ERROR_RESOURCE_ERROR) return -EIO; if (ret > 0) {
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
__func__, ret); return -EPROTO;
} if (ret) return ret;
staticint hidpp20_query_battery_info_1000(struct hidpp_device *hidpp)
{ int ret; int status, capacity, next_capacity, level;
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_BATTERY_LEVEL_STATUS,
&hidpp->battery.feature_index); if (ret) return ret;
}
ret = hidpp20_batterylevel_get_battery_capacity(hidpp,
hidpp->battery.feature_index,
&status, &capacity,
&next_capacity, &level); if (ret) return ret;
ret = hidpp20_batterylevel_get_battery_info(hidpp,
hidpp->battery.feature_index); if (ret) return ret;
hidpp->battery.status = status;
hidpp->battery.capacity = capacity;
hidpp->battery.level = level; /* the capacity is only available when discharging or full */
hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
status == POWER_SUPPLY_STATUS_FULL;
if (report->fap.feature_index != hidpp->battery.feature_index ||
report->fap.funcindex_clientid != EVENT_BATTERY_LEVEL_STATUS_BROADCAST) return0;
status = hidpp20_batterylevel_map_status_capacity(report->fap.params,
&capacity,
&next_capacity,
&level);
/* the capacity is only available when discharging or full */
hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
status == POWER_SUPPLY_STATUS_FULL;
staticint hidpp20_battery_map_status_voltage(u8 data[3], int *voltage, int *level, int *charge_type)
{ int status;
long flags = (long) data[2];
*level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
if (flags & 0x80) switch (flags & 0x07) { case0:
status = POWER_SUPPLY_STATUS_CHARGING; break; case1:
status = POWER_SUPPLY_STATUS_FULL;
*level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; break; case2:
status = POWER_SUPPLY_STATUS_NOT_CHARGING; break; default:
status = POWER_SUPPLY_STATUS_UNKNOWN; break;
} else
status = POWER_SUPPLY_STATUS_DISCHARGING;
*charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD; if (test_bit(3, &flags)) {
*charge_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
} if (test_bit(4, &flags)) {
*charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
} if (test_bit(5, &flags)) {
*level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
}
*voltage = get_unaligned_be16(data);
return status;
}
staticint hidpp20_battery_get_battery_voltage(struct hidpp_device *hidpp,
u8 feature_index, int *status, int *voltage, int *level, int *charge_type)
{ struct hidpp_report response; int ret;
u8 *params = (u8 *)response.fap.params;
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE,
NULL, 0, &response);
if (ret > 0) {
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
__func__, ret); return -EPROTO;
} if (ret) return ret;
staticint hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
{ /* NB: This voltage curve doesn't necessarily map perfectly to all * devices that implement the BATTERY_VOLTAGE feature. This is because * there are a few devices that use different battery technology.
*/
if (unlikely(voltage < 3500 || voltage >= 5000))
hid_warn_once(hid_dev, "%s: possibly using the wrong voltage curve\n",
__func__);
for (i = 0; i < ARRAY_SIZE(voltages); i++) { if (voltage >= voltages[i]) return ARRAY_SIZE(voltages) - i;
}
return0;
}
staticint hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp)
{ int ret; int status, voltage, level, charge_type;
if (hidpp->battery.voltage_feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
&hidpp->battery.voltage_feature_index); if (ret) return ret;
}
ret = hidpp20_battery_get_battery_voltage(hidpp,
hidpp->battery.voltage_feature_index,
&status, &voltage, &level, &charge_type);
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS ||
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE) { /* we have already set the device capabilities, so let's skip */ return0;
}
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
CMD_UNIFIED_BATTERY_GET_CAPABILITIES,
NULL, 0, &response); /* Ignore these intermittent errors */ if (ret == HIDPP_ERROR_RESOURCE_ERROR) return -EIO; if (ret > 0) {
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
__func__, ret); return -EPROTO;
} if (ret) return ret;
/* * If the device supports state of charge (battery percentage) we won't * export the battery level information. there are 4 possible battery * levels and they all are optional, this means that the device might * not support any of them, we are just better off with the battery * percentage.
*/ if (params[1] & FLAG_UNIFIED_BATTERY_FLAGS_STATE_OF_CHARGE) {
hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_PERCENTAGE;
hidpp->battery.supported_levels_1004 = 0;
} else {
hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
hidpp->battery.supported_levels_1004 = params[0];
}
staticint hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
{ int ret;
u8 state_of_charge; int status, level;
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_UNIFIED_BATTERY,
&hidpp->battery.feature_index); if (ret) return ret;
}
ret = hidpp20_unifiedbattery_get_capabilities(hidpp,
hidpp->battery.feature_index); if (ret) return ret;
ret = hidpp20_unifiedbattery_get_status(hidpp,
hidpp->battery.feature_index,
&state_of_charge,
&status,
&level); if (ret) return ret;
staticint hidpp20_map_adc_measurement_1f20_capacity(struct hid_device *hid_dev, int voltage)
{ /* NB: This voltage curve doesn't necessarily map perfectly to all * devices that implement the ADC_MEASUREMENT feature. This is because * there are a few devices that use different battery technology. * * Adapted from: * https://github.com/Sapd/HeadsetControl/blob/acd972be0468e039b93aae81221f20a54d2d60f7/src/devices/logitech_g633_g933_935.c#L44-L52
*/ staticconstint voltages[100] = { 4030, 4024, 4018, 4011, 4003, 3994, 3985, 3975, 3963, 3951, 3937, 3922, 3907, 3893, 3880, 3868, 3857, 3846, 3837, 3828, 3820, 3812, 3805, 3798, 3791, 3785, 3779, 3773, 3768, 3762, 3757, 3752, 3747, 3742, 3738, 3733, 3729, 3724, 3720, 3716, 3712, 3708, 3704, 3700, 3696, 3692, 3688, 3685, 3681, 3677, 3674, 3670, 3667, 3663, 3660, 3657, 3653, 3650, 3646, 3643, 3640, 3637, 3633, 3630, 3627, 3624, 3620, 3617, 3614, 3611, 3608, 3604, 3601, 3598, 3595, 3592, 3589, 3585, 3582, 3579, 3576, 3573, 3569, 3566, 3563, 3560, 3556, 3553, 3550, 3546, 3543, 3539, 3536, 3532, 3529, 3525, 3499, 3466, 3433, 3399,
};
int i;
if (voltage == 0) return0;
if (unlikely(voltage < 3400 || voltage >= 5000))
hid_warn_once(hid_dev, "%s: possibly using the wrong voltage curve\n",
__func__);
for (i = 0; i < ARRAY_SIZE(voltages); i++) { if (voltage >= voltages[i]) return ARRAY_SIZE(voltages) - i;
}
return0;
}
staticint hidpp20_map_adc_measurement_1f20(u8 data[3], int *voltage)
{ int status;
u8 flags;
flags = data[2];
switch (flags) { case0x01:
status = POWER_SUPPLY_STATUS_DISCHARGING; break; case0x03:
status = POWER_SUPPLY_STATUS_CHARGING; break; case0x07:
status = POWER_SUPPLY_STATUS_FULL; break; case0x0F: default:
status = POWER_SUPPLY_STATUS_UNKNOWN; break;
}
*voltage = get_unaligned_be16(data);
dbg_hid("Parsed 1f20 data as flag 0x%02x voltage %dmV\n",
flags, *voltage);
return status;
}
/* Return value is whether the device is online */ staticbool hidpp20_get_adc_measurement_1f20(struct hidpp_device *hidpp,
u8 feature_index, int *status, int *voltage)
{ struct hidpp_report response; int ret;
u8 *params = (u8 *)response.fap.params;
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_SOLAR_KEYBOARD,
&hidpp->battery.solar_feature_index); if (ret) return ret;
}
ret = hidpp_send_fap_command_sync(hidpp,
hidpp->battery.solar_feature_index,
CMD_SOLAR_SET_LIGHT_MEASURE,
params, 2, &response); if (ret > 0) {
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
__func__, ret); return -EPROTO;
} if (ret) return ret;
if (report->fap.feature_index != hidpp->battery.solar_feature_index ||
!(function == EVENT_SOLAR_BATTERY_BROADCAST ||
function == EVENT_SOLAR_BATTERY_LIGHT_MEASURE ||
function == EVENT_SOLAR_CHECK_LIGHT_BUTTON)) return0;
capacity = report->fap.params[0];
switch (function) { case EVENT_SOLAR_BATTERY_LIGHT_MEASURE:
lux = (report->fap.params[1] << 8) | report->fap.params[2]; if (lux > 200)
status = POWER_SUPPLY_STATUS_CHARGING; else
status = POWER_SUPPLY_STATUS_DISCHARGING; break; case EVENT_SOLAR_CHECK_LIGHT_BUTTON: default: if (capacity < hidpp->battery.capacity)
status = POWER_SUPPLY_STATUS_DISCHARGING; else
status = POWER_SUPPLY_STATUS_CHARGING;
}
if (capacity == 100)
status = POWER_SUPPLY_STATUS_FULL;
hidpp->battery.online = true; if (capacity != hidpp->battery.capacity ||
status != hidpp->battery.status) {
hidpp->battery.capacity = capacity;
hidpp->battery.status = status; if (hidpp->battery.ps)
power_supply_changed(hidpp->battery.ps);
}
/* * send a set state command to the device by reading the current items->state * field. items is then filled with the current state.
*/ staticint hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp,
u8 feature_index, struct hidpp_touchpad_fw_items *items)
{ struct hidpp_report response; int ret;
u8 *params = (u8 *)response.fap.params;
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 1, &response);
if (ret > 0) {
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
__func__, ret); return -EPROTO;
} if (ret) return ret;
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response);
if (ret > 0) {
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
__func__, ret); return -EPROTO;
} if (ret) return ret;
raw_info->x_size = get_unaligned_be16(¶ms[0]);
raw_info->y_size = get_unaligned_be16(¶ms[2]);
raw_info->z_range = params[4];
raw_info->area_range = params[5];
raw_info->maxcontacts = params[7];
raw_info->origin = params[8]; /* res is given in unit per inch */
raw_info->res = get_unaligned_be16(¶ms[13]) * 2 / 51;
/* add slot number if needed */ switch (wd->effect_id) { case HIDPP_FF_EFFECTID_AUTOCENTER:
wd->params[0] = data->slot_autocenter; break; case HIDPP_FF_EFFECTID_NONE: /* leave slot as zero */ break; default: /* find current slot for effect */
wd->params[0] = hidpp_ff_find_effect(data, wd->effect_id); break;
}
/* send command and wait for reply */
ret = hidpp_send_fap_command_sync(data->hidpp, data->feature_index,
wd->command, wd->params, wd->size, &response);
if (ret) {
hid_err(data->hidpp->hid_dev, "Failed to send command to device!\n"); goto out;
}
/* parse return data */ switch (wd->command) { case HIDPP_FF_DOWNLOAD_EFFECT:
slot = response.fap.params[0]; if (slot > 0 && slot <= data->num_effects) { if (wd->effect_id >= 0) /* regular effect uploaded */
data->effect_ids[slot-1] = wd->effect_id; elseif (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER) /* autocenter spring uploaded */
data->slot_autocenter = slot;
} break; case HIDPP_FF_DESTROY_EFFECT: if (wd->effect_id >= 0) /* regular effect destroyed */
data->effect_ids[wd->params[0]-1] = -1; elseif (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER) /* autocenter spring destroyed */
data->slot_autocenter = 0; break; case HIDPP_FF_SET_GLOBAL_GAINS:
data->gain = (wd->params[0] << 8) + wd->params[1]; break; case HIDPP_FF_SET_APERTURE:
data->range = (wd->params[0] << 8) + wd->params[1]; break; default: /* no action needed */ break;
}
if (!hid_is_usb(hid)) {
hid_err(hid, "device is not USB\n"); return -ENODEV;
}
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;
if (!dev) {
hid_err(hid, "Struct input_dev not set!\n"); return -EINVAL;
}
/* Get firmware release */
udesc = &(hid_to_usb_dev(hid)->descriptor);
bcdDevice = le16_to_cpu(udesc->bcdDevice);
version = bcdDevice & 255;
/* Set supported force feedback capabilities */ for (j = 0; hidpp_ff_effects[j] >= 0; j++)
set_bit(hidpp_ff_effects[j], dev->ffbit); if (version > 1) for (j = 0; hidpp_ff_effects_v2[j] >= 0; j++)
set_bit(hidpp_ff_effects_v2[j], dev->ffbit);
error = input_ff_create(dev, num_slots);
if (error) {
hid_err(dev, "Failed to create FF device!\n"); return error;
} /* * Create a copy of passed data, so we can transfer memory * ownership to FF core
*/
data = kmemdup(data, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM;
data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL); if (!data->effect_ids) {
kfree(data); return -ENOMEM;
}
data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue"); if (!data->wq) {
kfree(data->effect_ids);
kfree(data); return -ENOMEM;
}
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
&wd->mt_feature_index); if (ret) /* means that the device is not powered up */ return ret;
ret = hidpp_touchpad_get_raw_info(hidpp, wd->mt_feature_index,
&raw_info); if (ret) return ret;
/* * Logitech M560 protocol overview * * The Logitech M560 mouse, is designed for windows 8. When the middle and/or * the sides buttons are pressed, it sends some keyboard keys events * instead of buttons ones. * To complicate things further, the middle button keys sequence * is different from the odd press and the even press. * * forward button -> Super_R * backward button -> Super_L+'d' (press only) * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only) * 2nd time: left-click (press only) * NB: press-only means that when the button is pressed, the * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated * together sequentially; instead when the button is released, no event is * generated ! * * With the command * 10<xx>0a 3500af03 (where <xx> is the mouse id), * the mouse reacts differently: * - it never sends a keyboard key event * - for the three mouse button it sends: * middle button press 11<xx>0a 3500af00... * side 1 button (forward) press 11<xx>0a 3500b000... * side 2 button (backward) press 11<xx>0a 3500ae00... * middle/side1/side2 button release 11<xx>0a 35000000...
*/
/* * The Logitech K400 keyboard has an embedded touchpad which is seen * as a mouse from the OS point of view. There is a hardware shortcut to disable * tap-to-click but the setting is not remembered accross reset, annoying some * users. * * We can toggle this feature from the host by using the feature 0x6010: * Touchpad FW items
*/
if (!k400->feature_index) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
&k400->feature_index); if (ret) /* means that the device is not powered up */ return ret;
}
ret = hidpp_touchpad_fw_items_set(hidpp, k400->feature_index, &items); if (ret) return ret;
/* ------------------------------------------------------------------------- */ /* Logitech G920 Driving Force Racing Wheel for Xbox One */ /* ------------------------------------------------------------------------- */
/* Find feature and store for later use */
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
&data->feature_index); if (ret) return ret;
/* Read number of slots available in device */
ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
HIDPP_FF_GET_INFO,
NULL, 0,
&response); if (ret) { if (ret < 0) return ret;
hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", __func__, ret); return -EPROTO;
}
/* reset all forces */
ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
HIDPP_FF_RESET_ALL,
NULL, 0,
&response); if (ret)
hid_warn(hidpp->hid_dev, "Failed to reset all forces!\n");
ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
HIDPP_FF_GET_APERTURE,
NULL, 0,
&response); if (ret) {
hid_warn(hidpp->hid_dev, "Failed to read range from device!\n");
}
data->range = ret ? 900 : get_unaligned_be16(&response.fap.params[0]);
/* Read the current gain values */
ret = hidpp_send_fap_command_sync(hidpp, data->feature_index,
HIDPP_FF_GET_GLOBAL_GAINS,
NULL, 0,
&response); if (ret)
hid_warn(hidpp->hid_dev, "Failed to read gain values from device!\n");
data->gain = ret ? 0xffff : get_unaligned_be16(&response.fap.params[0]);
/* ignore boost value at response.fap.params[2] */
return g920_ff_set_autocenter(hidpp, data);
}
/* -------------------------------------------------------------------------- */ /* Logitech Dinovo Mini keyboard with builtin touchpad */ /* -------------------------------------------------------------------------- */ #define DINOVO_MINI_PRODUCT_ID 0xb30c
/* -------------------------------------------------------------------------- */ /* HID++1.0 mice which use HID++ reports for extra mouse buttons */ /* -------------------------------------------------------------------------- */ staticint hidpp10_extra_mouse_buttons_connect(struct hidpp_device *hidpp)
{ return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0,
HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT,
HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT);
}
staticint hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
u8 *data, int size)
{ int i;
if (!hidpp->input) return -EINVAL;
if (size < 7) return0;
if (data[0] != REPORT_ID_HIDPP_SHORT ||
data[2] != HIDPP_SUB_ID_MOUSE_EXTRA_BTNS) return0;
/* * Buttons are either delivered through the regular mouse report *or* * through the extra buttons report. At least for button 6 how it is * delivered differs per receiver firmware version. Even receivers with * the same usb-id show different behavior, so we handle both cases.
*/ for (i = 0; i < 8; i++)
input_report_key(hidpp->input, BTN_MOUSE + i,
(data[3] & (1 << i)));
/* Some mice report events on button 9+, use BTN_MISC */ for (i = 0; i < 8; i++)
input_report_key(hidpp->input, BTN_MISC + i,
(data[4] & (1 << i)));
input_sync(hidpp->input); return1;
}
staticvoid hidpp10_extra_mouse_buttons_populate_input( struct hidpp_device *hidpp, struct input_dev *input_dev)
{ /* BTN_MOUSE - BTN_MOUSE+7 are set already by the descriptor */
__set_bit(BTN_0, input_dev->keybit);
__set_bit(BTN_1, input_dev->keybit);
__set_bit(BTN_2, input_dev->keybit);
__set_bit(BTN_3, input_dev->keybit);
__set_bit(BTN_4, input_dev->keybit);
__set_bit(BTN_5, input_dev->keybit);
__set_bit(BTN_6, input_dev->keybit);
__set_bit(BTN_7, input_dev->keybit);
}
/* -------------------------------------------------------------------------- */ /* HID++1.0 kbds which only report 0x10xx consumer usages through sub-id 0x03 */ /* -------------------------------------------------------------------------- */
if (data[0] != REPORT_ID_HIDPP_SHORT ||
data[2] != HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS) return0;
/* * Build a normal consumer report (3) out of the data, this detour * is necessary to get some keyboards to report their 0x10xx usages.
*/
consumer_report[0] = 0x03;
memcpy(&consumer_report[1], &data[3], 4); /* We are called from atomic context */
hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT,
consumer_report, 5, 1);
/* For 27 MHz keyboards the quirk gets set after hid_parse. */ if (hdev->group == HID_GROUP_LOGITECH_27MHZ_DEVICE ||
(hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS))
rdesc = hidpp10_consumer_keys_report_fixup(hidpp, rdesc, rsize);
staticint hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, int size)
{ struct hidpp_report *question = hidpp->send_receive_buf; struct hidpp_report *answer = hidpp->send_receive_buf; struct hidpp_report *report = (struct hidpp_report *)data; int ret; int last_online;
/* * If the mutex is locked then we have a pending answer from a * previously sent command.
*/ if (unlikely(mutex_is_locked(&hidpp->send_mutex))) { /* * Check for a correct hidpp20 answer or the corresponding * error
*/ if (hidpp_match_answer(question, report) ||
hidpp_match_error(question, report)) {
*answer = *report;
hidpp->answer_available = true;
wake_up(&hidpp->wait); /* * This was an answer to a command that this driver sent * We return 1 to hid-core to avoid forwarding the * command upstream as it has been treated by the driver
*/
return1;
}
}
if (unlikely(hidpp_report_is_connect_event(hidpp, report))) { if (schedule_work(&hidpp->work) == 0)
dbg_hid("%s: connect event already queued\n", __func__); return1;
}
if (hidpp->hid_dev->group == HID_GROUP_LOGITECH_27MHZ_DEVICE &&
data[0] == REPORT_ID_HIDPP_SHORT &&
data[2] == HIDPP_SUB_ID_USER_IFACE_EVENT &&
(data[3] & HIDPP_USER_IFACE_EVENT_ENCRYPTION_KEY_LOST)) {
dev_err_ratelimited(&hidpp->hid_dev->dev, "Error the keyboard's wireless encryption key has been lost, your keyboard will not work unless you re-configure encryption.\n");
dev_err_ratelimited(&hidpp->hid_dev->dev, "See: https://gitlab.freedesktop.org/jwrdegoede/logitech-27mhz-keyboard-encryption-setup/\n");
}
last_online = hidpp->battery.online; if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
ret = hidpp20_battery_event_1000(hidpp, data, size); if (ret != 0) return ret;
ret = hidpp20_battery_event_1004(hidpp, data, size); if (ret != 0) return ret;
ret = hidpp_solar_battery_event(hidpp, data, size); if (ret != 0) return ret;
ret = hidpp20_battery_voltage_event(hidpp, data, size); if (ret != 0) return ret;
ret = hidpp20_adc_measurement_event_1f20(hidpp, data, size); if (ret != 0) return ret;
}
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
ret = hidpp10_battery_event(hidpp, data, size); if (ret != 0) return ret;
}
if (hidpp->quirks & HIDPP_QUIRK_RESET_HI_RES_SCROLL) { if (last_online == 0 && hidpp->battery.online == 1)
schedule_work(&hidpp->reset_hi_res_work);
}
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) {
ret = hidpp10_wheel_raw_event(hidpp, data, size); if (ret != 0) return ret;
}
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) {
ret = hidpp10_extra_mouse_buttons_raw_event(hidpp, data, size); if (ret != 0) return ret;
}
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
ret = hidpp10_consumer_keys_raw_event(hidpp, data, size); if (ret != 0) return ret;
}
return0;
}
staticint hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{ struct hidpp_device *hidpp = hid_get_drvdata(hdev); int ret = 0;
if (!hidpp) return0;
/* Generic HID++ processing. */ switch (data[0]) { case REPORT_ID_HIDPP_VERY_LONG: if (size != hidpp->very_long_report_length) {
hid_err(hdev, "received hid++ report of bad size (%d)",
size); return1;
}
ret = hidpp_raw_hidpp_event(hidpp, data, size); break; case REPORT_ID_HIDPP_LONG: if (size != HIDPP_REPORT_LONG_LENGTH) {
hid_err(hdev, "received hid++ report of bad size (%d)",
size); return1;
}
ret = hidpp_raw_hidpp_event(hidpp, data, size); break; case REPORT_ID_HIDPP_SHORT: if (size != HIDPP_REPORT_SHORT_LENGTH) {
hid_err(hdev, "received hid++ report of bad size (%d)",
size); return1;
}
ret = hidpp_raw_hidpp_event(hidpp, data, size); break;
}
/* If no report is available for further processing, skip calling
* raw_event of subclasses. */ if (ret != 0) return ret;
staticint hidpp_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value)
{ /* This function will only be called for scroll events, due to the * restriction imposed in hidpp_usages.
*/ struct hidpp_device *hidpp = hid_get_drvdata(hdev); struct hidpp_scroll_counter *counter;
if (!hidpp) return0;
counter = &hidpp->vertical_wheel_counter; /* A scroll event may occur before the multiplier has been retrieved or * the input device set, or high-res scroll enabling may fail. In such * cases we must return early (falling back to default behaviour) to * avoid a crash in hidpp_scroll_counter_handle_scroll.
*/ if (!(hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL)
|| value == 0 || hidpp->input == NULL
|| counter->wheel_multiplier == 0) return0;
if (hidpp->protocol_major >= 2) { if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
ret = hidpp_solar_request_battery_event(hidpp); else { /* we only support one battery feature right now, so let's first check the ones that support battery level first
and leave voltage for last */
ret = hidpp20_query_battery_info_1000(hidpp); if (ret)
ret = hidpp20_query_battery_info_1004(hidpp); if (ret)
ret = hidpp20_query_battery_voltage_info(hidpp); if (ret)
ret = hidpp20_query_adc_measurement_info_1f20(hidpp);
}
if (ret) return ret;
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY;
} else {
ret = hidpp10_query_battery_status(hidpp); if (ret) {
ret = hidpp10_query_battery_mileage(hidpp); if (ret) return -ENOENT;
hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;
} else {
hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
}
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_BATTERY;
}
battery_props = devm_kmemdup(&hidpp->hid_dev->dev,
hidpp_battery_props, sizeof(hidpp_battery_props),
GFP_KERNEL); if (!battery_props) return -ENOMEM;
/* Get name + serial for USB and Bluetooth HID++ devices */ staticvoid hidpp_non_unifying_init(struct hidpp_device *hidpp)
{ struct hid_device *hdev = hidpp->hid_dev; char *name;
/* Bluetooth devices already have their serialnr set */ if (hid_is_usb(hdev))
hidpp_serial_init(hidpp);
name = hidpp_get_device_name(hidpp); if (name) {
dbg_hid("HID++: Got name: %s\n", name);
snprintf(hdev->name, sizeof(hdev->name), "%s", name);
kfree(name);
}
}
/* Get device version to check if it is connected */
ret = hidpp_root_get_protocol_version(hidpp); if (ret) {
hid_dbg(hidpp->hid_dev, "Disconnected\n"); if (hidpp->battery.ps) {
hidpp->battery.online = false;
hidpp->battery.status = POWER_SUPPLY_STATUS_UNKNOWN;
hidpp->battery.level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
power_supply_changed(hidpp->battery.ps);
} return;
}
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
ret = wtp_connect(hdev); if (ret) return;
} elseif (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
ret = m560_send_config_command(hdev); if (ret) return;
} elseif (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
ret = k400_connect(hdev); if (ret) return;
}
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) {
ret = hidpp10_wheel_connect(hidpp); if (ret) return;
}
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) {
ret = hidpp10_extra_mouse_buttons_connect(hidpp); if (ret) return;
}
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
ret = hidpp10_consumer_keys_connect(hidpp); if (ret) return;
}
if (hidpp->protocol_major >= 2) {
u8 feature_index;
if (!hidpp_get_wireless_feature_index(hidpp, &feature_index))
hidpp->wireless_feature_index = feature_index;
}
if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) {
name = hidpp_get_device_name(hidpp); if (name) {
devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name);
kfree(name); if (!devm_name) return;
hidpp->name = devm_name;
}
}
hidpp_initialize_battery(hidpp); if (!hid_is_usb(hidpp->hid_dev))
hidpp_initialize_hires_scroll(hidpp);
/* forward current battery state */ if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
hidpp10_enable_battery_reporting(hidpp); if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE)
hidpp10_query_battery_mileage(hidpp); else
hidpp10_query_battery_status(hidpp);
} elseif (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) { if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
hidpp20_query_battery_voltage_info(hidpp); elseif (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY)
hidpp20_query_battery_info_1004(hidpp); elseif (hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
hidpp20_query_adc_measurement_info_1f20(hidpp); else
hidpp20_query_battery_info_1000(hidpp);
} if (hidpp->battery.ps)
power_supply_changed(hidpp->battery.ps);
if (hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL)
hi_res_scroll_enable(hidpp);
if (!(hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) || hidpp->delayed_input) /* if the input nodes are already created, we can stop now */ return;
input = hidpp_allocate_input(hdev); if (!input) {
hid_err(hdev, "cannot allocate new input device: %d\n", ret); return;
}
hidpp_populate_input(hidpp, input);
ret = input_register_device(input); if (ret) {
input_free_device(input); return;
}
/* report_fixup needs drvdata to be set before we call hid_parse */
hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL); if (!hidpp) return -ENOMEM;
/* indicates we are handling the battery properties in the kernel */
ret = sysfs_create_group(&hdev->dev.kobj, &ps_attribute_group); if (ret)
hid_warn(hdev, "Cannot allocate sysfs group for %s\n",
hdev->name);
/* * First call hid_hw_start(hdev, 0) to allow IO without connecting any * hid subdrivers (hid-input, hidraw). This allows retrieving the dev's * name and serial number and store these in hdev->name and hdev->uniq, * before the hid-input and hidraw drivers expose these to userspace.
*/
ret = hid_hw_start(hdev, 0); if (ret) {
hid_err(hdev, "hw start failed\n"); goto hid_hw_start_fail;
}
ret = hid_hw_open(hdev); if (ret < 0) {
dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n",
__func__, ret); goto hid_hw_open_fail;
}
/* Get name + serial, store in hdev->name + hdev->uniq */ if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
hidpp_unifying_init(hidpp); else
hidpp_non_unifying_init(hidpp);
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
connect_mask &= ~HID_CONNECT_HIDINPUT;
/* Now export the actual inputs and hidraw nodes to the world */
hid_device_io_stop(hdev);
ret = hid_connect(hdev, connect_mask); if (ret) {
hid_err(hdev, "%s:hid_connect returned error %d\n", __func__, ret); goto hid_hw_init_fail;
}
/* Check for connected devices now that incoming packets will not be disabled again */
hid_device_io_start(hdev);
schedule_work(&hidpp->work);
flush_work(&hidpp->work);
if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { struct hidpp_ff_private_data data;
ret = g920_get_config(hidpp, &data); if (!ret)
ret = hidpp_ff_init(hidpp, &data);
if (ret)
hid_warn(hidpp->hid_dev, "Unable to initialize force feedback support, errno %d\n",
ret);
}
/* * This relies on logi_dj_ll_close() being a no-op so that DJ connection * events will still be received.
*/
hid_hw_close(hdev); return ret;
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.