]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
HID: amd_sfh: Allow configuring whether HPD is enabled or disabled
authorMario Limonciello <mario.limonciello@amd.com>
Fri, 28 Feb 2025 16:31:51 +0000 (10:31 -0600)
committerJiri Kosina <jkosina@suse.com>
Tue, 4 Mar 2025 20:49:32 +0000 (21:49 +0100)
Human presence detection (HPD) sensor uses a camera to determine when
user is physically in front of the machine.  This might not be a
desirable behavior because it can (for example) cause the machine to
wake on approach.

Add a new sysfs file "hpd" that will control whether this sensor is
enabled. Use the value of this sysfs file to turn off HPD and prevent
it from re-enabling after resume from suspend.

Cc: Pratap Nirujogi <pratap.nirujogi@amd.com>
Tested-by: Anson Tsao <anson.tsao@amd.com>
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Documentation/ABI/testing/sysfs-driver-amd-sfh [new file with mode: 0644]
drivers/hid/amd-sfh-hid/amd_sfh_common.h
drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c
drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.h

diff --git a/Documentation/ABI/testing/sysfs-driver-amd-sfh b/Documentation/ABI/testing/sysfs-driver-amd-sfh
new file mode 100644 (file)
index 0000000..c053126
--- /dev/null
@@ -0,0 +1,13 @@
+What:          /sys/bus/pci/drivers/pcie_mp2_amd/*/hpd
+Date:          April 2025
+Contact:       mario.limonciello@amd.com
+Description:
+               Human presence detection (HPD) enable/disable.
+               When HPD is enabled, the device will be able to detect the
+               presence of a human and will send an interrupt that can be
+               used to wake the system from a low power state.
+               When HPD is disabled, the device will not be able to detect
+               the presence of a human.
+
+               Access: Read/Write
+               Valid values: enabled/disabled
index 799b8686a88a46bbda2cf6f65fc6c3721d4c9122..f44a3bb2fbd4fe56cb5932773965a90b3876e71f 100644 (file)
@@ -42,6 +42,7 @@ struct amd_mp2_sensor_info {
 
 struct sfh_dev_status {
        bool is_hpd_present;
+       bool is_hpd_enabled;
        bool is_als_present;
        bool is_sra_present;
 };
index 48cfd0c5824149dd93cf35fa671262dc46a08240..1c1fd63330c939310424d2d65cda4b59a27f69dc 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/iopoll.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/string_choices.h>
 
 #include "amd_sfh_pcie.h"
 #include "sfh1_1/amd_sfh_init.h"
@@ -330,6 +331,57 @@ static const struct dmi_system_id dmi_nodevs[] = {
        { }
 };
 
+static ssize_t hpd_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
+
+       return sysfs_emit(buf, "%s\n", str_enabled_disabled(mp2->dev_en.is_hpd_enabled));
+}
+
+static ssize_t hpd_store(struct device *dev,
+                        struct device_attribute *attr,
+                        const char *buf, size_t count)
+{
+       struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
+       bool enabled;
+       int ret;
+
+       ret = kstrtobool(buf, &enabled);
+       if (ret)
+               return ret;
+
+       mp2->sfh1_1_ops->toggle_hpd(mp2, enabled);
+
+       return count;
+}
+static DEVICE_ATTR_RW(hpd);
+
+static umode_t sfh_attr_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+       struct device *dev = kobj_to_dev(kobj);
+       struct amd_mp2_dev *mp2 = dev_get_drvdata(dev);
+
+       if (!mp2->sfh1_1_ops || !mp2->dev_en.is_hpd_present)
+               return 0;
+
+       return attr->mode;
+}
+
+static struct attribute *sfh_attrs[] = {
+       &dev_attr_hpd.attr,
+       NULL,
+};
+
+static struct attribute_group sfh_attr_group = {
+       .attrs = sfh_attrs,
+       .is_visible = sfh_attr_is_visible,
+};
+
+static const struct attribute_group *amd_sfh_groups[] = {
+       &sfh_attr_group,
+       NULL,
+};
+
 static void sfh1_1_init_work(struct work_struct *work)
 {
        struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work);
@@ -341,6 +393,11 @@ static void sfh1_1_init_work(struct work_struct *work)
 
        amd_sfh_clear_intr(mp2);
        mp2->init_done = 1;
+
+       rc = sysfs_update_group(&mp2->pdev->dev.kobj, &sfh_attr_group);
+       if (rc)
+               dev_warn(&mp2->pdev->dev, "failed to update sysfs group\n");
+
 }
 
 static void sfh_init_work(struct work_struct *work)
@@ -487,6 +544,7 @@ static struct pci_driver amd_mp2_pci_driver = {
        .driver.pm      = &amd_mp2_pm_ops,
        .shutdown       = amd_sfh_shutdown,
        .remove         = amd_sfh_remove,
+       .dev_groups     = amd_sfh_groups,
 };
 module_pci_driver(amd_mp2_pci_driver);
 
index e9929c4aa72eb4e634da5885e2b7ebb950423eb8..fc9c297d0db7f083c3f0067d0b7158044044c13f 100644 (file)
@@ -212,6 +212,7 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
                        switch (cl_data->sensor_idx[i]) {
                        case HPD_IDX:
                                privdata->dev_en.is_hpd_present = true;
+                               privdata->dev_en.is_hpd_enabled = true;
                                break;
                        case ALS_IDX:
                                privdata->dev_en.is_als_present = true;
@@ -255,6 +256,10 @@ static void amd_sfh_resume(struct amd_mp2_dev *mp2)
        }
 
        for (i = 0; i < cl_data->num_hid_devices; i++) {
+               /* leave HPD alone; policy is controlled by sysfs */
+               if (cl_data->sensor_idx[i] == HPD_IDX)
+                       continue;
+
                if (cl_data->sensor_sts[i] == SENSOR_DISABLED) {
                        info.sensor_idx = cl_data->sensor_idx[i];
                        mp2->mp2_ops->start(mp2, info);
@@ -285,8 +290,10 @@ static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
        }
 
        for (i = 0; i < cl_data->num_hid_devices; i++) {
-               if (cl_data->sensor_idx[i] != HPD_IDX &&
-                   cl_data->sensor_sts[i] == SENSOR_ENABLED) {
+               /* leave HPD alone; policy is controlled by sysfs */
+               if (cl_data->sensor_idx[i] == HPD_IDX)
+                       continue;
+               if (cl_data->sensor_sts[i] == SENSOR_ENABLED) {
                        mp2->mp2_ops->stop(mp2, cl_data->sensor_idx[i]);
                        status = amd_sfh_wait_for_response
                                        (mp2, cl_data->sensor_idx[i], DISABLE_SENSOR);
@@ -304,6 +311,44 @@ static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
        amd_sfh_clear_intr(mp2);
 }
 
+void amd_sfh_toggle_hpd(struct amd_mp2_dev *mp2, bool enabled)
+{
+       struct amdtp_cl_data *cl_data = mp2->cl_data;
+       struct amd_mp2_sensor_info info;
+       int i, status;
+
+       if (mp2->dev_en.is_hpd_enabled == enabled)
+               return;
+
+       for (i = 0; i < cl_data->num_hid_devices; i++) {
+               if (cl_data->sensor_idx[i] != HPD_IDX)
+                       continue;
+               info.sensor_idx = cl_data->sensor_idx[i];
+               if (enabled) {
+                       mp2->mp2_ops->start(mp2, info);
+                       status = amd_sfh_wait_for_response
+                                       (mp2, cl_data->sensor_idx[i], ENABLE_SENSOR);
+                       if (status == 0)
+                               status = SENSOR_ENABLED;
+                       if (status == SENSOR_ENABLED)
+                               cl_data->sensor_sts[i] = SENSOR_ENABLED;
+               } else {
+                       mp2->mp2_ops->stop(mp2, cl_data->sensor_idx[i]);
+                       status = amd_sfh_wait_for_response
+                                       (mp2, cl_data->sensor_idx[i], DISABLE_SENSOR);
+                       if (status == 0)
+                               status = SENSOR_DISABLED;
+                       if (status != SENSOR_ENABLED)
+                               cl_data->sensor_sts[i] = SENSOR_DISABLED;
+               }
+               dev_dbg(&mp2->pdev->dev, "toggle sid 0x%x (%s) status 0x%x\n",
+                       cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
+                       cl_data->sensor_sts[i]);
+               break;
+       }
+       mp2->dev_en.is_hpd_enabled = enabled;
+}
+
 static void amd_mp2_pci_remove(void *privdata)
 {
        struct amd_mp2_dev *mp2 = privdata;
index 21c44990bbebad26ec8b26ae25f7fcd37da513d9..797d206641c69d15bb6eb193530b0be3ae08f979 100644 (file)
 
 struct amd_sfh1_1_ops {
        int (*init)(struct amd_mp2_dev *mp2);
+       void (*toggle_hpd)(struct amd_mp2_dev *mp2, bool enable);
 };
 
 int amd_sfh1_1_init(struct amd_mp2_dev *mp2);
+void amd_sfh_toggle_hpd(struct amd_mp2_dev *mp2, bool enabled);
 
 static const struct amd_sfh1_1_ops __maybe_unused sfh1_1_ops = {
        .init = amd_sfh1_1_init,
+       .toggle_hpd = amd_sfh_toggle_hpd,
 };
 
 #endif