summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJiri Kosina <jkosina@suse.cz>2023-04-26 23:57:40 +0300
committerJiri Kosina <jkosina@suse.cz>2023-04-26 23:57:40 +0300
commit0549fbac401ced4adea4fca44baf8bab7ebcfc74 (patch)
treea2c496c26fcf53f55653d3f3b84103a5845a39fe
parentd411b5aa1f49c065dfe23b7a7e72ee56f2677331 (diff)
parent539adfedbd2d5cda2f9e2d83b35b364834b67d58 (diff)
downloadlinux-0549fbac401ced4adea4fca44baf8bab7ebcfc74.tar.xz
Merge branch 'for-6.4/logitech-hidpp' into for-linus
- support for ADC measurement (Bastien Nocera) - support for Logitech G935 (Bastien Nocera)
-rw-r--r--Documentation/ABI/testing/sysfs-bus-usb17
-rw-r--r--drivers/hid/hid-logitech-hidpp.c256
-rw-r--r--drivers/usb/core/message.c40
-rw-r--r--drivers/usb/core/sysfs.c50
-rw-r--r--drivers/usb/core/usb.h1
-rw-r--r--include/linux/usb.h16
6 files changed, 373 insertions, 7 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb
index 545c2dd97ed0..cb172db41b34 100644
--- a/Documentation/ABI/testing/sysfs-bus-usb
+++ b/Documentation/ABI/testing/sysfs-bus-usb
@@ -166,6 +166,23 @@ Description:
The file will be present for all speeds of USB devices, and will
always read "no" for USB 1.1 and USB 2.0 devices.
+What: /sys/bus/usb/devices/<INTERFACE>/wireless_status
+Date: February 2023
+Contact: Bastien Nocera <hadess@hadess.net>
+Description:
+ Some USB devices use a USB receiver dongle to communicate
+ wirelessly with their device using proprietary protocols. This
+ attribute allows user-space to know whether the device is
+ connected to its receiver dongle, and, for example, consider
+ the device to be absent when choosing whether to show the
+ device's battery, show a headset in a list of outputs, or show
+ an on-screen keyboard if the only wireless keyboard is
+ turned off.
+ This attribute is not to be used to replace protocol specific
+ statuses available in WWAN, WLAN/Wi-Fi, Bluetooth, etc.
+ If the device does not use a receiver dongle with a wireless
+ device, then this attribute will not exist.
+
What: /sys/bus/usb/devices/.../<hub_interface>/port<X>
Date: August 2012
Contact: Lan Tianyu <tianyu.lan@intel.com>
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 5fc88a063297..0fcfd85fea0f 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -74,6 +74,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(27)
#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(28)
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(29)
+#define HIDPP_QUIRK_WIRELESS_STATUS BIT(30)
/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -94,6 +95,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL BIT(7)
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL BIT(8)
#define HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL BIT(9)
+#define HIDPP_CAPABILITY_ADC_MEASUREMENT BIT(10)
#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
@@ -145,6 +147,7 @@ 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];
@@ -471,6 +474,26 @@ static void hidpp_prefix_name(char **name, int name_length)
*name = new_name;
}
+/*
+ * Updates the USB wireless_status based on whether the headset
+ * is turned on and reachable.
+ */
+static void 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;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ usb_set_wireless_status(intf, hidpp->battery.online ?
+ USB_WIRELESS_STATUS_CONNECTED :
+ USB_WIRELESS_STATUS_DISCONNECTED);
+}
+
/**
* hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll
* events given a high-resolution wheel
@@ -853,8 +876,7 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp)
if (ret)
return ret;
- snprintf(hdev->uniq, sizeof(hdev->uniq), "%04x-%4phD",
- hdev->product, &serial);
+ snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq);
name = hidpp_unifying_get_name(hidpp);
@@ -948,6 +970,54 @@ print_version:
}
/* -------------------------------------------------------------------------- */
+/* 0x0003: Device Information */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_DEVICE_INFORMATION 0x0003
+
+#define CMD_GET_DEVICE_INFO 0x00
+
+static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
+{
+ struct hidpp_report response;
+ u8 feature_type;
+ u8 feature_index;
+ int ret;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION,
+ &feature_index,
+ &feature_type);
+ if (ret)
+ return ret;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+ CMD_GET_DEVICE_INFO,
+ NULL, 0, &response);
+ if (ret)
+ return ret;
+
+ /* See hidpp_unifying_get_serial() */
+ *serial = *((u32 *)&response.rap.params[1]);
+ return 0;
+}
+
+static int hidpp_serial_init(struct hidpp_device *hidpp)
+{
+ struct hid_device *hdev = hidpp->hid_dev;
+ u32 serial;
+ int ret;
+
+ ret = hidpp_get_serial(hidpp, &serial);
+ if (ret)
+ return ret;
+
+ snprintf(hdev->uniq, sizeof(hdev->uniq), "%4phD", &serial);
+ dbg_hid("HID++ DeviceInformation: Got serial: %s\n", hdev->uniq);
+
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
/* 0x0005: GetDeviceNameType */
/* -------------------------------------------------------------------------- */
@@ -1357,7 +1427,7 @@ static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
* there are a few devices that use different battery technology.
*/
- static const int voltages[] = {
+ static const int voltages[100] = {
4186, 4156, 4143, 4133, 4122, 4113, 4103, 4094, 4086, 4075,
4067, 4059, 4051, 4043, 4035, 4027, 4019, 4011, 4003, 3997,
3989, 3983, 3976, 3969, 3961, 3955, 3949, 3942, 3935, 3929,
@@ -1372,8 +1442,6 @@ static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
int i;
- BUILD_BUG_ON(ARRAY_SIZE(voltages) != 100);
-
if (unlikely(voltage < 3500 || voltage >= 5000))
hid_warn_once(hid_dev,
"%s: possibly using the wrong voltage curve\n",
@@ -1746,6 +1814,164 @@ static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp)
}
/* -------------------------------------------------------------------------- */
+/* 0x1f20: ADC measurement */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_ADC_MEASUREMENT 0x1f20
+
+#define CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT 0x00
+
+#define EVENT_ADC_MEASUREMENT_STATUS_BROADCAST 0x00
+
+static int 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
+ */
+ static const int 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)
+ return 0;
+
+ 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;
+ }
+
+ return 0;
+}
+
+static int hidpp20_map_adc_measurement_1f20(u8 data[3], int *voltage)
+{
+ int status;
+ u8 flags;
+
+ flags = data[2];
+
+ switch (flags) {
+ case 0x01:
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case 0x03:
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case 0x07:
+ status = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case 0x0F:
+ 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 */
+static bool 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;
+
+ *status = POWER_SUPPLY_STATUS_UNKNOWN;
+ *voltage = 0;
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+ CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT,
+ NULL, 0, &response);
+
+ if (ret > 0) {
+ hid_dbg(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+ __func__, ret);
+ return false;
+ }
+
+ *status = hidpp20_map_adc_measurement_1f20(params, voltage);
+ return true;
+}
+
+static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
+{
+ u8 feature_type;
+
+ if (hidpp->battery.adc_measurement_feature_index == 0xff) {
+ int ret;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
+ &hidpp->battery.adc_measurement_feature_index,
+ &feature_type);
+ if (ret)
+ return ret;
+
+ hidpp->capabilities |= HIDPP_CAPABILITY_ADC_MEASUREMENT;
+ }
+
+ hidpp->battery.online = hidpp20_get_adc_measurement_1f20(hidpp,
+ hidpp->battery.adc_measurement_feature_index,
+ &hidpp->battery.status,
+ &hidpp->battery.voltage);
+ hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev,
+ hidpp->battery.voltage);
+ hidpp_update_usb_wireless_status(hidpp);
+
+ return 0;
+}
+
+static int hidpp20_adc_measurement_event_1f20(struct hidpp_device *hidpp,
+ u8 *data, int size)
+{
+ struct hidpp_report *report = (struct hidpp_report *)data;
+ int status, voltage;
+
+ if (report->fap.feature_index != hidpp->battery.adc_measurement_feature_index ||
+ report->fap.funcindex_clientid != EVENT_ADC_MEASUREMENT_STATUS_BROADCAST)
+ return 0;
+
+ status = hidpp20_map_adc_measurement_1f20(report->fap.params, &voltage);
+
+ hidpp->battery.online = status != POWER_SUPPLY_STATUS_UNKNOWN;
+
+ if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) {
+ hidpp->battery.status = status;
+ hidpp->battery.voltage = voltage;
+ hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev, voltage);
+ if (hidpp->battery.ps)
+ power_supply_changed(hidpp->battery.ps);
+ hidpp_update_usb_wireless_status(hidpp);
+ }
+ return 0;
+}
+
+/* -------------------------------------------------------------------------- */
/* 0x2120: Hi-resolution scrolling */
/* -------------------------------------------------------------------------- */
@@ -3663,6 +3889,9 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
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) {
@@ -3786,6 +4015,7 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
hidpp->battery.feature_index = 0xff;
hidpp->battery.solar_feature_index = 0xff;
hidpp->battery.voltage_feature_index = 0xff;
+ hidpp->battery.adc_measurement_feature_index = 0xff;
if (hidpp->protocol_major >= 2) {
if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
@@ -3799,6 +4029,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
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)
@@ -3828,7 +4060,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE ||
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE ||
- hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
+ hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
+ hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
battery_props[num_battery_props++] =
POWER_SUPPLY_PROP_CAPACITY;
@@ -3836,7 +4069,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
battery_props[num_battery_props++] =
POWER_SUPPLY_PROP_CAPACITY_LEVEL;
- if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
+ if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
+ hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
battery_props[num_battery_props++] =
POWER_SUPPLY_PROP_VOLTAGE_NOW;
@@ -4009,6 +4243,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
hidpp20_query_battery_voltage_info(hidpp);
else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY)
hidpp20_query_battery_info_1004(hidpp);
+ else if (hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
+ hidpp20_query_adc_measurement_info_1f20(hidpp);
else
hidpp20_query_battery_info_1000(hidpp);
}
@@ -4210,6 +4446,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (hidpp->quirks & HIDPP_QUIRK_UNIFYING)
hidpp_unifying_init(hidpp);
+ else if (hid_is_usb(hidpp->hid_dev))
+ hidpp_serial_init(hidpp);
connected = hidpp_root_get_protocol_version(hidpp) == 0;
atomic_set(&hidpp->connected, connected);
@@ -4379,6 +4617,10 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* Logitech G Pro Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
+ { /* G935 Gaming Headset */
+ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87),
+ .driver_data = HIDPP_QUIRK_WIRELESS_STATUS },
+
{ /* MX5000 keyboard over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305),
.driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index cc404bb7e8f7..b5811620f1de 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1908,6 +1908,45 @@ static void __usb_queue_reset_device(struct work_struct *ws)
usb_put_intf(iface); /* Undo _get_ in usb_queue_reset_device() */
}
+/*
+ * Internal function to set the wireless_status sysfs attribute
+ * See usb_set_wireless_status() for more details
+ */
+static void __usb_wireless_status_intf(struct work_struct *ws)
+{
+ struct usb_interface *iface =
+ container_of(ws, struct usb_interface, wireless_status_work);
+
+ device_lock(iface->dev.parent);
+ if (iface->sysfs_files_created)
+ usb_update_wireless_status_attr(iface);
+ device_unlock(iface->dev.parent);
+ usb_put_intf(iface); /* Undo _get_ in usb_set_wireless_status() */
+}
+
+/**
+ * usb_set_wireless_status - sets the wireless_status struct member
+ * @iface: the interface to modify
+ * @status: the new wireless status
+ *
+ * Set the wireless_status struct member to the new value, and emit
+ * sysfs changes as necessary.
+ *
+ * Returns: 0 on success, -EALREADY if already set.
+ */
+int usb_set_wireless_status(struct usb_interface *iface,
+ enum usb_wireless_status status)
+{
+ if (iface->wireless_status == status)
+ return -EALREADY;
+
+ usb_get_intf(iface);
+ iface->wireless_status = status;
+ schedule_work(&iface->wireless_status_work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_set_wireless_status);
/*
* usb_set_configuration - Makes a particular device setting be current
@@ -2100,6 +2139,7 @@ free_interfaces:
intf->dev.type = &usb_if_device_type;
intf->dev.groups = usb_interface_groups;
INIT_WORK(&intf->reset_ws, __usb_queue_reset_device);
+ INIT_WORK(&intf->wireless_status_work, __usb_wireless_status_intf);
intf->minor = -1;
device_initialize(&intf->dev);
pm_runtime_no_callbacks(&intf->dev);
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c
index b63f78e48c74..323dc02becbe 100644
--- a/drivers/usb/core/sysfs.c
+++ b/drivers/usb/core/sysfs.c
@@ -1227,9 +1227,59 @@ static const struct attribute_group intf_assoc_attr_grp = {
.is_visible = intf_assoc_attrs_are_visible,
};
+static ssize_t wireless_status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf;
+
+ intf = to_usb_interface(dev);
+ if (intf->wireless_status == USB_WIRELESS_STATUS_DISCONNECTED)
+ return sysfs_emit(buf, "%s\n", "disconnected");
+ return sysfs_emit(buf, "%s\n", "connected");
+}
+static DEVICE_ATTR_RO(wireless_status);
+
+static struct attribute *intf_wireless_status_attrs[] = {
+ &dev_attr_wireless_status.attr,
+ NULL
+};
+
+static umode_t intf_wireless_status_attr_is_visible(struct kobject *kobj,
+ struct attribute *a, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct usb_interface *intf = to_usb_interface(dev);
+
+ if (a != &dev_attr_wireless_status.attr ||
+ intf->wireless_status != USB_WIRELESS_STATUS_NA)
+ return a->mode;
+ return 0;
+}
+
+static const struct attribute_group intf_wireless_status_attr_grp = {
+ .attrs = intf_wireless_status_attrs,
+ .is_visible = intf_wireless_status_attr_is_visible,
+};
+
+int usb_update_wireless_status_attr(struct usb_interface *intf)
+{
+ struct device *dev = &intf->dev;
+ int ret;
+
+ ret = sysfs_update_group(&dev->kobj, &intf_wireless_status_attr_grp);
+ if (ret < 0)
+ return ret;
+
+ sysfs_notify(&dev->kobj, NULL, "wireless_status");
+ kobject_uevent(&dev->kobj, KOBJ_CHANGE);
+
+ return 0;
+}
+
const struct attribute_group *usb_interface_groups[] = {
&intf_attr_grp,
&intf_assoc_attr_grp,
+ &intf_wireless_status_attr_grp,
NULL
};
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 0eac7d4285d1..3f14e15f07f6 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -15,6 +15,7 @@ extern int usb_create_sysfs_dev_files(struct usb_device *dev);
extern void usb_remove_sysfs_dev_files(struct usb_device *dev);
extern void usb_create_sysfs_intf_files(struct usb_interface *intf);
extern void usb_remove_sysfs_intf_files(struct usb_interface *intf);
+extern int usb_update_wireless_status_attr(struct usb_interface *intf);
extern int usb_create_ep_devs(struct device *parent,
struct usb_host_endpoint *endpoint,
struct usb_device *udev);
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 9642ee02d713..0a81e0f5beb4 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -170,6 +170,12 @@ usb_find_last_int_out_endpoint(struct usb_host_interface *alt,
return usb_find_common_endpoints_reverse(alt, NULL, NULL, NULL, int_out);
}
+enum usb_wireless_status {
+ USB_WIRELESS_STATUS_NA = 0,
+ USB_WIRELESS_STATUS_DISCONNECTED,
+ USB_WIRELESS_STATUS_CONNECTED,
+};
+
/**
* struct usb_interface - what usb device drivers talk to
* @altsetting: array of interface structures, one for each alternate
@@ -197,6 +203,10 @@ usb_find_last_int_out_endpoint(struct usb_host_interface *alt,
* following a reset or suspend operation it doesn't support.
* @authorized: This allows to (de)authorize individual interfaces instead
* a whole device in contrast to the device authorization.
+ * @wireless_status: if the USB device uses a receiver/emitter combo, whether
+ * the emitter is connected.
+ * @wireless_status_work: Used for scheduling wireless status changes
+ * from atomic context.
* @dev: driver model's view of this device
* @usb_dev: if an interface is bound to the USB major, this will point
* to the sysfs representation for that device.
@@ -253,6 +263,8 @@ struct usb_interface {
unsigned needs_binding:1; /* needs delayed unbind/rebind */
unsigned resetting_device:1; /* true: bandwidth alloc after reset */
unsigned authorized:1; /* used for interface authorization */
+ enum usb_wireless_status wireless_status;
+ struct work_struct wireless_status_work;
struct device dev; /* interface specific device info */
struct device *usb_dev;
@@ -887,6 +899,10 @@ static inline int usb_interface_claimed(struct usb_interface *iface)
extern void usb_driver_release_interface(struct usb_driver *driver,
struct usb_interface *iface);
+
+int usb_set_wireless_status(struct usb_interface *iface,
+ enum usb_wireless_status status);
+
const struct usb_device_id *usb_match_id(struct usb_interface *interface,
const struct usb_device_id *id);
extern int usb_match_one_id(struct usb_interface *interface,