From 336af689d58f245d9e12c2ddec1531e455080c68 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 24 Jun 2026 22:35:58 +0800 Subject: [PATCH 1/6] HID: amd_sfh: return an error when response wait times out amdtp_wait_for_response() waits for request_done before completing a report request. wait_event_interruptible_timeout() returns 0 when the wait expires, but the current code treats only negative values as errors and returns success on timeout. Return -ETIMEDOUT when the response wait expires while preserving the existing success path when the response has already been observed. Signed-off-by: Pengpeng Hou Acked-by: Basavaraj Natikar Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/amd_sfh_hid.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c index b04f675d49b0..8f88f965fbd5 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c @@ -87,16 +87,17 @@ static int amdtp_wait_for_response(struct hid_device *hid) break; } - if (!cli_data->request_done[i]) + if (!cli_data->request_done[i]) { ret = wait_event_interruptible_timeout(hid_data->hid_wait, cli_data->request_done[i], msecs_to_jiffies(AMD_SFH_RESPONSE_TIMEOUT)); - if (ret == -ERESTARTSYS) - return -ERESTARTSYS; - else if (ret < 0) - return -ETIMEDOUT; - else - return 0; + if (ret == -ERESTARTSYS) + return -ERESTARTSYS; + if (ret <= 0) + return -ETIMEDOUT; + } + + return 0; } void amdtp_hid_wakeup(struct hid_device *hid) From 1a73a4e7673f577d8cb9911107694c124a3eae54 Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Mon, 3 Aug 2026 23:22:26 +0530 Subject: [PATCH 2/6] HID: amd_sfh: Track MP2 version explicitly The MP2 version is currently known only implicitly, from whether an ops pointer was stored in the PCI driver_data. Subsequent changes need to act on the MP2 version directly, for example to read the operating-mode register only on confirmed MP2 v2. Track the MP2 version explicitly so that version-specific behaviour can be gated on it, and leave it unset for generations that do not require such handling. Signed-off-by: Basavaraj Natikar Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/amd_sfh_common.h | 6 ++++++ drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index 78f830c133e5..68586f08ab23 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -35,6 +35,11 @@ enum cmd_id { STOP_ALL_SENSORS = 8, }; +enum amd_mp2_version { + MP2_VER_V2 = 1, + MP2_VER_1_1 = 2, +}; + struct amd_mp2_sensor_info { u8 sensor_idx; u32 period; @@ -64,6 +69,7 @@ struct amd_mp2_dev { struct mutex lock; u8 init_done; u8 rver; + u8 mp2_ver; }; struct amd_mp2_ops { diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 4b81cebdc335..92801ca38957 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -285,6 +285,7 @@ static void mp2_select_ops(struct amd_mp2_dev *privdata) switch (acs) { case V2_STATUS: privdata->mp2_ops = &amd_sfh_ops_v2; + privdata->mp2_ver = MP2_VER_V2; break; default: privdata->mp2_ops = &amd_sfh_ops; @@ -471,8 +472,9 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i if (rc) return rc; - privdata->sfh1_1_ops = (const struct amd_sfh1_1_ops *)id->driver_data; - if (privdata->sfh1_1_ops) { + privdata->mp2_ver = (enum amd_mp2_version)id->driver_data; + if (privdata->mp2_ver >= MP2_VER_1_1) { + privdata->sfh1_1_ops = &sfh1_1_ops; if (boot_cpu_data.x86 >= 0x1A) privdata->rver = 1; @@ -540,8 +542,7 @@ static SIMPLE_DEV_PM_OPS(amd_mp2_pm_ops, amd_mp2_pci_suspend, static const struct pci_device_id amd_mp2_pci_tbl[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2) }, - { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MP2_1_1), - .driver_data = (kernel_ulong_t)&sfh1_1_ops }, + { PCI_DEVICE_DATA(AMD, MP2_1_1, MP2_VER_1_1) }, { } }; MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl); From c57016bcf6d0b57cda5579e5d6856bd9bbe52a77 Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Mon, 3 Aug 2026 23:22:27 +0530 Subject: [PATCH 3/6] HID: amd_sfh: Serialize access to the shared emp2 pointer The SFH accessors reach the device through a file-global emp2 pointer that is published at probe and cleared on remove. amd_get_sfh_info() is exported and called from other modules on unrelated threads, so a reader can observe a non-NULL emp2 and then race a concurrent unbind that clears it and frees the device. Serialize the emp2 publish/clear and all readers under a mutex, so a reader either sees a live device for the whole access or sees NULL. Signed-off-by: Basavaraj Natikar Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c index 837d59e7a661..dd2720bae65c 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c @@ -8,12 +8,14 @@ * Author: Basavaraj Natikar */ #include +#include #include #include #include "amd_sfh_interface.h" static struct amd_mp2_dev *emp2; +static DEFINE_MUTEX(emp2_lock); static int amd_sfh_wait_response(struct amd_mp2_dev *mp2, u8 sid, u32 cmd_id) { @@ -78,12 +80,14 @@ static struct amd_mp2_ops amd_sfh_ops = { void sfh_deinit_emp2(void) { + guard(mutex)(&emp2_lock); emp2 = NULL; } void sfh_interface_init(struct amd_mp2_dev *mp2) { mp2->mp2_ops = &amd_sfh_ops; + guard(mutex)(&emp2_lock); emp2 = mp2; } @@ -160,6 +164,8 @@ static int amd_sfh_als_info(u32 *ambient_light) int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op) { + guard(mutex)(&emp2_lock); + if (sfh_info) { switch (op) { case MT_HPD: From f797387307c2c33ca251c3ac1665bb15c663612f Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Mon, 3 Aug 2026 23:22:28 +0530 Subject: [PATCH 4/6] HID: amd_sfh: Add accessor to read the operating-mode sensor Allow other drivers to query the operating mode (laptop or tablet) reported by the Sensor Fusion Hub. This is the interface used by the tablet-mode switch driver to report the device posture to userspace. Signed-off-by: Basavaraj Natikar Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/amd_sfh_client.c | 16 +++++++++ drivers/hid/amd-sfh-hid/amd_sfh_common.h | 5 +++ drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 4 +++ .../amd-sfh-hid/sfh1_1/amd_sfh_interface.c | 33 +++++++++++++++++++ .../amd-sfh-hid/sfh1_1/amd_sfh_interface.h | 1 - include/linux/amd-pmf-io.h | 14 ++++++++ 6 files changed, 72 insertions(+), 1 deletion(-) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c index 96ae792beeb6..ae6add0b9ce3 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c @@ -383,3 +383,19 @@ int amd_sfh_hid_client_deinit(struct amd_mp2_dev *privdata) return 0; } + +bool amd_sfh_op_idx_enabled(struct amd_mp2_dev *mp2) +{ + struct amdtp_cl_data *cl = mp2->cl_data; + int i; + + if (!cl) + return false; + + for (i = 0; i < cl->num_hid_devices; i++) + if (cl->sensor_idx[i] == op_idx && + READ_ONCE(cl->sensor_sts[i]) == SENSOR_ENABLED) + return true; + + return false; +} diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index 68586f08ab23..0ca3254151ac 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -106,4 +106,9 @@ static inline u64 amd_get_p2c_val(struct amd_mp2_dev *mp2, u32 idx) { return mp2->rver == 1 ? AMD_P2C_MSG_V1(idx) : AMD_P2C_MSG(idx); } + +bool amd_sfh_op_idx_enabled(struct amd_mp2_dev *mp2); +void sfh_set_emp2(struct amd_mp2_dev *mp2); +void sfh_deinit_emp2(void); + #endif diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 92801ca38957..4b1cd260410d 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -251,6 +251,8 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id) static void amd_mp2_pci_remove(void *privdata) { struct amd_mp2_dev *mp2 = privdata; + + sfh_deinit_emp2(); amd_sfh_hid_client_deinit(privdata); mp2->mp2_ops->stop_all(mp2); pcim_intx(mp2->pdev, false); @@ -419,6 +421,7 @@ static void sfh_init_work(struct work_struct *work) return; } + sfh_set_emp2(mp2); amd_sfh_clear_intr(mp2); mp2->init_done = 1; } @@ -448,6 +451,7 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i privdata->pdev = pdev; dev_set_drvdata(&pdev->dev, privdata); + rc = pcim_enable_device(pdev); if (rc) return rc; diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c index dd2720bae65c..097c5513ccd8 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c @@ -84,6 +84,12 @@ void sfh_deinit_emp2(void) emp2 = NULL; } +void sfh_set_emp2(struct amd_mp2_dev *mp2) +{ + guard(mutex)(&emp2_lock); + emp2 = mp2; +} + void sfh_interface_init(struct amd_mp2_dev *mp2) { mp2->mp2_ops = &amd_sfh_ops; @@ -91,6 +97,31 @@ void sfh_interface_init(struct amd_mp2_dev *mp2) emp2 = mp2; } +static int amd_sfh_op_mode_info(u32 *op_mode) +{ + struct sfh_op_mode mode; + bool present; + + if (!op_mode) + return -EINVAL; + if (!emp2) + return -ENODEV; + + present = emp2->sfh1_1_ops ? emp2->dev_en.is_sra_present + : (emp2->mp2_ver == MP2_VER_V2 && + amd_sfh_op_idx_enabled(emp2)); + if (!present) + return -ENODEV; + + mode.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 3)); + dev_dbg(&emp2->pdev->dev, "op-mode: %s (mode=%u)\n", + mode.op_mode.mode == SFH_MODE_TABLET ? "tablet" : "laptop", + mode.op_mode.mode); + *op_mode = mode.op_mode.mode; + + return 0; +} + static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) { struct sfh_op_mode mode; @@ -175,6 +206,8 @@ int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op) case MT_SRA: return amd_sfh_mode_info(&sfh_info->platform_type, &sfh_info->laptop_placement); + case MT_OP_MODE: + return amd_sfh_op_mode_info(&sfh_info->op_mode); } } return -EINVAL; diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h index 665c99ad779f..56258c4d1b3a 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.h @@ -185,7 +185,6 @@ struct sfh_op_mode { }; void sfh_interface_init(struct amd_mp2_dev *mp2); -void sfh_deinit_emp2(void); void amd_sfh1_1_set_desc_ops(struct amd_mp2_ops *mp2_ops); int amd_sfh_float_to_int(u32 flt32_val); #endif diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h index 55198d2875cc..dc59c43bd8f2 100644 --- a/include/linux/amd-pmf-io.h +++ b/include/linux/amd-pmf-io.h @@ -19,11 +19,13 @@ * @MT_HPD: Message ID to know the Human presence info from MP2 FW * @MT_ALS: Message ID to know the Ambient light info from MP2 FW * @MT_SRA: Message ID to know the SRA data from MP2 FW + * @MT_OP_MODE: Message ID to know the operating-mode (tablet/laptop) info */ enum sfh_message_type { MT_HPD, MT_ALS, MT_SRA, + MT_OP_MODE, }; /** @@ -44,12 +46,24 @@ enum sfh_hpd_info { * @user_present: Populates the user presence information * @platform_type: Operating modes (clamshell, flat, tent, etc.) * @laptop_placement: Device states (ontable, onlap, outbag) + * @op_mode: Operating-mode field (see enum sfh_dev_mode); used for tablet detection */ struct amd_sfh_info { u32 ambient_light; u8 user_present; u32 platform_type; u32 laptop_placement; + u32 op_mode; +}; + +/** + * enum sfh_dev_mode - SFH operating-mode field (sfh_op_mode.mode, bits 0-2) + * @SFH_MODE_LAPTOP: Device is in laptop/clamshell posture + * @SFH_MODE_TABLET: Device is in tablet posture + */ +enum sfh_dev_mode { + SFH_MODE_LAPTOP = 1, + SFH_MODE_TABLET = 3, }; enum laptop_placement { From c669183ef138d9051341f5a8f6a6c6ee5a06b033 Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Mon, 3 Aug 2026 23:22:29 +0530 Subject: [PATCH 5/6] HID: amd_sfh: Register tablet-mode auxiliary device Register an auxiliary device when the operating-mode sensor is present, so a dedicated input driver can bind to it and report the device posture. This keeps the input handling out of the sensor transport driver. Signed-off-by: Basavaraj Natikar Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/Kconfig | 1 + drivers/hid/amd-sfh-hid/amd_sfh_common.h | 2 + drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 50 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/drivers/hid/amd-sfh-hid/Kconfig b/drivers/hid/amd-sfh-hid/Kconfig index 3291786a5ee6..d86d83ffebd7 100644 --- a/drivers/hid/amd-sfh-hid/Kconfig +++ b/drivers/hid/amd-sfh-hid/Kconfig @@ -6,6 +6,7 @@ menu "AMD SFH HID Support" config AMD_SFH_HID tristate "AMD Sensor Fusion Hub" depends on X86 + select AUXILIARY_BUS help If you say yes to this option, support will be included for the AMD Sensor Fusion Hub. diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index 0ca3254151ac..f8c6b7fc34fb 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -10,6 +10,7 @@ #ifndef AMD_SFH_COMMON_H #define AMD_SFH_COMMON_H +#include #include #include #include "amd_sfh_hid.h" @@ -70,6 +71,7 @@ struct amd_mp2_dev { u8 init_done; u8 rver; u8 mp2_ver; + struct auxiliary_device *tm_auxdev; }; struct amd_mp2_ops { diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 4b1cd260410d..1405167aa369 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -8,11 +8,13 @@ * Basavaraj Natikar */ +#include #include #include #include #include #include +#include #include #include #include @@ -389,6 +391,51 @@ static const struct attribute_group *amd_sfh_groups[] = { NULL, }; +static DEFINE_IDA(sfh_tm_ida); + +static void amd_sfh_maybe_register_tm(struct amd_mp2_dev *mp2) +{ + struct auxiliary_device *adev; + bool present; + int id; + + if (mp2->tm_auxdev) + return; + + present = mp2->sfh1_1_ops ? mp2->dev_en.is_sra_present + : (mp2->mp2_ver == MP2_VER_V2 && + amd_sfh_op_idx_enabled(mp2)); + if (!present) + return; + + id = ida_alloc(&sfh_tm_ida, GFP_KERNEL); + if (id < 0) + return; + + adev = auxiliary_device_create(&mp2->pdev->dev, KBUILD_MODNAME, + "tabletmode", NULL, id); + if (!adev) { + ida_free(&sfh_tm_ida, id); + dev_warn(&mp2->pdev->dev, "tabletmode auxdev create failed\n"); + return; + } + + mp2->tm_auxdev = adev; +} + +static void amd_sfh_unregister_tm(struct amd_mp2_dev *mp2) +{ + int id; + + if (!mp2->tm_auxdev) + return; + + id = mp2->tm_auxdev->id; + auxiliary_device_destroy(mp2->tm_auxdev); + mp2->tm_auxdev = NULL; + ida_free(&sfh_tm_ida, id); +} + static void sfh1_1_init_work(struct work_struct *work) { struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work); @@ -405,6 +452,7 @@ static void sfh1_1_init_work(struct work_struct *work) if (rc) dev_warn(&mp2->pdev->dev, "failed to update sysfs group\n"); + amd_sfh_maybe_register_tm(mp2); } static void sfh_init_work(struct work_struct *work) @@ -424,6 +472,7 @@ static void sfh_init_work(struct work_struct *work) sfh_set_emp2(mp2); amd_sfh_clear_intr(mp2); mp2->init_done = 1; + amd_sfh_maybe_register_tm(mp2); } static void amd_sfh_remove(struct pci_dev *pdev) @@ -431,6 +480,7 @@ static void amd_sfh_remove(struct pci_dev *pdev) struct amd_mp2_dev *mp2 = pci_get_drvdata(pdev); flush_work(&mp2->work); + amd_sfh_unregister_tm(mp2); if (mp2->init_done) mp2->mp2_ops->remove(mp2); } From b472474e28bd3539e02581a9c3c28906061c9ec2 Mon Sep 17 00:00:00 2001 From: Basavaraj Natikar Date: Mon, 3 Aug 2026 23:22:30 +0530 Subject: [PATCH 6/6] Input: misc: Add AMD SFH tablet-mode switch driver Report whether an AMD convertible is in laptop or tablet mode using the operating-mode sensor provided by the Sensor Fusion Hub, and expose it to userspace as SW_TABLET_MODE, so userspace can react to the device being folded into tablet posture. Cc: Helge Bahmann Signed-off-by: Basavaraj Natikar Acked-by: Dmitry Torokhov Signed-off-by: Jiri Kosina --- MAINTAINERS | 1 + drivers/input/misc/Kconfig | 15 +++++ drivers/input/misc/Makefile | 1 + drivers/input/misc/amd_sfh_tabletmode.c | 81 +++++++++++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 drivers/input/misc/amd_sfh_tabletmode.c diff --git a/MAINTAINERS b/MAINTAINERS index f37a81950e25..19046f224401 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1301,6 +1301,7 @@ L: linux-input@vger.kernel.org S: Maintained F: Documentation/hid/amd-sfh* F: drivers/hid/amd-sfh-hid/ +F: drivers/input/misc/amd_sfh_tabletmode.c AMD SPI DRIVER M: Raju Rangoju diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 1f6c57dba030..5b76be31f687 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -899,6 +899,21 @@ config INPUT_SOC_BUTTON_ARRAY To compile this driver as a module, choose M here: the module will be called soc_button_array. +config INPUT_AMD_SFH_TABLETMODE + tristate "AMD SFH tablet-mode switch" + depends on AMD_SFH_HID + select AUXILIARY_BUS + help + Expose SW_TABLET_MODE for AMD convertible laptops whose + operation-mode sensor is provided by the AMD Sensor Fusion Hub. + + Userspace components such as systemd-logind and modern desktop + environments react to SW_TABLET_MODE when the device is folded + into tablet posture. + + To compile this driver as a module, choose M here: the module + will be called amd_sfh_tabletmode. + config INPUT_DRV260X_HAPTICS tristate "TI DRV260X haptics support" depends on INPUT && I2C diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 2281d6803fce..6fab9acf3de3 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_INPUT_AD714X_SPI) += ad714x-spi.o obj-$(CONFIG_INPUT_ADXL34X) += adxl34x.o obj-$(CONFIG_INPUT_ADXL34X_I2C) += adxl34x-i2c.o obj-$(CONFIG_INPUT_ADXL34X_SPI) += adxl34x-spi.o +obj-$(CONFIG_INPUT_AMD_SFH_TABLETMODE) += amd_sfh_tabletmode.o obj-$(CONFIG_INPUT_APANEL) += apanel.o obj-$(CONFIG_INPUT_ARIEL_PWRBUTTON) += ariel-pwrbutton.o obj-$(CONFIG_INPUT_ARIZONA_HAPTICS) += arizona-haptics.o diff --git a/drivers/input/misc/amd_sfh_tabletmode.c b/drivers/input/misc/amd_sfh_tabletmode.c new file mode 100644 index 000000000000..3931a1c6eb74 --- /dev/null +++ b/drivers/input/misc/amd_sfh_tabletmode.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD SFH tablet-mode switch driver + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Author: Basavaraj Natikar + */ + +#include +#include +#include +#include +#include + +#define POLL_INTERVAL_MS 200 + +static void sfh_tm_poll(struct input_dev *input) +{ + struct amd_sfh_info info = {}; + + if (amd_get_sfh_info(&info, MT_OP_MODE)) + return; + + input_report_switch(input, SW_TABLET_MODE, + info.op_mode == SFH_MODE_TABLET); + input_sync(input); +} + +static int sfh_tm_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &auxdev->dev; + struct amd_sfh_info info = {}; + struct input_dev *input; + int error; + + error = amd_get_sfh_info(&info, MT_OP_MODE); + if (error) + return error == -EINVAL ? -ENODEV : error; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + + input->name = "AMD SFH tablet mode switch"; + input->phys = "amd-sfh/tabletmode"; + input->id.bustype = BUS_HOST; + input->id.vendor = PCI_VENDOR_ID_AMD; + input_set_capability(input, EV_SW, SW_TABLET_MODE); + + error = input_setup_polling(input, sfh_tm_poll); + if (error) + return error; + input_set_poll_interval(input, POLL_INTERVAL_MS); + + sfh_tm_poll(input); + + error = input_register_device(input); + if (error) + return error; + + return 0; +} + +static const struct auxiliary_device_id sfh_tm_id_table[] = { + { .name = "amd_sfh.tabletmode" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, sfh_tm_id_table); + +static struct auxiliary_driver sfh_tm_driver = { + .name = "tabletmode", + .id_table = sfh_tm_id_table, + .probe = sfh_tm_probe, +}; +module_auxiliary_driver(sfh_tm_driver); + +MODULE_DESCRIPTION("AMD SFH tablet mode switch"); +MODULE_LICENSE("GPL");