mirror of
https://github.com/torvalds/linux.git
synced 2026-09-13 14:54:42 +02:00
hid-for-linus-2026081901
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEL65usyKPHcrRDEicpmLzj2vtYEkFAmqFXCEACgkQpmLzj2vt
YEkTbw//QD2eA9n18g/iDkUk6SAZ6h3YZxhOTQIlHe4sv6auuW64epL/Nz0XKnD7
5odcrnukMIGXRtUmcu0bO8FP85+bvsPUERz82IWtKSaP1vdYtkTNxeBin6HlAHOR
A0TfOOMJjfWuVGUVZ1G3AbBq3GGR80Q8c8YyhPnAnTzdh0cjw6+++1iP4NiGfalX
yAgHmQh++Fo/Ar4c2QyvyEdSqMiAeocVU3qqeNUm7tRImtcsbmFa4cBOMupLSiKR
YMkQD8A2QqS3M3b9BbXo4HdtB8JLiskkClm8ytUadIsUYSUaRah7aj8C3RaDSc4C
mHCLZWjNGKEW+My1Q+lNjELRGLWKPkjw0tv+ZK3Ci0rsCTo7/lnopyufBe9XCptQ
acLvnaCt2RYNRNFPPFhnIBOAuYrIwnWFnhYnNlyGVl9mcgeZfiiRjtk1Lp80YgTo
WCD7i6/zIm2OobrIpRoEbk3ffNXkZ6wcMkp42p0oNLnI191Gspt6SOmLCnQ2GqHd
efJAIFPPfbdWbydZW8qPBdRG/lzlaPHoyOL94fTIwqvpznX/Mp5FPQE9RD8APU2Z
iAhrD6Jxk+5Z27BHCcVUU2jrdeBUGisD6zNdJ+st9Xdp6qLYbU8pl3NLdhCwc6NA
Avt0IwhrboQZSGu8nZkFcYMc/pGe4I8dcf3+MRqIIjWwXLvUHUY=
=7+OR
-----END PGP SIGNATURE-----
Merge tag 'hid-for-linus-2026081901' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
Pull HID updates from Jiri Kosina:
"Core:
- fix long-standing force-feedback initialization race across the
subsystem (Dmitry Torokhov)
- switch to system_dfl_wq (Marco Crivellari)
AMD-SFH:
- support for tablet-mode switch for AMD SFH-based systems (Basavaraj
Natikar)
HyperX:
- support for HyperX QuadCast 2 (Benjamin Blume)
I2C-HID:
- support for devices that provide HID descriptor solely through
the ACPI _DSM method (XIE Zhibang)
Intel-THC-HID:
- support for full I2C bus config parameters (Even Xu)
Logitech:
- HID++ 2.0 repogrammable button support (Elliot Douglas)
- Bolt receiver support for HID++ devices (Erik Håkansson)
MSI:
- support for MSI Claw (Derek J. Clark)
Steam:
- initial support for 2026 Steam Controller (Vicki Pfau)
- support for sensor events on the 2025 Steam Controller (Vicki Pfau)
And many, many other fixes for various long standing issues that were
found by new modern tools, and quite a few device ID additions"
* tag 'hid-for-linus-2026081901' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (146 commits)
HID: tmff: Use 64-bit arithmetic for force feedback scaling
HID: multitouch: reclassify HTIX5288 to WIN_8_FORCE_MULTI_INPUT_NSMU
HID: sensor: custom: Fix field sysfs group cleanup on failure
HID: sensor: custom: Fix use-after-free in enable_sensor
HID: intel-thc-hid: intel-quickspi: bound GET_REPORT response to the caller buffer
HID: haptic: don't write an uninitialized value to unhandled usages
HID: intel-thc-hid: intel-quickspi: fix autosuspend cleanup during teardown
HID: intel-thc-hid: intel-quicki2c: fix autosuspend cleanup during teardown
HID: steam: Zero out inputs when disabling gamepad mode
HID: steam: Clean up locking
HID: steam: Don't set feature reports when disconnecting
HID: steam: Fix wording of connect/disconnect logs
HID: steam: Initial 2026 Steam Controller support
HID: steam: Refactor registration
HID: logitech: add Bolt receiver support for Logitech HID++ devices
HID: sensor-hub: Fix out-of-bounds write in sensor_hub_get_feature
HID: universal-pidff: stop the device when force-feedback init fails
HID: haptic: move FF initialization into .input_configured()
HID: logitech-hidpp: move FF initialization to .input_configured()
HID: megaworld: move FF initialization to .input_configured()
...
This commit is contained in:
commit
a93f3bf4e1
|
|
@ -522,3 +522,53 @@ This should really be your last resort.
|
|||
vendor: 0x093a
|
||||
product: 0x2510
|
||||
...
|
||||
|
||||
Input Device Registration and Lifecycle
|
||||
========================================
|
||||
|
||||
HID drivers that rely on the HID core to register input devices (by using the
|
||||
``HID_CONNECT_HIDINPUT`` flag, which is part of ``HID_CONNECT_DEFAULT``)
|
||||
must be aware of the registration timing.
|
||||
|
||||
When ``hid_hw_start(hdev, flags)`` is called with ``HID_CONNECT_HIDINPUT``,
|
||||
the HID core immediately parses the report descriptor, allocates ``input_dev``
|
||||
structures, and calls ``input_register_device()`` for each of them.
|
||||
|
||||
This means the input device becomes **live and visible to userspace** before
|
||||
``hid_hw_start()`` returns.
|
||||
|
||||
If a driver needs to perform additional configuration on the input device (such
|
||||
as adding force-feedback support, setting extra bits in ``evbit``, or
|
||||
assigning custom event handlers), doing so in the ``probe`` function after
|
||||
``hid_hw_start()`` is **incorrect and racy**. Userspace may trigger
|
||||
callbacks (like ``play_effect``) via ioctls immediately after registration,
|
||||
leading to potential NULL pointer dereferences if the driver hasn't finished
|
||||
initializing its private data.
|
||||
|
||||
The correct way to augment an input device before it is registered is to use the
|
||||
``.input_configured`` callback in ``struct hid_driver``. This hook is
|
||||
called by the HID core after the ``input_dev`` is fully formed but **before**
|
||||
``input_register_device()`` is invoked.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static int my_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
|
||||
{
|
||||
struct input_dev *input = hidinput->input;
|
||||
|
||||
/* Initialize private data and capabilities here */
|
||||
set_bit(EV_FF, input->evbit);
|
||||
return input_ff_create_memless(input, NULL, my_play_effect);
|
||||
}
|
||||
|
||||
static struct hid_driver my_driver = {
|
||||
.name = "my_driver",
|
||||
.probe = my_probe,
|
||||
.input_configured = my_input_configured,
|
||||
};
|
||||
|
||||
Drivers that require even more control over the lifecycle should mask out
|
||||
``HID_CONNECT_HIDINPUT`` and call ``input_register_device()`` manually
|
||||
when they are ready.
|
||||
|
|
|
|||
19
MAINTAINERS
19
MAINTAINERS
|
|
@ -1301,6 +1301,7 @@ L: linux-input@vger.kernel.org
|
|||
S: Maintained
|
||||
F: Documentation/hid/amd-sfh*
|
||||
F: drivers/hid/amd-sfh-hid/
|
||||
F: drivers/input/misc/amd_sfh_tabletmode.c
|
||||
|
||||
AMD SPI DRIVER
|
||||
M: Raju Rangoju <Raju.Rangoju@amd.com>
|
||||
|
|
@ -11514,6 +11515,12 @@ F: include/uapi/linux/hid*
|
|||
F: samples/hid/
|
||||
F: tools/testing/selftests/hid/
|
||||
|
||||
HID HYPERX DRIVER
|
||||
M: Benjamin Blume <benjaminblume@posteo.de>
|
||||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hid/hid-hyperx.c
|
||||
|
||||
HID LOGITECH DRIVERS
|
||||
R: Filipe Laíns <lains@riseup.net>
|
||||
L: linux-input@vger.kernel.org
|
||||
|
|
@ -11550,6 +11557,12 @@ F: drivers/hid/hid-sensor-*
|
|||
F: drivers/iio/*/hid-*
|
||||
F: include/linux/hid-sensor-*
|
||||
|
||||
HID STEAM CONTROLLER
|
||||
M: Vicki Pfau <vi@endrift.com>
|
||||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hid/hid-steam.c
|
||||
|
||||
HID VRC-2 CAR CONTROLLER DRIVER
|
||||
M: Marcus Folkesson <marcus.folkesson@gmail.com>
|
||||
L: linux-input@vger.kernel.org
|
||||
|
|
@ -18350,6 +18363,12 @@ S: Odd Fixes
|
|||
F: Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt
|
||||
F: drivers/net/ieee802154/mrf24j40.c
|
||||
|
||||
MSI HID DRIVER
|
||||
M: Derek J. Clark <derekjohn.clark@gmail.com>
|
||||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hid/hid-msi.c
|
||||
|
||||
MSI EC DRIVER
|
||||
M: Nikita Kravets <teackot@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
|
|
|
|||
|
|
@ -433,6 +433,19 @@ config HOLTEK_FF
|
|||
Say Y here if you have a Holtek On Line Grip based game controller
|
||||
and want to have force feedback support for it.
|
||||
|
||||
config HID_HYPERX
|
||||
tristate "HyperX microphones"
|
||||
depends on USB_HID
|
||||
help
|
||||
Support for the mute button of HyperX microphones.
|
||||
|
||||
The button is handled in the device firmware and only reports the
|
||||
resulting mute state through a vendor-defined collection, so without
|
||||
this driver userspace never learns that the microphone was muted.
|
||||
|
||||
Supported devices:
|
||||
- HyperX QuadCast 2
|
||||
|
||||
config HID_VIVALDI_COMMON
|
||||
tristate
|
||||
help
|
||||
|
|
@ -485,6 +498,19 @@ config HID_GT683R
|
|||
Currently the following devices are know to be supported:
|
||||
- MSI GT683R
|
||||
|
||||
config HID_MSI
|
||||
tristate "MSI Claw Gamepad Support"
|
||||
depends on USB_HID
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
select LEDS_CLASS_MULTICOLOR
|
||||
help
|
||||
Support for the MSI Claw RGB and controller configuration
|
||||
|
||||
Say Y here to include configuration interface support for the MSI Claw Line
|
||||
of Handheld Console Controllers. Say M here to compile this driver as a
|
||||
module. The module will be called hid-msi.
|
||||
|
||||
config HID_KEYTOUCH
|
||||
tristate "Keytouch HID devices"
|
||||
help
|
||||
|
|
@ -1048,6 +1074,7 @@ config HID_PXRC
|
|||
|
||||
config HID_RAPOO
|
||||
tristate "Rapoo non-fully HID-compliant devices"
|
||||
depends on USB_HID
|
||||
help
|
||||
Support for Rapoo devices that are not fully compliant with the
|
||||
HID standard.
|
||||
|
|
@ -1079,6 +1106,15 @@ config HID_ROCCAT
|
|||
Say Y here if you have a Roccat mouse or keyboard and want
|
||||
support for its special functionalities.
|
||||
|
||||
config HID_ROCCAT_KONE_KUNIT_TEST
|
||||
bool "KUnit tests for the Roccat Kone driver" if !KUNIT_ALL_TESTS
|
||||
depends on HID_ROCCAT=y && KUNIT=y
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
Enable the KUnit regression tests for the Roccat Kone driver,
|
||||
covering bounds checking of device-supplied profile indices.
|
||||
If unsure, say N.
|
||||
|
||||
config HID_SAITEK
|
||||
tristate "Saitek (Mad Catz) non-fully HID-compliant devices"
|
||||
help
|
||||
|
|
@ -1215,6 +1251,16 @@ config HID_HYPERV_MOUSE
|
|||
help
|
||||
Select this option to enable the Hyper-V mouse driver.
|
||||
|
||||
config HID_HYPERV_MOUSE_KUNIT_TEST
|
||||
bool "KUnit tests for Hyper-V mouse driver" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT && HID_HYPERV_MOUSE
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
Builds unit tests for the Hyper-V synthetic HID driver.
|
||||
These tests exercise the initial device-info parser with
|
||||
malformed host-provided HID descriptors and are only useful
|
||||
for kernel developers running KUnit.
|
||||
|
||||
config HID_SMARTJOYPLUS
|
||||
tristate "SmartJoy PLUS PS2/USB adapter support"
|
||||
help
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
|
|||
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o
|
||||
obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o
|
||||
obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o
|
||||
obj-$(CONFIG_HID_HYPERX) += hid-hyperx.o
|
||||
obj-$(CONFIG_HID_ICADE) += hid-icade.o
|
||||
obj-$(CONFIG_HID_ITE) += hid-ite.o
|
||||
obj-$(CONFIG_HID_JABRA) += hid-jabra.o
|
||||
|
|
@ -92,6 +93,7 @@ obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o
|
|||
obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o
|
||||
obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o
|
||||
obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
|
||||
obj-$(CONFIG_HID_MSI) += hid-msi.o
|
||||
obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o
|
||||
obj-$(CONFIG_HID_NINTENDO) += hid-nintendo.o
|
||||
obj-$(CONFIG_HID_NTI) += hid-nti.o
|
||||
|
|
@ -134,7 +136,7 @@ obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o
|
|||
obj-$(CONFIG_HID_SONY) += hid-sony.o
|
||||
obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o
|
||||
obj-$(CONFIG_HID_STEAM) += hid-steam.o
|
||||
obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o
|
||||
obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o hid-steelseries-arctis.o
|
||||
obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o
|
||||
obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o
|
||||
obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o hid-thrustmaster.o
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ menu "AMD SFH HID Support"
|
|||
config AMD_SFH_HID
|
||||
tristate "AMD Sensor Fusion Hub"
|
||||
depends on X86
|
||||
select AUXILIARY_BUS
|
||||
help
|
||||
If you say yes to this option, support will be included for the
|
||||
AMD Sensor Fusion Hub.
|
||||
|
|
|
|||
|
|
@ -383,3 +383,19 @@ int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool amd_sfh_op_idx_enabled(struct amd_mp2_dev *mp2)
|
||||
{
|
||||
struct amdtp_cl_data *cl = mp2->cl_data;
|
||||
int i;
|
||||
|
||||
if (!cl)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < cl->num_hid_devices; i++)
|
||||
if (cl->sensor_idx[i] == op_idx &&
|
||||
READ_ONCE(cl->sensor_sts[i]) == SENSOR_ENABLED)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#ifndef AMD_SFH_COMMON_H
|
||||
#define AMD_SFH_COMMON_H
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
#include "amd_sfh_hid.h"
|
||||
|
|
@ -35,6 +36,11 @@ enum cmd_id {
|
|||
STOP_ALL_SENSORS = 8,
|
||||
};
|
||||
|
||||
enum amd_mp2_version {
|
||||
MP2_VER_V2 = 1,
|
||||
MP2_VER_1_1 = 2,
|
||||
};
|
||||
|
||||
struct amd_mp2_sensor_info {
|
||||
u8 sensor_idx;
|
||||
u32 period;
|
||||
|
|
@ -64,6 +70,8 @@ struct amd_mp2_dev {
|
|||
struct mutex lock;
|
||||
u8 init_done;
|
||||
u8 rver;
|
||||
u8 mp2_ver;
|
||||
struct auxiliary_device *tm_auxdev;
|
||||
};
|
||||
|
||||
struct amd_mp2_ops {
|
||||
|
|
@ -100,4 +108,9 @@ static inline u64 amd_get_p2c_val(struct amd_mp2_dev *mp2, u32 idx)
|
|||
{
|
||||
return mp2->rver == 1 ? AMD_P2C_MSG_V1(idx) : AMD_P2C_MSG(idx);
|
||||
}
|
||||
|
||||
bool amd_sfh_op_idx_enabled(struct amd_mp2_dev *mp2);
|
||||
void sfh_set_emp2(struct amd_mp2_dev *mp2);
|
||||
void sfh_deinit_emp2(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -87,16 +87,17 @@ static int amdtp_wait_for_response(struct hid_device *hid)
|
|||
break;
|
||||
}
|
||||
|
||||
if (!cli_data->request_done[i])
|
||||
if (!cli_data->request_done[i]) {
|
||||
ret = wait_event_interruptible_timeout(hid_data->hid_wait,
|
||||
cli_data->request_done[i],
|
||||
msecs_to_jiffies(AMD_SFH_RESPONSE_TIMEOUT));
|
||||
if (ret == -ERESTARTSYS)
|
||||
return -ERESTARTSYS;
|
||||
else if (ret < 0)
|
||||
return -ETIMEDOUT;
|
||||
else
|
||||
return 0;
|
||||
if (ret == -ERESTARTSYS)
|
||||
return -ERESTARTSYS;
|
||||
if (ret <= 0)
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void amdtp_hid_wakeup(struct hid_device *hid)
|
||||
|
|
@ -162,6 +163,7 @@ int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data)
|
|||
return 0;
|
||||
|
||||
err_hid_device:
|
||||
cli_data->hid_sensor_hubs[cur_hid_dev] = NULL;
|
||||
kfree(hid_data);
|
||||
err_hid_data:
|
||||
hid_destroy_device(hid);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@
|
|||
* Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
*/
|
||||
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io-64-nonatomic-lo-hi.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
|
@ -122,19 +124,10 @@ static irqreturn_t amd_sfh_irq_handler(int irq, void *data)
|
|||
|
||||
int amd_sfh_irq_init_v2(struct amd_mp2_dev *privdata)
|
||||
{
|
||||
int rc;
|
||||
|
||||
pcim_intx(privdata->pdev, true);
|
||||
|
||||
rc = devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq,
|
||||
amd_sfh_irq_handler, 0, DRIVER_NAME, privdata);
|
||||
if (rc) {
|
||||
dev_err(&privdata->pdev->dev, "failed to request irq %d err=%d\n",
|
||||
privdata->pdev->irq, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return devm_request_irq(&privdata->pdev->dev, privdata->pdev->irq,
|
||||
amd_sfh_irq_handler, 0, DRIVER_NAME, privdata);
|
||||
}
|
||||
|
||||
static int amd_sfh_dis_sts_v2(struct amd_mp2_dev *privdata)
|
||||
|
|
@ -251,6 +244,8 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
|
|||
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);
|
||||
pcim_intx(mp2->pdev, false);
|
||||
|
|
@ -285,6 +280,7 @@ static void mp2_select_ops(struct amd_mp2_dev *privdata)
|
|||
switch (acs) {
|
||||
case V2_STATUS:
|
||||
privdata->mp2_ops = &amd_sfh_ops_v2;
|
||||
privdata->mp2_ver = MP2_VER_V2;
|
||||
break;
|
||||
default:
|
||||
privdata->mp2_ops = &amd_sfh_ops;
|
||||
|
|
@ -386,6 +382,51 @@ static const struct attribute_group *amd_sfh_groups[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
static DEFINE_IDA(sfh_tm_ida);
|
||||
|
||||
static void amd_sfh_maybe_register_tm(struct amd_mp2_dev *mp2)
|
||||
{
|
||||
struct auxiliary_device *adev;
|
||||
bool present;
|
||||
int id;
|
||||
|
||||
if (mp2->tm_auxdev)
|
||||
return;
|
||||
|
||||
present = mp2->sfh1_1_ops ? mp2->dev_en.is_sra_present
|
||||
: (mp2->mp2_ver == MP2_VER_V2 &&
|
||||
amd_sfh_op_idx_enabled(mp2));
|
||||
if (!present)
|
||||
return;
|
||||
|
||||
id = ida_alloc(&sfh_tm_ida, GFP_KERNEL);
|
||||
if (id < 0)
|
||||
return;
|
||||
|
||||
adev = auxiliary_device_create(&mp2->pdev->dev, KBUILD_MODNAME,
|
||||
"tabletmode", NULL, id);
|
||||
if (!adev) {
|
||||
ida_free(&sfh_tm_ida, id);
|
||||
dev_warn(&mp2->pdev->dev, "tabletmode auxdev create failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mp2->tm_auxdev = adev;
|
||||
}
|
||||
|
||||
static void amd_sfh_unregister_tm(struct amd_mp2_dev *mp2)
|
||||
{
|
||||
int id;
|
||||
|
||||
if (!mp2->tm_auxdev)
|
||||
return;
|
||||
|
||||
id = mp2->tm_auxdev->id;
|
||||
auxiliary_device_destroy(mp2->tm_auxdev);
|
||||
mp2->tm_auxdev = NULL;
|
||||
ida_free(&sfh_tm_ida, id);
|
||||
}
|
||||
|
||||
static void sfh1_1_init_work(struct work_struct *work)
|
||||
{
|
||||
struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work);
|
||||
|
|
@ -402,6 +443,7 @@ static void sfh1_1_init_work(struct work_struct *work)
|
|||
if (rc)
|
||||
dev_warn(&mp2->pdev->dev, "failed to update sysfs group\n");
|
||||
|
||||
amd_sfh_maybe_register_tm(mp2);
|
||||
}
|
||||
|
||||
static void sfh_init_work(struct work_struct *work)
|
||||
|
|
@ -418,8 +460,10 @@ static void sfh_init_work(struct work_struct *work)
|
|||
return;
|
||||
}
|
||||
|
||||
sfh_set_emp2(mp2);
|
||||
amd_sfh_clear_intr(mp2);
|
||||
mp2->init_done = 1;
|
||||
amd_sfh_maybe_register_tm(mp2);
|
||||
}
|
||||
|
||||
static void amd_sfh_remove(struct pci_dev *pdev)
|
||||
|
|
@ -427,6 +471,7 @@ static void amd_sfh_remove(struct pci_dev *pdev)
|
|||
struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev);
|
||||
|
||||
flush_work(&mp2->work);
|
||||
amd_sfh_unregister_tm(mp2);
|
||||
if (mp2->init_done)
|
||||
mp2->mp2_ops->remove(mp2);
|
||||
}
|
||||
|
|
@ -447,6 +492,7 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
|
|||
|
||||
privdata->pdev = pdev;
|
||||
dev_set_drvdata(&pdev->dev, privdata);
|
||||
|
||||
rc = pcim_enable_device(pdev);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
|
@ -471,8 +517,9 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
|
|||
if (rc)
|
||||
return rc;
|
||||
|
||||
privdata->sfh1_1_ops = (const struct amd_sfh1_1_ops *)id->driver_data;
|
||||
if (privdata->sfh1_1_ops) {
|
||||
privdata->mp2_ver = (enum amd_mp2_version)id->driver_data;
|
||||
if (privdata->mp2_ver >= MP2_VER_1_1) {
|
||||
privdata->sfh1_1_ops = &sfh1_1_ops;
|
||||
if (boot_cpu_data.x86 >= 0x1A)
|
||||
privdata->rver = 1;
|
||||
|
||||
|
|
@ -540,8 +587,7 @@ static SIMPLE_DEV_PM_OPS(amd_mp2_pm_ops, amd_mp2_pci_suspend,
|
|||
|
||||
static const struct pci_device_id amd_mp2_pci_tbl[] = {
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) },
|
||||
{ PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2_1_1),
|
||||
.driver_data = (kernel_ulong_t)&sfh1_1_ops },
|
||||
{ PCI_DEVICE_DATA(AMD, MP2_1_1, MP2_VER_1_1) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl);
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@
|
|||
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
*/
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/cleanup.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 DEFINE_MUTEX(emp2_lock);
|
||||
|
||||
static int amd_sfh_wait_response(struct amd_mp2_dev *mp2, u8 sid, u32 cmd_id)
|
||||
{
|
||||
|
|
@ -78,15 +80,48 @@ static struct amd_mp2_ops amd_sfh_ops = {
|
|||
|
||||
void sfh_deinit_emp2(void)
|
||||
{
|
||||
guard(mutex)(&emp2_lock);
|
||||
emp2 = NULL;
|
||||
}
|
||||
|
||||
void sfh_set_emp2(struct amd_mp2_dev *mp2)
|
||||
{
|
||||
guard(mutex)(&emp2_lock);
|
||||
emp2 = mp2;
|
||||
}
|
||||
|
||||
void sfh_interface_init(struct amd_mp2_dev *mp2)
|
||||
{
|
||||
mp2->mp2_ops = &amd_sfh_ops;
|
||||
guard(mutex)(&emp2_lock);
|
||||
emp2 = mp2;
|
||||
}
|
||||
|
||||
static int amd_sfh_op_mode_info(u32 *op_mode)
|
||||
{
|
||||
struct sfh_op_mode mode;
|
||||
bool present;
|
||||
|
||||
if (!op_mode)
|
||||
return -EINVAL;
|
||||
if (!emp2)
|
||||
return -ENODEV;
|
||||
|
||||
present = emp2->sfh1_1_ops ? emp2->dev_en.is_sra_present
|
||||
: (emp2->mp2_ver == MP2_VER_V2 &&
|
||||
amd_sfh_op_idx_enabled(emp2));
|
||||
if (!present)
|
||||
return -ENODEV;
|
||||
|
||||
mode.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 3));
|
||||
dev_dbg(&emp2->pdev->dev, "op-mode: %s (mode=%u)\n",
|
||||
mode.op_mode.mode == SFH_MODE_TABLET ? "tablet" : "laptop",
|
||||
mode.op_mode.mode);
|
||||
*op_mode = mode.op_mode.mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement)
|
||||
{
|
||||
struct sfh_op_mode mode;
|
||||
|
|
@ -160,6 +195,8 @@ static int amd_sfh_als_info(u32 *ambient_light)
|
|||
|
||||
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op)
|
||||
{
|
||||
guard(mutex)(&emp2_lock);
|
||||
|
||||
if (sfh_info) {
|
||||
switch (op) {
|
||||
case MT_HPD:
|
||||
|
|
@ -169,6 +206,8 @@ int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op)
|
|||
case MT_SRA:
|
||||
return amd_sfh_mode_info(&sfh_info->platform_type,
|
||||
&sfh_info->laptop_placement);
|
||||
case MT_OP_MODE:
|
||||
return amd_sfh_op_mode_info(&sfh_info->op_mode);
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
|
|
|
|||
|
|
@ -185,7 +185,6 @@ struct sfh_op_mode {
|
|||
};
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ struct apple_sc_backlight {
|
|||
struct hid_device *hdev;
|
||||
};
|
||||
|
||||
/* T2 VHCI re-enumerates the internal keyboard across system resume. */
|
||||
static int apple_backlight_resume_brightness = -1;
|
||||
|
||||
struct apple_backlight_config_report {
|
||||
u8 report_id;
|
||||
u8 version;
|
||||
|
|
@ -368,6 +371,8 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
|
|||
{ "TH87" }, /* EPOMAKER TH87 BT mode */
|
||||
{ "HFD Epomaker TH87" }, /* EPOMAKER TH87 USB mode */
|
||||
{ "2.4G Wireless Receiver" }, /* EPOMAKER TH87 dongle */
|
||||
{ "Thock TKL Wireless" }, /* ENDORFY THOCK TKL BT mode */
|
||||
{ "USB Dongle" }, /* ENDORFY THOCK TKL dongle */
|
||||
};
|
||||
|
||||
static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
|
||||
|
|
@ -825,6 +830,7 @@ static int apple_backlight_led_set(struct led_classdev *led_cdev,
|
|||
static int apple_backlight_init(struct hid_device *hdev)
|
||||
{
|
||||
int ret;
|
||||
int brightness;
|
||||
struct apple_sc *asc = hid_get_drvdata(hdev);
|
||||
struct apple_backlight_config_report *rep;
|
||||
|
||||
|
|
@ -857,16 +863,23 @@ static int apple_backlight_init(struct hid_device *hdev)
|
|||
}
|
||||
|
||||
asc->backlight->hdev = hdev;
|
||||
asc->backlight->cdev.name = "apple::kbd_backlight";
|
||||
asc->backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
|
||||
asc->backlight->cdev.max_brightness = rep->backlight_on_max;
|
||||
asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set;
|
||||
asc->backlight->cdev.flags = LED_CORE_SUSPENDRESUME;
|
||||
/* VHCI re-enumeration restores the cached brightness in the next probe. */
|
||||
|
||||
ret = apple_backlight_set(hdev, 0, 0);
|
||||
brightness = READ_ONCE(apple_backlight_resume_brightness);
|
||||
if (brightness < 0)
|
||||
brightness = LED_OFF;
|
||||
else
|
||||
brightness = min_t(int, brightness, rep->backlight_on_max);
|
||||
|
||||
ret = apple_backlight_set(hdev, brightness, 0);
|
||||
if (ret < 0) {
|
||||
hid_err(hdev, "backlight set request failed: %d\n", ret);
|
||||
goto cleanup_and_exit;
|
||||
}
|
||||
asc->backlight->cdev.brightness = brightness;
|
||||
|
||||
ret = devm_led_classdev_register(&hdev->dev, &asc->backlight->cdev);
|
||||
|
||||
|
|
@ -999,6 +1012,9 @@ static void apple_remove(struct hid_device *hdev)
|
|||
|
||||
if (asc->quirks & APPLE_RDESC_BATTERY)
|
||||
timer_delete_sync(&asc->battery_timer);
|
||||
if (asc->backlight)
|
||||
WRITE_ONCE(apple_backlight_resume_brightness,
|
||||
asc->backlight->cdev.brightness);
|
||||
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ static void appletb_inactivity_work(struct work_struct *work)
|
|||
if (!kbd->has_dimmed) {
|
||||
backlight_device_set_brightness(kbd->backlight_dev, 1);
|
||||
kbd->has_dimmed = true;
|
||||
mod_delayed_work(system_wq, &kbd->inactivity_work,
|
||||
mod_delayed_work(system_dfl_wq, &kbd->inactivity_work,
|
||||
secs_to_jiffies(appletb_tb_idle_timeout));
|
||||
} else if (!kbd->has_turned_off) {
|
||||
backlight_device_set_brightness(kbd->backlight_dev, 0);
|
||||
|
|
@ -201,7 +201,7 @@ static void reset_inactivity_timer(struct appletb_kbd *kbd)
|
|||
kbd->has_turned_off = false;
|
||||
schedule_work(&kbd->restore_brightness_work);
|
||||
}
|
||||
mod_delayed_work(system_wq, &kbd->inactivity_work,
|
||||
mod_delayed_work(system_dfl_wq, &kbd->inactivity_work,
|
||||
secs_to_jiffies(appletb_tb_dim_timeout));
|
||||
}
|
||||
}
|
||||
|
|
@ -423,7 +423,7 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id
|
|||
INIT_DELAYED_WORK(&kbd->inactivity_work, appletb_inactivity_work);
|
||||
INIT_WORK(&kbd->restore_brightness_work,
|
||||
appletb_restore_brightness_work);
|
||||
mod_delayed_work(system_wq, &kbd->inactivity_work,
|
||||
mod_delayed_work(system_dfl_wq, &kbd->inactivity_work,
|
||||
secs_to_jiffies(appletb_tb_dim_timeout));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
|
|||
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
|
||||
#define QUIRK_ROG_ALLY_XPAD BIT(13)
|
||||
#define QUIRK_HID_FN_LOCK BIT(14)
|
||||
#define QUIRK_FILTER_CAMERA_COMPANION BIT(15)
|
||||
|
||||
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
|
||||
QUIRK_NO_INIT_REPORTS | \
|
||||
|
|
@ -109,11 +110,36 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
|
|||
|
||||
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
|
||||
|
||||
struct asus_kbd_leds {
|
||||
struct asus_hid_listener listener;
|
||||
enum asus_work_action_type {
|
||||
FN_LOCK_SYNC,
|
||||
BRIGHTNESS_SET,
|
||||
WMI_FAN,
|
||||
};
|
||||
|
||||
struct hid_raw_event_data {
|
||||
u8 report_data[FEATURE_KBD_REPORT_SIZE];
|
||||
size_t report_size;
|
||||
};
|
||||
|
||||
struct asus_work_action {
|
||||
struct list_head node;
|
||||
enum asus_work_action_type type;
|
||||
union {
|
||||
/* Data for BRIGHTNESS_SET */
|
||||
unsigned int brightness;
|
||||
|
||||
/* Data for FN_LOCK_SYNC */
|
||||
bool fn_lock;
|
||||
|
||||
/* Data for WMI_FAN */
|
||||
struct hid_raw_event_data fan_hid_data;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct asus_worker {
|
||||
struct hid_device *hdev;
|
||||
struct work_struct work;
|
||||
unsigned int brightness;
|
||||
struct list_head actions;
|
||||
spinlock_t lock;
|
||||
bool removed;
|
||||
};
|
||||
|
|
@ -133,7 +159,8 @@ struct asus_drvdata {
|
|||
struct hid_device *hdev;
|
||||
struct input_dev *input;
|
||||
struct input_dev *tp_kbd_input;
|
||||
struct asus_kbd_leds *kbd_backlight;
|
||||
struct asus_worker *worker;
|
||||
unsigned int kbd_backlight_brightness;
|
||||
const struct asus_touchpad_info *tp;
|
||||
struct power_supply *battery;
|
||||
struct power_supply_desc battery_desc;
|
||||
|
|
@ -141,7 +168,7 @@ struct asus_drvdata {
|
|||
int battery_stat;
|
||||
bool battery_in_query;
|
||||
unsigned long battery_next_query;
|
||||
struct work_struct fn_lock_sync_work;
|
||||
struct asus_hid_listener listener;
|
||||
bool fn_lock;
|
||||
};
|
||||
|
||||
|
|
@ -211,6 +238,29 @@ static const u8 asus_report_id_init[] = {
|
|||
FEATURE_KBD_LED_REPORT_ID2
|
||||
};
|
||||
|
||||
/*
|
||||
* Send events to asus-wmi driver for handling special keys
|
||||
*/
|
||||
static int asus_wmi_send_event(struct asus_drvdata *drvdata, u8 code)
|
||||
{
|
||||
int err;
|
||||
u32 retval;
|
||||
|
||||
err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
|
||||
ASUS_WMI_METHODID_NOTIF, code, &retval);
|
||||
if (err) {
|
||||
pr_warn("Failed to notify asus-wmi: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (retval != 0) {
|
||||
pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void asus_report_contact_down(struct asus_drvdata *drvdat,
|
||||
int toolType, u8 *data)
|
||||
{
|
||||
|
|
@ -331,25 +381,71 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
|
|||
}
|
||||
|
||||
/*
|
||||
* Send events to asus-wmi driver for handling special keys
|
||||
* Used in atomic contexts to schedule work involving sleeps operations or
|
||||
* asus-wmi interactions.
|
||||
*
|
||||
* Caller is responsible to store relevant data in the structure to carry out
|
||||
* the required action.
|
||||
*
|
||||
* This function must be called while the spin lock protecting the workqueue
|
||||
* is already being held.
|
||||
*/
|
||||
static int asus_wmi_send_event(struct asus_drvdata *drvdata, u8 code)
|
||||
static void asus_worker_schedule(struct asus_worker *worker, struct asus_work_action *action)
|
||||
{
|
||||
int err;
|
||||
u32 retval;
|
||||
|
||||
err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS,
|
||||
ASUS_WMI_METHODID_NOTIF, code, &retval);
|
||||
if (err) {
|
||||
pr_warn("Failed to notify asus-wmi: %d\n", err);
|
||||
return err;
|
||||
if (worker->removed) {
|
||||
kfree(action);
|
||||
return;
|
||||
}
|
||||
|
||||
if (retval != 0) {
|
||||
pr_warn("Failed to notify asus-wmi (retval): 0x%x\n", retval);
|
||||
return -EIO;
|
||||
list_add_tail(&action->node, &worker->actions);
|
||||
schedule_work(&worker->work);
|
||||
}
|
||||
|
||||
static int asus_kbd_fn_lock_set(struct asus_drvdata *drvdata, bool enabled)
|
||||
{
|
||||
struct asus_work_action *action;
|
||||
unsigned long flags;
|
||||
|
||||
action = kzalloc(sizeof(struct asus_work_action), GFP_ATOMIC);
|
||||
if (!action)
|
||||
return -ENOMEM;
|
||||
|
||||
drvdata->fn_lock = enabled;
|
||||
action->type = FN_LOCK_SYNC;
|
||||
action->data.fn_lock = drvdata->fn_lock;
|
||||
INIT_LIST_HEAD(&action->node);
|
||||
|
||||
spin_lock_irqsave(&drvdata->worker->lock, flags);
|
||||
asus_worker_schedule(drvdata->worker, action);
|
||||
spin_unlock_irqrestore(&drvdata->worker->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_kbd_wmi_fan_send(struct asus_drvdata *drvdata, u8 *report_data,
|
||||
size_t report_size)
|
||||
{
|
||||
struct asus_work_action *action;
|
||||
unsigned long flags;
|
||||
|
||||
if (report_size > FEATURE_KBD_REPORT_SIZE) {
|
||||
hid_err(drvdata->hdev, "Invalid report size for fan event: %zu\n", report_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
action = kzalloc(sizeof(struct asus_work_action), GFP_NOWAIT);
|
||||
if (!action)
|
||||
return -ENOMEM;
|
||||
|
||||
action->type = WMI_FAN;
|
||||
action->data.fan_hid_data.report_size = report_size;
|
||||
memcpy(action->data.fan_hid_data.report_data, report_data, report_size);
|
||||
INIT_LIST_HEAD(&action->node);
|
||||
|
||||
spin_lock_irqsave(&drvdata->worker->lock, flags);
|
||||
asus_worker_schedule(drvdata->worker, action);
|
||||
spin_unlock_irqrestore(&drvdata->worker->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -357,6 +453,7 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
|
|||
struct hid_usage *usage, __s32 value)
|
||||
{
|
||||
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
int ret;
|
||||
|
||||
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
|
||||
(usage->hid & HID_USAGE) != 0x00 &&
|
||||
|
|
@ -375,8 +472,11 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
|
|||
return !asus_hid_event(ASUS_EV_BRTTOGGLE);
|
||||
case KEY_FN_ESC:
|
||||
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
|
||||
drvdata->fn_lock = !drvdata->fn_lock;
|
||||
schedule_work(&drvdata->fn_lock_sync_work);
|
||||
ret = asus_kbd_fn_lock_set(drvdata, !drvdata->fn_lock);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Error while toggling FN lock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -389,6 +489,12 @@ static int asus_raw_event(struct hid_device *hdev,
|
|||
struct hid_report *report, u8 *data, int size)
|
||||
{
|
||||
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
int ret;
|
||||
|
||||
if (size < 2) {
|
||||
hid_dbg(hdev, "Unexpected keyboard report size %d\n", size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
|
||||
return asus_report_battery(drvdata, data, size);
|
||||
|
|
@ -414,19 +520,13 @@ static int asus_raw_event(struct hid_device *hdev,
|
|||
* pass to userspace so it can implement its own fan control.
|
||||
*/
|
||||
if (data[1] == ASUS_FAN_CTRL_KEY_CODE) {
|
||||
int ret = asus_wmi_send_event(drvdata, ASUS_FAN_CTRL_KEY_CODE);
|
||||
ret = asus_kbd_wmi_fan_send(drvdata, data, size);
|
||||
|
||||
if (ret == 0) {
|
||||
/* Successfully handled by asus-wmi, block event */
|
||||
/* if execution deferred successfully block event */
|
||||
if (ret == 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Warn if asus-wmi failed (but not if it's unavailable).
|
||||
* Let the event reach userspace in all failure cases.
|
||||
*/
|
||||
if (ret != -ENODEV)
|
||||
hid_warn(hdev, "Failed to notify asus-wmi: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -460,6 +560,17 @@ static int asus_raw_event(struct hid_device *hdev,
|
|||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The camera-toggle key reports its vendor usage (0x85) together with a
|
||||
* companion state byte in the same array report, e.g. "5a 85 01" and
|
||||
* "5a 85 10" for the two toggle positions. The 0x10 companion aliases the
|
||||
* brightness-down vendor usage and would spuriously dim the panel, so drop
|
||||
* the companion slots and leave only the camera usage for input mapping.
|
||||
*/
|
||||
if (drvdata->quirks & QUIRK_FILTER_CAMERA_COMPANION &&
|
||||
report->id == FEATURE_KBD_REPORT_ID && size >= 3 && data[1] == 0x85)
|
||||
memset(&data[2], 0, size - 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -569,59 +680,157 @@ static int asus_kbd_disable_oobe(struct hid_device *hdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int asus_kbd_set_fn_lock(struct hid_device *hdev, bool enabled)
|
||||
static void asus_kbd_set_fn_lock(struct hid_device *hdev, bool enabled)
|
||||
{
|
||||
u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xd0, 0x4e, !!enabled };
|
||||
|
||||
return asus_kbd_set_report(hdev, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
static void asus_sync_fn_lock(struct work_struct *work)
|
||||
{
|
||||
struct asus_drvdata *drvdata =
|
||||
container_of(work, struct asus_drvdata, fn_lock_sync_work);
|
||||
|
||||
asus_kbd_set_fn_lock(drvdata->hdev, drvdata->fn_lock);
|
||||
}
|
||||
|
||||
static void asus_schedule_work(struct asus_kbd_leds *led)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led->lock, flags);
|
||||
if (!led->removed)
|
||||
schedule_work(&led->work);
|
||||
spin_unlock_irqrestore(&led->lock, flags);
|
||||
}
|
||||
|
||||
static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
|
||||
int brightness)
|
||||
{
|
||||
struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
|
||||
listener);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led->lock, flags);
|
||||
led->brightness = brightness;
|
||||
spin_unlock_irqrestore(&led->lock, flags);
|
||||
|
||||
asus_schedule_work(led);
|
||||
}
|
||||
|
||||
static void asus_kbd_backlight_work(struct work_struct *work)
|
||||
{
|
||||
struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
|
||||
u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
|
||||
const u8 buf[FEATURE_KBD_REPORT_SIZE] = { FEATURE_KBD_REPORT_ID, 0xd0, 0x4e, !!enabled };
|
||||
int ret;
|
||||
|
||||
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
|
||||
if (ret < 0)
|
||||
hid_err(hdev, "Asus failed to set fn lock: %d\n", ret);
|
||||
}
|
||||
|
||||
static void asus_kbd_set_brightness(struct hid_device *hdev, u8 brightness)
|
||||
{
|
||||
const u8 buf[FEATURE_KBD_REPORT_SIZE] = {
|
||||
FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, brightness
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
|
||||
if (ret < 0)
|
||||
hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
|
||||
}
|
||||
|
||||
static void asus_kbd_wmi_fan(struct hid_device *hdev, struct hid_raw_event_data *data)
|
||||
{
|
||||
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
int ret;
|
||||
|
||||
ret = asus_wmi_send_event(drvdata, ASUS_FAN_CTRL_KEY_CODE);
|
||||
|
||||
/*
|
||||
* Warn if asus-wmi failed (but not if it's unavailable).
|
||||
* Let the event reach userspace in all failure cases.
|
||||
*/
|
||||
switch (ret) {
|
||||
case -ENODEV:
|
||||
break;
|
||||
case 0:
|
||||
return;
|
||||
default:
|
||||
hid_warn(hdev, "Failed to notify asus-wmi: %d\n", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallback: pass the raw event to the HID core; to avoid
|
||||
* racing against the hid_report_raw_event() that generated
|
||||
* this event use the same locking mechanism and wait for
|
||||
* that function to terminate and signal the deferred execution
|
||||
* before raising the stored event.
|
||||
*/
|
||||
down(&hdev->driver_input_lock);
|
||||
hid_report_raw_event(hdev, HID_INPUT_REPORT,
|
||||
data->report_data, data->report_size,
|
||||
data->report_size, 1);
|
||||
up(&hdev->driver_input_lock);
|
||||
}
|
||||
|
||||
static void asus_kbd_backlight_set(struct asus_hid_listener *listener, int brightness)
|
||||
{
|
||||
struct asus_drvdata *drvdata = container_of(listener, struct asus_drvdata, listener);
|
||||
struct asus_worker *worker = drvdata->worker;
|
||||
struct asus_work_action *action;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led->lock, flags);
|
||||
buf[4] = led->brightness;
|
||||
spin_unlock_irqrestore(&led->lock, flags);
|
||||
drvdata->kbd_backlight_brightness = brightness;
|
||||
|
||||
ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf));
|
||||
if (ret < 0)
|
||||
hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
|
||||
action = kzalloc(sizeof(struct asus_work_action), GFP_NOWAIT);
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
action->type = BRIGHTNESS_SET;
|
||||
action->data.brightness = brightness;
|
||||
INIT_LIST_HEAD(&action->node);
|
||||
|
||||
spin_lock_irqsave(&worker->lock, flags);
|
||||
asus_worker_schedule(worker, action);
|
||||
spin_unlock_irqrestore(&worker->lock, flags);
|
||||
}
|
||||
|
||||
static void asus_work(struct work_struct *work)
|
||||
{
|
||||
struct asus_worker *worker = container_of(work, struct asus_worker, work);
|
||||
struct asus_work_action *action = NULL;
|
||||
unsigned long flags;
|
||||
|
||||
/* Save the action to be performed and clear the flag */
|
||||
spin_lock_irqsave(&worker->lock, flags);
|
||||
if (!list_empty(&worker->actions)) {
|
||||
action = list_first_entry(&worker->actions,
|
||||
struct asus_work_action, node);
|
||||
list_del(&action->node);
|
||||
}
|
||||
spin_unlock_irqrestore(&worker->lock, flags);
|
||||
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
switch (action->type) {
|
||||
case BRIGHTNESS_SET:
|
||||
asus_kbd_set_brightness(worker->hdev, action->data.brightness);
|
||||
break;
|
||||
case FN_LOCK_SYNC:
|
||||
asus_kbd_set_fn_lock(worker->hdev, action->data.fn_lock);
|
||||
break;
|
||||
case WMI_FAN:
|
||||
asus_kbd_wmi_fan(worker->hdev, &action->data.fan_hid_data);
|
||||
break;
|
||||
default:
|
||||
hid_err(worker->hdev, "Invalid action type: %d\n", action->type);
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(action);
|
||||
|
||||
/* Re-schedule if there are more pending actions */
|
||||
spin_lock_irqsave(&worker->lock, flags);
|
||||
if (!list_empty(&worker->actions))
|
||||
schedule_work(&worker->work);
|
||||
spin_unlock_irqrestore(&worker->lock, flags);
|
||||
}
|
||||
|
||||
static int asus_worker_create(struct hid_device *hdev, struct asus_drvdata *drvdata)
|
||||
{
|
||||
drvdata->worker = devm_kzalloc(&hdev->dev, sizeof(struct asus_worker), GFP_KERNEL);
|
||||
if (!drvdata->worker)
|
||||
return -ENOMEM;
|
||||
|
||||
drvdata->worker->removed = false;
|
||||
drvdata->worker->hdev = hdev;
|
||||
INIT_LIST_HEAD(&drvdata->worker->actions);
|
||||
|
||||
INIT_WORK(&drvdata->worker->work, asus_work);
|
||||
spin_lock_init(&drvdata->worker->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void asus_worker_stop(struct asus_worker *worker)
|
||||
{
|
||||
struct asus_work_action *action, *tmp;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&worker->lock, flags);
|
||||
worker->removed = true;
|
||||
list_for_each_entry_safe(action, tmp, &worker->actions, node) {
|
||||
list_del(&action->node);
|
||||
kfree(action);
|
||||
}
|
||||
spin_unlock_irqrestore(&worker->lock, flags);
|
||||
|
||||
cancel_work_sync(&worker->work);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -644,7 +853,7 @@ static int mcu_parse_version_string(const u8 *response, size_t response_size)
|
|||
dots++;
|
||||
}
|
||||
|
||||
if (dots != 2 || p >= end || (p + 3) >= end)
|
||||
if (dots != 2 || end - p < 3)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(buf, p, 3);
|
||||
|
|
@ -753,30 +962,18 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
|
||||
if ((drvdata->quirks & QUIRK_ROG_ALLY_XPAD) && hid_is_usb(hdev)) {
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
udev = interface_to_usbdev(intf);
|
||||
validate_mcu_fw_version(hdev,
|
||||
le16_to_cpu(udev->descriptor.idProduct));
|
||||
}
|
||||
|
||||
drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
|
||||
sizeof(struct asus_kbd_leds),
|
||||
GFP_KERNEL);
|
||||
if (!drvdata->kbd_backlight)
|
||||
return -ENOMEM;
|
||||
|
||||
drvdata->kbd_backlight->removed = false;
|
||||
drvdata->kbd_backlight->brightness = 0;
|
||||
drvdata->kbd_backlight->hdev = hdev;
|
||||
drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
|
||||
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
|
||||
spin_lock_init(&drvdata->kbd_backlight->lock);
|
||||
|
||||
ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
|
||||
drvdata->listener.brightness_set = asus_kbd_backlight_set;
|
||||
ret = asus_hid_register_listener(&drvdata->listener);
|
||||
if (ret < 0) {
|
||||
/* No need to have this still around */
|
||||
devm_kfree(&hdev->dev, drvdata->kbd_backlight);
|
||||
hid_err(hdev, "Unable to register kbd brightness listener: %d\n", ret);
|
||||
drvdata->listener.brightness_set = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
@ -998,11 +1195,9 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
|||
|
||||
drvdata->input = input;
|
||||
|
||||
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
|
||||
drvdata->fn_lock = true;
|
||||
INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
|
||||
asus_kbd_set_fn_lock(hdev, true);
|
||||
}
|
||||
if ((drvdata->quirks & QUIRK_HID_FN_LOCK) &&
|
||||
(asus_kbd_fn_lock_set(drvdata, true)))
|
||||
hid_warn(hdev, "Error while setting FN lock to ON\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1045,6 +1240,8 @@ static int asus_input_mapping(struct hid_device *hdev,
|
|||
case 0x6c: asus_map_key_clear(KEY_SLEEP); break;
|
||||
case 0x7c: asus_map_key_clear(KEY_MICMUTE); break;
|
||||
case 0x82: asus_map_key_clear(KEY_CAMERA); break;
|
||||
case 0x85: asus_map_key_clear(KEY_CAMERA); break;
|
||||
case 0x86: asus_map_key_clear(KEY_PROG1); break; /* MyASUS key */
|
||||
case 0x88: asus_map_key_clear(KEY_RFKILL); break;
|
||||
case 0xb5: asus_map_key_clear(KEY_CALC); break;
|
||||
case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP); break;
|
||||
|
|
@ -1054,6 +1251,7 @@ static int asus_input_mapping(struct hid_device *hdev,
|
|||
case 0x7e: asus_map_key_clear(KEY_EMOJI_PICKER); break;
|
||||
|
||||
case 0x8b: asus_map_key_clear(KEY_PROG1); break; /* ProArt Creator Hub key */
|
||||
case 0x5f: asus_map_key_clear(KEY_PROG2); break; /* S-shaped programmable key */
|
||||
case 0x6b: asus_map_key_clear(KEY_F21); break; /* ASUS touchpad toggle */
|
||||
case 0x38: asus_map_key_clear(KEY_PROG1); break; /* ROG key */
|
||||
case 0xba: asus_map_key_clear(KEY_PROG2); break; /* Fn+C ASUS Splendid */
|
||||
|
|
@ -1165,20 +1363,16 @@ static int asus_start_multitouch(struct hid_device *hdev)
|
|||
static int __maybe_unused asus_resume(struct hid_device *hdev)
|
||||
{
|
||||
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
int ret = 0;
|
||||
|
||||
if (drvdata->kbd_backlight) {
|
||||
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
|
||||
drvdata->kbd_backlight->brightness };
|
||||
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
|
||||
if (ret < 0) {
|
||||
hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
|
||||
goto asus_resume_err;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If we have a backlight listener registered, restore the previous state,
|
||||
* in case of error do not fail: most models restore the backlight
|
||||
* automatically, and the error is non-fatal.
|
||||
*/
|
||||
if (drvdata->listener.brightness_set)
|
||||
asus_kbd_backlight_set(&drvdata->listener, drvdata->kbd_backlight_brightness);
|
||||
|
||||
asus_resume_err:
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
|
||||
|
|
@ -1200,10 +1394,8 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
int ret;
|
||||
|
||||
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
|
||||
if (drvdata == NULL) {
|
||||
hid_err(hdev, "Can't alloc Asus descriptor\n");
|
||||
if (drvdata == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
hid_set_drvdata(hdev, drvdata);
|
||||
|
||||
|
|
@ -1288,8 +1480,15 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
is_vendor = true;
|
||||
}
|
||||
|
||||
ret = asus_worker_create(hdev, drvdata);
|
||||
if (ret) {
|
||||
hid_warn(hdev, "Failed to initialize worker: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
asus_worker_stop(drvdata->worker);
|
||||
hid_err(hdev, "Asus hw start failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1337,6 +1536,10 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
|
||||
return 0;
|
||||
err_stop_hw:
|
||||
if (drvdata->listener.brightness_set)
|
||||
asus_hid_unregister_listener(&drvdata->listener);
|
||||
|
||||
asus_worker_stop(drvdata->worker);
|
||||
hid_hw_stop(hdev);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1344,21 +1547,11 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
static void asus_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
unsigned long flags;
|
||||
|
||||
if (drvdata->kbd_backlight) {
|
||||
asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
|
||||
|
||||
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
|
||||
drvdata->kbd_backlight->removed = true;
|
||||
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
|
||||
|
||||
cancel_work_sync(&drvdata->kbd_backlight->work);
|
||||
}
|
||||
|
||||
if (drvdata->quirks & QUIRK_HID_FN_LOCK)
|
||||
cancel_work_sync(&drvdata->fn_lock_sync_work);
|
||||
if (drvdata->listener.brightness_set)
|
||||
asus_hid_unregister_listener(&drvdata->listener);
|
||||
|
||||
asus_worker_stop(drvdata->worker);
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
|
|
@ -1477,6 +1670,9 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
|||
static const struct hid_device_id asus_devices[] = {
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD), I2C_KEYBOARD_QUIRKS},
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_KEYBOARD),
|
||||
I2C_KEYBOARD_QUIRKS | QUIRK_FILTER_CAMERA_COMPANION },
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD), I2C_TOUCHPAD_QUIRKS },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
|
|
@ -1494,6 +1690,9 @@ static const struct hid_device_id asus_devices[] = {
|
|||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
|
|
|
|||
|
|
@ -59,30 +59,24 @@ static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int axff_init(struct hid_device *hid)
|
||||
static int ax_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct axff_device *axff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct input_dev *dev;
|
||||
struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int field_count = 0;
|
||||
int i, j;
|
||||
int error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output reports found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
report = list_first_entry(report_list, struct hid_report, list);
|
||||
for (i = 0; i < report->maxfield; i++) {
|
||||
for (j = 0; j < report->field[i]->report_count; j++) {
|
||||
report->field[i]->value[j] = 0x00;
|
||||
|
|
@ -100,13 +94,13 @@ static int axff_init(struct hid_device *hid)
|
|||
if (!axff)
|
||||
return -ENOMEM;
|
||||
|
||||
axff->report = report;
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, axff, axff_play);
|
||||
if (error)
|
||||
goto err_free_mem;
|
||||
|
||||
axff->report = report;
|
||||
hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT);
|
||||
|
||||
hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun <x0r@dv-life.ru>\n");
|
||||
|
|
@ -118,7 +112,8 @@ static int axff_init(struct hid_device *hid)
|
|||
return error;
|
||||
}
|
||||
#else
|
||||
static inline int axff_init(struct hid_device *hid)
|
||||
static inline int ax_input_configured(struct hid_device *hid,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -136,23 +131,11 @@ static int ax_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
return error;
|
||||
}
|
||||
|
||||
error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
error = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (error) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
error = axff_init(hdev);
|
||||
if (error) {
|
||||
/*
|
||||
* Do not fail device initialization completely as device
|
||||
* may still be partially operable, just warn.
|
||||
*/
|
||||
hid_warn(hdev,
|
||||
"Failed to enable force feedback support, error: %d\n",
|
||||
error);
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to start polling device right away, otherwise
|
||||
* it will go into a coma.
|
||||
|
|
@ -185,6 +168,7 @@ static struct hid_driver ax_driver = {
|
|||
.id_table = ax_devices,
|
||||
.probe = ax_probe,
|
||||
.remove = ax_remove,
|
||||
.input_configured = ax_input_configured,
|
||||
};
|
||||
module_hid_driver(ax_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -52,31 +52,24 @@ static int hid_betopff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int betopff_init(struct hid_device *hid)
|
||||
static int betop_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct betopff_device *betopff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct list_head *report_list =
|
||||
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
int i, j;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output reports found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
report = list_first_entry(report_list, struct hid_report, list);
|
||||
/*
|
||||
* Actually there are 4 fields for 4 Bytes as below:
|
||||
* -----------------------------------------
|
||||
|
|
@ -104,6 +97,7 @@ static int betopff_init(struct hid_device *hid)
|
|||
if (!betopff)
|
||||
return -ENOMEM;
|
||||
|
||||
betopff->report = report;
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, betopff, hid_betopff_play);
|
||||
|
|
@ -112,7 +106,6 @@ static int betopff_init(struct hid_device *hid)
|
|||
return error;
|
||||
}
|
||||
|
||||
betopff->report = report;
|
||||
hid_hw_request(hid, betopff->report, HID_REQ_SET_REPORT);
|
||||
|
||||
hid_info(hid, "Force feedback for betop devices by huangbo <huangbobupt@163.com>\n");
|
||||
|
|
@ -130,20 +123,15 @@ static int betop_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
betopff_init(hdev);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id betop_devices[] = {
|
||||
|
|
@ -159,6 +147,7 @@ static struct hid_driver betop_driver = {
|
|||
.name = "betop",
|
||||
.id_table = betop_devices,
|
||||
.probe = betop_probe,
|
||||
.input_configured = betop_input_configured,
|
||||
};
|
||||
module_hid_driver(betop_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -366,58 +366,29 @@ static void bigben_remove(struct hid_device *hid)
|
|||
hid_hw_stop(hid);
|
||||
}
|
||||
|
||||
static int bigben_probe(struct hid_device *hid,
|
||||
const struct hid_device_id *id)
|
||||
static int bigben_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct bigben_device *bigben;
|
||||
struct hid_input *hidinput;
|
||||
struct bigben_device *bigben = hid_get_drvdata(hid);
|
||||
struct input_dev *input_dev = hidinput->input;
|
||||
struct led_classdev *led;
|
||||
char *name;
|
||||
size_t name_sz;
|
||||
int n, error;
|
||||
|
||||
bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL);
|
||||
if (!bigben)
|
||||
return -ENOMEM;
|
||||
hid_set_drvdata(hid, bigben);
|
||||
bigben->hid = hid;
|
||||
bigben->removed = false;
|
||||
|
||||
error = hid_parse(hid);
|
||||
if (error) {
|
||||
hid_err(hid, "parse failed\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (error) {
|
||||
hid_err(hid, "hw start failed\n");
|
||||
return error;
|
||||
}
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8);
|
||||
if (!bigben->report) {
|
||||
hid_err(hid, "no output report found\n");
|
||||
error = -ENODEV;
|
||||
goto error_hw_stop;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
error = -ENODEV;
|
||||
goto error_hw_stop;
|
||||
}
|
||||
set_bit(FF_RUMBLE, input_dev->ffbit);
|
||||
|
||||
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
|
||||
set_bit(FF_RUMBLE, hidinput->input->ffbit);
|
||||
|
||||
INIT_WORK(&bigben->worker, bigben_worker);
|
||||
spin_lock_init(&bigben->lock);
|
||||
|
||||
error = input_ff_create_memless(hidinput->input, NULL,
|
||||
hid_bigben_play_effect);
|
||||
error = input_ff_create_memless(input_dev, NULL, hid_bigben_play_effect);
|
||||
if (error)
|
||||
goto error_hw_stop;
|
||||
return error;
|
||||
|
||||
name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1;
|
||||
|
||||
|
|
@ -427,10 +398,9 @@ static int bigben_probe(struct hid_device *hid,
|
|||
sizeof(struct led_classdev) + name_sz,
|
||||
GFP_KERNEL
|
||||
);
|
||||
if (!led) {
|
||||
error = -ENOMEM;
|
||||
goto error_hw_stop;
|
||||
}
|
||||
if (!led)
|
||||
return -ENOMEM;
|
||||
|
||||
name = (void *)(&led[1]);
|
||||
snprintf(name, name_sz,
|
||||
"%s:red:bigben%d",
|
||||
|
|
@ -444,7 +414,7 @@ static int bigben_probe(struct hid_device *hid,
|
|||
bigben->leds[n] = led;
|
||||
error = devm_led_classdev_register(&hid->dev, led);
|
||||
if (error)
|
||||
goto error_hw_stop;
|
||||
return error;
|
||||
}
|
||||
|
||||
/* initial state: LED1 is on, no rumble effect */
|
||||
|
|
@ -458,10 +428,36 @@ static int bigben_probe(struct hid_device *hid,
|
|||
hid_info(hid, "LED and force feedback support for BigBen gamepad\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
error_hw_stop:
|
||||
hid_hw_stop(hid);
|
||||
return error;
|
||||
static int bigben_probe(struct hid_device *hid, const struct hid_device_id *id)
|
||||
{
|
||||
struct bigben_device *bigben;
|
||||
int error;
|
||||
|
||||
bigben = devm_kzalloc(&hid->dev, sizeof(*bigben), GFP_KERNEL);
|
||||
if (!bigben)
|
||||
return -ENOMEM;
|
||||
|
||||
hid_set_drvdata(hid, bigben);
|
||||
bigben->hid = hid;
|
||||
bigben->removed = false;
|
||||
INIT_WORK(&bigben->worker, bigben_worker);
|
||||
spin_lock_init(&bigben->lock);
|
||||
|
||||
error = hid_parse(hid);
|
||||
if (error) {
|
||||
hid_err(hid, "parse failed\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
error = hid_hw_start(hid, HID_CONNECT_DEFAULT);
|
||||
if (error) {
|
||||
hid_err(hid, "hw start failed\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc,
|
||||
|
|
@ -487,6 +483,7 @@ static struct hid_driver bigben_driver = {
|
|||
.probe = bigben_probe,
|
||||
.report_fixup = bigben_report_fixup,
|
||||
.remove = bigben_remove,
|
||||
.input_configured = bigben_input_configured,
|
||||
};
|
||||
module_hid_driver(bigben_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -379,6 +379,9 @@ static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsign
|
|||
|
||||
static u32 item_udata(struct hid_item *item)
|
||||
{
|
||||
if (item->format != HID_ITEM_FORMAT_SHORT)
|
||||
return 0;
|
||||
|
||||
switch (item->size) {
|
||||
case 1: return item->data.u8;
|
||||
case 2: return item->data.u16;
|
||||
|
|
@ -389,6 +392,9 @@ static u32 item_udata(struct hid_item *item)
|
|||
|
||||
static s32 item_sdata(struct hid_item *item)
|
||||
{
|
||||
if (item->format != HID_ITEM_FORMAT_SHORT)
|
||||
return 0;
|
||||
|
||||
switch (item->size) {
|
||||
case 1: return item->data.s8;
|
||||
case 2: return item->data.s16;
|
||||
|
|
@ -1933,13 +1939,14 @@ int hid_set_field(struct hid_field *field, unsigned offset, __s32 value)
|
|||
|
||||
size = field->report_size;
|
||||
|
||||
hid_dump_input(field->report->device, field->usage + offset, value);
|
||||
|
||||
if (offset >= field->report_count) {
|
||||
hid_err(field->report->device, "offset (%d) exceeds report_count (%d)\n",
|
||||
offset, field->report_count);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hid_dump_input(field->report->device, field->usage + offset, value);
|
||||
|
||||
if (field->logical_minimum < 0) {
|
||||
if (value != snto32(s32ton(value, size), size)) {
|
||||
hid_err(field->report->device, "value %d is out of range\n", value);
|
||||
|
|
@ -2312,8 +2319,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
|
|||
if (hid_hiddev(hdev))
|
||||
connect_mask |= HID_CONNECT_HIDDEV_FORCE;
|
||||
|
||||
if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev,
|
||||
connect_mask & HID_CONNECT_HIDINPUT_FORCE))
|
||||
if ((connect_mask & HID_CONNECT_HIDINPUT) &&
|
||||
!hidinput_connect(hdev, connect_mask))
|
||||
hdev->claimed |= HID_CLAIMED_INPUT;
|
||||
|
||||
if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect &&
|
||||
|
|
@ -2335,10 +2342,6 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
|
|||
|
||||
hid_process_ordering(hdev);
|
||||
|
||||
if ((hdev->claimed & HID_CLAIMED_INPUT) &&
|
||||
(connect_mask & HID_CONNECT_FF) && hdev->ff_init)
|
||||
hdev->ff_init(hdev);
|
||||
|
||||
len = 0;
|
||||
if (hdev->claimed & HID_CLAIMED_INPUT)
|
||||
len += sprintf(buf + len, "input");
|
||||
|
|
@ -2447,9 +2450,16 @@ EXPORT_SYMBOL_GPL(hid_hw_start);
|
|||
*
|
||||
* This is usually called from remove function or from probe when something
|
||||
* failed and hid_hw_start was called already.
|
||||
*
|
||||
* If the caller enabled HID input via hid_device_io_start() and is unwinding
|
||||
* without an explicit hid_device_io_stop(), quiesce input first so that
|
||||
* in-flight reports cannot reach handlers (e.g. hidraw_report_event) whose
|
||||
* backing objects hid_disconnect() is about to free.
|
||||
*/
|
||||
void hid_hw_stop(struct hid_device *hdev)
|
||||
{
|
||||
if (hdev->io_started)
|
||||
hid_device_io_stop(hdev);
|
||||
hid_disconnect(hdev);
|
||||
hdev->ll_driver->stop(hdev);
|
||||
}
|
||||
|
|
@ -2842,6 +2852,8 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
|||
*/
|
||||
|
||||
if (ret) {
|
||||
if (hdev->io_started)
|
||||
hid_device_io_stop(hdev);
|
||||
devres_release_group(&hdev->dev, hdev->devres_group_id);
|
||||
hid_close_report(hdev);
|
||||
hdev->driver = NULL;
|
||||
|
|
@ -2907,6 +2919,45 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
|
|||
}
|
||||
static DEVICE_ATTR_RO(modalias);
|
||||
|
||||
/*
|
||||
* Expose this as bustype instead of bus as
|
||||
* that's the name the input subsystem uses
|
||||
*/
|
||||
static ssize_t bustype_show(struct device *dev, struct device_attribute *a,
|
||||
char *buf)
|
||||
{
|
||||
struct hid_device *hdev = to_hid_device(dev);
|
||||
|
||||
return sysfs_emit(buf, "%04x\n", hdev->bus);
|
||||
}
|
||||
static DEVICE_ATTR_RO(bustype);
|
||||
|
||||
#define HID_DEV_ID_ATTR(name) \
|
||||
static ssize_t name##_show(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct hid_device *hdev = to_hid_device(dev); \
|
||||
\
|
||||
return sysfs_emit(buf, "%04x\n", hdev->name); \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(name)
|
||||
|
||||
HID_DEV_ID_ATTR(vendor);
|
||||
HID_DEV_ID_ATTR(product);
|
||||
HID_DEV_ID_ATTR(version);
|
||||
|
||||
static struct attribute *hid_dev_id_attrs[] = {
|
||||
&dev_attr_bustype.attr,
|
||||
&dev_attr_vendor.attr,
|
||||
&dev_attr_product.attr,
|
||||
&dev_attr_version.attr,
|
||||
NULL
|
||||
};
|
||||
static const struct attribute_group hid_dev_id_attr_group = {
|
||||
.name = "id",
|
||||
.attrs = hid_dev_id_attrs,
|
||||
};
|
||||
static struct attribute *hid_dev_attrs[] = {
|
||||
&dev_attr_modalias.attr,
|
||||
NULL,
|
||||
|
|
@ -2919,7 +2970,11 @@ static const struct attribute_group hid_dev_group = {
|
|||
.attrs = hid_dev_attrs,
|
||||
.bin_attrs = hid_dev_bin_attrs,
|
||||
};
|
||||
__ATTRIBUTE_GROUPS(hid_dev);
|
||||
static const struct attribute_group *hid_dev_groups[] = {
|
||||
&hid_dev_group,
|
||||
&hid_dev_id_attr_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@
|
|||
#define CORSAIR_VOID_STATUS_REPORT_ID 0x64
|
||||
#define CORSAIR_VOID_FIRMWARE_REPORT_ID 0x66
|
||||
|
||||
#define CORSAIR_VOID_STATUS_REPORT_SIZE 5
|
||||
#define CORSAIR_VOID_FIRMWARE_REPORT_SIZE 5
|
||||
|
||||
#define CORSAIR_VOID_USB_SIDETONE_REQUEST 0x1
|
||||
#define CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE 0x21
|
||||
#define CORSAIR_VOID_USB_SIDETONE_VALUE 0x200
|
||||
|
|
@ -742,6 +745,13 @@ static int corsair_void_raw_event(struct hid_device *hid_dev,
|
|||
|
||||
/* Description of packets are documented at the top of this file */
|
||||
if (hid_report->id == CORSAIR_VOID_STATUS_REPORT_ID) {
|
||||
if (size < CORSAIR_VOID_STATUS_REPORT_SIZE) {
|
||||
hid_warn_ratelimited(hid_dev,
|
||||
"unexpected status report of size %d",
|
||||
size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
drvdata->mic_up = FIELD_GET(CORSAIR_VOID_MIC_MASK, data[2]);
|
||||
drvdata->connected = (data[3] == CORSAIR_VOID_WIRELESS_CONNECTED) ||
|
||||
drvdata->is_wired;
|
||||
|
|
@ -750,6 +760,13 @@ static int corsair_void_raw_event(struct hid_device *hid_dev,
|
|||
FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]),
|
||||
data[3], data[4]);
|
||||
} else if (hid_report->id == CORSAIR_VOID_FIRMWARE_REPORT_ID) {
|
||||
if (size < CORSAIR_VOID_FIRMWARE_REPORT_SIZE) {
|
||||
hid_warn_ratelimited(hid_dev,
|
||||
"unexpected firmware report of size %d",
|
||||
size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
drvdata->fw_receiver_major = data[1];
|
||||
drvdata->fw_receiver_minor = data[2];
|
||||
drvdata->fw_headset_major = data[3];
|
||||
|
|
|
|||
|
|
@ -524,8 +524,8 @@ static void k90_cleanup_backlight(struct hid_device *dev)
|
|||
|
||||
if (drvdata->backlight) {
|
||||
drvdata->backlight->removed = true;
|
||||
led_classdev_unregister(&drvdata->backlight->cdev);
|
||||
cancel_work_sync(&drvdata->backlight->work);
|
||||
led_classdev_unregister(&drvdata->backlight->cdev);
|
||||
kfree(drvdata->backlight->cdev.name);
|
||||
kfree(drvdata->backlight);
|
||||
}
|
||||
|
|
@ -540,11 +540,12 @@ static void k90_cleanup_macro_functions(struct hid_device *dev)
|
|||
sysfs_remove_group(&dev->dev.kobj, &k90_attr_group);
|
||||
|
||||
k90->record_led.removed = true;
|
||||
led_classdev_unregister(&k90->record_led.cdev);
|
||||
cancel_work_sync(&k90->record_led.work);
|
||||
led_classdev_unregister(&k90->record_led.cdev);
|
||||
kfree(k90->record_led.cdev.name);
|
||||
|
||||
kfree(k90);
|
||||
drvdata->k90 = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -596,10 +597,10 @@ static int corsair_probe(struct hid_device *dev, const struct hid_device_id *id)
|
|||
|
||||
static void corsair_remove(struct hid_device *dev)
|
||||
{
|
||||
hid_hw_stop(dev);
|
||||
|
||||
k90_cleanup_macro_functions(dev);
|
||||
k90_cleanup_backlight(dev);
|
||||
|
||||
hid_hw_stop(dev);
|
||||
}
|
||||
|
||||
static int corsair_event(struct hid_device *dev, struct hid_field *field,
|
||||
|
|
|
|||
|
|
@ -71,29 +71,26 @@ static int drff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int drff_init(struct hid_device *hid)
|
||||
static int dr_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct drff_device *drff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct list_head *report_list =
|
||||
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
if (hid->product != 0x0006)
|
||||
return 0;
|
||||
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output reports found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
report = list_first_entry(report_list, struct hid_report, list);
|
||||
if (report->maxfield < 1) {
|
||||
hid_err(hid, "no fields in the report\n");
|
||||
return -ENODEV;
|
||||
|
|
@ -108,6 +105,7 @@ static int drff_init(struct hid_device *hid)
|
|||
if (!drff)
|
||||
return -ENOMEM;
|
||||
|
||||
drff->report = report;
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, drff, drff_play);
|
||||
|
|
@ -116,7 +114,6 @@ static int drff_init(struct hid_device *hid)
|
|||
return error;
|
||||
}
|
||||
|
||||
drff->report = report;
|
||||
drff->report->field[0]->value[0] = 0xf3;
|
||||
drff->report->field[0]->value[1] = 0x00;
|
||||
drff->report->field[0]->value[2] = 0x00;
|
||||
|
|
@ -132,7 +129,8 @@ static int drff_init(struct hid_device *hid)
|
|||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int drff_init(struct hid_device *hid)
|
||||
static inline int dr_input_configured(struct hid_device *hid,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -266,43 +264,9 @@ static int dr_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int dr_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dev_dbg(&hdev->dev, "DragonRise Inc. HID hardware probe...");
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
switch (hdev->product) {
|
||||
case 0x0006:
|
||||
ret = drff_init(hdev);
|
||||
if (ret) {
|
||||
dev_err(&hdev->dev, "force feedback init failed\n");
|
||||
hid_hw_stop(hdev);
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id dr_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006), },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011), },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, dr_devices);
|
||||
|
|
@ -311,8 +275,8 @@ static struct hid_driver dr_driver = {
|
|||
.name = "dragonrise",
|
||||
.id_table = dr_devices,
|
||||
.report_fixup = dr_report_fixup,
|
||||
.probe = dr_probe,
|
||||
.input_mapping = dr_input_mapping,
|
||||
.input_configured = dr_input_configured,
|
||||
};
|
||||
module_hid_driver(dr_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,29 +43,23 @@ static int emsff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int emsff_init(struct hid_device *hid)
|
||||
static int ems_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct emsff_device *emsff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct list_head *report_list =
|
||||
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output reports found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
report = list_first_entry(report_list, struct hid_report, list);
|
||||
if (report->maxfield < 1) {
|
||||
hid_err(hid, "no fields in the report\n");
|
||||
return -ENODEV;
|
||||
|
|
@ -80,6 +74,7 @@ static int emsff_init(struct hid_device *hid)
|
|||
if (!emsff)
|
||||
return -ENOMEM;
|
||||
|
||||
emsff->report = report;
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, emsff, emsff_play);
|
||||
|
|
@ -88,7 +83,6 @@ static int emsff_init(struct hid_device *hid)
|
|||
return error;
|
||||
}
|
||||
|
||||
emsff->report = report;
|
||||
emsff->report->field[0]->value[0] = 0x01;
|
||||
emsff->report->field[0]->value[1] = 0x00;
|
||||
emsff->report->field[0]->value[2] = 0x00;
|
||||
|
|
@ -103,34 +97,6 @@ static int emsff_init(struct hid_device *hid)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ems_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = emsff_init(hdev);
|
||||
if (ret) {
|
||||
dev_err(&hdev->dev, "force feedback init failed\n");
|
||||
hid_hw_stop(hdev);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id ems_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II) },
|
||||
{ }
|
||||
|
|
@ -140,7 +106,7 @@ MODULE_DEVICE_TABLE(hid, ems_devices);
|
|||
static struct hid_driver ems_driver = {
|
||||
.name = "hkems",
|
||||
.id_table = ems_devices,
|
||||
.probe = ems_probe,
|
||||
.input_configured = ems_input_configured,
|
||||
};
|
||||
module_hid_driver(ems_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,8 @@ struct ft260_device {
|
|||
struct mutex lock;
|
||||
u8 write_buf[FT260_REPORT_MAX_LENGTH];
|
||||
unsigned long need_wakeup_at;
|
||||
/* Protects read_buf, read_idx and read_len against ft260_raw_event() */
|
||||
spinlock_t read_lock;
|
||||
u8 *read_buf;
|
||||
u16 read_idx;
|
||||
u16 read_len;
|
||||
|
|
@ -501,16 +503,25 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
|
|||
int timeout, ret = 0;
|
||||
struct ft260_i2c_read_request_report rep;
|
||||
struct hid_device *hdev = dev->hdev;
|
||||
unsigned long irqflags;
|
||||
u8 bus_busy = 0;
|
||||
/*
|
||||
* STOP terminates the last chunk; clear means hold the bus so a
|
||||
* follow-up call continues the same I2C transaction.
|
||||
*/
|
||||
bool want_stop = !!(flag & FT260_FLAG_STOP);
|
||||
|
||||
if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
|
||||
flag = FT260_FLAG_START_REPEATED;
|
||||
else
|
||||
else if (flag & FT260_FLAG_START)
|
||||
flag = FT260_FLAG_START;
|
||||
else
|
||||
flag = 0; /* no fresh START - continue current transaction */
|
||||
do {
|
||||
if (len <= rd_data_max) {
|
||||
rd_len = len;
|
||||
flag |= FT260_FLAG_STOP;
|
||||
if (want_stop)
|
||||
flag |= FT260_FLAG_STOP;
|
||||
} else {
|
||||
rd_len = rd_data_max;
|
||||
}
|
||||
|
|
@ -526,9 +537,11 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
|
|||
|
||||
reinit_completion(&dev->wait);
|
||||
|
||||
spin_lock_irqsave(&dev->read_lock, irqflags);
|
||||
dev->read_idx = 0;
|
||||
dev->read_buf = data;
|
||||
dev->read_len = rd_len;
|
||||
spin_unlock_irqrestore(&dev->read_lock, irqflags);
|
||||
|
||||
ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
|
||||
if (ret < 0) {
|
||||
|
|
@ -543,7 +556,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
|
|||
goto ft260_i2c_read_exit;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dev->read_lock, irqflags);
|
||||
dev->read_buf = NULL;
|
||||
spin_unlock_irqrestore(&dev->read_lock, irqflags);
|
||||
|
||||
if (flag & FT260_FLAG_STOP)
|
||||
bus_busy = FT260_I2C_STATUS_BUS_BUSY;
|
||||
|
|
@ -562,7 +577,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
|
|||
} while (len > 0);
|
||||
|
||||
ft260_i2c_read_exit:
|
||||
spin_lock_irqsave(&dev->read_lock, irqflags);
|
||||
dev->read_buf = NULL;
|
||||
spin_unlock_irqrestore(&dev->read_lock, irqflags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -708,14 +725,41 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
|
|||
break;
|
||||
case I2C_SMBUS_BLOCK_DATA:
|
||||
if (read_write == I2C_SMBUS_READ) {
|
||||
u8 count = 0;
|
||||
|
||||
/*
|
||||
* SMBus 2.0 section 6.5.7 block read in one I2C
|
||||
* transaction:
|
||||
*
|
||||
* S Addr+Wr A Reg A Sr Addr+Rd A Count A Data... P
|
||||
*
|
||||
* The count is read separately and validated
|
||||
* before sizing the data read so a misbehaving
|
||||
* slave cannot drive a write past data->block[].
|
||||
*/
|
||||
ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
|
||||
FT260_FLAG_START);
|
||||
if (ret)
|
||||
goto smbus_exit;
|
||||
|
||||
ret = ft260_i2c_read(dev, addr, data->block,
|
||||
data->block[0] + 1,
|
||||
FT260_FLAG_START_STOP_REPEATED);
|
||||
ret = ft260_i2c_read(dev, addr, &count, 1,
|
||||
FT260_FLAG_START_REPEATED);
|
||||
if (ret)
|
||||
goto smbus_exit;
|
||||
|
||||
if (count == 0 || count > I2C_SMBUS_BLOCK_MAX) {
|
||||
hid_warn(hdev,
|
||||
"smbus block read: invalid count %u from slave 0x%02x\n",
|
||||
count, addr);
|
||||
ft260_i2c_reset(hdev);
|
||||
ret = -EPROTO;
|
||||
goto smbus_exit;
|
||||
}
|
||||
|
||||
data->block[0] = count;
|
||||
|
||||
ret = ft260_i2c_read(dev, addr, data->block + 1,
|
||||
count, FT260_FLAG_STOP);
|
||||
} else {
|
||||
ret = ft260_smbus_write(dev, addr, cmd, data->block,
|
||||
data->block[0] + 1,
|
||||
|
|
@ -1018,6 +1062,7 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
"FT260 usb-i2c bridge");
|
||||
|
||||
mutex_init(&dev->lock);
|
||||
spin_lock_init(&dev->read_lock);
|
||||
init_completion(&dev->wait);
|
||||
|
||||
ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
|
||||
|
|
@ -1067,6 +1112,7 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|||
{
|
||||
struct ft260_device *dev = hid_get_drvdata(hdev);
|
||||
struct ft260_i2c_input_report *xfer = (void *)data;
|
||||
unsigned long irqflags;
|
||||
|
||||
if (size < offsetof(struct ft260_i2c_input_report, data)) {
|
||||
hid_err(hdev, "short report %d\n", size);
|
||||
|
|
@ -1075,6 +1121,8 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|||
|
||||
if (xfer->report >= FT260_I2C_REPORT_MIN &&
|
||||
xfer->report <= FT260_I2C_REPORT_MAX) {
|
||||
bool complete_read;
|
||||
|
||||
ft260_dbg("i2c resp: rep %#02x len %d size %d\n",
|
||||
xfer->report, xfer->length, size);
|
||||
|
||||
|
|
@ -1085,8 +1133,15 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hold read_lock so a timed-out ft260_i2c_read() cannot
|
||||
* clear read_buf between the NULL check and the memcpy.
|
||||
*/
|
||||
spin_lock_irqsave(&dev->read_lock, irqflags);
|
||||
|
||||
if ((dev->read_buf == NULL) ||
|
||||
(xfer->length > dev->read_len - dev->read_idx)) {
|
||||
spin_unlock_irqrestore(&dev->read_lock, irqflags);
|
||||
hid_err(hdev, "unexpected report %#02x, length %d\n",
|
||||
xfer->report, xfer->length);
|
||||
return -1;
|
||||
|
|
@ -1095,8 +1150,11 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|||
memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
|
||||
xfer->length);
|
||||
dev->read_idx += xfer->length;
|
||||
complete_read = dev->read_idx == dev->read_len;
|
||||
|
||||
if (dev->read_idx == dev->read_len)
|
||||
spin_unlock_irqrestore(&dev->read_lock, irqflags);
|
||||
|
||||
if (complete_read)
|
||||
complete(&dev->wait);
|
||||
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -60,32 +60,23 @@ static int hid_gaff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int gaff_init(struct hid_device *hid)
|
||||
static int gaff_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct gaff_device *gaff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct list_head *report_list =
|
||||
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct list_head *report_ptr = report_list;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output reports found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
report_ptr = report_ptr->next;
|
||||
|
||||
report = list_entry(report_ptr, struct hid_report, list);
|
||||
if (report->maxfield < 1) {
|
||||
hid_err(hid, "no fields in the report\n");
|
||||
return -ENODEV;
|
||||
|
|
@ -100,6 +91,7 @@ static int gaff_init(struct hid_device *hid)
|
|||
if (!gaff)
|
||||
return -ENOMEM;
|
||||
|
||||
gaff->report = report;
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, gaff, hid_gaff_play);
|
||||
|
|
@ -108,7 +100,6 @@ static int gaff_init(struct hid_device *hid)
|
|||
return error;
|
||||
}
|
||||
|
||||
gaff->report = report;
|
||||
gaff->report->field[0]->value[0] = 0x51;
|
||||
gaff->report->field[0]->value[1] = 0x00;
|
||||
gaff->report->field[0]->value[2] = 0x00;
|
||||
|
|
@ -125,37 +116,13 @@ static int gaff_init(struct hid_device *hid)
|
|||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int gaff_init(struct hid_device *hdev)
|
||||
static inline int gaff_input_configured(struct hid_device *hdev,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ga_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dev_dbg(&hdev->dev, "Greenasia HID hardware probe...");
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
gaff_init(hdev);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id ga_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0012), },
|
||||
{ }
|
||||
|
|
@ -165,7 +132,7 @@ MODULE_DEVICE_TABLE(hid, ga_devices);
|
|||
static struct hid_driver ga_driver = {
|
||||
.name = "greenasia",
|
||||
.id_table = ga_devices,
|
||||
.probe = ga_probe,
|
||||
.input_configured = gaff_input_configured,
|
||||
};
|
||||
module_hid_driver(ga_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -722,11 +722,8 @@ static int goodix_spi_probe(struct spi_device *spi)
|
|||
error = devm_request_threaded_irq(&ts->spi->dev, ts->spi->irq,
|
||||
NULL, goodix_hid_irq, IRQF_ONESHOT,
|
||||
"goodix_spi_hid", ts);
|
||||
if (error) {
|
||||
dev_err(ts->dev, "could not register interrupt, irq = %d, %d",
|
||||
ts->spi->irq, error);
|
||||
if (error)
|
||||
goto err_destroy_hid;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@
|
|||
struct stadiaff_device {
|
||||
struct hid_device *hid;
|
||||
struct hid_report *report;
|
||||
spinlock_t lock;
|
||||
bool removed;
|
||||
uint16_t strong_magnitude;
|
||||
uint16_t weak_magnitude;
|
||||
u32 magnitudes;
|
||||
struct work_struct work;
|
||||
};
|
||||
|
||||
|
|
@ -29,12 +26,10 @@ static void stadiaff_work(struct work_struct *work)
|
|||
struct stadiaff_device *stadiaff =
|
||||
container_of(work, struct stadiaff_device, work);
|
||||
struct hid_field *rumble_field = stadiaff->report->field[0];
|
||||
unsigned long flags;
|
||||
u32 mags = READ_ONCE(stadiaff->magnitudes);
|
||||
|
||||
spin_lock_irqsave(&stadiaff->lock, flags);
|
||||
rumble_field->value[0] = stadiaff->strong_magnitude;
|
||||
rumble_field->value[1] = stadiaff->weak_magnitude;
|
||||
spin_unlock_irqrestore(&stadiaff->lock, flags);
|
||||
rumble_field->value[0] = mags & 0xffff;
|
||||
rumble_field->value[1] = (mags >> 16) & 0xffff;
|
||||
|
||||
hid_hw_request(stadiaff->hid, stadiaff->report, HID_REQ_SET_REPORT);
|
||||
}
|
||||
|
|
@ -44,33 +39,50 @@ static int stadiaff_play(struct input_dev *dev, void *data,
|
|||
{
|
||||
struct hid_device *hid = input_get_drvdata(dev);
|
||||
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
|
||||
unsigned long flags;
|
||||
u32 mags = (u32)effect->u.rumble.strong_magnitude |
|
||||
((u32)effect->u.rumble.weak_magnitude << 16);
|
||||
|
||||
spin_lock_irqsave(&stadiaff->lock, flags);
|
||||
if (!stadiaff->removed) {
|
||||
stadiaff->strong_magnitude = effect->u.rumble.strong_magnitude;
|
||||
stadiaff->weak_magnitude = effect->u.rumble.weak_magnitude;
|
||||
schedule_work(&stadiaff->work);
|
||||
}
|
||||
spin_unlock_irqrestore(&stadiaff->lock, flags);
|
||||
WRITE_ONCE(stadiaff->magnitudes, mags);
|
||||
schedule_work(&stadiaff->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stadiaff_init(struct hid_device *hid)
|
||||
static int stadia_input_open(struct input_dev *dev)
|
||||
{
|
||||
struct hid_device *hid = input_get_drvdata(dev);
|
||||
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
|
||||
int error;
|
||||
|
||||
error = hid_hw_open(hid);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
enable_work(&stadiaff->work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stadia_input_close(struct input_dev *dev)
|
||||
{
|
||||
struct hid_device *hid = input_get_drvdata(dev);
|
||||
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
|
||||
|
||||
WRITE_ONCE(stadiaff->magnitudes, 0);
|
||||
stadiaff_work(&stadiaff->work);
|
||||
disable_work_sync(&stadiaff->work);
|
||||
|
||||
hid_hw_close(hid);
|
||||
}
|
||||
|
||||
static int stadia_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct stadiaff_device *stadiaff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
report = hid_validate_values(hid, HID_OUTPUT_REPORT,
|
||||
STADIA_FF_REPORT_ID, 0, 2);
|
||||
|
|
@ -90,56 +102,19 @@ static int stadiaff_init(struct hid_device *hid)
|
|||
if (error)
|
||||
return error;
|
||||
|
||||
stadiaff->removed = false;
|
||||
stadiaff->hid = hid;
|
||||
stadiaff->report = report;
|
||||
INIT_WORK(&stadiaff->work, stadiaff_work);
|
||||
spin_lock_init(&stadiaff->lock);
|
||||
disable_work_sync(&stadiaff->work);
|
||||
|
||||
dev->open = stadia_input_open;
|
||||
dev->close = stadia_input_close;
|
||||
|
||||
hid_info(hid, "Force Feedback for Google Stadia controller\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = stadiaff_init(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "force feedback init failed\n");
|
||||
hid_hw_stop(hdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stadia_remove(struct hid_device *hid)
|
||||
{
|
||||
struct stadiaff_device *stadiaff = hid_get_drvdata(hid);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&stadiaff->lock, flags);
|
||||
stadiaff->removed = true;
|
||||
spin_unlock_irqrestore(&stadiaff->lock, flags);
|
||||
|
||||
cancel_work_sync(&stadiaff->work);
|
||||
hid_hw_stop(hid);
|
||||
}
|
||||
|
||||
static const struct hid_device_id stadia_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) },
|
||||
|
|
@ -150,8 +125,7 @@ MODULE_DEVICE_TABLE(hid, stadia_devices);
|
|||
static struct hid_driver stadia_driver = {
|
||||
.name = "stadia",
|
||||
.id_table = stadia_devices,
|
||||
.probe = stadia_probe,
|
||||
.remove = stadia_remove,
|
||||
.input_configured = stadia_input_configured,
|
||||
};
|
||||
module_hid_driver(stadia_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -82,16 +82,24 @@ int hid_haptic_input_configured(struct hid_device *hdev,
|
|||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (hi->application == HID_DG_TOUCHPAD) {
|
||||
if (haptic->auto_trigger_report &&
|
||||
haptic->manual_trigger_report) {
|
||||
__set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit);
|
||||
return 1;
|
||||
}
|
||||
if (hi->application != HID_DG_TOUCHPAD)
|
||||
return -1;
|
||||
|
||||
if (!haptic->auto_trigger_report || !haptic->manual_trigger_report)
|
||||
return 0;
|
||||
|
||||
__set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit);
|
||||
|
||||
error = hid_haptic_init(hdev, haptic, hi->input);
|
||||
if (error) {
|
||||
dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
|
||||
hdev->name);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
|
||||
|
||||
|
|
@ -187,7 +195,7 @@ static void fill_effect_buf(struct hid_haptic_device *haptic,
|
|||
value = waveform_ordinal;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
field->value[j] = value;
|
||||
|
|
@ -401,11 +409,9 @@ static void hid_haptic_destroy(struct ff_device *ff)
|
|||
}
|
||||
|
||||
int hid_haptic_init(struct hid_device *hdev,
|
||||
struct hid_haptic_device **haptic_ptr)
|
||||
struct hid_haptic_device *haptic,
|
||||
struct input_dev *dev)
|
||||
{
|
||||
struct hid_haptic_device *haptic = *haptic_ptr;
|
||||
struct input_dev *dev = NULL;
|
||||
struct hid_input *hidinput;
|
||||
struct ff_device *ff;
|
||||
int ret = 0, r;
|
||||
struct ff_haptic_effect stop_effect = {
|
||||
|
|
@ -447,19 +453,6 @@ int hid_haptic_init(struct hid_device *hdev,
|
|||
for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
|
||||
parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
|
||||
|
||||
list_for_each_entry(hidinput, &hdev->inputs, list) {
|
||||
if (hidinput->application == HID_DG_TOUCHPAD) {
|
||||
dev = hidinput->input;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev) {
|
||||
dev_err(&hdev->dev, "Failed to find the input device\n");
|
||||
ret = -ENODEV;
|
||||
goto duration_map;
|
||||
}
|
||||
|
||||
haptic->input_dev = dev;
|
||||
haptic->manual_trigger_report_len =
|
||||
hid_report_len(haptic->manual_trigger_report);
|
||||
|
|
@ -535,10 +528,6 @@ int hid_haptic_init(struct hid_device *hdev,
|
|||
|
||||
input_free:
|
||||
input_ff_destroy(dev);
|
||||
/* Do not let double free happen, input_ff_destroy will call
|
||||
* hid_haptic_destroy.
|
||||
*/
|
||||
*haptic_ptr = NULL;
|
||||
/* Restore dev flush and event */
|
||||
dev->flush = flush;
|
||||
dev->event = event;
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ int hid_haptic_input_mapping(struct hid_device *hdev,
|
|||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi);
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device *haptic,
|
||||
struct input_dev *dev);
|
||||
void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
|
|
@ -107,7 +108,8 @@ static inline
|
|||
void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
|
||||
{}
|
||||
static inline
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device *haptic,
|
||||
struct input_dev *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,30 +120,24 @@ static int holtekff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int holtekff_init(struct hid_device *hid)
|
||||
static int holtek_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct holtekff_device *holtekff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct list_head *report_list =
|
||||
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output report found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
report = list_entry(report_list->next, struct hid_report, list);
|
||||
|
||||
if (report->maxfield < 1 || report->field[0]->report_count != 7) {
|
||||
hid_err(hid, "unexpected output report layout\n");
|
||||
return -ENODEV;
|
||||
|
|
@ -172,35 +166,13 @@ static int holtekff_init(struct hid_device *hid)
|
|||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int holtekff_init(struct hid_device *hid)
|
||||
static inline int holtek_input_configured(struct hid_device *hid,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
holtekff_init(hdev);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id holtek_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
|
||||
{ }
|
||||
|
|
@ -210,7 +182,7 @@ MODULE_DEVICE_TABLE(hid, holtek_devices);
|
|||
static struct hid_driver holtek_driver = {
|
||||
.name = "holtek",
|
||||
.id_table = holtek_devices,
|
||||
.probe = holtek_probe,
|
||||
.input_configured = holtek_input_configured,
|
||||
};
|
||||
module_hid_driver(holtek_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -44,11 +44,12 @@ static const __u8 huawei_cd30_kbd_rdesc_fixed[] = {
|
|||
static const __u8 *huawei_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
struct usb_interface *intf = hid_is_usb(hdev) ?
|
||||
to_usb_interface(hdev->dev.parent) : NULL;
|
||||
|
||||
switch (hdev->product) {
|
||||
case USB_DEVICE_ID_HUAWEI_CD30KBD:
|
||||
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
|
||||
if (!intf || intf->cur_altsetting->desc.bInterfaceNumber == 1) {
|
||||
if (*rsize != sizeof(huawei_cd30_kbd_rdesc_fixed) ||
|
||||
memcmp(huawei_cd30_kbd_rdesc_fixed, rdesc,
|
||||
sizeof(huawei_cd30_kbd_rdesc_fixed)) != 0) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
#include <linux/hiddev.h>
|
||||
#include <linux/hyperv.h>
|
||||
|
||||
#if IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST)
|
||||
#include <kunit/test.h>
|
||||
#endif
|
||||
|
||||
struct hv_input_dev_info {
|
||||
unsigned int size;
|
||||
|
|
@ -171,18 +174,32 @@ static void mousevsc_free_device(struct mousevsc_dev *device)
|
|||
}
|
||||
|
||||
static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
|
||||
struct synthhid_device_info *device_info)
|
||||
struct synthhid_device_info *device_info,
|
||||
u32 device_info_size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct hid_descriptor *desc;
|
||||
struct mousevsc_prt_msg ack;
|
||||
size_t desc_offset;
|
||||
size_t desc_size;
|
||||
|
||||
input_device->dev_info_status = -ENOMEM;
|
||||
|
||||
if (device_info_size < sizeof(*device_info)) {
|
||||
input_device->dev_info_status = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
input_device->hid_dev_info = device_info->hid_dev_info;
|
||||
desc = &device_info->hid_descriptor;
|
||||
desc_offset = offsetof(struct synthhid_device_info, hid_descriptor);
|
||||
desc_size = device_info_size - desc_offset;
|
||||
if (desc->bLength == 0)
|
||||
goto cleanup;
|
||||
if (desc->bLength < sizeof(*desc) || desc->bLength > desc_size) {
|
||||
input_device->dev_info_status = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* The pointer is not NULL when we resume from hibernation */
|
||||
kfree(input_device->hid_desc);
|
||||
|
|
@ -197,6 +214,10 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
|
|||
input_device->dev_info_status = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
if (input_device->report_desc_size > desc_size - desc->bLength) {
|
||||
input_device->dev_info_status = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* The pointer is not NULL when we resume from hibernation */
|
||||
kfree(input_device->report_desc);
|
||||
|
|
@ -222,13 +243,18 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
|
|||
ack.ack.header.size = 1;
|
||||
ack.ack.reserved = 0;
|
||||
|
||||
ret = vmbus_sendpacket(input_device->device->channel,
|
||||
&ack,
|
||||
sizeof(struct pipe_prt_msg) +
|
||||
sizeof(struct synthhid_device_info_ack),
|
||||
(unsigned long)&ack,
|
||||
VM_PKT_DATA_INBAND,
|
||||
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
|
||||
if (IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST) &&
|
||||
!input_device->device) {
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = vmbus_sendpacket(input_device->device->channel,
|
||||
&ack,
|
||||
sizeof(struct pipe_prt_msg) +
|
||||
sizeof(struct synthhid_device_info_ack),
|
||||
(unsigned long)&ack,
|
||||
VM_PKT_DATA_INBAND,
|
||||
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
input_device->dev_info_status = 0;
|
||||
|
|
@ -273,14 +299,17 @@ static void mousevsc_on_receive(struct hv_device *device,
|
|||
break;
|
||||
|
||||
case SYNTH_HID_INITIAL_DEVICE_INFO:
|
||||
WARN_ON(pipe_msg->size < sizeof(struct hv_input_dev_info));
|
||||
if (WARN_ON_ONCE(pipe_msg->size <
|
||||
sizeof(struct synthhid_device_info)))
|
||||
break;
|
||||
|
||||
/*
|
||||
* Parse out the device info into device attr,
|
||||
* hid desc and report desc
|
||||
*/
|
||||
mousevsc_on_receive_device_info(input_dev,
|
||||
(struct synthhid_device_info *)pipe_msg->data);
|
||||
(struct synthhid_device_info *)pipe_msg->data,
|
||||
pipe_msg->size);
|
||||
break;
|
||||
case SYNTH_HID_INPUT_REPORT:
|
||||
input_report =
|
||||
|
|
@ -614,5 +643,100 @@ static void __exit mousevsc_exit(void)
|
|||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Microsoft Hyper-V Synthetic HID Driver");
|
||||
|
||||
#if IS_ENABLED(CONFIG_HID_HYPERV_MOUSE_KUNIT_TEST)
|
||||
static struct mousevsc_dev *mousevsc_kunit_alloc_dev(struct kunit *test)
|
||||
{
|
||||
struct mousevsc_dev *input_dev;
|
||||
|
||||
input_dev = kunit_kzalloc(test, sizeof(*input_dev), GFP_KERNEL);
|
||||
if (!input_dev)
|
||||
return NULL;
|
||||
|
||||
init_completion(&input_dev->wait_event);
|
||||
|
||||
return input_dev;
|
||||
}
|
||||
|
||||
static void mousevsc_device_info_zero_blength(struct kunit *test)
|
||||
{
|
||||
struct synthhid_device_info *info;
|
||||
struct mousevsc_dev *input_dev;
|
||||
|
||||
input_dev = mousevsc_kunit_alloc_dev(test);
|
||||
KUNIT_ASSERT_NOT_NULL(test, input_dev);
|
||||
info = kunit_kzalloc(test, sizeof(*info), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, info);
|
||||
|
||||
info->hid_descriptor.bLength = 0;
|
||||
|
||||
mousevsc_on_receive_device_info(input_dev, info, sizeof(*info));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, -ENOMEM);
|
||||
}
|
||||
|
||||
static void mousevsc_device_info_valid_descriptor(struct kunit *test)
|
||||
{
|
||||
struct synthhid_device_info *info;
|
||||
struct mousevsc_dev *input_dev;
|
||||
u8 *report;
|
||||
|
||||
input_dev = mousevsc_kunit_alloc_dev(test);
|
||||
KUNIT_ASSERT_NOT_NULL(test, input_dev);
|
||||
info = kunit_kzalloc(test, sizeof(*info) + 4, GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, info);
|
||||
|
||||
info->hid_descriptor.bLength = sizeof(struct hid_descriptor);
|
||||
info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(4);
|
||||
report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength;
|
||||
memset(report, 0x42, 4);
|
||||
|
||||
mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 4);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, 0);
|
||||
KUNIT_EXPECT_EQ(test, input_dev->report_desc_size, 4);
|
||||
KUNIT_EXPECT_MEMEQ(test, input_dev->report_desc, report, 4);
|
||||
|
||||
kfree(input_dev->hid_desc);
|
||||
kfree(input_dev->report_desc);
|
||||
}
|
||||
|
||||
static void mousevsc_device_info_report_desc_oob(struct kunit *test)
|
||||
{
|
||||
struct synthhid_device_info *info;
|
||||
struct mousevsc_dev *input_dev;
|
||||
u8 *report;
|
||||
|
||||
input_dev = mousevsc_kunit_alloc_dev(test);
|
||||
KUNIT_ASSERT_NOT_NULL(test, input_dev);
|
||||
info = kunit_kzalloc(test, sizeof(*info) + 8, GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, info);
|
||||
|
||||
info->hid_descriptor.bLength = sizeof(struct hid_descriptor);
|
||||
info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(64);
|
||||
report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength;
|
||||
memset(report, 0x42, 8);
|
||||
|
||||
mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 8);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, input_dev->dev_info_status, -EINVAL);
|
||||
|
||||
kfree(input_dev->hid_desc);
|
||||
}
|
||||
|
||||
static struct kunit_case mousevsc_test_cases[] = {
|
||||
KUNIT_CASE(mousevsc_device_info_zero_blength),
|
||||
KUNIT_CASE(mousevsc_device_info_valid_descriptor),
|
||||
KUNIT_CASE(mousevsc_device_info_report_desc_oob),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct kunit_suite mousevsc_test_suite = {
|
||||
.name = "hid_hyperv_mouse",
|
||||
.test_cases = mousevsc_test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(mousevsc_test_suite);
|
||||
#endif
|
||||
|
||||
module_init(mousevsc_init);
|
||||
module_exit(mousevsc_exit);
|
||||
|
|
|
|||
117
drivers/hid/hid-hyperx.c
Normal file
117
drivers/hid/hid-hyperx.c
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HID driver for the HyperX QuadCast 2 microphone
|
||||
*
|
||||
* The tap-to-mute button is handled entirely in the device firmware: it gates
|
||||
* the audio internally and does not send the Telephony "Phone Mute" usage its
|
||||
* own report descriptor advertises. The resulting mute state is only reported
|
||||
* through a vendor-defined collection, which hid-input cannot map, so the
|
||||
* button is invisible to userspace.
|
||||
*
|
||||
* Copyright (c) 2026 Benjamin Blume <benjaminblume@posteo.de>
|
||||
*/
|
||||
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#define HYPERX_QC2_REPORT_ID 0x77
|
||||
#define HYPERX_QC2_EVENT_MUTE 0x06
|
||||
#define HYPERX_QC2_MUTE_LEN 3
|
||||
|
||||
struct hyperx_drvdata {
|
||||
struct input_dev *input;
|
||||
bool muted;
|
||||
bool have_state;
|
||||
};
|
||||
|
||||
static int hyperx_input_configured(struct hid_device *hdev,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
struct hyperx_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
|
||||
/*
|
||||
* The device exposes several application collections. Prefer the
|
||||
* telephony one, which already advertises KEY_MICMUTE, and fall back
|
||||
* to the first collection otherwise.
|
||||
*/
|
||||
if (!drvdata->input || test_bit(KEY_MICMUTE, hi->input->keybit)) {
|
||||
drvdata->input = hi->input;
|
||||
input_set_capability(drvdata->input, EV_KEY, KEY_MICMUTE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hyperx_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
u8 *data, int size)
|
||||
{
|
||||
struct hyperx_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
bool muted;
|
||||
|
||||
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !drvdata->input)
|
||||
return 0;
|
||||
|
||||
if (size < HYPERX_QC2_MUTE_LEN || data[0] != HYPERX_QC2_REPORT_ID ||
|
||||
data[1] != HYPERX_QC2_EVENT_MUTE)
|
||||
return 0;
|
||||
|
||||
muted = data[2];
|
||||
if (drvdata->have_state && muted == drvdata->muted)
|
||||
return 0;
|
||||
|
||||
drvdata->muted = muted;
|
||||
drvdata->have_state = true;
|
||||
|
||||
/*
|
||||
* The device reports the resulting absolute state, while KEY_MICMUTE is
|
||||
* a momentary key that userspace acts on as a toggle. Emit one
|
||||
* keypress per state change.
|
||||
*/
|
||||
input_report_key(drvdata->input, KEY_MICMUTE, 1);
|
||||
input_sync(drvdata->input);
|
||||
input_report_key(drvdata->input, KEY_MICMUTE, 0);
|
||||
input_sync(drvdata->input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hyperx_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
struct hyperx_drvdata *drvdata;
|
||||
int ret;
|
||||
|
||||
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
|
||||
hid_set_drvdata(hdev, drvdata);
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
}
|
||||
|
||||
static const struct hid_device_id hyperx_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HP,
|
||||
USB_PRODUCT_ID_HP_HYPERX_QUADCAST_2) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, hyperx_devices);
|
||||
|
||||
static struct hid_driver hyperx_driver = {
|
||||
.name = "hyperx",
|
||||
.id_table = hyperx_devices,
|
||||
.probe = hyperx_probe,
|
||||
.input_configured = hyperx_input_configured,
|
||||
.raw_event = hyperx_raw_event,
|
||||
};
|
||||
module_hid_driver(hyperx_driver);
|
||||
|
||||
MODULE_DESCRIPTION("HID driver for the HyperX QuadCast 2 microphone");
|
||||
MODULE_AUTHOR("Benjamin Blume <benjaminblume@posteo.de>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -220,6 +220,7 @@
|
|||
#define USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD 0x183d
|
||||
#define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD 0x184a
|
||||
#define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD 0x8585
|
||||
#define USB_DEVICE_ID_ASUSTEK_I2C_ZENBOOK_KEYBOARD 0x4b42
|
||||
#define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD 0x0101
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
|
||||
|
|
@ -263,6 +264,9 @@
|
|||
#define USB_VENDOR_ID_BAANTO 0x2453
|
||||
#define USB_DEVICE_ID_BAANTO_MT_190W2 0x0100
|
||||
|
||||
#define USB_VENDOR_ID_BEITONG 0x20dd
|
||||
#define USB_DEVICE_ID_BEITONG_KP20D 0x5159
|
||||
|
||||
#define USB_VENDOR_ID_BELKIN 0x050d
|
||||
#define USB_DEVICE_ID_FLIP_KVM 0x3201
|
||||
|
||||
|
|
@ -688,6 +692,7 @@
|
|||
#define USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD 0x00f6
|
||||
|
||||
#define USB_VENDOR_ID_HP 0x03f0
|
||||
#define USB_PRODUCT_ID_HP_HYPERX_QUADCAST_2 0x07b4
|
||||
#define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
|
||||
#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
|
||||
#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A 0x0b4a
|
||||
|
|
@ -775,6 +780,7 @@
|
|||
#define USB_DEVICE_ID_JESS_YUREX 0x1010
|
||||
#define USB_DEVICE_ID_ASUS_MD_5112 0x5112
|
||||
#define USB_DEVICE_ID_REDRAGON_ASURA 0x760b
|
||||
#define USB_DEVICE_ID_AULA_MINI_60_HE_PRO 0xfefe
|
||||
|
||||
#define USB_VENDOR_ID_JESS2 0x0f30
|
||||
#define USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD 0x0111
|
||||
|
|
@ -967,6 +973,7 @@
|
|||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc543
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xc547
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4 0xc54d
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_5 0xc545
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a
|
||||
#define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER 0xc548
|
||||
#define USB_DEVICE_ID_SPACETRAVELLER 0xc623
|
||||
|
|
@ -1071,7 +1078,12 @@
|
|||
#define USB_DEVICE_ID_MOZA_R16_R21_2 0x0010
|
||||
|
||||
#define USB_VENDOR_ID_MSI 0x1770
|
||||
#define USB_VENDOR_ID_MSI_2 0x0db0
|
||||
#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
|
||||
#define USB_DEVICE_ID_MSI_CLAW_XINPUT 0x1901
|
||||
#define USB_DEVICE_ID_MSI_CLAW_DINPUT 0x1902
|
||||
#define USB_DEVICE_ID_MSI_CLAW_DESKTOP 0x1903
|
||||
#define USB_DEVICE_ID_MSI_CLAW_BIOS 0x1904
|
||||
|
||||
#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
|
||||
#define USB_DEVICE_ID_N_S_HARMONY 0xc359
|
||||
|
|
@ -1374,11 +1386,31 @@
|
|||
#define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102
|
||||
#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142
|
||||
#define USB_DEVICE_ID_STEAM_DECK 0x1205
|
||||
#define USB_DEVICE_ID_STEAM_CONTROLLER_IBEX 0x1302
|
||||
#define USB_DEVICE_ID_STEAM_CONTROLLER_IBEX_BLE 0x1303
|
||||
#define USB_DEVICE_ID_STEAM_CONTROLLER_PROTEUS 0x1304
|
||||
#define USB_DEVICE_ID_STEAM_CONTROLLER_NEREID 0x1305
|
||||
|
||||
#define USB_VENDOR_ID_STEELSERIES 0x1038
|
||||
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X 0x2206
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2 0x22a4
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO 0x223a
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW 0x227a
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026 0x22a1
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026 0x22a7
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026 0x22a5
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026 0x22a9
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2 0x227e
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2 0x2258
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2 0x229e
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3 0x22ad
|
||||
#define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122
|
||||
#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
|
||||
|
||||
#define USB_VENDOR_ID_SUN 0x0430
|
||||
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
|
||||
|
|
|
|||
|
|
@ -375,6 +375,9 @@ static const struct hid_device_id hid_battery_quirks[] = {
|
|||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
|
||||
USB_DEVICE_ID_APPLE_MAGICTRACKPAD),
|
||||
HID_BATTERY_QUIRK_IGNORE },
|
||||
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
|
||||
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC),
|
||||
HID_BATTERY_QUIRK_AVOID_QUERY },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM,
|
||||
USB_DEVICE_ID_ELECOM_BM084),
|
||||
HID_BATTERY_QUIRK_IGNORE },
|
||||
|
|
@ -432,17 +435,25 @@ static int hidinput_scale_battery_capacity(struct hid_battery *bat,
|
|||
static int hidinput_query_battery_capacity(struct hid_battery *bat)
|
||||
{
|
||||
int ret;
|
||||
/*
|
||||
* The capacity field may not be the first field in the report: some
|
||||
* devices (e.g. the Apple Magic Trackpad 2 over Bluetooth) precede it
|
||||
* with status flags. Read it from its actual byte offset in the report
|
||||
* (report_offset is in bits; the leading byte is the report id).
|
||||
*/
|
||||
int offset = 1 + bat->report_offset / 8;
|
||||
int len = offset + 1;
|
||||
|
||||
u8 *buf __free(kfree) = kmalloc(4, GFP_KERNEL);
|
||||
u8 *buf __free(kfree) = kmalloc(max(len, 4), GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, 4,
|
||||
ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, max(len, 4),
|
||||
bat->report_type, HID_REQ_GET_REPORT);
|
||||
if (ret < 2)
|
||||
if (ret < len)
|
||||
return -ENODATA;
|
||||
|
||||
return hidinput_scale_battery_capacity(bat, buf[1]);
|
||||
return hidinput_scale_battery_capacity(bat, buf[offset]);
|
||||
}
|
||||
|
||||
static int hidinput_get_battery_property(struct power_supply *psy,
|
||||
|
|
@ -593,6 +604,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
|
|||
bat->max = max;
|
||||
bat->report_type = report_type;
|
||||
bat->report_id = field->report->id;
|
||||
bat->report_offset = field->report_offset;
|
||||
bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
bat->status = HID_BATTERY_UNKNOWN;
|
||||
|
||||
|
|
@ -2317,7 +2329,19 @@ static inline void hidinput_configure_usages(struct hid_input *hidinput,
|
|||
* Read all reports and initialize the absolute field values.
|
||||
*/
|
||||
|
||||
int hidinput_connect(struct hid_device *hid, unsigned int force)
|
||||
static bool hid_has_ff_input(struct hid_device *hdev)
|
||||
{
|
||||
struct hid_input *hidinput;
|
||||
|
||||
list_for_each_entry(hidinput, &hdev->inputs, list) {
|
||||
if (test_bit(EV_FF, hidinput->input->evbit))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int hidinput_connect(struct hid_device *hid, unsigned int connect_mask)
|
||||
{
|
||||
struct hid_driver *drv = hid->driver;
|
||||
struct hid_report *report;
|
||||
|
|
@ -2330,7 +2354,7 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
|
|||
|
||||
hid->status &= ~HID_STAT_DUP_DETECTED;
|
||||
|
||||
if (!force) {
|
||||
if (!(connect_mask & HID_CONNECT_HIDINPUT_FORCE)) {
|
||||
for (i = 0; i < hid->maxcollection; i++) {
|
||||
struct hid_collection *col = &hid->collection[i];
|
||||
if (col->type == HID_COLLECTION_APPLICATION ||
|
||||
|
|
@ -2396,6 +2420,11 @@ int hidinput_connect(struct hid_device *hid, unsigned int force)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (list_is_first(&hidinput->list, &hid->inputs) &&
|
||||
(connect_mask & HID_CONNECT_FF) && hid->ff_init &&
|
||||
!hid_has_ff_input(hid))
|
||||
hid->ff_init(hid);
|
||||
|
||||
if (input_register_device(hidinput->input))
|
||||
goto out_unwind;
|
||||
hidinput->registered = true;
|
||||
|
|
|
|||
|
|
@ -336,6 +336,8 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|||
if (entry->wdata.combine) {
|
||||
switch (entry->wdata.product_id) {
|
||||
case USB_DEVICE_ID_LOGITECH_WHEEL:
|
||||
if (size < 7)
|
||||
return 0;
|
||||
rd[5] = rd[3];
|
||||
rd[6] = 0x7F;
|
||||
return 1;
|
||||
|
|
@ -343,10 +345,14 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|||
case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
|
||||
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
|
||||
case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
|
||||
if (size < 6)
|
||||
return 0;
|
||||
rd[4] = rd[3];
|
||||
rd[5] = 0x7F;
|
||||
return 1;
|
||||
case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
|
||||
if (size < 7)
|
||||
return 0;
|
||||
rd[5] = rd[4];
|
||||
rd[6] = 0x7F;
|
||||
return 1;
|
||||
|
|
@ -366,6 +372,8 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
|
|||
}
|
||||
|
||||
/* Compute a combined axis when wheel does not supply it */
|
||||
if (size <= offset + 1)
|
||||
return 0;
|
||||
rd[offset] = (0xFF + rd[offset] - rd[offset+1]) >> 1;
|
||||
rd[offset+1] = 0x7F;
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ enum recvr_type {
|
|||
recvr_type_27mhz,
|
||||
recvr_type_bluetooth,
|
||||
recvr_type_dinovo,
|
||||
recvr_type_bolt,
|
||||
};
|
||||
|
||||
struct dj_report {
|
||||
|
|
@ -1156,6 +1157,10 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev,
|
|||
logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
|
||||
workitem.reports_supported |= STD_KEYBOARD;
|
||||
break;
|
||||
case 0x10:
|
||||
device_type = "Bolt";
|
||||
logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem);
|
||||
break;
|
||||
}
|
||||
|
||||
/* custom receiver device (eg. powerplay) */
|
||||
|
|
@ -1745,6 +1750,24 @@ static int logi_dj_hidpp_event(struct hid_device *hdev,
|
|||
|
||||
dj_dev = djrcv_dev->paired_dj_devices[device_index];
|
||||
|
||||
/*
|
||||
* Bolt receivers send explicit unpair notifications as HID++ events;
|
||||
* queue device removal when we receive one.
|
||||
*/
|
||||
if (djrcv_dev->type == recvr_type_bolt &&
|
||||
hidpp_report->report_id == REPORT_ID_HIDPP_SHORT &&
|
||||
hidpp_report->sub_id == REPORT_TYPE_NOTIF_DEVICE_UNPAIRED) {
|
||||
struct dj_workitem workitem = {
|
||||
.device_index = device_index,
|
||||
.type = WORKITEM_TYPE_UNPAIRED,
|
||||
};
|
||||
|
||||
kfifo_in(&djrcv_dev->notif_fifo, &workitem, sizeof(workitem));
|
||||
schedule_work(&djrcv_dev->work);
|
||||
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* With 27 MHz receivers, we do not get an explicit unpair event,
|
||||
* remove the old device if the user has paired a *different* device.
|
||||
|
|
@ -1884,6 +1907,9 @@ static int logi_dj_probe(struct hid_device *hdev,
|
|||
* treat these as logitech-dj interfaces then this causes input events
|
||||
* reported through this extra interface to not be reported correctly.
|
||||
* To avoid this, we treat these as generic-hid devices.
|
||||
*
|
||||
* Bolt receivers only use LOGITECH_DJ_INTERFACE_NUMBER for receiver
|
||||
* reporting. Treat all other Bolt interfaces as generic-hid devices.
|
||||
*/
|
||||
switch (id->driver_data) {
|
||||
case recvr_type_dj: no_dj_interfaces = 3; break;
|
||||
|
|
@ -1897,10 +1923,20 @@ static int logi_dj_probe(struct hid_device *hdev,
|
|||
}
|
||||
if (hid_is_usb(hdev)) {
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
if (intf && intf->altsetting->desc.bInterfaceNumber >=
|
||||
no_dj_interfaces) {
|
||||
hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
|
||||
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (intf) {
|
||||
bool generic_hid_interface;
|
||||
|
||||
if (id->driver_data == recvr_type_bolt)
|
||||
generic_hid_interface =
|
||||
intf->altsetting->desc.bInterfaceNumber !=
|
||||
LOGITECH_DJ_INTERFACE_NUMBER;
|
||||
else
|
||||
generic_hid_interface =
|
||||
intf->altsetting->desc.bInterfaceNumber >= no_dj_interfaces;
|
||||
if (generic_hid_interface) {
|
||||
hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
|
||||
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2103,10 +2139,18 @@ static const struct hid_device_id logi_dj_receivers[] = {
|
|||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3),
|
||||
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
|
||||
{ /* Logitech Bolt receiver (0xc548) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER),
|
||||
.driver_data = recvr_type_bolt},
|
||||
{ /* Logitech lightspeed receiver (0xc54d) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4),
|
||||
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
|
||||
{ /* Logitech lightspeed receiver (0xc545) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_5),
|
||||
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
|
||||
|
||||
{ /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
|
|||
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(28)
|
||||
#define HIDPP_QUIRK_WIRELESS_STATUS BIT(29)
|
||||
#define HIDPP_QUIRK_RESET_HI_RES_SCROLL BIT(30)
|
||||
#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS BIT(31)
|
||||
|
||||
/* These are just aliases for now */
|
||||
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
|
||||
|
|
@ -178,6 +179,8 @@ struct hidpp_scroll_counter {
|
|||
unsigned long long last_time;
|
||||
};
|
||||
|
||||
struct hidpp_reprog_control_mapping;
|
||||
|
||||
struct hidpp_device {
|
||||
struct hid_device *hid_dev;
|
||||
struct input_dev *input;
|
||||
|
|
@ -205,6 +208,8 @@ struct hidpp_device {
|
|||
struct hidpp_scroll_counter vertical_wheel_counter;
|
||||
|
||||
u8 wireless_feature_index;
|
||||
u8 reprog_controls_feature_index;
|
||||
const struct hidpp_reprog_control_mapping *reprog_controls;
|
||||
|
||||
int hires_wheel_multiplier;
|
||||
u8 hires_wheel_feature_index;
|
||||
|
|
@ -983,7 +988,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
|
|||
}
|
||||
|
||||
/* the device might not be connected */
|
||||
if (ret == HIDPP_ERROR_RESOURCE_ERROR ||
|
||||
if (ret == HIDPP_ERROR_CONNECT_FAIL ||
|
||||
ret == HIDPP_ERROR_RESOURCE_ERROR ||
|
||||
ret == HIDPP_ERROR_UNKNOWN_DEVICE)
|
||||
return -EIO;
|
||||
|
||||
|
|
@ -2861,18 +2867,19 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
|
|||
* ownership to FF core
|
||||
*/
|
||||
data = kmemdup(data, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
if (!data) {
|
||||
error = -ENOMEM;
|
||||
goto err_destroy_ff;
|
||||
}
|
||||
data->effect_ids = kzalloc_objs(int, num_slots);
|
||||
if (!data->effect_ids) {
|
||||
kfree(data);
|
||||
return -ENOMEM;
|
||||
error = -ENOMEM;
|
||||
goto err_free_data;
|
||||
}
|
||||
data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
|
||||
if (!data->wq) {
|
||||
kfree(data->effect_ids);
|
||||
kfree(data);
|
||||
return -ENOMEM;
|
||||
error = -ENOMEM;
|
||||
goto err_free_effect_ids;
|
||||
}
|
||||
|
||||
data->hidpp = hidpp;
|
||||
|
|
@ -2902,6 +2909,14 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
|
|||
version);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_effect_ids:
|
||||
kfree(data->effect_ids);
|
||||
err_free_data:
|
||||
kfree(data);
|
||||
err_destroy_ff:
|
||||
input_ff_destroy(dev);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
|
@ -3601,6 +3616,211 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* HID++2.0 reprogrammable controls */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
#define HIDPP_PAGE_REPROG_CONTROLS_V4 0x1b04
|
||||
|
||||
#define HIDPP_REPROG_CONTROLS_GET_COUNT 0x00
|
||||
#define HIDPP_REPROG_CONTROLS_GET_CID_INFO 0x10
|
||||
#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING 0x30
|
||||
|
||||
#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE BIT(0)
|
||||
#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT BIT(5)
|
||||
|
||||
#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED BIT(0)
|
||||
#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT BIT(1)
|
||||
|
||||
#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00
|
||||
|
||||
#define HIDPP_REPROG_CONTROL_BACK 0x0053
|
||||
#define HIDPP_REPROG_CONTROL_FORWARD 0x0056
|
||||
|
||||
#define HIDPP_PRODUCT_SIGNATURE_M650 0xb02a
|
||||
|
||||
struct hidpp_reprog_control_mapping {
|
||||
u16 control;
|
||||
u16 code;
|
||||
};
|
||||
|
||||
static const struct hidpp_reprog_control_mapping m650_reprog_control_mappings[] = {
|
||||
{ HIDPP_REPROG_CONTROL_BACK, BTN_BACK },
|
||||
{ HIDPP_REPROG_CONTROL_FORWARD, BTN_FORWARD },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct hidpp_reprog_control_mapping *
|
||||
hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp)
|
||||
{
|
||||
switch (hidpp->hid_dev->product) {
|
||||
case HIDPP_PRODUCT_SIGNATURE_M650:
|
||||
return m650_reprog_control_mappings;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
|
||||
{
|
||||
struct hidpp_report response;
|
||||
u8 feature_index = hidpp->reprog_controls_feature_index;
|
||||
u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
|
||||
int ret;
|
||||
|
||||
ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
|
||||
&response);
|
||||
if (ret > 0)
|
||||
return -EPROTO;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return response.fap.params[0];
|
||||
}
|
||||
|
||||
static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
|
||||
u8 index, u16 *control,
|
||||
u8 *flags)
|
||||
{
|
||||
struct hidpp_report response;
|
||||
u8 feature_index = hidpp->reprog_controls_feature_index;
|
||||
u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
|
||||
int ret;
|
||||
|
||||
ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
|
||||
sizeof(index), &response);
|
||||
if (ret > 0)
|
||||
return -EPROTO;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*control = get_unaligned_be16(&response.fap.params[0]);
|
||||
*flags = response.fap.params[4];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
|
||||
u16 control)
|
||||
{
|
||||
int count, ret;
|
||||
u16 cid;
|
||||
u8 flags;
|
||||
int i;
|
||||
|
||||
count = hidpp20_reprog_controls_get_count(hidpp);
|
||||
if (count < 0)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
|
||||
&flags);
|
||||
if (ret)
|
||||
return false;
|
||||
|
||||
if (cid == control)
|
||||
return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
|
||||
(flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
|
||||
u16 control, u8 flags)
|
||||
{
|
||||
struct hidpp_report response;
|
||||
u8 params[5];
|
||||
|
||||
put_unaligned_be16(control, ¶ms[0]);
|
||||
params[2] = flags;
|
||||
put_unaligned_be16(control, ¶ms[3]);
|
||||
|
||||
return hidpp_send_fap_command_sync(hidpp,
|
||||
hidpp->reprog_controls_feature_index,
|
||||
HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
|
||||
params, sizeof(params), &response);
|
||||
}
|
||||
|
||||
static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)
|
||||
{
|
||||
const struct hidpp_reprog_control_mapping *mapping;
|
||||
u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
|
||||
HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
|
||||
|
||||
if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
|
||||
return;
|
||||
|
||||
if (!hidpp->reprog_controls)
|
||||
return;
|
||||
|
||||
if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
|
||||
&hidpp->reprog_controls_feature_index))
|
||||
return;
|
||||
|
||||
for (mapping = hidpp->reprog_controls; mapping->control; mapping++) {
|
||||
if (!hidpp20_reprog_controls_find_control(hidpp, mapping->control))
|
||||
continue;
|
||||
|
||||
hidpp20_reprog_controls_set_control_reporting(hidpp,
|
||||
mapping->control,
|
||||
flags);
|
||||
}
|
||||
}
|
||||
|
||||
static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
|
||||
u8 *data, int size)
|
||||
{
|
||||
const struct hidpp_reprog_control_mapping *mapping;
|
||||
struct hidpp_report *report = (struct hidpp_report *)data;
|
||||
u16 controls[4];
|
||||
bool pressed;
|
||||
unsigned int i, j;
|
||||
|
||||
if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
|
||||
!hidpp->input ||
|
||||
!hidpp->reprog_controls ||
|
||||
hidpp->reprog_controls_feature_index == 0xff)
|
||||
return 0;
|
||||
|
||||
if (size < HIDPP_REPORT_LONG_LENGTH ||
|
||||
report->fap.feature_index != hidpp->reprog_controls_feature_index ||
|
||||
report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(controls); i++)
|
||||
controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
|
||||
|
||||
for (mapping = hidpp->reprog_controls; mapping->control; mapping++) {
|
||||
pressed = false;
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(controls); j++) {
|
||||
if (controls[j] == mapping->control) {
|
||||
pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
input_report_key(hidpp->input, mapping->code, pressed);
|
||||
}
|
||||
|
||||
input_sync(hidpp->input);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
|
||||
struct input_dev *input_dev)
|
||||
{
|
||||
const struct hidpp_reprog_control_mapping *mapping;
|
||||
|
||||
if (!hidpp->reprog_controls)
|
||||
return;
|
||||
|
||||
for (mapping = hidpp->reprog_controls; mapping->control; mapping++)
|
||||
input_set_capability(input_dev, EV_KEY, mapping->code);
|
||||
}
|
||||
|
||||
static void hidpp10_extra_mouse_buttons_populate_input(
|
||||
struct hidpp_device *hidpp, struct input_dev *input_dev)
|
||||
{
|
||||
|
|
@ -3859,17 +4079,37 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
|
|||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
|
||||
hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
|
||||
hidpp20_reprog_controls_populate_input(hidpp, input);
|
||||
}
|
||||
|
||||
static int hidpp_input_configured(struct hid_device *hdev,
|
||||
struct hid_input *hidinput)
|
||||
static int hidpp_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
|
||||
{
|
||||
struct hidpp_device *hidpp = hid_get_drvdata(hdev);
|
||||
struct input_dev *input = hidinput->input;
|
||||
int ret;
|
||||
|
||||
if (!hidpp)
|
||||
return 0;
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
|
||||
struct hidpp_ff_private_data data;
|
||||
|
||||
if (!list_is_first(&hidinput->list, &hdev->inputs))
|
||||
return 0;
|
||||
|
||||
ret = g920_get_config(hidpp, &data);
|
||||
if (!ret)
|
||||
ret = hidpp_ff_init(hidpp, &data);
|
||||
|
||||
if (ret) {
|
||||
hid_warn(hidpp->hid_dev,
|
||||
"Unable to initialize force feedback support, errno %d\n",
|
||||
ret);
|
||||
}
|
||||
}
|
||||
|
||||
hidpp_populate_input(hidpp, input);
|
||||
|
||||
return 0;
|
||||
|
|
@ -3971,6 +4211,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
|
||||
ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
|
||||
if (ret != 0)
|
||||
|
|
@ -4161,8 +4405,50 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool hidpp_is_bolt_child(struct hid_device *hdev)
|
||||
{
|
||||
struct device *parent = hdev->dev.parent;
|
||||
struct hid_device *receiver_hdev;
|
||||
|
||||
if (!parent)
|
||||
return false;
|
||||
|
||||
receiver_hdev = to_hid_device(parent);
|
||||
return receiver_hdev->vendor == USB_VENDOR_ID_LOGITECH &&
|
||||
receiver_hdev->product == USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER;
|
||||
}
|
||||
|
||||
static int hidpp_bolt_init(struct hidpp_device *hidpp)
|
||||
{
|
||||
struct hid_device *hdev = hidpp->hid_dev;
|
||||
char *name;
|
||||
int ret;
|
||||
|
||||
ret = hidpp_serial_init(hidpp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
name = hidpp_get_device_name(hidpp);
|
||||
if (!name)
|
||||
return -EIO;
|
||||
|
||||
snprintf(hdev->name, sizeof(hdev->name), "%s", name);
|
||||
dbg_hid("HID++ Bolt: Got name: %s\n", name);
|
||||
|
||||
kfree(name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hidpp_receiver_init(struct hidpp_device *hidpp)
|
||||
{
|
||||
if (hidpp_is_bolt_child(hidpp->hid_dev))
|
||||
return hidpp_bolt_init(hidpp);
|
||||
|
||||
return hidpp_unifying_init(hidpp);
|
||||
}
|
||||
|
||||
/* Get name + serial for USB and Bluetooth HID++ devices */
|
||||
static void hidpp_non_unifying_init(struct hidpp_device *hidpp)
|
||||
static void hidpp_non_receiver_init(struct hidpp_device *hidpp)
|
||||
{
|
||||
struct hid_device *hdev = hidpp->hid_dev;
|
||||
char *name;
|
||||
|
|
@ -4264,6 +4550,8 @@ static void hidpp_connect_event(struct work_struct *work)
|
|||
return;
|
||||
}
|
||||
|
||||
hidpp20_reprog_controls_connect(hidpp);
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
|
||||
ret = hidpp10_consumer_keys_connect(hidpp);
|
||||
if (ret)
|
||||
|
|
@ -4437,6 +4725,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
hidpp->hid_dev = hdev;
|
||||
hidpp->name = hdev->name;
|
||||
hidpp->quirks = id->driver_data;
|
||||
hidpp->reprog_controls_feature_index = 0xff;
|
||||
hidpp->reprog_controls = hidpp20_reprog_controls_get_mappings(hidpp);
|
||||
hid_set_drvdata(hdev, hidpp);
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
|
|
@ -4510,9 +4800,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
|
||||
/* Get name + serial, store in hdev->name + hdev->uniq */
|
||||
if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
|
||||
hidpp_unifying_init(hidpp);
|
||||
hidpp_receiver_init(hidpp);
|
||||
else
|
||||
hidpp_non_unifying_init(hidpp);
|
||||
hidpp_non_receiver_init(hidpp);
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
|
||||
connect_mask &= ~HID_CONNECT_HIDINPUT;
|
||||
|
|
@ -4530,21 +4820,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
schedule_work(&hidpp->work);
|
||||
flush_work(&hidpp->work);
|
||||
|
||||
if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
|
||||
struct hidpp_ff_private_data data;
|
||||
|
||||
ret = g920_get_config(hidpp, &data);
|
||||
if (!ret)
|
||||
ret = hidpp_ff_init(hidpp, &data);
|
||||
|
||||
if (ret) {
|
||||
hid_warn(hidpp->hid_dev,
|
||||
"Unable to initialize force feedback support, errno %d\n",
|
||||
ret);
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This relies on logi_dj_ll_close() being a no-op so that DJ connection
|
||||
* events will still be received.
|
||||
|
|
@ -4647,6 +4922,8 @@ static const struct hid_device_id hidpp_devices[] = {
|
|||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) },
|
||||
{ /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) },
|
||||
{ /* Logitech G502 X Plus Wireless Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC095) },
|
||||
{ /* Logitech G703 Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) },
|
||||
{ /* Logitech G703 Hero Gaming Mouse over USB */
|
||||
|
|
@ -4659,8 +4936,6 @@ static const struct hid_device_id hidpp_devices[] = {
|
|||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
|
||||
{ /* MX Vertical over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08A) },
|
||||
{ /* Logitech G703 Hero Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) },
|
||||
{ /* Logitech G903 Hero Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC091) },
|
||||
{ /* Logitech G915 TKL Keyboard over USB */
|
||||
|
|
@ -4707,7 +4982,9 @@ static const struct hid_device_id hidpp_devices[] = {
|
|||
{ /* MX Vertical mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
|
||||
{ /* Signature M650 over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
HIDPP_PRODUCT_SIGNATURE_M650),
|
||||
.driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },
|
||||
{ /* MX Master 3 mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
|
||||
{ /* MX Anywhere 3 mouse over Bluetooth */
|
||||
|
|
|
|||
|
|
@ -383,8 +383,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
|
|||
}
|
||||
}
|
||||
|
||||
static int magicmouse_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *data, int size)
|
||||
static int __magicmouse_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *data, int size, bool nested)
|
||||
{
|
||||
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
|
||||
struct input_dev *input = msc->input;
|
||||
|
|
@ -495,6 +495,15 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
|||
* packet.
|
||||
*/
|
||||
|
||||
/*
|
||||
* A double report only ever wraps two normal reports, so it is
|
||||
* never nested. Refuse to recurse a second time; otherwise a
|
||||
* malicious device could chain DOUBLE_REPORT_ID packets to drive
|
||||
* unbounded recursion and overflow the kernel stack.
|
||||
*/
|
||||
if (nested)
|
||||
return 0;
|
||||
|
||||
/* Ensure that we have at least 2 elements (report type and size) */
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
|
@ -506,9 +515,9 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
magicmouse_raw_event(hdev, report, data + 2, data[1]);
|
||||
magicmouse_raw_event(hdev, report, data + 2 + data[1],
|
||||
size - 2 - data[1]);
|
||||
__magicmouse_raw_event(hdev, report, data + 2, data[1], true);
|
||||
__magicmouse_raw_event(hdev, report, data + 2 + data[1],
|
||||
size - 2 - data[1], true);
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
|
|
@ -534,6 +543,12 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int magicmouse_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *data, int size)
|
||||
{
|
||||
return __magicmouse_raw_event(hdev, report, data, size, false);
|
||||
}
|
||||
|
||||
static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
|
||||
struct hid_usage *usage, __s32 value)
|
||||
{
|
||||
|
|
@ -828,6 +843,12 @@ static bool is_usb_magictrackpad2(__u32 vendor, __u32 product)
|
|||
product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC;
|
||||
}
|
||||
|
||||
static bool is_bt_magictrackpad2(__u32 vendor, __u32 product)
|
||||
{
|
||||
return vendor == BT_VENDOR_ID_APPLE &&
|
||||
product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC;
|
||||
}
|
||||
|
||||
static int magicmouse_fetch_battery(struct hid_device *hdev)
|
||||
{
|
||||
#ifdef CONFIG_HID_BATTERY_STRENGTH
|
||||
|
|
@ -838,7 +859,8 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
|
|||
bat = hid_get_battery(hdev);
|
||||
if (!bat ||
|
||||
(!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
|
||||
!is_usb_magictrackpad2(hdev->vendor, hdev->product)))
|
||||
!is_usb_magictrackpad2(hdev->vendor, hdev->product) &&
|
||||
!is_bt_magictrackpad2(hdev->vendor, hdev->product)))
|
||||
return -1;
|
||||
|
||||
report_enum = &hdev->report_enum[bat->report_type];
|
||||
|
|
@ -900,6 +922,16 @@ static int magicmouse_probe(struct hid_device *hdev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* When hidinput_connect() fails it frees every input device it
|
||||
* created, but that does not fail hid_hw_start(): the core simply
|
||||
* does not claim an input. msc->input, cached in ->input_mapping
|
||||
* while the report descriptor was parsed, would then be a dangling
|
||||
* pointer that passes every NULL check. Trust the core's claim.
|
||||
*/
|
||||
if (!(hdev->claimed & HID_CLAIMED_INPUT))
|
||||
msc->input = NULL;
|
||||
|
||||
if (is_usb_magicmouse2(id->vendor, id->product) ||
|
||||
is_usb_magictrackpad2(id->vendor, id->product)) {
|
||||
timer_setup(&msc->battery_timer, magicmouse_battery_timer_tick, 0);
|
||||
|
|
@ -971,6 +1003,16 @@ static int magicmouse_probe(struct hid_device *hdev,
|
|||
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
|
||||
}
|
||||
|
||||
/*
|
||||
* Query the Bluetooth Magic Trackpad USB-C battery as done for USB.
|
||||
* Start io first: probe holds driver_input_lock and the synchronous
|
||||
* GET_REPORT reply would otherwise be dropped.
|
||||
*/
|
||||
if (is_bt_magictrackpad2(id->vendor, id->product)) {
|
||||
hid_device_io_start(hdev);
|
||||
magicmouse_fetch_battery(hdev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_stop_hw:
|
||||
if (is_usb_magicmouse2(id->vendor, id->product) ||
|
||||
|
|
@ -995,6 +1037,22 @@ static void magicmouse_remove(struct hid_device *hdev)
|
|||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int magicmouse_reset_resume(struct hid_device *hdev)
|
||||
{
|
||||
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
|
||||
|
||||
/* The device drops out of multitouch mode on resume; re-send the
|
||||
* enable report. Only the HID_TYPE_USBMOUSE interface accepts it, and
|
||||
* it must be deferred. Sending it inline here is too early.
|
||||
*/
|
||||
if (msc && hdev->type == HID_TYPE_USBMOUSE)
|
||||
schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
|
|
@ -1058,6 +1116,9 @@ static struct hid_driver magicmouse_driver = {
|
|||
.event = magicmouse_event,
|
||||
.input_mapping = magicmouse_input_mapping,
|
||||
.input_configured = magicmouse_input_configured,
|
||||
#ifdef CONFIG_PM
|
||||
.reset_resume = magicmouse_reset_resume,
|
||||
#endif
|
||||
};
|
||||
module_hid_driver(magicmouse_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
|
|||
|
||||
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 4);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto out;
|
||||
|
||||
mcp->rxbuf_idx = 0;
|
||||
|
||||
|
|
@ -365,7 +365,7 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
|
|||
} else {
|
||||
usleep_range(980, 1000);
|
||||
mcp_cancel_last_cmd(mcp);
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
retries = 0;
|
||||
|
|
@ -375,6 +375,10 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
|
|||
usleep_range(980, 1000);
|
||||
ret = mcp_chk_last_cmd_status_free_bus(mcp);
|
||||
|
||||
out:
|
||||
mcp->rxbuf = NULL;
|
||||
mcp->rxbuf_size = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -861,6 +865,9 @@ static int mcp2221_raw_event(struct hid_device *hdev,
|
|||
u8 *buf;
|
||||
struct mcp2221 *mcp = hid_get_drvdata(hdev);
|
||||
|
||||
if (size < 4)
|
||||
return 0;
|
||||
|
||||
switch (data[0]) {
|
||||
|
||||
case MCP2221_I2C_WR_DATA:
|
||||
|
|
@ -926,6 +933,10 @@ static int mcp2221_raw_event(struct hid_device *hdev,
|
|||
mcp->status = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (4 + data[3] > size) {
|
||||
mcp->status = -EINVAL;
|
||||
break;
|
||||
}
|
||||
buf = mcp->rxbuf;
|
||||
memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]);
|
||||
mcp->rxbuf_idx = mcp->rxbuf_idx + data[3];
|
||||
|
|
@ -1049,6 +1060,8 @@ static void mcp2221_hid_unregister(void *ptr)
|
|||
{
|
||||
struct hid_device *hdev = ptr;
|
||||
|
||||
if (hdev->io_started)
|
||||
hid_device_io_stop(hdev);
|
||||
hid_hw_close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,21 +35,16 @@ static int mwctrl_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int mwctrl_init(struct hid_device *hid)
|
||||
static int mwctrl_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct mwctrl_device *mwctrl;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
int i;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1);
|
||||
|
|
@ -61,16 +56,7 @@ static int mwctrl_init(struct hid_device *hid)
|
|||
if (!mwctrl)
|
||||
return -ENOMEM;
|
||||
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, mwctrl, mwctrl_play);
|
||||
if (error) {
|
||||
kfree(mwctrl);
|
||||
return error;
|
||||
}
|
||||
|
||||
mwctrl->report = report;
|
||||
|
||||
/* Field 0 is always 2, and field 1 is always 0. The original
|
||||
* windows driver has a 5 bytes command, where the 5th byte is
|
||||
* a repeat of the 3rd byte, however the device has only 4
|
||||
|
|
@ -82,32 +68,17 @@ static int mwctrl_init(struct hid_device *hid)
|
|||
mwctrl->strong = &report->field[2]->value[0];
|
||||
mwctrl->weak = &report->field[3]->value[0];
|
||||
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, mwctrl, mwctrl_play);
|
||||
if (error) {
|
||||
kfree(mwctrl);
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mwctrl_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mwctrl_init(hdev);
|
||||
if (ret)
|
||||
hid_hw_stop(hdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id mwctrl_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_MEGAWORLD,
|
||||
USB_DEVICE_ID_MEGAWORLD_GAMEPAD) },
|
||||
|
|
@ -118,7 +89,7 @@ MODULE_DEVICE_TABLE(hid, mwctrl_devices);
|
|||
static struct hid_driver mwctrl_driver = {
|
||||
.name = "megaworld",
|
||||
.id_table = mwctrl_devices,
|
||||
.probe = mwctrl_probe,
|
||||
.input_configured = mwctrl_input_configured,
|
||||
};
|
||||
module_hid_driver(mwctrl_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -54,61 +54,45 @@ static int mf_play(struct input_dev *dev, void *data, struct ff_effect *effect)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int mf_init(struct hid_device *hid)
|
||||
static int mf_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct mf_device *mf;
|
||||
|
||||
struct list_head *report_list =
|
||||
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
|
||||
struct list_head *report_ptr;
|
||||
struct hid_report *report;
|
||||
|
||||
struct list_head *input_ptr = &hid->inputs;
|
||||
struct hid_input *input;
|
||||
|
||||
struct input_dev *dev;
|
||||
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
|
||||
/* Setup each of the four inputs */
|
||||
list_for_each(report_ptr, report_list) {
|
||||
report = list_entry(report_ptr, struct hid_report, list);
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
if (report->maxfield < 1 || report->field[0]->report_count < 2) {
|
||||
hid_err(hid, "Invalid report, this should never happen!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (list_is_last(input_ptr, &hid->inputs)) {
|
||||
hid_err(hid, "Missing input, this should never happen!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
input_ptr = input_ptr->next;
|
||||
input = list_entry(input_ptr, struct hid_input, list);
|
||||
|
||||
mf = kzalloc_obj(struct mf_device);
|
||||
if (!mf)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = input->input;
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, mf, mf_play);
|
||||
if (error) {
|
||||
kfree(mf);
|
||||
return error;
|
||||
}
|
||||
|
||||
mf->report = report;
|
||||
mf->report->field[0]->value[0] = 0x00;
|
||||
mf->report->field[0]->value[1] = 0x00;
|
||||
hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT);
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output reports found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hid_info(hid, "Force feedback for HJZ Mayflash game controller "
|
||||
"adapters by Marcel Hasler <mahasler@gmail.com>\n");
|
||||
if (report->maxfield < 1 || report->field[0]->report_count < 2) {
|
||||
hid_err(hid, "Invalid report, this should never happen!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mf = kzalloc_obj(struct mf_device);
|
||||
if (!mf)
|
||||
return -ENOMEM;
|
||||
|
||||
mf->report = report;
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, mf, mf_play);
|
||||
if (error) {
|
||||
kfree(mf);
|
||||
return error;
|
||||
}
|
||||
|
||||
mf->report->field[0]->value[0] = 0x00;
|
||||
mf->report->field[0]->value[1] = 0x00;
|
||||
hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -128,22 +112,14 @@ static int mf_probe(struct hid_device *hid, const struct hid_device_id *id)
|
|||
return error;
|
||||
}
|
||||
|
||||
error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
error = hid_hw_start(hid, HID_CONNECT_DEFAULT);
|
||||
if (error) {
|
||||
hid_err(hid, "HID hw start failed\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
error = mf_init(hid);
|
||||
if (error) {
|
||||
hid_err(hid, "Force feedback init failed.\n");
|
||||
hid_hw_stop(hid);
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hid_device_id mf_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3),
|
||||
.driver_data = HID_QUIRK_MULTI_INPUT },
|
||||
|
|
@ -163,6 +139,7 @@ static struct hid_driver mf_driver = {
|
|||
.name = "hid_mf",
|
||||
.id_table = mf_devices,
|
||||
.probe = mf_probe,
|
||||
.input_configured = mf_input_configured,
|
||||
};
|
||||
module_hid_driver(mf_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -323,22 +323,17 @@ static int ms_play_effect(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ms_init_ff(struct hid_device *hdev)
|
||||
static int ms_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
|
||||
{
|
||||
struct hid_input *hidinput;
|
||||
struct input_dev *input_dev;
|
||||
struct ms_data *ms = hid_get_drvdata(hdev);
|
||||
|
||||
if (list_empty(&hdev->inputs)) {
|
||||
hid_err(hdev, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_entry(hdev->inputs.next, struct hid_input, list);
|
||||
input_dev = hidinput->input;
|
||||
struct input_dev *input_dev = hidinput->input;
|
||||
|
||||
if (!(ms->quirks & MS_QUIRK_FF))
|
||||
return 0;
|
||||
|
||||
if (!list_is_first(&hidinput->list, &hdev->inputs))
|
||||
return 0;
|
||||
|
||||
ms->hdev = hdev;
|
||||
INIT_WORK(&ms->ff_worker, ms_ff_worker);
|
||||
|
||||
|
|
@ -352,16 +347,6 @@ static int ms_init_ff(struct hid_device *hdev)
|
|||
return input_ff_create_memless(input_dev, NULL, ms_play_effect);
|
||||
}
|
||||
|
||||
static void ms_remove_ff(struct hid_device *hdev)
|
||||
{
|
||||
struct ms_data *ms = hid_get_drvdata(hdev);
|
||||
|
||||
if (!(ms->quirks & MS_QUIRK_FF))
|
||||
return;
|
||||
|
||||
cancel_work_sync(&ms->ff_worker);
|
||||
}
|
||||
|
||||
static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
unsigned long quirks = id->driver_data;
|
||||
|
|
@ -385,29 +370,21 @@ static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err_free;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | ((quirks & MS_HIDINPUT) ?
|
||||
HID_CONNECT_HIDINPUT_FORCE : 0));
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err_free;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = ms_init_ff(hdev);
|
||||
if (ret)
|
||||
hid_err(hdev, "could not initialize ff, continuing anyway");
|
||||
|
||||
return 0;
|
||||
err_free:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ms_remove(struct hid_device *hdev)
|
||||
{
|
||||
hid_hw_stop(hdev);
|
||||
ms_remove_ff(hdev);
|
||||
}
|
||||
|
||||
static const struct hid_device_id ms_devices[] = {
|
||||
|
|
@ -469,6 +446,7 @@ static struct hid_driver ms_driver = {
|
|||
.report_fixup = ms_report_fixup,
|
||||
.input_mapping = ms_input_mapping,
|
||||
.input_mapped = ms_input_mapped,
|
||||
.input_configured = ms_input_configured,
|
||||
.event = ms_event,
|
||||
.probe = ms_probe,
|
||||
.remove = ms_remove,
|
||||
|
|
|
|||
2063
drivers/hid/hid-msi.c
Normal file
2063
drivers/hid/hid-msi.c
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -2189,16 +2189,8 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
|
||||
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
|
||||
|
||||
if (td->is_haptic_touchpad) {
|
||||
if (hid_haptic_init(hdev, &td->haptic)) {
|
||||
dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
|
||||
hdev->name);
|
||||
td->is_haptic_touchpad = false;
|
||||
devm_kfree(&hdev->dev, td->haptic);
|
||||
}
|
||||
} else {
|
||||
if (!td->is_haptic_touchpad)
|
||||
devm_kfree(&hdev->dev, td->haptic);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2722,7 +2714,7 @@ static const struct hid_device_id mt_devices[] = {
|
|||
HID_ANY_ID) },
|
||||
|
||||
/* Hantick */
|
||||
{ .driver_data = MT_CLS_NSMU,
|
||||
{ .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU,
|
||||
HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
|
||||
I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) },
|
||||
|
||||
|
|
|
|||
|
|
@ -609,6 +609,8 @@ struct joycon_ctlr {
|
|||
unsigned int last_input_report_msecs;
|
||||
unsigned int last_subcmd_sent_msecs;
|
||||
unsigned int consecutive_valid_report_deltas;
|
||||
unsigned int subcmd_rate_exhaustions;
|
||||
bool subcmd_rate_relaxed;
|
||||
|
||||
/* factory calibration data */
|
||||
struct joycon_stick_cal left_stick_cal_x;
|
||||
|
|
@ -841,10 +843,11 @@ static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr)
|
|||
#define JC_SUBCMD_TX_OFFSET_MS 4
|
||||
#define JC_SUBCMD_VALID_DELTA_REQ 3
|
||||
#define JC_SUBCMD_RATE_MAX_ATTEMPTS 25
|
||||
#define JC_SUBCMD_RATE_MAX_FAILURES 4
|
||||
#define JC_SUBCMD_RATE_LIMITER_USB_MS 20
|
||||
#define JC_SUBCMD_RATE_LIMITER_BT_MS 60
|
||||
#define JC_SUBCMD_RATE_LIMITER_MS(ctlr) ((ctlr)->hdev->bus == BUS_USB ? JC_SUBCMD_RATE_LIMITER_USB_MS : JC_SUBCMD_RATE_LIMITER_BT_MS)
|
||||
static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
|
||||
static void joycon_enforce_subcmd_rate_strict(struct joycon_ctlr *ctlr)
|
||||
{
|
||||
unsigned int current_ms;
|
||||
unsigned long subcmd_delta;
|
||||
|
|
@ -872,6 +875,14 @@ static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
|
|||
|
||||
if (attempts >= JC_SUBCMD_RATE_MAX_ATTEMPTS) {
|
||||
hid_warn(ctlr->hdev, "%s: exceeded max attempts", __func__);
|
||||
|
||||
if (++ctlr->subcmd_rate_exhaustions == JC_SUBCMD_RATE_MAX_FAILURES) {
|
||||
ctlr->subcmd_rate_relaxed = true;
|
||||
hid_info(ctlr->hdev,
|
||||
"input report cadence does not fit the %d-%dms window; using the legacy subcommand throttle\n",
|
||||
JC_INPUT_REPORT_MIN_DELTA,
|
||||
JC_INPUT_REPORT_MAX_DELTA);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -886,6 +897,32 @@ static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
|
|||
msleep(JC_SUBCMD_TX_OFFSET_MS);
|
||||
}
|
||||
|
||||
/* The rate limiter as it was before commit d750d1480362, without the report
|
||||
* cadence requirement.
|
||||
*/
|
||||
static void joycon_enforce_subcmd_rate_legacy(struct joycon_ctlr *ctlr)
|
||||
{
|
||||
static const unsigned int max_subcmd_rate_ms = 25;
|
||||
unsigned int current_ms = jiffies_to_msecs(jiffies);
|
||||
unsigned int delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
|
||||
|
||||
while (delta_ms < max_subcmd_rate_ms &&
|
||||
ctlr->ctlr_state == JOYCON_CTLR_STATE_READ) {
|
||||
joycon_wait_for_input_report(ctlr);
|
||||
current_ms = jiffies_to_msecs(jiffies);
|
||||
delta_ms = current_ms - ctlr->last_subcmd_sent_msecs;
|
||||
}
|
||||
ctlr->last_subcmd_sent_msecs = current_ms;
|
||||
}
|
||||
|
||||
static void joycon_enforce_subcmd_rate(struct joycon_ctlr *ctlr)
|
||||
{
|
||||
if (ctlr->subcmd_rate_relaxed)
|
||||
joycon_enforce_subcmd_rate_legacy(ctlr);
|
||||
else
|
||||
joycon_enforce_subcmd_rate_strict(ctlr);
|
||||
}
|
||||
|
||||
static int joycon_hid_send_sync(struct joycon_ctlr *ctlr, u8 *data, size_t len,
|
||||
u32 timeout)
|
||||
{
|
||||
|
|
@ -1474,7 +1511,6 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr,
|
|||
dropped_threshold = ctlr->imu_avg_delta_ms * 3 / 2;
|
||||
dropped_pkts = (delta - min(delta, dropped_threshold)) /
|
||||
ctlr->imu_avg_delta_ms;
|
||||
ctlr->imu_timestamp_us += 1000 * ctlr->imu_avg_delta_ms;
|
||||
if (dropped_pkts > JC_IMU_DROPPED_PKT_WARNING) {
|
||||
hid_warn_ratelimited(ctlr->hdev,
|
||||
"compensating for %u dropped IMU reports\n",
|
||||
|
|
@ -2162,10 +2198,6 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
|
|||
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);
|
||||
|
|
@ -2208,6 +2240,10 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
|
|||
if (joycon_has_rumble(ctlr))
|
||||
joycon_config_rumble(ctlr);
|
||||
|
||||
ret = input_register_device(ctlr->input);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -2607,7 +2643,12 @@ static int joycon_ctlr_read_handler(struct joycon_ctlr *ctlr, u8 *data,
|
|||
{
|
||||
if (data[0] == JC_INPUT_SUBCMD_REPLY || data[0] == JC_INPUT_IMU_DATA ||
|
||||
data[0] == JC_INPUT_MCU_DATA) {
|
||||
if (size >= 12) /* make sure it contains the input report */
|
||||
/*
|
||||
* The whole struct is cast and parsed below, including the
|
||||
* IMU/subcmd union, not just the 12-byte partial header this
|
||||
* used to check for.
|
||||
*/
|
||||
if (size >= sizeof(struct joycon_input_report))
|
||||
joycon_parse_report(ctlr,
|
||||
(struct joycon_input_report *)data);
|
||||
}
|
||||
|
|
@ -2736,14 +2777,14 @@ static int nintendo_hid_probe(struct hid_device *hdev,
|
|||
ret = joycon_init(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to initialize controller; ret=%d\n", ret);
|
||||
goto err_close;
|
||||
goto err_io_stop;
|
||||
}
|
||||
|
||||
/* Initialize the leds */
|
||||
ret = joycon_leds_create(ctlr);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to create leds; ret=%d\n", ret);
|
||||
goto err_close;
|
||||
goto err_io_stop;
|
||||
}
|
||||
|
||||
/* Initialize the battery power supply */
|
||||
|
|
@ -2766,7 +2807,8 @@ static int nintendo_hid_probe(struct hid_device *hdev,
|
|||
|
||||
err_ida:
|
||||
ida_free(&nintendo_player_id_allocator, ctlr->player_id);
|
||||
err_close:
|
||||
err_io_stop:
|
||||
hid_device_io_stop(hdev);
|
||||
hid_hw_close(hdev);
|
||||
err_stop:
|
||||
hid_hw_stop(hdev);
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
|
|||
* Re-apply our settings after this has been received.
|
||||
*/
|
||||
if (data[3] == OXP_EFFECT_MONO_TRUE) {
|
||||
mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
|
||||
mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -788,7 +788,7 @@ static ssize_t map_button_store(struct device *dev,
|
|||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
mod_delayed_work(system_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50));
|
||||
mod_delayed_work(system_dfl_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50));
|
||||
return count;
|
||||
}
|
||||
|
||||
|
|
@ -1349,7 +1349,7 @@ static void oxp_rgb_brightness_set(struct led_classdev *led_cdev,
|
|||
enum led_brightness brightness)
|
||||
{
|
||||
led_cdev->brightness = brightness;
|
||||
mod_delayed_work(system_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50));
|
||||
mod_delayed_work(system_dfl_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50));
|
||||
}
|
||||
|
||||
static struct attribute *oxp_rgb_attrs[] = {
|
||||
|
|
@ -1502,7 +1502,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
|
|||
drvdata.rumble_intensity = 5;
|
||||
|
||||
INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
|
||||
mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
|
||||
mod_delayed_work(system_dfl_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
|
||||
|
||||
ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
|
||||
if (ret)
|
||||
|
|
|
|||
|
|
@ -99,6 +99,15 @@ static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u,
|
|||
ret = resp->raw_data[2];
|
||||
if (ret > s)
|
||||
ret = s;
|
||||
/*
|
||||
* raw_data[2] is a device-supplied length; also clamp it to
|
||||
* what picolcd_raw_event() actually stored (raw_size), or a
|
||||
* hostile device overruns the raw_data[] buffer.
|
||||
*/
|
||||
if (ret > resp->raw_size - 3)
|
||||
ret = resp->raw_size - 3;
|
||||
if (ret < 0)
|
||||
ret = 0;
|
||||
if (copy_to_user(u, resp->raw_data+3, ret))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
|
|
|
|||
|
|
@ -62,15 +62,13 @@ static int hid_plff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int plff_init(struct hid_device *hid)
|
||||
static int pl_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct plff_device *plff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct list_head *report_list =
|
||||
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct list_head *report_ptr = report_list;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
s32 maxval;
|
||||
s32 *strong;
|
||||
|
|
@ -83,89 +81,80 @@ static int plff_init(struct hid_device *hid)
|
|||
The input reports also contain a field which contains
|
||||
8 ff00.0001 usages and 8 boolean values. Their meaning is
|
||||
currently unknown.
|
||||
|
||||
|
||||
A version of the 0e8f:0003 exists that has all the values in
|
||||
separate fields and misses the extra input field, thus resembling
|
||||
Zeroplus (hid-zpff) devices.
|
||||
*/
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output reports found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
list_for_each_entry(hidinput, &hid->inputs, list) {
|
||||
|
||||
report_ptr = report_ptr->next;
|
||||
|
||||
if (report_ptr == report_list) {
|
||||
hid_err(hid, "required output report is missing\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
report = list_entry(report_ptr, struct hid_report, list);
|
||||
if (report->maxfield < 1) {
|
||||
hid_err(hid, "no fields in the report\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
maxval = 0x7f;
|
||||
if (report->field[0]->report_count >= 4) {
|
||||
report->field[0]->value[0] = 0x00;
|
||||
report->field[0]->value[1] = 0x00;
|
||||
strong = &report->field[0]->value[2];
|
||||
weak = &report->field[0]->value[3];
|
||||
hid_dbg(hid, "detected single-field device");
|
||||
} else if (report->field[0]->maxusage == 1 &&
|
||||
report->field[0]->usage[0].hid ==
|
||||
(HID_UP_LED | 0x43) &&
|
||||
report->maxfield >= 4 &&
|
||||
report->field[0]->report_count >= 1 &&
|
||||
report->field[1]->report_count >= 1 &&
|
||||
report->field[2]->report_count >= 1 &&
|
||||
report->field[3]->report_count >= 1) {
|
||||
report->field[0]->value[0] = 0x00;
|
||||
report->field[1]->value[0] = 0x00;
|
||||
strong = &report->field[2]->value[0];
|
||||
weak = &report->field[3]->value[0];
|
||||
if (hid->vendor == USB_VENDOR_ID_JESS2)
|
||||
maxval = 0xff;
|
||||
hid_dbg(hid, "detected 4-field device");
|
||||
} else {
|
||||
hid_err(hid, "not enough fields or values\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
plff = kzalloc_obj(struct plff_device);
|
||||
if (!plff)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = hidinput->input;
|
||||
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, plff, hid_plff_play);
|
||||
if (error) {
|
||||
kfree(plff);
|
||||
return error;
|
||||
}
|
||||
|
||||
plff->report = report;
|
||||
plff->strong = strong;
|
||||
plff->weak = weak;
|
||||
plff->maxval = maxval;
|
||||
|
||||
*strong = 0x00;
|
||||
*weak = 0x00;
|
||||
hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
|
||||
if (report->maxfield < 1) {
|
||||
hid_err(hid, "no fields in the report\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
|
||||
maxval = 0x7f;
|
||||
if (report->field[0]->report_count >= 4) {
|
||||
report->field[0]->value[0] = 0x00;
|
||||
report->field[0]->value[1] = 0x00;
|
||||
strong = &report->field[0]->value[2];
|
||||
weak = &report->field[0]->value[3];
|
||||
hid_dbg(hid, "detected single-field device");
|
||||
} else if (report->field[0]->maxusage == 1 &&
|
||||
report->field[0]->usage[0].hid ==
|
||||
(HID_UP_LED | 0x43) &&
|
||||
report->maxfield >= 4 &&
|
||||
report->field[0]->report_count >= 1 &&
|
||||
report->field[1]->report_count >= 1 &&
|
||||
report->field[2]->report_count >= 1 &&
|
||||
report->field[3]->report_count >= 1) {
|
||||
report->field[0]->value[0] = 0x00;
|
||||
report->field[1]->value[0] = 0x00;
|
||||
strong = &report->field[2]->value[0];
|
||||
weak = &report->field[3]->value[0];
|
||||
if (hid->vendor == USB_VENDOR_ID_JESS2)
|
||||
maxval = 0xff;
|
||||
hid_dbg(hid, "detected 4-field device");
|
||||
} else {
|
||||
hid_err(hid, "not enough fields or values\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
plff = kzalloc_obj(struct plff_device);
|
||||
if (!plff)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = hidinput->input;
|
||||
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, plff, hid_plff_play);
|
||||
if (error) {
|
||||
kfree(plff);
|
||||
return error;
|
||||
}
|
||||
|
||||
plff->report = report;
|
||||
plff->strong = strong;
|
||||
plff->weak = weak;
|
||||
plff->maxval = maxval;
|
||||
|
||||
*strong = 0x00;
|
||||
*weak = 0x00;
|
||||
hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int plff_init(struct hid_device *hid)
|
||||
static inline int pl_input_configured(struct hid_device *hid,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -181,27 +170,17 @@ static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = plff_init(hdev);
|
||||
if (ret)
|
||||
goto stop;
|
||||
|
||||
return 0;
|
||||
|
||||
stop:
|
||||
hid_hw_stop(hdev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id pl_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
|
||||
.driver_data = 1 }, /* Twin USB Joystick */
|
||||
|
|
@ -217,6 +196,7 @@ static struct hid_driver pl_driver = {
|
|||
.name = "pantherlord",
|
||||
.id_table = pl_devices,
|
||||
.probe = pl_probe,
|
||||
.input_configured = pl_input_configured,
|
||||
};
|
||||
module_hid_driver(pl_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ static const struct hid_device_id hid_quirks[] = {
|
|||
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682), HID_QUIRK_NOGET },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692), HID_QUIRK_NOGET },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM), HID_QUIRK_NOGET },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_BEITONG, USB_DEVICE_ID_BEITONG_KP20D), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH), HID_QUIRK_MULTI_INPUT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2), HID_QUIRK_ALWAYS_POLL },
|
||||
|
|
@ -112,6 +113,8 @@ static const struct hid_device_id hid_quirks[] = {
|
|||
{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680), HID_QUIRK_MULTI_INPUT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI), HID_QUIRK_MULTI_INPUT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_AULA_MINI_60_HE_PRO),
|
||||
HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M406), HID_QUIRK_MULTI_INPUT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M506), HID_QUIRK_MULTI_INPUT },
|
||||
|
|
@ -747,8 +750,22 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
|||
#endif
|
||||
#if IS_ENABLED(CONFIG_HID_STEELSERIES)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3) },
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HID_SUNPLUS)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ static int rapoo_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (hdev->bus == BUS_USB) {
|
||||
if (hid_is_usb(hdev)) {
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
|
||||
if (intf->cur_altsetting->desc.bInterfaceNumber != 1)
|
||||
|
|
|
|||
|
|
@ -365,7 +365,7 @@ static int rmi_check_sanity(struct hid_device *hdev, u8 *data, int size)
|
|||
* such reports here.
|
||||
*/
|
||||
|
||||
while ((data[valid_size - 1] == 0xff) && valid_size > 0)
|
||||
while (valid_size > 0 && data[valid_size - 1] == 0xff)
|
||||
valid_size--;
|
||||
|
||||
return valid_size;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ static uint profile_numbers[5] = {0, 1, 2, 3, 4};
|
|||
|
||||
static void kone_profile_activated(struct kone_device *kone, uint new_profile)
|
||||
{
|
||||
if (new_profile < 1 || new_profile > ARRAY_SIZE(kone->profiles))
|
||||
new_profile = 1;
|
||||
kone->actual_profile = new_profile;
|
||||
kone->actual_dpi = kone->profiles[new_profile - 1].startup_dpi;
|
||||
}
|
||||
|
|
@ -793,8 +795,10 @@ static void kone_keep_values_up_to_date(struct kone_device *kone,
|
|||
{
|
||||
switch (event->event) {
|
||||
case kone_mouse_event_switch_profile:
|
||||
kone->actual_dpi = kone->profiles[event->value - 1].
|
||||
startup_dpi;
|
||||
if (event->value >= 1 &&
|
||||
event->value <= ARRAY_SIZE(kone->profiles))
|
||||
kone->actual_dpi =
|
||||
kone->profiles[event->value - 1].startup_dpi;
|
||||
fallthrough;
|
||||
case kone_mouse_event_osd_profile:
|
||||
kone->actual_profile = event->value;
|
||||
|
|
@ -915,3 +919,60 @@ module_exit(kone_exit);
|
|||
MODULE_AUTHOR("Stefan Achatz");
|
||||
MODULE_DESCRIPTION("USB Roccat Kone driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
#if IS_ENABLED(CONFIG_HID_ROCCAT_KONE_KUNIT_TEST)
|
||||
#include <kunit/test.h>
|
||||
|
||||
/*
|
||||
* Regression test for the out-of-bounds read in
|
||||
* kone_keep_values_up_to_date(): a malicious USB device sends a
|
||||
* "switch profile" HID event (event == kone_mouse_event_switch_profile)
|
||||
* with an attacker-chosen value in 0..255, which is used unbounded as
|
||||
* profiles[value - 1]. On an unpatched kernel the attack case triggers a
|
||||
* KASAN slab-out-of-bounds read; the fix must leave actual_dpi unchanged.
|
||||
*/
|
||||
static void kone_profile_index_oob_test(struct kunit *test)
|
||||
{
|
||||
struct kone_device *kone;
|
||||
struct kone_mouse_event ev = {};
|
||||
/*
|
||||
* Allocate only up to the end of profiles[] so that any index past
|
||||
* the 5-element array is IMMEDIATELY out of bounds and lands in the
|
||||
* KASAN redzone (a far over-read would hit unrelated valid memory and
|
||||
* escape KASAN).
|
||||
*/
|
||||
size_t sz = offsetof(struct kone_device, profiles) +
|
||||
sizeof(kone->profiles);
|
||||
|
||||
kone = kunit_kzalloc(test, sz, GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, kone);
|
||||
kone->profiles[0].startup_dpi = 0x42;
|
||||
|
||||
/* benign control: a valid in-range value drives the SAME path and
|
||||
* must succeed (proves the trigger reaches the real code).
|
||||
*/
|
||||
ev.event = kone_mouse_event_switch_profile;
|
||||
ev.value = 1;
|
||||
kone_keep_values_up_to_date(kone, &ev);
|
||||
KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42);
|
||||
|
||||
/* attack: value == ARRAY_SIZE(profiles) + 1 reads profiles[5], one
|
||||
* element past the array end -> KASAN slab-out-of-bounds read on an
|
||||
* unpatched kernel. The fix must reject it (actual_dpi unchanged).
|
||||
*/
|
||||
ev.value = ARRAY_SIZE(kone->profiles) + 1;
|
||||
kone_keep_values_up_to_date(kone, &ev);
|
||||
KUNIT_EXPECT_EQ(test, kone->actual_dpi, 0x42);
|
||||
}
|
||||
|
||||
static struct kunit_case kone_test_cases[] = {
|
||||
KUNIT_CASE(kone_profile_index_oob_test),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct kunit_suite kone_test_suite = {
|
||||
.name = "hid-roccat-kone",
|
||||
.test_cases = kone_test_cases,
|
||||
};
|
||||
kunit_test_suite(kone_test_suite);
|
||||
#endif /* CONFIG_HID_ROCCAT_KONE_KUNIT_TEST */
|
||||
|
|
|
|||
|
|
@ -70,6 +70,15 @@ static struct roccat_device *devices[ROCCAT_MAX_DEVICES];
|
|||
/* protects modifications of devices array */
|
||||
static DEFINE_MUTEX(devices_lock);
|
||||
|
||||
static void roccat_free_device(struct roccat_device *device)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ROCCAT_CBUF_SIZE; i++)
|
||||
kfree(device->cbuf[i].value);
|
||||
kfree(device);
|
||||
}
|
||||
|
||||
static ssize_t roccat_read(struct file *file, char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
|
|
@ -226,7 +235,7 @@ static int roccat_release(struct inode *inode, struct file *file)
|
|||
hid_hw_power(device->hid, PM_HINT_NORMAL);
|
||||
hid_hw_close(device->hid);
|
||||
} else {
|
||||
kfree(device);
|
||||
roccat_free_device(device);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -374,7 +383,7 @@ void roccat_disconnect(int minor)
|
|||
hid_hw_close(device->hid);
|
||||
wake_up_interruptible(&device->wait);
|
||||
} else {
|
||||
kfree(device);
|
||||
roccat_free_device(device);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(roccat_disconnect);
|
||||
|
|
|
|||
|
|
@ -609,7 +609,7 @@ static int hid_sensor_custom_add_attributes(struct hid_sensor_custom
|
|||
&sensor_inst->fields[i].
|
||||
hid_custom_attribute_group);
|
||||
if (ret)
|
||||
break;
|
||||
goto err_remove_groups;
|
||||
|
||||
/* For power or report field store indexes */
|
||||
if (sensor_inst->fields[i].attribute.attrib_id ==
|
||||
|
|
@ -621,6 +621,13 @@ static int hid_sensor_custom_add_attributes(struct hid_sensor_custom
|
|||
}
|
||||
|
||||
return ret;
|
||||
|
||||
err_remove_groups:
|
||||
while (--i >= 0)
|
||||
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
|
||||
&sensor_inst->fields[i].hid_custom_attribute_group);
|
||||
kfree(sensor_inst->fields);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom *
|
||||
|
|
@ -1005,26 +1012,26 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
|
||||
&enable_sensor_attr_group);
|
||||
ret = hid_sensor_custom_add_attributes(sensor_inst);
|
||||
if (ret)
|
||||
goto err_remove_callback;
|
||||
|
||||
ret = hid_sensor_custom_add_attributes(sensor_inst);
|
||||
if (ret)
|
||||
goto err_remove_group;
|
||||
|
||||
ret = hid_sensor_custom_dev_if_add(sensor_inst);
|
||||
ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
|
||||
&enable_sensor_attr_group);
|
||||
if (ret)
|
||||
goto err_remove_attributes;
|
||||
|
||||
ret = hid_sensor_custom_dev_if_add(sensor_inst);
|
||||
if (ret)
|
||||
goto err_remove_group;
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_attributes:
|
||||
hid_sensor_custom_remove_attributes(sensor_inst);
|
||||
err_remove_group:
|
||||
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
|
||||
&enable_sensor_attr_group);
|
||||
err_remove_attributes:
|
||||
hid_sensor_custom_remove_attributes(sensor_inst);
|
||||
err_remove_callback:
|
||||
sensor_hub_remove_callback(hsdev, hsdev->usage);
|
||||
|
||||
|
|
@ -1042,9 +1049,10 @@ static void hid_sensor_custom_remove(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
hid_sensor_custom_dev_if_remove(sensor_inst);
|
||||
hid_sensor_custom_remove_attributes(sensor_inst);
|
||||
/* Remove enable_sensor first as it uses fields via power_state/report_state. */
|
||||
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
|
||||
&enable_sensor_attr_group);
|
||||
hid_sensor_custom_remove_attributes(sensor_inst);
|
||||
sensor_hub_remove_callback(hsdev, hsdev->usage);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -239,12 +239,17 @@ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
|
|||
u32 field_index, int buffer_size, void *buffer)
|
||||
{
|
||||
struct hid_report *report;
|
||||
struct hid_field *field;
|
||||
struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
|
||||
int report_size;
|
||||
size_t field_size;
|
||||
size_t report_size;
|
||||
size_t copied = 0;
|
||||
size_t to_copy;
|
||||
int ret = 0;
|
||||
u8 *val_ptr;
|
||||
int buffer_index = 0;
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
if (!buffer || buffer_size <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
memset(buffer, 0, buffer_size);
|
||||
|
||||
|
|
@ -258,27 +263,30 @@ int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
|
|||
hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT);
|
||||
hid_hw_wait(hsdev->hdev);
|
||||
|
||||
field = report->field[field_index];
|
||||
|
||||
/* calculate number of bytes required to read this field */
|
||||
report_size = DIV_ROUND_UP(report->field[field_index]->report_size,
|
||||
8) *
|
||||
report->field[field_index]->report_count;
|
||||
if (!report_size) {
|
||||
field_size = DIV_ROUND_UP(field->report_size, 8);
|
||||
/* HID core stores each parsed report value in a __s32 slot. */
|
||||
if (!field_size || field_size > sizeof(field->value[0])) {
|
||||
ret = -EINVAL;
|
||||
goto done_proc;
|
||||
}
|
||||
ret = min(report_size, buffer_size);
|
||||
|
||||
val_ptr = (u8 *)report->field[field_index]->value;
|
||||
for (i = 0; i < report->field[field_index]->report_count; ++i) {
|
||||
if (buffer_index >= ret)
|
||||
break;
|
||||
|
||||
memcpy(&((u8 *)buffer)[buffer_index], val_ptr,
|
||||
report->field[field_index]->report_size / 8);
|
||||
val_ptr += sizeof(__s32);
|
||||
buffer_index += (report->field[field_index]->report_size / 8);
|
||||
if (field->report_count > SIZE_MAX / field_size) {
|
||||
ret = -EINVAL;
|
||||
goto done_proc;
|
||||
}
|
||||
|
||||
report_size = field_size * field->report_count;
|
||||
report_size = min_t(size_t, report_size, buffer_size);
|
||||
|
||||
for (i = 0; i < field->report_count && copied < report_size; ++i) {
|
||||
to_copy = min(field_size, report_size - copied);
|
||||
memcpy(&((u8 *)buffer)[copied], &field->value[i], to_copy);
|
||||
copied += to_copy;
|
||||
}
|
||||
ret = copied;
|
||||
|
||||
done_proc:
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
|
|
|
|||
|
|
@ -48,68 +48,56 @@ static int hid_sjoyff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int sjoyff_init(struct hid_device *hid)
|
||||
static int sjoy_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct sjoyff_device *sjoyff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct list_head *report_list =
|
||||
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
|
||||
struct list_head *report_ptr = report_list;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int error;
|
||||
|
||||
if (list_empty(report_list)) {
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
report = list_first_entry_or_null(report_list, struct hid_report, list);
|
||||
if (!report) {
|
||||
hid_err(hid, "no output reports found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
list_for_each_entry(hidinput, &hid->inputs, list) {
|
||||
report_ptr = report_ptr->next;
|
||||
|
||||
if (report_ptr == report_list) {
|
||||
hid_err(hid, "required output report is missing\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
report = list_entry(report_ptr, struct hid_report, list);
|
||||
if (report->maxfield < 1) {
|
||||
hid_err(hid, "no fields in the report\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (report->field[0]->report_count < 3) {
|
||||
hid_err(hid, "not enough values in the field\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
sjoyff = kzalloc_obj(struct sjoyff_device);
|
||||
if (!sjoyff)
|
||||
return -ENOMEM;
|
||||
|
||||
dev = hidinput->input;
|
||||
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
sjoyff->report = report;
|
||||
sjoyff->report->field[0]->value[0] = 0x01;
|
||||
sjoyff->report->field[0]->value[1] = 0x00;
|
||||
sjoyff->report->field[0]->value[2] = 0x00;
|
||||
hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
|
||||
|
||||
error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
|
||||
if (error) {
|
||||
kfree(sjoyff);
|
||||
return error;
|
||||
}
|
||||
if (report->maxfield < 1) {
|
||||
hid_err(hid, "no fields in the report\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n");
|
||||
if (report->field[0]->report_count < 3) {
|
||||
hid_err(hid, "not enough values in the field\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
sjoyff = kzalloc_obj(struct sjoyff_device);
|
||||
if (!sjoyff)
|
||||
return -ENOMEM;
|
||||
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
sjoyff->report = report;
|
||||
sjoyff->report->field[0]->value[0] = 0x01;
|
||||
sjoyff->report->field[0]->value[1] = 0x00;
|
||||
sjoyff->report->field[0]->value[2] = 0x00;
|
||||
hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
|
||||
|
||||
error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
|
||||
if (error) {
|
||||
kfree(sjoyff);
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int sjoyff_init(struct hid_device *hid)
|
||||
static inline int sjoy_input_configured(struct hid_device *hid,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -124,20 +112,16 @@ static int sjoy_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
sjoyff_init(hdev);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id sjoy_devices[] = {
|
||||
|
|
@ -165,6 +149,7 @@ static struct hid_driver sjoy_driver = {
|
|||
.name = "smartjoyplus",
|
||||
.id_table = sjoy_devices,
|
||||
.probe = sjoy_probe,
|
||||
.input_configured = sjoy_input_configured,
|
||||
};
|
||||
module_hid_driver(sjoy_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
* There will be no PIN request from the device.
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
|
|
@ -81,6 +82,7 @@
|
|||
#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER)
|
||||
#define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT)
|
||||
#define NSG_MRXU_REMOTE (NSG_MR5U_REMOTE_BT | NSG_MR7U_REMOTE_BT)
|
||||
#define RB4_GUITAR_PS4 (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT)
|
||||
|
||||
#define MAX_LEDS 4
|
||||
#define NSG_MRXU_MAX_X 1667
|
||||
|
|
@ -503,7 +505,7 @@ struct motion_output_report_02 {
|
|||
u8 r, g, b;
|
||||
u8 zero2;
|
||||
u8 rumble;
|
||||
};
|
||||
} __packed;
|
||||
static_assert(sizeof(struct motion_output_report_02) == 7);
|
||||
|
||||
#define SIXAXIS_REPORT_0xF2_SIZE 17
|
||||
|
|
@ -521,10 +523,6 @@ static DEFINE_SPINLOCK(sony_dev_list_lock);
|
|||
static LIST_HEAD(sony_device_list);
|
||||
static DEFINE_IDA(sony_device_id_allocator);
|
||||
|
||||
enum sony_worker {
|
||||
SONY_WORKER_STATE
|
||||
};
|
||||
|
||||
struct sony_sc {
|
||||
spinlock_t lock;
|
||||
struct list_head list_node;
|
||||
|
|
@ -534,6 +532,7 @@ struct sony_sc {
|
|||
struct input_dev *sensor_dev;
|
||||
struct led_classdev *leds[MAX_LEDS];
|
||||
unsigned long quirks;
|
||||
int (*raw_event)(struct sony_sc *sc, u8 *rd, int size);
|
||||
struct work_struct state_worker;
|
||||
void (*send_output_report)(struct sony_sc *sc);
|
||||
struct power_supply *battery;
|
||||
|
|
@ -566,19 +565,11 @@ struct sony_sc {
|
|||
|
||||
static void sony_set_leds(struct sony_sc *sc);
|
||||
|
||||
static inline void sony_schedule_work(struct sony_sc *sc,
|
||||
enum sony_worker which)
|
||||
static inline void sony_schedule_work(struct sony_sc *sc)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
switch (which) {
|
||||
case SONY_WORKER_STATE:
|
||||
spin_lock_irqsave(&sc->lock, flags);
|
||||
if (!sc->defer_initialization && sc->state_worker_initialized)
|
||||
schedule_work(&sc->state_worker);
|
||||
spin_unlock_irqrestore(&sc->lock, flags);
|
||||
break;
|
||||
}
|
||||
guard(spinlock_irqsave)(&sc->lock);
|
||||
if (!sc->defer_initialization && sc->state_worker_initialized)
|
||||
schedule_work(&sc->state_worker);
|
||||
}
|
||||
|
||||
static void ghl_magic_poke_cb(struct urb *urb)
|
||||
|
|
@ -946,15 +937,39 @@ static const u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc,
|
|||
return rdesc;
|
||||
}
|
||||
|
||||
static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
||||
static int sixaxis_raw_event(struct sony_sc *sc, u8 *rd, int size)
|
||||
{
|
||||
static const u8 sixaxis_battery_capacity[] = { 0, 1, 25, 50, 75, 100 };
|
||||
unsigned long flags;
|
||||
int offset;
|
||||
u8 index;
|
||||
u8 battery_capacity;
|
||||
int battery_status;
|
||||
|
||||
if (unlikely(size != 49 || rd[0] != 0x01))
|
||||
return 0;
|
||||
|
||||
if (sc->quirks & SIXAXIS_CONTROLLER) {
|
||||
/*
|
||||
* When connected via Bluetooth the Sixaxis occasionally sends
|
||||
* a report with the second byte 0xff and the rest zeroed.
|
||||
*
|
||||
* This report does not reflect the actual state of the
|
||||
* controller must be ignored to avoid generating false input
|
||||
* events.
|
||||
*/
|
||||
if (rd[1] == 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Sixaxis HID report has acclerometers/gyro with MSByte first, this
|
||||
* has to be BYTE_SWAPPED before passing up to joystick interface
|
||||
*/
|
||||
swap(rd[41], rd[42]);
|
||||
swap(rd[43], rd[44]);
|
||||
swap(rd[45], rd[46]);
|
||||
swap(rd[47], rd[48]);
|
||||
}
|
||||
|
||||
/*
|
||||
* The sixaxis is charging if the battery value is 0xee
|
||||
* and it is fully charged if the value is 0xef.
|
||||
|
|
@ -972,10 +987,10 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
|||
battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sc->lock, flags);
|
||||
sc->battery_capacity = battery_capacity;
|
||||
sc->battery_status = battery_status;
|
||||
spin_unlock_irqrestore(&sc->lock, flags);
|
||||
scoped_guard(spinlock_irqsave, &sc->lock) {
|
||||
sc->battery_capacity = battery_capacity;
|
||||
sc->battery_status = battery_status;
|
||||
}
|
||||
|
||||
if (sc->quirks & SIXAXIS_CONTROLLER) {
|
||||
int val;
|
||||
|
|
@ -993,13 +1008,18 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
|||
|
||||
input_sync(sc->sensor_dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
||||
static int nsg_mrxu_raw_event(struct sony_sc *sc, u8 *rd, int size)
|
||||
{
|
||||
int n, offset, relx, rely;
|
||||
u8 active;
|
||||
|
||||
if (unlikely(size < 12 || rd[0] != 0x02))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The NSG-MRxU multi-touch trackpad data starts at offset 1 and
|
||||
* the touch-related data starts at offset 2.
|
||||
|
|
@ -1067,10 +1087,33 @@ static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
|||
input_mt_sync_frame(sc->touchpad);
|
||||
|
||||
input_sync(sc->touchpad);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
||||
static int rb3_pro_instrument_raw_event(struct sony_sc *sc, u8 *rd, int size)
|
||||
{
|
||||
/* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're
|
||||
* sending full reports, and 0x02 when only sending navigation.
|
||||
*/
|
||||
if (size < 25 || rd[24] != 0x02)
|
||||
return 0;
|
||||
|
||||
/* Only attempt to enable full report every 8 seconds */
|
||||
if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) {
|
||||
sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8);
|
||||
rb3_pro_instrument_enable_full_report(sc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rb4_ps4_guitar_raw_event(struct sony_sc *sc, u8 *rd, int size)
|
||||
{
|
||||
const int expected_size = (sc->quirks & RB4_GUITAR_PS4_BT) ? 78 : 64;
|
||||
|
||||
if (unlikely(size != expected_size || rd[0] != 0x01))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Rock Band 4 PS4 guitars have whammy and
|
||||
* tilt functionality, they're located at
|
||||
|
|
@ -1084,15 +1127,18 @@ static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
|||
input_report_abs(sc->input_dev, ABS_RZ, rd[45]);
|
||||
|
||||
input_sync(sc->input_dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
||||
static int rb4_ps5_guitar_raw_event(struct sony_sc *sc, u8 *rd, int size)
|
||||
{
|
||||
u8 charging_status;
|
||||
u8 battery_data;
|
||||
u8 battery_capacity;
|
||||
u8 battery_status;
|
||||
unsigned long flags;
|
||||
|
||||
if (unlikely(size != 64 || rd[0] != 0x01))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Rock Band 4 PS5 guitars have whammy and
|
||||
|
|
@ -1132,73 +1178,30 @@ static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
|
|||
break;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sc->lock, flags);
|
||||
sc->battery_capacity = battery_capacity;
|
||||
sc->battery_status = battery_status;
|
||||
spin_unlock_irqrestore(&sc->lock, flags);
|
||||
scoped_guard(spinlock_irqsave, &sc->lock) {
|
||||
sc->battery_capacity = battery_capacity;
|
||||
sc->battery_status = battery_status;
|
||||
}
|
||||
|
||||
input_sync(sc->input_dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
u8 *rd, int size)
|
||||
{
|
||||
struct sony_sc *sc = hid_get_drvdata(hdev);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Sixaxis HID report has acclerometers/gyro with MSByte first, this
|
||||
* has to be BYTE_SWAPPED before passing up to joystick interface
|
||||
*/
|
||||
if ((sc->quirks & SIXAXIS_CONTROLLER) && rd[0] == 0x01 && size == 49) {
|
||||
/*
|
||||
* When connected via Bluetooth the Sixaxis occasionally sends
|
||||
* a report with the second byte 0xff and the rest zeroed.
|
||||
*
|
||||
* This report does not reflect the actual state of the
|
||||
* controller must be ignored to avoid generating false input
|
||||
* events.
|
||||
*/
|
||||
if (rd[1] == 0xff)
|
||||
return -EINVAL;
|
||||
|
||||
swap(rd[41], rd[42]);
|
||||
swap(rd[43], rd[44]);
|
||||
swap(rd[45], rd[46]);
|
||||
swap(rd[47], rd[48]);
|
||||
|
||||
sixaxis_parse_report(sc, rd, size);
|
||||
} else if ((sc->quirks & MOTION_CONTROLLER_BT) && rd[0] == 0x01 && size == 49) {
|
||||
sixaxis_parse_report(sc, rd, size);
|
||||
} else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 && size == 49) {
|
||||
sixaxis_parse_report(sc, rd, size);
|
||||
} else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02 && size >= 12) {
|
||||
nsg_mrxu_parse_report(sc, rd, size);
|
||||
return 1;
|
||||
} else if ((sc->quirks & RB4_GUITAR_PS4_USB) && rd[0] == 0x01 && size == 64) {
|
||||
rb4_ps4_guitar_parse_report(sc, rd, size);
|
||||
return 1;
|
||||
} else if ((sc->quirks & RB4_GUITAR_PS4_BT) && rd[0] == 0x01 && size == 78) {
|
||||
rb4_ps4_guitar_parse_report(sc, rd, size);
|
||||
return 1;
|
||||
} else if ((sc->quirks & RB4_GUITAR_PS5) && rd[0] == 0x01 && size == 64) {
|
||||
rb4_ps5_guitar_parse_report(sc, rd, size);
|
||||
return 1;
|
||||
if (sc->raw_event) {
|
||||
ret = sc->raw_event(sc, rd, size);
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're
|
||||
* sending full reports, and 0x02 when only sending navigation.
|
||||
*/
|
||||
if ((sc->quirks & RB3_PRO_INSTRUMENT) && size >= 25 && rd[24] == 0x02) {
|
||||
/* Only attempt to enable full report every 8 seconds */
|
||||
if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) {
|
||||
sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8);
|
||||
rb3_pro_instrument_enable_full_report(sc);
|
||||
}
|
||||
}
|
||||
|
||||
if (sc->defer_initialization) {
|
||||
if (unlikely(sc->defer_initialization)) {
|
||||
sc->defer_initialization = 0;
|
||||
sony_schedule_work(sc, SONY_WORKER_STATE);
|
||||
sony_schedule_work(sc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -1256,7 +1259,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
if (sc->quirks & DJH_TURNTABLE)
|
||||
return djh_turntable_mapping(hdev, hi, field, usage, bit, max);
|
||||
|
||||
if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
|
||||
if (sc->quirks & RB4_GUITAR_PS4)
|
||||
return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
|
||||
|
||||
if (sc->quirks & RB4_GUITAR_PS5)
|
||||
|
|
@ -1269,8 +1272,6 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
|
||||
int w, int h, int touch_major, int touch_minor, int orientation)
|
||||
{
|
||||
size_t name_sz;
|
||||
char *name;
|
||||
int ret;
|
||||
|
||||
sc->touchpad = devm_input_allocate_device(&sc->hdev->dev);
|
||||
|
|
@ -1292,12 +1293,10 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
|
|||
* a suffix. Other devices which were added later like Sony TV remotes
|
||||
* inhirited this suffix.
|
||||
*/
|
||||
name_sz = strlen(sc->hdev->name) + sizeof(TOUCHPAD_SUFFIX);
|
||||
name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
|
||||
if (!name)
|
||||
sc->touchpad->name = devm_kasprintf(&sc->hdev->dev, GFP_KERNEL, "%s" TOUCHPAD_SUFFIX,
|
||||
sc->hdev->name);
|
||||
if (!sc->touchpad->name)
|
||||
return -ENOMEM;
|
||||
snprintf(name, name_sz, "%s" TOUCHPAD_SUFFIX, sc->hdev->name);
|
||||
sc->touchpad->name = name;
|
||||
|
||||
/* We map the button underneath the touchpad to BTN_LEFT. */
|
||||
__set_bit(EV_KEY, sc->touchpad->evbit);
|
||||
|
|
@ -1334,8 +1333,6 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
|
|||
|
||||
static int sony_register_sensors(struct sony_sc *sc)
|
||||
{
|
||||
size_t name_sz;
|
||||
char *name;
|
||||
int ret;
|
||||
|
||||
sc->sensor_dev = devm_input_allocate_device(&sc->hdev->dev);
|
||||
|
|
@ -1354,12 +1351,10 @@ static int sony_register_sensors(struct sony_sc *sc)
|
|||
/* Append a suffix to the controller name as there are various
|
||||
* DS4 compatible non-Sony devices with different names.
|
||||
*/
|
||||
name_sz = strlen(sc->hdev->name) + sizeof(SENSOR_SUFFIX);
|
||||
name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
|
||||
if (!name)
|
||||
sc->sensor_dev->name = devm_kasprintf(&sc->hdev->dev, GFP_KERNEL, "%s" SENSOR_SUFFIX,
|
||||
sc->hdev->name);
|
||||
if (!sc->sensor_dev->name)
|
||||
return -ENOMEM;
|
||||
snprintf(name, name_sz, "%s" SENSOR_SUFFIX, sc->hdev->name);
|
||||
sc->sensor_dev->name = name;
|
||||
|
||||
if (sc->quirks & SIXAXIS_CONTROLLER) {
|
||||
/* For the DS3 we only support the accelerometer, which works
|
||||
|
|
@ -1507,7 +1502,7 @@ static void buzz_set_leds(struct sony_sc *sc)
|
|||
static void sony_set_leds(struct sony_sc *sc)
|
||||
{
|
||||
if (!(sc->quirks & BUZZ_CONTROLLER))
|
||||
sony_schedule_work(sc, SONY_WORKER_STATE);
|
||||
sony_schedule_work(sc);
|
||||
else
|
||||
buzz_set_leds(sc);
|
||||
}
|
||||
|
|
@ -1618,7 +1613,7 @@ static int sony_led_blink_set(struct led_classdev *led, unsigned long *delay_on,
|
|||
new_off != drv_data->led_delay_off[n]) {
|
||||
drv_data->led_delay_on[n] = new_on;
|
||||
drv_data->led_delay_off[n] = new_off;
|
||||
sony_schedule_work(drv_data, SONY_WORKER_STATE);
|
||||
sony_schedule_work(drv_data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -1846,7 +1841,7 @@ static int sony_play_effect(struct input_dev *dev, void *data,
|
|||
sc->left = effect->u.rumble.strong_magnitude / 256;
|
||||
sc->right = effect->u.rumble.weak_magnitude / 256;
|
||||
|
||||
sony_schedule_work(sc, SONY_WORKER_STATE);
|
||||
sony_schedule_work(sc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1869,15 +1864,14 @@ static int sony_battery_get_property(struct power_supply *psy,
|
|||
union power_supply_propval *val)
|
||||
{
|
||||
struct sony_sc *sc = power_supply_get_drvdata(psy);
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
u8 battery_capacity;
|
||||
int battery_status;
|
||||
|
||||
spin_lock_irqsave(&sc->lock, flags);
|
||||
battery_capacity = sc->battery_capacity;
|
||||
battery_status = sc->battery_status;
|
||||
spin_unlock_irqrestore(&sc->lock, flags);
|
||||
scoped_guard(spinlock_irqsave, &sc->lock) {
|
||||
battery_capacity = sc->battery_capacity;
|
||||
battery_status = sc->battery_status;
|
||||
}
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
|
|
@ -1959,10 +1953,9 @@ static inline int sony_compare_connection_type(struct sony_sc *sc0,
|
|||
static int sony_check_add_dev_list(struct sony_sc *sc)
|
||||
{
|
||||
struct sony_sc *entry;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&sony_dev_list_lock, flags);
|
||||
guard(spinlock_irqsave)(&sony_dev_list_lock);
|
||||
|
||||
list_for_each_entry(entry, &sony_device_list, list_node) {
|
||||
ret = memcmp(sc->mac_address, entry->mac_address,
|
||||
|
|
@ -1976,27 +1969,23 @@ static int sony_check_add_dev_list(struct sony_sc *sc)
|
|||
"controller with MAC address %pMR already connected\n",
|
||||
sc->mac_address);
|
||||
}
|
||||
goto unlock;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
list_add(&(sc->list_node), &sony_device_list);
|
||||
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&sony_dev_list_lock, flags);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sony_remove_dev_list(struct sony_sc *sc)
|
||||
{
|
||||
unsigned long flags;
|
||||
guard(spinlock_irqsave)(&sony_dev_list_lock);
|
||||
|
||||
if (sc->list_node.next) {
|
||||
spin_lock_irqsave(&sony_dev_list_lock, flags);
|
||||
list_del(&(sc->list_node));
|
||||
spin_unlock_irqrestore(&sony_dev_list_lock, flags);
|
||||
}
|
||||
if (!list_empty(&sc->list_node))
|
||||
list_del_init(&sc->list_node);
|
||||
}
|
||||
|
||||
static int sony_get_bt_devaddr(struct sony_sc *sc)
|
||||
|
|
@ -2110,6 +2099,12 @@ static void sony_release_device_id(struct sony_sc *sc)
|
|||
}
|
||||
}
|
||||
|
||||
static inline void sony_init_raw_event_handler(struct sony_sc *sc,
|
||||
int (*raw_event)(struct sony_sc *, u8 *, int))
|
||||
{
|
||||
sc->raw_event = raw_event;
|
||||
}
|
||||
|
||||
static inline void sony_init_output_report(struct sony_sc *sc,
|
||||
void (*send_output_report)(struct sony_sc *))
|
||||
{
|
||||
|
|
@ -2123,16 +2118,21 @@ static inline void sony_init_output_report(struct sony_sc *sc,
|
|||
|
||||
static inline void sony_cancel_work_sync(struct sony_sc *sc)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (sc->state_worker_initialized) {
|
||||
spin_lock_irqsave(&sc->lock, flags);
|
||||
sc->state_worker_initialized = 0;
|
||||
spin_unlock_irqrestore(&sc->lock, flags);
|
||||
scoped_guard(spinlock_irqsave, &sc->lock) {
|
||||
sc->state_worker_initialized = 0;
|
||||
}
|
||||
cancel_work_sync(&sc->state_worker);
|
||||
}
|
||||
}
|
||||
|
||||
static void sony_cleanup(struct sony_sc *sc)
|
||||
{
|
||||
sony_cancel_work_sync(sc);
|
||||
sony_remove_dev_list(sc);
|
||||
sony_release_device_id(sc);
|
||||
}
|
||||
|
||||
static int sony_input_configured(struct hid_device *hdev,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
|
|
@ -2185,6 +2185,7 @@ static int sony_input_configured(struct hid_device *hdev,
|
|||
goto err_stop;
|
||||
}
|
||||
|
||||
sony_init_raw_event_handler(sc, sixaxis_raw_event);
|
||||
sony_init_output_report(sc, sixaxis_send_output_report);
|
||||
} else if (sc->quirks & NAVIGATION_CONTROLLER_BT) {
|
||||
/*
|
||||
|
|
@ -2199,6 +2200,7 @@ static int sony_input_configured(struct hid_device *hdev,
|
|||
goto err_stop;
|
||||
}
|
||||
|
||||
sony_init_raw_event_handler(sc, sixaxis_raw_event);
|
||||
sony_init_output_report(sc, sixaxis_send_output_report);
|
||||
} else if (sc->quirks & RB3_PRO_INSTRUMENT) {
|
||||
/*
|
||||
|
|
@ -2213,6 +2215,8 @@ static int sony_input_configured(struct hid_device *hdev,
|
|||
*/
|
||||
hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
|
||||
hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID;
|
||||
|
||||
sony_init_raw_event_handler(sc, rb3_pro_instrument_raw_event);
|
||||
} else if (sc->quirks & SIXAXIS_CONTROLLER_USB) {
|
||||
/*
|
||||
* The Sony Sixaxis does not handle HID Output Reports on the
|
||||
|
|
@ -2237,6 +2241,7 @@ static int sony_input_configured(struct hid_device *hdev,
|
|||
goto err_stop;
|
||||
}
|
||||
|
||||
sony_init_raw_event_handler(sc, sixaxis_raw_event);
|
||||
sony_init_output_report(sc, sixaxis_send_output_report);
|
||||
} else if (sc->quirks & SIXAXIS_CONTROLLER_BT) {
|
||||
/*
|
||||
|
|
@ -2258,6 +2263,7 @@ static int sony_input_configured(struct hid_device *hdev,
|
|||
goto err_stop;
|
||||
}
|
||||
|
||||
sony_init_raw_event_handler(sc, sixaxis_raw_event);
|
||||
sony_init_output_report(sc, sixaxis_send_output_report);
|
||||
} else if (sc->quirks & NSG_MRXU_REMOTE) {
|
||||
/*
|
||||
|
|
@ -2273,8 +2279,15 @@ static int sony_input_configured(struct hid_device *hdev,
|
|||
goto err_stop;
|
||||
}
|
||||
|
||||
sony_init_raw_event_handler(sc, nsg_mrxu_raw_event);
|
||||
} else if (sc->quirks & MOTION_CONTROLLER) {
|
||||
if (sc->quirks & MOTION_CONTROLLER_BT)
|
||||
sony_init_raw_event_handler(sc, sixaxis_raw_event);
|
||||
sony_init_output_report(sc, motion_send_output_report);
|
||||
} else if (sc->quirks & RB4_GUITAR_PS4) {
|
||||
sony_init_raw_event_handler(sc, rb4_ps4_guitar_raw_event);
|
||||
} else if (sc->quirks & RB4_GUITAR_PS5) {
|
||||
sony_init_raw_event_handler(sc, rb4_ps5_guitar_raw_event);
|
||||
}
|
||||
|
||||
if (sc->quirks & SONY_LED_SUPPORT) {
|
||||
|
|
@ -2306,9 +2319,7 @@ static int sony_input_configured(struct hid_device *hdev,
|
|||
err_close:
|
||||
hid_hw_close(hdev);
|
||||
err_stop:
|
||||
sony_cancel_work_sync(sc);
|
||||
sony_remove_dev_list(sc);
|
||||
sony_release_device_id(sc);
|
||||
sony_cleanup(sc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2332,6 +2343,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&sc->lock);
|
||||
INIT_LIST_HEAD(&sc->list_node);
|
||||
sc->device_id = -1;
|
||||
|
||||
sc->quirks = quirks;
|
||||
hid_set_drvdata(hdev, sc);
|
||||
|
|
@ -2360,6 +2373,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
ret = hid_hw_start(hdev, connect_mask);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
sony_cleanup(sc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2414,7 +2428,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
|
||||
err:
|
||||
usb_free_urb(sc->ghl_urb);
|
||||
|
||||
sony_cleanup(sc);
|
||||
hid_hw_stop(hdev);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -2424,18 +2438,14 @@ static void sony_remove(struct hid_device *hdev)
|
|||
struct sony_sc *sc = hid_get_drvdata(hdev);
|
||||
|
||||
if (sc->quirks & (GHL_GUITAR_PS3WIIU | GHL_GUITAR_PS4)) {
|
||||
timer_delete_sync(&sc->ghl_poke_timer);
|
||||
/* poison, not kill: a pending timer must not re-submit during teardown */
|
||||
usb_poison_urb(sc->ghl_urb);
|
||||
timer_shutdown_sync(&sc->ghl_poke_timer);
|
||||
usb_free_urb(sc->ghl_urb);
|
||||
}
|
||||
|
||||
hid_hw_close(hdev);
|
||||
|
||||
sony_cancel_work_sync(sc);
|
||||
|
||||
sony_remove_dev_list(sc);
|
||||
|
||||
sony_release_device_id(sc);
|
||||
|
||||
sony_cleanup(sc);
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
718
drivers/hid/hid-steelseries-arctis.c
Normal file
718
drivers/hid/hid-steelseries-arctis.c
Normal file
|
|
@ -0,0 +1,718 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HID driver for Steelseries arctis headsets
|
||||
*
|
||||
* Copyright (c) 2023 Bastien Nocera
|
||||
* Copyright (c) 2026 Sriman Achanta
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#define SS_CAP_BATTERY BIT(0)
|
||||
|
||||
struct steelseries_device;
|
||||
|
||||
struct steelseries_device_info {
|
||||
unsigned long capabilities;
|
||||
|
||||
u8 sync_interface;
|
||||
u8 async_interface;
|
||||
|
||||
int (*request_status)(struct hid_device *hdev);
|
||||
void (*parse_status)(struct steelseries_device *sd, u8 *data, int size);
|
||||
};
|
||||
|
||||
struct steelseries_device {
|
||||
struct kref refcnt;
|
||||
|
||||
struct hid_device *hdev;
|
||||
const struct steelseries_device_info *info;
|
||||
|
||||
struct delayed_work status_work;
|
||||
|
||||
struct power_supply_desc battery_desc;
|
||||
struct power_supply *battery;
|
||||
bool headset_connected;
|
||||
u8 battery_capacity;
|
||||
bool battery_charging;
|
||||
|
||||
spinlock_t lock;
|
||||
bool removed;
|
||||
};
|
||||
|
||||
static void steelseries_device_release(struct kref *ref)
|
||||
{
|
||||
struct steelseries_device *sd =
|
||||
container_of(ref, struct steelseries_device, refcnt);
|
||||
|
||||
kfree(sd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Headset report helpers
|
||||
*/
|
||||
|
||||
static int steelseries_send_report(struct hid_device *hdev, const u8 *data,
|
||||
int len, enum hid_report_type type)
|
||||
{
|
||||
u8 *buf;
|
||||
int ret;
|
||||
|
||||
buf = kmemdup(data, len, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_hw_raw_request(hdev, data[0], buf, len, type,
|
||||
HID_REQ_SET_REPORT);
|
||||
kfree(buf);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret < len)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int steelseries_send_output_report(struct hid_device *hdev,
|
||||
const u8 *data, int len)
|
||||
{
|
||||
return steelseries_send_report(hdev, data, len, HID_OUTPUT_REPORT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Headset status request functions
|
||||
*/
|
||||
|
||||
static int steelseries_arctis_1_request_status(struct hid_device *hdev)
|
||||
{
|
||||
const u8 data[] = { 0x06, 0x12 };
|
||||
|
||||
return steelseries_send_output_report(hdev, data, sizeof(data));
|
||||
}
|
||||
|
||||
static int steelseries_arctis_9_request_status(struct hid_device *hdev)
|
||||
{
|
||||
const u8 data[] = { 0x00, 0x20 };
|
||||
|
||||
return steelseries_send_output_report(hdev, data, sizeof(data));
|
||||
}
|
||||
|
||||
static int steelseries_arctis_nova_request_status(struct hid_device *hdev)
|
||||
{
|
||||
const u8 data[] = { 0x00, 0xb0 };
|
||||
|
||||
return steelseries_send_output_report(hdev, data, sizeof(data));
|
||||
}
|
||||
|
||||
/*
|
||||
* Headset battery helpers
|
||||
*/
|
||||
|
||||
static int battery_capacity_to_level(int capacity)
|
||||
{
|
||||
if (capacity >= 50)
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
|
||||
if (capacity >= 20)
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
||||
}
|
||||
|
||||
static u8 steelseries_map_capacity(u8 capacity, u8 min_in, u8 max_in)
|
||||
{
|
||||
if (capacity >= max_in)
|
||||
return 100;
|
||||
if (capacity <= min_in)
|
||||
return 0;
|
||||
return (capacity - min_in) * 100 / (max_in - min_in);
|
||||
}
|
||||
|
||||
/*
|
||||
* Headset status parse functions
|
||||
*/
|
||||
|
||||
static void steelseries_arctis_1_parse_status(struct steelseries_device *sd,
|
||||
u8 *data, int size)
|
||||
{
|
||||
/* Only the battery status report echoes the request header. */
|
||||
if (size < 8 || data[0] != 0x06 || data[1] != 0x12)
|
||||
return;
|
||||
|
||||
sd->headset_connected = (data[2] != 0x01);
|
||||
sd->battery_capacity = data[3];
|
||||
}
|
||||
|
||||
static void steelseries_arctis_9_parse_status(struct steelseries_device *sd,
|
||||
u8 *data, int size)
|
||||
{
|
||||
if (size < 5)
|
||||
return;
|
||||
|
||||
if (data[0] == 0xaa && data[1] == 0x01) {
|
||||
sd->headset_connected = true;
|
||||
sd->battery_charging = (data[4] == 0x01);
|
||||
sd->battery_capacity = steelseries_map_capacity(data[3], 0x64, 0x9a);
|
||||
} else {
|
||||
/* Device off: 0x55 (no status) or 0x03 (stale status). */
|
||||
sd->headset_connected = false;
|
||||
sd->battery_charging = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void steelseries_arctis_nova_parse_status(struct steelseries_device *sd,
|
||||
u8 *data, int size)
|
||||
{
|
||||
if (size < 2)
|
||||
return;
|
||||
|
||||
switch (data[0]) {
|
||||
case 0xb0:
|
||||
if (size < 4)
|
||||
return;
|
||||
sd->headset_connected = (data[1] == 0x03);
|
||||
sd->battery_capacity = data[2];
|
||||
sd->battery_charging = (data[3] == 0x01);
|
||||
break;
|
||||
case 0xb7:
|
||||
sd->battery_capacity = data[1];
|
||||
break;
|
||||
case 0xb9:
|
||||
sd->headset_connected = (data[1] == 0x03);
|
||||
break;
|
||||
case 0xbb:
|
||||
sd->battery_charging = (data[1] == 0x01);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void steelseries_arctis_nova_7_parse_status(struct steelseries_device *sd,
|
||||
u8 *data, int size)
|
||||
{
|
||||
if (size < 2)
|
||||
return;
|
||||
|
||||
switch (data[0]) {
|
||||
case 0xb0:
|
||||
if (size < 4)
|
||||
return;
|
||||
sd->headset_connected = (data[1] == 0x03);
|
||||
sd->battery_capacity = steelseries_map_capacity(data[2], 0, 4);
|
||||
sd->battery_charging = (data[3] == 0x01);
|
||||
break;
|
||||
case 0xb7:
|
||||
sd->battery_capacity = steelseries_map_capacity(data[1], 0, 4);
|
||||
break;
|
||||
case 0xb9:
|
||||
sd->headset_connected = (data[1] == 0x03);
|
||||
break;
|
||||
case 0xbb:
|
||||
sd->battery_charging = (data[1] == 0x01);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Device info definitions
|
||||
*/
|
||||
|
||||
static const struct steelseries_device_info arctis_1_info = {
|
||||
.sync_interface = 3,
|
||||
.capabilities = SS_CAP_BATTERY,
|
||||
.request_status = steelseries_arctis_1_request_status,
|
||||
.parse_status = steelseries_arctis_1_parse_status,
|
||||
};
|
||||
|
||||
static const struct steelseries_device_info arctis_9_info = {
|
||||
.sync_interface = 0,
|
||||
.capabilities = SS_CAP_BATTERY,
|
||||
.request_status = steelseries_arctis_9_request_status,
|
||||
.parse_status = steelseries_arctis_9_parse_status,
|
||||
};
|
||||
|
||||
static const struct steelseries_device_info arctis_nova_info = {
|
||||
.sync_interface = 3,
|
||||
.async_interface = 5,
|
||||
.capabilities = SS_CAP_BATTERY,
|
||||
.request_status = steelseries_arctis_nova_request_status,
|
||||
.parse_status = steelseries_arctis_nova_parse_status,
|
||||
};
|
||||
|
||||
static const struct steelseries_device_info arctis_nova_7_info = {
|
||||
.sync_interface = 3,
|
||||
.async_interface = 5,
|
||||
.capabilities = SS_CAP_BATTERY,
|
||||
.request_status = steelseries_arctis_nova_request_status,
|
||||
.parse_status = steelseries_arctis_nova_7_parse_status,
|
||||
};
|
||||
|
||||
/*
|
||||
* Headset wireless status and battery infrastructure
|
||||
*/
|
||||
|
||||
#define STEELSERIES_HEADSET_STATUS_TIMEOUT_MS 3000
|
||||
|
||||
static void
|
||||
steelseries_headset_set_wireless_status(struct hid_device *hdev,
|
||||
bool connected)
|
||||
{
|
||||
struct usb_interface *intf;
|
||||
|
||||
if (!hid_is_usb(hdev))
|
||||
return;
|
||||
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
usb_set_wireless_status(intf, connected ?
|
||||
USB_WIRELESS_STATUS_CONNECTED :
|
||||
USB_WIRELESS_STATUS_DISCONNECTED);
|
||||
}
|
||||
|
||||
#define STEELSERIES_PREFIX "SteelSeries "
|
||||
|
||||
static int steelseries_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct steelseries_device *sd = power_supply_get_drvdata(psy);
|
||||
size_t prefix_len;
|
||||
int ret = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||||
val->strval = sd->hdev->name;
|
||||
while ((prefix_len = str_has_prefix(val->strval, STEELSERIES_PREFIX)))
|
||||
val->strval += prefix_len;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||
val->strval = "SteelSeries";
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = 1;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (!sd->headset_connected)
|
||||
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
else if (sd->battery_charging)
|
||||
val->intval = sd->battery_capacity >= 100 ?
|
||||
POWER_SUPPLY_STATUS_FULL :
|
||||
POWER_SUPPLY_STATUS_CHARGING;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SCOPE:
|
||||
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
val->intval = sd->battery_capacity;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
||||
val->intval = battery_capacity_to_level(sd->battery_capacity);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum power_supply_property steelseries_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_MODEL_NAME,
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
POWER_SUPPLY_PROP_PRESENT,
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_SCOPE,
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
||||
};
|
||||
|
||||
/*
|
||||
* Delayed work handlers for status polling
|
||||
*/
|
||||
|
||||
static void steelseries_status_timer_work_handler(struct work_struct *work)
|
||||
{
|
||||
struct steelseries_device *sd = container_of(
|
||||
work, struct steelseries_device, status_work.work);
|
||||
unsigned long flags;
|
||||
|
||||
sd->info->request_status(sd->hdev);
|
||||
|
||||
spin_lock_irqsave(&sd->lock, flags);
|
||||
/* Async devices push status events themselves; only poll once. */
|
||||
if (!sd->removed && !sd->info->async_interface)
|
||||
schedule_delayed_work(&sd->status_work,
|
||||
msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS));
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
}
|
||||
|
||||
static int steelseries_battery_register(struct steelseries_device *sd)
|
||||
{
|
||||
struct power_supply_config battery_cfg = { .drv_data = sd, };
|
||||
struct power_supply *battery;
|
||||
int ret;
|
||||
|
||||
sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
sd->battery_desc.properties = steelseries_battery_props;
|
||||
sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props);
|
||||
sd->battery_desc.get_property = steelseries_battery_get_property;
|
||||
sd->battery_desc.use_for_apm = 0;
|
||||
sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
|
||||
"steelseries_headset_battery_%s",
|
||||
sd->hdev->uniq[0] ? sd->hdev->uniq :
|
||||
dev_name(&sd->hdev->dev));
|
||||
if (!sd->battery_desc.name)
|
||||
return -ENOMEM;
|
||||
|
||||
/* avoid the warning of 0% battery while waiting for the first info */
|
||||
sd->battery_capacity = 100;
|
||||
sd->battery_charging = false;
|
||||
sd->headset_connected = false;
|
||||
steelseries_headset_set_wireless_status(sd->hdev, false);
|
||||
|
||||
battery = power_supply_register(&sd->hdev->dev,
|
||||
&sd->battery_desc, &battery_cfg);
|
||||
if (IS_ERR(battery)) {
|
||||
ret = PTR_ERR(battery);
|
||||
hid_err(sd->hdev,
|
||||
"%s:power_supply_register failed with error %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
power_supply_powers(battery, &sd->hdev->dev);
|
||||
|
||||
/* Assign on success only, so a concurrent raw_event never sees an ERR_PTR. */
|
||||
sd->battery = battery;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct hid_driver steelseries_arctis_driver;
|
||||
|
||||
static struct steelseries_device *
|
||||
steelseries_get_sibling_sd(struct hid_device *hdev, int interface_num)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
struct usb_device *usb_dev = interface_to_usbdev(intf);
|
||||
struct usb_interface *sibling_intf;
|
||||
struct hid_device *sibling_hdev;
|
||||
struct steelseries_device *sd = NULL;
|
||||
|
||||
sibling_intf = usb_ifnum_to_if(usb_dev, interface_num);
|
||||
if (!sibling_intf)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* usb_get_intfdata() only yields a hid_device when usbhid is bound;
|
||||
* gate on the descriptor class so a non-HID sibling (e.g. a crafted
|
||||
* device exposing storage or audio here) is never treated as one.
|
||||
*/
|
||||
if (sibling_intf->cur_altsetting->desc.bInterfaceClass != USB_INTERFACE_CLASS_HID)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Take the sibling's device lock across the intfdata read and the
|
||||
* kref_get so a concurrent unbind cannot free the hid_device underneath
|
||||
* us; usbhid leaves intfdata dangling on disconnect, so dev.driver is
|
||||
* the reliable "still bound" test under this lock. Use device_trylock()
|
||||
* to stay off the lockdep chain of the interface being probed and let
|
||||
* the caller retry via -EPROBE_DEFER if the sibling is momentarily busy.
|
||||
*/
|
||||
if (!device_trylock(&sibling_intf->dev))
|
||||
return NULL;
|
||||
if (sibling_intf->dev.driver) {
|
||||
sibling_hdev = usb_get_intfdata(sibling_intf);
|
||||
if (sibling_hdev &&
|
||||
sibling_hdev->driver == &steelseries_arctis_driver) {
|
||||
sd = hid_get_drvdata(sibling_hdev);
|
||||
if (sd)
|
||||
kref_get(&sd->refcnt);
|
||||
}
|
||||
}
|
||||
device_unlock(&sibling_intf->dev);
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
static int steelseries_arctis_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
const struct steelseries_device_info *info =
|
||||
(const struct steelseries_device_info *)id->driver_data;
|
||||
struct steelseries_device *sd;
|
||||
struct usb_interface *intf;
|
||||
u8 interface_num;
|
||||
int ret;
|
||||
|
||||
if (hid_is_usb(hdev)) {
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
|
||||
} else {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Let hid-generic handle non-vendor or unknown interfaces */
|
||||
if (interface_num != info->sync_interface &&
|
||||
(!info->async_interface || interface_num != info->async_interface))
|
||||
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
|
||||
if (interface_num == info->sync_interface) {
|
||||
sd = kzalloc_obj(*sd, GFP_KERNEL);
|
||||
if (!sd)
|
||||
return -ENOMEM;
|
||||
|
||||
kref_init(&sd->refcnt);
|
||||
sd->hdev = hdev;
|
||||
sd->info = info;
|
||||
spin_lock_init(&sd->lock);
|
||||
INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler);
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
ret = hid_hw_open(hdev);
|
||||
if (ret)
|
||||
goto err_stop;
|
||||
|
||||
if (info->capabilities & SS_CAP_BATTERY) {
|
||||
ret = steelseries_battery_register(sd);
|
||||
if (ret < 0)
|
||||
hid_warn(hdev, "Failed to register battery: %d\n", ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Publish drvdata only once fully initialised: the async sibling
|
||||
* attaches by reading it, so it must never observe a half-built or
|
||||
* failed instance. A failed probe never gets here, so the error
|
||||
* path below has nothing to unpublish.
|
||||
*/
|
||||
hid_set_drvdata(hdev, sd);
|
||||
schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The async interface shares the steelseries_device created by the
|
||||
* sync interface. Defer until the sync interface has probed and
|
||||
* published its drvdata.
|
||||
*/
|
||||
if (info->async_interface && interface_num == info->async_interface) {
|
||||
sd = steelseries_get_sibling_sd(hdev, info->sync_interface);
|
||||
if (!sd)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
hid_set_drvdata(hdev, sd);
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
kref_put(&sd->refcnt, steelseries_device_release);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_open(hdev);
|
||||
if (ret) {
|
||||
hid_hw_stop(hdev);
|
||||
kref_put(&sd->refcnt, steelseries_device_release);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
|
||||
err_stop:
|
||||
hid_hw_stop(hdev);
|
||||
err_free:
|
||||
/* drvdata is unpublished until full success, so no sibling can hold sd. */
|
||||
kref_put(&sd->refcnt, steelseries_device_release);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steelseries_arctis_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct steelseries_device *sd;
|
||||
struct power_supply *battery;
|
||||
unsigned long flags;
|
||||
struct usb_interface *intf;
|
||||
u8 interface_num;
|
||||
|
||||
if (hid_is_usb(hdev)) {
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
sd = hid_get_drvdata(hdev);
|
||||
|
||||
if (!sd) {
|
||||
hid_hw_stop(hdev);
|
||||
return;
|
||||
}
|
||||
|
||||
if (interface_num == sd->info->sync_interface) {
|
||||
spin_lock_irqsave(&sd->lock, flags);
|
||||
sd->removed = true;
|
||||
battery = sd->battery;
|
||||
sd->battery = NULL;
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
|
||||
cancel_delayed_work_sync(&sd->status_work);
|
||||
|
||||
if (battery)
|
||||
power_supply_unregister(battery);
|
||||
}
|
||||
|
||||
hid_hw_close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
|
||||
kref_put(&sd->refcnt, steelseries_device_release);
|
||||
}
|
||||
|
||||
static int steelseries_arctis_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *data, int size)
|
||||
{
|
||||
struct steelseries_device *sd = hid_get_drvdata(hdev);
|
||||
u8 old_capacity;
|
||||
bool old_connected;
|
||||
bool old_charging;
|
||||
bool is_async_interface;
|
||||
unsigned long flags;
|
||||
|
||||
if (!sd)
|
||||
return 0;
|
||||
|
||||
is_async_interface = (hdev != sd->hdev);
|
||||
|
||||
spin_lock_irqsave(&sd->lock, flags);
|
||||
|
||||
if (sd->removed) {
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
old_capacity = sd->battery_capacity;
|
||||
old_connected = sd->headset_connected;
|
||||
old_charging = sd->battery_charging;
|
||||
|
||||
sd->info->parse_status(sd, data, size);
|
||||
|
||||
if (sd->headset_connected != old_connected) {
|
||||
hid_dbg(hdev,
|
||||
"Connected status changed from %sconnected to %sconnected\n",
|
||||
old_connected ? "" : "not ",
|
||||
sd->headset_connected ? "" : "not ");
|
||||
|
||||
if (sd->headset_connected && !old_connected &&
|
||||
sd->info->async_interface && is_async_interface)
|
||||
schedule_delayed_work(&sd->status_work, 0);
|
||||
|
||||
if (sd->battery) {
|
||||
steelseries_headset_set_wireless_status(sd->hdev,
|
||||
sd->headset_connected);
|
||||
power_supply_changed(sd->battery);
|
||||
}
|
||||
}
|
||||
|
||||
if (sd->battery_capacity != old_capacity) {
|
||||
hid_dbg(hdev, "Battery capacity changed from %d%% to %d%%\n",
|
||||
old_capacity, sd->battery_capacity);
|
||||
if (sd->battery)
|
||||
power_supply_changed(sd->battery);
|
||||
}
|
||||
|
||||
if (sd->battery_charging != old_charging) {
|
||||
hid_dbg(hdev,
|
||||
"Battery charging status changed from %scharging to %scharging\n",
|
||||
old_charging ? "" : "not ",
|
||||
sd->battery_charging ? "" : "not ");
|
||||
if (sd->battery)
|
||||
power_supply_changed(sd->battery);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hid_device_id steelseries_arctis_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
|
||||
.driver_data = (unsigned long)&arctis_1_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
|
||||
.driver_data = (unsigned long)&arctis_9_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7),
|
||||
.driver_data = (unsigned long)&arctis_nova_7_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X),
|
||||
.driver_data = (unsigned long)&arctis_nova_7_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2),
|
||||
.driver_data = (unsigned long)&arctis_nova_7_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO),
|
||||
.driver_data = (unsigned long)&arctis_nova_7_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW),
|
||||
.driver_data = (unsigned long)&arctis_nova_7_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
|
||||
USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3),
|
||||
.driver_data = (unsigned long)&arctis_nova_info },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices);
|
||||
|
||||
static struct hid_driver steelseries_arctis_driver = {
|
||||
.name = "hid-steelseries-arctis",
|
||||
.id_table = steelseries_arctis_devices,
|
||||
.probe = steelseries_arctis_probe,
|
||||
.remove = steelseries_arctis_remove,
|
||||
.raw_event = steelseries_arctis_raw_event,
|
||||
};
|
||||
|
||||
module_hid_driver(steelseries_arctis_driver);
|
||||
MODULE_DESCRIPTION("HID driver for Steelseries arctis headsets");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
|
||||
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
|
||||
MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>");
|
||||
|
|
@ -3,37 +3,45 @@
|
|||
* HID driver for Steelseries devices
|
||||
*
|
||||
* Copyright (c) 2013 Simon Wood
|
||||
* Copyright (c) 2023 Bastien Nocera
|
||||
*/
|
||||
|
||||
/*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/led-class-multicolor.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#define STEELSERIES_SRWS1 BIT(0)
|
||||
#define STEELSERIES_ARCTIS_1 BIT(1)
|
||||
#define STEELSERIES_ARCTIS_9 BIT(2)
|
||||
#define STEELSERIES_MSI_RGB BIT(1)
|
||||
|
||||
#define STEELSERIES_MSI_RGB_WVALUE 0x0300 /* Feature report, ID 0 */
|
||||
#define STEELSERIES_MSI_RGB_REPORT_LEN 524
|
||||
#define STEELSERIES_MSI_RGB_OPCODE 0x0c
|
||||
#define STEELSERIES_MSI_RGB_KLC_MODE 0x66
|
||||
#define STEELSERIES_MSI_RGB_ALC_MODE 0x06
|
||||
|
||||
#define STEELSERIES_HAS_LEDS_MULTICOLOR \
|
||||
(IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
|
||||
(IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
|
||||
|
||||
struct steelseries_device {
|
||||
struct hid_device *hdev;
|
||||
unsigned long quirks;
|
||||
|
||||
struct delayed_work battery_work;
|
||||
spinlock_t lock;
|
||||
bool removed;
|
||||
|
||||
struct power_supply_desc battery_desc;
|
||||
struct power_supply *battery;
|
||||
uint8_t battery_capacity;
|
||||
bool headset_connected;
|
||||
bool battery_charging;
|
||||
#if STEELSERIES_HAS_LEDS_MULTICOLOR
|
||||
struct led_classdev_mc mc_cdev;
|
||||
struct mc_subled subled_info[3];
|
||||
struct mutex rgb_lock; /* protects rgb_buf */
|
||||
u8 *rgb_buf;
|
||||
#endif
|
||||
};
|
||||
|
||||
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
||||
|
|
@ -340,193 +348,221 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
|
|||
}
|
||||
#endif
|
||||
|
||||
#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
|
||||
|
||||
#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
|
||||
#define ARCTIS_9_BATTERY_RESPONSE_LEN 64
|
||||
static const char arctis_1_battery_request[] = { 0x06, 0x12 };
|
||||
static const char arctis_9_battery_request[] = { 0x00, 0x20 };
|
||||
|
||||
static int steelseries_headset_request_battery(struct hid_device *hdev,
|
||||
const char *request, size_t len)
|
||||
{
|
||||
u8 *write_buf;
|
||||
int ret;
|
||||
|
||||
/* Request battery information */
|
||||
write_buf = kmemdup(request, len, GFP_KERNEL);
|
||||
if (!write_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
hid_dbg(hdev, "Sending battery request report");
|
||||
ret = hid_hw_raw_request(hdev, request[0], write_buf, len,
|
||||
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
|
||||
if (ret < (int)len) {
|
||||
hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
|
||||
ret = -ENODATA;
|
||||
}
|
||||
|
||||
kfree(write_buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steelseries_headset_fetch_battery(struct hid_device *hdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1)
|
||||
ret = steelseries_headset_request_battery(hdev,
|
||||
arctis_1_battery_request, sizeof(arctis_1_battery_request));
|
||||
else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
|
||||
ret = steelseries_headset_request_battery(hdev,
|
||||
arctis_9_battery_request, sizeof(arctis_9_battery_request));
|
||||
|
||||
if (ret < 0)
|
||||
hid_dbg(hdev,
|
||||
"Battery query failed (err: %d)\n", ret);
|
||||
}
|
||||
|
||||
static int battery_capacity_to_level(int capacity)
|
||||
{
|
||||
if (capacity >= 50)
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
|
||||
if (capacity >= 20)
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
|
||||
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
||||
}
|
||||
|
||||
static void steelseries_headset_battery_timer_tick(struct work_struct *work)
|
||||
{
|
||||
struct steelseries_device *sd = container_of(work,
|
||||
struct steelseries_device, battery_work.work);
|
||||
struct hid_device *hdev = sd->hdev;
|
||||
|
||||
steelseries_headset_fetch_battery(hdev);
|
||||
}
|
||||
|
||||
#define STEELSERIES_PREFIX "SteelSeries "
|
||||
#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
|
||||
|
||||
static int steelseries_headset_battery_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct steelseries_device *sd = power_supply_get_drvdata(psy);
|
||||
int ret = 0;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||||
val->strval = sd->hdev->name;
|
||||
while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN))
|
||||
val->strval += STEELSERIES_PREFIX_LEN;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||
val->strval = "SteelSeries";
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = 1;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (sd->headset_connected) {
|
||||
val->intval = sd->battery_charging ?
|
||||
POWER_SUPPLY_STATUS_CHARGING :
|
||||
POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
} else
|
||||
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_SCOPE:
|
||||
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
val->intval = sd->battery_capacity;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
||||
val->intval = battery_capacity_to_level(sd->battery_capacity);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
steelseries_headset_set_wireless_status(struct hid_device *hdev,
|
||||
bool connected)
|
||||
{
|
||||
struct usb_interface *intf;
|
||||
|
||||
if (!hid_is_usb(hdev))
|
||||
return;
|
||||
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
usb_set_wireless_status(intf, connected ?
|
||||
USB_WIRELESS_STATUS_CONNECTED :
|
||||
USB_WIRELESS_STATUS_DISCONNECTED);
|
||||
}
|
||||
|
||||
static enum power_supply_property steelseries_headset_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_MODEL_NAME,
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
POWER_SUPPLY_PROP_PRESENT,
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_SCOPE,
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
||||
static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
static int steelseries_headset_battery_register(struct steelseries_device *sd)
|
||||
static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
|
||||
{
|
||||
static atomic_t battery_no = ATOMIC_INIT(0);
|
||||
struct power_supply_config battery_cfg = { .drv_data = sd, };
|
||||
unsigned long n;
|
||||
if (!hid_is_usb(hdev))
|
||||
return NULL;
|
||||
|
||||
return to_usb_interface(hdev->dev.parent);
|
||||
}
|
||||
|
||||
static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
|
||||
{
|
||||
struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
|
||||
struct usb_device *udev;
|
||||
|
||||
if (!intf)
|
||||
return false;
|
||||
|
||||
udev = interface_to_usbdev(intf);
|
||||
|
||||
return intf == usb_ifnum_to_if(udev, 0);
|
||||
}
|
||||
|
||||
#if STEELSERIES_HAS_LEDS_MULTICOLOR
|
||||
|
||||
static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
|
||||
{
|
||||
struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
|
||||
|
||||
if (!intf)
|
||||
return NULL;
|
||||
|
||||
return interface_to_usbdev(intf);
|
||||
}
|
||||
|
||||
static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
|
||||
struct steelseries_device *sd = container_of(mc_cdev,
|
||||
struct steelseries_device,
|
||||
mc_cdev);
|
||||
struct hid_device *hdev = sd->hdev;
|
||||
struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
|
||||
int i, ret;
|
||||
u8 r, g, b;
|
||||
|
||||
static const u8 keys[] = {
|
||||
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
||||
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
|
||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
|
||||
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
|
||||
0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
|
||||
0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
|
||||
0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
|
||||
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
|
||||
};
|
||||
static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03 };
|
||||
|
||||
if (!udev)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&sd->rgb_lock);
|
||||
|
||||
led_mc_calc_color_components(mc_cdev, brightness);
|
||||
|
||||
r = mc_cdev->subled_info[0].brightness;
|
||||
g = mc_cdev->subled_info[1].brightness;
|
||||
b = mc_cdev->subled_info[2].brightness;
|
||||
|
||||
/*
|
||||
* Report layout (524 bytes):
|
||||
* Byte 0: Opcode (0x0c)
|
||||
* Byte 1: 0x00
|
||||
* Byte 2: Mode (0x66 for Keyboard, 0x06 for Lightbar)
|
||||
* Byte 3: 0x00
|
||||
* Bytes 4+: 4-byte chunks per LED (Index, R, G, B)
|
||||
*/
|
||||
memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
|
||||
sd->rgb_buf[0] = STEELSERIES_MSI_RGB_OPCODE;
|
||||
sd->rgb_buf[1] = 0x00;
|
||||
sd->rgb_buf[3] = 0x00;
|
||||
|
||||
for (i = 0; i < (STEELSERIES_MSI_RGB_REPORT_LEN - 4) / 4; i++)
|
||||
sd->rgb_buf[4 + i * 4] = 0xff;
|
||||
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
|
||||
sd->rgb_buf[2] = STEELSERIES_MSI_RGB_KLC_MODE;
|
||||
for (i = 0; i < ARRAY_SIZE(keys); i++) {
|
||||
sd->rgb_buf[4 + i * 4] = keys[i];
|
||||
sd->rgb_buf[5 + i * 4] = r;
|
||||
sd->rgb_buf[6 + i * 4] = g;
|
||||
sd->rgb_buf[7 + i * 4] = b;
|
||||
}
|
||||
} else {
|
||||
sd->rgb_buf[2] = STEELSERIES_MSI_RGB_ALC_MODE;
|
||||
for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
|
||||
sd->rgb_buf[4 + i * 4] = alc_zones[i];
|
||||
sd->rgb_buf[5 + i * 4] = r;
|
||||
sd->rgb_buf[6 + i * 4] = g;
|
||||
sd->rgb_buf[7 + i * 4] = b;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Send the vendor report verbatim with usb_control_msg(): byte 0 is a
|
||||
* protocol opcode (0x0c), not a HID report ID, and the controller
|
||||
* expects it under report ID 0 (wValue 0x0300). hid_hw_raw_request()
|
||||
* would write the report number into byte 0, so the direct control
|
||||
* transfer is used to keep the payload byte-identical to the tested
|
||||
* userspace implementation.
|
||||
*/
|
||||
ret = hid_hw_power(hdev, PM_HINT_FULLON);
|
||||
if (ret < 0)
|
||||
goto out_unlock;
|
||||
|
||||
ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
|
||||
HID_REQ_SET_REPORT,
|
||||
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
|
||||
STEELSERIES_MSI_RGB_WVALUE, 0,
|
||||
sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
|
||||
USB_CTRL_SET_TIMEOUT);
|
||||
|
||||
hid_hw_power(hdev, PM_HINT_NORMAL);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&sd->rgb_lock);
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
static void steelseries_msi_rgb_free_buf(void *data)
|
||||
{
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
static int steelseries_msi_rgb_register(struct steelseries_device *sd)
|
||||
{
|
||||
struct hid_device *hdev = sd->hdev;
|
||||
struct led_classdev *led_cdev;
|
||||
int ret;
|
||||
|
||||
sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
sd->battery_desc.properties = steelseries_headset_battery_props;
|
||||
sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props);
|
||||
sd->battery_desc.get_property = steelseries_headset_battery_get_property;
|
||||
sd->battery_desc.use_for_apm = 0;
|
||||
n = atomic_inc_return(&battery_no) - 1;
|
||||
sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
|
||||
"steelseries_headset_battery_%ld", n);
|
||||
if (!sd->battery_desc.name)
|
||||
sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
|
||||
if (!sd->rgb_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* avoid the warning of 0% battery while waiting for the first info */
|
||||
steelseries_headset_set_wireless_status(sd->hdev, false);
|
||||
sd->battery_capacity = 100;
|
||||
sd->battery_charging = false;
|
||||
|
||||
sd->battery = devm_power_supply_register(&sd->hdev->dev,
|
||||
&sd->battery_desc, &battery_cfg);
|
||||
if (IS_ERR(sd->battery)) {
|
||||
ret = PTR_ERR(sd->battery);
|
||||
hid_err(sd->hdev,
|
||||
"%s:power_supply_register failed with error %d\n",
|
||||
__func__, ret);
|
||||
ret = devm_add_action_or_reset(&hdev->dev,
|
||||
steelseries_msi_rgb_free_buf,
|
||||
sd->rgb_buf);
|
||||
if (ret) {
|
||||
sd->rgb_buf = NULL;
|
||||
return ret;
|
||||
}
|
||||
power_supply_powers(sd->battery, &sd->hdev->dev);
|
||||
|
||||
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
|
||||
steelseries_headset_fetch_battery(sd->hdev);
|
||||
ret = devm_mutex_init(&hdev->dev, &sd->rgb_lock);
|
||||
if (ret) {
|
||||
devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
|
||||
sd->rgb_buf);
|
||||
kfree(sd->rgb_buf);
|
||||
sd->rgb_buf = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (sd->quirks & STEELSERIES_ARCTIS_9) {
|
||||
/* The first fetch_battery request can remain unanswered in some cases */
|
||||
schedule_delayed_work(&sd->battery_work,
|
||||
msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
|
||||
sd->subled_info[0].color_index = LED_COLOR_ID_RED;
|
||||
sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
|
||||
sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
|
||||
sd->subled_info[0].intensity = 255;
|
||||
sd->subled_info[1].intensity = 255;
|
||||
sd->subled_info[2].intensity = 255;
|
||||
sd->subled_info[0].channel = 0;
|
||||
sd->subled_info[1].channel = 1;
|
||||
sd->subled_info[2].channel = 2;
|
||||
|
||||
sd->mc_cdev.subled_info = sd->subled_info;
|
||||
sd->mc_cdev.num_colors = 3;
|
||||
|
||||
led_cdev = &sd->mc_cdev.led_cdev;
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
|
||||
led_cdev->name = "steelseries::kbd_backlight";
|
||||
else
|
||||
led_cdev->name = "steelseries::lightbar";
|
||||
|
||||
led_cdev->max_brightness = 255;
|
||||
led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
|
||||
|
||||
ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
|
||||
if (ret) {
|
||||
devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
|
||||
sd->rgb_buf);
|
||||
kfree(sd->rgb_buf);
|
||||
sd->rgb_buf = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t usage_page)
|
||||
#else
|
||||
static int steelseries_msi_rgb_register(struct steelseries_device *sd)
|
||||
{
|
||||
return hdev->rdesc[0] == 0x06 &&
|
||||
hdev->rdesc[1] == usage_page &&
|
||||
hdev->rdesc[2] == 0xff;
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
|
|
@ -549,36 +585,45 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
|
|||
sd->hdev = hdev;
|
||||
sd->quirks = id->driver_data;
|
||||
|
||||
if (sd->quirks & STEELSERIES_MSI_RGB) {
|
||||
if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
|
||||
!steelseries_msi_rgb_is_interface0(hdev)) {
|
||||
hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
|
||||
sd->quirks &= ~STEELSERIES_MSI_RGB;
|
||||
}
|
||||
}
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (sd->quirks & STEELSERIES_ARCTIS_9 &&
|
||||
!steelseries_is_vendor_usage_page(hdev, 0xc0))
|
||||
return -ENODEV;
|
||||
|
||||
spin_lock_init(&sd->lock);
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hid_hw_open(hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto err_stop;
|
||||
|
||||
if (steelseries_headset_battery_register(sd) < 0)
|
||||
hid_err(sd->hdev,
|
||||
"Failed to register battery for headset\n");
|
||||
if (sd->quirks & STEELSERIES_MSI_RGB) {
|
||||
ret = steelseries_msi_rgb_register(sd);
|
||||
if (ret) {
|
||||
hid_warn(hdev,
|
||||
"Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
|
||||
ret);
|
||||
sd->quirks &= ~STEELSERIES_MSI_RGB;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_stop:
|
||||
hid_hw_stop(hdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steelseries_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct steelseries_device *sd;
|
||||
unsigned long flags;
|
||||
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
|
||||
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
||||
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
||||
|
|
@ -587,14 +632,6 @@ static void steelseries_remove(struct hid_device *hdev)
|
|||
return;
|
||||
}
|
||||
|
||||
sd = hid_get_drvdata(hdev);
|
||||
|
||||
spin_lock_irqsave(&sd->lock, flags);
|
||||
sd->removed = true;
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
|
||||
cancel_delayed_work_sync(&sd->battery_work);
|
||||
|
||||
hid_hw_close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
|
@ -615,122 +652,19 @@ static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
|
|||
return rdesc;
|
||||
}
|
||||
|
||||
static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in, uint8_t max_in)
|
||||
{
|
||||
if (capacity >= max_in)
|
||||
return 100;
|
||||
if (capacity <= min_in)
|
||||
return 0;
|
||||
return (capacity - min_in) * 100 / (max_in - min_in);
|
||||
}
|
||||
|
||||
static int steelseries_headset_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *read_buf,
|
||||
int size)
|
||||
{
|
||||
struct steelseries_device *sd = hid_get_drvdata(hdev);
|
||||
int capacity = sd->battery_capacity;
|
||||
bool connected = sd->headset_connected;
|
||||
bool charging = sd->battery_charging;
|
||||
unsigned long flags;
|
||||
|
||||
/* Not a headset */
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
|
||||
return 0;
|
||||
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
|
||||
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
|
||||
memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) {
|
||||
if (!delayed_work_pending(&sd->battery_work))
|
||||
goto request_battery;
|
||||
return 0;
|
||||
}
|
||||
if (read_buf[2] == 0x01) {
|
||||
connected = false;
|
||||
capacity = 100;
|
||||
} else {
|
||||
connected = true;
|
||||
capacity = read_buf[3];
|
||||
}
|
||||
}
|
||||
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
|
||||
if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
|
||||
if (!delayed_work_pending(&sd->battery_work))
|
||||
goto request_battery;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (read_buf[0] == 0xaa && read_buf[1] == 0x01) {
|
||||
connected = true;
|
||||
charging = read_buf[4] == 0x01;
|
||||
|
||||
/*
|
||||
* Found no official documentation about min and max.
|
||||
* Values defined by testing.
|
||||
*/
|
||||
capacity = steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d);
|
||||
} else {
|
||||
/*
|
||||
* Device is off and sends the last known status read_buf[1] == 0x03 or
|
||||
* there is no known status of the device read_buf[0] == 0x55
|
||||
*/
|
||||
connected = false;
|
||||
charging = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (connected != sd->headset_connected) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Connected status changed from %sconnected to %sconnected\n",
|
||||
sd->headset_connected ? "" : "not ",
|
||||
connected ? "" : "not ");
|
||||
sd->headset_connected = connected;
|
||||
steelseries_headset_set_wireless_status(hdev, connected);
|
||||
}
|
||||
|
||||
if (capacity != sd->battery_capacity) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Battery capacity changed from %d%% to %d%%\n",
|
||||
sd->battery_capacity, capacity);
|
||||
sd->battery_capacity = capacity;
|
||||
power_supply_changed(sd->battery);
|
||||
}
|
||||
|
||||
if (charging != sd->battery_charging) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Battery charging status changed from %scharging to %scharging\n",
|
||||
sd->battery_charging ? "" : "not ",
|
||||
charging ? "" : "not ");
|
||||
sd->battery_charging = charging;
|
||||
power_supply_changed(sd->battery);
|
||||
}
|
||||
|
||||
request_battery:
|
||||
spin_lock_irqsave(&sd->lock, flags);
|
||||
if (!sd->removed)
|
||||
schedule_delayed_work(&sd->battery_work,
|
||||
msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS));
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hid_device_id steelseries_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
|
||||
.driver_data = STEELSERIES_SRWS1 },
|
||||
|
||||
{ /* SteelSeries Arctis 1 Wireless for XBox */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
|
||||
.driver_data = STEELSERIES_ARCTIS_1 },
|
||||
#if STEELSERIES_HAS_LEDS_MULTICOLOR
|
||||
{ /* MSI Raider A18 KLC */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
|
||||
.driver_data = STEELSERIES_MSI_RGB },
|
||||
|
||||
{ /* SteelSeries Arctis 9 Wireless for XBox */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
|
||||
.driver_data = STEELSERIES_ARCTIS_9 },
|
||||
{ /* MSI Raider A18 ALC */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
|
||||
.driver_data = STEELSERIES_MSI_RGB },
|
||||
#endif
|
||||
|
||||
{ }
|
||||
};
|
||||
|
|
@ -742,12 +676,9 @@ static struct hid_driver steelseries_driver = {
|
|||
.probe = steelseries_probe,
|
||||
.remove = steelseries_remove,
|
||||
.report_fixup = steelseries_srws1_report_fixup,
|
||||
.raw_event = steelseries_headset_raw_event,
|
||||
};
|
||||
|
||||
module_hid_driver(steelseries_driver);
|
||||
MODULE_DESCRIPTION("HID driver for Steelseries devices");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
|
||||
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
|
||||
MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
|
|
@ -47,9 +48,9 @@ struct tmff_device {
|
|||
/* Changes values from 0 to 0xffff into values from minimum to maximum */
|
||||
static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum)
|
||||
{
|
||||
int ret;
|
||||
s64 ret;
|
||||
|
||||
ret = (in * (maximum - minimum) / 0xffff) + minimum;
|
||||
ret = div_s64((s64)in * ((s64)maximum - minimum), 0xffff) + minimum;
|
||||
if (ret < minimum)
|
||||
return minimum;
|
||||
if (ret > maximum)
|
||||
|
|
@ -60,9 +61,9 @@ static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum)
|
|||
/* Changes values from -0x80 to 0x7f into values from minimum to maximum */
|
||||
static inline int tmff_scale_s8(int in, int minimum, int maximum)
|
||||
{
|
||||
int ret;
|
||||
s64 ret;
|
||||
|
||||
ret = (((in + 0x80) * (maximum - minimum)) / 0xff) + minimum;
|
||||
ret = div_s64((s64)(in + 0x80) * ((s64)maximum - minimum), 0xff) + minimum;
|
||||
if (ret < minimum)
|
||||
return minimum;
|
||||
if (ret > maximum)
|
||||
|
|
@ -115,22 +116,25 @@ static int tmff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
|
||||
static int tm_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct tmff_device *tmff;
|
||||
struct hid_report *report;
|
||||
struct list_head *report_list;
|
||||
struct hid_input *hidinput;
|
||||
struct input_dev *input_dev;
|
||||
struct input_dev *input_dev = hidinput->input;
|
||||
const struct hid_device_id *id;
|
||||
const signed short *ff_bits;
|
||||
int error;
|
||||
int i;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
id = hid_match_device(hid, hid->driver);
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
|
||||
input_dev = hidinput->input;
|
||||
|
||||
ff_bits = (void *)id->driver_data;
|
||||
|
||||
tmff = kzalloc_obj(struct tmff_device);
|
||||
if (!tmff)
|
||||
|
|
@ -204,35 +208,13 @@ static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
|
|||
return error;
|
||||
}
|
||||
#else
|
||||
static inline int tmff_init(struct hid_device *hid, const signed short *ff_bits)
|
||||
static inline int tm_input_configured(struct hid_device *hid,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int tm_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
tmff_init(hdev, (void *)id->driver_data);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id tm_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300),
|
||||
.driver_data = (unsigned long)ff_rumble },
|
||||
|
|
@ -261,7 +243,7 @@ MODULE_DEVICE_TABLE(hid, tm_devices);
|
|||
static struct hid_driver tm_driver = {
|
||||
.name = "thrustmaster",
|
||||
.id_table = tm_devices,
|
||||
.probe = tm_probe,
|
||||
.input_configured = tm_input_configured,
|
||||
};
|
||||
module_hid_driver(tm_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -548,7 +548,17 @@ static void uclogic_remove(struct hid_device *hdev)
|
|||
{
|
||||
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
|
||||
|
||||
timer_delete_sync(&drvdata->inrange_timer);
|
||||
/*
|
||||
* Shut the in-range timer down before stopping the device.
|
||||
* uclogic_raw_event_pen() re-arms inrange_timer on every pen report
|
||||
* and keeps running until hid_hw_stop() stops the transport, so a
|
||||
* plain timer_delete_sync() here can be undone by a report landing in
|
||||
* the window before hid_hw_stop(). timer_shutdown_sync() cancels the
|
||||
* timer and makes any later re-arm a no-op, so it is provably dead
|
||||
* before hid_hw_stop() frees the input device drvdata->pen_input
|
||||
* points at.
|
||||
*/
|
||||
timer_shutdown_sync(&drvdata->inrange_timer);
|
||||
hid_hw_stop(hdev);
|
||||
kfree(drvdata->desc_ptr);
|
||||
uclogic_params_cleanup(&drvdata->params);
|
||||
|
|
|
|||
|
|
@ -104,12 +104,14 @@ static int universal_pidff_probe(struct hid_device *hdev,
|
|||
error = init_function(hdev, id->driver_data);
|
||||
if (error) {
|
||||
hid_warn(hdev, "Error initialising force feedback\n");
|
||||
goto err;
|
||||
goto err_stop;
|
||||
}
|
||||
|
||||
hid_info(hdev, "Universal pidff driver loaded successfully!");
|
||||
|
||||
return 0;
|
||||
err_stop:
|
||||
hid_hw_stop(hdev);
|
||||
err:
|
||||
return error;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,20 +50,15 @@ static int zpff_play(struct input_dev *dev, void *data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int zpff_init(struct hid_device *hid)
|
||||
static int zp_input_configured(struct hid_device *hid, struct hid_input *hidinput)
|
||||
{
|
||||
struct zpff_device *zpff;
|
||||
struct hid_report *report;
|
||||
struct hid_input *hidinput;
|
||||
struct input_dev *dev;
|
||||
struct input_dev *dev = hidinput->input;
|
||||
int i, error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hidinput = list_entry(hid->inputs.next, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
if (!list_is_first(&hidinput->list, &hid->inputs))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1);
|
||||
|
|
@ -75,6 +70,7 @@ static int zpff_init(struct hid_device *hid)
|
|||
if (!zpff)
|
||||
return -ENOMEM;
|
||||
|
||||
zpff->report = report;
|
||||
set_bit(FF_RUMBLE, dev->ffbit);
|
||||
|
||||
error = input_ff_create_memless(dev, zpff, zpff_play);
|
||||
|
|
@ -83,7 +79,6 @@ static int zpff_init(struct hid_device *hid)
|
|||
return error;
|
||||
}
|
||||
|
||||
zpff->report = report;
|
||||
zpff->report->field[0]->value[0] = 0x00;
|
||||
zpff->report->field[1]->value[0] = 0x02;
|
||||
zpff->report->field[2]->value[0] = 0x00;
|
||||
|
|
@ -95,35 +90,13 @@ static int zpff_init(struct hid_device *hid)
|
|||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int zpff_init(struct hid_device *hid)
|
||||
static inline int zp_input_configured(struct hid_device *hid,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int zp_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
zpff_init(hdev);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct hid_device_id zp_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
|
||||
|
|
@ -134,7 +107,7 @@ MODULE_DEVICE_TABLE(hid, zp_devices);
|
|||
static struct hid_driver zp_driver = {
|
||||
.name = "zeroplus",
|
||||
.id_table = zp_devices,
|
||||
.probe = zp_probe,
|
||||
.input_configured = zp_input_configured,
|
||||
};
|
||||
module_hid_driver(zp_driver);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ obj-$(CONFIG_I2C_HID_CORE) += i2c-hid.o
|
|||
i2c-hid-objs = i2c-hid-core.o
|
||||
i2c-hid-$(CONFIG_DMI) += i2c-hid-dmi-quirks.o
|
||||
|
||||
obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o
|
||||
obj-$(CONFIG_I2C_HID_ACPI) += i2c-hid-acpi.o i2c-hid-acpi-prp0001.o
|
||||
obj-$(CONFIG_I2C_HID_OF) += i2c-hid-of.o
|
||||
obj-$(CONFIG_I2C_HID_OF_ELAN) += i2c-hid-of-elan.o
|
||||
obj-$(CONFIG_I2C_HID_OF_GOODIX) += i2c-hid-of-goodix.o
|
||||
|
|
|
|||
104
drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c
Normal file
104
drivers/hid/i2c-hid/i2c-hid-acpi-prp0001.c
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* HID over I2C driver for PRP0001 devices missing hid-descr-addr
|
||||
*
|
||||
* Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3, use
|
||||
* _HID "PRP0001" with _DSD compatible "hid-over-i2c" but lack "hid-descr-addr"
|
||||
* from the _DSD. The HID descriptor address is provided only through an ACPI
|
||||
* _DSM. The TPD0 node in the DSDT shows _DSM Function 1 returning 0x20.
|
||||
*
|
||||
* Copyright (C) 2026 谢致邦 (XIE Zhibang) <Yeking@Red54.com>
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "i2c-hid.h"
|
||||
#include "i2c-hid-acpi.h"
|
||||
|
||||
static int i2c_hid_acpi_prp0001_power_up(struct i2chid_ops *ops)
|
||||
{
|
||||
/* give the device time to power up */
|
||||
msleep(750);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2chid_ops i2c_hid_acpi_prp0001_ops = {
|
||||
.power_up = i2c_hid_acpi_prp0001_power_up,
|
||||
/*
|
||||
* No .restore_sequence needed: the _DSM on these devices returns a
|
||||
* constant (0x20) with no side effects, unlike some PNP0C50 _DSM
|
||||
* implementations that switch the hardware between PS/2 and I2C modes.
|
||||
*/
|
||||
};
|
||||
|
||||
static int i2c_hid_acpi_prp0001_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct acpi_device *adev;
|
||||
u16 hid_descriptor_address;
|
||||
int ret;
|
||||
|
||||
/* If hid-descr-addr is present, let i2c-hid-of handle it */
|
||||
if (device_property_present(dev, "hid-descr-addr"))
|
||||
return -ENODEV;
|
||||
|
||||
adev = ACPI_COMPANION(dev);
|
||||
if (!adev)
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_hid_acpi_get_descriptor(adev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dev_warn(dev,
|
||||
"hid-descr-addr device property NOT found, using ACPI _DSM fallback. Contact vendor for firmware update!\n");
|
||||
hid_descriptor_address = ret;
|
||||
|
||||
/*
|
||||
* No acpi_device_fix_up_power() needed: TPD0 has no _PS0, _PS3, _PSC
|
||||
* or _PRx methods and follows I2C bus power.
|
||||
*/
|
||||
return i2c_hid_core_probe(client, &i2c_hid_acpi_prp0001_ops,
|
||||
hid_descriptor_address, 0);
|
||||
}
|
||||
|
||||
static const struct of_device_id i2c_hid_acpi_prp0001_of_match[] = {
|
||||
{ .compatible = "hid-over-i2c" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i2c_hid_acpi_prp0001_of_match);
|
||||
|
||||
static const struct i2c_device_id i2c_hid_acpi_prp0001_id[] = {
|
||||
{ .name = "hid-over-i2c" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, i2c_hid_acpi_prp0001_id);
|
||||
|
||||
static struct i2c_driver i2c_hid_acpi_prp0001_driver = {
|
||||
.driver = {
|
||||
.name = "i2c_hid_acpi_prp0001",
|
||||
.pm = &i2c_hid_core_pm,
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
/*
|
||||
* of_match_ptr() makes this NULL when CONFIG_OF=n, but that's
|
||||
* fine: the I2C id_table with "hid-over-i2c" handles matching
|
||||
* via client->name (set by acpi_set_modalias() from the _DSD
|
||||
* compatible property).
|
||||
*/
|
||||
.of_match_table = of_match_ptr(i2c_hid_acpi_prp0001_of_match),
|
||||
},
|
||||
|
||||
.probe = i2c_hid_acpi_prp0001_probe,
|
||||
.remove = i2c_hid_core_remove,
|
||||
.shutdown = i2c_hid_core_shutdown,
|
||||
.id_table = i2c_hid_acpi_prp0001_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(i2c_hid_acpi_prp0001_driver);
|
||||
|
||||
MODULE_DESCRIPTION("HID over I2C driver for PRP0001 devices missing hid-descr-addr");
|
||||
MODULE_AUTHOR("谢致邦 (XIE Zhibang) <Yeking@Red54.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -25,9 +25,9 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/uuid.h>
|
||||
|
||||
#include "i2c-hid.h"
|
||||
#include "i2c-hid-acpi.h"
|
||||
|
||||
struct i2c_hid_acpi {
|
||||
struct i2chid_ops ops;
|
||||
|
|
@ -48,39 +48,11 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
|
|||
{ }
|
||||
};
|
||||
|
||||
/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
|
||||
static guid_t i2c_hid_guid =
|
||||
GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
|
||||
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
|
||||
|
||||
static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
|
||||
{
|
||||
struct acpi_device *adev = ihid_acpi->adev;
|
||||
acpi_handle handle = acpi_device_handle(adev);
|
||||
union acpi_object *obj;
|
||||
u16 hid_descriptor_address;
|
||||
|
||||
if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
|
||||
return -ENODEV;
|
||||
|
||||
obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL,
|
||||
ACPI_TYPE_INTEGER);
|
||||
if (!obj) {
|
||||
acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hid_descriptor_address = obj->integer.value;
|
||||
ACPI_FREE(obj);
|
||||
|
||||
return hid_descriptor_address;
|
||||
}
|
||||
|
||||
static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops)
|
||||
{
|
||||
struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
|
||||
|
||||
i2c_hid_acpi_get_descriptor(ihid_acpi);
|
||||
i2c_hid_acpi_get_descriptor(ihid_acpi->adev);
|
||||
}
|
||||
|
||||
static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
|
||||
|
|
@ -93,24 +65,28 @@ static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
|
|||
static int i2c_hid_acpi_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct acpi_device *adev = ACPI_COMPANION(dev);
|
||||
struct i2c_hid_acpi *ihid_acpi;
|
||||
u16 hid_descriptor_address;
|
||||
int ret;
|
||||
|
||||
ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL);
|
||||
if (!ihid_acpi)
|
||||
return -ENOMEM;
|
||||
if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
|
||||
return -ENODEV;
|
||||
|
||||
ihid_acpi->adev = ACPI_COMPANION(dev);
|
||||
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
|
||||
ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
|
||||
|
||||
ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
|
||||
ret = i2c_hid_acpi_get_descriptor(adev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
hid_descriptor_address = ret;
|
||||
|
||||
acpi_device_fix_up_power(ihid_acpi->adev);
|
||||
ihid_acpi = devm_kzalloc(dev, sizeof(*ihid_acpi), GFP_KERNEL);
|
||||
if (!ihid_acpi)
|
||||
return -ENOMEM;
|
||||
|
||||
ihid_acpi->adev = adev;
|
||||
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
|
||||
ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
|
||||
|
||||
acpi_device_fix_up_power(adev);
|
||||
|
||||
return i2c_hid_core_probe(client, &ihid_acpi->ops,
|
||||
hid_descriptor_address, 0);
|
||||
|
|
|
|||
33
drivers/hid/i2c-hid/i2c-hid-acpi.h
Normal file
33
drivers/hid/i2c-hid/i2c-hid-acpi.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef _I2C_HID_ACPI_H
|
||||
#define _I2C_HID_ACPI_H
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/uuid.h>
|
||||
|
||||
static inline int i2c_hid_acpi_get_descriptor(struct acpi_device *adev)
|
||||
{
|
||||
/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
|
||||
static const guid_t i2c_hid_guid =
|
||||
GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
|
||||
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
|
||||
|
||||
acpi_handle handle = acpi_device_handle(adev);
|
||||
union acpi_object *obj;
|
||||
u16 addr;
|
||||
|
||||
obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid,
|
||||
1, 1, NULL, ACPI_TYPE_INTEGER);
|
||||
if (!obj) {
|
||||
acpi_handle_err(handle,
|
||||
"Error _DSM call to get HID descriptor address failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
addr = obj->integer.value;
|
||||
ACPI_FREE(obj);
|
||||
return addr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -792,7 +792,7 @@ static int i2c_hid_parse(struct hid_device *hid)
|
|||
ihid->hdesc.wReportDescRegister,
|
||||
rdesc, rsize);
|
||||
if (ret) {
|
||||
hid_err(hid, "reading report descriptor failed\n");
|
||||
dev_err(&client->dev, "reading report descriptor failed\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,8 +51,10 @@ static int goodix_i2c_hid_power_up(struct i2chid_ops *ops)
|
|||
return ret;
|
||||
|
||||
ret = regulator_enable(ihid_goodix->vddio);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
regulator_disable(ihid_goodix->vdd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ihid_goodix->timings->post_power_delay_ms)
|
||||
msleep(ihid_goodix->timings->post_power_delay_ms);
|
||||
|
|
|
|||
|
|
@ -232,10 +232,8 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|||
|
||||
ret = devm_request_irq(dev, pdev->irq, ish_irq_handler,
|
||||
irq_flag, KBUILD_MODNAME, ishtp);
|
||||
if (ret) {
|
||||
dev_err(dev, "ISH: request IRQ %d failed\n", pdev->irq);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_set_drvdata(ishtp->devc, ishtp);
|
||||
|
||||
|
|
|
|||
|
|
@ -113,8 +113,7 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
|
|||
|
||||
switch (recv_msg->hdr.command & CMD_MASK) {
|
||||
case HOSTIF_DM_ENUM_DEVICES:
|
||||
if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
|
||||
client_data->init_done)) {
|
||||
if (!(recv_msg->hdr.command & ~CMD_MASK)) {
|
||||
++client_data->bad_recv_cnt;
|
||||
report_bad_packet(hid_ishtp_cl, recv_msg,
|
||||
cur_pos,
|
||||
|
|
@ -122,6 +121,8 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
|
|||
ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
|
||||
break;
|
||||
}
|
||||
if (client_data->init_done)
|
||||
break;
|
||||
client_data->hid_dev_count = (unsigned int)*payload;
|
||||
if (!client_data->hid_devices)
|
||||
client_data->hid_devices = devm_kcalloc(
|
||||
|
|
|
|||
|
|
@ -175,7 +175,9 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev)
|
|||
if (i2c_param.addressing_mode != HIDI2C_ADDRESSING_MODE_7BIT)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
qcdev->i2c_slave_addr = i2c_param.device_address;
|
||||
qcdev->i2c_config.addr_mode = HIDI2C_ADDRESSING_MODE_7BIT;
|
||||
|
||||
qcdev->i2c_config.target_addr = i2c_param.device_address;
|
||||
|
||||
ret = quicki2c_acpi_get_dsd_property(adev, QUICKI2C_ACPI_METHOD_NAME_ISUB,
|
||||
ACPI_TYPE_BUFFER, &i2c_config);
|
||||
|
|
@ -184,24 +186,32 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev)
|
|||
|
||||
if (i2c_param.connection_speed > 0 &&
|
||||
i2c_param.connection_speed <= QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED) {
|
||||
qcdev->i2c_speed_mode = THC_I2C_STANDARD;
|
||||
qcdev->i2c_clock_hcnt = i2c_config.SMHX;
|
||||
qcdev->i2c_clock_lcnt = i2c_config.SMLX;
|
||||
qcdev->i2c_config.speed = THC_I2C_STANDARD;
|
||||
qcdev->i2c_config.scl_hcnt = (u32)i2c_config.SMHX;
|
||||
qcdev->i2c_config.scl_lcnt = (u32)i2c_config.SMLX;
|
||||
qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.SMTD;
|
||||
qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.SMRD;
|
||||
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_STANDARD_MODE_MAX_SPEED &&
|
||||
i2c_param.connection_speed <= QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED) {
|
||||
qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS;
|
||||
qcdev->i2c_clock_hcnt = i2c_config.FMHX;
|
||||
qcdev->i2c_clock_lcnt = i2c_config.FMLX;
|
||||
qcdev->i2c_config.speed = THC_I2C_FAST_AND_PLUS;
|
||||
qcdev->i2c_config.scl_hcnt = (u32)i2c_config.FMHX;
|
||||
qcdev->i2c_config.scl_lcnt = (u32)i2c_config.FMLX;
|
||||
qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.FMTD;
|
||||
qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.FMRD;
|
||||
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FAST_MODE_MAX_SPEED &&
|
||||
i2c_param.connection_speed <= QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED) {
|
||||
qcdev->i2c_speed_mode = THC_I2C_FAST_AND_PLUS;
|
||||
qcdev->i2c_clock_hcnt = i2c_config.FPHX;
|
||||
qcdev->i2c_clock_lcnt = i2c_config.FPLX;
|
||||
qcdev->i2c_config.speed = THC_I2C_FAST_AND_PLUS;
|
||||
qcdev->i2c_config.scl_hcnt = (u32)i2c_config.FPHX;
|
||||
qcdev->i2c_config.scl_lcnt = (u32)i2c_config.FPLX;
|
||||
qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.FPTD;
|
||||
qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.FPRD;
|
||||
} else if (i2c_param.connection_speed > QUICKI2C_SUBIP_FASTPLUS_MODE_MAX_SPEED &&
|
||||
i2c_param.connection_speed <= QUICKI2C_SUBIP_HIGH_SPEED_MODE_MAX_SPEED) {
|
||||
qcdev->i2c_speed_mode = THC_I2C_HIGH_SPEED;
|
||||
qcdev->i2c_clock_hcnt = i2c_config.HMHX;
|
||||
qcdev->i2c_clock_lcnt = i2c_config.HMLX;
|
||||
qcdev->i2c_config.speed = THC_I2C_HIGH_SPEED;
|
||||
qcdev->i2c_config.scl_hcnt = (u32)i2c_config.HMHX;
|
||||
qcdev->i2c_config.scl_lcnt = (u32)i2c_config.HMLX;
|
||||
qcdev->i2c_config.sda_tx_hold = (u32)i2c_config.HMTD;
|
||||
qcdev->i2c_config.sda_rx_hold = (u32)i2c_config.HMRD;
|
||||
} else {
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
|
@ -245,28 +255,33 @@ static irqreturn_t quicki2c_irq_quick_handler(int irq, void *dev_id)
|
|||
}
|
||||
|
||||
/**
|
||||
* try_recover - Try to recovery THC and Device
|
||||
* @qcdev: Pointer to quicki2c_device structure
|
||||
* try_recover - Recover callback to recover THC
|
||||
* @work: pointer to work_struct
|
||||
*
|
||||
* This function is an error handler, called when fatal error happens.
|
||||
* It try to reset touch device and re-configure THC to recovery
|
||||
* communication between touch device and THC.
|
||||
*
|
||||
* Return: 0 if successful or error code on failure
|
||||
* It try to reset Touch Device and re-configure THC to recover
|
||||
* transferring between Device and THC.
|
||||
*/
|
||||
static int try_recover(struct quicki2c_device *qcdev)
|
||||
static void try_recover(struct work_struct *work)
|
||||
{
|
||||
int ret;
|
||||
struct quicki2c_device *qcdev = container_of(work, struct quicki2c_device, recover_work);
|
||||
|
||||
thc_dma_unconfigure(qcdev->thc_hw);
|
||||
if (READ_ONCE(qcdev->recovery_disabled))
|
||||
return;
|
||||
|
||||
ret = thc_dma_configure(qcdev->thc_hw);
|
||||
if (ret) {
|
||||
dev_err(qcdev->dev, "Reconfig DMA failed\n");
|
||||
return ret;
|
||||
if (pm_runtime_resume_and_get(qcdev->dev))
|
||||
return;
|
||||
|
||||
thc_interrupt_enable(qcdev->thc_hw, false);
|
||||
|
||||
if (thc_rxdma_reset(qcdev->thc_hw)) {
|
||||
qcdev->state = QUICKI2C_DISABLED;
|
||||
dev_err(qcdev->dev, "RxDMA reset failed during recover, disable QuickI2C\n");
|
||||
} else {
|
||||
thc_interrupt_enable(qcdev->thc_hw, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
pm_runtime_put_autosuspend(qcdev->dev);
|
||||
}
|
||||
|
||||
static int handle_input_report(struct quicki2c_device *qcdev)
|
||||
|
|
@ -343,11 +358,10 @@ static irqreturn_t quicki2c_irq_thread_handler(int irq, void *dev_id)
|
|||
}
|
||||
|
||||
exit:
|
||||
thc_interrupt_enable(qcdev->thc_hw, true);
|
||||
|
||||
if (err_recover)
|
||||
if (try_recover(qcdev))
|
||||
qcdev->state = QUICKI2C_DISABLED;
|
||||
schedule_work(&qcdev->recover_work);
|
||||
else
|
||||
thc_interrupt_enable(qcdev->thc_hw, true);
|
||||
|
||||
pm_runtime_put_autosuspend(qcdev->dev);
|
||||
|
||||
|
|
@ -386,6 +400,8 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io
|
|||
qcdev->ddata = ddata;
|
||||
|
||||
init_waitqueue_head(&qcdev->reset_ack_wq);
|
||||
WRITE_ONCE(qcdev->recovery_disabled, false);
|
||||
INIT_WORK(&qcdev->recover_work, try_recover);
|
||||
|
||||
/* THC hardware init */
|
||||
qcdev->thc_hw = thc_dev_init(qcdev->dev, qcdev->mem_addr);
|
||||
|
|
@ -411,10 +427,7 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io
|
|||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr,
|
||||
qcdev->i2c_speed_mode,
|
||||
qcdev->i2c_clock_hcnt,
|
||||
qcdev->i2c_clock_lcnt);
|
||||
ret = thc_i2c_subip_init(qcdev->thc_hw, &qcdev->i2c_config);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
|
|
@ -439,6 +452,9 @@ static struct quicki2c_device *quicki2c_dev_init(struct pci_dev *pdev, void __io
|
|||
*/
|
||||
static void quicki2c_dev_deinit(struct quicki2c_device *qcdev)
|
||||
{
|
||||
WRITE_ONCE(qcdev->recovery_disabled, true);
|
||||
cancel_work_sync(&qcdev->recover_work);
|
||||
|
||||
thc_interrupt_quiesce(qcdev->thc_hw, true);
|
||||
thc_interrupt_enable(qcdev->thc_hw, false);
|
||||
thc_ltr_unconfig(qcdev->thc_hw);
|
||||
|
|
@ -682,11 +698,8 @@ static int quicki2c_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|||
quicki2c_irq_thread_handler,
|
||||
IRQF_ONESHOT, KBUILD_MODNAME,
|
||||
qcdev);
|
||||
if (ret) {
|
||||
dev_err_once(&pdev->dev,
|
||||
"Failed to request threaded IRQ, irq = %d.\n", pdev->irq);
|
||||
if (ret)
|
||||
goto dev_deinit;
|
||||
}
|
||||
|
||||
ret = quicki2c_get_device_descriptor(qcdev);
|
||||
if (ret) {
|
||||
|
|
@ -772,12 +785,14 @@ static void quicki2c_remove(struct pci_dev *pdev)
|
|||
return;
|
||||
|
||||
quicki2c_hid_remove(qcdev);
|
||||
quicki2c_dma_deinit(qcdev);
|
||||
|
||||
pm_runtime_get_noresume(qcdev->dev);
|
||||
|
||||
quicki2c_dev_deinit(qcdev);
|
||||
|
||||
quicki2c_dma_deinit(qcdev);
|
||||
|
||||
pm_runtime_dont_use_autosuspend(qcdev->dev);
|
||||
pm_runtime_get_noresume(qcdev->dev);
|
||||
|
||||
pci_clear_master(pdev);
|
||||
}
|
||||
|
||||
|
|
@ -796,10 +811,10 @@ static void quicki2c_shutdown(struct pci_dev *pdev)
|
|||
if (!qcdev)
|
||||
return;
|
||||
|
||||
quicki2c_dev_deinit(qcdev);
|
||||
|
||||
/* Must stop DMA before reboot to avoid DMA entering into unknown state */
|
||||
quicki2c_dma_deinit(qcdev);
|
||||
|
||||
quicki2c_dev_deinit(qcdev);
|
||||
}
|
||||
|
||||
static int quicki2c_suspend(struct device *device)
|
||||
|
|
@ -826,6 +841,9 @@ static int quicki2c_suspend(struct device *device)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
WRITE_ONCE(qcdev->recovery_disabled, true);
|
||||
cancel_work_sync(&qcdev->recover_work);
|
||||
|
||||
ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -867,6 +885,8 @@ static int quicki2c_resume(struct device *device)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
WRITE_ONCE(qcdev->recovery_disabled, false);
|
||||
|
||||
if (!device_may_wakeup(qcdev->dev))
|
||||
return quicki2c_set_power(qcdev, HIDI2C_ON);
|
||||
|
||||
|
|
@ -883,6 +903,9 @@ static int quicki2c_freeze(struct device *device)
|
|||
if (!qcdev)
|
||||
return -ENODEV;
|
||||
|
||||
WRITE_ONCE(qcdev->recovery_disabled, true);
|
||||
cancel_work_sync(&qcdev->recover_work);
|
||||
|
||||
ret = thc_interrupt_quiesce(qcdev->thc_hw, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -914,6 +937,8 @@ static int quicki2c_thaw(struct device *device)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
WRITE_ONCE(qcdev->recovery_disabled, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -938,6 +963,8 @@ static int quicki2c_poweroff(struct device *device)
|
|||
|
||||
thc_ltr_unconfig(qcdev->thc_hw);
|
||||
|
||||
quicki2c_dev_deinit(qcdev);
|
||||
|
||||
quicki2c_dma_deinit(qcdev);
|
||||
|
||||
return 0;
|
||||
|
|
@ -958,10 +985,7 @@ static int quicki2c_restore(struct device *device)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = thc_i2c_subip_init(qcdev->thc_hw, qcdev->i2c_slave_addr,
|
||||
qcdev->i2c_speed_mode,
|
||||
qcdev->i2c_clock_hcnt,
|
||||
qcdev->i2c_clock_lcnt);
|
||||
ret = thc_i2c_subip_init(qcdev->thc_hw, &qcdev->i2c_config);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#include <linux/hid-over-i2c.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "intel-thc-dev.h"
|
||||
|
||||
#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT1 0xA848
|
||||
#define PCI_DEVICE_ID_INTEL_THC_LNL_DEVICE_ID_I2C_PORT2 0xA84A
|
||||
#define PCI_DEVICE_ID_INTEL_THC_PTL_H_DEVICE_ID_I2C_PORT1 0xE348
|
||||
|
|
@ -159,7 +161,6 @@ struct quicki2c_ddata {
|
|||
|
||||
struct device;
|
||||
struct pci_dev;
|
||||
struct thc_device;
|
||||
struct hid_device;
|
||||
struct acpi_device;
|
||||
|
||||
|
|
@ -174,13 +175,10 @@ struct acpi_device;
|
|||
* @state: THC I2C device state
|
||||
* @mem_addr: MMIO memory address
|
||||
* @dev_desc: Device descriptor for HIDI2C protocol
|
||||
* @i2c_slave_addr: HIDI2C device slave address
|
||||
* @i2c_config: I2C bus configuration
|
||||
* @hid_desc_addr: Register address for retrieve HID device descriptor
|
||||
* @active_ltr_val: THC active LTR value
|
||||
* @low_power_ltr_val: THC low power LTR value
|
||||
* @i2c_speed_mode: 0 - standard mode, 1 - fast mode, 2 - fast mode plus
|
||||
* @i2c_clock_hcnt: I2C CLK high period time (unit in cycle count)
|
||||
* @i2c_clock_lcnt: I2C CLK low period time (unit in cycle count)
|
||||
* @report_descriptor: Store a copy of device report descriptor
|
||||
* @input_buf: Store a copy of latest input report data
|
||||
* @report_buf: Store a copy of latest input/output report packet from set/get feature
|
||||
|
|
@ -191,6 +189,8 @@ struct acpi_device;
|
|||
* @i2c_max_frame_size: Max RX frame size (unit in Bytes)
|
||||
* @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not
|
||||
* @i2c_int_delay: Interrupt detection delay value (unit in 10 us)
|
||||
* @recover_work: Work structure for recovery
|
||||
* @recovery_disabled: Whether recovery work is blocked during teardown
|
||||
*/
|
||||
struct quicki2c_device {
|
||||
struct device *dev;
|
||||
|
|
@ -204,16 +204,12 @@ struct quicki2c_device {
|
|||
void __iomem *mem_addr;
|
||||
|
||||
struct hidi2c_dev_descriptor dev_desc;
|
||||
u8 i2c_slave_addr;
|
||||
struct thc_i2c_config i2c_config;
|
||||
u16 hid_desc_addr;
|
||||
|
||||
u32 active_ltr_val;
|
||||
u32 low_power_ltr_val;
|
||||
|
||||
u32 i2c_speed_mode;
|
||||
u32 i2c_clock_hcnt;
|
||||
u32 i2c_clock_lcnt;
|
||||
|
||||
u8 *report_descriptor;
|
||||
u8 *input_buf;
|
||||
u8 *report_buf;
|
||||
|
|
@ -226,6 +222,9 @@ struct quicki2c_device {
|
|||
u32 i2c_max_frame_size;
|
||||
u32 i2c_int_delay_enable;
|
||||
u32 i2c_int_delay;
|
||||
|
||||
struct work_struct recover_work;
|
||||
bool recovery_disabled;
|
||||
};
|
||||
|
||||
#endif /* _QUICKI2C_DEV_H_ */
|
||||
|
|
|
|||
|
|
@ -252,34 +252,33 @@ static irqreturn_t quickspi_irq_quick_handler(int irq, void *dev_id)
|
|||
}
|
||||
|
||||
/**
|
||||
* try_recover - Try to recovery THC and Device
|
||||
* @qsdev: pointer to quickspi device
|
||||
* try_recover - Recover callback to recover THC
|
||||
* @work: pointer to work_struct
|
||||
*
|
||||
* This function is a error handler, called when fatal error happens.
|
||||
* It try to reset Touch Device and re-configure THC to recovery
|
||||
* This function is an error handler, called when fatal error happens.
|
||||
* It try to reset Touch Device and re-configure THC to recover
|
||||
* transferring between Device and THC.
|
||||
*
|
||||
* Return: 0 if successful or error code on failed.
|
||||
*/
|
||||
static int try_recover(struct quickspi_device *qsdev)
|
||||
static void try_recover(struct work_struct *work)
|
||||
{
|
||||
int ret;
|
||||
struct quickspi_device *qsdev = container_of(work, struct quickspi_device, recover_work);
|
||||
|
||||
ret = reset_tic(qsdev);
|
||||
if (ret) {
|
||||
dev_err(qsdev->dev, "Reset touch device failed, ret = %d\n", ret);
|
||||
return ret;
|
||||
if (READ_ONCE(qsdev->recovery_disabled))
|
||||
return;
|
||||
|
||||
if (pm_runtime_resume_and_get(qsdev->dev))
|
||||
return;
|
||||
|
||||
thc_interrupt_enable(qsdev->thc_hw, false);
|
||||
|
||||
if (thc_rxdma_reset(qsdev->thc_hw)) {
|
||||
qsdev->state = QUICKSPI_DISABLED;
|
||||
dev_err(qsdev->dev, "RxDMA reset failed during recover, disable QuickSPI\n");
|
||||
} else {
|
||||
thc_interrupt_enable(qsdev->thc_hw, true);
|
||||
}
|
||||
|
||||
thc_dma_unconfigure(qsdev->thc_hw);
|
||||
|
||||
ret = thc_dma_configure(qsdev->thc_hw);
|
||||
if (ret) {
|
||||
dev_err(qsdev->dev, "Re-configure THC DMA failed, ret = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
pm_runtime_put_autosuspend(qsdev->dev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -337,11 +336,10 @@ static irqreturn_t quickspi_irq_thread_handler(int irq, void *dev_id)
|
|||
}
|
||||
|
||||
end:
|
||||
thc_interrupt_enable(qsdev->thc_hw, true);
|
||||
|
||||
if (err_recover)
|
||||
if (try_recover(qsdev))
|
||||
qsdev->state = QUICKSPI_DISABLED;
|
||||
schedule_work(&qsdev->recover_work);
|
||||
else
|
||||
thc_interrupt_enable(qsdev->thc_hw, true);
|
||||
|
||||
pm_runtime_put_autosuspend(qsdev->dev);
|
||||
|
||||
|
|
@ -385,6 +383,8 @@ static struct quickspi_device *quickspi_dev_init(struct pci_dev *pdev, void __io
|
|||
init_waitqueue_head(&qsdev->report_desc_got_wq);
|
||||
init_waitqueue_head(&qsdev->get_report_cmpl_wq);
|
||||
init_waitqueue_head(&qsdev->set_report_cmpl_wq);
|
||||
WRITE_ONCE(qsdev->recovery_disabled, false);
|
||||
INIT_WORK(&qsdev->recover_work, try_recover);
|
||||
|
||||
/* thc hw init */
|
||||
qsdev->thc_hw = thc_dev_init(qsdev->dev, qsdev->mem_addr);
|
||||
|
|
@ -461,6 +461,10 @@ static struct quickspi_device *quickspi_dev_init(struct pci_dev *pdev, void __io
|
|||
*/
|
||||
static void quickspi_dev_deinit(struct quickspi_device *qsdev)
|
||||
{
|
||||
WRITE_ONCE(qsdev->recovery_disabled, true);
|
||||
cancel_work_sync(&qsdev->recover_work);
|
||||
|
||||
thc_interrupt_quiesce(qsdev->thc_hw, true);
|
||||
thc_interrupt_enable(qsdev->thc_hw, false);
|
||||
thc_ltr_unconfig(qsdev->thc_hw);
|
||||
thc_wot_unconfig(qsdev->thc_hw);
|
||||
|
|
@ -555,7 +559,14 @@ static int quickspi_alloc_report_buf(struct quickspi_device *qsdev)
|
|||
max_report_len = max(le16_to_cpu(qsdev->dev_desc.max_output_len),
|
||||
le16_to_cpu(qsdev->dev_desc.max_input_len));
|
||||
|
||||
qsdev->report_buf = devm_kzalloc(qsdev->dev, max_report_len, GFP_KERNEL);
|
||||
/*
|
||||
* write_cmd_to_txdma() writes the output report header ahead of the
|
||||
* content in this buffer, so it has to hold both.
|
||||
*/
|
||||
qsdev->report_buf_size = HIDSPI_OUTPUT_REPORT_SIZE(max_report_len);
|
||||
|
||||
qsdev->report_buf = devm_kzalloc(qsdev->dev, qsdev->report_buf_size,
|
||||
GFP_KERNEL);
|
||||
if (!qsdev->report_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -636,11 +647,8 @@ static int quickspi_probe(struct pci_dev *pdev,
|
|||
quickspi_irq_thread_handler,
|
||||
IRQF_ONESHOT, KBUILD_MODNAME,
|
||||
qsdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to request threaded IRQ, irq = %d.\n", pdev->irq);
|
||||
if (ret)
|
||||
goto dev_deinit;
|
||||
}
|
||||
|
||||
ret = reset_tic(qsdev);
|
||||
if (ret) {
|
||||
|
|
@ -711,12 +719,14 @@ static void quickspi_remove(struct pci_dev *pdev)
|
|||
return;
|
||||
|
||||
quickspi_hid_remove(qsdev);
|
||||
quickspi_dma_deinit(qsdev);
|
||||
|
||||
pm_runtime_get_noresume(qsdev->dev);
|
||||
|
||||
quickspi_dev_deinit(qsdev);
|
||||
|
||||
quickspi_dma_deinit(qsdev);
|
||||
|
||||
pm_runtime_dont_use_autosuspend(qsdev->dev);
|
||||
pm_runtime_get_noresume(qsdev->dev);
|
||||
|
||||
pci_clear_master(pdev);
|
||||
}
|
||||
|
||||
|
|
@ -737,10 +747,10 @@ static void quickspi_shutdown(struct pci_dev *pdev)
|
|||
if (!qsdev)
|
||||
return;
|
||||
|
||||
quickspi_dev_deinit(qsdev);
|
||||
|
||||
/* Must stop DMA before reboot to avoid DMA entering into unknown state */
|
||||
quickspi_dma_deinit(qsdev);
|
||||
|
||||
quickspi_dev_deinit(qsdev);
|
||||
}
|
||||
|
||||
static int quickspi_suspend(struct device *device)
|
||||
|
|
@ -759,6 +769,9 @@ static int quickspi_suspend(struct device *device)
|
|||
return ret;
|
||||
}
|
||||
|
||||
WRITE_ONCE(qsdev->recovery_disabled, true);
|
||||
cancel_work_sync(&qsdev->recover_work);
|
||||
|
||||
ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -784,6 +797,8 @@ static int quickspi_resume(struct device *device)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
WRITE_ONCE(qsdev->recovery_disabled, false);
|
||||
|
||||
/*
|
||||
* A wake-enabled device keeps its power and state across suspend, so
|
||||
* only restore the THC context. Resetting it here would discard a
|
||||
|
|
@ -864,6 +879,9 @@ static int quickspi_freeze(struct device *device)
|
|||
if (!qsdev)
|
||||
return -ENODEV;
|
||||
|
||||
WRITE_ONCE(qsdev->recovery_disabled, true);
|
||||
cancel_work_sync(&qsdev->recover_work);
|
||||
|
||||
ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -895,6 +913,8 @@ static int quickspi_thaw(struct device *device)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
WRITE_ONCE(qsdev->recovery_disabled, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -919,6 +939,8 @@ static int quickspi_poweroff(struct device *device)
|
|||
|
||||
thc_ltr_unconfig(qsdev->thc_hw);
|
||||
|
||||
quickspi_dev_deinit(qsdev);
|
||||
|
||||
quickspi_dma_deinit(qsdev);
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <linux/hid-over-spi.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "quickspi-protocol.h"
|
||||
|
||||
|
|
@ -126,6 +127,8 @@ struct acpi_device;
|
|||
* @get_feature_cmpl: indicate get feature received or not
|
||||
* @set_feature_cmpl_wq: workqueue for waiting set feature to device
|
||||
* @set_feature_cmpl: indicate set feature send complete or not
|
||||
* @recover_work: Work structure for recovery
|
||||
* @recovery_disabled: Whether recovery work is blocked during teardown
|
||||
*/
|
||||
struct quickspi_device {
|
||||
struct device *dev;
|
||||
|
|
@ -157,6 +160,7 @@ struct quickspi_device {
|
|||
u8 *report_descriptor;
|
||||
u8 *input_buf;
|
||||
u8 *report_buf;
|
||||
u32 report_buf_size;
|
||||
u32 report_len;
|
||||
|
||||
wait_queue_head_t reset_ack_wq;
|
||||
|
|
@ -173,6 +177,9 @@ struct quickspi_device {
|
|||
|
||||
wait_queue_head_t set_report_cmpl_wq;
|
||||
bool set_report_cmpl;
|
||||
|
||||
struct work_struct recover_work;
|
||||
bool recovery_disabled;
|
||||
};
|
||||
|
||||
#endif /* _QUICKSPI_DEV_H_ */
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ static int quickspi_hid_raw_request(struct hid_device *hid,
|
|||
|
||||
switch (reqtype) {
|
||||
case HID_REQ_GET_REPORT:
|
||||
ret = quickspi_get_report(qsdev, rtype, reportnum, buf);
|
||||
ret = quickspi_get_report(qsdev, rtype, reportnum, buf, len);
|
||||
break;
|
||||
case HID_REQ_SET_REPORT:
|
||||
ret = quickspi_set_report(qsdev, rtype, reportnum, buf, len);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ static int write_cmd_to_txdma(struct quickspi_device *qsdev,
|
|||
|
||||
write_buf = (struct output_report *)qsdev->report_buf;
|
||||
|
||||
if (HIDSPI_OUTPUT_REPORT_SIZE(report_buf_len) > qsdev->report_buf_size)
|
||||
return -EINVAL;
|
||||
|
||||
write_buf->output_hdr.report_type = report_type;
|
||||
write_buf->output_hdr.content_len = cpu_to_le16(report_buf_len);
|
||||
write_buf->output_hdr.content_id = report_id;
|
||||
|
|
@ -342,10 +345,12 @@ int reset_tic(struct quickspi_device *qsdev)
|
|||
}
|
||||
|
||||
int quickspi_get_report(struct quickspi_device *qsdev,
|
||||
u8 report_type, unsigned int report_id, void *buf)
|
||||
u8 report_type, unsigned int report_id, void *buf,
|
||||
u32 buf_len)
|
||||
{
|
||||
int rep_type;
|
||||
int ret;
|
||||
u32 report_len;
|
||||
|
||||
if (report_type == HID_INPUT_REPORT) {
|
||||
rep_type = GET_INPUT_REPORT;
|
||||
|
|
@ -372,9 +377,17 @@ int quickspi_get_report(struct quickspi_device *qsdev,
|
|||
}
|
||||
qsdev->get_report_cmpl = false;
|
||||
|
||||
memcpy(buf, qsdev->report_buf, qsdev->report_len);
|
||||
/* quickspi_handle_input_data() updates this from IRQ context. */
|
||||
report_len = READ_ONCE(qsdev->report_len);
|
||||
if (report_len > buf_len) {
|
||||
dev_err_once(qsdev->dev, "Get report response too big, %u vs %u\n",
|
||||
report_len, buf_len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return qsdev->report_len;
|
||||
memcpy(buf, qsdev->report_buf, report_len);
|
||||
|
||||
return report_len;
|
||||
}
|
||||
|
||||
int quickspi_set_report(struct quickspi_device *qsdev,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ struct quickspi_device;
|
|||
|
||||
void quickspi_handle_input_data(struct quickspi_device *qsdev, u32 buf_len);
|
||||
int quickspi_get_report(struct quickspi_device *qsdev, u8 report_type,
|
||||
unsigned int report_id, void *buf);
|
||||
unsigned int report_id, void *buf, u32 buf_len);
|
||||
int quickspi_set_report(struct quickspi_device *qsdev, u8 report_type,
|
||||
unsigned int report_id, void *buf, u32 buf_len);
|
||||
int quickspi_get_report_descriptor(struct quickspi_device *qsdev);
|
||||
|
|
|
|||
|
|
@ -1422,14 +1422,25 @@ static int thc_i2c_subip_pio_write(struct thc_device *dev, const u32 address,
|
|||
#define I2C_SUBIP_DMA_TDLR_DEFAULT 7
|
||||
#define I2C_SUBIP_DMA_RDLR_DEFAULT 7
|
||||
|
||||
static int thc_i2c_subip_set_speed(struct thc_device *dev, const u32 speed,
|
||||
const u32 hcnt, const u32 lcnt)
|
||||
static int thc_i2c_subip_bus_config(struct thc_device *dev, const struct thc_i2c_config *i2c_config)
|
||||
{
|
||||
u32 hcnt_offset, lcnt_offset;
|
||||
u32 val;
|
||||
u32 read_size = sizeof(u32);
|
||||
u32 val = 0;
|
||||
int ret;
|
||||
|
||||
switch (speed) {
|
||||
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val &= ~(THC_I2C_IC_TAR_IC_TAR | THC_I2C_IC_TAR_IC_10BITADDR_MASTER);
|
||||
val |= FIELD_PREP(THC_I2C_IC_TAR_IC_10BITADDR_MASTER, i2c_config->addr_mode);
|
||||
val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, i2c_config->target_addr);
|
||||
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
switch (i2c_config->speed) {
|
||||
case THC_I2C_STANDARD:
|
||||
hcnt_offset = THC_I2C_IC_SS_SCL_HCNT_OFFSET;
|
||||
lcnt_offset = THC_I2C_IC_SS_SCL_LCNT_OFFSET;
|
||||
|
|
@ -1446,25 +1457,43 @@ static int thc_i2c_subip_set_speed(struct thc_device *dev, const u32 speed,
|
|||
break;
|
||||
|
||||
default:
|
||||
dev_err_once(dev->dev, "Unsupported i2c speed %d\n", speed);
|
||||
dev_err_once(dev->dev, "Unsupported i2c speed %d\n", i2c_config->speed);
|
||||
ret = -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &hcnt);
|
||||
ret = thc_i2c_subip_pio_write(dev, hcnt_offset, sizeof(u32), &i2c_config->scl_hcnt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &lcnt);
|
||||
ret = thc_i2c_subip_pio_write(dev, lcnt_offset, sizeof(u32), &i2c_config->scl_lcnt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = I2C_SUBIP_CON_DEFAULT & ~THC_I2C_IC_CON_SPEED;
|
||||
val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, speed);
|
||||
val |= FIELD_PREP(THC_I2C_IC_CON_SPEED, i2c_config->speed);
|
||||
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_CON_OFFSET, sizeof(u32), &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_SDA_HOLD_OFFSET, &read_size, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (i2c_config->sda_tx_hold) {
|
||||
val &= ~THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD;
|
||||
val |= FIELD_PREP(THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD, i2c_config->sda_tx_hold);
|
||||
}
|
||||
|
||||
if (i2c_config->sda_rx_hold) {
|
||||
val &= ~THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD;
|
||||
val |= FIELD_PREP(THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD, i2c_config->sda_rx_hold);
|
||||
}
|
||||
|
||||
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_SDA_HOLD_OFFSET, sizeof(u32), &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1474,6 +1503,7 @@ static u32 i2c_subip_regs[] = {
|
|||
THC_I2C_IC_INTR_MASK_OFFSET,
|
||||
THC_I2C_IC_RX_TL_OFFSET,
|
||||
THC_I2C_IC_TX_TL_OFFSET,
|
||||
THC_I2C_IC_SDA_HOLD_OFFSET,
|
||||
THC_I2C_IC_DMA_CR_OFFSET,
|
||||
THC_I2C_IC_DMA_TDLR_OFFSET,
|
||||
THC_I2C_IC_DMA_RDLR_OFFSET,
|
||||
|
|
@ -1490,20 +1520,19 @@ static u32 i2c_subip_regs[] = {
|
|||
* thc_i2c_subip_init - Initialize and configure THC I2C subsystem
|
||||
*
|
||||
* @dev: The pointer of THC private device context
|
||||
* @target_address: Slave address of touch device (TIC)
|
||||
* @speed: I2C bus frequency speed mode
|
||||
* @hcnt: I2C clock SCL high count
|
||||
* @lcnt: I2C clock SCL low count
|
||||
* @i2c_config: The pointer of THC I2C bus configure structure
|
||||
*
|
||||
* Return: 0 on success, other error codes on failed.
|
||||
*/
|
||||
int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
|
||||
const u32 speed, const u32 hcnt, const u32 lcnt)
|
||||
int thc_i2c_subip_init(struct thc_device *dev, const struct thc_i2c_config *i2c_config)
|
||||
{
|
||||
u32 read_size = sizeof(u32);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!dev || !i2c_config)
|
||||
return -EINVAL;
|
||||
|
||||
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_ENABLE_OFFSET, &read_size, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
|
@ -1513,17 +1542,7 @@ int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = thc_i2c_subip_pio_read(dev, THC_I2C_IC_TAR_OFFSET, &read_size, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val &= ~THC_I2C_IC_TAR_IC_TAR;
|
||||
val |= FIELD_PREP(THC_I2C_IC_TAR_IC_TAR, target_address);
|
||||
ret = thc_i2c_subip_pio_write(dev, THC_I2C_IC_TAR_OFFSET, sizeof(u32), &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = thc_i2c_subip_set_speed(dev, speed, hcnt, lcnt);
|
||||
ret = thc_i2c_subip_bus_config(dev, i2c_config);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,26 @@ enum thc_int_type {
|
|||
THC_UNKNOWN_INT
|
||||
};
|
||||
|
||||
/**
|
||||
* struct thc_i2c_config - THC I2C bus configuration
|
||||
* @target_addr: Slave address of touch device (TIC)
|
||||
* @addr_mode: Slave address mode of touch device (TIC), 7bit or 10bit
|
||||
* @speed: I2C bus frequency speed mode
|
||||
* @scl_hcnt: I2C clock SCL high count
|
||||
* @scl_lcnt: I2C clock SCL low count
|
||||
* @sda_tx_hold: I2C Data SDA transmit hold period
|
||||
* @sda_rx_hold: I2C Data SDA receive hold period
|
||||
*/
|
||||
struct thc_i2c_config {
|
||||
u16 target_addr;
|
||||
u8 addr_mode;
|
||||
u32 speed;
|
||||
u32 scl_hcnt;
|
||||
u32 scl_lcnt;
|
||||
u32 sda_tx_hold;
|
||||
u32 sda_rx_hold;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct thc_device - THC private device struct
|
||||
* @thc_regmap: MMIO regmap structure for accessing THC registers
|
||||
|
|
@ -121,8 +141,7 @@ int thc_spi_write_config(struct thc_device *dev, u32 spi_freq_val,
|
|||
u32 io_mode, u32 opcode, u32 spi_wr_mps, u32 perf_limit);
|
||||
void thc_spi_input_output_address_config(struct thc_device *dev, u32 input_hdr_addr,
|
||||
u32 input_bdy_addr, u32 output_addr);
|
||||
int thc_i2c_subip_init(struct thc_device *dev, const u32 target_address,
|
||||
const u32 speed, const u32 hcnt, const u32 lcnt);
|
||||
int thc_i2c_subip_init(struct thc_device *dev, const struct thc_i2c_config *i2c_config);
|
||||
int thc_i2c_subip_regs_save(struct thc_device *dev);
|
||||
int thc_i2c_subip_regs_restore(struct thc_device *dev);
|
||||
int thc_i2c_set_rx_max_size(struct thc_device *dev, u32 max_rx_size);
|
||||
|
|
|
|||
|
|
@ -561,6 +561,57 @@ static int thc_wait_for_dma_pause(struct thc_device *dev, enum thc_dma_channel c
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* thc_rxdma_reset - Reset all read DMA engines
|
||||
*
|
||||
* @dev: The pointer of THC private device context
|
||||
*
|
||||
* This is a helper function to reset RxDMA configure. It's typically used
|
||||
* for RxDMA recovery when fatal error happens.
|
||||
*
|
||||
* Return: 0 if successful or error code on failure.
|
||||
*/
|
||||
int thc_rxdma_reset(struct thc_device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (mutex_lock_interruptible(&dev->thc_bus_lock))
|
||||
return -EINTR;
|
||||
|
||||
ret = thc_interrupt_quiesce(dev, true);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "Quiesce interrupt failed during RxDMA reset\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = thc_wait_for_dma_pause(dev, THC_RXDMA1);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "Wait for RxDMA1 pause failed during RxDMA reset\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = thc_wait_for_dma_pause(dev, THC_RXDMA2);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "Wait for RxDMA2 pause failed during RxDMA reset\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
thc_dma_unconfigure(dev);
|
||||
|
||||
ret = thc_dma_configure(dev);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "Re-config DMA failed during RxDMA reset\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
thc_interrupt_quiesce(dev, false);
|
||||
|
||||
end:
|
||||
mutex_unlock(&dev->thc_bus_lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(thc_rxdma_reset, "INTEL_THC");
|
||||
|
||||
static int read_dma_buffer(struct thc_device *dev,
|
||||
struct thc_dma_configuration *read_config,
|
||||
u8 prd_table_index, void *read_buff)
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ int thc_dma_allocate(struct thc_device *dev);
|
|||
int thc_dma_configure(struct thc_device *dev);
|
||||
void thc_dma_unconfigure(struct thc_device *dev);
|
||||
void thc_dma_release(struct thc_device *dev);
|
||||
int thc_rxdma_reset(struct thc_device *dev);
|
||||
int thc_rxdma_read(struct thc_device *dev, enum thc_dma_channel dma_channel,
|
||||
void *read_buff, size_t *read_len, int *read_finished);
|
||||
int thc_swdma_read(struct thc_device *dev, void *write_buff, size_t write_len,
|
||||
|
|
|
|||
|
|
@ -887,4 +887,7 @@ enum THC_I2C_SPEED_MODE {
|
|||
#define THC_I2C_IC_DMA_CR_RDMAE BIT(0)
|
||||
#define THC_I2C_IC_DMA_CR_TDMAE BIT(1)
|
||||
|
||||
#define THC_I2C_IC_SDA_HOLD_IC_SDA_TX_HOLD GENMASK(15, 0)
|
||||
#define THC_I2C_IC_SDA_HOLD_IC_SDA_RX_HOLD GENMASK(23, 16)
|
||||
|
||||
#endif /* _INTEL_THC_HW_H_ */
|
||||
|
|
|
|||
|
|
@ -1539,13 +1539,20 @@ static int pidff_check_autocenter(struct pidff_device *pidff,
|
|||
int hid_pidff_init_with_quirks(struct hid_device *hid, u32 initial_quirks)
|
||||
{
|
||||
struct pidff_device *pidff;
|
||||
struct hid_input *hidinput =
|
||||
list_entry(hid->inputs.next, struct hid_input, list);
|
||||
struct input_dev *dev = hidinput->input;
|
||||
struct hid_input *hidinput;
|
||||
struct input_dev *dev;
|
||||
struct ff_device *ff;
|
||||
int max_effects;
|
||||
int error;
|
||||
|
||||
if (list_empty(&hid->inputs)) {
|
||||
hid_err(hid, "no inputs found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
|
||||
dev = hidinput->input;
|
||||
|
||||
hid_dbg(hid, "starting pid init\n");
|
||||
|
||||
if (list_empty(&hid->report_enum[HID_OUTPUT_REPORT].report_list)) {
|
||||
|
|
|
|||
|
|
@ -1192,8 +1192,11 @@ static int int_dist(int x1, int y1, int x2, int y2)
|
|||
static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
|
||||
unsigned char *data)
|
||||
{
|
||||
memcpy(wacom->data, data, 10);
|
||||
u8 *saved_data = wacom->data;
|
||||
|
||||
wacom->data = data;
|
||||
wacom_intuos_irq(wacom);
|
||||
wacom->data = saved_data;
|
||||
|
||||
input_sync(wacom->pen_input);
|
||||
if (wacom->pad_input)
|
||||
|
|
@ -1202,7 +1205,7 @@ static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
|
|||
|
||||
static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
|
||||
{
|
||||
u8 *data = kmemdup(wacom->data, len, GFP_KERNEL);
|
||||
u8 *data = wacom->data;
|
||||
int i = 1;
|
||||
unsigned power_raw, battery_capacity, bat_charging, ps_connected;
|
||||
|
||||
|
|
@ -1242,7 +1245,6 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
|
|||
break;
|
||||
}
|
||||
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -914,6 +914,21 @@ config INPUT_SOC_BUTTON_ARRAY
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called soc_button_array.
|
||||
|
||||
config INPUT_AMD_SFH_TABLETMODE
|
||||
tristate "AMD SFH tablet-mode switch"
|
||||
depends on AMD_SFH_HID
|
||||
select AUXILIARY_BUS
|
||||
help
|
||||
Expose SW_TABLET_MODE for AMD convertible laptops whose
|
||||
operation-mode sensor is provided by the AMD Sensor Fusion Hub.
|
||||
|
||||
Userspace components such as systemd-logind and modern desktop
|
||||
environments react to SW_TABLET_MODE when the device is folded
|
||||
into tablet posture.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called amd_sfh_tabletmode.
|
||||
|
||||
config INPUT_DRV260X_HAPTICS
|
||||
tristate "TI DRV260X haptics support"
|
||||
depends on INPUT && I2C
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ obj-$(CONFIG_INPUT_AD714X_SPI) += ad714x-spi.o
|
|||
obj-$(CONFIG_INPUT_ADXL34X) += adxl34x.o
|
||||
obj-$(CONFIG_INPUT_ADXL34X_I2C) += adxl34x-i2c.o
|
||||
obj-$(CONFIG_INPUT_ADXL34X_SPI) += adxl34x-spi.o
|
||||
obj-$(CONFIG_INPUT_AMD_SFH_TABLETMODE) += amd_sfh_tabletmode.o
|
||||
obj-$(CONFIG_INPUT_APANEL) += apanel.o
|
||||
obj-$(CONFIG_INPUT_ARIEL_PWRBUTTON) += ariel-pwrbutton.o
|
||||
obj-$(CONFIG_INPUT_ARIZONA_HAPTICS) += arizona-haptics.o
|
||||
|
|
|
|||
81
drivers/input/misc/amd_sfh_tabletmode.c
Normal file
81
drivers/input/misc/amd_sfh_tabletmode.c
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* AMD SFH tablet-mode switch driver
|
||||
*
|
||||
* Copyright (c) 2026, Advanced Micro Devices, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
*/
|
||||
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci_ids.h>
|
||||
|
||||
#define POLL_INTERVAL_MS 200
|
||||
|
||||
static void sfh_tm_poll(struct input_dev *input)
|
||||
{
|
||||
struct amd_sfh_info info = {};
|
||||
|
||||
if (amd_get_sfh_info(&info, MT_OP_MODE))
|
||||
return;
|
||||
|
||||
input_report_switch(input, SW_TABLET_MODE,
|
||||
info.op_mode == SFH_MODE_TABLET);
|
||||
input_sync(input);
|
||||
}
|
||||
|
||||
static int sfh_tm_probe(struct auxiliary_device *auxdev,
|
||||
const struct auxiliary_device_id *id)
|
||||
{
|
||||
struct device *dev = &auxdev->dev;
|
||||
struct amd_sfh_info info = {};
|
||||
struct input_dev *input;
|
||||
int error;
|
||||
|
||||
error = amd_get_sfh_info(&info, MT_OP_MODE);
|
||||
if (error)
|
||||
return error == -EINVAL ? -ENODEV : error;
|
||||
|
||||
input = devm_input_allocate_device(dev);
|
||||
if (!input)
|
||||
return -ENOMEM;
|
||||
|
||||
input->name = "AMD SFH tablet mode switch";
|
||||
input->phys = "amd-sfh/tabletmode";
|
||||
input->id.bustype = BUS_HOST;
|
||||
input->id.vendor = PCI_VENDOR_ID_AMD;
|
||||
input_set_capability(input, EV_SW, SW_TABLET_MODE);
|
||||
|
||||
error = input_setup_polling(input, sfh_tm_poll);
|
||||
if (error)
|
||||
return error;
|
||||
input_set_poll_interval(input, POLL_INTERVAL_MS);
|
||||
|
||||
sfh_tm_poll(input);
|
||||
|
||||
error = input_register_device(input);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id sfh_tm_id_table[] = {
|
||||
{ .name = "amd_sfh.tabletmode" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(auxiliary, sfh_tm_id_table);
|
||||
|
||||
static struct auxiliary_driver sfh_tm_driver = {
|
||||
.name = "tabletmode",
|
||||
.id_table = sfh_tm_id_table,
|
||||
.probe = sfh_tm_probe,
|
||||
};
|
||||
module_auxiliary_driver(sfh_tm_driver);
|
||||
|
||||
MODULE_DESCRIPTION("AMD SFH tablet mode switch");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -19,11 +19,13 @@
|
|||
* @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
|
||||
* @MT_SRA: Message ID to know the SRA data from MP2 FW
|
||||
* @MT_OP_MODE: Message ID to know the operating-mode (tablet/laptop) info
|
||||
*/
|
||||
enum sfh_message_type {
|
||||
MT_HPD,
|
||||
MT_ALS,
|
||||
MT_SRA,
|
||||
MT_OP_MODE,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -44,12 +46,24 @@ enum sfh_hpd_info {
|
|||
* @user_present: Populates the user presence information
|
||||
* @platform_type: Operating modes (clamshell, flat, tent, etc.)
|
||||
* @laptop_placement: Device states (ontable, onlap, outbag)
|
||||
* @op_mode: Operating-mode field (see enum sfh_dev_mode); used for tablet detection
|
||||
*/
|
||||
struct amd_sfh_info {
|
||||
u32 ambient_light;
|
||||
u8 user_present;
|
||||
u32 platform_type;
|
||||
u32 laptop_placement;
|
||||
u32 op_mode;
|
||||
};
|
||||
|
||||
/**
|
||||
* enum sfh_dev_mode - SFH operating-mode field (sfh_op_mode.mode, bits 0-2)
|
||||
* @SFH_MODE_LAPTOP: Device is in laptop/clamshell posture
|
||||
* @SFH_MODE_TABLET: Device is in tablet posture
|
||||
*/
|
||||
enum sfh_dev_mode {
|
||||
SFH_MODE_LAPTOP = 1,
|
||||
SFH_MODE_TABLET = 3,
|
||||
};
|
||||
|
||||
enum laptop_placement {
|
||||
|
|
|
|||
|
|
@ -642,6 +642,7 @@ enum hid_battery_status {
|
|||
* @max: maximum battery value from HID descriptor
|
||||
* @report_type: HID report type (input/feature)
|
||||
* @report_id: HID report ID for this battery
|
||||
* @report_offset: bit offset of the capacity field within its report
|
||||
* @charge_status: current charging status
|
||||
* @status: battery reporting status
|
||||
* @capacity: current battery capacity (0-100)
|
||||
|
|
@ -657,6 +658,7 @@ struct hid_battery {
|
|||
__s32 max;
|
||||
__s32 report_type;
|
||||
__s32 report_id;
|
||||
__s32 report_offset;
|
||||
__s32 charge_status;
|
||||
enum hid_battery_status status;
|
||||
__s32 capacity;
|
||||
|
|
@ -1021,7 +1023,7 @@ extern void hid_unregister_driver(struct hid_driver *);
|
|||
|
||||
extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct hid_usage *, __s32);
|
||||
extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report);
|
||||
extern int hidinput_connect(struct hid_device *hid, unsigned int force);
|
||||
extern int hidinput_connect(struct hid_device *hid, unsigned int connect_mask);
|
||||
extern void hidinput_disconnect(struct hid_device *);
|
||||
void hidinput_reset_resume(struct hid_device *hid);
|
||||
|
||||
|
|
|
|||
34
scripts/coccinelle/hid/ff_race.cocci
Normal file
34
scripts/coccinelle/hid/ff_race.cocci
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/// Detect HID drivers that initialize force-feedback after hid_hw_start()
|
||||
/// when HID_CONNECT_HIDINPUT is used. This is a lifecycle violation as
|
||||
/// the input device is already registered.
|
||||
//
|
||||
// Confidence: High
|
||||
// Copyright: (C) 2026 Gemini. GPLv2.
|
||||
|
||||
virtual report
|
||||
|
||||
@r@
|
||||
identifier probe_fn;
|
||||
expression hdev, flags;
|
||||
position p1, p2;
|
||||
@@
|
||||
|
||||
probe_fn(struct hid_device *hdev, ...) {
|
||||
<...
|
||||
hid_hw_start@p1(hdev, flags)
|
||||
...
|
||||
\(input_ff_create\|input_ff_create_memless\)@p2(...)
|
||||
...>
|
||||
}
|
||||
|
||||
@script:python depends on report@
|
||||
p1 << r.p1;
|
||||
p2 << r.p2;
|
||||
flags << r.flags;
|
||||
@@
|
||||
|
||||
# Check if flags include HID_CONNECT_HIDINPUT (0x01) or HID_CONNECT_DEFAULT (0x0f)
|
||||
# Note: HID_CONNECT_DEFAULT is 0x0f, HID_CONNECT_HIDINPUT is 0x01
|
||||
if "HID_CONNECT_HIDINPUT" in flags or "HID_CONNECT_DEFAULT" in flags:
|
||||
msg = "WARNING: force-feedback initialized after hid_hw_start() with HID_CONNECT_HIDINPUT. Input device is already registered at this point. Use .input_configured() instead."
|
||||
coccilib.report.print_report(p2[0], msg)
|
||||
Loading…
Reference in New Issue
Block a user