* [PATCH v10 2/3] HID: nintendo: Add rumble support for Switch 2 controllers
From: Vicki Pfau @ 2026-07-02 3:41 UTC (permalink / raw)
To: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
Cc: Vicki Pfau, Silvan Jegen
In-Reply-To: <20260702034106.1498056-1-vi@endrift.com>
This adds rumble support for both the "HD Rumble" linear resonant actuator
type as used in the Joy-Cons and Pro Controller, as well as the eccentric
rotating mass type used in the GameCube controller. Note that since there's
currently no API for exposing full control of LRAs with evdev, it only
simulates a basic rumble for now.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/hid/Kconfig | 8 +-
drivers/hid/hid-nintendo.c | 214 ++++++++++++++++++++++++++++++++++++-
2 files changed, 216 insertions(+), 6 deletions(-)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 19c77c323ec9..851eed76c236 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -859,10 +859,10 @@ config NINTENDO_FF
depends on HID_NINTENDO
select INPUT_FF_MEMLESS
help
- Say Y here if you have a Nintendo Switch controller and want to enable
- force feedback support for it. This works for both joy-cons, the pro
- controller, and the NSO N64 controller. For the pro controller, both
- rumble motors can be controlled individually.
+ Say Y here if you have a Nintendo Switch or Switch 2 controller and want
+ to enable force feedback support for it. This works for Joy-Cons, the Pro
+ Controllers, and the NSO N64 and GameCube controller. For the Pro
+ Controller, both rumble motors can be controlled individually.
config HID_NTI
tristate "NTI keyboard adapters"
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index f82034decb9e..71a04c438271 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -37,6 +37,7 @@
#include <linux/unaligned.h>
#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/devm-helpers.h>
#include <linux/kernel.h>
#include <linux/hid.h>
#include <linux/idr.h>
@@ -2989,6 +2990,7 @@ enum switch2_init_step {
NS2_INIT_READ_USER_SECONDARY_CALIB,
NS2_INIT_SET_FEATURE_MASK,
NS2_INIT_ENABLE_FEATURES,
+ NS2_INIT_ENABLE_RUMBLE,
NS2_INIT_GRIP_BUTTONS,
NS2_INIT_REPORT_FORMAT,
NS2_INIT_INPUT,
@@ -3020,6 +3022,18 @@ struct switch2_stick_calibration {
struct switch2_axis_calibration y;
};
+struct switch2_hd_rumble {
+ uint16_t hi_freq : 10;
+ uint16_t hi_amp : 10;
+ uint16_t lo_freq : 10;
+ uint16_t lo_amp : 10;
+} __packed;
+
+struct switch2_erm_rumble {
+ uint16_t error;
+ uint16_t amplitude;
+};
+
struct switch2_controller {
struct hid_device *hdev;
struct switch2_cfg_intf *cfg;
@@ -3043,8 +3057,45 @@ struct switch2_controller {
uint32_t player_id;
struct led_classdev *leds;
+
+#if IS_ENABLED(CONFIG_NINTENDO_FF)
+ spinlock_t rumble_lock;
+ uint8_t rumble_seq;
+ union {
+ struct switch2_hd_rumble hd;
+ struct switch2_erm_rumble sd;
+ } rumble;
+ uint64_t last_rumble_work;
+ struct delayed_work rumble_work;
+ uint8_t *rumble_buffer;
+#endif
};
+enum gc_rumble {
+ GC_RUMBLE_OFF = 0,
+ GC_RUMBLE_ON = 1,
+ GC_RUMBLE_STOP = 2,
+};
+
+/*
+ * The highest rumble level for "HD Rumble" is strong enough to potentially damage the controller,
+ * and also leaves your hands feeling like melted jelly, so we set a semi-arbitrary scaling factor
+ * to artificially limit the maximum for safety and comfort. It is currently unknown if the Switch
+ * 2 itself does something similar, but it's quite likely.
+ *
+ * This value must be between 0 and 1024, otherwise the math below will overflow.
+ */
+#define RUMBLE_MAX 450u
+
+/*
+ * Semi-arbitrary values used to simulate the "rumble" sensation of an eccentric rotating
+ * mass type haptic motor on the Switch 2 controllers' linear resonant actuator type haptics.
+ *
+ * The units used are unknown, but the values must be between 0 and 1023.
+ */
+#define RUMBLE_HI_FREQ 0x187
+#define RUMBLE_LO_FREQ 0x112
+
static DEFINE_MUTEX(switch2_controllers_lock);
static LIST_HEAD(switch2_controllers);
@@ -3136,7 +3187,7 @@ static const uint8_t switch2_init_cmd_data[] = {
static const uint8_t switch2_one_data[] = { 0x01, 0x00, 0x00, 0x00 };
static const uint8_t switch2_feature_mask[] = {
- NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG | NS2_FEATURE_IMU,
+ NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG | NS2_FEATURE_IMU | NS2_FEATURE_RUMBLE,
0x00, 0x00, 0x00
};
@@ -3209,6 +3260,125 @@ static void switch2_kref_put(struct kref *refcount)
kfree(ns2);
}
+#if IS_ENABLED(CONFIG_NINTENDO_FF)
+static void switch2_encode_rumble(struct switch2_hd_rumble *rumble, uint8_t buffer[5])
+{
+ buffer[0] = rumble->hi_freq;
+ buffer[1] = (rumble->hi_freq >> 8) | (rumble->hi_amp << 2);
+ buffer[2] = (rumble->hi_amp >> 6) | (rumble->lo_freq << 4);
+ buffer[3] = (rumble->lo_freq >> 4) | (rumble->lo_amp << 6);
+ buffer[4] = rumble->lo_amp >> 2;
+}
+
+static int switch2_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct switch2_controller *ns2 = input_get_drvdata(dev);
+ unsigned long flags;
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ spin_lock_irqsave(&ns2->rumble_lock, flags);
+ if (ns2->ctlr_type == NS2_CTLR_TYPE_GC) {
+ ns2->rumble.sd.amplitude = max(effect->u.rumble.strong_magnitude,
+ (uint16_t) (effect->u.rumble.weak_magnitude >> 1));
+ } else {
+ ns2->rumble.hd.hi_freq = RUMBLE_HI_FREQ;
+ ns2->rumble.hd.lo_freq = RUMBLE_LO_FREQ;
+ ns2->rumble.hd.hi_amp = effect->u.rumble.weak_magnitude * RUMBLE_MAX >> 16;
+ ns2->rumble.hd.lo_amp = effect->u.rumble.strong_magnitude * RUMBLE_MAX >> 16;
+ }
+ spin_unlock_irqrestore(&ns2->rumble_lock, flags);
+
+ schedule_delayed_work(&ns2->rumble_work, 0);
+
+ return 0;
+}
+
+static void switch2_rumble_work(struct work_struct *work)
+{
+ struct switch2_controller *ns2 = container_of(to_delayed_work(work),
+ struct switch2_controller, rumble_work);
+ unsigned long flags;
+ bool active;
+ int ret = 0;
+
+ spin_lock_irqsave(&ns2->rumble_lock, flags);
+ ns2->rumble_buffer[0x1] = 0x50 | ns2->rumble_seq;
+ if (ns2->ctlr_type == NS2_CTLR_TYPE_GC) {
+ ns2->rumble_buffer[0] = 3;
+ if (ns2->rumble.sd.amplitude == 0) {
+ ns2->rumble_buffer[2] = GC_RUMBLE_STOP;
+ ns2->rumble.sd.error = 0;
+ active = false;
+ } else {
+ if (ns2->rumble.sd.error < ns2->rumble.sd.amplitude) {
+ ns2->rumble_buffer[2] = GC_RUMBLE_ON;
+ ns2->rumble.sd.error += U16_MAX - ns2->rumble.sd.amplitude;
+ } else {
+ ns2->rumble_buffer[2] = GC_RUMBLE_OFF;
+ ns2->rumble.sd.error -= ns2->rumble.sd.amplitude;
+ }
+ active = true;
+ }
+ } else {
+ ns2->rumble_buffer[0] = 1;
+ switch2_encode_rumble(&ns2->rumble.hd, &ns2->rumble_buffer[0x2]);
+ active = ns2->rumble.hd.hi_amp || ns2->rumble.hd.lo_amp;
+ if (ns2->ctlr_type == NS2_CTLR_TYPE_PRO) {
+ /*
+ * The Pro Controller contains separate LRAs on each
+ * side that can be controlled individually.
+ */
+ ns2->rumble_buffer[0] = 2;
+ ns2->rumble_buffer[0x11] = 0x50 | ns2->rumble_seq;
+ switch2_encode_rumble(&ns2->rumble.hd, &ns2->rumble_buffer[0x12]);
+ }
+ }
+ ns2->rumble_seq = (ns2->rumble_seq + 1) & 0xF;
+ spin_unlock_irqrestore(&ns2->rumble_lock, flags);
+
+ if (active) {
+ unsigned long interval = msecs_to_jiffies(4);
+ uint64_t current_jiffies = get_jiffies_64();
+
+ if (!ns2->last_rumble_work)
+ ns2->last_rumble_work = current_jiffies;
+ else
+ ns2->last_rumble_work += interval;
+
+ /* Reschedule a little early to make sure the buffer never underruns */
+ interval -= msecs_to_jiffies(2);
+ if (ns2->last_rumble_work + interval >= current_jiffies)
+ schedule_delayed_work(&ns2->rumble_work,
+ ns2->last_rumble_work + interval - current_jiffies);
+ else
+ schedule_delayed_work(&ns2->rumble_work, 0);
+ } else {
+ ns2->last_rumble_work = 0;
+ }
+
+ mutex_lock(&ns2->lock);
+ if (!ns2->hdev) {
+ cancel_delayed_work(&ns2->rumble_work);
+ } else {
+ ret = hid_hw_output_report(ns2->hdev, ns2->rumble_buffer, 64);
+ /*
+ * Don't log on ENODEV, ESHUTDOWN, or EPROTO, which can happen
+ * mid-hotplug. Also cancel any further work on ENODEV or
+ * ESHUTDOWN as they're clear indications that the endpoint
+ * is dead.
+ */
+ if (ret == -ENODEV || ret == -ESHUTDOWN)
+ cancel_delayed_work(&ns2->rumble_work);
+ else if (ret < 0 && ret != -EPROTO)
+ hid_warn_ratelimited(ns2->hdev,
+ "Failed to send output report ret=%d\n", ret);
+ }
+ mutex_unlock(&ns2->lock);
+}
+#endif
+
static int switch2_set_leds(struct switch2_controller *ns2)
{
int i;
@@ -3332,6 +3502,26 @@ static int switch2_init_input(struct switch2_controller *ns2)
return -EINVAL;
}
+#if IS_ENABLED(CONFIG_NINTENDO_FF)
+ ns2->rumble_buffer = devm_kzalloc(&input->dev, 64, GFP_KERNEL);
+ if (!ns2->rumble_buffer) {
+ input_free_device(input);
+ return -ENOMEM;
+ }
+ ret = devm_delayed_work_autocancel(&input->dev, &ns2->rumble_work, switch2_rumble_work);
+ if (ret < 0) {
+ input_free_device(input);
+ return ret;
+ }
+
+ input_set_capability(input, EV_FF, FF_RUMBLE);
+ ret = input_ff_create_memless(input, NULL, switch2_play_effect);
+ if (ret) {
+ input_free_device(input);
+ return ret;
+ }
+#endif
+
hid_info(ns2->hdev, "Firmware version %u.%u.%u (type %i)\n", ns2->version.major,
ns2->version.minor, ns2->version.patch, ns2->version.ctlr_type);
if (ns2->version.dsp_type >= 0)
@@ -3764,7 +3954,16 @@ int switch2_init_controller(struct switch2_controller *ns2)
return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_SET_MASK,
switch2_feature_mask, sizeof(switch2_feature_mask), ns2->cfg);
case NS2_INIT_ENABLE_FEATURES:
- return switch2_features_enable(ns2, NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG);
+ return switch2_features_enable(ns2, NS2_FEATURE_BUTTONS |
+ NS2_FEATURE_ANALOG | NS2_FEATURE_RUMBLE);
+ case NS2_INIT_ENABLE_RUMBLE:
+ /*
+ * It is unclear what this packet is supposed to be for, but it
+ * appears to be needed for rumble to work reliably. The reply
+ * data indicates it might be a query of some sort, but we
+ * ignore the reply so long as it doesn't return an error.
+ */
+ return ns2->cfg->send_command(0x11, 1, NULL, 0, ns2->cfg);
case NS2_INIT_GRIP_BUTTONS:
if (!switch2_ctlr_is_joycon(ns2->ctlr_type)) {
switch2_init_step_done(ns2, ns2->init_step);
@@ -3876,6 +4075,10 @@ int switch2_receive_command(struct switch2_controller *ns2,
switch2_init_step_done(ns2, NS2_INIT_GET_FIRMWARE_INFO);
}
break;
+ case 0x11:
+ if (header->subcommand == 1)
+ switch2_init_step_done(ns2, NS2_INIT_ENABLE_RUMBLE);
+ break;
default:
break;
}
@@ -3995,6 +4198,10 @@ static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id
else
ns2->player_id = ret;
+#if IS_ENABLED(CONFIG_NINTENDO_FF)
+ spin_lock_init(&ns2->rumble_lock);
+#endif
+
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret) {
hid_err(hdev, "hw_start failed %d\n", ret);
@@ -4038,6 +4245,9 @@ static void switch2_remove(struct hid_device *hdev)
ns2->hdev = NULL;
ida_free(&nintendo_player_id_allocator, ns2->player_id);
mutex_unlock(&ns2->lock);
+#if IS_ENABLED(CONFIG_NINTENDO_FF)
+ cancel_delayed_work_sync(&ns2->rumble_work);
+#endif
kref_put(&ns2->refcount, switch2_kref_put);
hid_hw_close(hdev);
hid_hw_stop(hdev);
--
2.54.0
^ permalink raw reply related
* [PATCH v10 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
From: Vicki Pfau @ 2026-07-02 3:41 UTC (permalink / raw)
To: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
Cc: Vicki Pfau, Silvan Jegen
In-Reply-To: <20260702034106.1498056-1-vi@endrift.com>
This adds a new driver for the Switch 2 controllers. The Switch 2 uses an
unusual split-interface design such that input and rumble occur on the main
HID interface, but all other communication occurs over a "configuration"
interface. This is the case on both USB and Bluetooth, so this new driver
uses a split-driver design with the HID interface being the "main" driver
and the configuration interface is a secondary driver that looks up to the
HID interface, sharing resources on a common struct.
Due to using a non-standard pairing interface as well as Bluetooth
communications being extremely limited in the kernel, a custom interface
between userspace and the kernel will need to be designed, along with
bringup in BlueZ. That is beyond the scope of this initial patch, which
only contains the generic HID and USB configuration interface drivers.
This initial work supports general input for the Joy-Con 2, Pro Controller
2, and GameCube NSO controllers. IMU, rumble and battery support is not yet
present.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
MAINTAINERS | 1 +
drivers/hid/Kconfig | 11 +-
drivers/hid/hid-ids.h | 4 +
drivers/hid/hid-nintendo.c | 1276 ++++++++++++++++-
drivers/hid/hid-nintendo.h | 72 +
drivers/input/joystick/Kconfig | 11 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/nintendo-switch2-usb.c | 468 ++++++
8 files changed, 1834 insertions(+), 10 deletions(-)
create mode 100644 drivers/hid/hid-nintendo.h
create mode 100644 drivers/input/joystick/nintendo-switch2-usb.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 4ecd282f8f52..778982ab298e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19051,6 +19051,7 @@ F: drivers/scsi/nsp32*
NINTENDO HID DRIVER
M: Daniel J. Ogorchock <djogorchock@gmail.com>
+M: Vicki Pfau <vi@endrift.com>
L: linux-input@vger.kernel.org
S: Maintained
F: drivers/hid/hid-nintendo*
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index f9bcaeb66385..19c77c323ec9 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -843,10 +843,13 @@ config HID_NINTENDO
depends on LEDS_CLASS
select POWER_SUPPLY
help
- Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
- All controllers support bluetooth, and the Pro Controller also supports
- its USB mode. This also includes support for the Nintendo Switch Online
- Controllers which include the NES, Genesis, SNES, and N64 controllers.
+ Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller, as
+ well as Nintendo Switch 2 Joy-Cons, Pro Controller, and NSO GameCube
+ controllers. All Switch controllers support bluetooth, and the Pro
+ Controller also supports its USB mode. This also includes support for
+ the Nintendo Switch Online Controllers which include the NES, Genesis,
+ SNES, and N64 controllers. Switch 2 controllers currently only support
+ USB mode.
To compile this driver as a module, choose M here: the
module will be called hid-nintendo.
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 1059922baaac..9ba62b8fb894 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1103,6 +1103,10 @@
#define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017
#define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
#define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
+#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONR 0x2066
+#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONL 0x2067
+#define USB_DEVICE_ID_NINTENDO_NS2_PROCON 0x2069
+#define USB_DEVICE_ID_NINTENDO_NS2_GCCON 0x2073
#define USB_VENDOR_ID_NOVATEK 0x0603
#define USB_DEVICE_ID_NOVATEK_PCT 0x0600
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index e7302ec01ff1..f82034decb9e 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * HID driver for Nintendo Switch Joy-Cons and Pro Controllers
+ * HID driver for Nintendo Switch Joy-Cons and Pro Controllers, as well as
+ * Nintendo Switch 2 Joy-Cons, Pro Controller, and GameCube Controller
*
* Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com>
* Portions Copyright (c) 2020 Nadia Holmquist Pedersen <nadia@nhp.sh>
* Copyright (c) 2022 Emily Strickland <linux@emily.st>
* Copyright (c) 2023 Ryan McClelland <rymcclel@gmail.com>
+ * Copyright (c) 2026 Valve Software
*
* The following resources/projects were referenced for this driver:
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
@@ -13,6 +15,8 @@
* https://github.com/FrotBot/SwitchProConLinuxUSB
* https://github.com/MTCKC/ProconXInput
* https://github.com/Davidobot/BetterJoyForCemu
+ * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
+ * https://github.com/ndeadly/switch2_controller_research
* hid-wiimote kernel hid driver
* hid-logitech-hidpp driver
* hid-sony driver
@@ -29,6 +33,7 @@
*/
#include "hid-ids.h"
+#include "hid-nintendo.h"
#include <linux/unaligned.h>
#include <linux/delay.h>
#include <linux/device.h>
@@ -41,6 +46,8 @@
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include "usbhid/usbhid.h"
/*
* Reference the url below for the following HID report defines:
@@ -2662,7 +2669,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
return ret;
}
-static int nintendo_hid_event(struct hid_device *hdev,
+static int joycon_event(struct hid_device *hdev,
struct hid_report *report, u8 *raw_data, int size)
{
struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
@@ -2673,7 +2680,7 @@ static int nintendo_hid_event(struct hid_device *hdev,
return joycon_ctlr_handle_event(ctlr, raw_data, size);
}
-static int nintendo_hid_probe(struct hid_device *hdev,
+static int joycon_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
@@ -2777,7 +2784,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
return ret;
}
-static void nintendo_hid_remove(struct hid_device *hdev)
+static void joycon_remove(struct hid_device *hdev)
{
struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
unsigned long flags;
@@ -2796,7 +2803,7 @@ static void nintendo_hid_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
}
-static int nintendo_hid_resume(struct hid_device *hdev)
+static int joycon_resume(struct hid_device *hdev)
{
struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
int ret;
@@ -2819,7 +2826,7 @@ static int nintendo_hid_resume(struct hid_device *hdev)
return ret;
}
-static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
+static int joycon_suspend(struct hid_device *hdev, pm_message_t message)
{
struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
@@ -2838,7 +2845,1206 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
return 0;
}
+/*
+ * =============================================================================
+ * Switch 2 support
+ * =============================================================================
+ */
+#define NS2_BTNR_B BIT(0)
+#define NS2_BTNR_A BIT(1)
+#define NS2_BTNR_Y BIT(2)
+#define NS2_BTNR_X BIT(3)
+#define NS2_BTNR_R BIT(4)
+#define NS2_BTNR_ZR BIT(5)
+#define NS2_BTNR_PLUS BIT(6)
+#define NS2_BTNR_RS BIT(7)
+
+#define NS2_BTNL_DOWN BIT(0)
+#define NS2_BTNL_RIGHT BIT(1)
+#define NS2_BTNL_LEFT BIT(2)
+#define NS2_BTNL_UP BIT(3)
+#define NS2_BTNL_L BIT(4)
+#define NS2_BTNL_ZL BIT(5)
+#define NS2_BTNL_MINUS BIT(6)
+#define NS2_BTNL_LS BIT(7)
+
+#define NS2_BTN3_C BIT(4)
+#define NS2_BTN3_SR BIT(6)
+#define NS2_BTN3_SL BIT(7)
+
+#define NS2_BTN_JCR_HOME BIT(0)
+#define NS2_BTN_JCR_GR BIT(2)
+#define NS2_BTN_JCR_C NS2_BTN3_C
+#define NS2_BTN_JCR_SR NS2_BTN3_SR
+#define NS2_BTN_JCR_SL NS2_BTN3_SL
+
+#define NS2_BTN_JCL_CAPTURE BIT(0)
+#define NS2_BTN_JCL_GL BIT(2)
+#define NS2_BTN_JCL_SR NS2_BTN3_SR
+#define NS2_BTN_JCL_SL NS2_BTN3_SL
+
+#define NS2_BTN_PRO_HOME BIT(0)
+#define NS2_BTN_PRO_CAPTURE BIT(1)
+#define NS2_BTN_PRO_GR BIT(2)
+#define NS2_BTN_PRO_GL BIT(3)
+#define NS2_BTN_PRO_C NS2_BTN3_C
+
+#define NS2_BTN_GC_HOME BIT(0)
+#define NS2_BTN_GC_CAPTURE BIT(1)
+#define NS2_BTN_GC_C NS2_BTN3_C
+
+#define NS2_TRIGGER_RANGE 4095
+#define NS2_AXIS_MIN -32768
+#define NS2_AXIS_MAX 32767
+
+#define NS2_MAX_PLAYER_ID 8
+
+#define NS2_MAX_INIT_RETRIES 4
+
+#define NS2_FLASH_ADDR_SERIAL 0x13002
+#define NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB 0x130a8
+#define NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB 0x130e8
+#define NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB 0x13140
+#define NS2_FLASH_ADDR_USER_PRIMARY_CALIB 0x1fc040
+#define NS2_FLASH_ADDR_USER_SECONDARY_CALIB 0x1fc080
+
+#define NS2_FLASH_SIZE_SERIAL 0x10
+#define NS2_FLASH_SIZE_FACTORY_AXIS_CALIB 9
+#define NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB 2
+#define NS2_FLASH_SIZE_USER_AXIS_CALIB 11
+
+#define NS2_USER_CALIB_MAGIC 0xa1b2
+
+#define NS2_FEATURE_BUTTONS BIT(0)
+#define NS2_FEATURE_ANALOG BIT(1)
+#define NS2_FEATURE_IMU BIT(2)
+#define NS2_FEATURE_MOUSE BIT(4)
+#define NS2_FEATURE_RUMBLE BIT(5)
+#define NS2_FEATURE_MAGNETO BIT(7)
+
+enum switch2_subcmd_flash {
+ NS2_SUBCMD_FLASH_READ_BLOCK = 0x01,
+ NS2_SUBCMD_FLASH_WRITE_BLOCK = 0x02,
+ NS2_SUBCMD_FLASH_ERASE_BLOCK = 0x03,
+ NS2_SUBCMD_FLASH_READ = 0x04,
+ NS2_SUBCMD_FLASH_WRITE = 0x05,
+};
+
+enum switch2_subcmd_init {
+ NS2_SUBCMD_INIT_SELECT_REPORT = 0xa,
+ NS2_SUBCMD_INIT_USB = 0xd,
+};
+
+enum switch2_subcmd_feature_select {
+ NS2_SUBCMD_FEATSEL_GET_INFO = 0x1,
+ NS2_SUBCMD_FEATSEL_SET_MASK = 0x2,
+ NS2_SUBCMD_FEATSEL_CLEAR_MASK = 0x3,
+ NS2_SUBCMD_FEATSEL_ENABLE = 0x4,
+ NS2_SUBCMD_FEATSEL_DISABLE = 0x5,
+};
+
+enum switch2_subcmd_grip {
+ NS2_SUBCMD_GRIP_GET_INFO = 0x1,
+ NS2_SUBCMD_GRIP_ENABLE_BUTTONS = 0x2,
+ NS2_SUBCMD_GRIP_GET_INFO_EXT = 0x3,
+};
+
+enum switch2_subcmd_led {
+ NS2_SUBCMD_LED_P1 = 0x1,
+ NS2_SUBCMD_LED_P2 = 0x2,
+ NS2_SUBCMD_LED_P3 = 0x3,
+ NS2_SUBCMD_LED_P4 = 0x4,
+ NS2_SUBCMD_LED_ALL_ON = 0x5,
+ NS2_SUBCMD_LED_ALL_OFF = 0x6,
+ NS2_SUBCMD_LED_PATTERN = 0x7,
+ NS2_SUBCMD_LED_BLINK = 0x8,
+};
+
+enum switch2_subcmd_fw_info {
+ NS2_SUBCMD_FW_INFO_GET = 0x1,
+};
+
+enum switch2_ctlr_type {
+ NS2_CTLR_TYPE_JCL = 0x00,
+ NS2_CTLR_TYPE_JCR = 0x01,
+ NS2_CTLR_TYPE_PRO = 0x02,
+ NS2_CTLR_TYPE_GC = 0x03,
+};
+
+enum switch2_report_id {
+ NS2_REPORT_UNIFIED = 0x05,
+ NS2_REPORT_JCL = 0x07,
+ NS2_REPORT_JCR = 0x08,
+ NS2_REPORT_PRO = 0x09,
+ NS2_REPORT_GC = 0x0a,
+};
+
+enum switch2_init_step {
+ NS2_INIT_READ_SERIAL,
+ NS2_INIT_GET_FIRMWARE_INFO,
+ NS2_INIT_READ_FACTORY_PRIMARY_CALIB,
+ NS2_INIT_READ_FACTORY_SECONDARY_CALIB,
+ NS2_INIT_READ_FACTORY_TRIGGER_CALIB,
+ NS2_INIT_READ_USER_PRIMARY_CALIB,
+ NS2_INIT_READ_USER_SECONDARY_CALIB,
+ NS2_INIT_SET_FEATURE_MASK,
+ NS2_INIT_ENABLE_FEATURES,
+ NS2_INIT_GRIP_BUTTONS,
+ NS2_INIT_REPORT_FORMAT,
+ NS2_INIT_INPUT,
+ NS2_INIT_SET_PLAYER_LEDS,
+ NS2_INIT_FINISH,
+ NS2_INIT_DONE,
+};
+
+struct switch2_version_info {
+ uint8_t major;
+ uint8_t minor;
+ uint8_t patch;
+ uint8_t ctlr_type;
+ __le32 unk;
+ int8_t dsp_major;
+ int8_t dsp_minor;
+ int8_t dsp_patch;
+ int8_t dsp_type;
+};
+
+struct switch2_axis_calibration {
+ uint16_t neutral;
+ uint16_t negative;
+ uint16_t positive;
+};
+
+struct switch2_stick_calibration {
+ struct switch2_axis_calibration x;
+ struct switch2_axis_calibration y;
+};
+
+struct switch2_controller {
+ struct hid_device *hdev;
+ struct switch2_cfg_intf *cfg;
+ struct kref refcount;
+
+ char name[64];
+ char phys[64];
+ struct list_head entry;
+ struct mutex lock;
+
+ enum switch2_ctlr_type ctlr_type;
+ enum switch2_init_step init_step;
+ int init_retries;
+ struct input_dev __rcu *input;
+ char serial[NS2_FLASH_SIZE_SERIAL + 1];
+ struct switch2_version_info version;
+
+ struct switch2_stick_calibration stick_calib[2];
+ uint8_t lt_zero;
+ uint8_t rt_zero;
+
+ uint32_t player_id;
+ struct led_classdev *leds;
+};
+
+static DEFINE_MUTEX(switch2_controllers_lock);
+static LIST_HEAD(switch2_controllers);
+
+struct switch2_ctlr_button_mapping {
+ uint32_t code;
+ int byte;
+ uint32_t bit;
+};
+
+static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_mappings[] = {
+ { BTN_DPAD_LEFT, 0, NS2_BTNL_LEFT, },
+ { BTN_DPAD_UP, 0, NS2_BTNL_UP, },
+ { BTN_DPAD_DOWN, 0, NS2_BTNL_DOWN, },
+ { BTN_DPAD_RIGHT, 0, NS2_BTNL_RIGHT, },
+ { BTN_TL, 0, NS2_BTNL_L, },
+ { BTN_TL2, 0, NS2_BTNL_ZL, },
+ { BTN_SELECT, 0, NS2_BTNL_MINUS, },
+ { BTN_THUMBL, 0, NS2_BTNL_LS, },
+ { KEY_RECORD, 1, NS2_BTN_JCL_CAPTURE, },
+ { BTN_GRIPR, 1, NS2_BTN_JCL_SL, },
+ { BTN_GRIPR2, 1, NS2_BTN_JCL_SR, },
+ { BTN_GRIPL, 1, NS2_BTN_JCL_GL, },
+ { /* sentinel */ },
+};
+
+static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings[] = {
+ { BTN_SOUTH, 0, NS2_BTNR_A, },
+ { BTN_EAST, 0, NS2_BTNR_B, },
+ { BTN_NORTH, 0, NS2_BTNR_X, },
+ { BTN_WEST, 0, NS2_BTNR_Y, },
+ { BTN_TR, 0, NS2_BTNR_R, },
+ { BTN_TR2, 0, NS2_BTNR_ZR, },
+ { BTN_START, 0, NS2_BTNR_PLUS, },
+ { BTN_THUMBR, 0, NS2_BTNR_RS, },
+ { BTN_C, 1, NS2_BTN_JCR_C, },
+ { BTN_MODE, 1, NS2_BTN_JCR_HOME, },
+ { BTN_GRIPL2, 1, NS2_BTN_JCR_SL, },
+ { BTN_GRIPL, 1, NS2_BTN_JCR_SR, },
+ { BTN_GRIPR, 1, NS2_BTN_JCR_GR, },
+ { /* sentinel */ },
+};
+
+static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
+ { BTN_SOUTH, 0, NS2_BTNR_A, },
+ { BTN_EAST, 0, NS2_BTNR_B, },
+ { BTN_NORTH, 0, NS2_BTNR_X, },
+ { BTN_WEST, 0, NS2_BTNR_Y, },
+ { BTN_TL, 1, NS2_BTNL_L, },
+ { BTN_TR, 0, NS2_BTNR_R, },
+ { BTN_TL2, 1, NS2_BTNL_ZL, },
+ { BTN_TR2, 0, NS2_BTNR_ZR, },
+ { BTN_SELECT, 1, NS2_BTNL_MINUS, },
+ { BTN_START, 0, NS2_BTNR_PLUS, },
+ { BTN_THUMBL, 1, NS2_BTNL_LS, },
+ { BTN_THUMBR, 0, NS2_BTNR_RS, },
+ { BTN_MODE, 2, NS2_BTN_PRO_HOME },
+ { KEY_RECORD, 2, NS2_BTN_PRO_CAPTURE },
+ { BTN_GRIPR, 2, NS2_BTN_PRO_GR },
+ { BTN_GRIPL, 2, NS2_BTN_PRO_GL },
+ { BTN_C, 2, NS2_BTN_PRO_C },
+ { /* sentinel */ },
+};
+
+static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
+ { BTN_SOUTH, 0, NS2_BTNR_A, },
+ { BTN_EAST, 0, NS2_BTNR_B, },
+ { BTN_NORTH, 0, NS2_BTNR_X, },
+ { BTN_WEST, 0, NS2_BTNR_Y, },
+ { BTN_TL2, 1, NS2_BTNL_L, },
+ { BTN_TR2, 0, NS2_BTNR_R, },
+ { BTN_TL, 1, NS2_BTNL_ZL, },
+ { BTN_TR, 0, NS2_BTNR_ZR, },
+ { BTN_SELECT, 1, NS2_BTNL_MINUS, },
+ { BTN_START, 0, NS2_BTNR_PLUS, },
+ { BTN_MODE, 2, NS2_BTN_GC_HOME },
+ { KEY_RECORD, 2, NS2_BTN_GC_CAPTURE },
+ { BTN_C, 2, NS2_BTN_GC_C },
+ { /* sentinel */ },
+};
+
+static const uint8_t switch2_init_cmd_data[] = {
+ /*
+ * The last 6 bytes of this packet are the MAC address of
+ * the console, but we don't need that for USB
+ */
+ 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+static const uint8_t switch2_one_data[] = { 0x01, 0x00, 0x00, 0x00 };
+
+static const uint8_t switch2_feature_mask[] = {
+ NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG | NS2_FEATURE_IMU,
+ 0x00, 0x00, 0x00
+};
+
+static int switch2_init_controller(struct switch2_controller *ns2);
+
+static void switch2_init_step_done(struct switch2_controller *ns2, enum switch2_init_step step)
+{
+ if (ns2->init_step != step)
+ return;
+
+ ns2->init_retries = 0;
+ ns2->init_step++;
+}
+
+static inline bool switch2_ctlr_is_joycon(enum switch2_ctlr_type type)
+{
+ return type == NS2_CTLR_TYPE_JCL || type == NS2_CTLR_TYPE_JCR;
+}
+
+static struct switch2_controller *switch2_get_controller(const char *phys)
+{
+ struct switch2_controller *ns2;
+
+ guard(mutex)(&switch2_controllers_lock);
+ list_for_each_entry(ns2, &switch2_controllers, entry) {
+ if (strncmp(ns2->phys, phys, sizeof(ns2->phys)) == 0) {
+ if (kref_get_unless_zero(&ns2->refcount))
+ return ns2;
+ }
+ }
+ ns2 = kzalloc(sizeof(*ns2), GFP_KERNEL);
+ if (!ns2)
+ return ERR_PTR(-ENOMEM);
+
+ kref_init(&ns2->refcount);
+ mutex_init(&ns2->lock);
+ INIT_LIST_HEAD(&ns2->entry);
+ list_add(&ns2->entry, &switch2_controllers);
+ strscpy(ns2->phys, phys, sizeof(ns2->phys));
+ return ns2;
+}
+
+static void switch2_controller_put(struct switch2_controller *ns2)
+{
+ struct input_dev *input;
+
+ mutex_lock(&ns2->lock);
+ rcu_read_lock();
+ input = rcu_dereference(ns2->input);
+ rcu_read_unlock();
+
+ rcu_assign_pointer(ns2->input, NULL);
+ synchronize_rcu();
+
+ ns2->init_step = 0;
+ mutex_unlock(&ns2->lock);
+
+ if (input)
+ input_unregister_device(input);
+}
+
+static void switch2_kref_put(struct kref *refcount)
+{
+ struct switch2_controller *ns2 = container_of(refcount,
+ struct switch2_controller, refcount);
+
+ guard(mutex)(&switch2_controllers_lock);
+ list_del_init(&ns2->entry);
+ mutex_destroy(&ns2->lock);
+ kfree(ns2);
+}
+
+static int switch2_set_leds(struct switch2_controller *ns2)
+{
+ int i;
+ uint8_t message[8] = { 0 };
+
+ for (i = 0; i < JC_NUM_LEDS; i++)
+ message[0] |= (!!ns2->leds[i].brightness) << i;
+
+ if (!ns2->cfg)
+ return -ENOTCONN;
+ return ns2->cfg->send_command(NS2_CMD_LED, NS2_SUBCMD_LED_PATTERN,
+ &message, sizeof(message),
+ ns2->cfg);
+}
+
+static int switch2_player_led_brightness_set(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct device *dev = led->dev->parent;
+ struct input_dev *input = to_input_dev(dev);
+ struct switch2_controller *ns2 = input_get_drvdata(input);
+
+ if (!ns2)
+ return -ENODEV;
+
+ guard(mutex)(&ns2->lock);
+ return switch2_set_leds(ns2);
+}
+
+static void switch2_config_buttons(struct input_dev *idev,
+ const struct switch2_ctlr_button_mapping button_mappings[])
+{
+ const struct switch2_ctlr_button_mapping *button;
+
+ for (button = button_mappings; button->code; button++)
+ input_set_capability(idev, EV_KEY, button->code);
+}
+
+static int switch2_input_ref(struct input_dev *input)
+{
+ struct switch2_controller *ns2 = input_get_drvdata(input);
+
+ kref_get(&ns2->refcount);
+
+ return 0;
+}
+
+static void switch2_input_deref(struct input_dev *input)
+{
+ struct switch2_controller *ns2 = input_get_drvdata(input);
+
+ kref_put(&ns2->refcount, switch2_kref_put);
+}
+
+static int switch2_init_input(struct switch2_controller *ns2)
+{
+ struct input_dev *input;
+ struct hid_device *hdev = ns2->hdev;
+ int player_led_pattern;
+ int i;
+ int ret;
+
+ rcu_read_lock();
+ input = rcu_dereference(ns2->input);
+ rcu_read_unlock();
+
+ if (input) {
+ switch2_init_step_done(ns2, NS2_INIT_INPUT);
+ return 0;
+ }
+
+ input = input_allocate_device();
+ if (!input)
+ return -ENOMEM;
+
+ input_set_drvdata(input, ns2);
+ input->open = switch2_input_ref;
+ input->close = switch2_input_deref;
+ input->dev.parent = &hdev->dev;
+ input->id.bustype = hdev->bus;
+ input->id.vendor = hdev->vendor;
+ input->id.product = hdev->product;
+ input->id.version = hdev->version;
+ input->uniq = ns2->serial;
+ input->name = ns2->name;
+ input->phys = hdev->phys;
+
+ switch (ns2->ctlr_type) {
+ case NS2_CTLR_TYPE_JCL:
+ input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ switch2_config_buttons(input, ns2_left_joycon_button_mappings);
+ break;
+ case NS2_CTLR_TYPE_JCR:
+ input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ switch2_config_buttons(input, ns2_right_joycon_button_mappings);
+ break;
+ case NS2_CTLR_TYPE_GC:
+ input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_Z, 0, NS2_TRIGGER_RANGE, 32, 128);
+ input_set_abs_params(input, ABS_RZ, 0, NS2_TRIGGER_RANGE, 32, 128);
+ input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
+ input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
+ switch2_config_buttons(input, ns2_gccon_mappings);
+ break;
+ case NS2_CTLR_TYPE_PRO:
+ input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
+ input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
+ input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
+ switch2_config_buttons(input, ns2_procon_mappings);
+ break;
+ default:
+ input_free_device(input);
+ return -EINVAL;
+ }
+
+ hid_info(ns2->hdev, "Firmware version %u.%u.%u (type %i)\n", ns2->version.major,
+ ns2->version.minor, ns2->version.patch, ns2->version.ctlr_type);
+ if (ns2->version.dsp_type >= 0)
+ hid_info(ns2->hdev, "DSP version %u.%u.%u\n", ns2->version.dsp_major,
+ ns2->version.dsp_minor, ns2->version.dsp_patch);
+
+ ret = input_register_device(input);
+ if (ret < 0) {
+ hid_err(ns2->hdev, "Failed to register input; ret=%d\n", ret);
+ input_free_device(input);
+ return ret;
+ }
+
+ player_led_pattern = ns2->player_id % JC_NUM_LED_PATTERNS;
+ hid_dbg(hdev, "assigned player %d led pattern", player_led_pattern + 1);
+
+ ns2->leds = devm_kcalloc(&input->dev, JC_NUM_LEDS, sizeof(*ns2->leds), GFP_KERNEL);
+ if (!ns2->leds) {
+ hid_err(ns2->hdev, "Failed to allocate LEDs\n");
+ input_free_device(input);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < JC_NUM_LEDS; i++) {
+ struct led_classdev *led = &ns2->leds[i];
+
+ led->brightness = joycon_player_led_patterns[player_led_pattern][i];
+ led->max_brightness = 1;
+ led->brightness_set_blocking = switch2_player_led_brightness_set;
+ led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE | LED_RETAIN_AT_SHUTDOWN;
+ char *name = devm_kasprintf(&input->dev, GFP_KERNEL, "%s:%s:%s",
+ dev_name(&input->dev),
+ "green",
+ joycon_player_led_names[i]);
+
+ if (!name) {
+ dev_err(&input->dev, "Failed to allocate name for player %d LED; ret=%d\n",
+ i + 1, ret);
+ break;
+ }
+
+ led->name = name;
+ ret = devm_led_classdev_register(&input->dev, led);
+ if (ret < 0) {
+ dev_err(&input->dev, "Failed to register player %d LED; ret=%d\n",
+ i + 1, ret);
+ break;
+ }
+ }
+
+ rcu_assign_pointer(ns2->input, input);
+ synchronize_rcu();
+
+ switch2_init_step_done(ns2, NS2_INIT_INPUT);
+ return switch2_init_controller(ns2);
+}
+
+static bool switch2_parse_stick_calibration(struct switch2_stick_calibration *calib,
+ const uint8_t *data)
+{
+ static const uint8_t UNCALIBRATED[9] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+ };
+ if (memcmp(UNCALIBRATED, data, sizeof(UNCALIBRATED)) == 0)
+ return false;
+
+ calib->x.neutral = data[0];
+ calib->x.neutral |= (data[1] & 0x0F) << 8;
+
+ calib->y.neutral = data[1] >> 4;
+ calib->y.neutral |= data[2] << 4;
+
+ calib->x.positive = data[3];
+ calib->x.positive |= (data[4] & 0x0F) << 8;
+
+ calib->y.positive = data[4] >> 4;
+ calib->y.positive |= data[5] << 4;
+
+ calib->x.negative = data[6];
+ calib->x.negative |= (data[7] & 0x0F) << 8;
+
+ calib->y.negative = data[7] >> 4;
+ calib->y.negative |= data[8] << 4;
+
+ return true;
+}
+
+static void switch2_handle_flash_read(struct switch2_controller *ns2, uint8_t size,
+ uint32_t address, const uint8_t *data)
+{
+ bool ok;
+
+ switch (address) {
+ case NS2_FLASH_ADDR_SERIAL:
+ if (size != NS2_FLASH_SIZE_SERIAL)
+ return;
+ memcpy(ns2->serial, data, size);
+ switch2_init_step_done(ns2, NS2_INIT_READ_SERIAL);
+ break;
+ case NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB:
+ if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
+ return;
+ switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_PRIMARY_CALIB);
+ ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], data);
+ if (ns2->hdev) {
+ if (ok) {
+ hid_dbg(ns2->hdev, "Got factory primary stick calibration:\n");
+ hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
+ ns2->stick_calib[0].x.negative,
+ ns2->stick_calib[0].x.neutral,
+ ns2->stick_calib[0].x.positive);
+ hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
+ ns2->stick_calib[0].y.negative,
+ ns2->stick_calib[0].y.neutral,
+ ns2->stick_calib[0].y.positive);
+ } else {
+ hid_dbg(ns2->hdev, "Factory primary stick calibration not present\n");
+ }
+ }
+ break;
+ case NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB:
+ if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
+ return;
+ switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
+ ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], data);
+ if (ns2->hdev) {
+ if (ok) {
+ hid_dbg(ns2->hdev, "Got factory secondary stick calibration:\n");
+ hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
+ ns2->stick_calib[1].x.negative,
+ ns2->stick_calib[1].x.neutral,
+ ns2->stick_calib[1].x.positive);
+ hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
+ ns2->stick_calib[1].y.negative,
+ ns2->stick_calib[1].y.neutral,
+ ns2->stick_calib[1].y.positive);
+ } else {
+ hid_dbg(ns2->hdev, "Factory secondary stick calibration not present\n");
+ }
+ }
+ break;
+ case NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB:
+ if (size != NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB)
+ return;
+ switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
+ if (data[0] != 0xFF && data[1] != 0xFF) {
+ ns2->lt_zero = data[0];
+ ns2->rt_zero = data[1];
+
+ if (ns2->hdev) {
+ hid_dbg(ns2->hdev, "Got factory trigger calibration:\n");
+ hid_dbg(ns2->hdev, "Left zero point: %i\n", ns2->lt_zero);
+ hid_dbg(ns2->hdev, "Right zero point: %i\n", ns2->rt_zero);
+ }
+ } else if (ns2->hdev) {
+ hid_dbg(ns2->hdev, "Factory trigger calibration not present\n");
+ }
+ break;
+ case NS2_FLASH_ADDR_USER_PRIMARY_CALIB:
+ if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
+ return;
+ switch2_init_step_done(ns2, NS2_INIT_READ_USER_PRIMARY_CALIB);
+ if (get_unaligned_le16((__le16 *)data) != NS2_USER_CALIB_MAGIC) {
+ if (ns2->hdev)
+ hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
+ break;
+ }
+
+ ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], &data[2]);
+ if (ns2->hdev) {
+ if (ok) {
+ hid_dbg(ns2->hdev, "Got user primary stick calibration:\n");
+ hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
+ ns2->stick_calib[0].x.negative,
+ ns2->stick_calib[0].x.neutral,
+ ns2->stick_calib[0].x.positive);
+ hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
+ ns2->stick_calib[0].y.negative,
+ ns2->stick_calib[0].y.neutral,
+ ns2->stick_calib[0].y.positive);
+ } else {
+ hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
+ }
+ }
+ break;
+ case NS2_FLASH_ADDR_USER_SECONDARY_CALIB:
+ if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
+ return;
+ switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
+ if (get_unaligned_le16((__le16 *)data) != NS2_USER_CALIB_MAGIC) {
+ if (ns2->hdev)
+ hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
+ break;
+ }
+
+ ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], &data[2]);
+ if (ns2->hdev) {
+ if (ok) {
+ hid_dbg(ns2->hdev, "Got user secondary stick calibration:\n");
+ hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
+ ns2->stick_calib[1].x.negative,
+ ns2->stick_calib[1].x.neutral,
+ ns2->stick_calib[1].x.positive);
+ hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
+ ns2->stick_calib[1].y.negative,
+ ns2->stick_calib[1].y.neutral,
+ ns2->stick_calib[1].y.positive);
+ } else {
+ hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
+ }
+ }
+ break;
+ }
+}
+
+static void switch2_report_buttons(struct input_dev *input, const uint8_t *bytes,
+ const struct switch2_ctlr_button_mapping button_mappings[])
+{
+ const struct switch2_ctlr_button_mapping *button;
+
+ for (button = button_mappings; button->code; button++)
+ input_report_key(input, button->code, bytes[button->byte] & button->bit);
+}
+
+static void switch2_report_axis(struct input_dev *input, struct switch2_axis_calibration *calib,
+ int axis, bool invert, int value)
+{
+ if (calib && calib->neutral && calib->negative && calib->positive) {
+ value -= calib->neutral;
+ value *= NS2_AXIS_MAX + 1;
+ if (value < 0)
+ value /= calib->negative;
+ else
+ value /= calib->positive;
+ } else {
+ value = (value - 2048) * 16;
+ }
+
+ if (invert)
+ value = -value;
+ input_report_abs(input, axis,
+ clamp(value, NS2_AXIS_MIN, NS2_AXIS_MAX));
+}
+
+static void switch2_report_stick(struct input_dev *input, struct switch2_stick_calibration *calib,
+ int x, bool invert_x, int y, bool invert_y, const uint8_t *data)
+{
+ switch2_report_axis(input, &calib->x, x, invert_x, data[0] | ((data[1] & 0x0F) << 8));
+ switch2_report_axis(input, &calib->y, y, invert_y, (data[1] >> 4) | (data[2] << 4));
+}
+
+static void switch2_report_trigger(struct input_dev *input, uint8_t zero, int abs, uint8_t data)
+{
+ int value = (NS2_TRIGGER_RANGE + 1) * (data - zero);
+
+ if (zero != 232)
+ value /= (232 - zero);
+ input_report_abs(input, abs, clamp(value, 0, NS2_TRIGGER_RANGE));
+}
+
+static int switch2_event(struct hid_device *hdev, struct hid_report *report, uint8_t *raw_data,
+ int size)
+{
+ struct switch2_controller *ns2 = hid_get_drvdata(hdev);
+ struct input_dev *input;
+
+ if (report->type != HID_INPUT_REPORT)
+ return 0;
+
+ if (size < 15)
+ return -EINVAL;
+
+ guard(rcu)();
+ input = rcu_dereference(ns2->input);
+
+ if (!input)
+ return 0;
+
+ switch (report->id) {
+ case NS2_REPORT_UNIFIED:
+ /*
+ * TODO
+ * This won't be sent unless the report type gets changed via command
+ * 03-0A, but we should support it at some point regardless.
+ */
+ break;
+ case NS2_REPORT_JCL:
+ switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
+ ABS_Y, true, &raw_data[6]);
+ switch2_report_buttons(input, &raw_data[3], ns2_left_joycon_button_mappings);
+ break;
+ case NS2_REPORT_JCR:
+ switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
+ ABS_Y, true, &raw_data[6]);
+ switch2_report_buttons(input, &raw_data[3], ns2_right_joycon_button_mappings);
+ break;
+ case NS2_REPORT_GC:
+ input_report_abs(input, ABS_HAT0X,
+ !!(raw_data[4] & NS2_BTNL_RIGHT) -
+ !!(raw_data[4] & NS2_BTNL_LEFT));
+ input_report_abs(input, ABS_HAT0Y,
+ !!(raw_data[4] & NS2_BTNL_DOWN) -
+ !!(raw_data[4] & NS2_BTNL_UP));
+ switch2_report_buttons(input, &raw_data[3], ns2_gccon_mappings);
+ switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
+ ABS_Y, true, &raw_data[6]);
+ switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
+ ABS_RY, true, &raw_data[9]);
+ switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[13]);
+ switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[14]);
+ break;
+ case NS2_REPORT_PRO:
+ input_report_abs(input, ABS_HAT0X,
+ !!(raw_data[4] & NS2_BTNL_RIGHT) -
+ !!(raw_data[4] & NS2_BTNL_LEFT));
+ input_report_abs(input, ABS_HAT0Y,
+ !!(raw_data[4] & NS2_BTNL_DOWN) -
+ !!(raw_data[4] & NS2_BTNL_UP));
+ switch2_report_buttons(input, &raw_data[3], ns2_procon_mappings);
+ switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
+ ABS_Y, true, &raw_data[6]);
+ switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
+ ABS_RY, true, &raw_data[9]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ input_sync(input);
+ return 0;
+}
+
+static int switch2_features_enable(struct switch2_controller *ns2, int features)
+{
+ __le32 feature_bits = __cpu_to_le32(features);
+
+ if (!ns2->cfg)
+ return -ENOTCONN;
+ return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_ENABLE,
+ &feature_bits, sizeof(feature_bits),
+ ns2->cfg);
+}
+
+static int switch2_read_flash(struct switch2_controller *ns2, uint32_t address,
+ uint8_t size)
+{
+ uint8_t message[8] = { size, 0x7e };
+
+ if (!ns2->cfg)
+ return -ENOTCONN;
+ put_unaligned_le32(address, &message[4]);
+ return ns2->cfg->send_command(NS2_CMD_FLASH, NS2_SUBCMD_FLASH_READ, message,
+ sizeof(message), ns2->cfg);
+}
+
+static int switch2_set_player_id(struct switch2_controller *ns2, uint32_t player_id)
+{
+ int i;
+ int player_led_pattern = player_id % JC_NUM_LED_PATTERNS;
+
+ for (i = 0; i < JC_NUM_LEDS; i++)
+ ns2->leds[i].brightness = joycon_player_led_patterns[player_led_pattern][i];
+
+ return switch2_set_leds(ns2);
+}
+
+static int switch2_set_report_format(struct switch2_controller *ns2, enum switch2_report_id fmt)
+{
+ __le32 format_id = __cpu_to_le32(fmt);
+
+ if (!ns2->cfg)
+ return -ENOTCONN;
+ return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_SELECT_REPORT,
+ &format_id, sizeof(format_id),
+ ns2->cfg);
+}
+
+int switch2_init_controller(struct switch2_controller *ns2)
+{
+ if (ns2->init_step == NS2_INIT_DONE)
+ return 0;
+
+ if (!ns2->cfg)
+ return -ENOTCONN;
+
+ if (ns2->init_retries > NS2_MAX_INIT_RETRIES) {
+ if (ns2->init_retries == NS2_MAX_INIT_RETRIES + 1) {
+ dev_err(ns2->cfg->dev, "Failed to configure controller\n");
+ ns2->init_retries++;
+ }
+ return -EIO;
+ }
+
+ ns2->init_retries++;
+ switch (ns2->init_step) {
+ case NS2_INIT_READ_SERIAL:
+ return switch2_read_flash(ns2, NS2_FLASH_ADDR_SERIAL,
+ NS2_FLASH_SIZE_SERIAL);
+ case NS2_INIT_GET_FIRMWARE_INFO:
+ return ns2->cfg->send_command(NS2_CMD_FW_INFO, NS2_SUBCMD_FW_INFO_GET,
+ NULL, 0, ns2->cfg);
+ case NS2_INIT_READ_FACTORY_PRIMARY_CALIB:
+ return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB,
+ NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
+ case NS2_INIT_READ_FACTORY_SECONDARY_CALIB:
+ if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
+ switch2_init_step_done(ns2, ns2->init_step);
+ return switch2_init_controller(ns2);
+ }
+ return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB,
+ NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
+ case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
+ if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
+ switch2_init_step_done(ns2, ns2->init_step);
+ return switch2_init_controller(ns2);
+ }
+ return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
+ NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
+ case NS2_INIT_READ_USER_PRIMARY_CALIB:
+ return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_PRIMARY_CALIB,
+ NS2_FLASH_SIZE_USER_AXIS_CALIB);
+ case NS2_INIT_READ_USER_SECONDARY_CALIB:
+ if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
+ switch2_init_step_done(ns2, ns2->init_step);
+ return switch2_init_controller(ns2);
+ }
+ return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_SECONDARY_CALIB,
+ NS2_FLASH_SIZE_USER_AXIS_CALIB);
+ case NS2_INIT_SET_FEATURE_MASK:
+ return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_SET_MASK,
+ switch2_feature_mask, sizeof(switch2_feature_mask), ns2->cfg);
+ case NS2_INIT_ENABLE_FEATURES:
+ return switch2_features_enable(ns2, NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG);
+ case NS2_INIT_GRIP_BUTTONS:
+ if (!switch2_ctlr_is_joycon(ns2->ctlr_type)) {
+ switch2_init_step_done(ns2, ns2->init_step);
+ return switch2_init_controller(ns2);
+ }
+ return ns2->cfg->send_command(NS2_CMD_GRIP, NS2_SUBCMD_GRIP_ENABLE_BUTTONS,
+ switch2_one_data, sizeof(switch2_one_data),
+ ns2->cfg);
+ case NS2_INIT_REPORT_FORMAT:
+ switch (ns2->ctlr_type) {
+ case NS2_CTLR_TYPE_JCL:
+ return switch2_set_report_format(ns2, NS2_REPORT_JCL);
+ case NS2_CTLR_TYPE_JCR:
+ return switch2_set_report_format(ns2, NS2_REPORT_JCR);
+ case NS2_CTLR_TYPE_PRO:
+ return switch2_set_report_format(ns2, NS2_REPORT_PRO);
+ case NS2_CTLR_TYPE_GC:
+ return switch2_set_report_format(ns2, NS2_REPORT_GC);
+ default:
+ switch2_init_step_done(ns2, ns2->init_step);
+ return switch2_init_controller(ns2);
+ }
+ case NS2_INIT_INPUT:
+ if (ns2->hdev)
+ return switch2_init_input(ns2);
+ break;
+ case NS2_INIT_SET_PLAYER_LEDS:
+ return switch2_set_player_id(ns2, ns2->player_id);
+ case NS2_INIT_FINISH:
+ return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_USB,
+ switch2_init_cmd_data, sizeof(switch2_init_cmd_data), ns2->cfg);
+ default:
+ WARN_ON_ONCE(1);
+ break;
+ }
+ return 0;
+}
+
+int switch2_receive_command(struct switch2_controller *ns2,
+ const uint8_t *message, size_t length)
+{
+ const struct switch2_cmd_header *header;
+ int ret = 0;
+
+ if (length < 8)
+ return -EINVAL;
+
+ print_hex_dump_debug("got cmd: ", DUMP_PREFIX_OFFSET, 16, 1, message, length, false);
+
+ mutex_lock(&ns2->lock);
+
+ header = (const struct switch2_cmd_header *)message;
+ if (!(header->flags & NS2_FLAG_OK)) {
+ dev_warn(ns2->cfg->dev, "Packet error %02x replying to command %x:%x",
+ header->flags, header->command, header->subcommand);
+ ret = -EIO;
+ goto exit;
+ }
+ message = &message[8];
+ length -= 8;
+
+ switch (header->command) {
+ case NS2_CMD_FLASH:
+ if (header->subcommand == NS2_SUBCMD_FLASH_READ) {
+ uint8_t read_size;
+ uint32_t read_address;
+
+ if (length < 8) {
+ ret = -EINVAL;
+ goto exit;
+ }
+ read_size = message[0];
+ read_address = get_unaligned_le32(&message[4]);
+ if (length < read_size + 8) {
+ ret = -EINVAL;
+ goto exit;
+ }
+ switch2_handle_flash_read(ns2, read_size, read_address, &message[8]);
+ }
+ break;
+ case NS2_CMD_INIT:
+ if (header->subcommand == NS2_SUBCMD_INIT_USB)
+ switch2_init_step_done(ns2, NS2_INIT_FINISH);
+ else if (header->subcommand == NS2_SUBCMD_INIT_SELECT_REPORT)
+ switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
+ break;
+ case NS2_CMD_GRIP:
+ if (header->subcommand == NS2_SUBCMD_GRIP_ENABLE_BUTTONS)
+ switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
+ break;
+ case NS2_CMD_LED:
+ if (header->subcommand == NS2_SUBCMD_LED_PATTERN)
+ switch2_init_step_done(ns2, NS2_INIT_SET_PLAYER_LEDS);
+ break;
+ case NS2_CMD_FEATSEL:
+ if (header->subcommand == NS2_SUBCMD_FEATSEL_SET_MASK)
+ switch2_init_step_done(ns2, NS2_INIT_SET_FEATURE_MASK);
+ else if (header->subcommand == NS2_SUBCMD_FEATSEL_ENABLE)
+ switch2_init_step_done(ns2, NS2_INIT_ENABLE_FEATURES);
+ break;
+ case NS2_CMD_FW_INFO:
+ if (header->subcommand == NS2_SUBCMD_FW_INFO_GET) {
+ if (length < sizeof(ns2->version)) {
+ ret = -EINVAL;
+ goto exit;
+ }
+ memcpy(&ns2->version, message, sizeof(ns2->version));
+ ns2->ctlr_type = ns2->version.ctlr_type;
+ switch2_init_step_done(ns2, NS2_INIT_GET_FIRMWARE_INFO);
+ }
+ break;
+ default:
+ break;
+ }
+
+exit:
+ if (ns2->init_step < NS2_INIT_DONE)
+ switch2_init_controller(ns2);
+
+ mutex_unlock(&ns2->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(switch2_receive_command);
+
+int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg)
+{
+ struct switch2_controller *ns2 = switch2_get_controller(phys);
+ int ret = 0;
+
+ if (IS_ERR(ns2))
+ return PTR_ERR(ns2);
+
+ mutex_lock(&ns2->lock);
+ if (ns2->cfg) {
+ ret = -EBUSY;
+ goto out;
+ }
+ cfg->parent = ns2;
+ ns2->cfg = cfg;
+
+ if (ns2->hdev)
+ ret = switch2_init_controller(ns2);
+
+ if (ret < 0) {
+ cfg->parent = NULL;
+ ns2->cfg = NULL;
+ }
+
+out:
+ mutex_unlock(&ns2->lock);
+
+ if (ret < 0)
+ kref_put(&ns2->refcount, switch2_kref_put);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(switch2_controller_attach_cfg);
+
+void switch2_controller_detach_cfg(struct switch2_controller *ns2)
+{
+ mutex_lock(&ns2->lock);
+ if (!ns2->cfg || WARN_ON(ns2 != ns2->cfg->parent)) {
+ mutex_unlock(&ns2->lock);
+ return;
+ }
+ ns2->cfg->parent = NULL;
+ ns2->cfg = NULL;
+ mutex_unlock(&ns2->lock);
+ switch2_controller_put(ns2);
+ kref_put(&ns2->refcount, switch2_kref_put);
+}
+EXPORT_SYMBOL_GPL(switch2_controller_detach_cfg);
+
+static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct switch2_controller *ns2;
+ struct usb_device *udev;
+ char phys[64];
+ int ret;
+
+ if (!hid_is_usb(hdev))
+ return -ENODEV;
+
+ udev = hid_to_usb_dev(hdev);
+ if (usb_make_path(udev, phys, sizeof(phys)) < 0)
+ return -EINVAL;
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "parse failed %d\n", ret);
+ return ret;
+ }
+
+ ns2 = switch2_get_controller(phys);
+ if (IS_ERR(ns2))
+ return PTR_ERR(ns2);
+
+ mutex_lock(&ns2->lock);
+ if (ns2->hdev) {
+ mutex_unlock(&ns2->lock);
+ hid_err(hdev,
+ "Second hdev tried to claim same controller, first=%p vs second=%p\n",
+ ns2->hdev, hdev);
+ kref_put(&ns2->refcount, switch2_kref_put);
+ return -EBUSY;
+ }
+ ns2->hdev = hdev;
+ hid_set_drvdata(hdev, ns2);
+
+ switch (hdev->product | (hdev->vendor << 16)) {
+ default:
+ strscpy(ns2->name, hdev->name, sizeof(ns2->name));
+ break;
+ /* Some controllers have slightly wrong names so we override them */
+ case USB_DEVICE_ID_NINTENDO_NS2_JOYCONR | (USB_VENDOR_ID_NINTENDO << 16):
+ /* Missing the "2" in the name */
+ strscpy(ns2->name, "Nintendo Joy-Con 2 (R)", sizeof(ns2->name));
+ break;
+ case USB_DEVICE_ID_NINTENDO_NS2_GCCON | (USB_VENDOR_ID_NINTENDO << 16):
+ /* Has "Nintendo" in the name twice */
+ strscpy(ns2->name, "Nintendo GameCube Controller", sizeof(ns2->name));
+ break;
+ }
+
+ ns2->player_id = U32_MAX;
+ ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
+ if (ret < 0)
+ hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
+ else
+ ns2->player_id = ret;
+
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hw_start failed %d\n", ret);
+ goto err_cleanup;
+ }
+
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_err(hdev, "hw_open failed %d\n", ret);
+ goto err_stop;
+ }
+
+ ret = 0;
+ if (ns2->cfg)
+ ret = switch2_init_controller(ns2);
+
+ if (!ret) {
+ mutex_unlock(&ns2->lock);
+ return 0;
+ }
+
+ hid_hw_close(hdev);
+err_stop:
+ hid_hw_stop(hdev);
+err_cleanup:
+ ida_free(&nintendo_player_id_allocator, ns2->player_id);
+ ns2->hdev = NULL;
+ mutex_unlock(&ns2->lock);
+ switch2_controller_put(ns2);
+ kref_put(&ns2->refcount, switch2_kref_put);
+
+ return ret;
+}
+
+static void switch2_remove(struct hid_device *hdev)
+{
+ struct switch2_controller *ns2 = hid_get_drvdata(hdev);
+
+ switch2_controller_put(ns2);
+ mutex_lock(&ns2->lock);
+ ns2->hdev = NULL;
+ ida_free(&nintendo_player_id_allocator, ns2->player_id);
+ mutex_unlock(&ns2->lock);
+ kref_put(&ns2->refcount, switch2_kref_put);
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
static const struct hid_device_id nintendo_hid_devices[] = {
+ /* Switch devices */
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_PROCON) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
@@ -2863,10 +4069,67 @@ static const struct hid_device_id nintendo_hid_devices[] = {
USB_DEVICE_ID_NINTENDO_N64CON) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_HORI,
USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD) },
+ /* Switch 2 devices */
+ { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
+ USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
{ }
};
MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
+static bool nintendo_is_switch2(struct hid_device *hdev)
+{
+ return hdev->vendor == USB_VENDOR_ID_NINTENDO &&
+ hdev->product >= USB_DEVICE_ID_NINTENDO_NS2_JOYCONR;
+}
+
+static void nintendo_hid_remove(struct hid_device *hdev)
+{
+ if (nintendo_is_switch2(hdev))
+ switch2_remove(hdev);
+ else
+ joycon_remove(hdev);
+}
+
+static int nintendo_hid_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *raw_data, int size)
+{
+ if (nintendo_is_switch2(hdev))
+ return switch2_event(hdev, report, raw_data, size);
+ else
+ return joycon_event(hdev, report, raw_data, size);
+}
+
+static int nintendo_hid_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ if (nintendo_is_switch2(hdev))
+ return switch2_probe(hdev, id);
+ else
+ return joycon_probe(hdev, id);
+}
+
+static int nintendo_hid_resume(struct hid_device *hdev)
+{
+ if (nintendo_is_switch2(hdev))
+ return 0;
+ else
+ return joycon_resume(hdev);
+}
+
+static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
+{
+ if (nintendo_is_switch2(hdev))
+ return 0;
+ else
+ return joycon_suspend(hdev, message);
+}
+
static struct hid_driver nintendo_hid_driver = {
.name = "nintendo",
.id_table = nintendo_hid_devices,
@@ -2894,4 +4157,5 @@ MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
MODULE_AUTHOR("Emily Strickland <linux@emily.st>");
MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>");
+MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");
diff --git a/drivers/hid/hid-nintendo.h b/drivers/hid/hid-nintendo.h
new file mode 100644
index 000000000000..7aff22f30266
--- /dev/null
+++ b/drivers/hid/hid-nintendo.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * HID driver for Nintendo Switch 2 controllers
+ *
+ * Copyright (c) 2025 Valve Software
+ *
+ * This driver is based on the following work:
+ * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
+ * https://github.com/ndeadly/switch2_controller_research
+ */
+
+#ifndef __HID_NINTENDO_H
+#define __HID_NINTENDO_H
+
+#include <linux/bits.h>
+
+#define NS2_FLAG_OK BIT(0)
+#define NS2_FLAG_NACK BIT(2)
+
+enum switch2_cmd {
+ NS2_CMD_NFC = 0x01,
+ NS2_CMD_FLASH = 0x02,
+ NS2_CMD_INIT = 0x03,
+ NS2_CMD_GRIP = 0x08,
+ NS2_CMD_LED = 0x09,
+ NS2_CMD_VIBRATE = 0x0a,
+ NS2_CMD_BATTERY = 0x0b,
+ NS2_CMD_FEATSEL = 0x0c,
+ NS2_CMD_FW_UPD = 0x0d,
+ NS2_CMD_FW_INFO = 0x10,
+ NS2_CMD_BT_PAIR = 0x15,
+};
+
+enum switch2_direction {
+ NS2_DIR_IN = 0x00,
+ NS2_DIR_OUT = 0x90,
+};
+
+enum switch2_transport {
+ NS2_TRANS_USB = 0x00,
+ NS2_TRANS_BT = 0x01,
+};
+
+struct switch2_cmd_header {
+ uint8_t command;
+ uint8_t flags;
+ uint8_t transport;
+ uint8_t subcommand;
+ uint8_t unk1;
+ uint8_t length;
+ uint16_t unk2;
+};
+static_assert(sizeof(struct switch2_cmd_header) == 8);
+
+struct device;
+struct switch2_controller;
+struct switch2_cfg_intf {
+ struct switch2_controller *parent;
+ struct device *dev;
+
+ int (*send_command)(enum switch2_cmd command, uint8_t subcommand,
+ const void *message, size_t length,
+ struct switch2_cfg_intf *intf);
+};
+
+int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg);
+void switch2_controller_detach_cfg(struct switch2_controller *controller);
+
+int switch2_receive_command(struct switch2_controller *controller,
+ const uint8_t *message, size_t length);
+
+#endif
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 7755e5b454d2..868262c6ccd9 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -422,4 +422,15 @@ config JOYSTICK_SEESAW
To compile this driver as a module, choose M here: the module will be
called adafruit-seesaw.
+config JOYSTICK_NINTENDO_SWITCH2_USB
+ tristate "Wired Nintendo Switch 2 controller support"
+ depends on HID_NINTENDO
+ depends on USB
+ help
+ Say Y here if you want to enable support for wired Nintendo Switch 2
+ controllers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called nintendo-switch2-usb.
+
endif
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 9976f596a920..8f92900ae885 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o
obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o
obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o
obj-$(CONFIG_JOYSTICK_STINGER) += stinger.o
+obj-$(CONFIG_JOYSTICK_NINTENDO_SWITCH2_USB) += nintendo-switch2-usb.o
obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o
obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o
obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
diff --git a/drivers/input/joystick/nintendo-switch2-usb.c b/drivers/input/joystick/nintendo-switch2-usb.c
new file mode 100644
index 000000000000..a6999a0a26ae
--- /dev/null
+++ b/drivers/input/joystick/nintendo-switch2-usb.c
@@ -0,0 +1,468 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB driver for Nintendo Switch 2 controllers configuration interface
+ *
+ * Copyright (c) 2025 Valve Software
+ *
+ * This driver is based on the following work:
+ * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
+ * https://github.com/ndeadly/switch2_controller_research
+ */
+
+#include "../../hid/hid-ids.h"
+#include "../../hid/hid-nintendo.h"
+#include <linux/module.h>
+#include <linux/usb/input.h>
+
+#define NS2_BULK_SIZE 64
+#define NS2_IN_URBS 2
+#define NS2_OUT_URBS 4
+
+static struct usb_driver switch2_usb;
+
+enum switch2_urb_state {
+ NS2_URB_FREE,
+ NS2_URB_OUT,
+ NS2_URB_IN,
+};
+
+struct switch2_urb {
+ struct urb *urb;
+ uint8_t *data;
+ enum switch2_urb_state state;
+};
+
+struct switch2_usb {
+ struct switch2_cfg_intf cfg;
+ struct usb_device *udev;
+
+ struct switch2_urb bulk_in[NS2_IN_URBS];
+ struct usb_anchor bulk_in_anchor;
+ bool shutdown;
+ spinlock_t bulk_in_lock;
+
+ struct switch2_urb bulk_out[NS2_OUT_URBS];
+ struct usb_anchor bulk_out_anchor;
+ spinlock_t bulk_out_lock;
+
+ struct work_struct message_in_work;
+};
+
+static void switch2_bulk_in(struct urb *urb)
+{
+ struct switch2_usb *ns2_usb = urb->context;
+ int i;
+ bool schedule = false;
+ unsigned long flags;
+
+ switch (urb->status) {
+ case 0:
+ schedule = true;
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -EPIPE:
+ break;
+ default:
+ dev_dbg(&ns2_usb->udev->dev, "unknown input urb status: %d\n", urb->status);
+ break;
+ }
+
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ for (i = 0; i < NS2_IN_URBS; i++) {
+ int err;
+ struct switch2_urb *ns2_urb;
+
+ if (ns2_usb->bulk_in[i].urb == urb) {
+ if (schedule) {
+ ns2_usb->bulk_in[i].state = NS2_URB_IN;
+ continue;
+ } else {
+ ns2_usb->bulk_in[i].state = NS2_URB_FREE;
+ }
+ }
+
+ if (ns2_usb->bulk_in[i].state != NS2_URB_FREE)
+ continue;
+
+ /*
+ * We want exactly one bulk in URB scheduled at a time, so only
+ * reschedule this immediately if nothing else is scheduled
+ * currently.
+ */
+ if (!usb_anchor_empty(&ns2_usb->bulk_in_anchor) || ns2_usb->shutdown)
+ continue;
+
+ ns2_urb = &ns2_usb->bulk_in[i];
+ if (!ns2_urb)
+ continue;
+
+ usb_anchor_urb(ns2_urb->urb, &ns2_usb->bulk_in_anchor);
+ err = usb_submit_urb(ns2_urb->urb, GFP_ATOMIC);
+ if (err) {
+ usb_unanchor_urb(ns2_urb->urb);
+ dev_dbg(&ns2_usb->udev->dev, "failed to queue input urb: %d\n", err);
+ } else {
+ ns2_urb->state = NS2_URB_OUT;
+ }
+ }
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+
+ if (schedule)
+ schedule_work(&ns2_usb->message_in_work);
+}
+
+static void switch2_bulk_out(struct urb *urb)
+{
+ struct switch2_usb *ns2_usb = urb->context;
+ int i;
+
+ guard(spinlock_irqsave)(&ns2_usb->bulk_out_lock);
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -EPIPE:
+ break;
+ default:
+ dev_dbg(&ns2_usb->udev->dev, "unknown output urb status: %d\n", urb->status);
+ break;
+ }
+
+ for (i = 0; i < NS2_OUT_URBS; i++) {
+ if (ns2_usb->bulk_out[i].urb != urb)
+ continue;
+
+ ns2_usb->bulk_out[i].state = NS2_URB_FREE;
+ break;
+ }
+}
+
+static int switch2_usb_send_cmd(enum switch2_cmd command, uint8_t subcommand,
+ const void *message, size_t size, struct switch2_cfg_intf *cfg)
+{
+ struct switch2_usb *ns2_usb = (struct switch2_usb *)cfg;
+ struct switch2_urb *urb = NULL;
+ int i;
+ int ret;
+ unsigned long flags;
+
+ struct switch2_cmd_header header = {
+ command, NS2_DIR_OUT | NS2_FLAG_OK, NS2_TRANS_USB, subcommand, 0, size
+ };
+
+ if (WARN_ON(size > 56))
+ return -EINVAL;
+
+ spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
+ for (i = 0; i < NS2_OUT_URBS; i++) {
+ if (ns2_usb->bulk_out[i].state != NS2_URB_FREE)
+ continue;
+
+ urb = &ns2_usb->bulk_out[i];
+ urb->state = NS2_URB_OUT;
+ break;
+ }
+ spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
+
+ if (!urb) {
+ dev_warn(&ns2_usb->udev->dev, "output queue full, dropping message\n");
+ return -ENOBUFS;
+ }
+
+ memcpy(urb->data, &header, sizeof(header));
+ if (message && size)
+ memcpy(&urb->data[8], message, size);
+ urb->urb->transfer_buffer_length = size + sizeof(header);
+
+ print_hex_dump_debug("sending cmd: ", DUMP_PREFIX_OFFSET, 16, 1, urb->data,
+ size + sizeof(header), false);
+
+ usb_anchor_urb(urb->urb, &ns2_usb->bulk_out_anchor);
+ ret = usb_submit_urb(urb->urb, GFP_KERNEL);
+ if (ret) {
+ if (ret != -ENODEV)
+ dev_warn(&ns2_usb->udev->dev, "failed to submit output urb: %i", ret);
+ spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
+ urb->state = NS2_URB_FREE;
+ spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
+ usb_unanchor_urb(urb->urb);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void switch2_usb_message_in_work(struct work_struct *work)
+{
+ struct switch2_usb *ns2_usb = container_of(work, struct switch2_usb, message_in_work);
+ struct switch2_urb *urb;
+ int err;
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ for (i = 0; i < NS2_IN_URBS; i++) {
+ urb = &ns2_usb->bulk_in[i];
+ if (urb->state != NS2_URB_IN)
+ continue;
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+
+ if (ns2_usb->cfg.parent) {
+ err = switch2_receive_command(ns2_usb->cfg.parent,
+ urb->urb->transfer_buffer, urb->urb->actual_length);
+ if (err)
+ dev_dbg(&ns2_usb->udev->dev, "receive command failed: %d\n", err);
+ } else {
+ dev_err(&ns2_usb->udev->dev,
+ "Got message before controller is fully set up; discarding\n");
+ }
+
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ urb->state = NS2_URB_FREE;
+ /*
+ * We want exactly one bulk in URB scheduled at a time, so only
+ * reschedule this immediately if nothing else is scheduled
+ * currently.
+ */
+ if (!usb_anchor_empty(&ns2_usb->bulk_in_anchor) || ns2_usb->shutdown)
+ continue;
+
+ usb_anchor_urb(urb->urb, &ns2_usb->bulk_in_anchor);
+ err = usb_submit_urb(urb->urb, GFP_ATOMIC);
+ if (err) {
+ usb_unanchor_urb(urb->urb);
+ dev_dbg(&ns2_usb->udev->dev,
+ "failed to queue input urb: %d\n", err);
+ } else {
+ urb->state = NS2_URB_OUT;
+ }
+ }
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+}
+
+static int switch2_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct switch2_usb *ns2_usb;
+ struct usb_device *udev;
+ struct usb_endpoint_descriptor *bulk_in, *bulk_out;
+ struct urb *urb;
+ uint8_t *data;
+ char phys[64];
+ int ret;
+ int i;
+ unsigned long flags;
+
+ udev = interface_to_usbdev(intf);
+ if (usb_make_path(udev, phys, sizeof(phys)) < 0)
+ return -EINVAL;
+
+ ret = usb_find_common_endpoints(intf->cur_altsetting, &bulk_in, &bulk_out, NULL, NULL);
+ if (ret) {
+ dev_err(&intf->dev, "failed to find bulk EPs\n");
+ return ret;
+ }
+
+ ns2_usb = devm_kzalloc(&intf->dev, sizeof(*ns2_usb), GFP_KERNEL);
+ if (!ns2_usb)
+ return -ENOMEM;
+
+ init_usb_anchor(&ns2_usb->bulk_out_anchor);
+ spin_lock_init(&ns2_usb->bulk_out_lock);
+ init_usb_anchor(&ns2_usb->bulk_in_anchor);
+ spin_lock_init(&ns2_usb->bulk_in_lock);
+ INIT_WORK(&ns2_usb->message_in_work, switch2_usb_message_in_work);
+
+ ns2_usb->udev = udev;
+ for (i = 0; i < NS2_IN_URBS; i++) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ ret = -ENOMEM;
+ goto err_free_in;
+ }
+
+ data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!data) {
+ usb_free_urb(urb);
+ ret = -ENOMEM;
+ goto err_free_in;
+ }
+
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ usb_fill_bulk_urb(urb, udev,
+ usb_rcvbulkpipe(udev, bulk_in->bEndpointAddress),
+ data, NS2_BULK_SIZE, switch2_bulk_in, ns2_usb);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ ns2_usb->bulk_in[i].urb = urb;
+ ns2_usb->bulk_in[i].data = data;
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+ }
+
+ for (i = 0; i < NS2_OUT_URBS; i++) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ ret = -ENOMEM;
+ goto err_free_out;
+ }
+
+ data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!data) {
+ usb_free_urb(urb);
+ ret = -ENOMEM;
+ goto err_free_out;
+ }
+
+ spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
+ usb_fill_bulk_urb(urb, udev,
+ usb_sndbulkpipe(udev, bulk_out->bEndpointAddress),
+ data, NS2_BULK_SIZE, switch2_bulk_out, ns2_usb);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ ns2_usb->bulk_out[i].urb = urb;
+ ns2_usb->bulk_out[i].data = data;
+ spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
+ }
+
+ usb_set_intfdata(intf, ns2_usb);
+
+ ns2_usb->cfg.dev = &ns2_usb->udev->dev;
+ ns2_usb->cfg.send_command = switch2_usb_send_cmd;
+
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ ns2_usb->bulk_in[0].state = NS2_URB_OUT;
+ usb_anchor_urb(ns2_usb->bulk_in[0].urb, &ns2_usb->bulk_in_anchor);
+ ret = usb_submit_urb(ns2_usb->bulk_in[0].urb, GFP_ATOMIC);
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+
+ if (ret < 0)
+ goto err_free_out;
+
+ ret = switch2_controller_attach_cfg(phys, &ns2_usb->cfg);
+ if (ret < 0)
+ goto err_free_out;
+
+ return 0;
+
+err_free_out:
+ usb_kill_anchored_urbs(&ns2_usb->bulk_out_anchor);
+ for (i = 0; i < NS2_OUT_URBS; i++) {
+ spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
+ urb = ns2_usb->bulk_out[i].urb;
+ data = ns2_usb->bulk_out[i].data;
+ if (!urb) {
+ spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
+ continue;
+ }
+
+ ns2_usb->bulk_out[i].urb = NULL;
+ ns2_usb->bulk_out[i].data = NULL;
+ spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
+
+ usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
+ usb_free_urb(urb);
+ }
+err_free_in:
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ ns2_usb->shutdown = true;
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+
+ usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor);
+ cancel_work_sync(&ns2_usb->message_in_work);
+ for (i = 0; i < NS2_IN_URBS; i++) {
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ urb = ns2_usb->bulk_in[i].urb;
+ data = ns2_usb->bulk_in[i].data;
+ if (!urb) {
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+ continue;
+ }
+
+ ns2_usb->bulk_in[i].urb = NULL;
+ ns2_usb->bulk_in[i].data = NULL;
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+
+ usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
+ usb_free_urb(urb);
+ }
+ devm_kfree(&intf->dev, ns2_usb);
+
+ return ret;
+}
+
+static void switch2_usb_disconnect(struct usb_interface *intf)
+{
+ struct switch2_usb *ns2_usb = usb_get_intfdata(intf);
+ unsigned long flags;
+ struct urb *urb;
+ uint8_t *data;
+ int i;
+
+ /* Prevent any further IN URBs from being scheduled */
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ ns2_usb->shutdown = true;
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+
+ usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor);
+ cancel_work_sync(&ns2_usb->message_in_work);
+ for (i = 0; i < NS2_IN_URBS; i++) {
+ spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
+ urb = ns2_usb->bulk_in[i].urb;
+ data = ns2_usb->bulk_in[i].data;
+ ns2_usb->bulk_in[i].urb = NULL;
+ ns2_usb->bulk_in[i].data = NULL;
+ spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
+
+ usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
+ usb_free_urb(urb);
+ }
+
+ /*
+ * We need to detach *before* we kill the out URBs to make sure no
+ * further URBs get scheduled by the HID endpoint in the meantime.
+ */
+ switch2_controller_detach_cfg(ns2_usb->cfg.parent);
+
+ usb_kill_anchored_urbs(&ns2_usb->bulk_out_anchor);
+ for (i = 0; i < NS2_OUT_URBS; i++) {
+ spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
+ urb = ns2_usb->bulk_out[i].urb;
+ data = ns2_usb->bulk_out[i].data;
+ ns2_usb->bulk_out[i].urb = NULL;
+ ns2_usb->bulk_out[i].data = NULL;
+ spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
+
+ usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
+ usb_free_urb(urb);
+ }
+}
+
+#define SWITCH2_CONTROLLER(vend, prod) \
+ USB_DEVICE_AND_INTERFACE_INFO(vend, prod, USB_CLASS_VENDOR_SPEC, 0, 0)
+
+static const struct usb_device_id switch2_usb_devices[] = {
+ { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
+ { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
+ { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
+ { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, switch2_usb_devices);
+
+static struct usb_driver switch2_usb = {
+ .name = "nintendo-switch2",
+ .id_table = switch2_usb_devices,
+ .probe = switch2_usb_probe,
+ .disconnect = switch2_usb_disconnect,
+};
+module_usb_driver(switch2_usb);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
+MODULE_DESCRIPTION("Driver for Nintendo Switch 2 Controllers");
--
2.54.0
^ permalink raw reply related
* [PATCH v10 3/3] HID: nintendo: Add unified report format support
From: Vicki Pfau @ 2026-07-02 3:41 UTC (permalink / raw)
To: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
Cc: Vicki Pfau, Silvan Jegen
In-Reply-To: <20260702034106.1498056-1-vi@endrift.com>
This adds support for the "unified" report format that all controllers also
support, which has overlapping fields for like buttons and axes between
them.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/hid/hid-nintendo.c | 151 +++++++++++++++++++++++++++++++++++--
1 file changed, 146 insertions(+), 5 deletions(-)
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 71a04c438271..d87fae2f64a2 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -2873,6 +2873,36 @@ static int joycon_suspend(struct hid_device *hdev, pm_message_t message)
#define NS2_BTN3_SR BIT(6)
#define NS2_BTN3_SL BIT(7)
+#define NS2_BTN_U1_Y BIT(0)
+#define NS2_BTN_U1_X BIT(1)
+#define NS2_BTN_U1_B BIT(2)
+#define NS2_BTN_U1_A BIT(3)
+#define NS2_BTN_U1_SR BIT(4)
+#define NS2_BTN_U1_SL BIT(5)
+#define NS2_BTN_U1_R BIT(6)
+#define NS2_BTN_U1_ZR BIT(7)
+
+#define NS2_BTN_U2_MINUS BIT(0)
+#define NS2_BTN_U2_PLUS BIT(1)
+#define NS2_BTN_U2_RS BIT(2)
+#define NS2_BTN_U2_LS BIT(3)
+#define NS2_BTN_U2_HOME BIT(4)
+#define NS2_BTN_U2_CAPTURE BIT(5)
+#define NS2_BTN_U2_C BIT(6)
+
+#define NS2_BTN_U3_DOWN BIT(0)
+#define NS2_BTN_U3_UP BIT(1)
+#define NS2_BTN_U3_RIGHT BIT(2)
+#define NS2_BTN_U3_LEFT BIT(3)
+#define NS2_BTN_U3_SR BIT(4)
+#define NS2_BTN_U3_SL BIT(5)
+#define NS2_BTN_U3_L BIT(6)
+#define NS2_BTN_U3_ZL BIT(7)
+
+#define NS2_BTN_U4_GR BIT(0)
+#define NS2_BTN_U4_GL BIT(1)
+#define NS2_BTN_U4_HEADSET BIT(5)
+
#define NS2_BTN_JCR_HOME BIT(0)
#define NS2_BTN_JCR_GR BIT(2)
#define NS2_BTN_JCR_C NS2_BTN3_C
@@ -3121,6 +3151,22 @@ static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_mappings[
{ /* sentinel */ },
};
+static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_unified_mappings[] = {
+ { BTN_DPAD_LEFT, 2, NS2_BTN_U3_LEFT, },
+ { BTN_DPAD_UP, 2, NS2_BTN_U3_UP, },
+ { BTN_DPAD_DOWN, 2, NS2_BTN_U3_DOWN, },
+ { BTN_DPAD_RIGHT, 2, NS2_BTN_U3_RIGHT, },
+ { BTN_TL, 2, NS2_BTN_U3_L, },
+ { BTN_TL2, 2, NS2_BTN_U3_ZL, },
+ { BTN_SELECT, 1, NS2_BTN_U2_MINUS, },
+ { BTN_THUMBL, 1, NS2_BTN_U2_LS, },
+ { KEY_RECORD, 1, NS2_BTN_U2_CAPTURE, },
+ { BTN_GRIPR, 2, NS2_BTN_U3_SL, },
+ { BTN_GRIPR2, 2, NS2_BTN_U3_SR, },
+ { BTN_GRIPL, 3, NS2_BTN_U4_GL, },
+ { /* sentinel */ },
+};
+
static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings[] = {
{ BTN_SOUTH, 0, NS2_BTNR_A, },
{ BTN_EAST, 0, NS2_BTNR_B, },
@@ -3138,6 +3184,23 @@ static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings
{ /* sentinel */ },
};
+static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_unified_mappings[] = {
+ { BTN_SOUTH, 0, NS2_BTN_U1_A, },
+ { BTN_EAST, 0, NS2_BTN_U1_B, },
+ { BTN_NORTH, 0, NS2_BTN_U1_X, },
+ { BTN_WEST, 0, NS2_BTN_U1_Y, },
+ { BTN_TR, 0, NS2_BTN_U1_R, },
+ { BTN_TR2, 0, NS2_BTN_U1_ZR },
+ { BTN_START, 1, NS2_BTN_U2_PLUS, },
+ { BTN_THUMBR, 1, NS2_BTN_U2_RS, },
+ { BTN_C, 1, NS2_BTN_U2_C, },
+ { BTN_MODE, 1, NS2_BTN_U2_HOME, },
+ { BTN_GRIPL2, 0, NS2_BTN_U1_SL, },
+ { BTN_GRIPL, 0, NS2_BTN_U1_SR, },
+ { BTN_GRIPR, 3, NS2_BTN_U4_GR, },
+ { /* sentinel */ },
+};
+
static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
{ BTN_SOUTH, 0, NS2_BTNR_A, },
{ BTN_EAST, 0, NS2_BTNR_B, },
@@ -3159,6 +3222,27 @@ static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
{ /* sentinel */ },
};
+static const struct switch2_ctlr_button_mapping ns2_procon_unified_mappings[] = {
+ { BTN_SOUTH, 0, NS2_BTN_U1_A, },
+ { BTN_EAST, 0, NS2_BTN_U1_B, },
+ { BTN_NORTH, 0, NS2_BTN_U1_X, },
+ { BTN_WEST, 0, NS2_BTN_U1_Y, },
+ { BTN_TL, 2, NS2_BTN_U3_L, },
+ { BTN_TR, 0, NS2_BTN_U1_R, },
+ { BTN_TL2, 2, NS2_BTN_U3_ZL, },
+ { BTN_TR2, 0, NS2_BTN_U1_ZR, },
+ { BTN_SELECT, 1, NS2_BTN_U2_MINUS, },
+ { BTN_START, 1, NS2_BTN_U2_PLUS, },
+ { BTN_THUMBL, 1, NS2_BTN_U2_LS, },
+ { BTN_THUMBR, 1, NS2_BTN_U2_RS, },
+ { BTN_MODE, 1, NS2_BTN_U2_HOME },
+ { KEY_RECORD, 1, NS2_BTN_U2_CAPTURE },
+ { BTN_GRIPR, 3, NS2_BTN_U4_GR },
+ { BTN_GRIPL, 3, NS2_BTN_U4_GL },
+ { BTN_C, 1, NS2_BTN_U2_C },
+ { /* sentinel */ },
+};
+
static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
{ BTN_SOUTH, 0, NS2_BTNR_A, },
{ BTN_EAST, 0, NS2_BTNR_B, },
@@ -3176,6 +3260,23 @@ static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
{ /* sentinel */ },
};
+static const struct switch2_ctlr_button_mapping ns2_gccon_unified_mappings[] = {
+ { BTN_SOUTH, 0, NS2_BTN_U1_A, },
+ { BTN_EAST, 0, NS2_BTN_U1_B, },
+ { BTN_NORTH, 0, NS2_BTN_U1_X, },
+ { BTN_WEST, 0, NS2_BTN_U1_Y, },
+ { BTN_TL2, 2, NS2_BTN_U3_L, },
+ { BTN_TR2, 0, NS2_BTN_U1_R, },
+ { BTN_TL, 2, NS2_BTN_U3_ZL },
+ { BTN_TR, 0, NS2_BTN_U1_ZR },
+ { BTN_SELECT, 1, NS2_BTN_U2_MINUS, },
+ { BTN_START, 1, NS2_BTN_U2_PLUS, },
+ { BTN_MODE, 1, NS2_BTN_U2_HOME },
+ { KEY_RECORD, 1, NS2_BTN_U2_CAPTURE },
+ { BTN_C, 1, NS2_BTN_U2_C },
+ { /* sentinel */ },
+};
+
static const uint8_t switch2_init_cmd_data[] = {
/*
* The last 6 bytes of this packet are the MAC address of
@@ -3802,11 +3903,51 @@ static int switch2_event(struct hid_device *hdev, struct hid_report *report, uin
switch (report->id) {
case NS2_REPORT_UNIFIED:
- /*
- * TODO
- * This won't be sent unless the report type gets changed via command
- * 03-0A, but we should support it at some point regardless.
- */
+ if (size < 0x3f)
+ return -EINVAL;
+
+ switch (ns2->ctlr_type) {
+ case NS2_CTLR_TYPE_JCL:
+ switch2_report_stick(input, &ns2->stick_calib[0],
+ ABS_X, false, ABS_Y, true, &raw_data[11]);
+ switch2_report_buttons(input, &raw_data[5],
+ ns2_left_joycon_button_unified_mappings);
+ break;
+ case NS2_CTLR_TYPE_JCR:
+ switch2_report_stick(input, &ns2->stick_calib[0],
+ ABS_X, false, ABS_Y, true, &raw_data[14]);
+ switch2_report_buttons(input, &raw_data[5],
+ ns2_right_joycon_button_unified_mappings);
+ break;
+ case NS2_CTLR_TYPE_GC:
+ input_report_abs(input, ABS_HAT0X,
+ !!(raw_data[7] & NS2_BTN_U3_RIGHT) -
+ !!(raw_data[7] & NS2_BTN_U3_LEFT));
+ input_report_abs(input, ABS_HAT0Y,
+ !!(raw_data[7] & NS2_BTN_U3_DOWN) -
+ !!(raw_data[7] & NS2_BTN_U3_UP));
+ switch2_report_buttons(input, &raw_data[5], ns2_gccon_unified_mappings);
+ switch2_report_stick(input, &ns2->stick_calib[0],
+ ABS_X, false, ABS_Y, true, &raw_data[11]);
+ switch2_report_stick(input, &ns2->stick_calib[1],
+ ABS_RX, false, ABS_RY, true, &raw_data[14]);
+ switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[0x3d]);
+ switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[0x3e]);
+ break;
+ case NS2_CTLR_TYPE_PRO:
+ input_report_abs(input, ABS_HAT0X,
+ !!(raw_data[7] & NS2_BTN_U3_RIGHT) -
+ !!(raw_data[7] & NS2_BTN_U3_LEFT));
+ input_report_abs(input, ABS_HAT0Y,
+ !!(raw_data[7] & NS2_BTN_U3_DOWN) -
+ !!(raw_data[7] & NS2_BTN_U3_UP));
+ switch2_report_buttons(input, &raw_data[5], ns2_procon_unified_mappings);
+ switch2_report_stick(input, &ns2->stick_calib[0],
+ ABS_X, false, ABS_Y, true, &raw_data[11]);
+ switch2_report_stick(input, &ns2->stick_calib[1],
+ ABS_RX, false, ABS_RY, true, &raw_data[14]);
+ break;
+ }
break;
case NS2_REPORT_JCL:
switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v10 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
From: sashiko-bot @ 2026-07-02 3:56 UTC (permalink / raw)
To: Vicki Pfau; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260702034106.1498056-2-vi@endrift.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Calling `input_free_device()` on a successfully registered `input_dev` when subsequent allocations fail.
- [High] Use-After-Free of `ns2->leds` due to devm lifetime mismatch when reusing the global controller structure.
- [High] Race condition causing NULL pointer dereference of `ns2->cfg` during USB initialization error.
- [High] Unconditional `ida_free()` with `U32_MAX` causes a kernel BUG_ON.
--
commit be3a7ca67ec1bc0f796b8ba1203737257398ef18
Author: Vicki Pfau <vi@endrift.com>
HID: nintendo: Add preliminary Switch 2 controller driver
This commit adds a new driver for the Switch 2 controllers. It utilizes a
split-interface design where input and rumble occur on the main HID
interface, while all other communication uses a configuration interface.
The current implementation supports general input for Joy-Con 2, Pro
Controller 2, and GameCube NSO controllers over USB.
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
> @@ -2838,7 +2845,1206 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
> [ ... ]
> +static int switch2_init_input(struct switch2_controller *ns2)
> +{
> [ ... ]
> + ret = input_register_device(input);
> + if (ret < 0) {
> + hid_err(ns2->hdev, "Failed to register input; ret=%d\n", ret);
> + input_free_device(input);
> + return ret;
> + }
> +
> + player_led_pattern = ns2->player_id % JC_NUM_LED_PATTERNS;
> + hid_dbg(hdev, "assigned player %d led pattern", player_led_pattern + 1);
> +
> + ns2->leds = devm_kcalloc(&input->dev, JC_NUM_LEDS, sizeof(*ns2->leds), GFP_KERNEL);
[Severity: High]
Since the ns2 structure is reused per physical port via
switch2_get_controller(), could there be a lifetime mismatch here?
If a device is unplugged but its evdev file descriptor is kept open by
userspace, input->dev stays alive. If a new device is then plugged into the
same port, ns2 is reused and ns2->leds is overwritten with an allocation
tied to the new input_dev.
When the new device is unplugged, freeing its devm resources, would a write
to the original device's LED sysfs node in
switch2_player_led_brightness_set() iterate over freed memory?
> + if (!ns2->leds) {
> + hid_err(ns2->hdev, "Failed to allocate LEDs\n");
> + input_free_device(input);
[Severity: High]
Is it safe to use input_free_device() here since input_register_device()
has already successfully registered the device above?
Once the input device is registered, won't calling input_free_device() just
drop the initial reference rather than properly unregistering the device,
leaving dangling sysfs nodes and leaking the device?
> + return -ENOMEM;
> + }
> [ ... ]
> +int switch2_receive_command(struct switch2_controller *ns2,
> + const uint8_t *message, size_t length)
> +{
> + const struct switch2_cmd_header *header;
> + int ret = 0;
> [ ... ]
> + header = (const struct switch2_cmd_header *)message;
> + if (!(header->flags & NS2_FLAG_OK)) {
> + dev_warn(ns2->cfg->dev, "Packet error %02x replying to command %x:%x",
[Severity: High]
Could this lead to a NULL pointer dereference in the error logging path?
In switch2_usb_probe(), we submit the bulk IN URB before calling
switch2_controller_attach_cfg(). If the device immediately returns an error
packet, switch2_receive_command() might run concurrently.
If attach_cfg fails and clears ns2->cfg to NULL, could the workqueue attempt
to log the error using dev_warn(ns2->cfg->dev, ...) and crash?
> + header->flags, header->command, header->subcommand);
> + ret = -EIO;
> + goto exit;
> + }
> [ ... ]
> +static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id)
> +{
> [ ... ]
> + ns2->player_id = U32_MAX;
> + ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
> + if (ret < 0)
> + hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
> + else
> + ns2->player_id = ret;
> [ ... ]
> +err_cleanup:
> + ida_free(&nintendo_player_id_allocator, ns2->player_id);
[Severity: High]
If ida_alloc() fails, ns2->player_id is left as U32_MAX.
If the driver probe subsequently fails and enters this cleanup path, won't
ida_free() with U32_MAX (which casts to a negative integer) trigger a BUG_ON
in the IDA core?
> + ns2->hdev = NULL;
> + mutex_unlock(&ns2->lock);
> + switch2_controller_put(ns2);
> + kref_put(&ns2->refcount, switch2_kref_put);
> +
> + return ret;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702034106.1498056-1-vi@endrift.com?part=1
^ permalink raw reply
* Re: [PATCH v10 2/3] HID: nintendo: Add rumble support for Switch 2 controllers
From: sashiko-bot @ 2026-07-02 3:58 UTC (permalink / raw)
To: Vicki Pfau; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260702034106.1498056-3-vi@endrift.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The devres-managed delayed work `ns2->rumble_work` is bound to the `input->dev` lifecycle, but resides in `ns2`, leading to a Use-After-Free.
- [High] LEDs registered with `devm_led_classdev_register` on `input->dev` outlive the controller context `ns2`, leading to a Use-After-Free when sysfs attributes are accessed.
- [Medium] Unconditional cancellation of potentially uninitialized `ns2->rumble_work` in `switch2_remove()` causes lockdep splats and kernel warnings.
--
commit 62e28913646bc7a9de5cfbf6dcd030ac10396625
Author: Vicki Pfau <vi@endrift.com>
HID: nintendo: Add rumble support for Switch 2 controllers
This adds rumble support for both the "HD Rumble" linear resonant actuator
type as used in the Joy-Cons and Pro Controller, as well as the eccentric
rotating mass type used in the GameCube controller. Note that since there's
currently no API for exposing full control of LRAs with evdev, it only
simulates a basic rumble for now.
Signed-off-by: Vicki Pfau <vi@endrift.com>
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index f82034decb9ee..71a04c438271f 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
[ ... ]
> @@ -3332,6 +3502,26 @@ static int switch2_init_input(struct switch2_controller *ns2)
> return -EINVAL;
> }
>
> +#if IS_ENABLED(CONFIG_NINTENDO_FF)
> + ns2->rumble_buffer = devm_kzalloc(&input->dev, 64, GFP_KERNEL);
> + if (!ns2->rumble_buffer) {
> + input_free_device(input);
> + return -ENOMEM;
> + }
> + ret = devm_delayed_work_autocancel(&input->dev, &ns2->rumble_work, switch2_rumble_work);
[Severity: High]
Could this lead to a use-after-free? ns2->rumble_work is tied to the
lifecycle of input->dev, but it resides in ns2. If the physical controller
is unplugged:
switch2_remove()
kref_put(&ns2->refcount, switch2_kref_put);
kfree(ns2);
If a userspace process holds the input device open, input->dev remains alive.
When the file descriptor is finally closed, the devres cleanup for input->dev
will execute and attempt to cancel the delayed work on the already-freed ns2
structure.
[Severity: High]
Are there similar lifecycle issues with the LED registration? If the LED class
device is registered using devm_led_classdev_register(&input->dev, led), it
decouples the LED lifecycle from the controller context.
If the controller is unplugged and ns2 is freed, but the input device remains
open, could a write to the sysfs brightness file trigger a use-after-free?
switch2_player_led_brightness_set()
struct switch2_controller *ns2 = input_get_drvdata(input);
if (!ns2)
return -ENODEV;
guard(mutex)(&ns2->lock);
Would dereferencing ns2 here cause memory corruption if the context has
already been freed?
> + if (ret < 0) {
> + input_free_device(input);
> + return ret;
> + }
> +
> + input_set_capability(input, EV_FF, FF_RUMBLE);
> + ret = input_ff_create_memless(input, NULL, switch2_play_effect);
> + if (ret) {
> + input_free_device(input);
> + return ret;
> + }
> +#endif
[ ... ]
> @@ -4038,6 +4245,9 @@ static void switch2_remove(struct hid_device *hdev)
> ns2->hdev = NULL;
> ida_free(&nintendo_player_id_allocator, ns2->player_id);
> mutex_unlock(&ns2->lock);
> +#if IS_ENABLED(CONFIG_NINTENDO_FF)
> + cancel_delayed_work_sync(&ns2->rumble_work);
[Severity: Medium]
Will this execute safely if the delayed work was never initialized?
Since ns2 is zero-allocated and ns2->rumble_work is only initialized in
switch2_init_input(), what happens if the device is unplugged or fails
initialization before switch2_init_input() is called?
Would calling cancel_delayed_work_sync() on a zero-initialized work structure
trigger lockdep splats or kernel warnings?
> +#endif
> kref_put(&ns2->refcount, switch2_kref_put);
> hid_hw_close(hdev);
> hid_hw_stop(hdev);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702034106.1498056-1-vi@endrift.com?part=2
^ permalink raw reply
* [PATCH v2 0/2] HID: sensor: custom: Fix fields lifetime issues
From: Haoxiang Li @ 2026-07-02 9:48 UTC (permalink / raw)
To: jikos, jic23, srinivas.pandruvada, bentiss
Cc: linux-input, linux-iio, linux-kernel, Haoxiang Li
Hi,
This series fixes lifetime issues around sensor_inst->fields and the
sysfs attributes that can access it.
The first patch fixes the remove path by removing enable_sensor before
freeing the field attributes. enable_sensor_store() can dereference
power_state and report_state, which point into sensor_inst->fields.
The second patch fixes the original field sysfs group leak on probe
failure. It creates the field attributes before exposing enable_sensor,
then unwinds any field groups that were created before a later
sysfs_create_group() failure.
Thanks, Jiri, for the review and for pointing out the UAF concern.
Changes in v2:
- Split the fix into two patches.
- Fix the pre-existing remove path ordering issue.
- Create field attributes before exposing enable_sensor.
- Unwind already-created field sysfs groups on failure.
Haoxiang Li (2):
HID: sensor: custom: Remove enable_sensor before freeing fields
HID: sensor: custom: Fix field sysfs group cleanup on failure
drivers/hid/hid-sensor-custom.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
base-commit: ef0c9f75a19532d7675384708fc8621e10850104
--
2.25.1
^ permalink raw reply
* [PATCH v2 2/2] HID: sensor: custom: Fix field sysfs group cleanup on failure
From: Haoxiang Li @ 2026-07-02 9:48 UTC (permalink / raw)
To: jikos, jic23, srinivas.pandruvada, bentiss
Cc: linux-input, linux-iio, linux-kernel, Haoxiang Li, stable
In-Reply-To: <20260702094856.1105555-1-haoxiang_li2024@163.com>
hid_sensor_custom_add_attributes() creates one sysfs group for each
custom sensor field. If sysfs_create_group() fails after some groups
have already been created, the function returns the error without
removing the previously created groups. Add a local unwind path to
remove the groups that were already created.
Create the field attributes before exposing enable_sensor, so the
failure path can free sensor_inst->fields without leaving enable_sensor
able to access power_state or report_state pointers into that array.
Fixes: 4a7de0519df5 ("HID: sensor: Custom and Generic sensor support")
Cc: stable@vger.kernel.org
Signed-off-by: Haoxiang Li <haoxiang_li2024@163.com>
---
drivers/hid/hid-sensor-custom.c | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
index d7bdbae96b50..ea98088e5112 100644
--- a/drivers/hid/hid-sensor-custom.c
+++ b/drivers/hid/hid-sensor-custom.c
@@ -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);
+ ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
+ &enable_sensor_attr_group);
if (ret)
- goto err_remove_group;
+ goto err_remove_attributes;
ret = hid_sensor_custom_dev_if_add(sensor_inst);
if (ret)
- goto err_remove_attributes;
+ 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);
--
2.25.1
^ permalink raw reply related
* [PATCH v2 1/2] HID: sensor: custom: Remove enable_sensor before freeing fields
From: Haoxiang Li @ 2026-07-02 9:48 UTC (permalink / raw)
To: jikos, jic23, srinivas.pandruvada, bentiss
Cc: linux-input, linux-iio, linux-kernel, Haoxiang Li, stable
In-Reply-To: <20260702094856.1105555-1-haoxiang_li2024@163.com>
enable_sensor_store() can call set_power_report_state(), which
dereferences sensor_inst->power_state and sensor_inst->report_state.
These pointers refer to entries in sensor_inst->fields.
hid_sensor_custom_remove() currently frees the field attributes before
removing the enable_sensor sysfs attribute, leaving a window where a
concurrent sysfs write can dereference freed memory.
Remove enable_sensor before freeing the field attributes.
Fixes: 4a7de0519df5 ("HID: sensor: Custom and Generic sensor support")
Cc: stable@vger.kernel.org
Signed-off-by: Haoxiang Li <haoxiang_li2024@163.com>
---
drivers/hid/hid-sensor-custom.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
index afffea894021..d7bdbae96b50 100644
--- a/drivers/hid/hid-sensor-custom.c
+++ b/drivers/hid/hid-sensor-custom.c
@@ -1042,9 +1042,9 @@ 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);
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);
}
--
2.25.1
^ permalink raw reply related
* Re: [PATCH v2 1/2] HID: sensor: custom: Remove enable_sensor before freeing fields
From: sashiko-bot @ 2026-07-02 10:05 UTC (permalink / raw)
To: Haoxiang Li; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260702094856.1105555-2-haoxiang_li2024@163.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Out-of-bounds array access in `show_value()` and `store_value()` due to flawed sysfs attribute index parsing.
- [High] A race condition in `hid_sensor_custom_remove()` allows incoming sensor events to trigger a Use-After-Free.
- [High] Using `devm_kzalloc` for the driver context linked to a `miscdevice` causes a Use-After-Free if the device is unbound while the file is open.
- [Medium] Concurrent reads on the `miscdevice` corrupt the `kfifo` state, leading to broken data streams.
--
commit a7339c2fa4bb12ea6bad0a949e3b22ab6d1f4ed6
Author: Haoxiang Li <haoxiang_li2024@163.com>
HID: sensor: custom: Remove enable_sensor before freeing fields
enable_sensor_store() can call set_power_report_state(), which
dereferences sensor_inst->power_state and sensor_inst->report_state.
These pointers refer to entries in sensor_inst->fields.
hid_sensor_custom_remove() currently frees the field attributes before
removing the enable_sensor sysfs attribute, leaving a window where a
concurrent sysfs write can dereference freed memory.
Remove enable_sensor before freeing the field attributes.
Fixes: 4a7de0519df5 ("HID: sensor: Custom and Generic sensor support")
Cc: stable@vger.kernel.org
Signed-off-by: Haoxiang Li <haoxiang_li2024@163.com>
> diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
> index afffea8940214..d7bdbae96b501 100644
> --- a/drivers/hid/hid-sensor-custom.c
> +++ b/drivers/hid/hid-sensor-custom.c
> @@ -1042,9 +1042,9 @@ 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);
> 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);
[Severity: High]
This is a pre-existing issue, but does freeing the kfifo resource before
unregistering the interrupt-driven callback leave a window for a
use-after-free?
If hid_sensor_custom_dev_if_remove() frees sensor_inst->data_fifo, and an
incoming hardware event occurs before sensor_hub_remove_callback() is called,
could an open file descriptor trigger hid_sensor_capture_sample() to write to
the freed kfifo?
drivers/hid/hid-sensor-custom.c:hid_sensor_capture_sample() {
...
if (kfifo_avail(&sensor_inst->data_fifo) >= required_size) {
kfifo_in(&sensor_inst->data_fifo, ...);
...
}
> }
[Severity: High]
This isn't a bug introduced by this patch, but it appears using devm_kzalloc()
for the driver context linked to a miscdevice might cause a use-after-free.
If the device is unbound (e.g., via USB unplug), devres frees sensor_inst.
However, if a user process holds the miscdevice file descriptor open, is it
possible for a subsequent call to read() to extract and dereference the freed
pointer?
drivers/hid/hid-sensor-custom.c:hid_sensor_custom_read() {
sensor_inst = container_of(file->private_data, struct hid_sensor_custom, custom_dev);
...
if (kfifo_is_empty(&sensor_inst->data_fifo)) {
...
}
[Severity: Critical]
This is a pre-existing issue, but we might have an out-of-bounds array access
when parsing the attribute name for an index.
In hid_sensor_custom_add_field(), the raw HID loop index is baked into the
sysfs filename:
drivers/hid/hid-sensor-custom.c:hid_sensor_custom_add_field() {
...
snprintf(sensor_field->group_name, sizeof(sensor_field->group_name),
"input-%x-%x", sensor_field->attribute.index,
sensor_field->attribute.attrib_id);
...
}
If fields are skipped and the array is reallocated to a smaller size, would a
user reading the sysfs file trigger an out-of-bounds access in show_value(),
given it uses the raw index directly from the filename?
drivers/hid/hid-sensor-custom.c:show_value() {
...
if (sscanf(attr->attr.name, "input-%x-%x-%s", &index, &usage, name) == 3) {
...
field_index = index;
...
attribute = &sensor_inst->fields[field_index].attribute;
...
}
[Severity: Medium]
This isn't a bug introduced by this patch, but can concurrent readers corrupt
the internal indices by invoking the kfifo consumer API without serialization?
While open() prevents multiple independent opens, a process can fork() or
spawn threads to invoke read() concurrently on the same file descriptor.
Since hid_sensor_custom_read() does not use a mutex around kfifo_to_user(),
will racing threads corrupt the kfifo state?
drivers/hid/hid-sensor-custom.c:hid_sensor_custom_read() {
...
ret = kfifo_to_user(&sensor_inst->data_fifo, buf, count, &copied);
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702094856.1105555-1-haoxiang_li2024@163.com?part=1
^ permalink raw reply
* Re: [PATCH v2 2/2] HID: sensor: custom: Fix field sysfs group cleanup on failure
From: sashiko-bot @ 2026-07-02 10:16 UTC (permalink / raw)
To: Haoxiang Li; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260702094856.1105555-3-haoxiang_li2024@163.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] Lack of synchronization during callback removal leads to Use-After-Free of `sensor_inst` in HID event handler.
- [High] Active file descriptors on the misc device cause Use-After-Free after driver unbind.
- [High] Out-of-bounds array access in sysfs `show_value` and `store_value` due to flawed index parsing.
- [Medium] Resource leak of HID sensor hub device open count on unbind.
--
commit 33ce389be4baf215143f759dd6b8350c60263845
Author: Haoxiang Li <haoxiang_li2024@163.com>
HID: sensor: custom: Fix field sysfs group cleanup on failure
hid_sensor_custom_add_attributes() creates one sysfs group for each
custom sensor field. If sysfs_create_group() fails after some groups
have already been created, the function returns the error without
removing the previously created groups. Add a local unwind path to
remove the groups that were already created.
> diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
> index d7bdbae96b501..ea98088e51123 100644
> --- a/drivers/hid/hid-sensor-custom.c
> +++ b/drivers/hid/hid-sensor-custom.c
[ ... ]
> @@ -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;
> }
[Severity: High]
This is a pre-existing issue, but can the sysfs read/write handlers access
out of bounds memory if fields are skipped during initialization?
When sysfs attributes are read or written, the original index is extracted
via sscanf() and used directly on sensor_inst->fields:
drivers/hid/hid-sensor-custom.c:show_value() {
...
if (sscanf(attr->attr.name, "input-%x-%x-%s", &index, &usage, name) == 3) {
input = true;
field_index = index;
}
...
attribute = &sensor_inst->fields[field_index].attribute;
}
If the initialization loop skips fields, the extracted index can exceed the
array bounds of sensor_inst->fields, leading to an out-of-bounds access.
> @@ -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);
> + ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
> + &enable_sensor_attr_group);
> if (ret)
> - goto err_remove_group;
> + goto err_remove_attributes;
>
> ret = hid_sensor_custom_dev_if_add(sensor_inst);
> if (ret)
> - goto err_remove_attributes;
> + goto err_remove_group;
[Severity: High]
This is a pre-existing issue, but does the misc device teardown properly
synchronize with active file descriptors?
When the driver unbinds or fails probe, it unregisters the misc device and
frees the data FIFO, but active file descriptors are not revoked.
If a userspace process holds an open file descriptor, can subsequent calls
dereference the freed sensor_inst memory?
drivers/hid/hid-sensor-custom.c:hid_sensor_custom_read() {
...
sensor_inst = container_of(file->private_data,
struct hid_sensor_custom, custom_dev);
if (kfifo_is_empty(&sensor_inst->data_fifo)) {
...
}
>
> 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);
[Severity: Medium]
This is a pre-existing issue, but is there a missing teardown for the sensor
hub open count?
If a user writes 1 to the enable_sensor attribute, it increments the hub's
ref_cnt. When the driver unbinds, hid_sensor_custom_remove() destroys the
sysfs groups but does not seem to check sensor_inst->enable or call
sensor_hub_device_close().
Does this leak the reference count and leave the hardware in a persistent
power state?
> +err_remove_attributes:
> + hid_sensor_custom_remove_attributes(sensor_inst);
> err_remove_callback:
> sensor_hub_remove_callback(hsdev, hsdev->usage);
[Severity: High]
This is a pre-existing issue, but does removing the callback here leave a
race condition with concurrent HID events?
drivers/hid/hid-sensor-hub.c:sensor_hub_remove_callback() removes the
callback from the list and frees it:
sensor_hub_remove_callback() {
...
list_del(&callback->list);
kfree(callback);
...
}
It doesn't synchronize with concurrent executions. If the driver fails probe
or is unbound, sensor_inst is freed via devres. Can this concurrently
dereference the freed sensor_inst when executing the callback?
drivers/hid/hid-sensor-hub.c:sensor_hub_raw_event() {
...
callback = sensor_hub_get_callback(hdev, ...);
if (callback->capture_sample) {
callback->capture_sample(hsdev, ..., callback->pdev);
}
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702094856.1105555-1-haoxiang_li2024@163.com?part=2
^ permalink raw reply
* Re: [PATCH v9 0/9] Add support for MT6392 PMIC
From: Lee Jones @ 2026-07-02 15:14 UTC (permalink / raw)
To: Luca Leonardo Scorcia
Cc: linux-mediatek, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Sen Chu, Sean Wang, Macpaul Lin, Matthias Brugger,
AngeloGioacchino Del Regno, Liam Girdwood, Mark Brown,
Linus Walleij, Louis-Alexis Eyraud, Val Packett, Julien Massot,
Fabien Parent, Akari Tsuyukusa, Chen Zhong, linux-input,
devicetree, linux-kernel, linux-pm, linux-arm-kernel, linux-gpio
In-Reply-To: <20260621081634.467858-1-l.scorcia@gmail.com>
On Sun, 21 Jun 2026, Luca Leonardo Scorcia wrote:
> The MediaTek MT6392 PMIC is usually found on devices powered by
> the MT8516/MT8167 SoC and is yet another MT6323/MT6397 variant.
The MFD pieces look fine to me.
I'm assuming this will go in via MFD.
Just let me know when you have all of the remaining Acks.
--
Lee Jones
^ permalink raw reply
* [PATCH v2 1/6] iio: hid-sensors: add/remove blank line
From: Sanjay Chitroda via B4 Relay @ 2026-07-02 16:17 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada
Cc: linux-iio, linux-kernel, linux-input, Sanjay Chitroda
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-0-b87f01f5efbc@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
Add a blank line after variable declarations and remove multiple blank
line across HID sensor IIO drivers to improve readability and align
with kernel coding style.
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/common/hid-sensors/hid-sensor-attributes.c | 1 -
drivers/iio/common/hid-sensors/hid-sensor-trigger.c | 3 +++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
index c115a72832b2..2f0a1ea42f48 100644
--- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
+++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
@@ -282,7 +282,6 @@ int hid_sensor_read_raw_hyst_rel_value(struct hid_sensor_common *st, int *val1,
}
EXPORT_SYMBOL_NS(hid_sensor_read_raw_hyst_rel_value, "IIO_HID");
-
int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st,
int val1, int val2)
{
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
index 417c4ab8c1b2..c8ccf96f3d03 100644
--- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
+++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
@@ -314,7 +314,9 @@ static int __maybe_unused hid_sensor_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev);
+
schedule_work(&attrb->work);
+
return 0;
}
@@ -322,6 +324,7 @@ static int __maybe_unused hid_sensor_runtime_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev);
+
return _hid_sensor_power_state(attrb, true);
}
--
2.34.1
^ permalink raw reply related
* [PATCH v2 0/6] HID: iio: warning clean up and prefer kernel coding style
From: Sanjay Chitroda via B4 Relay @ 2026-07-02 16:17 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada
Cc: linux-iio, linux-kernel, linux-input, Sanjay Chitroda,
Maxwell Doose, Zhang Lixu
Hi all,
This series updates HID sensor IIO drivers to address checkpatch
warnings, improves readability and compliance with kernel coding style.
The changes are:
- replacing explicit NULL comparisons with implicit pointer checks;
- adjusting indentation and alignment;
- reformatting multi-line function declarations for improved
readability and consistency.
- use common device for devres framework;
While updating alignment and line wrapping, function parameters are
grouped more logically and common callback signatures are formatted
consistently across HID sensor IIO drivers.
Testing:
- Compiled with W=1 for each patch in the series
---
Changes in v2:
- Following input from Joshua and Maxwell squash all related changes
in single change as this is code churn.
- Following input from Andy (re-)split function argument/parameter
logically.
- Added review tag of Maxwell to implicit NULL check change while
drop for parenthesis alignemnt to have a fresh review.
- Link to v1: https://lore.kernel.org/all/20260616-15-jun-hid-iio-alignment-v1-0-0cd544286575@gmail.com
To: Jonathan Cameron <jic23@kernel.org>
To: David Lechner <dlechner@baylibre.com>
To: Nuno Sá <nuno.sa@analog.com>
To: Andy Shevchenko <andy@kernel.org>
To: Jiri Kosina <jikos@kernel.org>
To: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Cc: linux-iio@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-input@vger.kernel.org
---
Sanjay Chitroda (6):
iio: hid-sensors: add/remove blank line
iio: hid-sensors: align function parenthesis for readability
iio: hid-sensors: Use implicit NULL pointer checks
iio: humidity: hid-sensor-humidity: use common device for devres
iio: position: hid-sensor-custom-intel-hinge: use common device for devres
iio: temperature: hid-sensor-temperature: use common device for devres
drivers/iio/accel/hid-sensor-accel-3d.c | 48 ++++++------
.../iio/common/hid-sensors/hid-sensor-attributes.c | 89 +++++++++++-----------
.../iio/common/hid-sensors/hid-sensor-trigger.c | 5 +-
.../iio/common/hid-sensors/hid-sensor-trigger.h | 2 +-
drivers/iio/gyro/hid-sensor-gyro-3d.c | 54 ++++++-------
drivers/iio/humidity/hid-sensor-humidity.c | 49 ++++++------
drivers/iio/light/hid-sensor-als.c | 32 ++++----
drivers/iio/light/hid-sensor-prox.c | 29 +++----
drivers/iio/magnetometer/hid-sensor-magn-3d.c | 76 +++++++++---------
drivers/iio/orientation/hid-sensor-incl-3d.c | 33 ++++----
drivers/iio/orientation/hid-sensor-rotation.c | 26 +++----
.../iio/position/hid-sensor-custom-intel-hinge.c | 15 ++--
drivers/iio/pressure/hid-sensor-press.c | 35 ++++-----
drivers/iio/temperature/hid-sensor-temperature.c | 42 +++++-----
14 files changed, 252 insertions(+), 283 deletions(-)
---
base-commit: 16f3a4a21cab16dc9242a2e0749b026e1f2c5706
change-id: 20260615-15-jun-hid-iio-alignment-46bba7279be3
Best regards,
--
Sanjay Chitroda <sanjayembeddedse@gmail.com>
^ permalink raw reply
* [PATCH v2 3/6] iio: hid-sensors: Use implicit NULL pointer checks
From: Sanjay Chitroda via B4 Relay @ 2026-07-02 16:18 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada
Cc: linux-iio, linux-kernel, linux-input, Sanjay Chitroda,
Maxwell Doose
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-0-b87f01f5efbc@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
Replace explicit NULL pointer comparisons with implicit checks across
HID sensor IIO drivers to follow the preferred kernel coding style.
Convert 'if (indio_dev == NULL)' -> 'if (!indio_dev)'.
No functional change.
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
Reviewed-by: Maxwell Doose <m32285159@gmail.com>
---
drivers/iio/accel/hid-sensor-accel-3d.c | 2 +-
drivers/iio/magnetometer/hid-sensor-magn-3d.c | 2 +-
drivers/iio/orientation/hid-sensor-incl-3d.c | 2 +-
drivers/iio/orientation/hid-sensor-rotation.c | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c
index 9197f3424c0c..225f8dd65ab1 100644
--- a/drivers/iio/accel/hid-sensor-accel-3d.c
+++ b/drivers/iio/accel/hid-sensor-accel-3d.c
@@ -325,7 +325,7 @@ static int hid_accel_3d_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(struct accel_3d_state));
- if (indio_dev == NULL)
+ if (!indio_dev)
return -ENOMEM;
platform_set_drvdata(pdev, indio_dev);
diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
index ad10fa20fae0..738bad65d74d 100644
--- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c
+++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
@@ -463,7 +463,7 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(struct magn_3d_state));
- if (indio_dev == NULL)
+ if (!indio_dev)
return -ENOMEM;
platform_set_drvdata(pdev, indio_dev);
diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c
index 870c8929491e..c8efb0dab8b6 100644
--- a/drivers/iio/orientation/hid-sensor-incl-3d.c
+++ b/drivers/iio/orientation/hid-sensor-incl-3d.c
@@ -302,7 +302,7 @@ static int hid_incl_3d_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(struct incl_3d_state));
- if (indio_dev == NULL)
+ if (!indio_dev)
return -ENOMEM;
platform_set_drvdata(pdev, indio_dev);
diff --git a/drivers/iio/orientation/hid-sensor-rotation.c b/drivers/iio/orientation/hid-sensor-rotation.c
index 2dad0453fc67..6db253c1635d 100644
--- a/drivers/iio/orientation/hid-sensor-rotation.c
+++ b/drivers/iio/orientation/hid-sensor-rotation.c
@@ -274,7 +274,7 @@ static int hid_dev_rot_probe(struct platform_device *pdev)
indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(struct dev_rot_state));
- if (indio_dev == NULL)
+ if (!indio_dev)
return -ENOMEM;
platform_set_drvdata(pdev, indio_dev);
--
2.34.1
^ permalink raw reply related
* [PATCH v2 4/6] iio: humidity: hid-sensor-humidity: use common device for devres
From: Sanjay Chitroda via B4 Relay @ 2026-07-02 16:18 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada
Cc: linux-iio, linux-kernel, linux-input, Sanjay Chitroda, Zhang Lixu
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-0-b87f01f5efbc@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
kmemdup() is used for memory that is logically tied to the HID
platform device, even though the driver binds into the IIO framework.
Using &indio_dev->dev for devres allocations works functionally, but it
results in two separate devres ownership trees—one for the HID
platform device (pdev) and another for the IIO device (indio_dev).
The devres framework is intended to have a single, well-defined parent
device. Since the memory originates from HID sensor probing and is not
IIO-specific, &pdev->dev is the correct and logical owner.
Switch to using the platform device for devm_kmemdup() so that all
resources are released deterministically and consistently.
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
Tested-by: Zhang Lixu <lixu.zhang@intel.com>
---
drivers/iio/humidity/hid-sensor-humidity.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/iio/humidity/hid-sensor-humidity.c b/drivers/iio/humidity/hid-sensor-humidity.c
index 8dd8bc0b3ba1..b7130eac0394 100644
--- a/drivers/iio/humidity/hid-sensor-humidity.c
+++ b/drivers/iio/humidity/hid-sensor-humidity.c
@@ -216,7 +216,7 @@ static int hid_humidity_probe(struct platform_device *pdev)
if (ret)
return ret;
- humid_chans = devm_kmemdup(&indio_dev->dev, humidity_channels,
+ humid_chans = devm_kmemdup(&pdev->dev, humidity_channels,
sizeof(humidity_channels), GFP_KERNEL);
if (!humid_chans)
return -ENOMEM;
--
2.34.1
^ permalink raw reply related
* [PATCH v2 2/6] iio: hid-sensors: align function parenthesis for readability
From: Sanjay Chitroda via B4 Relay @ 2026-07-02 16:17 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada
Cc: linux-iio, linux-kernel, linux-input, Sanjay Chitroda
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-0-b87f01f5efbc@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
Adjust alignment of parentheses across HID sensor IIO drivers to
improve readability and maintain consistency with kernel coding style.
While updating the formatting, group related arguments consistently in
multi-line function signatures where appropriate.
No functional change intended.
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/accel/hid-sensor-accel-3d.c | 46 ++++++-----
.../iio/common/hid-sensors/hid-sensor-attributes.c | 88 +++++++++++-----------
.../iio/common/hid-sensors/hid-sensor-trigger.c | 2 +-
.../iio/common/hid-sensors/hid-sensor-trigger.h | 2 +-
drivers/iio/gyro/hid-sensor-gyro-3d.c | 54 ++++++-------
drivers/iio/humidity/hid-sensor-humidity.c | 47 ++++++------
drivers/iio/light/hid-sensor-als.c | 32 ++++----
drivers/iio/light/hid-sensor-prox.c | 29 +++----
drivers/iio/magnetometer/hid-sensor-magn-3d.c | 74 +++++++++---------
drivers/iio/orientation/hid-sensor-incl-3d.c | 31 ++++----
drivers/iio/orientation/hid-sensor-rotation.c | 24 +++---
.../iio/position/hid-sensor-custom-intel-hinge.c | 13 ++--
drivers/iio/pressure/hid-sensor-press.c | 35 ++++-----
drivers/iio/temperature/hid-sensor-temperature.c | 38 +++++-----
14 files changed, 241 insertions(+), 274 deletions(-)
diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c
index 42c4259bf209..9197f3424c0c 100644
--- a/drivers/iio/accel/hid-sensor-accel-3d.c
+++ b/drivers/iio/accel/hid-sensor-accel-3d.c
@@ -122,9 +122,8 @@ static const struct iio_chan_spec gravity_channels[] = {
/* Channel read_raw handler */
static int accel_3d_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val, int *val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct accel_3d_state *accel_state = iio_priv(indio_dev);
int report_id = -1;
@@ -151,7 +150,7 @@ static int accel_3d_read_raw(struct iio_dev *indio_dev,
else {
*val = 0;
hid_sensor_power_state(&accel_state->common_attributes,
- false);
+ false);
return -EINVAL;
}
hid_sensor_power_state(&accel_state->common_attributes, false);
@@ -184,10 +183,8 @@ static int accel_3d_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int accel_3d_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct accel_3d_state *accel_state = iio_priv(indio_dev);
int ret = 0;
@@ -223,8 +220,7 @@ static void hid_sensor_push_data(struct iio_dev *indio_dev, void *data,
/* Callback handler to send event after all samples are received and captured */
static int accel_3d_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- void *priv)
+ u32 usage_id, void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct accel_3d_state *accel_state = iio_priv(indio_dev);
@@ -247,9 +243,9 @@ static int accel_3d_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int accel_3d_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- size_t raw_len, char *raw_data,
- void *priv)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct accel_3d_state *accel_state = iio_priv(indio_dev);
@@ -281,10 +277,10 @@ static int accel_3d_capture_sample(struct hid_sensor_hub_device *hsdev,
/* Parse report which is specific to an usage id*/
static int accel_3d_parse_report(struct platform_device *pdev,
- struct hid_sensor_hub_device *hsdev,
- struct iio_chan_spec *channels,
- u32 usage_id,
- struct accel_3d_state *st)
+ struct hid_sensor_hub_device *hsdev,
+ struct iio_chan_spec *channels,
+ u32 usage_id,
+ struct accel_3d_state *st)
{
int ret;
@@ -303,10 +299,10 @@ static int accel_3d_parse_report(struct platform_device *pdev,
};
}
dev_dbg(&pdev->dev, "accel_3d %x:%x, %x:%x, %x:%x\n",
- st->accel[0].index,
- st->accel[0].report_id,
- st->accel[1].index, st->accel[1].report_id,
- st->accel[2].index, st->accel[2].report_id);
+ st->accel[0].index,
+ st->accel[0].report_id,
+ st->accel[1].index, st->accel[1].report_id,
+ st->accel[2].index, st->accel[2].report_id);
st->scale_precision = hid_sensor_format_scale(
hsdev->usage,
@@ -366,8 +362,8 @@ static int hid_accel_3d_probe(struct platform_device *pdev)
return -ENOMEM;
}
ret = accel_3d_parse_report(pdev, hsdev,
- (struct iio_chan_spec *)indio_dev->channels,
- hsdev->usage, accel_state);
+ (struct iio_chan_spec *)indio_dev->channels,
+ hsdev->usage, accel_state);
if (ret) {
dev_err(&pdev->dev, "failed to setup attributes\n");
return ret;
@@ -380,7 +376,7 @@ static int hid_accel_3d_probe(struct platform_device *pdev)
atomic_set(&accel_state->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &accel_state->common_attributes);
+ &accel_state->common_attributes);
if (ret < 0) {
dev_err(&pdev->dev, "trigger setup failed\n");
return ret;
@@ -396,7 +392,7 @@ static int hid_accel_3d_probe(struct platform_device *pdev)
accel_state->callbacks.capture_sample = accel_3d_capture_sample;
accel_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, hsdev->usage,
- &accel_state->callbacks);
+ &accel_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
index 2f0a1ea42f48..13fa55e966f4 100644
--- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
+++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
@@ -72,8 +72,7 @@ static const struct {
{HID_USAGE_SENSOR_HUMAN_ATTENTION, 0, 1, 0},
};
-static void simple_div(int dividend, int divisor, int *whole,
- int *micro_frac)
+static void simple_div(int dividend, int divisor, int *whole, int *micro_frac)
{
int rem;
int exp = 0;
@@ -111,7 +110,7 @@ for 10^-2.
Negative numbers are 2's complement
*/
static void convert_from_vtf_format(u32 value, int size, int exp,
- int *val1, int *val2)
+ int *val1, int *val2)
{
int sign = 1;
@@ -176,7 +175,7 @@ s32 hid_sensor_read_poll_value(struct hid_sensor_common *st)
EXPORT_SYMBOL_NS(hid_sensor_read_poll_value, "IIO_HID_ATTRIBUTES");
int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st,
- int *val1, int *val2)
+ int *val1, int *val2)
{
s32 value;
int ret;
@@ -203,7 +202,7 @@ int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st,
EXPORT_SYMBOL_NS(hid_sensor_read_samp_freq_value, "IIO_HID");
int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st,
- int val1, int val2)
+ int val1, int val2)
{
s32 value;
int ret;
@@ -238,15 +237,15 @@ int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st,
EXPORT_SYMBOL_NS(hid_sensor_write_samp_freq_value, "IIO_HID");
int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st,
- int *val1, int *val2)
+ int *val1, int *val2)
{
s32 value;
int ret;
ret = sensor_hub_get_feature(st->hsdev,
st->sensitivity.report_id,
- st->sensitivity.index, sizeof(value),
- &value);
+ st->sensitivity.index,
+ sizeof(value), &value);
if (ret < 0 || value < 0) {
*val1 = *val2 = 0;
return -EINVAL;
@@ -268,8 +267,8 @@ int hid_sensor_read_raw_hyst_rel_value(struct hid_sensor_common *st, int *val1,
ret = sensor_hub_get_feature(st->hsdev,
st->sensitivity_rel.report_id,
- st->sensitivity_rel.index, sizeof(value),
- &value);
+ st->sensitivity_rel.index,
+ sizeof(value), &value);
if (ret < 0 || value < 0) {
*val1 = *val2 = 0;
return -EINVAL;
@@ -283,7 +282,7 @@ int hid_sensor_read_raw_hyst_rel_value(struct hid_sensor_common *st, int *val1,
EXPORT_SYMBOL_NS(hid_sensor_read_raw_hyst_rel_value, "IIO_HID");
int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st,
- int val1, int val2)
+ int val1, int val2)
{
s32 value;
int ret;
@@ -292,8 +291,8 @@ int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st,
return -EINVAL;
value = convert_to_vtf_format(st->sensitivity.size,
- st->sensitivity.unit_expo,
- val1, val2);
+ st->sensitivity.unit_expo,
+ val1, val2);
ret = sensor_hub_set_feature(st->hsdev, st->sensitivity.report_id,
st->sensitivity.index, sizeof(value),
&value);
@@ -302,8 +301,8 @@ int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st,
ret = sensor_hub_get_feature(st->hsdev,
st->sensitivity.report_id,
- st->sensitivity.index, sizeof(value),
- &value);
+ st->sensitivity.index,
+ sizeof(value), &value);
if (ret < 0 || value < 0)
return -EINVAL;
@@ -323,8 +322,8 @@ int hid_sensor_write_raw_hyst_rel_value(struct hid_sensor_common *st,
return -EINVAL;
value = convert_to_vtf_format(st->sensitivity_rel.size,
- st->sensitivity_rel.unit_expo,
- val1, val2);
+ st->sensitivity_rel.unit_expo,
+ val1, val2);
ret = sensor_hub_set_feature(st->hsdev, st->sensitivity_rel.report_id,
st->sensitivity_rel.index, sizeof(value),
&value);
@@ -333,8 +332,8 @@ int hid_sensor_write_raw_hyst_rel_value(struct hid_sensor_common *st,
ret = sensor_hub_get_feature(st->hsdev,
st->sensitivity_rel.report_id,
- st->sensitivity_rel.index, sizeof(value),
- &value);
+ st->sensitivity_rel.index,
+ sizeof(value), &value);
if (ret < 0 || value < 0)
return -EINVAL;
@@ -355,8 +354,8 @@ EXPORT_SYMBOL_NS(hid_sensor_write_raw_hyst_rel_value, "IIO_HID");
* 1.001745329 ->exp:4-> val0[10017]val1[453290000]
* 9.806650000 ->exp:-2-> val0[0]val1[98066500]
*/
-static void adjust_exponent_nano(int *val0, int *val1, int scale0,
- int scale1, int exp)
+static void adjust_exponent_nano(int *val0, int *val1,
+ int scale0, int scale1, int exp)
{
int divisor;
int i;
@@ -403,8 +402,8 @@ static void adjust_exponent_nano(int *val0, int *val1, int scale0,
}
int hid_sensor_format_scale(u32 usage_id,
- struct hid_sensor_hub_attribute_info *attr_info,
- int *val0, int *val1)
+ struct hid_sensor_hub_attribute_info *attr_info,
+ int *val0, int *val1)
{
int i;
int exp;
@@ -414,9 +413,8 @@ int hid_sensor_format_scale(u32 usage_id,
for (i = 0; i < ARRAY_SIZE(unit_conversion); ++i) {
if (unit_conversion[i].usage_id == usage_id &&
- unit_conversion[i].unit == attr_info->units) {
- exp = hid_sensor_convert_exponent(
- attr_info->unit_expo);
+ unit_conversion[i].unit == attr_info->units) {
+ exp = hid_sensor_convert_exponent(attr_info->unit_expo);
adjust_exponent_nano(val0, val1,
unit_conversion[i].scale_val0,
unit_conversion[i].scale_val1, exp);
@@ -437,8 +435,8 @@ EXPORT_SYMBOL_NS(hid_sensor_convert_timestamp, "IIO_HID");
static
int hid_sensor_get_reporting_interval(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- struct hid_sensor_common *st)
+ u32 usage_id,
+ struct hid_sensor_common *st)
{
sensor_hub_input_get_attribute_info(hsdev,
HID_FEATURE_REPORT, usage_id,
@@ -472,9 +470,10 @@ int hid_sensor_get_report_latency(struct hid_sensor_common *st)
int ret;
int value;
- ret = sensor_hub_get_feature(st->hsdev, st->report_latency.report_id,
- st->report_latency.index, sizeof(value),
- &value);
+ ret = sensor_hub_get_feature(st->hsdev,
+ st->report_latency.report_id,
+ st->report_latency.index,
+ sizeof(value), &value);
if (ret < 0)
return ret;
@@ -497,10 +496,10 @@ bool hid_sensor_batch_mode_supported(struct hid_sensor_common *st)
EXPORT_SYMBOL_NS(hid_sensor_batch_mode_supported, "IIO_HID_ATTRIBUTES");
int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- struct hid_sensor_common *st,
- const u32 *sensitivity_addresses,
- u32 sensitivity_addresses_len)
+ u32 usage_id,
+ struct hid_sensor_common *st,
+ const u32 *sensitivity_addresses,
+ u32 sensitivity_addresses_len)
{
struct hid_sensor_hub_attribute_info timestamp;
@@ -511,14 +510,14 @@ int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev,
hid_sensor_get_reporting_interval(hsdev, usage_id, st);
sensor_hub_input_get_attribute_info(hsdev,
- HID_FEATURE_REPORT, usage_id,
- HID_USAGE_SENSOR_PROP_REPORT_STATE,
- &st->report_state);
+ HID_FEATURE_REPORT, usage_id,
+ HID_USAGE_SENSOR_PROP_REPORT_STATE,
+ &st->report_state);
sensor_hub_input_get_attribute_info(hsdev,
- HID_FEATURE_REPORT, usage_id,
- HID_USAGE_SENSOR_PROY_POWER_STATE,
- &st->power_state);
+ HID_FEATURE_REPORT, usage_id,
+ HID_USAGE_SENSOR_PROY_POWER_STATE,
+ &st->power_state);
st->power_state.logical_minimum = 1;
st->report_state.logical_minimum = 1;
@@ -526,7 +525,7 @@ int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev,
sensor_hub_input_get_attribute_info(hsdev,
HID_FEATURE_REPORT, usage_id,
HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS,
- &st->sensitivity);
+ &st->sensitivity);
sensor_hub_input_get_attribute_info(hsdev,
HID_FEATURE_REPORT, usage_id,
@@ -577,8 +576,9 @@ int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev,
timestamp.index, timestamp.report_id);
ret = sensor_hub_get_feature(hsdev,
- st->power_state.report_id,
- st->power_state.index, sizeof(value), &value);
+ st->power_state.report_id,
+ st->power_state.index,
+ sizeof(value), &value);
if (ret < 0)
return ret;
if (value < 0)
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
index c8ccf96f3d03..fffaebe8c7f0 100644
--- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
+++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c
@@ -233,7 +233,7 @@ void hid_sensor_remove_trigger(struct iio_dev *indio_dev,
EXPORT_SYMBOL_NS(hid_sensor_remove_trigger, "IIO_HID");
int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name,
- struct hid_sensor_common *attrb)
+ struct hid_sensor_common *attrb)
{
const struct iio_dev_attr **fifo_attrs;
int ret;
diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h
index f94fca4f1edf..589de858e369 100644
--- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h
+++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h
@@ -15,7 +15,7 @@ struct iio_dev;
extern const struct dev_pm_ops hid_sensor_pm_ops;
int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name,
- struct hid_sensor_common *attrb);
+ struct hid_sensor_common *attrb);
void hid_sensor_remove_trigger(struct iio_dev *indio_dev,
struct hid_sensor_common *attrb);
int hid_sensor_power_state(struct hid_sensor_common *st, bool state);
diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c
index bbca2111e79b..0dba475845d7 100644
--- a/drivers/iio/gyro/hid-sensor-gyro-3d.c
+++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c
@@ -85,9 +85,8 @@ static const struct iio_chan_spec gyro_3d_channels[] = {
/* Channel read_raw handler */
static int gyro_3d_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val, int *val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct gyro_3d_state *gyro_state = iio_priv(indio_dev);
int report_id = -1;
@@ -112,8 +111,7 @@ static int gyro_3d_read_raw(struct iio_dev *indio_dev,
min < 0);
else {
*val = 0;
- hid_sensor_power_state(&gyro_state->common_attributes,
- false);
+ hid_sensor_power_state(&gyro_state->common_attributes, false);
return -EINVAL;
}
hid_sensor_power_state(&gyro_state->common_attributes, false);
@@ -146,10 +144,8 @@ static int gyro_3d_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int gyro_3d_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct gyro_3d_state *gyro_state = iio_priv(indio_dev);
int ret = 0;
@@ -177,8 +173,7 @@ static const struct iio_info gyro_3d_info = {
/* Callback handler to send event after all samples are received and captured */
static int gyro_3d_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- void *priv)
+ u32 usage_id, void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct gyro_3d_state *gyro_state = iio_priv(indio_dev);
@@ -199,9 +194,9 @@ static int gyro_3d_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int gyro_3d_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- size_t raw_len, char *raw_data,
- void *priv)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct gyro_3d_state *gyro_state = iio_priv(indio_dev);
@@ -240,11 +235,12 @@ static int gyro_3d_parse_report(struct platform_device *pdev,
int ret;
for (unsigned int ch = CHANNEL_SCAN_INDEX_X; ch <= CHANNEL_SCAN_INDEX_Z; ch++) {
- ret = sensor_hub_input_get_attribute_info(hsdev,
- HID_INPUT_REPORT,
- usage_id,
- HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS + ch,
- &st->gyro[ch]);
+ ret = sensor_hub_input_get_attribute_info(
+ hsdev,
+ HID_INPUT_REPORT,
+ usage_id,
+ HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS + ch,
+ &st->gyro[ch]);
if (ret < 0)
break;
channels[ch].scan_type = (struct iio_scan_type) {
@@ -254,10 +250,10 @@ static int gyro_3d_parse_report(struct platform_device *pdev,
};
}
dev_dbg(&pdev->dev, "gyro_3d %x:%x, %x:%x, %x:%x\n",
- st->gyro[0].index,
- st->gyro[0].report_id,
- st->gyro[1].index, st->gyro[1].report_id,
- st->gyro[2].index, st->gyro[2].report_id);
+ st->gyro[0].index,
+ st->gyro[0].report_id,
+ st->gyro[1].index, st->gyro[1].report_id,
+ st->gyro[2].index, st->gyro[2].report_id);
st->scale_precision = hid_sensor_format_scale(
HID_USAGE_SENSOR_GYRO_3D,
@@ -286,10 +282,10 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
gyro_state->common_attributes.pdev = pdev;
ret = hid_sensor_parse_common_attributes(hsdev,
- HID_USAGE_SENSOR_GYRO_3D,
- &gyro_state->common_attributes,
- gyro_3d_sensitivity_addresses,
- ARRAY_SIZE(gyro_3d_sensitivity_addresses));
+ HID_USAGE_SENSOR_GYRO_3D,
+ &gyro_state->common_attributes,
+ gyro_3d_sensitivity_addresses,
+ ARRAY_SIZE(gyro_3d_sensitivity_addresses));
if (ret) {
dev_err(&pdev->dev, "failed to setup common attributes\n");
return ret;
@@ -318,7 +314,7 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
atomic_set(&gyro_state->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &gyro_state->common_attributes);
+ &gyro_state->common_attributes);
if (ret < 0) {
dev_err(&pdev->dev, "trigger setup failed\n");
return ret;
@@ -334,7 +330,7 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
gyro_state->callbacks.capture_sample = gyro_3d_capture_sample;
gyro_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D,
- &gyro_state->callbacks);
+ &gyro_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
diff --git a/drivers/iio/humidity/hid-sensor-humidity.c b/drivers/iio/humidity/hid-sensor-humidity.c
index c376a247b137..8dd8bc0b3ba1 100644
--- a/drivers/iio/humidity/hid-sensor-humidity.c
+++ b/drivers/iio/humidity/hid-sensor-humidity.c
@@ -45,7 +45,7 @@ static const struct iio_chan_spec humidity_channels[] = {
/* Adjust channel real bits based on report descriptor */
static void humidity_adjust_channel_bit_mask(struct iio_chan_spec *channels,
- int channel, int size)
+ int channel, int size)
{
channels[channel].scan_type.sign = 's';
/* Real storage bits will change based on the report desc. */
@@ -55,8 +55,8 @@ static void humidity_adjust_channel_bit_mask(struct iio_chan_spec *channels,
}
static int humidity_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val, int *val2, long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct hid_humidity_state *humid_st = iio_priv(indio_dev);
@@ -101,8 +101,8 @@ static int humidity_read_raw(struct iio_dev *indio_dev,
}
static int humidity_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val, int val2, long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct hid_humidity_state *humid_st = iio_priv(indio_dev);
@@ -127,7 +127,7 @@ static const struct iio_info humidity_info = {
/* Callback handler to send event after all samples are received and captured */
static int humidity_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id, void *pdev)
+ u32 usage_id, void *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct hid_humidity_state *humid_st = iio_priv(indio_dev);
@@ -141,8 +141,9 @@ static int humidity_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int humidity_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id, size_t raw_len,
- char *raw_data, void *pdev)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct hid_humidity_state *humid_st = iio_priv(indio_dev);
@@ -159,17 +160,17 @@ static int humidity_capture_sample(struct hid_sensor_hub_device *hsdev,
/* Parse report which is specific to an usage id */
static int humidity_parse_report(struct platform_device *pdev,
- struct hid_sensor_hub_device *hsdev,
- struct iio_chan_spec *channels,
- u32 usage_id,
- struct hid_humidity_state *st)
+ struct hid_sensor_hub_device *hsdev,
+ struct iio_chan_spec *channels,
+ u32 usage_id,
+ struct hid_humidity_state *st)
{
int ret;
ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT,
- usage_id,
- HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY,
- &st->humidity_attr);
+ usage_id,
+ HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY,
+ &st->humidity_attr);
if (ret < 0)
return ret;
@@ -208,20 +209,20 @@ static int hid_humidity_probe(struct platform_device *pdev)
humid_st->common_attributes.pdev = pdev;
ret = hid_sensor_parse_common_attributes(hsdev,
- HID_USAGE_SENSOR_HUMIDITY,
- &humid_st->common_attributes,
- humidity_sensitivity_addresses,
- ARRAY_SIZE(humidity_sensitivity_addresses));
+ HID_USAGE_SENSOR_HUMIDITY,
+ &humid_st->common_attributes,
+ humidity_sensitivity_addresses,
+ ARRAY_SIZE(humidity_sensitivity_addresses));
if (ret)
return ret;
humid_chans = devm_kmemdup(&indio_dev->dev, humidity_channels,
- sizeof(humidity_channels), GFP_KERNEL);
+ sizeof(humidity_channels), GFP_KERNEL);
if (!humid_chans)
return -ENOMEM;
ret = humidity_parse_report(pdev, hsdev, humid_chans,
- HID_USAGE_SENSOR_HUMIDITY, humid_st);
+ HID_USAGE_SENSOR_HUMIDITY, humid_st);
if (ret)
return ret;
@@ -234,7 +235,7 @@ static int hid_humidity_probe(struct platform_device *pdev)
atomic_set(&humid_st->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &humid_st->common_attributes);
+ &humid_st->common_attributes);
if (ret)
return ret;
@@ -242,7 +243,7 @@ static int hid_humidity_probe(struct platform_device *pdev)
humidity_callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_HUMIDITY,
- &humidity_callbacks);
+ &humidity_callbacks);
if (ret)
goto error_remove_trigger;
diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c
index ae2fda8dc500..85a1ef55c616 100644
--- a/drivers/iio/light/hid-sensor-als.c
+++ b/drivers/iio/light/hid-sensor-als.c
@@ -120,9 +120,8 @@ static const struct iio_chan_spec als_channels[] = {
/* Channel read_raw handler */
static int als_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val, int *val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct als_state *als_state = iio_priv(indio_dev);
struct hid_sensor_hub_device *hsdev = als_state->common_attributes.hsdev;
@@ -163,12 +162,12 @@ static int als_read_raw(struct iio_dev *indio_dev,
}
if (report_id >= 0) {
hid_sensor_power_state(&als_state->common_attributes,
- true);
+ true);
*val = sensor_hub_input_attr_get_raw_value(
hsdev, hsdev->usage, address, report_id,
SENSOR_HUB_SYNC, min < 0);
hid_sensor_power_state(&als_state->common_attributes,
- false);
+ false);
} else {
*val = 0;
return -EINVAL;
@@ -206,10 +205,8 @@ static int als_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int als_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct als_state *als_state = iio_priv(indio_dev);
int ret = 0;
@@ -241,8 +238,7 @@ static const struct iio_info als_info = {
/* Callback handler to send event after all samples are received and captured */
static int als_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- void *priv)
+ u32 usage_id, void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct als_state *als_state = iio_priv(indio_dev);
@@ -263,9 +259,9 @@ static int als_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int als_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- size_t raw_len, char *raw_data,
- void *priv)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct als_state *als_state = iio_priv(indio_dev);
@@ -304,9 +300,9 @@ static int als_capture_sample(struct hid_sensor_hub_device *hsdev,
/* Parse report which is specific to an usage id*/
static int als_parse_report(struct platform_device *pdev,
- struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- struct als_state *st)
+ struct hid_sensor_hub_device *hsdev,
+ u32 usage_id,
+ struct als_state *st)
{
struct iio_chan_spec *channels;
int ret, index = 0;
@@ -400,7 +396,7 @@ static int hid_als_probe(struct platform_device *pdev)
atomic_set(&als_state->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &als_state->common_attributes);
+ &als_state->common_attributes);
if (ret < 0) {
dev_err(&pdev->dev, "trigger setup failed\n");
return ret;
diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c
index 9059f00f0ced..dd8dddce46ec 100644
--- a/drivers/iio/light/hid-sensor-prox.c
+++ b/drivers/iio/light/hid-sensor-prox.c
@@ -70,9 +70,8 @@ static const struct iio_chan_spec prox_channels[] = {
/* Channel read_raw handler */
static int prox_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val, int *val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct prox_state *prox_state = iio_priv(indio_dev);
struct hid_sensor_hub_device *hsdev;
@@ -135,10 +134,8 @@ static int prox_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int prox_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct prox_state *prox_state = iio_priv(indio_dev);
int ret = 0;
@@ -166,8 +163,7 @@ static const struct iio_info prox_info = {
/* Callback handler to send event after all samples are received and captured */
static int prox_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- void *priv)
+ u32 usage_id, void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct prox_state *prox_state = iio_priv(indio_dev);
@@ -183,9 +179,9 @@ static int prox_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int prox_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- size_t raw_len, char *raw_data,
- void *priv)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct prox_state *prox_state = iio_priv(indio_dev);
@@ -218,8 +214,8 @@ static int prox_capture_sample(struct hid_sensor_hub_device *hsdev,
/* Parse report which is specific to an usage id*/
static int prox_parse_report(struct platform_device *pdev,
- struct hid_sensor_hub_device *hsdev,
- struct prox_state *st)
+ struct hid_sensor_hub_device *hsdev,
+ struct prox_state *st)
{
struct iio_chan_spec *channels = st->channels;
int index = 0;
@@ -271,8 +267,7 @@ static int hid_prox_probe(struct platform_device *pdev)
struct iio_dev *indio_dev;
struct prox_state *prox_state;
- indio_dev = devm_iio_device_alloc(&pdev->dev,
- sizeof(struct prox_state));
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct prox_state));
if (!indio_dev)
return -ENOMEM;
platform_set_drvdata(pdev, indio_dev);
@@ -306,7 +301,7 @@ static int hid_prox_probe(struct platform_device *pdev)
atomic_set(&prox_state->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &prox_state->common_attributes);
+ &prox_state->common_attributes);
if (ret) {
dev_err(&pdev->dev, "trigger setup failed\n");
return ret;
diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
index 23884825eb00..ad10fa20fae0 100644
--- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c
+++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
@@ -135,9 +135,8 @@ static const struct iio_chan_spec magn_3d_channels[] = {
/* Channel read_raw handler */
static int magn_3d_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val, int *val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct magn_3d_state *magn_state = iio_priv(indio_dev);
int report_id = -1;
@@ -167,8 +166,7 @@ static int magn_3d_read_raw(struct iio_dev *indio_dev,
false);
return -EINVAL;
}
- hid_sensor_power_state(&magn_state->magn_flux_attributes,
- false);
+ hid_sensor_power_state(&magn_state->magn_flux_attributes, false);
ret_type = IIO_VAL_INT;
break;
case IIO_CHAN_INFO_SCALE:
@@ -229,10 +227,8 @@ static int magn_3d_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int magn_3d_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct magn_3d_state *magn_state = iio_priv(indio_dev);
int ret = 0;
@@ -270,8 +266,7 @@ static const struct iio_info magn_3d_info = {
/* Callback handler to send event after all samples are received and captured */
static int magn_3d_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- void *priv)
+ u32 usage_id, void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct magn_3d_state *magn_state = iio_priv(indio_dev);
@@ -292,9 +287,9 @@ static int magn_3d_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int magn_3d_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- size_t raw_len, char *raw_data,
- void *priv)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct magn_3d_state *magn_state = iio_priv(indio_dev);
@@ -353,11 +348,12 @@ static int magn_3d_parse_report(struct platform_device *pdev,
u32 address = magn_3d_addresses[i];
/* Check if usage attribute exists in the sensor hub device */
- status = sensor_hub_input_get_attribute_info(hsdev,
- HID_INPUT_REPORT,
- usage_id,
- address,
- &(st->magn[i]));
+ status = sensor_hub_input_get_attribute_info(
+ hsdev,
+ HID_INPUT_REPORT,
+ usage_id,
+ address,
+ &(st->magn[i]));
if (!status)
attr_count++;
}
@@ -368,18 +364,17 @@ static int magn_3d_parse_report(struct platform_device *pdev,
return -EINVAL;
}
- dev_dbg(&pdev->dev, "magn_3d Found %d usage attributes\n",
- attr_count);
+ dev_dbg(&pdev->dev, "magn_3d Found %d usage attributes\n", attr_count);
dev_dbg(&pdev->dev, "magn_3d X: %x:%x Y: %x:%x Z: %x:%x\n",
- st->magn[0].index,
- st->magn[0].report_id,
- st->magn[1].index, st->magn[1].report_id,
- st->magn[2].index, st->magn[2].report_id);
+ st->magn[0].index,
+ st->magn[0].report_id,
+ st->magn[1].index, st->magn[1].report_id,
+ st->magn[2].index, st->magn[2].report_id);
/* Setup IIO channel array */
_channels = devm_kcalloc(&pdev->dev, attr_count,
- sizeof(struct iio_chan_spec),
- GFP_KERNEL);
+ sizeof(struct iio_chan_spec),
+ GFP_KERNEL);
if (!_channels) {
dev_err(&pdev->dev,
"failed to allocate space for iio channels\n");
@@ -426,8 +421,7 @@ static int magn_3d_parse_report(struct platform_device *pdev,
*channels = _channels;
- dev_dbg(&pdev->dev, "magn_3d Setup %d IIO channels\n",
- *chan_count);
+ dev_dbg(&pdev->dev, "magn_3d Setup %d IIO channels\n", *chan_count);
st->magn_flux_attr.scale_precision = hid_sensor_format_scale(
HID_USAGE_SENSOR_COMPASS_3D,
@@ -442,7 +436,8 @@ static int magn_3d_parse_report(struct platform_device *pdev,
&st->rot_attr.scale_post_decml);
if (st->rot_attributes.sensitivity.index < 0) {
- sensor_hub_input_get_attribute_info(hsdev,
+ sensor_hub_input_get_attribute_info(
+ hsdev,
HID_FEATURE_REPORT, usage_id,
HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH,
@@ -477,11 +472,12 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
magn_state->magn_flux_attributes.hsdev = hsdev;
magn_state->magn_flux_attributes.pdev = pdev;
- ret = hid_sensor_parse_common_attributes(hsdev,
- HID_USAGE_SENSOR_COMPASS_3D,
- &magn_state->magn_flux_attributes,
- magn_3d_sensitivity_addresses,
- ARRAY_SIZE(magn_3d_sensitivity_addresses));
+ ret = hid_sensor_parse_common_attributes(
+ hsdev,
+ HID_USAGE_SENSOR_COMPASS_3D,
+ &magn_state->magn_flux_attributes,
+ magn_3d_sensitivity_addresses,
+ ARRAY_SIZE(magn_3d_sensitivity_addresses));
if (ret) {
dev_err(&pdev->dev, "failed to setup common attributes\n");
return ret;
@@ -491,8 +487,8 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
magn_state->rot_attributes.sensitivity.index = -1;
ret = magn_3d_parse_report(pdev, hsdev,
- &channels, &chan_count,
- HID_USAGE_SENSOR_COMPASS_3D, magn_state);
+ &channels, &chan_count,
+ HID_USAGE_SENSOR_COMPASS_3D, magn_state);
if (ret) {
dev_err(&pdev->dev, "failed to parse report\n");
return ret;
@@ -507,7 +503,7 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
atomic_set(&magn_state->magn_flux_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &magn_state->magn_flux_attributes);
+ &magn_state->magn_flux_attributes);
if (ret < 0) {
dev_err(&pdev->dev, "trigger setup failed\n");
return ret;
@@ -523,7 +519,7 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
magn_state->callbacks.capture_sample = magn_3d_capture_sample;
magn_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D,
- &magn_state->callbacks);
+ &magn_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c
index c7fbff498be7..870c8929491e 100644
--- a/drivers/iio/orientation/hid-sensor-incl-3d.c
+++ b/drivers/iio/orientation/hid-sensor-incl-3d.c
@@ -86,8 +86,7 @@ static const struct iio_chan_spec incl_3d_channels[] = {
};
/* Adjust channel real bits based on report descriptor */
-static void incl_3d_adjust_channel_bit_mask(struct iio_chan_spec *chan,
- int size)
+static void incl_3d_adjust_channel_bit_mask(struct iio_chan_spec *chan, int size)
{
chan->scan_type.sign = 's';
/* Real storage bits will change based on the report desc. */
@@ -98,9 +97,8 @@ static void incl_3d_adjust_channel_bit_mask(struct iio_chan_spec *chan,
/* Channel read_raw handler */
static int incl_3d_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val, int *val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct incl_3d_state *incl_state = iio_priv(indio_dev);
int report_id = -1;
@@ -125,7 +123,7 @@ static int incl_3d_read_raw(struct iio_dev *indio_dev,
min < 0);
else {
hid_sensor_power_state(&incl_state->common_attributes,
- false);
+ false);
return -EINVAL;
}
hid_sensor_power_state(&incl_state->common_attributes, false);
@@ -158,10 +156,8 @@ static int incl_3d_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int incl_3d_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct incl_3d_state *incl_state = iio_priv(indio_dev);
int ret;
@@ -189,8 +185,7 @@ static const struct iio_info incl_3d_info = {
/* Callback handler to send event after all samples are received and captured */
static int incl_3d_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- void *priv)
+ u32 usage_id, void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct incl_3d_state *incl_state = iio_priv(indio_dev);
@@ -212,9 +207,9 @@ static int incl_3d_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int incl_3d_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- size_t raw_len, char *raw_data,
- void *priv)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct incl_3d_state *incl_state = iio_priv(indio_dev);
@@ -350,7 +345,7 @@ static int hid_incl_3d_probe(struct platform_device *pdev)
atomic_set(&incl_state->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &incl_state->common_attributes);
+ &incl_state->common_attributes);
if (ret) {
dev_err(&pdev->dev, "trigger setup failed\n");
return ret;
@@ -366,8 +361,8 @@ static int hid_incl_3d_probe(struct platform_device *pdev)
incl_state->callbacks.capture_sample = incl_3d_capture_sample;
incl_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev,
- HID_USAGE_SENSOR_INCLINOMETER_3D,
- &incl_state->callbacks);
+ HID_USAGE_SENSOR_INCLINOMETER_3D,
+ &incl_state->callbacks);
if (ret) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
diff --git a/drivers/iio/orientation/hid-sensor-rotation.c b/drivers/iio/orientation/hid-sensor-rotation.c
index 20563d8efaf6..2dad0453fc67 100644
--- a/drivers/iio/orientation/hid-sensor-rotation.c
+++ b/drivers/iio/orientation/hid-sensor-rotation.c
@@ -81,9 +81,8 @@ static const struct iio_chan_spec dev_rot_channels[] = {
/* Channel read_raw handler */
static int dev_rot_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int size, int *vals, int *val_len,
- long mask)
+ struct iio_chan_spec const *chan,
+ int size, int *vals, int *val_len, long mask)
{
struct dev_rot_state *rot_state = iio_priv(indio_dev);
int ret_type;
@@ -129,10 +128,8 @@ static int dev_rot_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int dev_rot_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct dev_rot_state *rot_state = iio_priv(indio_dev);
int ret;
@@ -176,8 +173,7 @@ static const struct iio_info dev_rot_info = {
/* Callback handler to send event after all samples are received and captured */
static int dev_rot_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- void *priv)
+ u32 usage_id, void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct dev_rot_state *rot_state = iio_priv(indio_dev);
@@ -209,9 +205,9 @@ static int dev_rot_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int dev_rot_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- size_t raw_len, char *raw_data,
- void *priv)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct dev_rot_state *rot_state = iio_priv(indio_dev);
@@ -326,7 +322,7 @@ static int hid_dev_rot_probe(struct platform_device *pdev)
atomic_set(&rot_state->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &rot_state->common_attributes);
+ &rot_state->common_attributes);
if (ret) {
dev_err(&pdev->dev, "trigger setup failed\n");
return ret;
@@ -342,7 +338,7 @@ static int hid_dev_rot_probe(struct platform_device *pdev)
rot_state->callbacks.capture_sample = dev_rot_capture_sample;
rot_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, hsdev->usage,
- &rot_state->callbacks);
+ &rot_state->callbacks);
if (ret) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
diff --git a/drivers/iio/position/hid-sensor-custom-intel-hinge.c b/drivers/iio/position/hid-sensor-custom-intel-hinge.c
index 2139ddb670c4..1f4a40716c3f 100644
--- a/drivers/iio/position/hid-sensor-custom-intel-hinge.c
+++ b/drivers/iio/position/hid-sensor-custom-intel-hinge.c
@@ -107,8 +107,8 @@ static void hinge_adjust_channel_realbits(struct iio_chan_spec *channels,
/* Channel read_raw handler */
static int hinge_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan, int *val, int *val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct hinge_state *st = iio_priv(indio_dev);
struct hid_sensor_hub_device *hsdev;
@@ -154,8 +154,8 @@ static int hinge_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int hinge_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan, int val, int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct hinge_state *st = iio_priv(indio_dev);
@@ -209,8 +209,9 @@ static int hinge_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int hinge_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id, size_t raw_len,
- char *raw_data, void *priv)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct hinge_state *st = iio_priv(indio_dev);
diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c
index 3e47a10d72a8..de20740f6cca 100644
--- a/drivers/iio/pressure/hid-sensor-press.c
+++ b/drivers/iio/pressure/hid-sensor-press.c
@@ -56,9 +56,8 @@ static const struct iio_chan_spec press_channels[] = {
/* Channel read_raw handler */
static int press_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val, int *val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
{
struct press_state *press_state = iio_priv(indio_dev);
int report_id = -1;
@@ -82,7 +81,7 @@ static int press_read_raw(struct iio_dev *indio_dev,
}
if (report_id >= 0) {
hid_sensor_power_state(&press_state->common_attributes,
- true);
+ true);
*val = sensor_hub_input_attr_get_raw_value(
press_state->common_attributes.hsdev,
HID_USAGE_SENSOR_PRESSURE, address,
@@ -90,7 +89,7 @@ static int press_read_raw(struct iio_dev *indio_dev,
SENSOR_HUB_SYNC,
min < 0);
hid_sensor_power_state(&press_state->common_attributes,
- false);
+ false);
} else {
*val = 0;
return -EINVAL;
@@ -124,10 +123,8 @@ static int press_read_raw(struct iio_dev *indio_dev,
/* Channel write_raw handler */
static int press_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct press_state *press_state = iio_priv(indio_dev);
int ret = 0;
@@ -155,8 +152,7 @@ static const struct iio_info press_info = {
/* Callback handler to send event after all samples are received and captured */
static int press_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id,
- void *priv)
+ u32 usage_id, void *priv)
{
struct iio_dev *indio_dev = platform_get_drvdata(priv);
struct press_state *press_state = iio_priv(indio_dev);
@@ -202,10 +198,10 @@ static int press_capture_sample(struct hid_sensor_hub_device *hsdev,
/* Parse report which is specific to an usage id*/
static int press_parse_report(struct platform_device *pdev,
- struct hid_sensor_hub_device *hsdev,
- struct iio_chan_spec *channels,
- u32 usage_id,
- struct press_state *st)
+ struct hid_sensor_hub_device *hsdev,
+ struct iio_chan_spec *channels,
+ u32 usage_id,
+ struct press_state *st)
{
int ret;
@@ -222,7 +218,7 @@ static int press_parse_report(struct platform_device *pdev,
};
dev_dbg(&pdev->dev, "press %x:%x\n", st->press_attr.index,
- st->press_attr.report_id);
+ st->press_attr.report_id);
st->scale_precision = hid_sensor_format_scale(
HID_USAGE_SENSOR_PRESSURE,
@@ -241,8 +237,7 @@ static int hid_press_probe(struct platform_device *pdev)
struct iio_dev *indio_dev;
struct press_state *press_state;
- indio_dev = devm_iio_device_alloc(&pdev->dev,
- sizeof(struct press_state));
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct press_state));
if (!indio_dev)
return -ENOMEM;
platform_set_drvdata(pdev, indio_dev);
@@ -285,7 +280,7 @@ static int hid_press_probe(struct platform_device *pdev)
atomic_set(&press_state->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &press_state->common_attributes);
+ &press_state->common_attributes);
if (ret) {
dev_err(&pdev->dev, "trigger setup failed\n");
return ret;
@@ -301,7 +296,7 @@ static int hid_press_probe(struct platform_device *pdev)
press_state->callbacks.capture_sample = press_capture_sample;
press_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PRESSURE,
- &press_state->callbacks);
+ &press_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
diff --git a/drivers/iio/temperature/hid-sensor-temperature.c b/drivers/iio/temperature/hid-sensor-temperature.c
index a8d3a15f9c53..18da85e6c60e 100644
--- a/drivers/iio/temperature/hid-sensor-temperature.c
+++ b/drivers/iio/temperature/hid-sensor-temperature.c
@@ -45,7 +45,7 @@ static const struct iio_chan_spec temperature_channels[] = {
/* Adjust channel real bits based on report descriptor */
static void temperature_adjust_channel_bit_mask(struct iio_chan_spec *channels,
- int channel, int size)
+ int channel, int size)
{
channels[channel].scan_type.sign = 's';
/* Real storage bits will change based on the report desc. */
@@ -101,8 +101,8 @@ static int temperature_read_raw(struct iio_dev *indio_dev,
}
static int temperature_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val, int val2, long mask)
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
{
struct temperature_state *temp_st = iio_priv(indio_dev);
@@ -125,7 +125,7 @@ static const struct iio_info temperature_info = {
/* Callback handler to send event after all samples are received and captured */
static int temperature_proc_event(struct hid_sensor_hub_device *hsdev,
- u32 usage_id, void *pdev)
+ u32 usage_id, void *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct temperature_state *temp_st = iio_priv(indio_dev);
@@ -140,8 +140,9 @@ static int temperature_proc_event(struct hid_sensor_hub_device *hsdev,
/* Capture samples in local storage */
static int temperature_capture_sample(struct hid_sensor_hub_device *hsdev,
- u32 usage_id, size_t raw_len,
- char *raw_data, void *pdev)
+ u32 usage_id,
+ size_t raw_len, char *raw_data,
+ void *pdev)
{
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct temperature_state *temp_st = iio_priv(indio_dev);
@@ -157,10 +158,10 @@ static int temperature_capture_sample(struct hid_sensor_hub_device *hsdev,
/* Parse report which is specific to an usage id*/
static int temperature_parse_report(struct platform_device *pdev,
- struct hid_sensor_hub_device *hsdev,
- struct iio_chan_spec *channels,
- u32 usage_id,
- struct temperature_state *st)
+ struct hid_sensor_hub_device *hsdev,
+ struct iio_chan_spec *channels,
+ u32 usage_id,
+ struct temperature_state *st)
{
int ret;
@@ -171,8 +172,7 @@ static int temperature_parse_report(struct platform_device *pdev,
if (ret < 0)
return ret;
- temperature_adjust_channel_bit_mask(channels, 0,
- st->temperature_attr.size);
+ temperature_adjust_channel_bit_mask(channels, 0, st->temperature_attr.size);
st->scale_precision = hid_sensor_format_scale(
HID_USAGE_SENSOR_TEMPERATURE,
@@ -206,10 +206,10 @@ static int hid_temperature_probe(struct platform_device *pdev)
temp_st->common_attributes.pdev = pdev;
ret = hid_sensor_parse_common_attributes(hsdev,
- HID_USAGE_SENSOR_TEMPERATURE,
- &temp_st->common_attributes,
- temperature_sensitivity_addresses,
- ARRAY_SIZE(temperature_sensitivity_addresses));
+ HID_USAGE_SENSOR_TEMPERATURE,
+ &temp_st->common_attributes,
+ temperature_sensitivity_addresses,
+ ARRAY_SIZE(temperature_sensitivity_addresses));
if (ret)
return ret;
@@ -219,7 +219,7 @@ static int hid_temperature_probe(struct platform_device *pdev)
return -ENOMEM;
ret = temperature_parse_report(pdev, hsdev, temp_chans,
- HID_USAGE_SENSOR_TEMPERATURE, temp_st);
+ HID_USAGE_SENSOR_TEMPERATURE, temp_st);
if (ret)
return ret;
@@ -232,7 +232,7 @@ static int hid_temperature_probe(struct platform_device *pdev)
atomic_set(&temp_st->common_attributes.data_ready, 0);
ret = hid_sensor_setup_trigger(indio_dev, name,
- &temp_st->common_attributes);
+ &temp_st->common_attributes);
if (ret)
return ret;
@@ -240,7 +240,7 @@ static int hid_temperature_probe(struct platform_device *pdev)
temperature_callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE,
- &temperature_callbacks);
+ &temperature_callbacks);
if (ret)
goto error_remove_trigger;
--
2.34.1
^ permalink raw reply related
* [PATCH v2 5/6] iio: position: hid-sensor-custom-intel-hinge: use common device for devres
From: Sanjay Chitroda via B4 Relay @ 2026-07-02 16:18 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada
Cc: linux-iio, linux-kernel, linux-input, Sanjay Chitroda
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-0-b87f01f5efbc@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
kmemdup() is used for memory that is logically tied to the HID
platform device, even though the driver binds into the IIO framework.
Using &indio_dev->dev for devres allocations works functionally, but it
results in two separate devres ownership trees—one for the HID
platform device (pdev) and another for the IIO device (indio_dev).
The devres framework is intended to have a single, well-defined parent
device. Since the memory originates from HID sensor probing and is not
IIO-specific, &pdev->dev is the correct and logical owner.
Switch to using the platform device for devm_kmemdup() so that all
resources are released deterministically and consistently.
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/position/hid-sensor-custom-intel-hinge.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/iio/position/hid-sensor-custom-intel-hinge.c b/drivers/iio/position/hid-sensor-custom-intel-hinge.c
index 1f4a40716c3f..eb6c59f81c3f 100644
--- a/drivers/iio/position/hid-sensor-custom-intel-hinge.c
+++ b/drivers/iio/position/hid-sensor-custom-intel-hinge.c
@@ -293,7 +293,7 @@ static int hid_hinge_probe(struct platform_device *pdev)
}
indio_dev->num_channels = ARRAY_SIZE(hinge_channels);
- indio_dev->channels = devm_kmemdup(&indio_dev->dev, hinge_channels,
+ indio_dev->channels = devm_kmemdup(&pdev->dev, hinge_channels,
sizeof(hinge_channels), GFP_KERNEL);
if (!indio_dev->channels)
return -ENOMEM;
--
2.34.1
^ permalink raw reply related
* [PATCH v2 6/6] iio: temperature: hid-sensor-temperature: use common device for devres
From: Sanjay Chitroda via B4 Relay @ 2026-07-02 16:18 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada
Cc: linux-iio, linux-kernel, linux-input, Sanjay Chitroda
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-0-b87f01f5efbc@gmail.com>
From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
kmemdup() is used for memory that is logically tied to the HID
platform device, even though the driver binds into the IIO framework.
Using &indio_dev->dev for devres allocations works functionally, but it
results in two separate devres ownership trees—one for the HID
platform device (pdev) and another for the IIO device (indio_dev).
The devres framework is intended to have a single, well-defined parent
device. Since the memory originates from HID sensor probing and is not
IIO-specific, &pdev->dev is the correct and logical owner.
Switch to using the platform device for devm_kmemdup() so that all
resources are released deterministically and consistently.
Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
---
drivers/iio/temperature/hid-sensor-temperature.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/temperature/hid-sensor-temperature.c b/drivers/iio/temperature/hid-sensor-temperature.c
index 18da85e6c60e..6dfb606a53af 100644
--- a/drivers/iio/temperature/hid-sensor-temperature.c
+++ b/drivers/iio/temperature/hid-sensor-temperature.c
@@ -213,8 +213,8 @@ static int hid_temperature_probe(struct platform_device *pdev)
if (ret)
return ret;
- temp_chans = devm_kmemdup(&indio_dev->dev, temperature_channels,
- sizeof(temperature_channels), GFP_KERNEL);
+ temp_chans = devm_kmemdup(&pdev->dev, temperature_channels,
+ sizeof(temperature_channels), GFP_KERNEL);
if (!temp_chans)
return -ENOMEM;
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v9 0/7] mfd: Add support for Asus Transformer embedded controller
From: Lee Jones @ 2026-07-02 16:24 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Pavel Machek, Sebastian Reichel, Ion Agorria,
Michał Mirosław, devicetree, linux-kernel, linux-input,
linux-leds, linux-pm
In-Reply-To: <20260625081529.22447-1-clamor95@gmail.com>
On Thu, 25 Jun 2026, Svyatoslav Ryhel wrote:
> Add support for embedded controller used in Asus Transformers for
> managing power and input functions.
>
> ---
> Changes in v2:
> - converted sysfs debug exports into debugfs
> - added kernel-doc comments for exposed functions
> - fixed minor typos and inconsistencies
LEDs and MFD look okay.
Let me know when you have all the other Acks and I'll merge it through MFD.
--
Lee Jones
^ permalink raw reply
* Re: [PATCH v2 4/6] iio: humidity: hid-sensor-humidity: use common device for devres
From: sashiko-bot @ 2026-07-02 17:03 UTC (permalink / raw)
To: Sanjay Chitroda; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-4-b87f01f5efbc@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Switching devres allocation for `humid_chans` from `&indio_dev->dev` to `&pdev->dev` breaks the IIO object lifetime model, introducing a Use-After-Free (UAF) vulnerability when the IIO device outlives the platform device.
--
commit 2c7cc72d2b1a34bface667809d68f3aaec43f667
Author: Sanjay Chitroda <sanjayembeddedse@gmail.com>
iio: humidity: hid-sensor-humidity: use common device for devres
This commit changes the devres allocation for the humidity channels to use
the platform device instead of the IIO device. The goal is to consolidate
devres ownership under a single parent device.
> diff --git a/drivers/iio/humidity/hid-sensor-humidity.c b/drivers/iio/humidity/hid-sensor-humidity.c
> index 8dd8bc0b3ba18..b7130eac03945 100644
> --- a/drivers/iio/humidity/hid-sensor-humidity.c
> +++ b/drivers/iio/humidity/hid-sensor-humidity.c
> @@ -216,7 +216,7 @@ static int hid_humidity_probe(struct platform_device *pdev)
> if (ret)
> return ret;
>
> - humid_chans = devm_kmemdup(&indio_dev->dev, humidity_channels,
> + humid_chans = devm_kmemdup(&pdev->dev, humidity_channels,
[Severity: High]
Does this change introduce a regression if the IIO device outlives the
platform device?
If an in-kernel consumer obtains a channel reference via iio_channel_get(),
it increments the reference count of the IIO device (indio_dev->dev). This
allows indio_dev to outlive the platform device.
When the platform device is unbound, the devres framework will free
humid_chans because its lifetime is now tied to &pdev->dev. However, the
consumer will still hold an iio_channel structure where chan->channel points
into the now-freed humid_chans array.
If the consumer later calls APIs like iio_read_channel_ext_info(), could this
dereference chan->channel->ext_info and access freed memory?
> sizeof(humidity_channels), GFP_KERNEL);
> if (!humid_chans)
> return -ENOMEM;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-15-jun-hid-iio-alignment-v2-0-b87f01f5efbc@gmail.com?part=4
^ permalink raw reply
* Re: [PATCH v2 5/6] iio: position: hid-sensor-custom-intel-hinge: use common device for devres
From: sashiko-bot @ 2026-07-02 17:18 UTC (permalink / raw)
To: Sanjay Chitroda; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-5-b87f01f5efbc@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Lifetime mismatch creating a dangling pointer and latent Use-After-Free for indio_dev->channels
Pre-existing issues:
- [High] Preexisting Use-After-Free due to lack of synchronization in sensor_hub_remove_callback()
--
commit fb5b93327e543a3377eb1d115b291e219ae98165
Author: Sanjay Chitroda <sanjayembeddedse@gmail.com>
iio: position: hid-sensor-custom-intel-hinge: use common device for devres
This commit changes the devres allocation for the IIO channels array from
using the IIO device to using the platform device. The goal is to bind the
memory lifecycle to the HID platform device instead of the IIO device.
> diff --git a/drivers/iio/position/hid-sensor-custom-intel-hinge.c b/drivers/iio/position/hid-sensor-custom-intel-hinge.c
> index 1f4a40716c3f7..eb6c59f81c3f3 100644
> --- a/drivers/iio/position/hid-sensor-custom-intel-hinge.c
> +++ b/drivers/iio/position/hid-sensor-custom-intel-hinge.c
> @@ -293,7 +293,7 @@ static int hid_hinge_probe(struct platform_device *pdev)
> }
>
> indio_dev->num_channels = ARRAY_SIZE(hinge_channels);
> - indio_dev->channels = devm_kmemdup(&indio_dev->dev, hinge_channels,
> + indio_dev->channels = devm_kmemdup(&pdev->dev, hinge_channels,
> sizeof(hinge_channels), GFP_KERNEL);
[Severity: High]
Does binding the channels array to the platform device introduce a lifetime
issue for the IIO device?
In the IIO framework, indio_dev is reference-counted and can outlive the
platform device if an in-kernel consumer holds a reference via
iio_channel_get().
If the platform device unbinds, devres will automatically free the channels
array. However, because indio_dev remains alive, a subsequent access to
channel metadata by the consumer (such as calling
iio_get_channel_ext_info_count()) would dereference the freed channels
array.
> if (!indio_dev->channels)
> return -ENOMEM;
[Severity: High]
This isn't a bug introduced by this patch, but is there a missing
synchronization step when removing callbacks during the error paths here
and in the driver's unbind function?
The HID sensor hub processes events in sensor_hub_raw_event(), which looks
up a callback and executes it (like hinge_capture_sample()) while holding
pdata->lock.
When sensor_hub_remove_callback() is called, it removes the callback from
the list and returns without acquiring pdata->lock or waiting for the
ongoing event processing to finish:
drivers/hid/hid-sensor-hub.c:sensor_hub_remove_callback() {
spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
list_for_each_entry(callback, &pdata->dyn_callback_list, list)
if (callback->usage_id == usage_id &&
callback->hsdev == hsdev) {
list_del(&callback->list);
kfree(callback);
break;
}
spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
}
If the driver is unbound concurrently via sysfs while background sensor
events are being received, devres will free the driver's private state when
the unbind completes. The concurrent sensor_hub_raw_event() could then write
raw sensor data directly into the freed state structure.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702-15-jun-hid-iio-alignment-v2-0-b87f01f5efbc@gmail.com?part=5
^ permalink raw reply
* Re: [PATCH v2 2/6] iio: hid-sensors: align function parenthesis for readability
From: Jonathan Cameron @ 2026-07-02 17:20 UTC (permalink / raw)
To: Sanjay Chitroda via B4 Relay
Cc: sanjayembeddedse, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada, linux-iio, linux-kernel,
linux-input
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-2-b87f01f5efbc@gmail.com>
On Thu, 02 Jul 2026 21:47:59 +0530
Sanjay Chitroda via B4 Relay <devnull+sanjayembeddedse.gmail.com@kernel.org> wrote:
> From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
>
> Adjust alignment of parentheses across HID sensor IIO drivers to
> improve readability and maintain consistency with kernel coding style.
>
> While updating the formatting, group related arguments consistently in
> multi-line function signatures where appropriate.
>
> No functional change intended.
>
> Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
Hi Sanjay,
Whilst I appreciate this code isn't quite in line with standards
and usually like that stuff to be fixed up, in this particular case
this is a massive amount of churn. That churn will make backporting
fixes etc messier, so I'd like input on whether others consider this
one worthwhile. Jiri, Srinivas, Andy etc. What do you think?
A few comments on inconsistencies inline.
Jonathan
> ---
> drivers/iio/accel/hid-sensor-accel-3d.c | 46 ++++++-----
> .../iio/common/hid-sensors/hid-sensor-attributes.c | 88 +++++++++++-----------
> .../iio/common/hid-sensors/hid-sensor-trigger.c | 2 +-
> .../iio/common/hid-sensors/hid-sensor-trigger.h | 2 +-
> drivers/iio/gyro/hid-sensor-gyro-3d.c | 54 ++++++-------
> drivers/iio/humidity/hid-sensor-humidity.c | 47 ++++++------
> drivers/iio/light/hid-sensor-als.c | 32 ++++----
> drivers/iio/light/hid-sensor-prox.c | 29 +++----
> drivers/iio/magnetometer/hid-sensor-magn-3d.c | 74 +++++++++---------
> drivers/iio/orientation/hid-sensor-incl-3d.c | 31 ++++----
> drivers/iio/orientation/hid-sensor-rotation.c | 24 +++---
> .../iio/position/hid-sensor-custom-intel-hinge.c | 13 ++--
> drivers/iio/pressure/hid-sensor-press.c | 35 ++++-----
> drivers/iio/temperature/hid-sensor-temperature.c | 38 +++++-----
> 14 files changed, 241 insertions(+), 274 deletions(-)
> diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
> index 2f0a1ea42f48..13fa55e966f4 100644
> --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
> +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c
...
> }
>
> int hid_sensor_format_scale(u32 usage_id,
> - struct hid_sensor_hub_attribute_info *attr_info,
> - int *val0, int *val1)
> + struct hid_sensor_hub_attribute_info *attr_info,
> + int *val0, int *val1)
> {
> int i;
> int exp;
> @@ -414,9 +413,8 @@ int hid_sensor_format_scale(u32 usage_id,
>
> for (i = 0; i < ARRAY_SIZE(unit_conversion); ++i) {
> if (unit_conversion[i].usage_id == usage_id &&
> - unit_conversion[i].unit == attr_info->units) {
> - exp = hid_sensor_convert_exponent(
> - attr_info->unit_expo);
> + unit_conversion[i].unit == attr_info->units) {
> + exp = hid_sensor_convert_exponent(attr_info->unit_expo);
> adjust_exponent_nano(val0, val1,
> unit_conversion[i].scale_val0,
> unit_conversion[i].scale_val1, exp);
Why did this one get left alone?
> @@ -511,14 +510,14 @@ int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev,
> hid_sensor_get_reporting_interval(hsdev, usage_id, st);
>
> sensor_hub_input_get_attribute_info(hsdev,
> - HID_FEATURE_REPORT, usage_id,
> - HID_USAGE_SENSOR_PROP_REPORT_STATE,
> - &st->report_state);
> + HID_FEATURE_REPORT, usage_id,
> + HID_USAGE_SENSOR_PROP_REPORT_STATE,
> + &st->report_state);
>
> sensor_hub_input_get_attribute_info(hsdev,
> - HID_FEATURE_REPORT, usage_id,
> - HID_USAGE_SENSOR_PROY_POWER_STATE,
> - &st->power_state);
> + HID_FEATURE_REPORT, usage_id,
> + HID_USAGE_SENSOR_PROY_POWER_STATE,
> + &st->power_state);
See below.
> @@ -526,7 +525,7 @@ int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev,
> sensor_hub_input_get_attribute_info(hsdev,
> HID_FEATURE_REPORT, usage_id,
> HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS,
> - &st->sensitivity);
> + &st->sensitivity);
Some cases like this one are locally inconsistent so we'd want to fix those
even if leaving the majority alone.
> diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> index bbca2111e79b..0dba475845d7 100644
> --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c
> +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> @@ -240,11 +235,12 @@ static int gyro_3d_parse_report(struct platform_device *pdev,
> int ret;
>
> for (unsigned int ch = CHANNEL_SCAN_INDEX_X; ch <= CHANNEL_SCAN_INDEX_Z; ch++) {
> - ret = sensor_hub_input_get_attribute_info(hsdev,
> - HID_INPUT_REPORT,
> - usage_id,
> - HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS + ch,
> - &st->gyro[ch]);
> + ret = sensor_hub_input_get_attribute_info(
> + hsdev,
> + HID_INPUT_REPORT,
> + usage_id,
> + HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS + ch,
> + &st->gyro[ch]);
See below.
> diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
> index 23884825eb00..ad10fa20fae0 100644
> --- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c
> +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
...
> @@ -353,11 +348,12 @@ static int magn_3d_parse_report(struct platform_device *pdev,
> u32 address = magn_3d_addresses[i];
>
> /* Check if usage attribute exists in the sensor hub device */
> - status = sensor_hub_input_get_attribute_info(hsdev,
> - HID_INPUT_REPORT,
> - usage_id,
> - address,
> - &(st->magn[i]));
> + status = sensor_hub_input_get_attribute_info(
> + hsdev,
> + HID_INPUT_REPORT,
> + usage_id,
> + address,
> + &(st->magn[i]));
See below.
> @@ -442,7 +436,8 @@ static int magn_3d_parse_report(struct platform_device *pdev,
> &st->rot_attr.scale_post_decml);
>
> if (st->rot_attributes.sensitivity.index < 0) {
> - sensor_hub_input_get_attribute_info(hsdev,
> + sensor_hub_input_get_attribute_info(
> + hsdev,
> HID_FEATURE_REPORT, usage_id,
> HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
> HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH,
See below.
> @@ -477,11 +472,12 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
> magn_state->magn_flux_attributes.hsdev = hsdev;
> magn_state->magn_flux_attributes.pdev = pdev;
>
> - ret = hid_sensor_parse_common_attributes(hsdev,
> - HID_USAGE_SENSOR_COMPASS_3D,
> - &magn_state->magn_flux_attributes,
> - magn_3d_sensitivity_addresses,
> - ARRAY_SIZE(magn_3d_sensitivity_addresses));
> + ret = hid_sensor_parse_common_attributes(
> + hsdev,
> + HID_USAGE_SENSOR_COMPASS_3D,
> + &magn_state->magn_flux_attributes,
> + magn_3d_sensitivity_addresses,
> + ARRAY_SIZE(magn_3d_sensitivity_addresses));
See below - the movement of hsdev and the single tab vs two tab
difference was why I pointed out that this patch is inconsistent.
As noted there, I'd be tempted to leave these ones alone, even
if we are cleaning up most of the other indentation.
> diff --git a/drivers/iio/temperature/hid-sensor-temperature.c b/drivers/iio/temperature/hid-sensor-temperature.c
> index a8d3a15f9c53..18da85e6c60e 100644
> --- a/drivers/iio/temperature/hid-sensor-temperature.c
> +++ b/drivers/iio/temperature/hid-sensor-temperature.c
...
> @@ -206,10 +206,10 @@ static int hid_temperature_probe(struct platform_device *pdev)
> temp_st->common_attributes.pdev = pdev;
>
> ret = hid_sensor_parse_common_attributes(hsdev,
> - HID_USAGE_SENSOR_TEMPERATURE,
> - &temp_st->common_attributes,
> - temperature_sensitivity_addresses,
> - ARRAY_SIZE(temperature_sensitivity_addresses));
> + HID_USAGE_SENSOR_TEMPERATURE,
> + &temp_st->common_attributes,
> + temperature_sensitivity_addresses,
> + ARRAY_SIZE(temperature_sensitivity_addresses));
There aren't hard rules on how to format these cases where a break is needed.
That is kind of reflected in inconsistency in this set. I'd be tempted
to leave these ones alone.
Jonathan
>
^ permalink raw reply
* Re: [PATCH v2 3/6] iio: hid-sensors: Use implicit NULL pointer checks
From: Jonathan Cameron @ 2026-07-02 17:22 UTC (permalink / raw)
To: Sanjay Chitroda via B4 Relay
Cc: sanjayembeddedse, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada, linux-iio, linux-kernel,
linux-input, Maxwell Doose
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-3-b87f01f5efbc@gmail.com>
On Thu, 02 Jul 2026 21:48:00 +0530
Sanjay Chitroda via B4 Relay <devnull+sanjayembeddedse.gmail.com@kernel.org> wrote:
> From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
>
> Replace explicit NULL pointer comparisons with implicit checks across
> HID sensor IIO drivers to follow the preferred kernel coding style.
Is there anything in the kernel wide style guides about this?
I do prefer this style in IIO but perhaps we should document it
as local IIO style rather than implying general guidance (unless
there is some!)
>
> Convert 'if (indio_dev == NULL)' -> 'if (!indio_dev)'.
>
> No functional change.
>
> Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
> Reviewed-by: Maxwell Doose <m32285159@gmail.com>
> ---
> drivers/iio/accel/hid-sensor-accel-3d.c | 2 +-
> drivers/iio/magnetometer/hid-sensor-magn-3d.c | 2 +-
> drivers/iio/orientation/hid-sensor-incl-3d.c | 2 +-
> drivers/iio/orientation/hid-sensor-rotation.c | 2 +-
> 4 files changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c
> index 9197f3424c0c..225f8dd65ab1 100644
> --- a/drivers/iio/accel/hid-sensor-accel-3d.c
> +++ b/drivers/iio/accel/hid-sensor-accel-3d.c
> @@ -325,7 +325,7 @@ static int hid_accel_3d_probe(struct platform_device *pdev)
>
> indio_dev = devm_iio_device_alloc(&pdev->dev,
> sizeof(struct accel_3d_state));
> - if (indio_dev == NULL)
> + if (!indio_dev)
> return -ENOMEM;
>
> platform_set_drvdata(pdev, indio_dev);
> diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
> index ad10fa20fae0..738bad65d74d 100644
> --- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c
> +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
> @@ -463,7 +463,7 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
>
> indio_dev = devm_iio_device_alloc(&pdev->dev,
> sizeof(struct magn_3d_state));
> - if (indio_dev == NULL)
> + if (!indio_dev)
> return -ENOMEM;
>
> platform_set_drvdata(pdev, indio_dev);
> diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c
> index 870c8929491e..c8efb0dab8b6 100644
> --- a/drivers/iio/orientation/hid-sensor-incl-3d.c
> +++ b/drivers/iio/orientation/hid-sensor-incl-3d.c
> @@ -302,7 +302,7 @@ static int hid_incl_3d_probe(struct platform_device *pdev)
>
> indio_dev = devm_iio_device_alloc(&pdev->dev,
> sizeof(struct incl_3d_state));
> - if (indio_dev == NULL)
> + if (!indio_dev)
> return -ENOMEM;
>
> platform_set_drvdata(pdev, indio_dev);
> diff --git a/drivers/iio/orientation/hid-sensor-rotation.c b/drivers/iio/orientation/hid-sensor-rotation.c
> index 2dad0453fc67..6db253c1635d 100644
> --- a/drivers/iio/orientation/hid-sensor-rotation.c
> +++ b/drivers/iio/orientation/hid-sensor-rotation.c
> @@ -274,7 +274,7 @@ static int hid_dev_rot_probe(struct platform_device *pdev)
>
> indio_dev = devm_iio_device_alloc(&pdev->dev,
> sizeof(struct dev_rot_state));
> - if (indio_dev == NULL)
> + if (!indio_dev)
> return -ENOMEM;
>
> platform_set_drvdata(pdev, indio_dev);
>
^ permalink raw reply
* Re: [PATCH v2 4/6] iio: humidity: hid-sensor-humidity: use common device for devres
From: Jonathan Cameron @ 2026-07-02 17:26 UTC (permalink / raw)
To: Sanjay Chitroda via B4 Relay
Cc: sanjayembeddedse, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada, linux-iio, linux-kernel,
linux-input, Zhang Lixu
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-4-b87f01f5efbc@gmail.com>
On Thu, 02 Jul 2026 21:48:01 +0530
Sanjay Chitroda via B4 Relay <devnull+sanjayembeddedse.gmail.com@kernel.org> wrote:
> From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
>
> kmemdup() is used for memory that is logically tied to the HID
> platform device, even though the driver binds into the IIO framework.
>
> Using &indio_dev->dev for devres allocations works functionally, but it
> results in two separate devres ownership trees—one for the HID
> platform device (pdev) and another for the IIO device (indio_dev).
>
> The devres framework is intended to have a single, well-defined parent
> device. Since the memory originates from HID sensor probing and is not
> IIO-specific, &pdev->dev is the correct and logical owner.
>
> Switch to using the platform device for devm_kmemdup() so that all
> resources are released deterministically and consistently.
>
> Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
> Tested-by: Zhang Lixu <lixu.zhang@intel.com>
This one is more significant that the formatting change so I'll pick
it up now. Note that it does change the context for that formatting
change patch which touches the line below this change.
So applied by hand to the testing branch of iio.git.
Thanks,
Jonathan
> ---
> drivers/iio/humidity/hid-sensor-humidity.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/iio/humidity/hid-sensor-humidity.c b/drivers/iio/humidity/hid-sensor-humidity.c
> index 8dd8bc0b3ba1..b7130eac0394 100644
> --- a/drivers/iio/humidity/hid-sensor-humidity.c
> +++ b/drivers/iio/humidity/hid-sensor-humidity.c
> @@ -216,7 +216,7 @@ static int hid_humidity_probe(struct platform_device *pdev)
> if (ret)
> return ret;
>
> - humid_chans = devm_kmemdup(&indio_dev->dev, humidity_channels,
> + humid_chans = devm_kmemdup(&pdev->dev, humidity_channels,
> sizeof(humidity_channels), GFP_KERNEL);
> if (!humid_chans)
> return -ENOMEM;
>
^ permalink raw reply
* Re: [PATCH v2 5/6] iio: position: hid-sensor-custom-intel-hinge: use common device for devres
From: Jonathan Cameron @ 2026-07-02 17:26 UTC (permalink / raw)
To: Sanjay Chitroda via B4 Relay
Cc: sanjayembeddedse, David Lechner, Nuno Sá, Andy Shevchenko,
Jiri Kosina, Srinivas Pandruvada, linux-iio, linux-kernel,
linux-input
In-Reply-To: <20260702-15-jun-hid-iio-alignment-v2-5-b87f01f5efbc@gmail.com>
On Thu, 02 Jul 2026 21:48:02 +0530
Sanjay Chitroda via B4 Relay <devnull+sanjayembeddedse.gmail.com@kernel.org> wrote:
> From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
>
> kmemdup() is used for memory that is logically tied to the HID
> platform device, even though the driver binds into the IIO framework.
>
> Using &indio_dev->dev for devres allocations works functionally, but it
> results in two separate devres ownership trees—one for the HID
> platform device (pdev) and another for the IIO device (indio_dev).
>
> The devres framework is intended to have a single, well-defined parent
> device. Since the memory originates from HID sensor probing and is not
> IIO-specific, &pdev->dev is the correct and logical owner.
>
> Switch to using the platform device for devm_kmemdup() so that all
> resources are released deterministically and consistently.
>
> Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
Applied this one.
> ---
> drivers/iio/position/hid-sensor-custom-intel-hinge.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/iio/position/hid-sensor-custom-intel-hinge.c b/drivers/iio/position/hid-sensor-custom-intel-hinge.c
> index 1f4a40716c3f..eb6c59f81c3f 100644
> --- a/drivers/iio/position/hid-sensor-custom-intel-hinge.c
> +++ b/drivers/iio/position/hid-sensor-custom-intel-hinge.c
> @@ -293,7 +293,7 @@ static int hid_hinge_probe(struct platform_device *pdev)
> }
>
> indio_dev->num_channels = ARRAY_SIZE(hinge_channels);
> - indio_dev->channels = devm_kmemdup(&indio_dev->dev, hinge_channels,
> + indio_dev->channels = devm_kmemdup(&pdev->dev, hinge_channels,
> sizeof(hinge_channels), GFP_KERNEL);
> if (!indio_dev->channels)
> return -ENOMEM;
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox