hid-for-linus-2024010801
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIVAwUAZZxaAaZi849r7WBJAQL3kQ/+O657H6H/yfi2tC/i+S8Q13gWc9bhtYt/ dI90ixcrWnZbNEuSUZ9aLt5UzVfSO2GnsmwGUwRdfMCOIYv42mS9st0JAGvYx0jL xYHaqMW5VHn9pdUBDdgXG90DyivcbprxAldpTyJFr029g1H7vdnp/KXhzveBfaIw lBGOzM1miiK2/5quj9/tIW1rJLJiR8LLNBpjaDAVZrJqAjJXObCY9AmtpsgiGQSY kh8YTohNcMTo6w/CVoAekoQugE6tDHAvAg7QqRVwuMrFXu71fMjUcyOd8vFrptwC 8OnOVN8qZYohdE9o9AxO8jUm2dUI8hTvijdxERW6zZy3lRNOnfpiTYozGISJofUc +E1fY8/LCtow1RzH8tlfuc+JcWfBdn4egU+r727aRMZSgu+f61xXZGTGsUuwY37Q zSGoAa2P5xodk4S4bF40XKIYYlbEbfUJP73GRFk4QQYkE5lcAK+djG4e1guU+lw2 VsWwCHK4Nl9LxNIj5a6VLrK4JegBHuY9uBGrQUDw7NcB86o7le4wh1HdM8cfBDG5 RrLe/lfyonquFOAPIasVHB5oG+FK1E+ex4DL1qfvWcmV1RRVaEvtrBF2mpidMSoI W0m6iAPN5keGhlus50xXllfmbotpgFQtcBgJQKCUaSYP/WAJT9JpHLYPOA2ADM0g 3XoBX8i6s2M= =3SCb -----END PGP SIGNATURE----- Merge tag 'hid-for-linus-2024010801' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid Pull HID updates from Jiri Kosina: - assorted functional fixes for hid-steam ported from SteamOS betas (Vicki Pfau) - fix for custom sensor-hub sensors (hinge angle sensor and LISS sensors) not working (Yauhen Kharuzhy) - functional fix for handling Confidence in Wacom driver (Jason Gerecke) - support for Ilitek ili2901 touchscreen (Zhengqiao Xia) - power management fix for Wacom userspace battery exporting (Tatsunosuke Tobita) - rework of wait-for-reset in order to reduce the need for I2C_HID_QUIRK_NO_IRQ_AFTER_RESET qurk; the success rate is now 50% better, but there are still further improvements to be made (Hans de Goede) - greatly improved coverage of Tablets in hid-selftests (Benjamin Tissoires) - support for Nintendo NSO controllers -- SNES, Genesis and N64 (Ryan McClelland) - support for controlling mcp2200 GPIOs (Johannes Roith) - power management improvement for EHL OOB wakeup in intel-ish (Kai-Heng Feng) - other assorted device-specific fixes and code cleanups * tag 'hid-for-linus-2024010801' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (53 commits) HID: amd_sfh: Add a new interface for exporting ALS data HID: amd_sfh: Add a new interface for exporting HPD data HID: amd_sfh: rename float_to_int() to amd_sfh_float_to_int() HID: i2c-hid: elan: Add ili2901 timing dt-bindings: HID: i2c-hid: elan: Introduce Ilitek ili2901 HID: bpf: make bus_type const in struct hid_bpf_ops HID: make ishtp_cl_bus_type const HID: make hid_bus_type const HID: hid-steam: Add gamepad-only mode switched to by holding options HID: hid-steam: Better handling of serial number length HID: hid-steam: Update list of identifiers from SDL HID: hid-steam: Make client_opened a counter HID: hid-steam: Clean up locking HID: hid-steam: Disable watchdog instead of using a heartbeat HID: hid-steam: Avoid overwriting smoothing parameter HID: magicmouse: fix kerneldoc for struct magicmouse_sc HID: sensor-hub: Enable hid core report processing for all devices HID: wacom: Add additional tests of confidence behavior HID: wacom: Correct behavior when processing some confidence == false touches HID: nintendo: add support for nso controllers ...
This commit is contained in:
commit
fef018d819
@ -18,8 +18,9 @@ allOf:
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- const: elan,ekth6915
|
||||
enum:
|
||||
- elan,ekth6915
|
||||
- ilitek,ili2901
|
||||
|
||||
reg:
|
||||
const: 0x10
|
||||
|
@ -761,14 +761,15 @@ config HID_MULTITOUCH
|
||||
module will be called hid-multitouch.
|
||||
|
||||
config HID_NINTENDO
|
||||
tristate "Nintendo Joy-Con and Pro Controller support"
|
||||
tristate "Nintendo Joy-Con, NSO, and Pro Controller support"
|
||||
depends on NEW_LEDS
|
||||
depends on LEDS_CLASS
|
||||
select POWER_SUPPLY
|
||||
help
|
||||
Adds support for the Nintendo Switch Joy-Cons and Pro Controller.
|
||||
Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
|
||||
All controllers support bluetooth, and the Pro Controller also supports
|
||||
its USB mode.
|
||||
its USB mode. This also includes support for the Nintendo Switch Online
|
||||
Controllers which include the Genesis, SNES, and N64 controllers.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called hid-nintendo.
|
||||
@ -779,9 +780,9 @@ config NINTENDO_FF
|
||||
select INPUT_FF_MEMLESS
|
||||
help
|
||||
Say Y here if you have a Nintendo Switch controller and want to enable
|
||||
force feedback support for it. This works for both joy-cons and the pro
|
||||
controller. For the pro controller, both rumble motors can be controlled
|
||||
individually.
|
||||
force feedback support for it. This works for both joy-cons, the pro
|
||||
controller, and the NSO N64 controller. For the pro controller, both
|
||||
rumble motors can be controlled individually.
|
||||
|
||||
config HID_NTI
|
||||
tristate "NTI keyboard adapters"
|
||||
@ -1296,6 +1297,15 @@ config HID_ALPS
|
||||
Say Y here if you have a Alps touchpads over i2c-hid or usbhid
|
||||
and want support for its special functionalities.
|
||||
|
||||
config HID_MCP2200
|
||||
tristate "Microchip MCP2200 HID USB-to-GPIO bridge"
|
||||
depends on USB_HID && GPIOLIB
|
||||
help
|
||||
Provides GPIO functionality over USB-HID through MCP2200 device.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called hid-mcp2200.ko.
|
||||
|
||||
config HID_MCP2221
|
||||
tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support"
|
||||
depends on USB_HID && I2C
|
||||
|
@ -79,6 +79,7 @@ obj-$(CONFIG_HID_LOGITECH_HIDPP) += hid-logitech-hidpp.o
|
||||
obj-$(CONFIG_HID_MACALLY) += hid-macally.o
|
||||
obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o
|
||||
obj-$(CONFIG_HID_MALTRON) += hid-maltron.o
|
||||
obj-$(CONFIG_HID_MCP2200) += hid-mcp2200.o
|
||||
obj-$(CONFIG_HID_MCP2221) += hid-mcp2221.o
|
||||
obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o
|
||||
obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o
|
||||
|
@ -37,6 +37,11 @@ struct amd_mp2_sensor_info {
|
||||
dma_addr_t dma_address;
|
||||
};
|
||||
|
||||
struct sfh_dev_status {
|
||||
bool is_hpd_present;
|
||||
bool is_als_present;
|
||||
};
|
||||
|
||||
struct amd_mp2_dev {
|
||||
struct pci_dev *pdev;
|
||||
struct amdtp_cl_data *cl_data;
|
||||
@ -47,6 +52,7 @@ struct amd_mp2_dev {
|
||||
struct amd_input_data in_data;
|
||||
/* mp2 active control status */
|
||||
u32 mp2_acs;
|
||||
struct sfh_dev_status dev_en;
|
||||
};
|
||||
|
||||
struct amd_mp2_ops {
|
||||
|
@ -132,7 +132,7 @@ static void get_common_inputs(struct common_input_property *common, int report_i
|
||||
common->event_type = HID_USAGE_SENSOR_EVENT_DATA_UPDATED_ENUM;
|
||||
}
|
||||
|
||||
static int float_to_int(u32 flt32_val)
|
||||
int amd_sfh_float_to_int(u32 flt32_val)
|
||||
{
|
||||
int fraction, shift, mantissa, sign, exp, zeropre;
|
||||
|
||||
@ -201,9 +201,9 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
|
||||
OFFSET_SENSOR_DATA_DEFAULT;
|
||||
memcpy_fromio(&accel_data, sensoraddr, sizeof(struct sfh_accel_data));
|
||||
get_common_inputs(&acc_input.common_property, report_id);
|
||||
acc_input.in_accel_x_value = float_to_int(accel_data.acceldata.x) / 100;
|
||||
acc_input.in_accel_y_value = float_to_int(accel_data.acceldata.y) / 100;
|
||||
acc_input.in_accel_z_value = float_to_int(accel_data.acceldata.z) / 100;
|
||||
acc_input.in_accel_x_value = amd_sfh_float_to_int(accel_data.acceldata.x) / 100;
|
||||
acc_input.in_accel_y_value = amd_sfh_float_to_int(accel_data.acceldata.y) / 100;
|
||||
acc_input.in_accel_z_value = amd_sfh_float_to_int(accel_data.acceldata.z) / 100;
|
||||
memcpy(input_report, &acc_input, sizeof(acc_input));
|
||||
report_size = sizeof(acc_input);
|
||||
break;
|
||||
@ -212,9 +212,9 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
|
||||
OFFSET_SENSOR_DATA_DEFAULT;
|
||||
memcpy_fromio(&gyro_data, sensoraddr, sizeof(struct sfh_gyro_data));
|
||||
get_common_inputs(&gyro_input.common_property, report_id);
|
||||
gyro_input.in_angel_x_value = float_to_int(gyro_data.gyrodata.x) / 1000;
|
||||
gyro_input.in_angel_y_value = float_to_int(gyro_data.gyrodata.y) / 1000;
|
||||
gyro_input.in_angel_z_value = float_to_int(gyro_data.gyrodata.z) / 1000;
|
||||
gyro_input.in_angel_x_value = amd_sfh_float_to_int(gyro_data.gyrodata.x) / 1000;
|
||||
gyro_input.in_angel_y_value = amd_sfh_float_to_int(gyro_data.gyrodata.y) / 1000;
|
||||
gyro_input.in_angel_z_value = amd_sfh_float_to_int(gyro_data.gyrodata.z) / 1000;
|
||||
memcpy(input_report, &gyro_input, sizeof(gyro_input));
|
||||
report_size = sizeof(gyro_input);
|
||||
break;
|
||||
@ -223,9 +223,9 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
|
||||
OFFSET_SENSOR_DATA_DEFAULT;
|
||||
memcpy_fromio(&mag_data, sensoraddr, sizeof(struct sfh_mag_data));
|
||||
get_common_inputs(&magno_input.common_property, report_id);
|
||||
magno_input.in_magno_x = float_to_int(mag_data.magdata.x) / 100;
|
||||
magno_input.in_magno_y = float_to_int(mag_data.magdata.y) / 100;
|
||||
magno_input.in_magno_z = float_to_int(mag_data.magdata.z) / 100;
|
||||
magno_input.in_magno_x = amd_sfh_float_to_int(mag_data.magdata.x) / 100;
|
||||
magno_input.in_magno_y = amd_sfh_float_to_int(mag_data.magdata.y) / 100;
|
||||
magno_input.in_magno_z = amd_sfh_float_to_int(mag_data.magdata.z) / 100;
|
||||
magno_input.in_magno_accuracy = mag_data.accuracy / 100;
|
||||
memcpy(input_report, &magno_input, sizeof(magno_input));
|
||||
report_size = sizeof(magno_input);
|
||||
@ -235,13 +235,15 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
|
||||
OFFSET_SENSOR_DATA_DEFAULT;
|
||||
memcpy_fromio(&als_data, sensoraddr, sizeof(struct sfh_als_data));
|
||||
get_common_inputs(&als_input.common_property, report_id);
|
||||
als_input.illuminance_value = float_to_int(als_data.lux);
|
||||
als_input.illuminance_value = amd_sfh_float_to_int(als_data.lux);
|
||||
|
||||
memcpy_fromio(&binfo, mp2->vsbase, sizeof(struct sfh_base_info));
|
||||
if (binfo.sbase.s_prop[ALS_IDX].sf.feat & 0x2) {
|
||||
als_input.light_color_temp = als_data.light_color_temp;
|
||||
als_input.chromaticity_x_value = float_to_int(als_data.chromaticity_x);
|
||||
als_input.chromaticity_y_value = float_to_int(als_data.chromaticity_y);
|
||||
als_input.chromaticity_x_value =
|
||||
amd_sfh_float_to_int(als_data.chromaticity_x);
|
||||
als_input.chromaticity_y_value =
|
||||
amd_sfh_float_to_int(als_data.chromaticity_y);
|
||||
}
|
||||
|
||||
report_size = sizeof(als_input);
|
||||
|
@ -73,6 +73,15 @@ static int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
|
||||
int i, status;
|
||||
|
||||
for (i = 0; i < cl_data->num_hid_devices; i++) {
|
||||
switch (cl_data->sensor_idx[i]) {
|
||||
case HPD_IDX:
|
||||
privdata->dev_en.is_hpd_present = false;
|
||||
break;
|
||||
case ALS_IDX:
|
||||
privdata->dev_en.is_als_present = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
|
||||
privdata->mp2_ops->stop(privdata, cl_data->sensor_idx[i]);
|
||||
status = amd_sfh_wait_for_response
|
||||
@ -178,6 +187,14 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
|
||||
rc = amdtp_hid_probe(i, cl_data);
|
||||
if (rc)
|
||||
goto cleanup;
|
||||
switch (cl_data->sensor_idx[i]) {
|
||||
case HPD_IDX:
|
||||
privdata->dev_en.is_hpd_present = true;
|
||||
break;
|
||||
case ALS_IDX:
|
||||
privdata->dev_en.is_als_present = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n",
|
||||
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
|
||||
@ -259,6 +276,7 @@ static void amd_mp2_pci_remove(void *privdata)
|
||||
{
|
||||
struct amd_mp2_dev *mp2 = privdata;
|
||||
|
||||
sfh_deinit_emp2();
|
||||
amd_sfh_hid_client_deinit(privdata);
|
||||
mp2->mp2_ops->stop_all(mp2);
|
||||
pci_intx(mp2->pdev, false);
|
||||
@ -311,12 +329,14 @@ int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
|
||||
|
||||
rc = amd_sfh_irq_init(mp2);
|
||||
if (rc) {
|
||||
sfh_deinit_emp2();
|
||||
dev_err(dev, "amd_sfh_irq_init failed\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = amd_sfh1_1_hid_client_init(mp2);
|
||||
if (rc) {
|
||||
sfh_deinit_emp2();
|
||||
dev_err(dev, "amd_sfh1_1_hid_client_init failed\n");
|
||||
return rc;
|
||||
}
|
||||
|
@ -7,11 +7,14 @@
|
||||
*
|
||||
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
*/
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/io-64-nonatomic-lo-hi.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
#include "amd_sfh_interface.h"
|
||||
|
||||
static struct amd_mp2_dev *emp2;
|
||||
|
||||
static int amd_sfh_wait_response(struct amd_mp2_dev *mp2, u8 sid, u32 cmd_id)
|
||||
{
|
||||
struct sfh_cmd_response cmd_resp;
|
||||
@ -73,7 +76,63 @@ static struct amd_mp2_ops amd_sfh_ops = {
|
||||
.response = amd_sfh_wait_response,
|
||||
};
|
||||
|
||||
void sfh_deinit_emp2(void)
|
||||
{
|
||||
emp2 = NULL;
|
||||
}
|
||||
|
||||
void sfh_interface_init(struct amd_mp2_dev *mp2)
|
||||
{
|
||||
mp2->mp2_ops = &amd_sfh_ops;
|
||||
emp2 = mp2;
|
||||
}
|
||||
|
||||
static int amd_sfh_hpd_info(u8 *user_present)
|
||||
{
|
||||
struct hpd_status hpdstatus;
|
||||
|
||||
if (!user_present)
|
||||
return -EINVAL;
|
||||
|
||||
if (!emp2 || !emp2->dev_en.is_hpd_present)
|
||||
return -ENODEV;
|
||||
|
||||
hpdstatus.val = readl(emp2->mmio + AMD_C2P_MSG(4));
|
||||
*user_present = hpdstatus.shpd.presence;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_sfh_als_info(u32 *ambient_light)
|
||||
{
|
||||
struct sfh_als_data als_data;
|
||||
void __iomem *sensoraddr;
|
||||
|
||||
if (!ambient_light)
|
||||
return -EINVAL;
|
||||
|
||||
if (!emp2 || !emp2->dev_en.is_als_present)
|
||||
return -ENODEV;
|
||||
|
||||
sensoraddr = emp2->vsbase +
|
||||
(ALS_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
|
||||
OFFSET_SENSOR_DATA_DEFAULT;
|
||||
memcpy_fromio(&als_data, sensoraddr, sizeof(struct sfh_als_data));
|
||||
*ambient_light = amd_sfh_float_to_int(als_data.lux);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op)
|
||||
{
|
||||
if (sfh_info) {
|
||||
switch (op) {
|
||||
case MT_HPD:
|
||||
return amd_sfh_hpd_info(&sfh_info->user_present);
|
||||
case MT_ALS:
|
||||
return amd_sfh_als_info(&sfh_info->ambient_light);
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amd_get_sfh_info);
|
||||
|
@ -165,5 +165,7 @@ struct hpd_status {
|
||||
};
|
||||
|
||||
void sfh_interface_init(struct amd_mp2_dev *mp2);
|
||||
void sfh_deinit_emp2(void);
|
||||
void amd_sfh1_1_set_desc_ops(struct amd_mp2_ops *mp2_ops);
|
||||
int amd_sfh_float_to_int(u32 flt32_val);
|
||||
#endif
|
||||
|
@ -2749,7 +2749,7 @@ static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env)
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct bus_type hid_bus_type = {
|
||||
const struct bus_type hid_bus_type = {
|
||||
.name = "hid",
|
||||
.dev_groups = hid_dev_groups,
|
||||
.drv_groups = hid_drv_groups,
|
||||
|
@ -916,6 +916,7 @@
|
||||
#define USB_DEVICE_ID_PICK16F1454 0x0042
|
||||
#define USB_DEVICE_ID_PICK16F1454_V2 0xf2f7
|
||||
#define USB_DEVICE_ID_LUXAFOR 0xf372
|
||||
#define USB_DEVICE_ID_MCP2200 0x00df
|
||||
#define USB_DEVICE_ID_MCP2221 0x00dd
|
||||
|
||||
#define USB_VENDOR_ID_MICROSOFT 0x045e
|
||||
@ -987,7 +988,10 @@
|
||||
#define USB_DEVICE_ID_NINTENDO_JOYCONL 0x2006
|
||||
#define USB_DEVICE_ID_NINTENDO_JOYCONR 0x2007
|
||||
#define USB_DEVICE_ID_NINTENDO_PROCON 0x2009
|
||||
#define USB_DEVICE_ID_NINTENDO_CHRGGRIP 0x200E
|
||||
#define USB_DEVICE_ID_NINTENDO_CHRGGRIP 0x200e
|
||||
#define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017
|
||||
#define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
|
||||
#define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
|
||||
|
||||
#define USB_VENDOR_ID_NOVATEK 0x0603
|
||||
#define USB_DEVICE_ID_NOVATEK_PCT 0x0600
|
||||
|
@ -120,6 +120,9 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
|
||||
* @scroll_jiffies: Time of last scroll motion.
|
||||
* @touches: Most recent data for a touch, indexed by tracking ID.
|
||||
* @tracking_ids: Mapping of current touch input data to @touches.
|
||||
* @hdev: Pointer to the underlying HID device.
|
||||
* @work: Workqueue to handle initialization retry for quirky devices.
|
||||
* @battery_timer: Timer for obtaining battery level information.
|
||||
*/
|
||||
struct magicmouse_sc {
|
||||
struct input_dev *input;
|
||||
|
392
drivers/hid/hid-mcp2200.c
Normal file
392
drivers/hid/hid-mcp2200.c
Normal file
@ -0,0 +1,392 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* MCP2200 - Microchip USB to GPIO bridge
|
||||
*
|
||||
* Copyright (c) 2023, Johannes Roith <johannes@gnu-linux.rocks>
|
||||
*
|
||||
* Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/22228A.pdf
|
||||
* App Note for HID: https://ww1.microchip.com/downloads/en/DeviceDoc/93066A.pdf
|
||||
*/
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/hidraw.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include "hid-ids.h"
|
||||
|
||||
/* Commands codes in a raw output report */
|
||||
#define SET_CLEAR_OUTPUTS 0x08
|
||||
#define CONFIGURE 0x10
|
||||
#define READ_EE 0x20
|
||||
#define WRITE_EE 0x40
|
||||
#define READ_ALL 0x80
|
||||
|
||||
/* MCP GPIO direction encoding */
|
||||
enum MCP_IO_DIR {
|
||||
MCP2200_DIR_OUT = 0x00,
|
||||
MCP2200_DIR_IN = 0x01,
|
||||
};
|
||||
|
||||
/* Altternative pin assignments */
|
||||
#define TXLED 2
|
||||
#define RXLED 3
|
||||
#define USBCFG 6
|
||||
#define SSPND 7
|
||||
#define MCP_NGPIO 8
|
||||
|
||||
/* CMD to set or clear a GPIO output */
|
||||
struct mcp_set_clear_outputs {
|
||||
u8 cmd;
|
||||
u8 dummys1[10];
|
||||
u8 set_bmap;
|
||||
u8 clear_bmap;
|
||||
u8 dummys2[3];
|
||||
} __packed;
|
||||
|
||||
/* CMD to configure the IOs */
|
||||
struct mcp_configure {
|
||||
u8 cmd;
|
||||
u8 dummys1[3];
|
||||
u8 io_bmap;
|
||||
u8 config_alt_pins;
|
||||
u8 io_default_val_bmap;
|
||||
u8 config_alt_options;
|
||||
u8 baud_h;
|
||||
u8 baud_l;
|
||||
u8 dummys2[6];
|
||||
} __packed;
|
||||
|
||||
/* CMD to read all parameters */
|
||||
struct mcp_read_all {
|
||||
u8 cmd;
|
||||
u8 dummys[15];
|
||||
} __packed;
|
||||
|
||||
/* Response to the read all cmd */
|
||||
struct mcp_read_all_resp {
|
||||
u8 cmd;
|
||||
u8 eep_addr;
|
||||
u8 dummy;
|
||||
u8 eep_val;
|
||||
u8 io_bmap;
|
||||
u8 config_alt_pins;
|
||||
u8 io_default_val_bmap;
|
||||
u8 config_alt_options;
|
||||
u8 baud_h;
|
||||
u8 baud_l;
|
||||
u8 io_port_val_bmap;
|
||||
u8 dummys[5];
|
||||
} __packed;
|
||||
|
||||
struct mcp2200 {
|
||||
struct hid_device *hdev;
|
||||
struct mutex lock;
|
||||
struct completion wait_in_report;
|
||||
u8 gpio_dir;
|
||||
u8 gpio_val;
|
||||
u8 gpio_inval;
|
||||
u8 baud_h;
|
||||
u8 baud_l;
|
||||
u8 config_alt_pins;
|
||||
u8 gpio_reset_val;
|
||||
u8 config_alt_options;
|
||||
int status;
|
||||
struct gpio_chip gc;
|
||||
u8 hid_report[16];
|
||||
};
|
||||
|
||||
/* this executes the READ_ALL cmd */
|
||||
static int mcp_cmd_read_all(struct mcp2200 *mcp)
|
||||
{
|
||||
struct mcp_read_all *read_all;
|
||||
int len, t;
|
||||
|
||||
reinit_completion(&mcp->wait_in_report);
|
||||
|
||||
mutex_lock(&mcp->lock);
|
||||
|
||||
read_all = (struct mcp_read_all *) mcp->hid_report;
|
||||
read_all->cmd = READ_ALL;
|
||||
len = hid_hw_output_report(mcp->hdev, (u8 *) read_all,
|
||||
sizeof(struct mcp_read_all));
|
||||
|
||||
mutex_unlock(&mcp->lock);
|
||||
|
||||
if (len != sizeof(struct mcp_read_all))
|
||||
return -EINVAL;
|
||||
|
||||
t = wait_for_completion_timeout(&mcp->wait_in_report,
|
||||
msecs_to_jiffies(4000));
|
||||
if (!t)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
/* return status, negative value if wrong response was received */
|
||||
return mcp->status;
|
||||
}
|
||||
|
||||
static void mcp_set_multiple(struct gpio_chip *gc, unsigned long *mask,
|
||||
unsigned long *bits)
|
||||
{
|
||||
struct mcp2200 *mcp = gpiochip_get_data(gc);
|
||||
u8 value;
|
||||
int status;
|
||||
struct mcp_set_clear_outputs *cmd;
|
||||
|
||||
mutex_lock(&mcp->lock);
|
||||
cmd = (struct mcp_set_clear_outputs *) mcp->hid_report;
|
||||
|
||||
value = mcp->gpio_val & ~*mask;
|
||||
value |= (*mask & *bits);
|
||||
|
||||
cmd->cmd = SET_CLEAR_OUTPUTS;
|
||||
cmd->set_bmap = value;
|
||||
cmd->clear_bmap = ~(value);
|
||||
|
||||
status = hid_hw_output_report(mcp->hdev, (u8 *) cmd,
|
||||
sizeof(struct mcp_set_clear_outputs));
|
||||
|
||||
if (status == sizeof(struct mcp_set_clear_outputs))
|
||||
mcp->gpio_val = value;
|
||||
|
||||
mutex_unlock(&mcp->lock);
|
||||
}
|
||||
|
||||
static void mcp_set(struct gpio_chip *gc, unsigned int gpio_nr, int value)
|
||||
{
|
||||
unsigned long mask = 1 << gpio_nr;
|
||||
unsigned long bmap_value = value << gpio_nr;
|
||||
|
||||
mcp_set_multiple(gc, &mask, &bmap_value);
|
||||
}
|
||||
|
||||
static int mcp_get_multiple(struct gpio_chip *gc, unsigned long *mask,
|
||||
unsigned long *bits)
|
||||
{
|
||||
u32 val;
|
||||
struct mcp2200 *mcp = gpiochip_get_data(gc);
|
||||
int status;
|
||||
|
||||
status = mcp_cmd_read_all(mcp);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
val = mcp->gpio_inval;
|
||||
*bits = (val & *mask);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mcp_get(struct gpio_chip *gc, unsigned int gpio_nr)
|
||||
{
|
||||
unsigned long mask = 0, bits = 0;
|
||||
|
||||
mask = (1 << gpio_nr);
|
||||
mcp_get_multiple(gc, &mask, &bits);
|
||||
return bits > 0;
|
||||
}
|
||||
|
||||
static int mcp_get_direction(struct gpio_chip *gc, unsigned int gpio_nr)
|
||||
{
|
||||
struct mcp2200 *mcp = gpiochip_get_data(gc);
|
||||
|
||||
return (mcp->gpio_dir & (MCP2200_DIR_IN << gpio_nr))
|
||||
? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
|
||||
}
|
||||
|
||||
static int mcp_set_direction(struct gpio_chip *gc, unsigned int gpio_nr,
|
||||
enum MCP_IO_DIR io_direction)
|
||||
{
|
||||
struct mcp2200 *mcp = gpiochip_get_data(gc);
|
||||
struct mcp_configure *conf;
|
||||
int status;
|
||||
/* after the configure cmd we will need to set the outputs again */
|
||||
unsigned long mask = ~(mcp->gpio_dir); /* only set outputs */
|
||||
unsigned long bits = mcp->gpio_val;
|
||||
/* Offsets of alternative pins in config_alt_pins, 0 is not used */
|
||||
u8 alt_pin_conf[8] = {SSPND, USBCFG, 0, 0, 0, 0, RXLED, TXLED};
|
||||
u8 config_alt_pins = mcp->config_alt_pins;
|
||||
|
||||
/* Read in the reset baudrate first, we need it later */
|
||||
status = mcp_cmd_read_all(mcp);
|
||||
if (status != 0)
|
||||
return status;
|
||||
|
||||
mutex_lock(&mcp->lock);
|
||||
conf = (struct mcp_configure *) mcp->hid_report;
|
||||
|
||||
/* configure will reset the chip! */
|
||||
conf->cmd = CONFIGURE;
|
||||
conf->io_bmap = (mcp->gpio_dir & ~(1 << gpio_nr))
|
||||
| (io_direction << gpio_nr);
|
||||
/* Don't overwrite the reset parameters */
|
||||
conf->baud_h = mcp->baud_h;
|
||||
conf->baud_l = mcp->baud_l;
|
||||
conf->config_alt_options = mcp->config_alt_options;
|
||||
conf->io_default_val_bmap = mcp->gpio_reset_val;
|
||||
/* Adjust alt. func if necessary */
|
||||
if (alt_pin_conf[gpio_nr])
|
||||
config_alt_pins &= ~(1 << alt_pin_conf[gpio_nr]);
|
||||
conf->config_alt_pins = config_alt_pins;
|
||||
|
||||
status = hid_hw_output_report(mcp->hdev, (u8 *) conf,
|
||||
sizeof(struct mcp_set_clear_outputs));
|
||||
|
||||
if (status == sizeof(struct mcp_set_clear_outputs)) {
|
||||
mcp->gpio_dir = conf->io_bmap;
|
||||
mcp->config_alt_pins = config_alt_pins;
|
||||
} else {
|
||||
mutex_unlock(&mcp->lock);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mutex_unlock(&mcp->lock);
|
||||
|
||||
/* Configure CMD will clear all IOs -> rewrite them */
|
||||
mcp_set_multiple(gc, &mask, &bits);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mcp_direction_input(struct gpio_chip *gc, unsigned int gpio_nr)
|
||||
{
|
||||
return mcp_set_direction(gc, gpio_nr, MCP2200_DIR_IN);
|
||||
}
|
||||
|
||||
static int mcp_direction_output(struct gpio_chip *gc, unsigned int gpio_nr,
|
||||
int value)
|
||||
{
|
||||
int ret;
|
||||
unsigned long mask, bmap_value;
|
||||
|
||||
mask = 1 << gpio_nr;
|
||||
bmap_value = value << gpio_nr;
|
||||
|
||||
ret = mcp_set_direction(gc, gpio_nr, MCP2200_DIR_OUT);
|
||||
if (!ret)
|
||||
mcp_set_multiple(gc, &mask, &bmap_value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct gpio_chip template_chip = {
|
||||
.label = "mcp2200",
|
||||
.owner = THIS_MODULE,
|
||||
.get_direction = mcp_get_direction,
|
||||
.direction_input = mcp_direction_input,
|
||||
.direction_output = mcp_direction_output,
|
||||
.set = mcp_set,
|
||||
.set_multiple = mcp_set_multiple,
|
||||
.get = mcp_get,
|
||||
.get_multiple = mcp_get_multiple,
|
||||
.base = -1,
|
||||
.ngpio = MCP_NGPIO,
|
||||
.can_sleep = true,
|
||||
};
|
||||
|
||||
/*
|
||||
* MCP2200 uses interrupt endpoint for input reports. This function
|
||||
* is called by HID layer when it receives i/p report from mcp2200,
|
||||
* which is actually a response to the previously sent command.
|
||||
*/
|
||||
static int mcp2200_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
u8 *data, int size)
|
||||
{
|
||||
struct mcp2200 *mcp = hid_get_drvdata(hdev);
|
||||
struct mcp_read_all_resp *all_resp;
|
||||
|
||||
switch (data[0]) {
|
||||
case READ_ALL:
|
||||
all_resp = (struct mcp_read_all_resp *) data;
|
||||
mcp->status = 0;
|
||||
mcp->gpio_inval = all_resp->io_port_val_bmap;
|
||||
mcp->baud_h = all_resp->baud_h;
|
||||
mcp->baud_l = all_resp->baud_l;
|
||||
mcp->gpio_reset_val = all_resp->io_default_val_bmap;
|
||||
mcp->config_alt_pins = all_resp->config_alt_pins;
|
||||
mcp->config_alt_options = all_resp->config_alt_options;
|
||||
break;
|
||||
default:
|
||||
mcp->status = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
complete(&mcp->wait_in_report);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mcp2200_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct mcp2200 *mcp;
|
||||
|
||||
mcp = devm_kzalloc(&hdev->dev, sizeof(*mcp), GFP_KERNEL);
|
||||
if (!mcp)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "can't parse reports\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, 0);
|
||||
if (ret) {
|
||||
hid_err(hdev, "can't start hardware\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", hdev->version >> 8,
|
||||
hdev->version & 0xff, hdev->name, hdev->phys);
|
||||
|
||||
ret = hid_hw_open(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "can't open device\n");
|
||||
hid_hw_stop(hdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_init(&mcp->lock);
|
||||
init_completion(&mcp->wait_in_report);
|
||||
hid_set_drvdata(hdev, mcp);
|
||||
mcp->hdev = hdev;
|
||||
|
||||
mcp->gc = template_chip;
|
||||
mcp->gc.parent = &hdev->dev;
|
||||
|
||||
ret = devm_gpiochip_add_data(&hdev->dev, &mcp->gc, mcp);
|
||||
if (ret < 0) {
|
||||
hid_err(hdev, "Unable to register gpiochip\n");
|
||||
hid_hw_close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mcp2200_remove(struct hid_device *hdev)
|
||||
{
|
||||
hid_hw_close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
static const struct hid_device_id mcp2200_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2200) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, mcp2200_devices);
|
||||
|
||||
static struct hid_driver mcp2200_driver = {
|
||||
.name = "mcp2200",
|
||||
.id_table = mcp2200_devices,
|
||||
.probe = mcp2200_probe,
|
||||
.remove = mcp2200_remove,
|
||||
.raw_event = mcp2200_raw_event,
|
||||
};
|
||||
|
||||
/* Register with HID core */
|
||||
module_hid_driver(mcp2200_driver);
|
||||
|
||||
MODULE_AUTHOR("Johannes Roith <johannes@gnu-linux.rocks>");
|
||||
MODULE_DESCRIPTION("MCP2200 Microchip HID USB to GPIO bridge");
|
||||
MODULE_LICENSE("GPL");
|
@ -49,6 +49,7 @@ enum {
|
||||
MCP2221_I2C_MASK_ADDR_NACK = 0x40,
|
||||
MCP2221_I2C_WRADDRL_SEND = 0x21,
|
||||
MCP2221_I2C_ADDR_NACK = 0x25,
|
||||
MCP2221_I2C_READ_PARTIAL = 0x54,
|
||||
MCP2221_I2C_READ_COMPL = 0x55,
|
||||
MCP2221_ALT_F_NOT_GPIOV = 0xEE,
|
||||
MCP2221_ALT_F_NOT_GPIOD = 0xEF,
|
||||
@ -187,6 +188,25 @@ static int mcp_cancel_last_cmd(struct mcp2221 *mcp)
|
||||
return mcp_send_data_req_status(mcp, mcp->txbuf, 8);
|
||||
}
|
||||
|
||||
/* Check if the last command succeeded or failed and return the result.
|
||||
* If the command did fail, cancel that command which will free the i2c bus.
|
||||
*/
|
||||
static int mcp_chk_last_cmd_status_free_bus(struct mcp2221 *mcp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mcp_chk_last_cmd_status(mcp);
|
||||
if (ret) {
|
||||
/* The last command was a failure.
|
||||
* Send a cancel which will also free the bus.
|
||||
*/
|
||||
usleep_range(980, 1000);
|
||||
mcp_cancel_last_cmd(mcp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mcp_set_i2c_speed(struct mcp2221 *mcp)
|
||||
{
|
||||
int ret;
|
||||
@ -241,7 +261,7 @@ static int mcp_i2c_write(struct mcp2221 *mcp,
|
||||
usleep_range(980, 1000);
|
||||
|
||||
if (last_status) {
|
||||
ret = mcp_chk_last_cmd_status(mcp);
|
||||
ret = mcp_chk_last_cmd_status_free_bus(mcp);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -278,6 +298,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
|
||||
{
|
||||
int ret;
|
||||
u16 total_len;
|
||||
int retries = 0;
|
||||
|
||||
mcp->txbuf[0] = type;
|
||||
if (msg) {
|
||||
@ -301,20 +322,31 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
|
||||
mcp->rxbuf_idx = 0;
|
||||
|
||||
do {
|
||||
/* Wait for the data to be read by the device */
|
||||
usleep_range(980, 1000);
|
||||
|
||||
memset(mcp->txbuf, 0, 4);
|
||||
mcp->txbuf[0] = MCP2221_I2C_GET_DATA;
|
||||
|
||||
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mcp_chk_last_cmd_status(mcp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
usleep_range(980, 1000);
|
||||
if (ret) {
|
||||
if (retries < 5) {
|
||||
/* The data wasn't ready to read.
|
||||
* Wait a bit longer and try again.
|
||||
*/
|
||||
usleep_range(90, 100);
|
||||
retries++;
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
retries = 0;
|
||||
}
|
||||
} while (mcp->rxbuf_idx < total_len);
|
||||
|
||||
usleep_range(980, 1000);
|
||||
ret = mcp_chk_last_cmd_status_free_bus(mcp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -328,11 +360,6 @@ static int mcp_i2c_xfer(struct i2c_adapter *adapter,
|
||||
|
||||
mutex_lock(&mcp->lock);
|
||||
|
||||
/* Setting speed before every transaction is required for mcp2221 */
|
||||
ret = mcp_set_i2c_speed(mcp);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
if (num == 1) {
|
||||
if (msgs->flags & I2C_M_RD) {
|
||||
ret = mcp_i2c_smbus_read(mcp, msgs, MCP2221_I2C_RD_DATA,
|
||||
@ -417,9 +444,7 @@ static int mcp_smbus_write(struct mcp2221 *mcp, u16 addr,
|
||||
if (last_status) {
|
||||
usleep_range(980, 1000);
|
||||
|
||||
ret = mcp_chk_last_cmd_status(mcp);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = mcp_chk_last_cmd_status_free_bus(mcp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -437,10 +462,6 @@ static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
|
||||
|
||||
mutex_lock(&mcp->lock);
|
||||
|
||||
ret = mcp_set_i2c_speed(mcp);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
switch (size) {
|
||||
|
||||
case I2C_SMBUS_QUICK:
|
||||
@ -791,7 +812,8 @@ static int mcp2221_raw_event(struct hid_device *hdev,
|
||||
mcp->status = -EIO;
|
||||
break;
|
||||
}
|
||||
if (data[2] == MCP2221_I2C_READ_COMPL) {
|
||||
if (data[2] == MCP2221_I2C_READ_COMPL ||
|
||||
data[2] == MCP2221_I2C_READ_PARTIAL) {
|
||||
buf = mcp->rxbuf;
|
||||
memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]);
|
||||
mcp->rxbuf_idx = mcp->rxbuf_idx + data[3];
|
||||
@ -1150,12 +1172,18 @@ static int mcp2221_probe(struct hid_device *hdev,
|
||||
if (i2c_clk_freq < 50)
|
||||
i2c_clk_freq = 50;
|
||||
mcp->cur_i2c_clk_div = (12000000 / (i2c_clk_freq * 1000)) - 3;
|
||||
ret = mcp_set_i2c_speed(mcp);
|
||||
if (ret) {
|
||||
hid_err(hdev, "can't set i2c speed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mcp->adapter.owner = THIS_MODULE;
|
||||
mcp->adapter.class = I2C_CLASS_HWMON;
|
||||
mcp->adapter.algo = &mcp_i2c_algo;
|
||||
mcp->adapter.retries = 1;
|
||||
mcp->adapter.dev.parent = &hdev->dev;
|
||||
ACPI_COMPANION_SET(&mcp->adapter.dev, ACPI_COMPANION(hdev->dev.parent));
|
||||
snprintf(mcp->adapter.name, sizeof(mcp->adapter.name),
|
||||
"MCP2221 usb-i2c bridge");
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -632,7 +632,7 @@ static int sensor_hub_probe(struct hid_device *hdev,
|
||||
}
|
||||
INIT_LIST_HEAD(&hdev->inputs);
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | HID_CONNECT_DRIVER);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
return ret;
|
||||
|
@ -71,60 +71,222 @@ static LIST_HEAD(steam_devices);
|
||||
|
||||
/*
|
||||
* Commands that can be sent in a feature report.
|
||||
* Thanks to Valve for some valuable hints.
|
||||
* Thanks to Valve and SDL for the names.
|
||||
*/
|
||||
#define STEAM_CMD_SET_MAPPINGS 0x80
|
||||
#define STEAM_CMD_CLEAR_MAPPINGS 0x81
|
||||
#define STEAM_CMD_GET_MAPPINGS 0x82
|
||||
#define STEAM_CMD_GET_ATTRIB 0x83
|
||||
#define STEAM_CMD_GET_ATTRIB_LABEL 0x84
|
||||
#define STEAM_CMD_DEFAULT_MAPPINGS 0x85
|
||||
#define STEAM_CMD_FACTORY_RESET 0x86
|
||||
#define STEAM_CMD_WRITE_REGISTER 0x87
|
||||
#define STEAM_CMD_CLEAR_REGISTER 0x88
|
||||
#define STEAM_CMD_READ_REGISTER 0x89
|
||||
#define STEAM_CMD_GET_REGISTER_LABEL 0x8a
|
||||
#define STEAM_CMD_GET_REGISTER_MAX 0x8b
|
||||
#define STEAM_CMD_GET_REGISTER_DEFAULT 0x8c
|
||||
#define STEAM_CMD_SET_MODE 0x8d
|
||||
#define STEAM_CMD_DEFAULT_MOUSE 0x8e
|
||||
#define STEAM_CMD_FORCEFEEDBAK 0x8f
|
||||
#define STEAM_CMD_REQUEST_COMM_STATUS 0xb4
|
||||
#define STEAM_CMD_GET_SERIAL 0xae
|
||||
#define STEAM_CMD_HAPTIC_RUMBLE 0xeb
|
||||
enum {
|
||||
ID_SET_DIGITAL_MAPPINGS = 0x80,
|
||||
ID_CLEAR_DIGITAL_MAPPINGS = 0x81,
|
||||
ID_GET_DIGITAL_MAPPINGS = 0x82,
|
||||
ID_GET_ATTRIBUTES_VALUES = 0x83,
|
||||
ID_GET_ATTRIBUTE_LABEL = 0x84,
|
||||
ID_SET_DEFAULT_DIGITAL_MAPPINGS = 0x85,
|
||||
ID_FACTORY_RESET = 0x86,
|
||||
ID_SET_SETTINGS_VALUES = 0x87,
|
||||
ID_CLEAR_SETTINGS_VALUES = 0x88,
|
||||
ID_GET_SETTINGS_VALUES = 0x89,
|
||||
ID_GET_SETTING_LABEL = 0x8A,
|
||||
ID_GET_SETTINGS_MAXS = 0x8B,
|
||||
ID_GET_SETTINGS_DEFAULTS = 0x8C,
|
||||
ID_SET_CONTROLLER_MODE = 0x8D,
|
||||
ID_LOAD_DEFAULT_SETTINGS = 0x8E,
|
||||
ID_TRIGGER_HAPTIC_PULSE = 0x8F,
|
||||
ID_TURN_OFF_CONTROLLER = 0x9F,
|
||||
|
||||
/* Some useful register ids */
|
||||
#define STEAM_REG_LPAD_MODE 0x07
|
||||
#define STEAM_REG_RPAD_MODE 0x08
|
||||
#define STEAM_REG_RPAD_MARGIN 0x18
|
||||
#define STEAM_REG_LED 0x2d
|
||||
#define STEAM_REG_GYRO_MODE 0x30
|
||||
#define STEAM_REG_LPAD_CLICK_PRESSURE 0x34
|
||||
#define STEAM_REG_RPAD_CLICK_PRESSURE 0x35
|
||||
ID_GET_DEVICE_INFO = 0xA1,
|
||||
|
||||
/* Raw event identifiers */
|
||||
#define STEAM_EV_INPUT_DATA 0x01
|
||||
#define STEAM_EV_CONNECT 0x03
|
||||
#define STEAM_EV_BATTERY 0x04
|
||||
#define STEAM_EV_DECK_INPUT_DATA 0x09
|
||||
ID_CALIBRATE_TRACKPADS = 0xA7,
|
||||
ID_RESERVED_0 = 0xA8,
|
||||
ID_SET_SERIAL_NUMBER = 0xA9,
|
||||
ID_GET_TRACKPAD_CALIBRATION = 0xAA,
|
||||
ID_GET_TRACKPAD_FACTORY_CALIBRATION = 0xAB,
|
||||
ID_GET_TRACKPAD_RAW_DATA = 0xAC,
|
||||
ID_ENABLE_PAIRING = 0xAD,
|
||||
ID_GET_STRING_ATTRIBUTE = 0xAE,
|
||||
ID_RADIO_ERASE_RECORDS = 0xAF,
|
||||
ID_RADIO_WRITE_RECORD = 0xB0,
|
||||
ID_SET_DONGLE_SETTING = 0xB1,
|
||||
ID_DONGLE_DISCONNECT_DEVICE = 0xB2,
|
||||
ID_DONGLE_COMMIT_DEVICE = 0xB3,
|
||||
ID_DONGLE_GET_WIRELESS_STATE = 0xB4,
|
||||
ID_CALIBRATE_GYRO = 0xB5,
|
||||
ID_PLAY_AUDIO = 0xB6,
|
||||
ID_AUDIO_UPDATE_START = 0xB7,
|
||||
ID_AUDIO_UPDATE_DATA = 0xB8,
|
||||
ID_AUDIO_UPDATE_COMPLETE = 0xB9,
|
||||
ID_GET_CHIPID = 0xBA,
|
||||
|
||||
ID_CALIBRATE_JOYSTICK = 0xBF,
|
||||
ID_CALIBRATE_ANALOG_TRIGGERS = 0xC0,
|
||||
ID_SET_AUDIO_MAPPING = 0xC1,
|
||||
ID_CHECK_GYRO_FW_LOAD = 0xC2,
|
||||
ID_CALIBRATE_ANALOG = 0xC3,
|
||||
ID_DONGLE_GET_CONNECTED_SLOTS = 0xC4,
|
||||
|
||||
ID_RESET_IMU = 0xCE,
|
||||
|
||||
ID_TRIGGER_HAPTIC_CMD = 0xEA,
|
||||
ID_TRIGGER_RUMBLE_CMD = 0xEB,
|
||||
};
|
||||
|
||||
/* Settings IDs */
|
||||
enum {
|
||||
/* 0 */
|
||||
SETTING_MOUSE_SENSITIVITY,
|
||||
SETTING_MOUSE_ACCELERATION,
|
||||
SETTING_TRACKBALL_ROTATION_ANGLE,
|
||||
SETTING_HAPTIC_INTENSITY_UNUSED,
|
||||
SETTING_LEFT_GAMEPAD_STICK_ENABLED,
|
||||
SETTING_RIGHT_GAMEPAD_STICK_ENABLED,
|
||||
SETTING_USB_DEBUG_MODE,
|
||||
SETTING_LEFT_TRACKPAD_MODE,
|
||||
SETTING_RIGHT_TRACKPAD_MODE,
|
||||
SETTING_MOUSE_POINTER_ENABLED,
|
||||
|
||||
/* 10 */
|
||||
SETTING_DPAD_DEADZONE,
|
||||
SETTING_MINIMUM_MOMENTUM_VEL,
|
||||
SETTING_MOMENTUM_DECAY_AMMOUNT,
|
||||
SETTING_TRACKPAD_RELATIVE_MODE_TICKS_PER_PIXEL,
|
||||
SETTING_HAPTIC_INCREMENT,
|
||||
SETTING_DPAD_ANGLE_SIN,
|
||||
SETTING_DPAD_ANGLE_COS,
|
||||
SETTING_MOMENTUM_VERTICAL_DIVISOR,
|
||||
SETTING_MOMENTUM_MAXIMUM_VELOCITY,
|
||||
SETTING_TRACKPAD_Z_ON,
|
||||
|
||||
/* 20 */
|
||||
SETTING_TRACKPAD_Z_OFF,
|
||||
SETTING_SENSITIVY_SCALE_AMMOUNT,
|
||||
SETTING_LEFT_TRACKPAD_SECONDARY_MODE,
|
||||
SETTING_RIGHT_TRACKPAD_SECONDARY_MODE,
|
||||
SETTING_SMOOTH_ABSOLUTE_MOUSE,
|
||||
SETTING_STEAMBUTTON_POWEROFF_TIME,
|
||||
SETTING_UNUSED_1,
|
||||
SETTING_TRACKPAD_OUTER_RADIUS,
|
||||
SETTING_TRACKPAD_Z_ON_LEFT,
|
||||
SETTING_TRACKPAD_Z_OFF_LEFT,
|
||||
|
||||
/* 30 */
|
||||
SETTING_TRACKPAD_OUTER_SPIN_VEL,
|
||||
SETTING_TRACKPAD_OUTER_SPIN_RADIUS,
|
||||
SETTING_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY,
|
||||
SETTING_TRACKPAD_RELATIVE_MODE_DEADZONE,
|
||||
SETTING_TRACKPAD_RELATIVE_MODE_MAX_VEL,
|
||||
SETTING_TRACKPAD_RELATIVE_MODE_INVERT_Y,
|
||||
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED,
|
||||
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD,
|
||||
SETTING_TRACKPAD_DOUBLE_TAP_BEEP_COUNT,
|
||||
SETTING_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION,
|
||||
|
||||
/* 40 */
|
||||
SETTING_RADIAL_MODE_ANGLE,
|
||||
SETTING_HAPTIC_INTENSITY_MOUSE_MODE,
|
||||
SETTING_LEFT_DPAD_REQUIRES_CLICK,
|
||||
SETTING_RIGHT_DPAD_REQUIRES_CLICK,
|
||||
SETTING_LED_BASELINE_BRIGHTNESS,
|
||||
SETTING_LED_USER_BRIGHTNESS,
|
||||
SETTING_ENABLE_RAW_JOYSTICK,
|
||||
SETTING_ENABLE_FAST_SCAN,
|
||||
SETTING_IMU_MODE,
|
||||
SETTING_WIRELESS_PACKET_VERSION,
|
||||
|
||||
/* 50 */
|
||||
SETTING_SLEEP_INACTIVITY_TIMEOUT,
|
||||
SETTING_TRACKPAD_NOISE_THRESHOLD,
|
||||
SETTING_LEFT_TRACKPAD_CLICK_PRESSURE,
|
||||
SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE,
|
||||
SETTING_LEFT_BUMPER_CLICK_PRESSURE,
|
||||
SETTING_RIGHT_BUMPER_CLICK_PRESSURE,
|
||||
SETTING_LEFT_GRIP_CLICK_PRESSURE,
|
||||
SETTING_RIGHT_GRIP_CLICK_PRESSURE,
|
||||
SETTING_LEFT_GRIP2_CLICK_PRESSURE,
|
||||
SETTING_RIGHT_GRIP2_CLICK_PRESSURE,
|
||||
|
||||
/* 60 */
|
||||
SETTING_PRESSURE_MODE,
|
||||
SETTING_CONTROLLER_TEST_MODE,
|
||||
SETTING_TRIGGER_MODE,
|
||||
SETTING_TRACKPAD_Z_THRESHOLD,
|
||||
SETTING_FRAME_RATE,
|
||||
SETTING_TRACKPAD_FILT_CTRL,
|
||||
SETTING_TRACKPAD_CLIP,
|
||||
SETTING_DEBUG_OUTPUT_SELECT,
|
||||
SETTING_TRIGGER_THRESHOLD_PERCENT,
|
||||
SETTING_TRACKPAD_FREQUENCY_HOPPING,
|
||||
|
||||
/* 70 */
|
||||
SETTING_HAPTICS_ENABLED,
|
||||
SETTING_STEAM_WATCHDOG_ENABLE,
|
||||
SETTING_TIMP_TOUCH_THRESHOLD_ON,
|
||||
SETTING_TIMP_TOUCH_THRESHOLD_OFF,
|
||||
SETTING_FREQ_HOPPING,
|
||||
SETTING_TEST_CONTROL,
|
||||
SETTING_HAPTIC_MASTER_GAIN_DB,
|
||||
SETTING_THUMB_TOUCH_THRESH,
|
||||
SETTING_DEVICE_POWER_STATUS,
|
||||
SETTING_HAPTIC_INTENSITY,
|
||||
|
||||
/* 80 */
|
||||
SETTING_STABILIZER_ENABLED,
|
||||
SETTING_TIMP_MODE_MTE,
|
||||
};
|
||||
|
||||
/* Input report identifiers */
|
||||
enum
|
||||
{
|
||||
ID_CONTROLLER_STATE = 1,
|
||||
ID_CONTROLLER_DEBUG = 2,
|
||||
ID_CONTROLLER_WIRELESS = 3,
|
||||
ID_CONTROLLER_STATUS = 4,
|
||||
ID_CONTROLLER_DEBUG2 = 5,
|
||||
ID_CONTROLLER_SECONDARY_STATE = 6,
|
||||
ID_CONTROLLER_BLE_STATE = 7,
|
||||
ID_CONTROLLER_DECK_STATE = 9
|
||||
};
|
||||
|
||||
/* String attribute idenitifiers */
|
||||
enum {
|
||||
ATTRIB_STR_BOARD_SERIAL,
|
||||
ATTRIB_STR_UNIT_SERIAL,
|
||||
};
|
||||
|
||||
/* Values for GYRO_MODE (bitmask) */
|
||||
#define STEAM_GYRO_MODE_OFF 0x0000
|
||||
#define STEAM_GYRO_MODE_STEERING 0x0001
|
||||
#define STEAM_GYRO_MODE_TILT 0x0002
|
||||
#define STEAM_GYRO_MODE_SEND_ORIENTATION 0x0004
|
||||
#define STEAM_GYRO_MODE_SEND_RAW_ACCEL 0x0008
|
||||
#define STEAM_GYRO_MODE_SEND_RAW_GYRO 0x0010
|
||||
enum {
|
||||
SETTING_GYRO_MODE_OFF = 0,
|
||||
SETTING_GYRO_MODE_STEERING = BIT(0),
|
||||
SETTING_GYRO_MODE_TILT = BIT(1),
|
||||
SETTING_GYRO_MODE_SEND_ORIENTATION = BIT(2),
|
||||
SETTING_GYRO_MODE_SEND_RAW_ACCEL = BIT(3),
|
||||
SETTING_GYRO_MODE_SEND_RAW_GYRO = BIT(4),
|
||||
};
|
||||
|
||||
/* Trackpad modes */
|
||||
enum {
|
||||
TRACKPAD_ABSOLUTE_MOUSE,
|
||||
TRACKPAD_RELATIVE_MOUSE,
|
||||
TRACKPAD_DPAD_FOUR_WAY_DISCRETE,
|
||||
TRACKPAD_DPAD_FOUR_WAY_OVERLAP,
|
||||
TRACKPAD_DPAD_EIGHT_WAY,
|
||||
TRACKPAD_RADIAL_MODE,
|
||||
TRACKPAD_ABSOLUTE_DPAD,
|
||||
TRACKPAD_NONE,
|
||||
TRACKPAD_GESTURE_KEYBOARD,
|
||||
};
|
||||
|
||||
/* Pad identifiers for the deck */
|
||||
#define STEAM_PAD_LEFT 0
|
||||
#define STEAM_PAD_RIGHT 1
|
||||
#define STEAM_PAD_BOTH 2
|
||||
|
||||
/* Other random constants */
|
||||
#define STEAM_SERIAL_LEN 10
|
||||
#define STEAM_SERIAL_LEN 0x15
|
||||
|
||||
struct steam_device {
|
||||
struct list_head list;
|
||||
spinlock_t lock;
|
||||
struct hid_device *hdev, *client_hdev;
|
||||
struct mutex mutex;
|
||||
bool client_opened;
|
||||
struct mutex report_mutex;
|
||||
unsigned long client_opened;
|
||||
struct input_dev __rcu *input;
|
||||
unsigned long quirks;
|
||||
struct work_struct work_connect;
|
||||
@ -134,7 +296,9 @@ struct steam_device {
|
||||
struct power_supply __rcu *battery;
|
||||
u8 battery_charge;
|
||||
u16 voltage;
|
||||
struct delayed_work heartbeat;
|
||||
struct delayed_work mode_switch;
|
||||
bool did_mode_switch;
|
||||
bool gamepad_mode;
|
||||
struct work_struct rumble_work;
|
||||
u16 rumble_left;
|
||||
u16 rumble_right;
|
||||
@ -226,13 +390,13 @@ static inline int steam_send_report_byte(struct steam_device *steam, u8 cmd)
|
||||
return steam_send_report(steam, &cmd, 1);
|
||||
}
|
||||
|
||||
static int steam_write_registers(struct steam_device *steam,
|
||||
static int steam_write_settings(struct steam_device *steam,
|
||||
/* u8 reg, u16 val */...)
|
||||
{
|
||||
/* Send: 0x87 len (reg valLo valHi)* */
|
||||
u8 reg;
|
||||
u16 val;
|
||||
u8 cmd[64] = {STEAM_CMD_WRITE_REGISTER, 0x00};
|
||||
u8 cmd[64] = {ID_SET_SETTINGS_VALUES, 0x00};
|
||||
int ret;
|
||||
va_list args;
|
||||
|
||||
@ -265,23 +429,29 @@ static int steam_get_serial(struct steam_device *steam)
|
||||
{
|
||||
/*
|
||||
* Send: 0xae 0x15 0x01
|
||||
* Recv: 0xae 0x15 0x01 serialnumber (10 chars)
|
||||
* Recv: 0xae 0x15 0x01 serialnumber
|
||||
*/
|
||||
int ret;
|
||||
u8 cmd[] = {STEAM_CMD_GET_SERIAL, 0x15, 0x01};
|
||||
int ret = 0;
|
||||
u8 cmd[] = {ID_GET_STRING_ATTRIBUTE, sizeof(steam->serial_no), ATTRIB_STR_UNIT_SERIAL};
|
||||
u8 reply[3 + STEAM_SERIAL_LEN + 1];
|
||||
|
||||
mutex_lock(&steam->report_mutex);
|
||||
ret = steam_send_report(steam, cmd, sizeof(cmd));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
goto out;
|
||||
ret = steam_recv_report(steam, reply, sizeof(reply));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != 0x01)
|
||||
return -EIO;
|
||||
goto out;
|
||||
if (reply[0] != ID_GET_STRING_ATTRIBUTE || reply[1] < 1 ||
|
||||
reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
reply[3 + STEAM_SERIAL_LEN] = 0;
|
||||
strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
|
||||
return 0;
|
||||
strscpy(steam->serial_no, reply + 3, reply[1]);
|
||||
out:
|
||||
mutex_unlock(&steam->report_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -291,14 +461,50 @@ static int steam_get_serial(struct steam_device *steam)
|
||||
*/
|
||||
static inline int steam_request_conn_status(struct steam_device *steam)
|
||||
{
|
||||
return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS);
|
||||
int ret;
|
||||
mutex_lock(&steam->report_mutex);
|
||||
ret = steam_send_report_byte(steam, ID_DONGLE_GET_WIRELESS_STATE);
|
||||
mutex_unlock(&steam->report_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a haptic pulse to the trackpads
|
||||
* Duration and interval are measured in microseconds, count is the number
|
||||
* of pulses to send for duration time with interval microseconds between them
|
||||
* and gain is measured in decibels, ranging from -24 to +6
|
||||
*/
|
||||
static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
|
||||
u16 duration, u16 interval, u16 count, u8 gain)
|
||||
{
|
||||
int ret;
|
||||
u8 report[10] = {ID_TRIGGER_HAPTIC_PULSE, 8};
|
||||
|
||||
/* Left and right are swapped on this report for legacy reasons */
|
||||
if (pad < STEAM_PAD_BOTH)
|
||||
pad ^= 1;
|
||||
|
||||
report[2] = pad;
|
||||
report[3] = duration & 0xFF;
|
||||
report[4] = duration >> 8;
|
||||
report[5] = interval & 0xFF;
|
||||
report[6] = interval >> 8;
|
||||
report[7] = count & 0xFF;
|
||||
report[8] = count >> 8;
|
||||
report[9] = gain;
|
||||
|
||||
mutex_lock(&steam->report_mutex);
|
||||
ret = steam_send_report(steam, report, sizeof(report));
|
||||
mutex_unlock(&steam->report_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int steam_haptic_rumble(struct steam_device *steam,
|
||||
u16 intensity, u16 left_speed, u16 right_speed,
|
||||
u8 left_gain, u8 right_gain)
|
||||
{
|
||||
u8 report[11] = {STEAM_CMD_HAPTIC_RUMBLE, 9};
|
||||
int ret;
|
||||
u8 report[11] = {ID_TRIGGER_RUMBLE_CMD, 9};
|
||||
|
||||
report[3] = intensity & 0xFF;
|
||||
report[4] = intensity >> 8;
|
||||
@ -309,7 +515,10 @@ static inline int steam_haptic_rumble(struct steam_device *steam,
|
||||
report[9] = left_gain;
|
||||
report[10] = right_gain;
|
||||
|
||||
return steam_send_report(steam, report, sizeof(report));
|
||||
mutex_lock(&steam->report_mutex);
|
||||
ret = steam_send_report(steam, report, sizeof(report));
|
||||
mutex_unlock(&steam->report_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steam_haptic_rumble_cb(struct work_struct *work)
|
||||
@ -335,40 +544,36 @@ static int steam_play_effect(struct input_dev *dev, void *data,
|
||||
|
||||
static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
/* enable esc, enter, cursors */
|
||||
steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
|
||||
/* enable mouse */
|
||||
steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MOUSE);
|
||||
steam_write_registers(steam,
|
||||
STEAM_REG_RPAD_MARGIN, 0x01, /* enable margin */
|
||||
0);
|
||||
if (steam->gamepad_mode)
|
||||
enable = false;
|
||||
|
||||
cancel_delayed_work_sync(&steam->heartbeat);
|
||||
if (enable) {
|
||||
mutex_lock(&steam->report_mutex);
|
||||
/* enable esc, enter, cursors */
|
||||
steam_send_report_byte(steam, ID_SET_DEFAULT_DIGITAL_MAPPINGS);
|
||||
/* reset settings */
|
||||
steam_send_report_byte(steam, ID_LOAD_DEFAULT_SETTINGS);
|
||||
mutex_unlock(&steam->report_mutex);
|
||||
} else {
|
||||
mutex_lock(&steam->report_mutex);
|
||||
/* disable esc, enter, cursor */
|
||||
steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
|
||||
steam_send_report_byte(steam, ID_CLEAR_DIGITAL_MAPPINGS);
|
||||
|
||||
if (steam->quirks & STEAM_QUIRK_DECK) {
|
||||
steam_write_registers(steam,
|
||||
STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
|
||||
STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
|
||||
STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
|
||||
STEAM_REG_LPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
|
||||
STEAM_REG_RPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
|
||||
steam_write_settings(steam,
|
||||
SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */
|
||||
SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */
|
||||
SETTING_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */
|
||||
SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */
|
||||
SETTING_STEAM_WATCHDOG_ENABLE, 0, /* disable watchdog that tests if Steam is active */
|
||||
0);
|
||||
/*
|
||||
* The Steam Deck has a watchdog that automatically enables
|
||||
* lizard mode if it doesn't see any traffic for too long
|
||||
*/
|
||||
if (!work_busy(&steam->heartbeat.work))
|
||||
schedule_delayed_work(&steam->heartbeat, 5 * HZ);
|
||||
mutex_unlock(&steam->report_mutex);
|
||||
} else {
|
||||
steam_write_registers(steam,
|
||||
STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
|
||||
STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
|
||||
STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
|
||||
steam_write_settings(steam,
|
||||
SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */
|
||||
SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */
|
||||
0);
|
||||
mutex_unlock(&steam->report_mutex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -376,22 +581,38 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||||
static int steam_input_open(struct input_dev *dev)
|
||||
{
|
||||
struct steam_device *steam = input_get_drvdata(dev);
|
||||
unsigned long flags;
|
||||
bool set_lizard_mode;
|
||||
|
||||
/*
|
||||
* Disabling lizard mode automatically is only done on the Steam
|
||||
* Controller. On the Steam Deck, this is toggled manually by holding
|
||||
* the options button instead, handled by steam_mode_switch_cb.
|
||||
*/
|
||||
if (!(steam->quirks & STEAM_QUIRK_DECK)) {
|
||||
spin_lock_irqsave(&steam->lock, flags);
|
||||
set_lizard_mode = !steam->client_opened && lizard_mode;
|
||||
spin_unlock_irqrestore(&steam->lock, flags);
|
||||
if (set_lizard_mode)
|
||||
steam_set_lizard_mode(steam, false);
|
||||
}
|
||||
|
||||
mutex_lock(&steam->mutex);
|
||||
if (!steam->client_opened && lizard_mode)
|
||||
steam_set_lizard_mode(steam, false);
|
||||
mutex_unlock(&steam->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void steam_input_close(struct input_dev *dev)
|
||||
{
|
||||
struct steam_device *steam = input_get_drvdata(dev);
|
||||
unsigned long flags;
|
||||
bool set_lizard_mode;
|
||||
|
||||
mutex_lock(&steam->mutex);
|
||||
if (!steam->client_opened && lizard_mode)
|
||||
steam_set_lizard_mode(steam, true);
|
||||
mutex_unlock(&steam->mutex);
|
||||
if (!(steam->quirks & STEAM_QUIRK_DECK)) {
|
||||
spin_lock_irqsave(&steam->lock, flags);
|
||||
set_lizard_mode = !steam->client_opened && lizard_mode;
|
||||
spin_unlock_irqrestore(&steam->lock, flags);
|
||||
if (set_lizard_mode)
|
||||
steam_set_lizard_mode(steam, true);
|
||||
}
|
||||
}
|
||||
|
||||
static enum power_supply_property steam_battery_props[] = {
|
||||
@ -635,7 +856,8 @@ static void steam_battery_unregister(struct steam_device *steam)
|
||||
static int steam_register(struct steam_device *steam)
|
||||
{
|
||||
int ret;
|
||||
bool client_opened;
|
||||
unsigned long client_opened;
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* This function can be called several times in a row with the
|
||||
@ -648,11 +870,9 @@ static int steam_register(struct steam_device *steam)
|
||||
* Unlikely, but getting the serial could fail, and it is not so
|
||||
* important, so make up a serial number and go on.
|
||||
*/
|
||||
mutex_lock(&steam->mutex);
|
||||
if (steam_get_serial(steam) < 0)
|
||||
strscpy(steam->serial_no, "XXXXXXXXXX",
|
||||
sizeof(steam->serial_no));
|
||||
mutex_unlock(&steam->mutex);
|
||||
|
||||
hid_info(steam->hdev, "Steam Controller '%s' connected",
|
||||
steam->serial_no);
|
||||
@ -667,15 +887,13 @@ static int steam_register(struct steam_device *steam)
|
||||
mutex_unlock(&steam_devices_lock);
|
||||
}
|
||||
|
||||
mutex_lock(&steam->mutex);
|
||||
spin_lock_irqsave(&steam->lock, flags);
|
||||
client_opened = steam->client_opened;
|
||||
if (!client_opened)
|
||||
spin_unlock_irqrestore(&steam->lock, flags);
|
||||
if (!client_opened) {
|
||||
steam_set_lizard_mode(steam, lizard_mode);
|
||||
mutex_unlock(&steam->mutex);
|
||||
|
||||
if (!client_opened)
|
||||
ret = steam_input_register(steam);
|
||||
else
|
||||
} else
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
@ -719,6 +937,34 @@ static void steam_work_connect_cb(struct work_struct *work)
|
||||
}
|
||||
}
|
||||
|
||||
static void steam_mode_switch_cb(struct work_struct *work)
|
||||
{
|
||||
struct steam_device *steam = container_of(to_delayed_work(work),
|
||||
struct steam_device, mode_switch);
|
||||
unsigned long flags;
|
||||
bool client_opened;
|
||||
steam->gamepad_mode = !steam->gamepad_mode;
|
||||
if (!lizard_mode)
|
||||
return;
|
||||
|
||||
if (steam->gamepad_mode)
|
||||
steam_set_lizard_mode(steam, false);
|
||||
else {
|
||||
spin_lock_irqsave(&steam->lock, flags);
|
||||
client_opened = steam->client_opened;
|
||||
spin_unlock_irqrestore(&steam->lock, flags);
|
||||
if (!client_opened)
|
||||
steam_set_lizard_mode(steam, lizard_mode);
|
||||
}
|
||||
|
||||
steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0);
|
||||
if (steam->gamepad_mode) {
|
||||
steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x14D, 0x14D, 0x2D, 0);
|
||||
} else {
|
||||
steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x1F4, 0x1F4, 0x1E, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool steam_is_valve_interface(struct hid_device *hdev)
|
||||
{
|
||||
struct hid_report_enum *rep_enum;
|
||||
@ -738,22 +984,6 @@ static bool steam_is_valve_interface(struct hid_device *hdev)
|
||||
return !list_empty(&rep_enum->report_list);
|
||||
}
|
||||
|
||||
static void steam_lizard_mode_heartbeat(struct work_struct *work)
|
||||
{
|
||||
struct steam_device *steam = container_of(work, struct steam_device,
|
||||
heartbeat.work);
|
||||
|
||||
mutex_lock(&steam->mutex);
|
||||
if (!steam->client_opened && steam->client_hdev) {
|
||||
steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
|
||||
steam_write_registers(steam,
|
||||
STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
|
||||
0);
|
||||
schedule_delayed_work(&steam->heartbeat, 5 * HZ);
|
||||
}
|
||||
mutex_unlock(&steam->mutex);
|
||||
}
|
||||
|
||||
static int steam_client_ll_parse(struct hid_device *hdev)
|
||||
{
|
||||
struct steam_device *steam = hdev->driver_data;
|
||||
@ -774,10 +1004,11 @@ static void steam_client_ll_stop(struct hid_device *hdev)
|
||||
static int steam_client_ll_open(struct hid_device *hdev)
|
||||
{
|
||||
struct steam_device *steam = hdev->driver_data;
|
||||
unsigned long flags;
|
||||
|
||||
mutex_lock(&steam->mutex);
|
||||
steam->client_opened = true;
|
||||
mutex_unlock(&steam->mutex);
|
||||
spin_lock_irqsave(&steam->lock, flags);
|
||||
steam->client_opened++;
|
||||
spin_unlock_irqrestore(&steam->lock, flags);
|
||||
|
||||
steam_input_unregister(steam);
|
||||
|
||||
@ -792,17 +1023,14 @@ static void steam_client_ll_close(struct hid_device *hdev)
|
||||
bool connected;
|
||||
|
||||
spin_lock_irqsave(&steam->lock, flags);
|
||||
connected = steam->connected;
|
||||
steam->client_opened--;
|
||||
connected = steam->connected && !steam->client_opened;
|
||||
spin_unlock_irqrestore(&steam->lock, flags);
|
||||
|
||||
mutex_lock(&steam->mutex);
|
||||
steam->client_opened = false;
|
||||
if (connected)
|
||||
if (connected) {
|
||||
steam_set_lizard_mode(steam, lizard_mode);
|
||||
mutex_unlock(&steam->mutex);
|
||||
|
||||
if (connected)
|
||||
steam_input_register(steam);
|
||||
}
|
||||
}
|
||||
|
||||
static int steam_client_ll_raw_request(struct hid_device *hdev,
|
||||
@ -888,20 +1116,13 @@ static int steam_probe(struct hid_device *hdev,
|
||||
steam->hdev = hdev;
|
||||
hid_set_drvdata(hdev, steam);
|
||||
spin_lock_init(&steam->lock);
|
||||
mutex_init(&steam->mutex);
|
||||
mutex_init(&steam->report_mutex);
|
||||
steam->quirks = id->driver_data;
|
||||
INIT_WORK(&steam->work_connect, steam_work_connect_cb);
|
||||
INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
|
||||
INIT_LIST_HEAD(&steam->list);
|
||||
INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
|
||||
INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
|
||||
|
||||
steam->client_hdev = steam_create_client_hid(hdev);
|
||||
if (IS_ERR(steam->client_hdev)) {
|
||||
ret = PTR_ERR(steam->client_hdev);
|
||||
goto client_hdev_fail;
|
||||
}
|
||||
steam->client_hdev->driver_data = steam;
|
||||
|
||||
/*
|
||||
* With the real steam controller interface, do not connect hidraw.
|
||||
* Instead, create the client_hid and connect that.
|
||||
@ -910,10 +1131,6 @@ static int steam_probe(struct hid_device *hdev,
|
||||
if (ret)
|
||||
goto hid_hw_start_fail;
|
||||
|
||||
ret = hid_add_device(steam->client_hdev);
|
||||
if (ret)
|
||||
goto client_hdev_add_fail;
|
||||
|
||||
ret = hid_hw_open(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev,
|
||||
@ -939,17 +1156,28 @@ static int steam_probe(struct hid_device *hdev,
|
||||
}
|
||||
}
|
||||
|
||||
steam->client_hdev = steam_create_client_hid(hdev);
|
||||
if (IS_ERR(steam->client_hdev)) {
|
||||
ret = PTR_ERR(steam->client_hdev);
|
||||
goto client_hdev_fail;
|
||||
}
|
||||
steam->client_hdev->driver_data = steam;
|
||||
|
||||
ret = hid_add_device(steam->client_hdev);
|
||||
if (ret)
|
||||
goto client_hdev_add_fail;
|
||||
|
||||
return 0;
|
||||
|
||||
input_register_fail:
|
||||
hid_hw_open_fail:
|
||||
client_hdev_add_fail:
|
||||
hid_hw_stop(hdev);
|
||||
hid_hw_start_fail:
|
||||
hid_destroy_device(steam->client_hdev);
|
||||
client_hdev_fail:
|
||||
hid_destroy_device(steam->client_hdev);
|
||||
input_register_fail:
|
||||
hid_hw_open_fail:
|
||||
hid_hw_start_fail:
|
||||
cancel_work_sync(&steam->work_connect);
|
||||
cancel_delayed_work_sync(&steam->heartbeat);
|
||||
cancel_delayed_work_sync(&steam->mode_switch);
|
||||
cancel_work_sync(&steam->rumble_work);
|
||||
steam_alloc_fail:
|
||||
hid_err(hdev, "%s: failed with error %d\n",
|
||||
@ -966,13 +1194,11 @@ static void steam_remove(struct hid_device *hdev)
|
||||
return;
|
||||
}
|
||||
|
||||
hid_destroy_device(steam->client_hdev);
|
||||
mutex_lock(&steam->mutex);
|
||||
steam->client_hdev = NULL;
|
||||
steam->client_opened = false;
|
||||
cancel_delayed_work_sync(&steam->heartbeat);
|
||||
mutex_unlock(&steam->mutex);
|
||||
cancel_delayed_work_sync(&steam->mode_switch);
|
||||
cancel_work_sync(&steam->work_connect);
|
||||
hid_destroy_device(steam->client_hdev);
|
||||
steam->client_hdev = NULL;
|
||||
steam->client_opened = 0;
|
||||
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
|
||||
hid_info(hdev, "Steam wireless receiver disconnected");
|
||||
}
|
||||
@ -1254,6 +1480,17 @@ static void steam_do_deck_input_event(struct steam_device *steam,
|
||||
b13 = data[13];
|
||||
b14 = data[14];
|
||||
|
||||
if (!(b9 & BIT(6)) && steam->did_mode_switch) {
|
||||
steam->did_mode_switch = false;
|
||||
cancel_delayed_work_sync(&steam->mode_switch);
|
||||
} else if (!steam->client_opened && (b9 & BIT(6)) && !steam->did_mode_switch) {
|
||||
steam->did_mode_switch = true;
|
||||
schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100);
|
||||
}
|
||||
|
||||
if (!steam->gamepad_mode)
|
||||
return;
|
||||
|
||||
lpad_touched = b10 & BIT(3);
|
||||
rpad_touched = b10 & BIT(4);
|
||||
|
||||
@ -1375,7 +1612,7 @@ static int steam_raw_event(struct hid_device *hdev,
|
||||
return 0;
|
||||
|
||||
switch (data[2]) {
|
||||
case STEAM_EV_INPUT_DATA:
|
||||
case ID_CONTROLLER_STATE:
|
||||
if (steam->client_opened)
|
||||
return 0;
|
||||
rcu_read_lock();
|
||||
@ -1384,7 +1621,7 @@ static int steam_raw_event(struct hid_device *hdev,
|
||||
steam_do_input_event(steam, input, data);
|
||||
rcu_read_unlock();
|
||||
break;
|
||||
case STEAM_EV_DECK_INPUT_DATA:
|
||||
case ID_CONTROLLER_DECK_STATE:
|
||||
if (steam->client_opened)
|
||||
return 0;
|
||||
rcu_read_lock();
|
||||
@ -1393,7 +1630,7 @@ static int steam_raw_event(struct hid_device *hdev,
|
||||
steam_do_deck_input_event(steam, input, data);
|
||||
rcu_read_unlock();
|
||||
break;
|
||||
case STEAM_EV_CONNECT:
|
||||
case ID_CONTROLLER_WIRELESS:
|
||||
/*
|
||||
* The payload of this event is a single byte:
|
||||
* 0x01: disconnected.
|
||||
@ -1408,7 +1645,7 @@ static int steam_raw_event(struct hid_device *hdev,
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STEAM_EV_BATTERY:
|
||||
case ID_CONTROLLER_STATUS:
|
||||
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
|
||||
rcu_read_lock();
|
||||
battery = rcu_dereference(steam->battery);
|
||||
@ -1439,10 +1676,8 @@ static int steam_param_set_lizard_mode(const char *val,
|
||||
|
||||
mutex_lock(&steam_devices_lock);
|
||||
list_for_each_entry(steam, &steam_devices, list) {
|
||||
mutex_lock(&steam->mutex);
|
||||
if (!steam->client_opened)
|
||||
steam_set_lizard_mode(steam, lizard_mode);
|
||||
mutex_unlock(&steam->mutex);
|
||||
}
|
||||
mutex_unlock(&steam_devices_lock);
|
||||
return 0;
|
||||
|
@ -44,12 +44,11 @@
|
||||
#include "i2c-hid.h"
|
||||
|
||||
/* quirks to control the device */
|
||||
#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0)
|
||||
#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(1)
|
||||
#define I2C_HID_QUIRK_BOGUS_IRQ BIT(4)
|
||||
#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(5)
|
||||
#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(6)
|
||||
#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(7)
|
||||
#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(0)
|
||||
#define I2C_HID_QUIRK_BOGUS_IRQ BIT(1)
|
||||
#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(2)
|
||||
#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(3)
|
||||
#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(4)
|
||||
|
||||
/* Command opcodes */
|
||||
#define I2C_HID_OPCODE_RESET 0x01
|
||||
@ -120,8 +119,6 @@ static const struct i2c_hid_quirks {
|
||||
__u16 idProduct;
|
||||
__u32 quirks;
|
||||
} i2c_hid_quirks[] = {
|
||||
{ USB_VENDOR_ID_WEIDA, HID_ANY_ID,
|
||||
I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
|
||||
{ I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288,
|
||||
I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
|
||||
{ I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15,
|
||||
@ -395,8 +392,7 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state)
|
||||
* The call will get a return value (EREMOTEIO) but device will be
|
||||
* triggered and activated. After that, it goes like a normal device.
|
||||
*/
|
||||
if (power_state == I2C_HID_PWR_ON &&
|
||||
ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) {
|
||||
if (power_state == I2C_HID_PWR_ON) {
|
||||
ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON);
|
||||
|
||||
/* Device was already activated */
|
||||
@ -426,12 +422,23 @@ set_pwr_exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int i2c_hid_execute_reset(struct i2c_hid *ihid)
|
||||
static int i2c_hid_start_hwreset(struct i2c_hid *ihid)
|
||||
{
|
||||
size_t length = 0;
|
||||
int ret;
|
||||
|
||||
i2c_hid_dbg(ihid, "resetting...\n");
|
||||
i2c_hid_dbg(ihid, "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* This prevents sending feature reports while the device is
|
||||
* being reset. Otherwise we may lose the reset complete
|
||||
* interrupt.
|
||||
*/
|
||||
lockdep_assert_held(&ihid->reset_lock);
|
||||
|
||||
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Prepare reset command. Command register goes first. */
|
||||
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
|
||||
@ -444,60 +451,40 @@ static int i2c_hid_execute_reset(struct i2c_hid *ihid)
|
||||
|
||||
ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0);
|
||||
if (ret) {
|
||||
dev_err(&ihid->client->dev, "failed to reset device.\n");
|
||||
goto out;
|
||||
dev_err(&ihid->client->dev,
|
||||
"failed to reset device: %d\n", ret);
|
||||
goto err_clear_reset;
|
||||
}
|
||||
|
||||
if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) {
|
||||
msleep(100);
|
||||
goto out;
|
||||
}
|
||||
return 0;
|
||||
|
||||
i2c_hid_dbg(ihid, "%s: waiting...\n", __func__);
|
||||
if (!wait_event_timeout(ihid->wait,
|
||||
!test_bit(I2C_HID_RESET_PENDING, &ihid->flags),
|
||||
msecs_to_jiffies(5000))) {
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
i2c_hid_dbg(ihid, "%s: finished.\n", __func__);
|
||||
|
||||
out:
|
||||
err_clear_reset:
|
||||
clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
|
||||
i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int i2c_hid_hwreset(struct i2c_hid *ihid)
|
||||
static int i2c_hid_finish_hwreset(struct i2c_hid *ihid)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
i2c_hid_dbg(ihid, "%s\n", __func__);
|
||||
i2c_hid_dbg(ihid, "%s: waiting...\n", __func__);
|
||||
|
||||
/*
|
||||
* This prevents sending feature reports while the device is
|
||||
* being reset. Otherwise we may lose the reset complete
|
||||
* interrupt.
|
||||
*/
|
||||
mutex_lock(&ihid->reset_lock);
|
||||
|
||||
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
|
||||
ret = i2c_hid_execute_reset(ihid);
|
||||
if (ret) {
|
||||
dev_err(&ihid->client->dev,
|
||||
"failed to reset device: %d\n", ret);
|
||||
i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
|
||||
goto out_unlock;
|
||||
if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) {
|
||||
msleep(100);
|
||||
clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
|
||||
} else if (!wait_event_timeout(ihid->wait,
|
||||
!test_bit(I2C_HID_RESET_PENDING, &ihid->flags),
|
||||
msecs_to_jiffies(1000))) {
|
||||
dev_warn(&ihid->client->dev, "device did not ack reset within 1000 ms\n");
|
||||
clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
|
||||
}
|
||||
i2c_hid_dbg(ihid, "%s: finished.\n", __func__);
|
||||
|
||||
/* At least some SIS devices need this after reset */
|
||||
if (!(ihid->quirks & I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET))
|
||||
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&ihid->reset_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -729,11 +716,10 @@ static int i2c_hid_parse(struct hid_device *hid)
|
||||
struct i2c_client *client = hid->driver_data;
|
||||
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
||||
struct i2c_hid_desc *hdesc = &ihid->hdesc;
|
||||
char *rdesc = NULL, *use_override = NULL;
|
||||
unsigned int rsize;
|
||||
char *rdesc;
|
||||
int ret;
|
||||
int tries = 3;
|
||||
char *use_override;
|
||||
|
||||
i2c_hid_dbg(ihid, "entering %s\n", __func__);
|
||||
|
||||
@ -743,14 +729,15 @@ static int i2c_hid_parse(struct hid_device *hid)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&ihid->reset_lock);
|
||||
do {
|
||||
ret = i2c_hid_hwreset(ihid);
|
||||
ret = i2c_hid_start_hwreset(ihid);
|
||||
if (ret)
|
||||
msleep(1000);
|
||||
} while (tries-- > 0 && ret);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
goto abort_reset;
|
||||
|
||||
use_override = i2c_hid_get_dmi_hid_report_desc_override(client->name,
|
||||
&rsize);
|
||||
@ -762,8 +749,8 @@ static int i2c_hid_parse(struct hid_device *hid)
|
||||
rdesc = kzalloc(rsize, GFP_KERNEL);
|
||||
|
||||
if (!rdesc) {
|
||||
dbg_hid("couldn't allocate rdesc memory\n");
|
||||
return -ENOMEM;
|
||||
ret = -ENOMEM;
|
||||
goto abort_reset;
|
||||
}
|
||||
|
||||
i2c_hid_dbg(ihid, "asking HID report descriptor\n");
|
||||
@ -773,23 +760,34 @@ static int i2c_hid_parse(struct hid_device *hid)
|
||||
rdesc, rsize);
|
||||
if (ret) {
|
||||
hid_err(hid, "reading report descriptor failed\n");
|
||||
kfree(rdesc);
|
||||
return -EIO;
|
||||
goto abort_reset;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Windows directly reads the report-descriptor after sending reset
|
||||
* and then waits for resets completion afterwards. Some touchpads
|
||||
* actually wait for the report-descriptor to be read before signalling
|
||||
* reset completion.
|
||||
*/
|
||||
ret = i2c_hid_finish_hwreset(ihid);
|
||||
abort_reset:
|
||||
clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
|
||||
mutex_unlock(&ihid->reset_lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
i2c_hid_dbg(ihid, "Report Descriptor: %*ph\n", rsize, rdesc);
|
||||
|
||||
ret = hid_parse_report(hid, rdesc, rsize);
|
||||
if (ret)
|
||||
dbg_hid("parsing report descriptor failed\n");
|
||||
|
||||
out:
|
||||
if (!use_override)
|
||||
kfree(rdesc);
|
||||
|
||||
if (ret) {
|
||||
dbg_hid("parsing report descriptor failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int i2c_hid_start(struct hid_device *hid)
|
||||
@ -987,10 +985,15 @@ static int i2c_hid_core_resume(struct i2c_hid *ihid)
|
||||
* However some ALPS touchpads generate IRQ storm without reset, so
|
||||
* let's still reset them here.
|
||||
*/
|
||||
if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME)
|
||||
ret = i2c_hid_hwreset(ihid);
|
||||
else
|
||||
if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME) {
|
||||
mutex_lock(&ihid->reset_lock);
|
||||
ret = i2c_hid_start_hwreset(ihid);
|
||||
if (ret == 0)
|
||||
ret = i2c_hid_finish_hwreset(ihid);
|
||||
mutex_unlock(&ihid->reset_lock);
|
||||
} else {
|
||||
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -130,9 +130,17 @@ static const struct elan_i2c_hid_chip_data ilitek_ili9882t_chip_data = {
|
||||
.main_supply_name = NULL,
|
||||
};
|
||||
|
||||
static const struct elan_i2c_hid_chip_data ilitek_ili2901_chip_data = {
|
||||
.post_power_delay_ms = 10,
|
||||
.post_gpio_reset_on_delay_ms = 100,
|
||||
.hid_descriptor_address = 0x0001,
|
||||
.main_supply_name = "vcc33",
|
||||
};
|
||||
|
||||
static const struct of_device_id elan_i2c_hid_of_match[] = {
|
||||
{ .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data },
|
||||
{ .compatible = "ilitek,ili9882t", .data = &ilitek_ili9882t_chip_data },
|
||||
{ .compatible = "ilitek,ili2901", .data = &ilitek_ili2901_chip_data },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match);
|
||||
|
@ -119,50 +119,6 @@ static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
|
||||
return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID;
|
||||
}
|
||||
|
||||
static int enable_gpe(struct device *dev)
|
||||
{
|
||||
#ifdef CONFIG_ACPI
|
||||
acpi_status acpi_sts;
|
||||
struct acpi_device *adev;
|
||||
struct acpi_device_wakeup *wakeup;
|
||||
|
||||
adev = ACPI_COMPANION(dev);
|
||||
if (!adev) {
|
||||
dev_err(dev, "get acpi handle failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
wakeup = &adev->wakeup;
|
||||
|
||||
/*
|
||||
* Call acpi_disable_gpe(), so that reference count
|
||||
* gpe_event_info->runtime_count doesn't overflow.
|
||||
* When gpe_event_info->runtime_count = 0, the call
|
||||
* to acpi_disable_gpe() simply return.
|
||||
*/
|
||||
acpi_disable_gpe(wakeup->gpe_device, wakeup->gpe_number);
|
||||
|
||||
acpi_sts = acpi_enable_gpe(wakeup->gpe_device, wakeup->gpe_number);
|
||||
if (ACPI_FAILURE(acpi_sts)) {
|
||||
dev_err(dev, "enable ose_gpe failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return -ENODEV;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void enable_pme_wake(struct pci_dev *pdev)
|
||||
{
|
||||
if ((pci_pme_capable(pdev, PCI_D0) ||
|
||||
pci_pme_capable(pdev, PCI_D3hot) ||
|
||||
pci_pme_capable(pdev, PCI_D3cold)) && !enable_gpe(&pdev->dev)) {
|
||||
pci_pme_active(pdev, true);
|
||||
dev_dbg(&pdev->dev, "ish ipc driver pme wake enabled\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ish_probe() - PCI driver probe callback
|
||||
* @pdev: pci device
|
||||
@ -233,7 +189,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||
|
||||
/* Enable PME for EHL */
|
||||
if (pdev->device == EHL_Ax_DEVICE_ID)
|
||||
enable_pme_wake(pdev);
|
||||
device_init_wakeup(dev, true);
|
||||
|
||||
ret = ish_init(ishtp);
|
||||
if (ret)
|
||||
@ -256,6 +212,19 @@ static void ish_remove(struct pci_dev *pdev)
|
||||
ish_device_disable(ishtp_dev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ish_shutdown() - PCI driver shutdown callback
|
||||
* @pdev: pci device
|
||||
*
|
||||
* This function sets up wakeup for S5
|
||||
*/
|
||||
static void ish_shutdown(struct pci_dev *pdev)
|
||||
{
|
||||
if (pdev->device == EHL_Ax_DEVICE_ID)
|
||||
pci_prepare_to_sleep(pdev);
|
||||
}
|
||||
|
||||
static struct device __maybe_unused *ish_resume_device;
|
||||
|
||||
/* 50ms to get resume response */
|
||||
@ -378,13 +347,6 @@ static int __maybe_unused ish_resume(struct device *device)
|
||||
struct pci_dev *pdev = to_pci_dev(device);
|
||||
struct ishtp_device *dev = pci_get_drvdata(pdev);
|
||||
|
||||
/* add this to finish power flow for EHL */
|
||||
if (dev->pdev->device == EHL_Ax_DEVICE_ID) {
|
||||
pci_set_power_state(pdev, PCI_D0);
|
||||
enable_pme_wake(pdev);
|
||||
dev_dbg(dev->devc, "set power state to D0 for ehl\n");
|
||||
}
|
||||
|
||||
ish_resume_device = device;
|
||||
dev->resume_flag = 1;
|
||||
|
||||
@ -400,6 +362,7 @@ static struct pci_driver ish_driver = {
|
||||
.id_table = ish_pci_tbl,
|
||||
.probe = ish_probe,
|
||||
.remove = ish_remove,
|
||||
.shutdown = ish_shutdown,
|
||||
.driver.pm = &ish_pm_ops,
|
||||
};
|
||||
|
||||
|
@ -840,43 +840,22 @@ static void load_fw_from_host_handler(struct work_struct *work)
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure
|
||||
*/
|
||||
static int loader_init(struct ishtp_cl *loader_ishtp_cl, int reset)
|
||||
static int loader_init(struct ishtp_cl *loader_ishtp_cl, bool reset)
|
||||
{
|
||||
int rv;
|
||||
struct ishtp_fw_client *fw_client;
|
||||
struct ishtp_cl_data *client_data =
|
||||
ishtp_get_client_data(loader_ishtp_cl);
|
||||
|
||||
dev_dbg(cl_data_to_dev(client_data), "reset flag: %d\n", reset);
|
||||
|
||||
rv = ishtp_cl_link(loader_ishtp_cl);
|
||||
if (rv < 0) {
|
||||
dev_err(cl_data_to_dev(client_data), "ishtp_cl_link failed\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Connect to firmware client */
|
||||
ishtp_set_tx_ring_size(loader_ishtp_cl, LOADER_CL_TX_RING_SIZE);
|
||||
ishtp_set_rx_ring_size(loader_ishtp_cl, LOADER_CL_RX_RING_SIZE);
|
||||
|
||||
fw_client =
|
||||
ishtp_fw_cl_get_client(ishtp_get_ishtp_device(loader_ishtp_cl),
|
||||
&loader_ishtp_id_table[0].guid);
|
||||
if (!fw_client) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ISH client uuid not found\n");
|
||||
rv = -ENOENT;
|
||||
goto err_cl_unlink;
|
||||
}
|
||||
|
||||
ishtp_cl_set_fw_client_id(loader_ishtp_cl,
|
||||
ishtp_get_fw_client_id(fw_client));
|
||||
ishtp_set_connection_state(loader_ishtp_cl, ISHTP_CL_CONNECTING);
|
||||
|
||||
rv = ishtp_cl_connect(loader_ishtp_cl);
|
||||
rv = ishtp_cl_establish_connection(loader_ishtp_cl,
|
||||
&loader_ishtp_id_table[0].guid,
|
||||
LOADER_CL_TX_RING_SIZE,
|
||||
LOADER_CL_RX_RING_SIZE,
|
||||
reset);
|
||||
if (rv < 0) {
|
||||
dev_err(cl_data_to_dev(client_data), "Client connect fail\n");
|
||||
goto err_cl_unlink;
|
||||
goto err_cl_disconnect;
|
||||
}
|
||||
|
||||
dev_dbg(cl_data_to_dev(client_data), "Client connected\n");
|
||||
@ -885,17 +864,14 @@ static int loader_init(struct ishtp_cl *loader_ishtp_cl, int reset)
|
||||
|
||||
return 0;
|
||||
|
||||
err_cl_unlink:
|
||||
ishtp_cl_unlink(loader_ishtp_cl);
|
||||
err_cl_disconnect:
|
||||
ishtp_cl_destroy_connection(loader_ishtp_cl, reset);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void loader_deinit(struct ishtp_cl *loader_ishtp_cl)
|
||||
{
|
||||
ishtp_set_connection_state(loader_ishtp_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(loader_ishtp_cl);
|
||||
ishtp_cl_unlink(loader_ishtp_cl);
|
||||
ishtp_cl_flush_queues(loader_ishtp_cl);
|
||||
ishtp_cl_destroy_connection(loader_ishtp_cl, false);
|
||||
|
||||
/* Disband and free all Tx and Rx client-level rings */
|
||||
ishtp_cl_free(loader_ishtp_cl);
|
||||
@ -914,19 +890,7 @@ static void reset_handler(struct work_struct *work)
|
||||
loader_ishtp_cl = client_data->loader_ishtp_cl;
|
||||
cl_device = client_data->cl_device;
|
||||
|
||||
/* Unlink, flush queues & start again */
|
||||
ishtp_cl_unlink(loader_ishtp_cl);
|
||||
ishtp_cl_flush_queues(loader_ishtp_cl);
|
||||
ishtp_cl_free(loader_ishtp_cl);
|
||||
|
||||
loader_ishtp_cl = ishtp_cl_allocate(cl_device);
|
||||
if (!loader_ishtp_cl)
|
||||
return;
|
||||
|
||||
ishtp_set_drvdata(cl_device, loader_ishtp_cl);
|
||||
ishtp_set_client_data(loader_ishtp_cl, client_data);
|
||||
client_data->loader_ishtp_cl = loader_ishtp_cl;
|
||||
client_data->cl_device = cl_device;
|
||||
ishtp_cl_destroy_connection(loader_ishtp_cl, true);
|
||||
|
||||
rv = loader_init(loader_ishtp_cl, 1);
|
||||
if (rv < 0) {
|
||||
@ -974,7 +938,7 @@ static int loader_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
|
||||
INIT_WORK(&client_data->work_fw_load,
|
||||
load_fw_from_host_handler);
|
||||
|
||||
rv = loader_init(loader_ishtp_cl, 0);
|
||||
rv = loader_init(loader_ishtp_cl, false);
|
||||
if (rv < 0) {
|
||||
ishtp_cl_free(loader_ishtp_cl);
|
||||
return rv;
|
||||
|
@ -639,47 +639,26 @@ static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl,
|
||||
*
|
||||
* Return: 0 on success, non zero on error
|
||||
*/
|
||||
static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset)
|
||||
static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, bool reset)
|
||||
{
|
||||
struct ishtp_device *dev;
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
|
||||
struct ishtp_fw_client *fw_client;
|
||||
int i;
|
||||
int rv;
|
||||
|
||||
dev_dbg(cl_data_to_dev(client_data), "%s\n", __func__);
|
||||
hid_ishtp_trace(client_data, "%s reset flag: %d\n", __func__, reset);
|
||||
|
||||
rv = ishtp_cl_link(hid_ishtp_cl);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ishtp_cl_link failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
client_data->init_done = 0;
|
||||
|
||||
dev = ishtp_get_ishtp_device(hid_ishtp_cl);
|
||||
|
||||
/* Connect to FW client */
|
||||
ishtp_set_tx_ring_size(hid_ishtp_cl, HID_CL_TX_RING_SIZE);
|
||||
ishtp_set_rx_ring_size(hid_ishtp_cl, HID_CL_RX_RING_SIZE);
|
||||
|
||||
fw_client = ishtp_fw_cl_get_client(dev, &hid_ishtp_id_table[0].guid);
|
||||
if (!fw_client) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ish client uuid not found\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
ishtp_cl_set_fw_client_id(hid_ishtp_cl,
|
||||
ishtp_get_fw_client_id(fw_client));
|
||||
ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_CONNECTING);
|
||||
|
||||
rv = ishtp_cl_connect(hid_ishtp_cl);
|
||||
rv = ishtp_cl_establish_connection(hid_ishtp_cl,
|
||||
&hid_ishtp_id_table[0].guid,
|
||||
HID_CL_TX_RING_SIZE,
|
||||
HID_CL_RX_RING_SIZE,
|
||||
reset);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"client connect fail\n");
|
||||
goto err_cl_unlink;
|
||||
goto err_cl_disconnect;
|
||||
}
|
||||
|
||||
hid_ishtp_trace(client_data, "%s client connected\n", __func__);
|
||||
@ -723,10 +702,7 @@ static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset)
|
||||
return 0;
|
||||
|
||||
err_cl_disconnect:
|
||||
ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(hid_ishtp_cl);
|
||||
err_cl_unlink:
|
||||
ishtp_cl_unlink(hid_ishtp_cl);
|
||||
ishtp_cl_destroy_connection(hid_ishtp_cl, reset);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -738,8 +714,7 @@ err_cl_unlink:
|
||||
*/
|
||||
static void hid_ishtp_cl_deinit(struct ishtp_cl *hid_ishtp_cl)
|
||||
{
|
||||
ishtp_cl_unlink(hid_ishtp_cl);
|
||||
ishtp_cl_flush_queues(hid_ishtp_cl);
|
||||
ishtp_cl_destroy_connection(hid_ishtp_cl, false);
|
||||
|
||||
/* disband and free all Tx and Rx client-level rings */
|
||||
ishtp_cl_free(hid_ishtp_cl);
|
||||
@ -749,33 +724,23 @@ static void hid_ishtp_cl_reset_handler(struct work_struct *work)
|
||||
{
|
||||
struct ishtp_cl_data *client_data;
|
||||
struct ishtp_cl *hid_ishtp_cl;
|
||||
struct ishtp_cl_device *cl_device;
|
||||
int retry;
|
||||
int rv;
|
||||
|
||||
client_data = container_of(work, struct ishtp_cl_data, work);
|
||||
|
||||
hid_ishtp_cl = client_data->hid_ishtp_cl;
|
||||
cl_device = client_data->cl_device;
|
||||
|
||||
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
|
||||
hid_ishtp_cl);
|
||||
dev_dbg(ishtp_device(client_data->cl_device), "%s\n", __func__);
|
||||
|
||||
hid_ishtp_cl_deinit(hid_ishtp_cl);
|
||||
|
||||
hid_ishtp_cl = ishtp_cl_allocate(cl_device);
|
||||
if (!hid_ishtp_cl)
|
||||
return;
|
||||
|
||||
ishtp_set_drvdata(cl_device, hid_ishtp_cl);
|
||||
ishtp_set_client_data(hid_ishtp_cl, client_data);
|
||||
client_data->hid_ishtp_cl = hid_ishtp_cl;
|
||||
ishtp_cl_destroy_connection(hid_ishtp_cl, true);
|
||||
|
||||
client_data->num_hid_devices = 0;
|
||||
|
||||
for (retry = 0; retry < 3; ++retry) {
|
||||
rv = hid_ishtp_cl_init(hid_ishtp_cl, 1);
|
||||
rv = hid_ishtp_cl_init(hid_ishtp_cl, true);
|
||||
if (!rv)
|
||||
break;
|
||||
dev_err(cl_data_to_dev(client_data), "Retry reset init\n");
|
||||
@ -841,7 +806,7 @@ static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
|
||||
|
||||
ishtp_hid_print_trace = ishtp_trace_callback(cl_device);
|
||||
|
||||
rv = hid_ishtp_cl_init(hid_ishtp_cl, 0);
|
||||
rv = hid_ishtp_cl_init(hid_ishtp_cl, false);
|
||||
if (rv) {
|
||||
ishtp_cl_free(hid_ishtp_cl);
|
||||
return rv;
|
||||
@ -868,11 +833,9 @@ static void hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device)
|
||||
hid_ishtp_cl);
|
||||
|
||||
dev_dbg(ishtp_device(cl_device), "%s\n", __func__);
|
||||
ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(hid_ishtp_cl);
|
||||
hid_ishtp_cl_deinit(hid_ishtp_cl);
|
||||
ishtp_put_device(cl_device);
|
||||
ishtp_hid_remove(client_data);
|
||||
hid_ishtp_cl_deinit(hid_ishtp_cl);
|
||||
|
||||
hid_ishtp_cl = NULL;
|
||||
|
||||
|
@ -378,7 +378,7 @@ static const struct dev_pm_ops ishtp_cl_bus_dev_pm_ops = {
|
||||
.restore = ishtp_cl_device_resume,
|
||||
};
|
||||
|
||||
static struct bus_type ishtp_cl_bus_type = {
|
||||
static const struct bus_type ishtp_cl_bus_type = {
|
||||
.name = "ishtp",
|
||||
.dev_groups = ishtp_cl_dev_groups,
|
||||
.probe = ishtp_cl_device_probe,
|
||||
|
@ -339,16 +339,17 @@ static bool ishtp_cl_is_other_connecting(struct ishtp_cl *cl)
|
||||
}
|
||||
|
||||
/**
|
||||
* ishtp_cl_connect() - Send connect request to firmware
|
||||
* ishtp_cl_connect_to_fw() - Send connect request to firmware
|
||||
* @cl: client device instance
|
||||
*
|
||||
* Send a connect request for a client to firmware. If successful it will
|
||||
* RX and TX ring buffers
|
||||
* Send a connect request to the firmware and wait for firmware response.
|
||||
* If there is successful connection response from the firmware, change
|
||||
* client state to ISHTP_CL_CONNECTED, and bind client to related
|
||||
* firmware client_id.
|
||||
*
|
||||
* Return: 0 if successful connect response from the firmware and able
|
||||
* to bind and allocate ring buffers or error code on failure
|
||||
* Return: 0 for success and error code on failure
|
||||
*/
|
||||
int ishtp_cl_connect(struct ishtp_cl *cl)
|
||||
static int ishtp_cl_connect_to_fw(struct ishtp_cl *cl)
|
||||
{
|
||||
struct ishtp_device *dev;
|
||||
int rets;
|
||||
@ -358,8 +359,6 @@ int ishtp_cl_connect(struct ishtp_cl *cl)
|
||||
|
||||
dev = cl->dev;
|
||||
|
||||
dev->print_log(dev, "%s() current_state = %d\n", __func__, cl->state);
|
||||
|
||||
if (ishtp_cl_is_other_connecting(cl)) {
|
||||
dev->print_log(dev, "%s() Busy\n", __func__);
|
||||
return -EBUSY;
|
||||
@ -405,6 +404,38 @@ int ishtp_cl_connect(struct ishtp_cl *cl)
|
||||
return rets;
|
||||
}
|
||||
|
||||
return rets;
|
||||
}
|
||||
|
||||
/**
|
||||
* ishtp_cl_connect() - Build connection with firmware
|
||||
* @cl: client device instance
|
||||
*
|
||||
* Call ishtp_cl_connect_to_fw() to connect and bind to firmware. If successful,
|
||||
* allocate RX and TX ring buffers, and start flow control with firmware to
|
||||
* start communication.
|
||||
*
|
||||
* Return: 0 if there is successful connection to the firmware, allocate
|
||||
* ring buffers.
|
||||
*/
|
||||
int ishtp_cl_connect(struct ishtp_cl *cl)
|
||||
{
|
||||
struct ishtp_device *dev;
|
||||
int rets;
|
||||
|
||||
if (!cl || !cl->dev)
|
||||
return -ENODEV;
|
||||
|
||||
dev = cl->dev;
|
||||
|
||||
dev->print_log(dev, "%s() current_state = %d\n", __func__, cl->state);
|
||||
|
||||
rets = ishtp_cl_connect_to_fw(cl);
|
||||
if (rets) {
|
||||
dev->print_log(dev, "%s() Connect to fw failed\n", __func__);
|
||||
return rets;
|
||||
}
|
||||
|
||||
rets = ishtp_cl_alloc_rx_ring(cl);
|
||||
if (rets) {
|
||||
dev->print_log(dev, "%s() Alloc RX ring failed\n", __func__);
|
||||
@ -422,15 +453,147 @@ int ishtp_cl_connect(struct ishtp_cl *cl)
|
||||
return rets;
|
||||
}
|
||||
|
||||
/* Upon successful connection and allocation, emit flow-control */
|
||||
/*
|
||||
* Upon successful connection and allocation, start flow-control.
|
||||
*/
|
||||
rets = ishtp_cl_read_start(cl);
|
||||
|
||||
dev->print_log(dev, "%s() successful\n", __func__);
|
||||
|
||||
return rets;
|
||||
}
|
||||
EXPORT_SYMBOL(ishtp_cl_connect);
|
||||
|
||||
/**
|
||||
* ishtp_cl_establish_connection() - Establish connection with the firmware
|
||||
* @cl: client device instance
|
||||
* @uuid: uuid of the client to search
|
||||
* @tx_size: TX ring buffer size
|
||||
* @rx_size: RX ring buffer size
|
||||
* @reset: true if called for reset connection, otherwise for first connection
|
||||
*
|
||||
* This is a helper function for client driver to build connection with firmware.
|
||||
* If it's first time connecting to the firmware, set reset to false, this
|
||||
* function will link client to bus, find client id and send connect request to
|
||||
* the firmware.
|
||||
*
|
||||
* If it's called for reset handler where client lost connection after
|
||||
* firmware reset, set reset to true, this function will reinit client state and
|
||||
* establish connection again. In this case, this function reuses current client
|
||||
* structure and ring buffers to avoid allocation failure and memory fragments.
|
||||
*
|
||||
* Return: 0 for successful connection with the firmware,
|
||||
* or error code on failure
|
||||
*/
|
||||
int ishtp_cl_establish_connection(struct ishtp_cl *cl, const guid_t *uuid,
|
||||
int tx_size, int rx_size, bool reset)
|
||||
{
|
||||
struct ishtp_device *dev;
|
||||
struct ishtp_fw_client *fw_client;
|
||||
int rets;
|
||||
|
||||
if (!cl || !cl->dev)
|
||||
return -ENODEV;
|
||||
|
||||
dev = cl->dev;
|
||||
|
||||
ishtp_set_connection_state(cl, ISHTP_CL_INITIALIZING);
|
||||
|
||||
/* reinit ishtp_cl structure if call for reset */
|
||||
if (reset) {
|
||||
cl->host_client_id = 0;
|
||||
cl->fw_client_id = 0;
|
||||
cl->ishtp_flow_ctrl_creds = 0;
|
||||
cl->out_flow_ctrl_creds = 0;
|
||||
|
||||
cl->last_tx_path = CL_TX_PATH_IPC;
|
||||
cl->last_dma_acked = 1;
|
||||
cl->last_dma_addr = NULL;
|
||||
cl->last_ipc_acked = 1;
|
||||
|
||||
cl->sending = 0;
|
||||
cl->err_send_msg = 0;
|
||||
cl->err_send_fc = 0;
|
||||
|
||||
cl->send_msg_cnt_ipc = 0;
|
||||
cl->send_msg_cnt_dma = 0;
|
||||
cl->recv_msg_cnt_ipc = 0;
|
||||
cl->recv_msg_cnt_dma = 0;
|
||||
cl->recv_msg_num_frags = 0;
|
||||
cl->ishtp_flow_ctrl_cnt = 0;
|
||||
cl->out_flow_ctrl_cnt = 0;
|
||||
}
|
||||
|
||||
/* link to bus */
|
||||
rets = ishtp_cl_link(cl);
|
||||
if (rets) {
|
||||
dev->print_log(dev, "%s() ishtp_cl_link failed\n", __func__);
|
||||
return rets;
|
||||
}
|
||||
|
||||
/* find firmware client */
|
||||
fw_client = ishtp_fw_cl_get_client(dev, uuid);
|
||||
if (!fw_client) {
|
||||
dev->print_log(dev,
|
||||
"%s() ish client uuid not found\n", __func__);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
ishtp_set_tx_ring_size(cl, tx_size);
|
||||
ishtp_set_rx_ring_size(cl, rx_size);
|
||||
|
||||
ishtp_cl_set_fw_client_id(cl, ishtp_get_fw_client_id(fw_client));
|
||||
|
||||
ishtp_set_connection_state(cl, ISHTP_CL_CONNECTING);
|
||||
|
||||
/*
|
||||
* For reset case, not allocate tx/rx ring buffer which are already
|
||||
* done in ishtp_cl_connect() during first connection.
|
||||
*/
|
||||
if (reset) {
|
||||
rets = ishtp_cl_connect_to_fw(cl);
|
||||
if (!rets)
|
||||
rets = ishtp_cl_read_start(cl);
|
||||
else
|
||||
dev->print_log(dev,
|
||||
"%s() connect to fw failed\n", __func__);
|
||||
} else {
|
||||
rets = ishtp_cl_connect(cl);
|
||||
}
|
||||
|
||||
return rets;
|
||||
}
|
||||
EXPORT_SYMBOL(ishtp_cl_establish_connection);
|
||||
|
||||
/**
|
||||
* ishtp_cl_destroy_connection() - Disconnect with the firmware
|
||||
* @cl: client device instance
|
||||
* @reset: true if called for firmware reset, false for normal disconnection
|
||||
*
|
||||
* This is a helper function for client driver to disconnect with firmware,
|
||||
* unlink to bus and flush message queue.
|
||||
*/
|
||||
void ishtp_cl_destroy_connection(struct ishtp_cl *cl, bool reset)
|
||||
{
|
||||
if (!cl)
|
||||
return;
|
||||
|
||||
if (reset) {
|
||||
/*
|
||||
* For reset case, connection is already lost during fw reset.
|
||||
* Just set state to DISCONNECTED is enough.
|
||||
*/
|
||||
ishtp_set_connection_state(cl, ISHTP_CL_DISCONNECTED);
|
||||
} else {
|
||||
if (cl->state != ISHTP_CL_DISCONNECTED) {
|
||||
ishtp_set_connection_state(cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(cl);
|
||||
}
|
||||
}
|
||||
|
||||
ishtp_cl_unlink(cl);
|
||||
ishtp_cl_flush_queues(cl);
|
||||
}
|
||||
EXPORT_SYMBOL(ishtp_cl_destroy_connection);
|
||||
|
||||
/**
|
||||
* ishtp_cl_read_start() - Prepare to read client message
|
||||
* @cl: client device instance
|
||||
|
@ -164,6 +164,7 @@ struct wacom {
|
||||
struct work_struct battery_work;
|
||||
struct work_struct remote_work;
|
||||
struct delayed_work init_work;
|
||||
struct delayed_work aes_battery_work;
|
||||
struct wacom_remote *remote;
|
||||
struct work_struct mode_change_work;
|
||||
struct timer_list idleprox_timer;
|
||||
|
@ -1813,6 +1813,13 @@ static void wacom_destroy_battery(struct wacom *wacom)
|
||||
}
|
||||
}
|
||||
|
||||
static void wacom_aes_battery_handler(struct work_struct *work)
|
||||
{
|
||||
struct wacom *wacom = container_of(work, struct wacom, aes_battery_work.work);
|
||||
|
||||
wacom_destroy_battery(wacom);
|
||||
}
|
||||
|
||||
static ssize_t wacom_show_speed(struct device *dev,
|
||||
struct device_attribute
|
||||
*attr, char *buf)
|
||||
@ -2794,6 +2801,7 @@ static int wacom_probe(struct hid_device *hdev,
|
||||
|
||||
mutex_init(&wacom->lock);
|
||||
INIT_DELAYED_WORK(&wacom->init_work, wacom_init_work);
|
||||
INIT_DELAYED_WORK(&wacom->aes_battery_work, wacom_aes_battery_handler);
|
||||
INIT_WORK(&wacom->wireless_work, wacom_wireless_work);
|
||||
INIT_WORK(&wacom->battery_work, wacom_battery_work);
|
||||
INIT_WORK(&wacom->remote_work, wacom_remote_work);
|
||||
|
@ -2528,11 +2528,12 @@ static void wacom_wac_pen_report(struct hid_device *hdev,
|
||||
struct input_dev *input = wacom_wac->pen_input;
|
||||
bool range = wacom_wac->hid_data.inrange_state;
|
||||
bool sense = wacom_wac->hid_data.sense_state;
|
||||
bool entering_range = !wacom_wac->tool[0] && range;
|
||||
|
||||
if (wacom_wac->is_invalid_bt_frame)
|
||||
return;
|
||||
|
||||
if (!wacom_wac->tool[0] && range) { /* first in range */
|
||||
if (entering_range) { /* first in range */
|
||||
/* Going into range select tool */
|
||||
if (wacom_wac->hid_data.invert_state)
|
||||
wacom_wac->tool[0] = BTN_TOOL_RUBBER;
|
||||
@ -2583,6 +2584,15 @@ static void wacom_wac_pen_report(struct hid_device *hdev,
|
||||
input_sync(input);
|
||||
}
|
||||
|
||||
/* Handle AES battery timeout behavior */
|
||||
if (wacom_wac->features.quirks & WACOM_QUIRK_AESPEN) {
|
||||
if (entering_range)
|
||||
cancel_delayed_work(&wacom->aes_battery_work);
|
||||
if (!sense)
|
||||
schedule_delayed_work(&wacom->aes_battery_work,
|
||||
msecs_to_jiffies(WACOM_AES_BATTERY_TIMEOUT));
|
||||
}
|
||||
|
||||
if (!sense) {
|
||||
wacom_wac->tool[0] = 0;
|
||||
wacom_wac->id[0] = 0;
|
||||
@ -2649,8 +2659,8 @@ static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac,
|
||||
{
|
||||
struct hid_data *hid_data = &wacom_wac->hid_data;
|
||||
bool mt = wacom_wac->features.touch_max > 1;
|
||||
bool prox = hid_data->tipswitch &&
|
||||
report_touch_events(wacom_wac);
|
||||
bool touch_down = hid_data->tipswitch && hid_data->confidence;
|
||||
bool prox = touch_down && report_touch_events(wacom_wac);
|
||||
|
||||
if (touch_is_muted(wacom_wac)) {
|
||||
if (!wacom_wac->shared->touch_down)
|
||||
@ -2700,24 +2710,6 @@ static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac,
|
||||
}
|
||||
}
|
||||
|
||||
static bool wacom_wac_slot_is_active(struct input_dev *dev, int key)
|
||||
{
|
||||
struct input_mt *mt = dev->mt;
|
||||
struct input_mt_slot *s;
|
||||
|
||||
if (!mt)
|
||||
return false;
|
||||
|
||||
for (s = mt->slots; s != mt->slots + mt->num_slots; s++) {
|
||||
if (s->key == key &&
|
||||
input_mt_get_value(s, ABS_MT_TRACKING_ID) >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void wacom_wac_finger_event(struct hid_device *hdev,
|
||||
struct hid_field *field, struct hid_usage *usage, __s32 value)
|
||||
{
|
||||
@ -2768,14 +2760,8 @@ static void wacom_wac_finger_event(struct hid_device *hdev,
|
||||
}
|
||||
|
||||
if (usage->usage_index + 1 == field->report_count) {
|
||||
if (equivalent_usage == wacom_wac->hid_data.last_slot_field) {
|
||||
bool touch_removed = wacom_wac_slot_is_active(wacom_wac->touch_input,
|
||||
wacom_wac->hid_data.id) && !wacom_wac->hid_data.tipswitch;
|
||||
|
||||
if (wacom_wac->hid_data.confidence || touch_removed) {
|
||||
wacom_wac_finger_slot(wacom_wac, wacom_wac->touch_input);
|
||||
}
|
||||
}
|
||||
if (equivalent_usage == wacom_wac->hid_data.last_slot_field)
|
||||
wacom_wac_finger_slot(wacom_wac, wacom_wac->touch_input);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#define WACOM_MAX_REMOTES 5
|
||||
#define WACOM_STATUS_UNKNOWN 255
|
||||
#define WACOM_REMOTE_BATTERY_TIMEOUT 21000000000ll
|
||||
#define WACOM_AES_BATTERY_TIMEOUT 1800000
|
||||
|
||||
/* packet length for individual models */
|
||||
#define WACOM_PKGLEN_BBFUN 9
|
||||
|
@ -367,55 +367,33 @@ static void ish_event_cb(struct ishtp_cl_device *cl_device)
|
||||
/**
|
||||
* cros_ish_init() - Init function for ISHTP client
|
||||
* @cros_ish_cl: ISHTP client instance
|
||||
* @reset: true if called from reset handler
|
||||
*
|
||||
* This function complete the initializtion of the client.
|
||||
*
|
||||
* Return: 0 for success, negative error code for failure.
|
||||
*/
|
||||
static int cros_ish_init(struct ishtp_cl *cros_ish_cl)
|
||||
static int cros_ish_init(struct ishtp_cl *cros_ish_cl, bool reset)
|
||||
{
|
||||
int rv;
|
||||
struct ishtp_device *dev;
|
||||
struct ishtp_fw_client *fw_client;
|
||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl);
|
||||
|
||||
rv = ishtp_cl_link(cros_ish_cl);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ishtp_cl_link failed\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
dev = ishtp_get_ishtp_device(cros_ish_cl);
|
||||
|
||||
/* Connect to firmware client */
|
||||
ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE);
|
||||
ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE);
|
||||
|
||||
fw_client = ishtp_fw_cl_get_client(dev, &cros_ec_ishtp_id_table[0].guid);
|
||||
if (!fw_client) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"ish client uuid not found\n");
|
||||
rv = -ENOENT;
|
||||
goto err_cl_unlink;
|
||||
}
|
||||
|
||||
ishtp_cl_set_fw_client_id(cros_ish_cl,
|
||||
ishtp_get_fw_client_id(fw_client));
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING);
|
||||
|
||||
rv = ishtp_cl_connect(cros_ish_cl);
|
||||
rv = ishtp_cl_establish_connection(cros_ish_cl,
|
||||
&cros_ec_ishtp_id_table[0].guid,
|
||||
CROS_ISH_CL_TX_RING_SIZE,
|
||||
CROS_ISH_CL_RX_RING_SIZE,
|
||||
reset);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(client_data),
|
||||
"client connect fail\n");
|
||||
goto err_cl_unlink;
|
||||
goto err_cl_disconnect;
|
||||
}
|
||||
|
||||
ishtp_register_event_cb(client_data->cl_device, ish_event_cb);
|
||||
return 0;
|
||||
|
||||
err_cl_unlink:
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
err_cl_disconnect:
|
||||
ishtp_cl_destroy_connection(cros_ish_cl, reset);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -427,10 +405,7 @@ err_cl_unlink:
|
||||
*/
|
||||
static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl)
|
||||
{
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(cros_ish_cl);
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
ishtp_cl_destroy_connection(cros_ish_cl, false);
|
||||
|
||||
/* Disband and free all Tx and Rx client-level rings */
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
@ -592,7 +567,6 @@ static void reset_handler(struct work_struct *work)
|
||||
int rv;
|
||||
struct device *dev;
|
||||
struct ishtp_cl *cros_ish_cl;
|
||||
struct ishtp_cl_device *cl_device;
|
||||
struct ishtp_cl_data *client_data =
|
||||
container_of(work, struct ishtp_cl_data, work_ishtp_reset);
|
||||
|
||||
@ -600,26 +574,11 @@ static void reset_handler(struct work_struct *work)
|
||||
down_write(&init_lock);
|
||||
|
||||
cros_ish_cl = client_data->cros_ish_cl;
|
||||
cl_device = client_data->cl_device;
|
||||
|
||||
/* Unlink, flush queues & start again */
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
ishtp_cl_destroy_connection(cros_ish_cl, true);
|
||||
|
||||
cros_ish_cl = ishtp_cl_allocate(cl_device);
|
||||
if (!cros_ish_cl) {
|
||||
up_write(&init_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
ishtp_set_drvdata(cl_device, cros_ish_cl);
|
||||
ishtp_set_client_data(cros_ish_cl, client_data);
|
||||
client_data->cros_ish_cl = cros_ish_cl;
|
||||
|
||||
rv = cros_ish_init(cros_ish_cl);
|
||||
rv = cros_ish_init(cros_ish_cl, true);
|
||||
if (rv) {
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
dev_err(cl_data_to_dev(client_data), "Reset Failed\n");
|
||||
up_write(&init_lock);
|
||||
return;
|
||||
@ -672,7 +631,7 @@ static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device)
|
||||
INIT_WORK(&client_data->work_ec_evt,
|
||||
ish_evt_handler);
|
||||
|
||||
rv = cros_ish_init(cros_ish_cl);
|
||||
rv = cros_ish_init(cros_ish_cl, false);
|
||||
if (rv)
|
||||
goto end_ishtp_cl_init_error;
|
||||
|
||||
@ -690,10 +649,7 @@ static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device)
|
||||
return 0;
|
||||
|
||||
end_cros_ec_dev_init_error:
|
||||
ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(cros_ish_cl);
|
||||
ishtp_cl_unlink(cros_ish_cl);
|
||||
ishtp_cl_flush_queues(cros_ish_cl);
|
||||
ishtp_cl_destroy_connection(cros_ish_cl, false);
|
||||
ishtp_put_device(cl_device);
|
||||
end_ishtp_cl_init_error:
|
||||
ishtp_cl_free(cros_ish_cl);
|
||||
|
50
include/linux/amd-pmf-io.h
Normal file
50
include/linux/amd-pmf-io.h
Normal file
@ -0,0 +1,50 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* AMD Platform Management Framework Interface
|
||||
*
|
||||
* Copyright (c) 2023, Advanced Micro Devices, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
* Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
*/
|
||||
|
||||
#ifndef AMD_PMF_IO_H
|
||||
#define AMD_PMF_IO_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* enum sfh_message_type - Query the SFH message type
|
||||
* @MT_HPD: Message ID to know the Human presence info from MP2 FW
|
||||
* @MT_ALS: Message ID to know the Ambient light info from MP2 FW
|
||||
*/
|
||||
enum sfh_message_type {
|
||||
MT_HPD,
|
||||
MT_ALS,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum sfh_hpd_info - Query the Human presence information
|
||||
* @SFH_NOT_DETECTED: Check the HPD connection information from MP2 FW
|
||||
* @SFH_USER_PRESENT: Check if the user is present from HPD sensor
|
||||
* @SFH_USER_AWAY: Check if the user is away from HPD sensor
|
||||
*/
|
||||
enum sfh_hpd_info {
|
||||
SFH_NOT_DETECTED,
|
||||
SFH_USER_PRESENT,
|
||||
SFH_USER_AWAY,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct amd_sfh_info - get HPD sensor info from MP2 FW
|
||||
* @ambient_light: Populates the ambient light information
|
||||
* @user_present: Populates the user presence information
|
||||
*/
|
||||
struct amd_sfh_info {
|
||||
u32 ambient_light;
|
||||
u8 user_present;
|
||||
};
|
||||
|
||||
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op);
|
||||
#endif
|
@ -912,7 +912,7 @@ extern bool hid_ignore(struct hid_device *);
|
||||
extern int hid_add_device(struct hid_device *);
|
||||
extern void hid_destroy_device(struct hid_device *);
|
||||
|
||||
extern struct bus_type hid_bus_type;
|
||||
extern const struct bus_type hid_bus_type;
|
||||
|
||||
extern int __must_check __hid_register_driver(struct hid_driver *,
|
||||
struct module *, const char *mod_name);
|
||||
|
@ -115,7 +115,7 @@ struct hid_bpf_ops {
|
||||
size_t len, enum hid_report_type rtype,
|
||||
enum hid_class_request reqtype);
|
||||
struct module *owner;
|
||||
struct bus_type *bus_type;
|
||||
const struct bus_type *bus_type;
|
||||
};
|
||||
|
||||
extern struct hid_bpf_ops *hid_bpf_ops;
|
||||
|
@ -94,6 +94,9 @@ int ishtp_cl_link(struct ishtp_cl *cl);
|
||||
void ishtp_cl_unlink(struct ishtp_cl *cl);
|
||||
int ishtp_cl_disconnect(struct ishtp_cl *cl);
|
||||
int ishtp_cl_connect(struct ishtp_cl *cl);
|
||||
int ishtp_cl_establish_connection(struct ishtp_cl *cl, const guid_t *uuid,
|
||||
int tx_size, int rx_size, bool reset);
|
||||
void ishtp_cl_destroy_connection(struct ishtp_cl *cl, bool reset);
|
||||
int ishtp_cl_send(struct ishtp_cl *cl, uint8_t *buf, size_t length);
|
||||
int ishtp_cl_flush_queues(struct ishtp_cl *cl);
|
||||
int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb);
|
||||
|
@ -14,7 +14,7 @@ import logging
|
||||
|
||||
from hidtools.device.base_device import BaseDevice, EvdevMatch, SysfsFile
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
from typing import Final, List, Tuple
|
||||
|
||||
logger = logging.getLogger("hidtools.test.base")
|
||||
|
||||
@ -155,7 +155,7 @@ class BaseTestCase:
|
||||
# if any module is not available (not compiled), the test will skip.
|
||||
# Each element is a tuple '(kernel driver name, kernel module)',
|
||||
# for example ("playstation", "hid-playstation")
|
||||
kernel_modules = []
|
||||
kernel_modules: List[Tuple[str, str]] = []
|
||||
|
||||
def assertInputEventsIn(self, expected_events, effective_events):
|
||||
effective_events = effective_events.copy()
|
||||
@ -238,8 +238,7 @@ class BaseTestCase:
|
||||
try:
|
||||
with HIDTestUdevRule.instance():
|
||||
with new_uhdev as self.uhdev:
|
||||
skip_cond = request.node.get_closest_marker("skip_if_uhdev")
|
||||
if skip_cond:
|
||||
for skip_cond in request.node.iter_markers("skip_if_uhdev"):
|
||||
test, message, *rest = skip_cond.args
|
||||
|
||||
if test(self.uhdev):
|
||||
|
@ -52,13 +52,13 @@ class BaseMouse(base.UHIDTestDevice):
|
||||
:param reportID: the numeric report ID for this report, if needed
|
||||
"""
|
||||
if buttons is not None:
|
||||
l, r, m = buttons
|
||||
if l is not None:
|
||||
self.left = l
|
||||
if r is not None:
|
||||
self.right = r
|
||||
if m is not None:
|
||||
self.middle = m
|
||||
left, right, middle = buttons
|
||||
if left is not None:
|
||||
self.left = left
|
||||
if right is not None:
|
||||
self.right = right
|
||||
if middle is not None:
|
||||
self.middle = middle
|
||||
left = self.left
|
||||
right = self.right
|
||||
middle = self.middle
|
||||
|
@ -13,62 +13,133 @@ from hidtools.util import BusType
|
||||
import libevdev
|
||||
import logging
|
||||
import pytest
|
||||
from typing import Dict, Tuple
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
logger = logging.getLogger("hidtools.test.tablet")
|
||||
|
||||
|
||||
class BtnTouch(Enum):
|
||||
"""Represents whether the BTN_TOUCH event is set to True or False"""
|
||||
|
||||
DOWN = True
|
||||
UP = False
|
||||
|
||||
|
||||
class ToolType(Enum):
|
||||
PEN = libevdev.EV_KEY.BTN_TOOL_PEN
|
||||
RUBBER = libevdev.EV_KEY.BTN_TOOL_RUBBER
|
||||
|
||||
|
||||
class BtnPressed(Enum):
|
||||
"""Represents whether a button is pressed on the stylus"""
|
||||
|
||||
PRIMARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS
|
||||
SECONDARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS2
|
||||
|
||||
|
||||
class PenState(Enum):
|
||||
"""Pen states according to Microsoft reference:
|
||||
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
|
||||
|
||||
We extend it with the various buttons when we need to check them.
|
||||
"""
|
||||
|
||||
PEN_IS_OUT_OF_RANGE = (False, None)
|
||||
PEN_IS_IN_RANGE = (False, libevdev.EV_KEY.BTN_TOOL_PEN)
|
||||
PEN_IS_IN_CONTACT = (True, libevdev.EV_KEY.BTN_TOOL_PEN)
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT = (False, libevdev.EV_KEY.BTN_TOOL_RUBBER)
|
||||
PEN_IS_ERASING = (True, libevdev.EV_KEY.BTN_TOOL_RUBBER)
|
||||
PEN_IS_OUT_OF_RANGE = BtnTouch.UP, None, None
|
||||
PEN_IS_IN_RANGE = BtnTouch.UP, ToolType.PEN, None
|
||||
PEN_IS_IN_RANGE_WITH_BUTTON = BtnTouch.UP, ToolType.PEN, BtnPressed.PRIMARY_PRESSED
|
||||
PEN_IS_IN_RANGE_WITH_SECOND_BUTTON = (
|
||||
BtnTouch.UP,
|
||||
ToolType.PEN,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
PEN_IS_IN_CONTACT = BtnTouch.DOWN, ToolType.PEN, None
|
||||
PEN_IS_IN_CONTACT_WITH_BUTTON = (
|
||||
BtnTouch.DOWN,
|
||||
ToolType.PEN,
|
||||
BtnPressed.PRIMARY_PRESSED,
|
||||
)
|
||||
PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON = (
|
||||
BtnTouch.DOWN,
|
||||
ToolType.PEN,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT = BtnTouch.UP, ToolType.RUBBER, None
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON = (
|
||||
BtnTouch.UP,
|
||||
ToolType.RUBBER,
|
||||
BtnPressed.PRIMARY_PRESSED,
|
||||
)
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_SECOND_BUTTON = (
|
||||
BtnTouch.UP,
|
||||
ToolType.RUBBER,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
PEN_IS_ERASING = BtnTouch.DOWN, ToolType.RUBBER, None
|
||||
PEN_IS_ERASING_WITH_BUTTON = (
|
||||
BtnTouch.DOWN,
|
||||
ToolType.RUBBER,
|
||||
BtnPressed.PRIMARY_PRESSED,
|
||||
)
|
||||
PEN_IS_ERASING_WITH_SECOND_BUTTON = (
|
||||
BtnTouch.DOWN,
|
||||
ToolType.RUBBER,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
|
||||
def __init__(self, touch, tool):
|
||||
self.touch = touch
|
||||
self.tool = tool
|
||||
def __init__(self, touch: BtnTouch, tool: Optional[ToolType], button: Optional[BtnPressed]):
|
||||
self.touch = touch # type: ignore
|
||||
self.tool = tool # type: ignore
|
||||
self.button = button # type: ignore
|
||||
|
||||
@classmethod
|
||||
def from_evdev(cls, evdev) -> "PenState":
|
||||
touch = bool(evdev.value[libevdev.EV_KEY.BTN_TOUCH])
|
||||
touch = BtnTouch(evdev.value[libevdev.EV_KEY.BTN_TOUCH])
|
||||
tool = None
|
||||
button = None
|
||||
if (
|
||||
evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
|
||||
and not evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
|
||||
):
|
||||
tool = libevdev.EV_KEY.BTN_TOOL_RUBBER
|
||||
tool = ToolType(libevdev.EV_KEY.BTN_TOOL_RUBBER)
|
||||
elif (
|
||||
evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
|
||||
and not evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
|
||||
):
|
||||
tool = libevdev.EV_KEY.BTN_TOOL_PEN
|
||||
tool = ToolType(libevdev.EV_KEY.BTN_TOOL_PEN)
|
||||
elif (
|
||||
evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
|
||||
or evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
|
||||
):
|
||||
raise ValueError("2 tools are not allowed")
|
||||
|
||||
return cls((touch, tool))
|
||||
# we take only the highest button in account
|
||||
for b in [libevdev.EV_KEY.BTN_STYLUS, libevdev.EV_KEY.BTN_STYLUS2]:
|
||||
if bool(evdev.value[b]):
|
||||
button = BtnPressed(b)
|
||||
|
||||
def apply(self, events) -> "PenState":
|
||||
# the kernel tends to insert an EV_SYN once removing the tool, so
|
||||
# the button will be released after
|
||||
if tool is None:
|
||||
button = None
|
||||
|
||||
return cls((touch, tool, button)) # type: ignore
|
||||
|
||||
def apply(self, events: List[libevdev.InputEvent], strict: bool) -> "PenState":
|
||||
if libevdev.EV_SYN.SYN_REPORT in events:
|
||||
raise ValueError("EV_SYN is in the event sequence")
|
||||
touch = self.touch
|
||||
touch_found = False
|
||||
tool = self.tool
|
||||
tool_found = False
|
||||
button = self.button
|
||||
button_found = False
|
||||
|
||||
for ev in events:
|
||||
if ev == libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH):
|
||||
if touch_found:
|
||||
raise ValueError(f"duplicated BTN_TOUCH in {events}")
|
||||
touch_found = True
|
||||
touch = bool(ev.value)
|
||||
touch = BtnTouch(ev.value)
|
||||
elif ev in (
|
||||
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN),
|
||||
libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_RUBBER),
|
||||
@ -76,19 +147,113 @@ class PenState(Enum):
|
||||
if tool_found:
|
||||
raise ValueError(f"duplicated BTN_TOOL_* in {events}")
|
||||
tool_found = True
|
||||
if ev.value:
|
||||
tool = ev.code
|
||||
else:
|
||||
tool = None
|
||||
tool = ToolType(ev.code) if ev.value else None
|
||||
elif ev in (
|
||||
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS),
|
||||
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2),
|
||||
):
|
||||
if button_found:
|
||||
raise ValueError(f"duplicated BTN_STYLUS* in {events}")
|
||||
button_found = True
|
||||
button = BtnPressed(ev.code) if ev.value else None
|
||||
|
||||
new_state = PenState((touch, tool))
|
||||
assert (
|
||||
new_state in self.valid_transitions()
|
||||
), f"moving from {self} to {new_state} is forbidden"
|
||||
# the kernel tends to insert an EV_SYN once removing the tool, so
|
||||
# the button will be released after
|
||||
if tool is None:
|
||||
button = None
|
||||
|
||||
new_state = PenState((touch, tool, button)) # type: ignore
|
||||
if strict:
|
||||
assert (
|
||||
new_state in self.valid_transitions()
|
||||
), f"moving from {self} to {new_state} is forbidden"
|
||||
else:
|
||||
assert (
|
||||
new_state in self.historically_tolerated_transitions()
|
||||
), f"moving from {self} to {new_state} is forbidden"
|
||||
|
||||
return new_state
|
||||
|
||||
def valid_transitions(self) -> Tuple["PenState", ...]:
|
||||
"""Following the state machine in the URL above.
|
||||
|
||||
Note that those transitions are from the evdev point of view, not HID"""
|
||||
if self == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
return (
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_ERASING,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_CONTACT:
|
||||
return (
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_ERASING,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_ERASING:
|
||||
return (
|
||||
PenState.PEN_IS_ERASING,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
)
|
||||
|
||||
return tuple()
|
||||
|
||||
def historically_tolerated_transitions(self) -> Tuple["PenState", ...]:
|
||||
"""Following the state machine in the URL above, with a couple of addition
|
||||
for skipping the in-range state, due to historical reasons.
|
||||
|
||||
@ -97,14 +262,20 @@ class PenState(Enum):
|
||||
return (
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_ERASING,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
)
|
||||
@ -112,6 +283,8 @@ class PenState(Enum):
|
||||
if self == PenState.PEN_IS_IN_CONTACT:
|
||||
return (
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
)
|
||||
@ -130,110 +303,42 @@ class PenState(Enum):
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
)
|
||||
|
||||
return tuple()
|
||||
|
||||
|
||||
class Data(object):
|
||||
pass
|
||||
|
||||
|
||||
class Pen(object):
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.tipswitch = False
|
||||
self.tippressure = 15
|
||||
self.azimuth = 0
|
||||
self.inrange = False
|
||||
self.width = 10
|
||||
self.height = 10
|
||||
self.barrelswitch = False
|
||||
self.invert = False
|
||||
self.eraser = False
|
||||
self.x_tilt = 0
|
||||
self.y_tilt = 0
|
||||
self.twist = 0
|
||||
self._old_values = None
|
||||
self.current_state = None
|
||||
|
||||
def _restore(self):
|
||||
if self._old_values is not None:
|
||||
for i in [
|
||||
"x",
|
||||
"y",
|
||||
"tippressure",
|
||||
"azimuth",
|
||||
"width",
|
||||
"height",
|
||||
"twist",
|
||||
"x_tilt",
|
||||
"y_tilt",
|
||||
]:
|
||||
setattr(self, i, getattr(self._old_values, i))
|
||||
|
||||
def move_to(self, state):
|
||||
# fill in the previous values
|
||||
if self.current_state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
self._restore()
|
||||
|
||||
print(f"\n *** pen is moving to {state} ***")
|
||||
|
||||
if state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
self._old_values = copy.copy(self)
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.tipswitch = False
|
||||
self.tippressure = 0
|
||||
self.azimuth = 0
|
||||
self.inrange = False
|
||||
self.width = 0
|
||||
self.height = 0
|
||||
self.invert = False
|
||||
self.eraser = False
|
||||
self.x_tilt = 0
|
||||
self.y_tilt = 0
|
||||
self.twist = 0
|
||||
elif state == PenState.PEN_IS_IN_RANGE:
|
||||
self.tipswitch = False
|
||||
self.inrange = True
|
||||
self.invert = False
|
||||
self.eraser = False
|
||||
elif state == PenState.PEN_IS_IN_CONTACT:
|
||||
self.tipswitch = True
|
||||
self.inrange = True
|
||||
self.invert = False
|
||||
self.eraser = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
|
||||
self.tipswitch = False
|
||||
self.inrange = True
|
||||
self.invert = True
|
||||
self.eraser = False
|
||||
elif state == PenState.PEN_IS_ERASING:
|
||||
self.tipswitch = False
|
||||
self.inrange = True
|
||||
self.invert = True
|
||||
self.eraser = True
|
||||
|
||||
self.current_state = state
|
||||
|
||||
def __assert_axis(self, evdev, axis, value):
|
||||
if (
|
||||
axis == libevdev.EV_KEY.BTN_TOOL_RUBBER
|
||||
and evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] is None
|
||||
):
|
||||
return
|
||||
|
||||
assert (
|
||||
evdev.value[axis] == value
|
||||
), f"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}"
|
||||
|
||||
def assert_expected_input_events(self, evdev):
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
|
||||
assert self.current_state == PenState.from_evdev(evdev)
|
||||
|
||||
@staticmethod
|
||||
def legal_transitions() -> Dict[str, Tuple[PenState, ...]]:
|
||||
def legal_transitions() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""This is the first half of the Windows Pen Implementation state machine:
|
||||
we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
|
||||
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
|
||||
@ -259,7 +364,7 @@ class Pen(object):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def legal_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]:
|
||||
def legal_transitions_with_invert() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""This is the second half of the Windows Pen Implementation state machine:
|
||||
we now have Invert and Erase bits, so move in/out or proximity with the intend
|
||||
to erase.
|
||||
@ -297,7 +402,106 @@ class Pen(object):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def tolerated_transitions() -> Dict[str, Tuple[PenState, ...]]:
|
||||
def legal_transitions_with_primary_button() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""We revisit the Windows Pen Implementation state machine:
|
||||
we now have a primary button.
|
||||
"""
|
||||
return {
|
||||
"hover-button": (PenState.PEN_IS_IN_RANGE_WITH_BUTTON,),
|
||||
"hover-button -> out-of-range": (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
),
|
||||
"in-range -> button-press": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
),
|
||||
"in-range -> button-press -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> touch -> button-press -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
),
|
||||
"in-range -> touch -> button-press -> release -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> button-press -> touch -> release -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> button-press -> touch -> button-release -> release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def legal_transitions_with_secondary_button() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""We revisit the Windows Pen Implementation state machine:
|
||||
we now have a secondary button.
|
||||
Note: we don't looks for 2 buttons interactions.
|
||||
"""
|
||||
return {
|
||||
"hover-button": (PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,),
|
||||
"hover-button -> out-of-range": (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
),
|
||||
"in-range -> button-press": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
),
|
||||
"in-range -> button-press -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> touch -> button-press -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
),
|
||||
"in-range -> touch -> button-press -> release -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> button-press -> touch -> release -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> button-press -> touch -> button-release -> release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def tolerated_transitions() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""This is not adhering to the Windows Pen Implementation state machine
|
||||
but we should expect the kernel to behave properly, mostly for historical
|
||||
reasons."""
|
||||
@ -310,7 +514,7 @@ class Pen(object):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def tolerated_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]:
|
||||
def tolerated_transitions_with_invert() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""This is the second half of the Windows Pen Implementation state machine:
|
||||
we now have Invert and Erase bits, so move in/out or proximity with the intend
|
||||
to erase.
|
||||
@ -325,7 +529,7 @@ class Pen(object):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def broken_transitions() -> Dict[str, Tuple[PenState, ...]]:
|
||||
def broken_transitions() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""Those tests are definitely not part of the Windows specification.
|
||||
However, a half broken device might export those transitions.
|
||||
For example, a pen that has the eraser button might wobble between
|
||||
@ -363,6 +567,61 @@ class Pen(object):
|
||||
}
|
||||
|
||||
|
||||
class Pen(object):
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.tipswitch = False
|
||||
self.tippressure = 15
|
||||
self.azimuth = 0
|
||||
self.inrange = False
|
||||
self.width = 10
|
||||
self.height = 10
|
||||
self.barrelswitch = False
|
||||
self.secondarybarrelswitch = False
|
||||
self.invert = False
|
||||
self.eraser = False
|
||||
self.xtilt = 1
|
||||
self.ytilt = 1
|
||||
self.twist = 1
|
||||
self._old_values = None
|
||||
self.current_state = None
|
||||
|
||||
def restore(self):
|
||||
if self._old_values is not None:
|
||||
for i in [
|
||||
"x",
|
||||
"y",
|
||||
"tippressure",
|
||||
"azimuth",
|
||||
"width",
|
||||
"height",
|
||||
"twist",
|
||||
"xtilt",
|
||||
"ytilt",
|
||||
]:
|
||||
setattr(self, i, getattr(self._old_values, i))
|
||||
|
||||
def backup(self):
|
||||
self._old_values = copy.copy(self)
|
||||
|
||||
def __assert_axis(self, evdev, axis, value):
|
||||
if (
|
||||
axis == libevdev.EV_KEY.BTN_TOOL_RUBBER
|
||||
and evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] is None
|
||||
):
|
||||
return
|
||||
|
||||
assert (
|
||||
evdev.value[axis] == value
|
||||
), f"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}"
|
||||
|
||||
def assert_expected_input_events(self, evdev):
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
|
||||
assert self.current_state == PenState.from_evdev(evdev)
|
||||
|
||||
|
||||
class PenDigitizer(base.UHIDTestDevice):
|
||||
def __init__(
|
||||
self,
|
||||
@ -388,6 +647,89 @@ class PenDigitizer(base.UHIDTestDevice):
|
||||
continue
|
||||
self.fields = [f.usage_name for f in r]
|
||||
|
||||
def move_to(self, pen, state):
|
||||
# fill in the previous values
|
||||
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.restore()
|
||||
|
||||
print(f"\n *** pen is moving to {state} ***")
|
||||
|
||||
if state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.backup()
|
||||
pen.x = 0
|
||||
pen.y = 0
|
||||
pen.tipswitch = False
|
||||
pen.tippressure = 0
|
||||
pen.azimuth = 0
|
||||
pen.inrange = False
|
||||
pen.width = 0
|
||||
pen.height = 0
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.xtilt = 0
|
||||
pen.ytilt = 0
|
||||
pen.twist = 0
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_CONTACT:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = True
|
||||
pen.secondarybarrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = True
|
||||
pen.secondarybarrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = True
|
||||
elif state == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = True
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = True
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = False
|
||||
elif state == PenState.PEN_IS_ERASING:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = True
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = False
|
||||
|
||||
pen.current_state = state
|
||||
|
||||
def event(self, pen):
|
||||
rs = []
|
||||
r = self.create_report(application=self.cur_application, data=pen)
|
||||
@ -435,10 +777,14 @@ class BaseTest:
|
||||
self.debug_reports(r, uhdev, events)
|
||||
return events
|
||||
|
||||
def validate_transitions(self, from_state, pen, evdev, events):
|
||||
def validate_transitions(
|
||||
self, from_state, pen, evdev, events, allow_intermediate_states
|
||||
):
|
||||
# check that the final state is correct
|
||||
pen.assert_expected_input_events(evdev)
|
||||
|
||||
state = from_state
|
||||
|
||||
# check that the transitions are valid
|
||||
sync_events = []
|
||||
while libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) in events:
|
||||
@ -448,12 +794,12 @@ class BaseTest:
|
||||
events = events[idx + 1 :]
|
||||
|
||||
# now check for a valid transition
|
||||
from_state = from_state.apply(sync_events)
|
||||
state = state.apply(sync_events, not allow_intermediate_states)
|
||||
|
||||
if events:
|
||||
from_state = from_state.apply(sync_events)
|
||||
state = state.apply(sync_events, not allow_intermediate_states)
|
||||
|
||||
def _test_states(self, state_list, scribble):
|
||||
def _test_states(self, state_list, scribble, allow_intermediate_states):
|
||||
"""Internal method to test against a list of
|
||||
transition between states.
|
||||
state_list is a list of PenState objects
|
||||
@ -466,9 +812,11 @@ class BaseTest:
|
||||
cur_state = PenState.PEN_IS_OUT_OF_RANGE
|
||||
|
||||
p = Pen(50, 60)
|
||||
p.move_to(PenState.PEN_IS_OUT_OF_RANGE)
|
||||
uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE)
|
||||
events = self.post(uhdev, p)
|
||||
self.validate_transitions(cur_state, p, evdev, events)
|
||||
self.validate_transitions(
|
||||
cur_state, p, evdev, events, allow_intermediate_states
|
||||
)
|
||||
|
||||
cur_state = p.current_state
|
||||
|
||||
@ -477,38 +825,77 @@ class BaseTest:
|
||||
p.x += 1
|
||||
p.y -= 1
|
||||
events = self.post(uhdev, p)
|
||||
self.validate_transitions(cur_state, p, evdev, events)
|
||||
self.validate_transitions(
|
||||
cur_state, p, evdev, events, allow_intermediate_states
|
||||
)
|
||||
assert len(events) >= 3 # X, Y, SYN
|
||||
p.move_to(state)
|
||||
uhdev.move_to(p, state)
|
||||
if scribble and state != PenState.PEN_IS_OUT_OF_RANGE:
|
||||
p.x += 1
|
||||
p.y -= 1
|
||||
events = self.post(uhdev, p)
|
||||
self.validate_transitions(cur_state, p, evdev, events)
|
||||
self.validate_transitions(
|
||||
cur_state, p, evdev, events, allow_intermediate_states
|
||||
)
|
||||
cur_state = p.current_state
|
||||
|
||||
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
|
||||
@pytest.mark.parametrize(
|
||||
"state_list",
|
||||
[pytest.param(v, id=k) for k, v in Pen.legal_transitions().items()],
|
||||
[pytest.param(v, id=k) for k, v in PenState.legal_transitions().items()],
|
||||
)
|
||||
def test_valid_pen_states(self, state_list, scribble):
|
||||
"""This is the first half of the Windows Pen Implementation state machine:
|
||||
we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
|
||||
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
|
||||
"""
|
||||
self._test_states(state_list, scribble)
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=False)
|
||||
|
||||
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
|
||||
@pytest.mark.parametrize(
|
||||
"state_list",
|
||||
[pytest.param(v, id=k) for k, v in Pen.tolerated_transitions().items()],
|
||||
[
|
||||
pytest.param(v, id=k)
|
||||
for k, v in PenState.tolerated_transitions().items()
|
||||
],
|
||||
)
|
||||
def test_tolerated_pen_states(self, state_list, scribble):
|
||||
"""This is not adhering to the Windows Pen Implementation state machine
|
||||
but we should expect the kernel to behave properly, mostly for historical
|
||||
reasons."""
|
||||
self._test_states(state_list, scribble)
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=True)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Barrel Switch" not in uhdev.fields,
|
||||
"Device not compatible, missing Barrel Switch usage",
|
||||
)
|
||||
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
|
||||
@pytest.mark.parametrize(
|
||||
"state_list",
|
||||
[
|
||||
pytest.param(v, id=k)
|
||||
for k, v in PenState.legal_transitions_with_primary_button().items()
|
||||
],
|
||||
)
|
||||
def test_valid_primary_button_pen_states(self, state_list, scribble):
|
||||
"""Rework the transition state machine by adding the primary button."""
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=False)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Secondary Barrel Switch" not in uhdev.fields,
|
||||
"Device not compatible, missing Secondary Barrel Switch usage",
|
||||
)
|
||||
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
|
||||
@pytest.mark.parametrize(
|
||||
"state_list",
|
||||
[
|
||||
pytest.param(v, id=k)
|
||||
for k, v in PenState.legal_transitions_with_secondary_button().items()
|
||||
],
|
||||
)
|
||||
def test_valid_secondary_button_pen_states(self, state_list, scribble):
|
||||
"""Rework the transition state machine by adding the secondary button."""
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=False)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Invert" not in uhdev.fields,
|
||||
@ -519,7 +906,7 @@ class BaseTest:
|
||||
"state_list",
|
||||
[
|
||||
pytest.param(v, id=k)
|
||||
for k, v in Pen.legal_transitions_with_invert().items()
|
||||
for k, v in PenState.legal_transitions_with_invert().items()
|
||||
],
|
||||
)
|
||||
def test_valid_invert_pen_states(self, state_list, scribble):
|
||||
@ -528,7 +915,7 @@ class BaseTest:
|
||||
to erase.
|
||||
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
|
||||
"""
|
||||
self._test_states(state_list, scribble)
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=False)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Invert" not in uhdev.fields,
|
||||
@ -539,7 +926,7 @@ class BaseTest:
|
||||
"state_list",
|
||||
[
|
||||
pytest.param(v, id=k)
|
||||
for k, v in Pen.tolerated_transitions_with_invert().items()
|
||||
for k, v in PenState.tolerated_transitions_with_invert().items()
|
||||
],
|
||||
)
|
||||
def test_tolerated_invert_pen_states(self, state_list, scribble):
|
||||
@ -548,7 +935,7 @@ class BaseTest:
|
||||
to erase.
|
||||
https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
|
||||
"""
|
||||
self._test_states(state_list, scribble)
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=True)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Invert" not in uhdev.fields,
|
||||
@ -557,7 +944,7 @@ class BaseTest:
|
||||
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
|
||||
@pytest.mark.parametrize(
|
||||
"state_list",
|
||||
[pytest.param(v, id=k) for k, v in Pen.broken_transitions().items()],
|
||||
[pytest.param(v, id=k) for k, v in PenState.broken_transitions().items()],
|
||||
)
|
||||
def test_tolerated_broken_pen_states(self, state_list, scribble):
|
||||
"""Those tests are definitely not part of the Windows specification.
|
||||
@ -565,102 +952,7 @@ class BaseTest:
|
||||
For example, a pen that has the eraser button might wobble between
|
||||
touching and erasing if the tablet doesn't enforce the Windows
|
||||
state machine."""
|
||||
self._test_states(state_list, scribble)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Barrel Switch" not in uhdev.fields,
|
||||
"Device not compatible, missing Barrel Switch usage",
|
||||
)
|
||||
def test_primary_button(self):
|
||||
"""Primary button (stylus) pressed, reports as pressed even while hovering.
|
||||
Actual reporting from the device: hid=TIPSWITCH,BARRELSWITCH,INRANGE (code=TOUCH,STYLUS,PEN):
|
||||
{ 0, 0, 1 } <- hover
|
||||
{ 0, 1, 1 } <- primary button pressed
|
||||
{ 0, 1, 1 } <- liftoff
|
||||
{ 0, 0, 0 } <- leaves
|
||||
"""
|
||||
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
|
||||
p = Pen(50, 60)
|
||||
p.inrange = True
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1) in events
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_X] == 50
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_Y] == 60
|
||||
assert not evdev.value[libevdev.EV_KEY.BTN_STYLUS]
|
||||
|
||||
p.barrelswitch = True
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1) in events
|
||||
|
||||
p.x += 1
|
||||
p.y -= 1
|
||||
events = self.post(uhdev, p)
|
||||
assert len(events) == 3 # X, Y, SYN
|
||||
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 51) in events
|
||||
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 59) in events
|
||||
|
||||
p.barrelswitch = False
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0) in events
|
||||
|
||||
p.inrange = False
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0) in events
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Barrel Switch" not in uhdev.fields,
|
||||
"Device not compatible, missing Barrel Switch usage",
|
||||
)
|
||||
def test_contact_primary_button(self):
|
||||
"""Primary button (stylus) pressed, reports as pressed even while hovering.
|
||||
Actual reporting from the device: hid=TIPSWITCH,BARRELSWITCH,INRANGE (code=TOUCH,STYLUS,PEN):
|
||||
{ 0, 0, 1 } <- hover
|
||||
{ 0, 1, 1 } <- primary button pressed
|
||||
{ 1, 1, 1 } <- touch-down
|
||||
{ 1, 1, 1 } <- still touch, scribble on the screen
|
||||
{ 0, 1, 1 } <- liftoff
|
||||
{ 0, 0, 0 } <- leaves
|
||||
"""
|
||||
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
|
||||
p = Pen(50, 60)
|
||||
p.inrange = True
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1) in events
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_X] == 50
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_Y] == 60
|
||||
assert not evdev.value[libevdev.EV_KEY.BTN_STYLUS]
|
||||
|
||||
p.barrelswitch = True
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1) in events
|
||||
|
||||
p.tipswitch = True
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
|
||||
assert evdev.value[libevdev.EV_KEY.BTN_STYLUS]
|
||||
|
||||
p.x += 1
|
||||
p.y -= 1
|
||||
events = self.post(uhdev, p)
|
||||
assert len(events) == 3 # X, Y, SYN
|
||||
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 51) in events
|
||||
assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 59) in events
|
||||
|
||||
p.tipswitch = False
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
|
||||
|
||||
p.barrelswitch = False
|
||||
p.inrange = False
|
||||
events = self.post(uhdev, p)
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0) in events
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0) in events
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=True)
|
||||
|
||||
|
||||
class GXTP_pen(PenDigitizer):
|
||||
|
@ -27,6 +27,7 @@ from .descriptors_wacom import (
|
||||
)
|
||||
|
||||
import attr
|
||||
from collections import namedtuple
|
||||
from enum import Enum
|
||||
from hidtools.hut import HUT
|
||||
from hidtools.hid import HidUnit
|
||||
@ -862,6 +863,8 @@ class TestPTHX60_Pen(TestOpaqueCTLTablet):
|
||||
|
||||
|
||||
class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest):
|
||||
ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num")
|
||||
|
||||
def create_device(self):
|
||||
return test_multitouch.Digitizer(
|
||||
"DTH 2452",
|
||||
@ -869,6 +872,57 @@ class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest
|
||||
input_info=(0x3, 0x056A, 0x0383),
|
||||
)
|
||||
|
||||
def make_contact(self, contact_id=0, t=0):
|
||||
"""
|
||||
Make a single touch contact that can move over time.
|
||||
|
||||
Creates a touch object that has a well-known position in space that
|
||||
does not overlap with other contacts. The value of `t` may be
|
||||
incremented over time to move the point along a linear path.
|
||||
"""
|
||||
x = 50 + 10 * contact_id + t
|
||||
y = 100 + 100 * contact_id + t
|
||||
return test_multitouch.Touch(contact_id, x, y)
|
||||
|
||||
def make_contacts(self, n, t=0):
|
||||
"""
|
||||
Make multiple touch contacts that can move over time.
|
||||
|
||||
Returns a list of `n` touch objects that are positioned at well-known
|
||||
locations. The value of `t` may be incremented over time to move the
|
||||
points along a linear path.
|
||||
"""
|
||||
return [ self.make_contact(id, t) for id in range(0, n) ]
|
||||
|
||||
def assert_contact(self, uhdev, evdev, contact_ids, t=0):
|
||||
"""
|
||||
Assert properties of a contact generated by make_contact.
|
||||
"""
|
||||
contact_id = contact_ids.contact_id
|
||||
tracking_id = contact_ids.tracking_id
|
||||
slot_num = contact_ids.slot_num
|
||||
|
||||
x = 50 + 10 * contact_id + t
|
||||
y = 100 + 100 * contact_id + t
|
||||
|
||||
# If the data isn't supposed to be stored in any slots, there is
|
||||
# nothing we can check for in the evdev stream.
|
||||
if slot_num is None:
|
||||
assert tracking_id == -1
|
||||
return
|
||||
|
||||
assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == tracking_id
|
||||
if tracking_id != -1:
|
||||
assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_X] == x
|
||||
assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_Y] == y
|
||||
|
||||
def assert_contacts(self, uhdev, evdev, data, t=0):
|
||||
"""
|
||||
Assert properties of a list of contacts generated by make_contacts.
|
||||
"""
|
||||
for contact_ids in data:
|
||||
self.assert_contact(uhdev, evdev, contact_ids, t)
|
||||
|
||||
def test_contact_id_0(self):
|
||||
"""
|
||||
Bring a finger in contact with the tablet, then hold it down and remove it.
|
||||
@ -909,7 +963,7 @@ class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest
|
||||
Ensure that the confidence bit being set to false should not result in a touch event.
|
||||
"""
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
_evdev = uhdev.get_evdev()
|
||||
|
||||
t0 = test_multitouch.Touch(1, 50, 100)
|
||||
t0.confidence = False
|
||||
@ -917,6 +971,228 @@ class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest
|
||||
events = uhdev.next_sync_events()
|
||||
self.debug_reports(r, uhdev, events)
|
||||
|
||||
slot = self.get_slot(uhdev, t0, 0)
|
||||
_slot = self.get_slot(uhdev, t0, 0)
|
||||
|
||||
assert not events
|
||||
|
||||
def test_confidence_multitouch(self):
|
||||
"""
|
||||
Bring multiple fingers in contact with the tablet, some with the
|
||||
confidence bit set, and some without.
|
||||
|
||||
Ensure that all confident touches are reported and that all non-
|
||||
confident touches are ignored.
|
||||
"""
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
|
||||
touches = self.make_contacts(5)
|
||||
touches[0].confidence = False
|
||||
touches[2].confidence = False
|
||||
touches[4].confidence = False
|
||||
|
||||
r = uhdev.event(touches)
|
||||
events = uhdev.next_sync_events()
|
||||
self.debug_reports(r, uhdev, events)
|
||||
|
||||
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
|
||||
|
||||
self.assert_contacts(uhdev, evdev,
|
||||
[ self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None),
|
||||
self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0),
|
||||
self.ContactIds(contact_id = 2, tracking_id = -1, slot_num = None),
|
||||
self.ContactIds(contact_id = 3, tracking_id = 1, slot_num = 1),
|
||||
self.ContactIds(contact_id = 4, tracking_id = -1, slot_num = None) ])
|
||||
|
||||
def confidence_change_assert_playback(self, uhdev, evdev, timeline):
|
||||
"""
|
||||
Assert proper behavior of contacts that move and change tipswitch /
|
||||
confidence status over time.
|
||||
|
||||
Given a `timeline` list of touch states to iterate over, verify
|
||||
that the contacts move and are reported as up/down as expected
|
||||
by the state of the tipswitch and confidence bits.
|
||||
"""
|
||||
t = 0
|
||||
|
||||
for state in timeline:
|
||||
touches = self.make_contacts(len(state), t)
|
||||
|
||||
for item in zip(touches, state):
|
||||
item[0].tipswitch = item[1][1]
|
||||
item[0].confidence = item[1][2]
|
||||
|
||||
r = uhdev.event(touches)
|
||||
events = uhdev.next_sync_events()
|
||||
self.debug_reports(r, uhdev, events)
|
||||
|
||||
ids = [ x[0] for x in state ]
|
||||
self.assert_contacts(uhdev, evdev, ids, t)
|
||||
|
||||
t += 1
|
||||
|
||||
def test_confidence_loss_a(self):
|
||||
"""
|
||||
Transition a confident contact to a non-confident contact by
|
||||
first clearing the tipswitch.
|
||||
|
||||
Ensure that the driver reports the transitioned contact as
|
||||
being removed and that other contacts continue to report
|
||||
normally. This mode of confidence loss is used by the
|
||||
DTH-2452.
|
||||
"""
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
|
||||
self.confidence_change_assert_playback(uhdev, evdev, [
|
||||
# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
|
||||
# Both fingers confidently in contact
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=1: Contact 0 == !Down + confident; Contact 1 == Down + confident
|
||||
# First finger looses confidence and clears only the tipswitch flag
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
|
||||
# First finger has lost confidence and has both flags cleared
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
|
||||
# First finger has lost confidence and has both flags cleared
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)]
|
||||
])
|
||||
|
||||
def test_confidence_loss_b(self):
|
||||
"""
|
||||
Transition a confident contact to a non-confident contact by
|
||||
cleraing both tipswitch and confidence bits simultaneously.
|
||||
|
||||
Ensure that the driver reports the transitioned contact as
|
||||
being removed and that other contacts continue to report
|
||||
normally. This mode of confidence loss is used by some
|
||||
AES devices.
|
||||
"""
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
|
||||
self.confidence_change_assert_playback(uhdev, evdev, [
|
||||
# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
|
||||
# Both fingers confidently in contact
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=1: Contact 0 == !Down + !confident; Contact 1 == Down + confident
|
||||
# First finger looses confidence and has both flags cleared simultaneously
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
|
||||
# First finger has lost confidence and has both flags cleared
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
|
||||
# First finger has lost confidence and has both flags cleared
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)]
|
||||
])
|
||||
|
||||
def test_confidence_loss_c(self):
|
||||
"""
|
||||
Transition a confident contact to a non-confident contact by
|
||||
clearing only the confidence bit.
|
||||
|
||||
Ensure that the driver reports the transitioned contact as
|
||||
being removed and that other contacts continue to report
|
||||
normally.
|
||||
"""
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
|
||||
self.confidence_change_assert_playback(uhdev, evdev, [
|
||||
# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
|
||||
# Both fingers confidently in contact
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
|
||||
# First finger looses confidence and clears only the confidence flag
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), True, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
|
||||
# First finger has lost confidence and has both flags cleared
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
|
||||
# First finger has lost confidence and has both flags cleared
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)]
|
||||
])
|
||||
|
||||
def test_confidence_gain_a(self):
|
||||
"""
|
||||
Transition a contact that was always non-confident to confident.
|
||||
|
||||
Ensure that the confident contact is reported normally.
|
||||
"""
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
|
||||
self.confidence_change_assert_playback(uhdev, evdev, [
|
||||
# t=0: Contact 0 == Down + !confident; Contact 1 == Down + confident
|
||||
# Only second finger is confidently in contact
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None), True, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)],
|
||||
|
||||
# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
|
||||
# First finger gains confidence
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None), True, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)],
|
||||
|
||||
# t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
|
||||
# First finger remains confident
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = 1, slot_num = 1), True, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)],
|
||||
|
||||
# t=3: Contact 0 == Down + confident; Contact 1 == Down + confident
|
||||
# First finger remains confident
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = 1, slot_num = 1), True, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)]
|
||||
])
|
||||
|
||||
def test_confidence_gain_b(self):
|
||||
"""
|
||||
Transition a contact from non-confident to confident.
|
||||
|
||||
Ensure that the confident contact is reported normally.
|
||||
"""
|
||||
uhdev = self.uhdev
|
||||
evdev = uhdev.get_evdev()
|
||||
|
||||
self.confidence_change_assert_playback(uhdev, evdev, [
|
||||
# t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
|
||||
# First and second finger confidently in contact
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
|
||||
# Firtst finger looses confidence
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), True, False),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
|
||||
# First finger gains confidence
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = 2, slot_num = 0), True, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
|
||||
|
||||
# t=3: Contact 0 == !Down + confident; Contact 1 == Down + confident
|
||||
# First finger goes up
|
||||
[(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, True),
|
||||
(self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)]
|
||||
])
|
||||
|
@ -19,12 +19,12 @@ esac
|
||||
SCRIPT_DIR="$(dirname $(realpath $0))"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/results"
|
||||
KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
|
||||
B2C_URL="https://gitlab.freedesktop.org/mupuf/boot2container/-/raw/master/vm2c.py"
|
||||
B2C_URL="https://gitlab.freedesktop.org/gfx-ci/boot2container/-/raw/main/vm2c.py"
|
||||
NUM_COMPILE_JOBS="$(nproc)"
|
||||
LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
|
||||
LOG_FILE="${LOG_FILE_BASE}.log"
|
||||
EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
|
||||
CONTAINER_IMAGE="registry.freedesktop.org/libevdev/hid-tools/fedora/37:2023-02-17.1"
|
||||
CONTAINER_IMAGE="registry.freedesktop.org/bentiss/hid/fedora/39:2023-11-22.1"
|
||||
|
||||
TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
|
||||
DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
|
||||
@ -32,7 +32,7 @@ DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS
|
||||
usage()
|
||||
{
|
||||
cat <<EOF
|
||||
Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
|
||||
Usage: $0 [-j N] [-s] [-b] [-d <output_dir>] -- [<command>]
|
||||
|
||||
<command> is the command you would normally run when you are in
|
||||
the source kernel direcory. e.g:
|
||||
@ -55,6 +55,7 @@ Options:
|
||||
|
||||
-u) Update the boot2container script to a newer version.
|
||||
-d) Update the output directory (default: ${OUTPUT_DIR})
|
||||
-b) Run only the build steps for the kernel and the selftests
|
||||
-j) Number of jobs for compilation, similar to -j in make
|
||||
(default: ${NUM_COMPILE_JOBS})
|
||||
-s) Instead of powering off the VM, start an interactive
|
||||
@ -191,8 +192,9 @@ main()
|
||||
local command="${DEFAULT_COMMAND}"
|
||||
local update_b2c="no"
|
||||
local debug_shell="no"
|
||||
local build_only="no"
|
||||
|
||||
while getopts ':hsud:j:' opt; do
|
||||
while getopts ':hsud:j:b' opt; do
|
||||
case ${opt} in
|
||||
u)
|
||||
update_b2c="yes"
|
||||
@ -207,6 +209,9 @@ main()
|
||||
command="/bin/sh"
|
||||
debug_shell="yes"
|
||||
;;
|
||||
b)
|
||||
build_only="yes"
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
@ -226,8 +231,7 @@ main()
|
||||
shift $((OPTIND -1))
|
||||
|
||||
# trap 'catch "$?"' EXIT
|
||||
|
||||
if [[ "${debug_shell}" == "no" ]]; then
|
||||
if [[ "${build_only}" == "no" && "${debug_shell}" == "no" ]]; then
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
|
||||
else
|
||||
@ -267,24 +271,26 @@ main()
|
||||
update_kconfig "${kernel_checkout}" "${kconfig_file}"
|
||||
|
||||
recompile_kernel "${kernel_checkout}" "${make_command}"
|
||||
|
||||
if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
|
||||
echo "vm2c script not found in ${b2c}"
|
||||
update_b2c="yes"
|
||||
fi
|
||||
|
||||
if [[ "${update_b2c}" == "yes" ]]; then
|
||||
download $B2C_URL $b2c
|
||||
chmod +x $b2c
|
||||
fi
|
||||
|
||||
update_selftests "${kernel_checkout}" "${make_command}"
|
||||
run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
|
||||
if [[ "${debug_shell}" != "yes" ]]; then
|
||||
echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
|
||||
fi
|
||||
|
||||
exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
|
||||
if [[ "${build_only}" == "no" ]]; then
|
||||
if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
|
||||
echo "vm2c script not found in ${b2c}"
|
||||
update_b2c="yes"
|
||||
fi
|
||||
|
||||
if [[ "${update_b2c}" == "yes" ]]; then
|
||||
download $B2C_URL $b2c
|
||||
chmod +x $b2c
|
||||
fi
|
||||
|
||||
run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
|
||||
if [[ "${debug_shell}" != "yes" ]]; then
|
||||
echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
|
||||
fi
|
||||
|
||||
exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
Loading…
Reference in New Issue
Block a user