Skip to content

Commit c361982

Browse files
hadessbentiss
authored andcommitted
HID: logitech-hidpp: Add support for ADC measurement feature
This is used in a number of Logitech headsets to report the voltage of the battery. Tested on a Logitech G935. Signed-off-by: Bastien Nocera <hadess@hadess.net> BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=216483 Link: https://lore.kernel.org/r/20230302105555.51417-2-hadess@hadess.net Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
1 parent e013876 commit c361982

1 file changed

Lines changed: 170 additions & 2 deletions

File tree

drivers/hid/hid-logitech-hidpp.c

Lines changed: 170 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
9494
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL BIT(7)
9595
#define HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL BIT(8)
9696
#define HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL BIT(9)
97+
#define HIDPP_CAPABILITY_ADC_MEASUREMENT BIT(10)
9798

9899
#define lg_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
99100

@@ -145,6 +146,7 @@ struct hidpp_battery {
145146
u8 feature_index;
146147
u8 solar_feature_index;
147148
u8 voltage_feature_index;
149+
u8 adc_measurement_feature_index;
148150
struct power_supply_desc desc;
149151
struct power_supply *ps;
150152
char name[64];
@@ -1790,6 +1792,162 @@ static int hidpp_set_wireless_feature_index(struct hidpp_device *hidpp)
17901792
return ret;
17911793
}
17921794

1795+
/* -------------------------------------------------------------------------- */
1796+
/* 0x1f20: ADC measurement */
1797+
/* -------------------------------------------------------------------------- */
1798+
1799+
#define HIDPP_PAGE_ADC_MEASUREMENT 0x1f20
1800+
1801+
#define CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT 0x00
1802+
1803+
#define EVENT_ADC_MEASUREMENT_STATUS_BROADCAST 0x00
1804+
1805+
static int hidpp20_map_adc_measurement_1f20_capacity(struct hid_device *hid_dev, int voltage)
1806+
{
1807+
/* NB: This voltage curve doesn't necessarily map perfectly to all
1808+
* devices that implement the ADC_MEASUREMENT feature. This is because
1809+
* there are a few devices that use different battery technology.
1810+
*
1811+
* Adapted from:
1812+
* https://github.com/Sapd/HeadsetControl/blob/acd972be0468e039b93aae81221f20a54d2d60f7/src/devices/logitech_g633_g933_935.c#L44-L52
1813+
*/
1814+
static const int voltages[100] = {
1815+
4030, 4024, 4018, 4011, 4003, 3994, 3985, 3975, 3963, 3951,
1816+
3937, 3922, 3907, 3893, 3880, 3868, 3857, 3846, 3837, 3828,
1817+
3820, 3812, 3805, 3798, 3791, 3785, 3779, 3773, 3768, 3762,
1818+
3757, 3752, 3747, 3742, 3738, 3733, 3729, 3724, 3720, 3716,
1819+
3712, 3708, 3704, 3700, 3696, 3692, 3688, 3685, 3681, 3677,
1820+
3674, 3670, 3667, 3663, 3660, 3657, 3653, 3650, 3646, 3643,
1821+
3640, 3637, 3633, 3630, 3627, 3624, 3620, 3617, 3614, 3611,
1822+
3608, 3604, 3601, 3598, 3595, 3592, 3589, 3585, 3582, 3579,
1823+
3576, 3573, 3569, 3566, 3563, 3560, 3556, 3553, 3550, 3546,
1824+
3543, 3539, 3536, 3532, 3529, 3525, 3499, 3466, 3433, 3399,
1825+
};
1826+
1827+
int i;
1828+
1829+
if (voltage == 0)
1830+
return 0;
1831+
1832+
if (unlikely(voltage < 3400 || voltage >= 5000))
1833+
hid_warn_once(hid_dev,
1834+
"%s: possibly using the wrong voltage curve\n",
1835+
__func__);
1836+
1837+
for (i = 0; i < ARRAY_SIZE(voltages); i++) {
1838+
if (voltage >= voltages[i])
1839+
return ARRAY_SIZE(voltages) - i;
1840+
}
1841+
1842+
return 0;
1843+
}
1844+
1845+
static int hidpp20_map_adc_measurement_1f20(u8 data[3], int *voltage)
1846+
{
1847+
int status;
1848+
u8 flags;
1849+
1850+
flags = data[2];
1851+
1852+
switch (flags) {
1853+
case 0x01:
1854+
status = POWER_SUPPLY_STATUS_DISCHARGING;
1855+
break;
1856+
case 0x03:
1857+
status = POWER_SUPPLY_STATUS_CHARGING;
1858+
break;
1859+
case 0x07:
1860+
status = POWER_SUPPLY_STATUS_FULL;
1861+
break;
1862+
case 0x0F:
1863+
default:
1864+
status = POWER_SUPPLY_STATUS_UNKNOWN;
1865+
break;
1866+
}
1867+
1868+
*voltage = get_unaligned_be16(data);
1869+
1870+
dbg_hid("Parsed 1f20 data as flag 0x%02x voltage %dmV\n",
1871+
flags, *voltage);
1872+
1873+
return status;
1874+
}
1875+
1876+
/* Return value is whether the device is online */
1877+
static bool hidpp20_get_adc_measurement_1f20(struct hidpp_device *hidpp,
1878+
u8 feature_index,
1879+
int *status, int *voltage)
1880+
{
1881+
struct hidpp_report response;
1882+
int ret;
1883+
u8 *params = (u8 *)response.fap.params;
1884+
1885+
*status = POWER_SUPPLY_STATUS_UNKNOWN;
1886+
*voltage = 0;
1887+
ret = hidpp_send_fap_command_sync(hidpp, feature_index,
1888+
CMD_ADC_MEASUREMENT_GET_ADC_MEASUREMENT,
1889+
NULL, 0, &response);
1890+
1891+
if (ret > 0) {
1892+
hid_dbg(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
1893+
__func__, ret);
1894+
return false;
1895+
}
1896+
1897+
*status = hidpp20_map_adc_measurement_1f20(params, voltage);
1898+
return true;
1899+
}
1900+
1901+
static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
1902+
{
1903+
u8 feature_type;
1904+
1905+
if (hidpp->battery.adc_measurement_feature_index == 0xff) {
1906+
int ret;
1907+
1908+
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
1909+
&hidpp->battery.adc_measurement_feature_index,
1910+
&feature_type);
1911+
if (ret)
1912+
return ret;
1913+
1914+
hidpp->capabilities |= HIDPP_CAPABILITY_ADC_MEASUREMENT;
1915+
}
1916+
1917+
hidpp->battery.online = hidpp20_get_adc_measurement_1f20(hidpp,
1918+
hidpp->battery.adc_measurement_feature_index,
1919+
&hidpp->battery.status,
1920+
&hidpp->battery.voltage);
1921+
hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev,
1922+
hidpp->battery.voltage);
1923+
1924+
return 0;
1925+
}
1926+
1927+
static int hidpp20_adc_measurement_event_1f20(struct hidpp_device *hidpp,
1928+
u8 *data, int size)
1929+
{
1930+
struct hidpp_report *report = (struct hidpp_report *)data;
1931+
int status, voltage;
1932+
1933+
if (report->fap.feature_index != hidpp->battery.adc_measurement_feature_index ||
1934+
report->fap.funcindex_clientid != EVENT_ADC_MEASUREMENT_STATUS_BROADCAST)
1935+
return 0;
1936+
1937+
status = hidpp20_map_adc_measurement_1f20(report->fap.params, &voltage);
1938+
1939+
hidpp->battery.online = status != POWER_SUPPLY_STATUS_UNKNOWN;
1940+
1941+
if (voltage != hidpp->battery.voltage || status != hidpp->battery.status) {
1942+
hidpp->battery.status = status;
1943+
hidpp->battery.voltage = voltage;
1944+
hidpp->battery.capacity = hidpp20_map_adc_measurement_1f20_capacity(hidpp->hid_dev, voltage);
1945+
if (hidpp->battery.ps)
1946+
power_supply_changed(hidpp->battery.ps);
1947+
}
1948+
return 0;
1949+
}
1950+
17931951
/* -------------------------------------------------------------------------- */
17941952
/* 0x2120: Hi-resolution scrolling */
17951953
/* -------------------------------------------------------------------------- */
@@ -3708,6 +3866,9 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
37083866
ret = hidpp20_battery_voltage_event(hidpp, data, size);
37093867
if (ret != 0)
37103868
return ret;
3869+
ret = hidpp20_adc_measurement_event_1f20(hidpp, data, size);
3870+
if (ret != 0)
3871+
return ret;
37113872
}
37123873

