diff options
37 files changed, 2834 insertions, 1050 deletions
diff --git a/Documentation/devicetree/bindings/input/elan,ekth6915.yaml b/Documentation/devicetree/bindings/input/elan,ekth6915.yaml index 3e2d216c6432..dc4ac41f2441 100644 --- a/Documentation/devicetree/bindings/input/elan,ekth6915.yaml +++ b/Documentation/devicetree/bindings/input/elan,ekth6915.yaml @@ -18,8 +18,9 @@ allOf: properties: compatible: - items: - - const: elan,ekth6915 + enum: + - elan,ekth6915 + - ilitek,ili2901 reg: const: 0x10 diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4ce74af79657..4c682c650704 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -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 diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 8a06d0f840bc..082a728eac60 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -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 diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index 2643bb14fee2..a1950bc6e6ce 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -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 { diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c index 8a037de08e92..33fbdde8aff0 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_desc.c @@ -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); diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c index e9c6413af24a..9dbe6f4cb294 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c @@ -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; } diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c index 4f81ef2d4f56..ae36312bc236 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c @@ -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); diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h index 656c3e95ef8c..2c211d28764d 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h @@ -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 diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index e0181218ad85..de7a477d6665 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -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, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 72046039d1be..fb30e228d35f 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -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 diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index c9c968d4b36a..a46ff4e8b99f 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -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; diff --git a/drivers/hid/hid-mcp2200.c b/drivers/hid/hid-mcp2200.c new file mode 100644 index 000000000000..bf57f7f6caa0 --- /dev/null +++ b/drivers/hid/hid-mcp2200.c @@ -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"); diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c index aef0785c91cc..f9cceaeffd08 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -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"); diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 997c3a1adaca..ccc4032fb2b0 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -3,6 +3,9 @@ * HID driver for Nintendo Switch Joy-Cons and Pro Controllers * * Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com> + * Portions Copyright (c) 2020 Nadia Holmquist Pedersen <nadia@nhp.sh> + * Copyright (c) 2022 Emily Strickland <linux@emily.st> + * Copyright (c) 2023 Ryan McClelland <rymcclel@gmail.com> * * The following resources/projects were referenced for this driver: * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering @@ -17,6 +20,9 @@ * This driver supports the Nintendo Switch Joy-Cons and Pro Controllers. The * Pro Controllers can either be used over USB or Bluetooth. * + * This driver also incorporates support for Nintendo Switch Online controllers + * for the NES, SNES, Sega Genesis, and N64. + * * The driver will retrieve the factory calibration info from the controllers, * so little to no user calibration should be required. * @@ -305,9 +311,14 @@ enum joycon_ctlr_state { /* Controller type received as part of device info */ enum joycon_ctlr_type { - JOYCON_CTLR_TYPE_JCL = 0x01, - JOYCON_CTLR_TYPE_JCR = 0x02, - JOYCON_CTLR_TYPE_PRO = 0x03, + JOYCON_CTLR_TYPE_JCL = 0x01, + JOYCON_CTLR_TYPE_JCR = 0x02, + JOYCON_CTLR_TYPE_PRO = 0x03, + JOYCON_CTLR_TYPE_NESL = 0x09, + JOYCON_CTLR_TYPE_NESR = 0x0A, + JOYCON_CTLR_TYPE_SNES = 0x0B, + JOYCON_CTLR_TYPE_GEN = 0x0D, + JOYCON_CTLR_TYPE_N64 = 0x0C, }; struct joycon_stick_cal { @@ -348,6 +359,137 @@ struct joycon_imu_cal { #define JC_BTN_L BIT(22) #define JC_BTN_ZL BIT(23) +struct joycon_ctlr_button_mapping { + u32 code; + u32 bit; +}; + +/* + * D-pad is configured as buttons for the left Joy-Con only! + */ +static const struct joycon_ctlr_button_mapping left_joycon_button_mappings[] = { + { BTN_TL, JC_BTN_L, }, + { BTN_TL2, JC_BTN_ZL, }, + { BTN_SELECT, JC_BTN_MINUS, }, + { BTN_THUMBL, JC_BTN_LSTICK, }, + { BTN_DPAD_UP, JC_BTN_UP, }, + { BTN_DPAD_DOWN, JC_BTN_DOWN, }, + { BTN_DPAD_LEFT, JC_BTN_LEFT, }, + { BTN_DPAD_RIGHT, JC_BTN_RIGHT, }, + { BTN_Z, JC_BTN_CAP, }, + { /* sentinel */ }, +}; + +/* + * The unused *right*-side triggers become the SL/SR triggers for the *left* + * Joy-Con, if and only if we're not using a charging grip. + */ +static const struct joycon_ctlr_button_mapping left_joycon_s_button_mappings[] = { + { BTN_TR, JC_BTN_SL_L, }, + { BTN_TR2, JC_BTN_SR_L, }, + { /* sentinel */ }, +}; + +static const struct joycon_ctlr_button_mapping right_joycon_button_mappings[] = { + { BTN_EAST, JC_BTN_A, }, + { BTN_SOUTH, JC_BTN_B, }, + { BTN_NORTH, JC_BTN_X, }, + { BTN_WEST, JC_BTN_Y, }, + { BTN_TR, JC_BTN_R, }, + { BTN_TR2, JC_BTN_ZR, }, + { BTN_START, JC_BTN_PLUS, }, + { BTN_THUMBR, JC_BTN_RSTICK, }, + { BTN_MODE, JC_BTN_HOME, }, + { /* sentinel */ }, +}; + +/* + * The unused *left*-side triggers become the SL/SR triggers for the *right* + * Joy-Con, if and only if we're not using a charging grip. + */ +static const struct joycon_ctlr_button_mapping right_joycon_s_button_mappings[] = { + { BTN_TL, JC_BTN_SL_R, }, + { BTN_TL2, JC_BTN_SR_R, }, + { /* sentinel */ }, +}; + +static const struct joycon_ctlr_button_mapping procon_button_mappings[] = { + { BTN_EAST, JC_BTN_A, }, + { BTN_SOUTH, JC_BTN_B, }, + { BTN_NORTH, JC_BTN_X, }, + { BTN_WEST, JC_BTN_Y, }, + { BTN_TL, JC_BTN_L, }, + { BTN_TR, JC_BTN_R, }, + { BTN_TL2, JC_BTN_ZL, }, + { BTN_TR2, JC_BTN_ZR, }, + { BTN_SELECT, JC_BTN_MINUS, }, + { BTN_START, JC_BTN_PLUS, }, + { BTN_THUMBL, JC_BTN_LSTICK, }, + { BTN_THUMBR, JC_BTN_RSTICK, }, + { BTN_MODE, JC_BTN_HOME, }, + { BTN_Z, JC_BTN_CAP, }, + { /* sentinel */ }, +}; + +static const struct joycon_ctlr_button_mapping nescon_button_mappings[] = { + { BTN_SOUTH, JC_BTN_A, }, + { BTN_EAST, JC_BTN_B, }, + { BTN_TL, JC_BTN_L, }, + { BTN_TR, JC_BTN_R, }, + { BTN_SELECT, JC_BTN_MINUS, }, + { BTN_START, JC_BTN_PLUS, }, + { /* sentinel */ }, +}; + +static const struct joycon_ctlr_button_mapping snescon_button_mappings[] = { + { BTN_EAST, JC_BTN_A, }, + { BTN_SOUTH, JC_BTN_B, }, + { BTN_NORTH, JC_BTN_X, }, + { BTN_WEST, JC_BTN_Y, }, + { BTN_TL, JC_BTN_L, }, + { BTN_TR, JC_BTN_R, }, + { BTN_TL2, JC_BTN_ZL, }, + { BTN_TR2, JC_BTN_ZR, }, + { BTN_SELECT, JC_BTN_MINUS, }, + { BTN_START, JC_BTN_PLUS, }, + { /* sentinel */ }, +}; + +/* + * "A", "B", and "C" are mapped positionally, rather than by label (e.g., "A" + * gets assigned to BTN_EAST instead of BTN_A). + */ +static const struct joycon_ctlr_button_mapping gencon_button_mappings[] = { + { BTN_SOUTH, JC_BTN_A, }, + { BTN_EAST, JC_BTN_B, }, + { BTN_WEST, JC_BTN_R, }, + { BTN_SELECT, JC_BTN_ZR, }, + { BTN_START, JC_BTN_PLUS, }, + { BTN_MODE, JC_BTN_HOME, }, + { BTN_Z, JC_BTN_CAP, }, + { /* sentinel */ }, +}; + +/* + * N64's C buttons get assigned to d-pad directions and registered as buttons. + */ +static const struct joycon_ctlr_button_mapping n64con_button_mappings[] = { + { BTN_A, JC_BTN_A, }, + { BTN_B, JC_BTN_B, }, + { BTN_TL2, JC_BTN_ZL, }, /* Z */ + { BTN_TL, JC_BTN_L, }, + { BTN_TR, JC_BTN_R, }, + { BTN_TR2, JC_BTN_LSTICK, }, /* ZR */ + { BTN_START, JC_BTN_PLUS, }, + { BTN_FORWARD, JC_BTN_Y, }, /* C UP */ + { BTN_BACK, JC_BTN_ZR, }, /* C DOWN */ + { BTN_LEFT, JC_BTN_X, }, /* C LEFT */ + { BTN_RIGHT, JC_BTN_MINUS, }, /* C RIGHT */ + { BTN_MODE, JC_BTN_HOME, }, + { BTN_Z, JC_BTN_CAP, }, + { /* sentinel */ }, +}; + enum joycon_msg_type { JOYCON_MSG_TYPE_NONE, JOYCON_MSG_TYPE_USB, @@ -506,13 +648,182 @@ struct joycon_ctlr { /* Does this controller have inputs associated with left joycon? */ #define jc_type_has_left(ctlr) \ (ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCL || \ - ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO) + ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO || \ + ctlr->ctlr_type == JOYCON_CTLR_TYPE_N64) /* Does this controller have inputs associated with right joycon? */ #define jc_type_has_right(ctlr) \ (ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR || \ ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO) + +/* + * Controller device helpers + * + * These look at the device ID known to the HID subsystem to identify a device, + * but take caution: some NSO devices lie about themselves (NES Joy-Cons and + * Sega Genesis controller). See type helpers below. + * + * These helpers are most useful early during the HID probe or in conjunction + * with the capability helpers below. + */ +static inline bool joycon_device_is_left_joycon(struct joycon_ctlr *ctlr) +{ + return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL; +} + +static inline bool joycon_device_is_right_joycon(struct joycon_ctlr *ctlr) +{ + return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR; +} + +static inline bool joycon_device_is_procon(struct joycon_ctlr *ctlr) +{ + return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_PROCON; +} + +static inline bool joycon_device_is_chrggrip(struct joycon_ctlr *ctlr) +{ + return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_CHRGGRIP; +} + +static inline bool joycon_device_is_snescon(struct joycon_ctlr *ctlr) +{ + return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_SNESCON; +} + +static inline bool joycon_device_is_gencon(struct joycon_ctlr *ctlr) +{ + return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_GENCON; +} + +static inline bool joycon_device_is_n64con(struct joycon_ctlr *ctlr) +{ + return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_N64CON; +} + +static inline bool joycon_device_has_usb(struct joycon_ctlr *ctlr) +{ + return joycon_device_is_procon(ctlr) || + joycon_device_is_chrggrip(ctlr) || + joycon_device_is_snescon(ctlr) || + joycon_device_is_gencon(ctlr) || + joycon_device_is_n64con(ctlr); +} + +/* + * Controller type helpers + * + * These are slightly different than the device-ID-based helpers above. They are + * generally more reliable, since they can distinguish between, e.g., Genesis + * versus SNES, or NES Joy-Cons versus regular Switch Joy-Cons. They're most + * useful for reporting available inputs. For other kinds of distinctions, see + * the capability helpers below. + * + * They have two major drawbacks: (1) they're not available until after we set + * the reporting method and then request the device info; (2) they can't + * distinguish all controllers (like the Charging Grip from the Pro controller.) + */ +static inline bool joycon_type_is_left_joycon(struct joycon_ctlr *ctlr) +{ + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCL; +} + +static inline bool joycon_type_is_right_joycon(struct joycon_ctlr *ctlr) +{ + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_JCR; +} + +static inline bool joycon_type_is_procon(struct joycon_ctlr *ctlr) +{ + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO; +} + +static inline bool joycon_type_is_snescon(struct joycon_ctlr *ctlr) +{ + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_SNES; +} + +static inline bool joycon_type_is_gencon(struct joycon_ctlr *ctlr) +{ + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_GEN; +} + +static inline bool joycon_type_is_n64con(struct joycon_ctlr *ctlr) +{ + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_N64; +} + +static inline bool joycon_type_is_left_nescon(struct joycon_ctlr *ctlr) +{ + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESL; +} + +static inline bool joycon_type_is_right_nescon(struct joycon_ctlr *ctlr) +{ + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_NESR; +} + +static inline bool joycon_type_has_left_controls(struct joycon_ctlr *ctlr) +{ + return joycon_type_is_left_joycon(ctlr) || + joycon_type_is_procon(ctlr); +} + +static inline bool joycon_type_has_right_controls(struct joycon_ctlr *ctlr) +{ + return joycon_type_is_right_joycon(ctlr) || + joycon_type_is_procon(ctlr); +} + +static inline bool joycon_type_is_any_joycon(struct joycon_ctlr *ctlr) +{ + return joycon_type_is_left_joycon(ctlr) || + joycon_type_is_right_joycon(ctlr) || + joycon_device_is_chrggrip(ctlr); +} + +static inline bool joycon_type_is_any_nescon(struct joycon_ctlr *ctlr) +{ + return joycon_type_is_left_nescon(ctlr) || + joycon_type_is_right_nescon(ctlr); +} + +/* + * Controller capability helpers + * + * These helpers combine the use of the helpers above to detect certain + * capabilities during initialization. They are always accurate but (since they + * use type helpers) cannot be used early in the HID probe. + */ +static inline bool joycon_has_imu(struct joycon_ctlr *ctlr) +{ + return joycon_device_is_chrggrip(ctlr) || + joycon_type_is_any_joycon(ctlr) || + joycon_type_is_procon(ctlr); +} + +static inline bool joycon_has_joysticks(struct joycon_ctlr *ctlr) +{ + return joycon_device_is_chrggrip(ctlr) || + joycon_type_is_any_joycon(ctlr) || + joycon_type_is_procon(ctlr) || + joycon_type_is_n64con(ctlr); +} + +static inline bool joycon_has_rumble(struct joycon_ctlr *ctlr) +{ + return joycon_device_is_chrggrip(ctlr) || + joycon_type_is_any_joycon(ctlr) || + joycon_type_is_procon(ctlr) || + joycon_type_is_n64con(ctlr); +} + +static inline bool joycon_using_usb(struct joycon_ctlr *ctlr) +{ + return ctlr->hdev->bus == BUS_USB; +} + static int __joycon_hid_send(struct hid_device *hdev, u8 *data, size_t len) { u8 *buf; @@ -1296,15 +1607,10 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr, } } -static void joycon_parse_report(struct joycon_ctlr *ctlr, - struct joycon_input_report *rep) +static void joycon_handle_rumble_report(struct joycon_ctlr *ctlr, struct joycon_input_report *rep) { - struct input_dev *dev = ctlr->input; unsigned long flags; - u8 tmp; - u32 btns; unsigned long msecs = jiffies_to_msecs(jiffies); - unsigned long report_delta_ms = msecs - ctlr->last_input_report_msecs; spin_lock_irqsave(&ctlr->lock, flags); if (IS_ENABLED(CONFIG_NINTENDO_FF) && rep->vibrator_report && @@ -1323,11 +1629,21 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, queue_work(ctlr->rumble_queue, &ctlr->rumble_worker); } - /* Parse the battery status */ + spin_unlock_irqrestore(&ctlr->lock, flags); +} + +static void joycon_parse_battery_status(struct joycon_ctlr *ctlr, struct joycon_input_report *rep) +{ + u8 tmp; + unsigned long flags; + + spin_lock_irqsave(&ctlr->lock, flags); + tmp = rep->bat_con; ctlr->host_powered = tmp & BIT(0); ctlr->battery_charging = tmp & BIT(4); tmp = tmp >> 5; + switch (tmp) { case 0: /* empty */ ctlr->battery_capacity = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; @@ -1349,102 +1665,121 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, hid_warn(ctlr->hdev, "Invalid battery status\n"); break; } + spin_unlock_irqrestore(&ctlr->lock, flags); +} - /* Parse the buttons and sticks */ - btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24); - - if (jc_type_has_left(ctlr)) { - u16 raw_x; - u16 raw_y; - s32 x; - s32 y; - - /* get raw stick values */ - raw_x = hid_field_extract(ctlr->hdev, rep->left_stick, 0, 12); - raw_y = hid_field_extract(ctlr->hdev, - rep->left_stick + 1, 4, 12); - /* map the stick values */ - x = joycon_map_stick_val(&ctlr->left_stick_cal_x, raw_x); - y = -joycon_map_stick_val(&ctlr->left_stick_cal_y, raw_y); - /* report sticks */ - input_report_abs(dev, ABS_X, x); - input_report_abs(dev, ABS_Y, y); - - /* report buttons */ - input_report_key(dev, BTN_TL, btns & JC_BTN_L); - input_report_key(dev, BTN_TL2, btns & JC_BTN_ZL); - input_report_key(dev, BTN_SELECT, btns & JC_BTN_MINUS); - input_report_key(dev, BTN_THUMBL, btns & JC_BTN_LSTICK); - input_report_key(dev, BTN_Z, btns & JC_BTN_CAP); - - if (jc_type_is_joycon(ctlr)) { - /* Report the S buttons as the non-existent triggers */ - input_report_key(dev, BTN_TR, btns & JC_BTN_SL_L); - input_report_key(dev, BTN_TR2, btns & JC_BTN_SR_L); - - /* Report d-pad as digital buttons for the joy-cons */ - input_report_key(dev, BTN_DPAD_DOWN, - btns & JC_BTN_DOWN); - input_report_key(dev, BTN_DPAD_UP, btns & JC_BTN_UP); - input_report_key(dev, BTN_DPAD_RIGHT, - btns & JC_BTN_RIGHT); - input_report_key(dev, BTN_DPAD_LEFT, - btns & JC_BTN_LEFT); - } else { - int hatx = 0; - int haty = 0; - - /* d-pad x */ - if (btns & JC_BTN_LEFT) - hatx = -1; - else if (btns & JC_BTN_RIGHT) - hatx = 1; - input_report_abs(dev, ABS_HAT0X, hatx); - - /* d-pad y */ - if (btns & JC_BTN_UP) - haty = -1; - else if (btns & JC_BTN_DOWN) - haty = 1; - input_report_abs(dev, ABS_HAT0Y, haty); - } - } - if (jc_type_has_right(ctlr)) { - u16 raw_x; - u16 raw_y; - s32 x; - s32 y; - - /* get raw stick values */ - raw_x = hid_field_extract(ctlr->hdev, rep->right_stick, 0, 12); - raw_y = hid_field_extract(ctlr->hdev, - rep->right_stick + 1, 4, 12); - /* map stick values */ - x = joycon_map_stick_val(&ctlr->right_stick_cal_x, raw_x); - y = -joycon_map_stick_val(&ctlr->right_stick_cal_y, raw_y); - /* report sticks */ - input_report_abs(dev, ABS_RX, x); - input_report_abs(dev, ABS_RY, y); - - /* report buttons */ - input_report_key(dev, BTN_TR, btns & JC_BTN_R); - input_report_key(dev, BTN_TR2, btns & JC_BTN_ZR); - if (jc_type_is_joycon(ctlr)) { - /* Report the S buttons as the non-existent triggers */ - input_report_key(dev, BTN_TL, btns & JC_BTN_SL_R); - input_report_key(dev, BTN_TL2, btns & JC_BTN_SR_R); - } - input_report_key(dev, BTN_START, btns & JC_BTN_PLUS); - input_report_key(dev, BTN_THUMBR, btns & JC_BTN_RSTICK); - input_report_key(dev, BTN_MODE, btns & JC_BTN_HOME); - input_report_key(dev, BTN_WEST, btns & JC_BTN_Y); - input_report_key(dev, BTN_NORTH, btns & JC_BTN_X); - input_report_key(dev, BTN_EAST, btns & JC_BTN_A); - input_report_key(dev, BTN_SOUTH, btns & JC_BTN_B); +static void joycon_report_left_stick(struct joycon_ctlr *ctlr, + struct joycon_input_report *rep) +{ + u16 raw_x; + u16 raw_y; + s32 x; + s32 y; + + raw_x = hid_field_extract(ctlr->hdev, rep->left_stick, 0, 12); + raw_y = hid_field_extract(ctlr->hdev, rep->left_stick + 1, 4, 12); + + x = joycon_map_stick_val(&ctlr->left_stick_cal_x, raw_x); + y = -joycon_map_stick_val(&ctlr->left_stick_cal_y, raw_y); + + input_report_abs(ctlr->input, ABS_X, x); + input_report_abs(ctlr->input, ABS_Y, y); +} + +static void joycon_report_right_stick(struct joycon_ctlr *ctlr, + struct joycon_input_report *rep) +{ + u16 raw_x; + u16 raw_y; + s32 x; + s32 y; + + raw_x = hid_field_extract(ctlr->hdev, rep->right_stick, 0, 12); + raw_y = hid_field_extract(ctlr->hdev, rep->right_stick + 1, 4, 12); + + x = joycon_map_stick_val(&ctlr->right_stick_cal_x, raw_x); + y = -joycon_map_stick_val(&ctlr->right_stick_cal_y, raw_y); + + input_report_abs(ctlr->input, ABS_RX, x); + input_report_abs(ctlr->input, ABS_RY, y); +} + +static void joycon_report_dpad(struct joycon_ctlr *ctlr, + struct joycon_input_report *rep) +{ + int hatx = 0; + int haty = 0; + u32 btns = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24); + + if (btns & JC_BTN_LEFT) + hatx = -1; + else if (btns & JC_BTN_RIGHT) + hatx = 1; + + if (btns & JC_BTN_UP) + haty = -1; + else if (btns & JC_BTN_DOWN) + haty = 1; + + input_report_abs(ctlr->input, ABS_HAT0X, hatx); + input_report_abs(ctlr->input, ABS_HAT0Y, haty); +} + +static void joycon_report_buttons(struct joycon_ctlr *ctlr, + struct joycon_input_report *rep, + const struct joycon_ctlr_button_mapping button_mappings[]) +{ + const struct joycon_ctlr_button_mapping *button; + u32 status = hid_field_extract(ctlr->hdev, rep->button_status, 0, 24); + + for (button = button_mappings; button->code; button++) + input_report_key(ctlr->input, button->code, status & button->bit); +} + +static void joycon_parse_report(struct joycon_ctlr *ctlr, + struct joycon_input_report *rep) +{ + unsigned long flags; + unsigned long msecs = jiffies_to_msecs(jiffies); + unsigned long report_delta_ms = msecs - ctlr->last_input_report_msecs; + + if (joycon_has_rumble(ctlr)) + joycon_handle_rumble_report(ctlr, rep); + + joycon_parse_battery_status(ctlr, rep); + + if (joycon_type_is_left_joycon(ctlr)) { + joycon_report_left_stick(ctlr, rep); + joycon_report_buttons(ctlr, rep, left_joycon_button_mappings); + if (!joycon_device_is_chrggrip(ctlr)) + joycon_report_buttons(ctlr, rep, left_joycon_s_button_mappings); + } else if (joycon_type_is_right_joycon(ctlr)) { + joycon_report_right_stick(ctlr, rep); + joycon_report_buttons(ctlr, rep, right_joycon_button_mappings); + if (!joycon_device_is_chrggrip(ctlr)) + joycon_report_buttons(ctlr, rep, right_joycon_s_button_mappings); + } else if (joycon_type_is_procon(ctlr)) { + joycon_report_left_stick(ctlr, rep); + joycon_report_right_stick(ctlr, rep); + joycon_report_dpad(ctlr, rep); + joycon_report_buttons(ctlr, rep, procon_button_mappings); + } else if (joycon_type_is_any_nescon(ctlr)) { + joycon_report_dpad(ctlr, rep); + joycon_report_buttons(ctlr, rep, nescon_button_mappings); + } else if (joycon_type_is_snescon(ctlr)) { + joycon_report_dpad(ctlr, rep); + joycon_report_buttons(ctlr, rep, snescon_button_mappings); + } else if (joycon_type_is_gencon(ctlr)) { + joycon_report_dpad(ctlr, rep); + joycon_report_buttons(ctlr, rep, gencon_button_mappings); + } else if (joycon_type_is_n64con(ctlr)) { + joycon_report_left_stick(ctlr, rep); + joycon_report_dpad(ctlr, rep); + joycon_report_buttons(ctlr, rep, n64con_button_mappings); } - input_sync(dev); + input_sync(ctlr->input); spin_lock_irqsave(&ctlr->lock, flags); ctlr->last_input_report_msecs = msecs; @@ -1484,7 +1819,7 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr, } /* parse IMU data if present */ - if (rep->id == JC_INPUT_IMU_DATA) + if ((rep->id == JC_INPUT_IMU_DATA) && joycon_has_imu(ctlr)) joycon_parse_imu_report(ctlr, rep); } @@ -1697,123 +2032,65 @@ static int joycon_play_effect(struct input_dev *dev, void *data, } #endif /* IS_ENABLED(CONFIG_NINTENDO_FF) */ -static const unsigned int joycon_button_inputs_l[] = { - BTN_SELECT, BTN_Z, BTN_THUMBL, - BTN_TL, BTN_TL2, - 0 /* 0 signals end of array */ -}; - -static const unsigned int joycon_button_inputs_r[] = { - BTN_START, BTN_MODE, BTN_THUMBR, - BTN_SOUTH, BTN_EAST, BTN_NORTH, BTN_WEST, - BTN_TR, BTN_TR2, - 0 /* 0 signals end of array */ -}; - -/* We report joy-con d-pad inputs as buttons and pro controller as a hat. */ -static const unsigned int joycon_dpad_inputs_jc[] = { - BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, - 0 /* 0 signals end of array */ -}; - -static int joycon_input_create(struct joycon_ctlr *ctlr) +static void joycon_config_left_stick(struct input_dev *idev) { - struct hid_device *hdev; - const char *name; - const char *imu_name; - int ret; - int i; - - hdev = ctlr->hdev; + input_set_abs_params(idev, + ABS_X, + -JC_MAX_STICK_MAG, + JC_MAX_STICK_MAG, + JC_STICK_FUZZ, + JC_STICK_FLAT); + input_set_abs_params(idev, + ABS_Y, + -JC_MAX_STICK_MAG, + JC_MAX_STICK_MAG, + JC_STICK_FUZZ, + JC_STICK_FLAT); +} - switch (hdev->product) { - case USB_DEVICE_ID_NINTENDO_PROCON: - name = "Nintendo Switch Pro Controller"; - imu_name = "Nintendo Switch Pro Controller IMU"; - break; - case USB_DEVICE_ID_NINTENDO_CHRGGRIP: - if (jc_type_has_left(ctlr)) { - name = "Nintendo Switch Left Joy-Con (Grip)"; - imu_name = "Nintendo Switch Left Joy-Con IMU (Grip)"; - } else { - name = "Nintendo Switch Right Joy-Con (Grip)"; - imu_name = "Nintendo Switch Right Joy-Con IMU (Grip)"; - } - break; - case USB_DEVICE_ID_NINTENDO_JOYCONL: - name = "Nintendo Switch Left Joy-Con"; - imu_name = "Nintendo Switch Left Joy-Con IMU"; - break; - case USB_DEVICE_ID_NINTENDO_JOYCONR: - name = "Nintendo Switch Right Joy-Con"; - imu_name = "Nintendo Switch Right Joy-Con IMU"; - break; - default: /* Should be impossible */ - hid_err(hdev, "Invalid hid product\n"); - return -EINVAL; - } +static void joycon_config_right_stick(struct input_dev *idev) +{ + input_set_abs_params(idev, + ABS_RX, + -JC_MAX_STICK_MAG, + JC_MAX_STICK_MAG, + JC_STICK_FUZZ, + JC_STICK_FLAT); + input_set_abs_params(idev, + ABS_RY, + -JC_MAX_STICK_MAG, + JC_MAX_STICK_MAG, + JC_STICK_FUZZ, + JC_STICK_FLAT); +} - ctlr->input = devm_input_allocate_device(&hdev->dev); - if (!ctlr->input) - return -ENOMEM; - ctlr->input->id.bustype = hdev->bus; - ctlr->input->id.vendor = hdev->vendor; - ctlr->input->id.product = hdev->product; - ctlr->input->id.version = hdev->version; - ctlr->input->uniq = ctlr->mac_addr_str; - ctlr->input->name = name; - ctlr->input->phys = hdev->phys; - input_set_drvdata(ctlr->input, ctlr); +static void joycon_config_dpad(struct input_dev *idev) +{ + input_set_abs_params(idev, + ABS_HAT0X, + -JC_MAX_DPAD_MAG, + JC_MAX_DPAD_MAG, + JC_DPAD_FUZZ, + JC_DPAD_FLAT); + input_set_abs_params(idev, + ABS_HAT0Y, + -JC_MAX_DPAD_MAG, + JC_MAX_DPAD_MAG, + JC_DPAD_FUZZ, + JC_DPAD_FLAT); +} - /* set up sticks and buttons */ - if (jc_type_has_left(ctlr)) { - input_set_abs_params(ctlr->input, ABS_X, - -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, - JC_STICK_FUZZ, JC_STICK_FLAT); - input_set_abs_params(ctlr->input, ABS_Y, - -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, - JC_STICK_FUZZ, JC_STICK_FLAT); - - for (i = 0; joycon_button_inputs_l[i] > 0; i++) - input_set_capability(ctlr->input, EV_KEY, - joycon_button_inputs_l[i]); - - /* configure d-pad differently for joy-con vs pro controller */ - if (hdev->product != USB_DEVICE_ID_NINTENDO_PROCON) { - for (i = 0; joycon_dpad_inputs_jc[i] > 0; i++) - input_set_capability(ctlr->input, EV_KEY, - joycon_dpad_inputs_jc[i]); - } else { - input_set_abs_params(ctlr->input, ABS_HAT0X, - -JC_MAX_DPAD_MAG, JC_MAX_DPAD_MAG, - JC_DPAD_FUZZ, JC_DPAD_FLAT); - input_set_abs_params(ctlr->input, ABS_HAT0Y, - -JC_MAX_DPAD_MAG, JC_MAX_DPAD_MAG, - JC_DPAD_FUZZ, JC_DPAD_FLAT); - } - } - if (jc_type_has_right(ctlr)) { - input_set_abs_params(ctlr->input, ABS_RX, - -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, - JC_STICK_FUZZ, JC_STICK_FLAT); - input_set_abs_params(ctlr->input, ABS_RY, - -JC_MAX_STICK_MAG, JC_MAX_STICK_MAG, - JC_STICK_FUZZ, JC_STICK_FLAT); - - for (i = 0; joycon_button_inputs_r[i] > 0; i++) - input_set_capability(ctlr->input, EV_KEY, - joycon_button_inputs_r[i]); - } +static void joycon_config_buttons(struct input_dev *idev, + const struct joycon_ctlr_button_mapping button_mappings[]) +{ + const struct joycon_ctlr_button_mapping *button; - /* Let's report joy-con S triggers separately */ - if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONL) { - input_set_capability(ctlr->input, EV_KEY, BTN_TR); - input_set_capability(ctlr->input, EV_KEY, BTN_TR2); - } else if (hdev->product == USB_DEVICE_ID_NINTENDO_JOYCONR) { - input_set_capability(ctlr->input, EV_KEY, BTN_TL); - input_set_capability(ctlr->input, EV_KEY, BTN_TL2); - } + for (button = button_mappings; button->code; button++) + input_set_capability(idev, EV_KEY, button->code); +} +static void joycon_config_rumble(struct joycon_ctlr *ctlr) +{ #if IS_ENABLED(CONFIG_NINTENDO_FF) /* set up rumble */ input_set_capability(ctlr->input, EV_FF, FF_RUMBLE); @@ -1826,10 +2103,15 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) joycon_set_rumble(ctlr, 0, 0, false); ctlr->rumble_msecs = jiffies_to_msecs(jiffies); #endif +} - ret = input_register_device(ctlr->input); - if (ret) - return ret; +static int joycon_imu_input_create(struct joycon_ctlr *ctlr) +{ + struct hid_device *hdev; + const char *imu_name; + int ret; + + hdev = ctlr->hdev; /* configure the imu input device */ ctlr->imu_input = devm_input_allocate_device(&hdev->dev); @@ -1841,8 +2123,14 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) ctlr->imu_input->id.product = hdev->product; ctlr->imu_input->id.version = hdev->version; ctlr->imu_input->uniq = ctlr->mac_addr_str; - ctlr->imu_input->name = imu_name; ctlr->imu_input->phys = hdev->phys; + + imu_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s (IMU)", ctlr->input->name); + if (!imu_name) + return -ENOMEM; + + ctlr->imu_input->name = imu_name; + input_set_drvdata(ctlr->imu_input, ctlr); /* configure imu axes */ @@ -1884,6 +2172,71 @@ static int joycon_input_create(struct joycon_ctlr *ctlr) return 0; } +static int joycon_input_create(struct joycon_ctlr *ctlr) +{ + struct hid_device *hdev; + int ret; + + hdev = ctlr->hdev; + + ctlr->input = devm_input_allocate_device(&hdev->dev); + if (!ctlr->input) + return -ENOMEM; + ctlr->input->id.bustype = hdev->bus; + ctlr->input->id.vendor = hdev->vendor; + ctlr->input->id.product = hdev->product; + ctlr->input->id.version = hdev->version; + ctlr->input->uniq = ctlr->mac_addr_str; + ctlr->input->name = hdev->name; + ctlr->input->phys = hdev->phys; + input_set_drvdata(ctlr->input, ctlr); + + ret = input_register_device(ctlr->input); + if (ret) + return ret; + + if (joycon_type_is_right_joycon(ctlr)) { + joycon_config_right_stick(ctlr->input); + joycon_config_buttons(ctlr->input, right_joycon_button_mappings); + if (!joycon_device_is_chrggrip(ctlr)) + joycon_config_buttons(ctlr->input, right_joycon_s_button_mappings); + } else if (joycon_type_is_left_joycon(ctlr)) { + joycon_config_left_stick(ctlr->input); + joycon_config_buttons(ctlr->input, left_joycon_button_mappings); + if (!joycon_device_is_chrggrip(ctlr)) + joycon_config_buttons(ctlr->input, left_joycon_s_button_mappings); + } else if (joycon_type_is_procon(ctlr)) { + joycon_config_left_stick(ctlr->input); + joycon_config_right_stick(ctlr->input); + joycon_config_dpad(ctlr->input); + joycon_config_buttons(ctlr->input, procon_button_mappings); + } else if (joycon_type_is_any_nescon(ctlr)) { + joycon_config_dpad(ctlr->input); + joycon_config_buttons(ctlr->input, nescon_button_mappings); + } else if (joycon_type_is_snescon(ctlr)) { + joycon_config_dpad(ctlr->input); + joycon_config_buttons(ctlr->input, snescon_button_mappings); + } else if (joycon_type_is_gencon(ctlr)) { + joycon_config_dpad(ctlr->input); + joycon_config_buttons(ctlr->input, gencon_button_mappings); + } else if (joycon_type_is_n64con(ctlr)) { + joycon_config_dpad(ctlr->input); + joycon_config_left_stick(ctlr->input); + joycon_config_buttons(ctlr->input, n64con_button_mappings); + } + + if (joycon_has_imu(ctlr)) { + ret = joycon_imu_input_create(ctlr); + if (ret) + return ret; + } + + if (joycon_has_rumble(ctlr)) + joycon_config_rumble(ctlr); + + return 0; +} + /* Because the subcommand sets all the leds at once, the brightness argument is ignored */ static int joycon_player_led_brightness_set(struct led_classdev *led, enum led_brightness brightness) @@ -2120,9 +2473,7 @@ static int joycon_read_info(struct joycon_ctlr *ctlr) struct joycon_input_report *report; req.subcmd_id = JC_SUBCMD_REQ_DEV_INFO; - mutex_lock(&ctlr->output_mutex); ret = joycon_send_subcmd(ctlr, &req, 0, HZ); - mutex_unlock(&ctlr->output_mutex); if (ret) { hid_err(ctlr->hdev, "Failed to get joycon info; ret=%d\n", ret); return ret; @@ -2145,8 +2496,17 @@ static int joycon_read_info(struct joycon_ctlr *ctlr) return -ENOMEM; hid_info(ctlr->hdev, "controller MAC = %s\n", ctlr->mac_addr_str); - /* Retrieve the type so we can distinguish for charging grip */ + /* + * Retrieve the type so we can distinguish the controller type + * Unfortantly the hdev->product can't always be used due to a ?bug? + * with the NSO Genesis controller. Over USB, it will report the + * PID as 0x201E, but over bluetooth it will report the PID as 0x2017 + * which is the same as the NSO SNES controller. This is different from + * the rest of the controllers which will report the same PID over USB + * and bluetooth. + */ ctlr->ctlr_type = report->subcmd_reply.data[2]; + hid_dbg(ctlr->hdev, "controller type = 0x%02X\n", ctlr->ctlr_type); return 0; } @@ -2158,8 +2518,7 @@ static int joycon_init(struct hid_device *hdev) mutex_lock(&ctlr->output_mutex); /* if handshake command fails, assume ble pro controller */ - if ((jc_type_is_procon(ctlr) || jc_type_is_chrggrip(ctlr)) && - !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) { + if (joycon_using_usb(ctlr) && !joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ)) { hid_dbg(hdev, "detected USB controller\n"); /* set baudrate for improved latency */ ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M, HZ); @@ -2184,24 +2543,43 @@ static int joycon_init(struct hid_device *hdev) goto out_unlock; } - /* get controller calibration data, and parse it */ - ret = joycon_request_calibration(ctlr); + /* needed to retrieve the controller type */ + ret = joycon_read_info(ctlr); if (ret) { - /* - * We can function with default calibration, but it may be - * inaccurate. Provide a warning, and continue on. - */ - hid_warn(hdev, "Analog stick positions may be inaccurate\n"); + hid_err(hdev, "Failed to retrieve controller info; ret=%d\n", + ret); + goto out_unlock; } - /* get IMU calibration data, and parse it */ - ret = joycon_request_imu_calibration(ctlr); - if (ret) { - /* - * We can function with default calibration, but it may be - * inaccurate. Provide a warning, and continue on. - */ - hid_warn(hdev, "Unable to read IMU calibration data\n"); + if (joycon_has_joysticks(ctlr)) { + /* get controller calibration data, and parse it */ + ret = joycon_request_calibration(ctlr); + if (ret) { + /* + * We can function with default calibration, but it may be + * inaccurate. Provide a warning, and continue on. + */ + hid_warn(hdev, "Analog stick positions may be inaccurate\n"); + } + } + + if (joycon_has_imu(ctlr)) { + /* get IMU calibration data, and parse it */ + ret = joycon_request_imu_calibration(ctlr); + if (ret) { + /* + * We can function with default calibration, but it may be + * inaccurate. Provide a warning, and continue on. + */ + hid_warn(hdev, "Unable to read IMU calibration data\n"); + } + + /* Enable the IMU */ + ret = joycon_enable_imu(ctlr); + if (ret) { + hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret); + goto out_unlock; + } } /* Set the reporting mode to 0x30, which is the full report mode */ @@ -2211,18 +2589,13 @@ static int joycon_init(struct hid_device *hdev) goto out_unlock; } - /* Enable rumble */ - ret = joycon_enable_rumble(ctlr); - if (ret) { - hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret); - goto out_unlock; - } - - /* Enable the IMU */ - ret = joycon_enable_imu(ctlr); - if (ret) { - hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret); - goto out_unlock; + if (joycon_has_rumble(ctlr)) { + /* Enable rumble */ + ret = joycon_enable_rumble(ctlr); + if (ret) { + hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret); + goto out_unlock; + } } out_unlock: @@ -2367,13 +2740,6 @@ static int nintendo_hid_probe(struct hid_device *hdev, goto err_close; } - ret = joycon_read_info(ctlr); - if (ret) { - hid_err(hdev, "Failed to retrieve controller info; ret=%d\n", - ret); - goto err_close; - } - /* Initialize the leds */ ret = joycon_leds_create(ctlr); if (ret) { @@ -2445,6 +2811,12 @@ static int nintendo_hid_resume(struct hid_device *hdev) static const struct hid_device_id nintendo_hid_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, + USB_DEVICE_ID_NINTENDO_SNESCON) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, + USB_DEVICE_ID_NINTENDO_GENCON) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, + USB_DEVICE_ID_NINTENDO_N64CON) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_PROCON) }, { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, @@ -2453,6 +2825,12 @@ static const struct hid_device_id nintendo_hid_devices[] = { USB_DEVICE_ID_NINTENDO_JOYCONL) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_JOYCONR) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, + USB_DEVICE_ID_NINTENDO_SNESCON) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, + USB_DEVICE_ID_NINTENDO_GENCON) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, + USB_DEVICE_ID_NINTENDO_N64CON) }, { } }; MODULE_DEVICE_TABLE(hid, nintendo_hid_devices); @@ -2471,6 +2849,7 @@ static struct hid_driver nintendo_hid_driver = { module_hid_driver(nintendo_hid_driver); MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>"); +MODULE_AUTHOR("Emily Strickland <linux@emily.st>"); MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>"); MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers"); - diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 2eba152e8b90..26e93a331a51 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -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; diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c index b110818fc945..b3c4e50e248a 100644 --- a/drivers/hid/hid-steam.c +++ b/drivers/hid/hid-steam.c @@ -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 - -/* 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 - -/* 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 +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, + + ID_GET_DEVICE_INFO = 0xA1, + + 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 (steam->gamepad_mode) + enable = false; + if (enable) { + mutex_lock(&steam->report_mutex); /* 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); - - cancel_delayed_work_sync(&steam->heartbeat); + 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; } + cancel_delayed_work_sync(&steam->mode_switch); + cancel_work_sync(&steam->work_connect); 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_work_sync(&steam->work_connect); + 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; diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 2735cd585af0..90f316ae9819 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -44,12 +44,11 @@ #include "i2c-hid.h" /* quirks to control the device */ -#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0) -#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(1) -#define I2C_HID_QUIRK_BOGUS_IRQ BIT(4) -#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(5) -#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(6) -#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(7) +#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(0) +#define I2C_HID_QUIRK_BOGUS_IRQ BIT(1) +#define I2C_HID_QUIRK_RESET_ON_RESUME BIT(2) +#define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(3) +#define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(4) /* Command opcodes */ #define I2C_HID_OPCODE_RESET 0x01 @@ -120,8 +119,6 @@ static const struct i2c_hid_quirks { __u16 idProduct; __u32 quirks; } i2c_hid_quirks[] = { - { USB_VENDOR_ID_WEIDA, HID_ANY_ID, - I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV }, { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, @@ -395,8 +392,7 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) * The call will get a return value (EREMOTEIO) but device will be * triggered and activated. After that, it goes like a normal device. */ - if (power_state == I2C_HID_PWR_ON && - ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) { + if (power_state == I2C_HID_PWR_ON) { ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON); /* Device was already activated */ @@ -426,12 +422,23 @@ set_pwr_exit: return ret; } -static int i2c_hid_execute_reset(struct i2c_hid *ihid) +static int i2c_hid_start_hwreset(struct i2c_hid *ihid) { size_t length = 0; int ret; - i2c_hid_dbg(ihid, "resetting...\n"); + i2c_hid_dbg(ihid, "%s\n", __func__); + + /* + * This prevents sending feature reports while the device is + * being reset. Otherwise we may lose the reset complete + * interrupt. + */ + lockdep_assert_held(&ihid->reset_lock); + + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); + if (ret) + return ret; /* Prepare reset command. Command register goes first. */ *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; @@ -444,60 +451,40 @@ static int i2c_hid_execute_reset(struct i2c_hid *ihid) ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); if (ret) { - dev_err(&ihid->client->dev, "failed to reset device.\n"); - goto out; - } - - if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) { - msleep(100); - goto out; + dev_err(&ihid->client->dev, + "failed to reset device: %d\n", ret); + goto err_clear_reset; } - 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__); + return 0; -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__); - - /* - * 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; + i2c_hid_dbg(ihid, "%s: waiting...\n", __func__); - 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; diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c index 31abab57ad44..5b91fb106cfc 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c +++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c @@ -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); diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index 710fda5f19e1..65e7eeb2fa64 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -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, }; diff --git a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c index 16aa030af845..e157863a8b25 100644 --- a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c +++ b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c @@ -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; diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c index e3d70c5460e9..fbd4f8ea1951 100644 --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -639,47 +639,26 @@ static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl, * * Return: 0 on success, non zero on error */ -static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset) +static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, bool reset) { - struct ishtp_device *dev; struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); - struct ishtp_fw_client *fw_client; int i; int rv; dev_dbg(cl_data_to_dev(client_data), "%s\n", __func__); hid_ishtp_trace(client_data, "%s reset flag: %d\n", __func__, reset); - rv = ishtp_cl_link(hid_ishtp_cl); - if (rv) { - dev_err(cl_data_to_dev(client_data), - "ishtp_cl_link failed\n"); - return -ENOMEM; - } - client_data->init_done = 0; - dev = ishtp_get_ishtp_device(hid_ishtp_cl); - - /* Connect to FW client */ - ishtp_set_tx_ring_size(hid_ishtp_cl, HID_CL_TX_RING_SIZE); - ishtp_set_rx_ring_size(hid_ishtp_cl, HID_CL_RX_RING_SIZE); - - fw_client = ishtp_fw_cl_get_client(dev, &hid_ishtp_id_table[0].guid); - if (!fw_client) { - dev_err(cl_data_to_dev(client_data), - "ish client uuid not found\n"); - return -ENOENT; - } - ishtp_cl_set_fw_client_id(hid_ishtp_cl, - ishtp_get_fw_client_id(fw_client)); - ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_CONNECTING); - - rv = ishtp_cl_connect(hid_ishtp_cl); + rv = ishtp_cl_establish_connection(hid_ishtp_cl, + &hid_ishtp_id_table[0].guid, + HID_CL_TX_RING_SIZE, + HID_CL_RX_RING_SIZE, + reset); if (rv) { dev_err(cl_data_to_dev(client_data), "client connect fail\n"); - goto err_cl_unlink; + goto err_cl_disconnect; } hid_ishtp_trace(client_data, "%s client connected\n", __func__); @@ -723,10 +702,7 @@ static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset) return 0; err_cl_disconnect: - ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_DISCONNECTING); - ishtp_cl_disconnect(hid_ishtp_cl); -err_cl_unlink: - ishtp_cl_unlink(hid_ishtp_cl); + ishtp_cl_destroy_connection(hid_ishtp_cl, reset); return rv; } @@ -738,8 +714,7 @@ err_cl_unlink: */ static void hid_ishtp_cl_deinit(struct ishtp_cl *hid_ishtp_cl) { - ishtp_cl_unlink(hid_ishtp_cl); - ishtp_cl_flush_queues(hid_ishtp_cl); + ishtp_cl_destroy_connection(hid_ishtp_cl, false); /* disband and free all Tx and Rx client-level rings */ ishtp_cl_free(hid_ishtp_cl); @@ -749,33 +724,23 @@ static void hid_ishtp_cl_reset_handler(struct work_struct *work) { struct ishtp_cl_data *client_data; struct ishtp_cl *hid_ishtp_cl; - struct ishtp_cl_device *cl_device; int retry; int rv; client_data = container_of(work, struct ishtp_cl_data, work); hid_ishtp_cl = client_data->hid_ishtp_cl; - cl_device = client_data->cl_device; hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, hid_ishtp_cl); dev_dbg(ishtp_device(client_data->cl_device), "%s\n", __func__); - hid_ishtp_cl_deinit(hid_ishtp_cl); - - hid_ishtp_cl = ishtp_cl_allocate(cl_device); - if (!hid_ishtp_cl) - return; - - ishtp_set_drvdata(cl_device, hid_ishtp_cl); - ishtp_set_client_data(hid_ishtp_cl, client_data); - client_data->hid_ishtp_cl = hid_ishtp_cl; + ishtp_cl_destroy_connection(hid_ishtp_cl, true); client_data->num_hid_devices = 0; for (retry = 0; retry < 3; ++retry) { - rv = hid_ishtp_cl_init(hid_ishtp_cl, 1); + rv = hid_ishtp_cl_init(hid_ishtp_cl, true); if (!rv) break; dev_err(cl_data_to_dev(client_data), "Retry reset init\n"); @@ -841,7 +806,7 @@ static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device) ishtp_hid_print_trace = ishtp_trace_callback(cl_device); - rv = hid_ishtp_cl_init(hid_ishtp_cl, 0); + rv = hid_ishtp_cl_init(hid_ishtp_cl, false); if (rv) { ishtp_cl_free(hid_ishtp_cl); return rv; @@ -868,11 +833,9 @@ static void hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device) hid_ishtp_cl); dev_dbg(ishtp_device(cl_device), "%s\n", __func__); - ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_DISCONNECTING); - ishtp_cl_disconnect(hid_ishtp_cl); + hid_ishtp_cl_deinit(hid_ishtp_cl); ishtp_put_device(cl_device); ishtp_hid_remove(client_data); - hid_ishtp_cl_deinit(hid_ishtp_cl); hid_ishtp_cl = NULL; diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index 7fc738a22375..aa6cb033bb06 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -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, diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c index 2d92fc129ce4..82c907f01bd3 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client.c +++ b/drivers/hid/intel-ish-hid/ishtp/client.c @@ -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,16 +453,148 @@ 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 * diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 166a76c9bcad..77c5fb26cd14 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -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; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 3f704b8072e8..b613f11ed949 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -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); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 471db78dbbf0..da8a01fedd39 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -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); } } diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 57e185f18d53..e63b1e806e34 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -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 diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c index cb2031cf7106..5ac37bd024c8 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -367,55 +367,33 @@ static void ish_event_cb(struct ishtp_cl_device *cl_device) /** * cros_ish_init() - Init function for ISHTP client * @cros_ish_cl: ISHTP client instance + * @reset: true if called from reset handler * * This function complete the initializtion of the client. * * Return: 0 for success, negative error code for failure. */ -static int cros_ish_init(struct ishtp_cl *cros_ish_cl) +static int cros_ish_init(struct ishtp_cl *cros_ish_cl, bool reset) { int rv; - struct ishtp_device *dev; - struct ishtp_fw_client *fw_client; struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); - rv = ishtp_cl_link(cros_ish_cl); - if (rv) { - dev_err(cl_data_to_dev(client_data), - "ishtp_cl_link failed\n"); - return rv; - } - - dev = ishtp_get_ishtp_device(cros_ish_cl); - - /* Connect to firmware client */ - ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE); - ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE); - - fw_client = ishtp_fw_cl_get_client(dev, &cros_ec_ishtp_id_table[0].guid); - if (!fw_client) { - dev_err(cl_data_to_dev(client_data), - "ish client uuid not found\n"); - rv = -ENOENT; - goto err_cl_unlink; - } - - ishtp_cl_set_fw_client_id(cros_ish_cl, - ishtp_get_fw_client_id(fw_client)); - ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING); - - rv = ishtp_cl_connect(cros_ish_cl); + rv = ishtp_cl_establish_connection(cros_ish_cl, + &cros_ec_ishtp_id_table[0].guid, + CROS_ISH_CL_TX_RING_SIZE, + CROS_ISH_CL_RX_RING_SIZE, + reset); if (rv) { dev_err(cl_data_to_dev(client_data), "client connect fail\n"); - goto err_cl_unlink; + goto err_cl_disconnect; } ishtp_register_event_cb(client_data->cl_device, ish_event_cb); return 0; -err_cl_unlink: - ishtp_cl_unlink(cros_ish_cl); +err_cl_disconnect: + ishtp_cl_destroy_connection(cros_ish_cl, reset); return rv; } @@ -427,10 +405,7 @@ err_cl_unlink: */ static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl) { - ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING); - ishtp_cl_disconnect(cros_ish_cl); - ishtp_cl_unlink(cros_ish_cl); - ishtp_cl_flush_queues(cros_ish_cl); + ishtp_cl_destroy_connection(cros_ish_cl, false); /* Disband and free all Tx and Rx client-level rings */ ishtp_cl_free(cros_ish_cl); @@ -592,7 +567,6 @@ static void reset_handler(struct work_struct *work) int rv; struct device *dev; struct ishtp_cl *cros_ish_cl; - struct ishtp_cl_device *cl_device; struct ishtp_cl_data *client_data = container_of(work, struct ishtp_cl_data, work_ishtp_reset); @@ -600,26 +574,11 @@ static void reset_handler(struct work_struct *work) down_write(&init_lock); cros_ish_cl = client_data->cros_ish_cl; - cl_device = client_data->cl_device; - /* Unlink, flush queues & start again */ - ishtp_cl_unlink(cros_ish_cl); - ishtp_cl_flush_queues(cros_ish_cl); - ishtp_cl_free(cros_ish_cl); - - 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; + ishtp_cl_destroy_connection(cros_ish_cl, true); - 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); diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h new file mode 100644 index 000000000000..b4f818205216 --- /dev/null +++ b/include/linux/amd-pmf-io.h @@ -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 diff --git a/include/linux/hid.h b/include/linux/hid.h index bf43f3ff6664..7c26db874ff0 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -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); diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index e9afb61e6ee0..840cd254172d 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -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; diff --git a/include/linux/intel-ish-client-if.h b/include/linux/intel-ish-client-if.h index f45f13304add..771622650247 100644 --- a/include/linux/intel-ish-client-if.h +++ b/include/linux/intel-ish-client-if.h @@ -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); diff --git a/tools/testing/selftests/hid/tests/base.py b/tools/testing/selftests/hid/tests/base.py index 1305cfc9646e..51433063b227 100644 --- a/tools/testing/selftests/hid/tests/base.py +++ b/tools/testing/selftests/hid/tests/base.py @@ -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): diff --git a/tools/testing/selftests/hid/tests/test_mouse.py b/tools/testing/selftests/hid/tests/test_mouse.py index fd2ba62e783a..66daf7e5975c 100644 --- a/tools/testing/selftests/hid/tests/test_mouse.py +++ b/tools/testing/selftests/hid/tests/test_mouse.py @@ -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 diff --git a/tools/testing/selftests/hid/tests/test_tablet.py b/tools/testing/selftests/hid/tests/test_tablet.py index 303ffff9ee8d..903f19f7cbe9 100644 --- a/tools/testing/selftests/hid/tests/test_tablet.py +++ b/tools/testing/selftests/hid/tests/test_tablet.py @@ -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 - """ - 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) + We extend it with the various buttons when we need to check them. + """ - def __init__(self, touch, tool): - self.touch = touch - self.tool = tool + 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: 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,35 +147,55 @@ 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 - - new_state = PenState((touch, tool)) - assert ( - new_state in self.valid_transitions() - ), f"moving from {self} to {new_state} is forbidden" + 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 + + # 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, with a couple of addition - for skipping the in-range state, due to historical reasons. + """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, ) @@ -112,8 +203,9 @@ 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, ) if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT: @@ -127,113 +219,126 @@ class PenState(Enum): 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, ) - return tuple() + 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, + ) -class Data(object): - pass + 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() -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 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. - 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)) + 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, + ) - def move_to(self, state): - # fill in the previous values - if self.current_state == PenState.PEN_IS_OUT_OF_RANGE: - self._restore() + 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, + ) - print(f"\n *** pen is moving to {state} ***") + 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, + ) - 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 + 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, + ) - self.current_state = state + if self == PenState.PEN_IS_ERASING: + return ( + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_OUT_OF_RANGE, + ) - 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 + 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, + ) - assert ( - evdev.value[axis] == value - ), f"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}" + 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, + ) - 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) + 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() @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): diff --git a/tools/testing/selftests/hid/tests/test_wacom_generic.py b/tools/testing/selftests/hid/tests/test_wacom_generic.py index f92fe8e02c1b..352fc39f3c6c 100644 --- a/tools/testing/selftests/hid/tests/test_wacom_generic.py +++ b/tools/testing/selftests/hid/tests/test_wacom_generic.py @@ -27,6 +27,7 @@ from .descriptors_wacom import ( ) import attr +from collections import namedtuple from enum import Enum from hidtools.hut import HUT from hidtools.hid import HidUnit @@ -862,6 +863,8 @@ class TestPTHX60_Pen(TestOpaqueCTLTablet): class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest): + ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num") + def create_device(self): return test_multitouch.Digitizer( "DTH 2452", @@ -869,6 +872,57 @@ class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest input_info=(0x3, 0x056A, 0x0383), ) + def make_contact(self, contact_id=0, t=0): + """ + Make a single touch contact that can move over time. + + Creates a touch object that has a well-known position in space that + does not overlap with other contacts. The value of `t` may be + incremented over time to move the point along a linear path. + """ + x = 50 + 10 * contact_id + t + y = 100 + 100 * contact_id + t + return test_multitouch.Touch(contact_id, x, y) + + def make_contacts(self, n, t=0): + """ + Make multiple touch contacts that can move over time. + + Returns a list of `n` touch objects that are positioned at well-known + locations. The value of `t` may be incremented over time to move the + points along a linear path. + """ + return [ self.make_contact(id, t) for id in range(0, n) ] + + def assert_contact(self, uhdev, evdev, contact_ids, t=0): + """ + Assert properties of a contact generated by make_contact. + """ + contact_id = contact_ids.contact_id + tracking_id = contact_ids.tracking_id + slot_num = contact_ids.slot_num + + x = 50 + 10 * contact_id + t + y = 100 + 100 * contact_id + t + + # If the data isn't supposed to be stored in any slots, there is + # nothing we can check for in the evdev stream. + if slot_num is None: + assert tracking_id == -1 + return + + assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == tracking_id + if tracking_id != -1: + assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_X] == x + assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_Y] == y + + def assert_contacts(self, uhdev, evdev, data, t=0): + """ + Assert properties of a list of contacts generated by make_contacts. + """ + for contact_ids in data: + self.assert_contact(uhdev, evdev, contact_ids, t) + def test_contact_id_0(self): """ Bring a finger in contact with the tablet, then hold it down and remove it. @@ -909,7 +963,7 @@ class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest Ensure that the confidence bit being set to false should not result in a touch event. """ uhdev = self.uhdev - evdev = uhdev.get_evdev() + _evdev = uhdev.get_evdev() t0 = test_multitouch.Touch(1, 50, 100) t0.confidence = False @@ -917,6 +971,228 @@ class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest events = uhdev.next_sync_events() self.debug_reports(r, uhdev, events) - slot = self.get_slot(uhdev, t0, 0) + _slot = self.get_slot(uhdev, t0, 0) + + assert not events + + def test_confidence_multitouch(self): + """ + Bring multiple fingers in contact with the tablet, some with the + confidence bit set, and some without. + + Ensure that all confident touches are reported and that all non- + confident touches are ignored. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + touches = self.make_contacts(5) + touches[0].confidence = False + touches[2].confidence = False + touches[4].confidence = False + + r = uhdev.event(touches) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events + + self.assert_contacts(uhdev, evdev, + [ self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None), + self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), + self.ContactIds(contact_id = 2, tracking_id = -1, slot_num = None), + self.ContactIds(contact_id = 3, tracking_id = 1, slot_num = 1), + self.ContactIds(contact_id = 4, tracking_id = -1, slot_num = None) ]) + + def confidence_change_assert_playback(self, uhdev, evdev, timeline): + """ + Assert proper behavior of contacts that move and change tipswitch / + confidence status over time. + + Given a `timeline` list of touch states to iterate over, verify + that the contacts move and are reported as up/down as expected + by the state of the tipswitch and confidence bits. + """ + t = 0 + + for state in timeline: + touches = self.make_contacts(len(state), t) + + for item in zip(touches, state): + item[0].tipswitch = item[1][1] + item[0].confidence = item[1][2] + + r = uhdev.event(touches) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + ids = [ x[0] for x in state ] + self.assert_contacts(uhdev, evdev, ids, t) + + t += 1 + + def test_confidence_loss_a(self): + """ + Transition a confident contact to a non-confident contact by + first clearing the tipswitch. + + Ensure that the driver reports the transitioned contact as + being removed and that other contacts continue to report + normally. This mode of confidence loss is used by the + DTH-2452. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + self.confidence_change_assert_playback(uhdev, evdev, [ + # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident + # Both fingers confidently in contact + [(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=1: Contact 0 == !Down + confident; Contact 1 == Down + confident + # First finger looses confidence and clears only the tipswitch flag + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, True), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident + # First finger has lost confidence and has both flags cleared + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident + # First finger has lost confidence and has both flags cleared + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)] + ]) + + def test_confidence_loss_b(self): + """ + Transition a confident contact to a non-confident contact by + cleraing both tipswitch and confidence bits simultaneously. + + Ensure that the driver reports the transitioned contact as + being removed and that other contacts continue to report + normally. This mode of confidence loss is used by some + AES devices. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + self.confidence_change_assert_playback(uhdev, evdev, [ + # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident + # Both fingers confidently in contact + [(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=1: Contact 0 == !Down + !confident; Contact 1 == Down + confident + # First finger looses confidence and has both flags cleared simultaneously + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident + # First finger has lost confidence and has both flags cleared + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident + # First finger has lost confidence and has both flags cleared + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)] + ]) + + def test_confidence_loss_c(self): + """ + Transition a confident contact to a non-confident contact by + clearing only the confidence bit. + + Ensure that the driver reports the transitioned contact as + being removed and that other contacts continue to report + normally. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + self.confidence_change_assert_playback(uhdev, evdev, [ + # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident + # Both fingers confidently in contact + [(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident + # First finger looses confidence and clears only the confidence flag + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), True, False), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident + # First finger has lost confidence and has both flags cleared + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)], + + # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident + # First finger has lost confidence and has both flags cleared + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False), + (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)] + ]) + + def test_confidence_gain_a(self): + """ + Transition a contact that was always non-confident to confident. + + Ensure that the confident contact is reported normally. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + self.confidence_change_assert_playback(uhdev, evdev, [ + # t=0: Contact 0 == Down + !confident; Contact 1 == Down + confident + # Only second finger is confidently in contact + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None), True, False), + (self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)], + + # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident + # First finger gains confidence + [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None), True, False), + (self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)], + + # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident + # First finger remains confident + [(self.ContactIds(contact_id = 0, tracking_id = 1, slot_num = 1), True, True), + (self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)], + + # t=3: Contact 0 == Down + confident; Contact 1 == Down + confident + # First finger remains confident + [(self.ContactIds(contact_id = 0, tracking_id = 1, slot_num = 1), True, True), + (self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)] + ]) + + def test_confidence_gain_b(self): + """ + Transition a contact from non-confident to confident. + + Ensure that the confident contact is reported normally. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() - assert not events
\ No newline at end of file + 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)] + ]) diff --git a/tools/testing/selftests/hid/vmtest.sh b/tools/testing/selftests/hid/vmtest.sh index 4da48bf6b328..db534e9099a8 100755 --- a/tools/testing/selftests/hid/vmtest.sh +++ b/tools/testing/selftests/hid/vmtest.sh @@ -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}" + update_selftests "${kernel_checkout}" "${make_command}" - if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then - echo "vm2c script not found in ${b2c}" - update_b2c="yes" - fi + 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 + 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 + 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}) + exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE}) + fi } main "$@" |