Merge branch 'for-6.4/logitech-hidpp' into for-linus
- support for ADC measurement (Bastien Nocera) - support for Logitech G935 (Bastien Nocera)
This commit is contained in:
commit
0549fbac40
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
@ -947,6 +969,54 @@ print_version:
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* 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",
|
||||
|
@ -1745,6 +1813,164 @@ static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* 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 },
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue