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:
Linus Torvalds 2024-01-12 14:45:13 -08:00
commit fef018d819
37 changed files with 2833 additions and 1049 deletions

View file

@ -18,8 +18,9 @@ allOf:
properties:
compatible:
items:
- const: elan,ekth6915
enum:
- elan,ekth6915
- ilitek,ili2901
reg:
const: 0x10

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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
View 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");

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state)
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;

View file

@ -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);

View file

@ -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,
};

View file

@ -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;

View file

@ -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 @@ static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset)
*/
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;

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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

View file

@ -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 @@ static int cros_ish_init(struct ishtp_cl *cros_ish_cl)
*/
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);

View 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

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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):

View file

@ -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

View file

@ -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):

View file

@ -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
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)]
])

View file

@ -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 "$@"