37133874
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
@@ -3831,6 +3992,7 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
38313992
hidpp->battery.feature_index = 0xff;
38323993
hidpp->battery.solar_feature_index = 0xff;
38333994
hidpp->battery.voltage_feature_index = 0xff;
3995+
hidpp->battery.adc_measurement_feature_index = 0xff;
38343996

38353997
if (hidpp->protocol_major >= 2) {
38363998
if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
@@ -3844,6 +4006,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
38444006
ret = hidpp20_query_battery_info_1004(hidpp);
38454007
if (ret)
38464008
ret = hidpp20_query_battery_voltage_info(hidpp);
4009+
if (ret)
4010+
ret = hidpp20_query_adc_measurement_info_1f20(hidpp);
38474011
}
38484012

38494013
if (ret)
@@ -3873,15 +4037,17 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
38734037

38744038
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE ||
38754039
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_PERCENTAGE ||
3876-
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
4040+
hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
4041+
hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
38774042
battery_props[num_battery_props++] =
38784043
POWER_SUPPLY_PROP_CAPACITY;
38794044

38804045
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS)
38814046
battery_props[num_battery_props++] =
38824047
POWER_SUPPLY_PROP_CAPACITY_LEVEL;
38834048

3884-
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE)
4049+
if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_VOLTAGE ||
4050+
hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
38854051
battery_props[num_battery_props++] =
38864052
POWER_SUPPLY_PROP_VOLTAGE_NOW;
38874053

@@ -4054,6 +4220,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
40544220
hidpp20_query_battery_voltage_info(hidpp);
40554221
else if (hidpp->capabilities & HIDPP_CAPABILITY_UNIFIED_BATTERY)
40564222
hidpp20_query_battery_info_1004(hidpp);
4223+
else if (hidpp->capabilities & HIDPP_CAPABILITY_ADC_MEASUREMENT)
4224+
hidpp20_query_adc_measurement_info_1f20(hidpp);
40574225
else
40584226
hidpp20_query_battery_info_1000(hidpp);
40594227
}

0 commit comments

Comments
 (0)