Merge branch 'for-7.4/amd_sfh' into for-linus

- support for newer AMD SoCs (Basavaraj Natikar)
- sysfs attributes of operating-mode telemetry (Basavaraj Natikar)
This commit is contained in:
Jiri Kosina 2026-10-21 21:57:39 +02:00
commit 60ccbc2343
13 changed files with 282 additions and 70 deletions

View File

@ -11,3 +11,63 @@ Description:
Access: Read/Write
Valid values: enabled/disabled
What: /sys/bus/pci/drivers/pcie_mp2_amd/*/in_bag_state
Date: August 2026
Contact: Basavaraj.Natikar@amd.com
Description:
Operating-mode telemetry reported by the SRA sensor that
indicates whether the device is detected inside a bag.
Access: Read only
Valid values: unsigned integer
What: /sys/bus/pci/drivers/pcie_mp2_amd/*/out_bag_state
Date: August 2026
Contact: Basavaraj.Natikar@amd.com
Description:
Operating-mode telemetry reported by the SRA sensor that
indicates whether the device is detected outside a bag.
Access: Read only
Valid values: unsigned integer
What: /sys/bus/pci/drivers/pcie_mp2_amd/*/on_table_state
Date: August 2026
Contact: Basavaraj.Natikar@amd.com
Description:
Operating-mode telemetry reported by the SRA sensor that
indicates whether the device is placed on a table or on a lap.
Access: Read only
Valid values: unsigned integer
What: /sys/bus/pci/drivers/pcie_mp2_amd/*/hinge_angle
Date: August 2026
Contact: Basavaraj.Natikar@amd.com
Description:
Operating-mode telemetry reported by the SRA sensor that
reports the hinge angle of the device in degrees.
Access: Read only
Valid values: unsigned integer (degrees)
What: /sys/bus/pci/drivers/pcie_mp2_amd/*/lid_status
Date: August 2026
Contact: Basavaraj.Natikar@amd.com
Description:
Operating-mode telemetry reported by the SRA sensor that
indicates whether the device lid is open or closed.
Access: Read only
Valid values: unsigned integer
What: /sys/bus/pci/drivers/pcie_mp2_amd/*/device_mode
Date: August 2026
Contact: Basavaraj.Natikar@amd.com
Description:
Operating-mode telemetry reported by the SRA sensor that
reports the platform/device mode.
Access: Read only
Valid values: unsigned integer

View File

@ -1322,6 +1322,7 @@ AMD SENSOR FUSION HUB DRIVER
M: Basavaraj Natikar <basavaraj.natikar@amd.com>
L: linux-input@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-driver-amd-sfh
F: Documentation/hid/amd-sfh*
F: drivers/hid/amd-sfh-hid/
F: drivers/input/misc/amd_sfh_tabletmode.c

View File

@ -8,6 +8,7 @@ obj-$(CONFIG_AMD_SFH_HID) += amd_sfh.o
amd_sfh-objs := amd_sfh_hid.o
amd_sfh-objs += amd_sfh_client.o
amd_sfh-objs += amd_sfh_pcie.o
amd_sfh-objs += amd_sfh_sysfs.o
amd_sfh-objs += hid_descriptor/amd_sfh_hid_desc.o
amd_sfh-objs += sfh1_1/amd_sfh_init.o
amd_sfh-objs += sfh1_1/amd_sfh_interface.o

View File

@ -95,7 +95,7 @@ void amd_sfh_work(struct work_struct *work)
cli_data->feature_report[current_index]);
if (report_size)
hid_input_report(cli_data->hid_sensor_hubs[current_index],
cli_data->report_type[current_index],
node_type,
cli_data->feature_report[current_index], report_size, 0);
else
pr_err("AMDSFH: Invalid report size\n");
@ -104,7 +104,7 @@ void amd_sfh_work(struct work_struct *work)
report_size = mp2_ops->get_in_rep(current_index, sensor_index, report_id, in_data);
if (report_size)
hid_input_report(cli_data->hid_sensor_hubs[current_index],
cli_data->report_type[current_index],
node_type,
in_data->input_report[current_index], report_size, 0);
else
pr_err("AMDSFH: Invalid report size\n");

View File

@ -18,6 +18,7 @@
#define PCI_DEVICE_ID_AMD_MP2 0x15E4
#define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A
#define PCI_DEVICE_ID_AMD_MP2_1_2 0x1152
/* The BAR 2 size must cover the highest register offset (0x10958) */
#define AMD_SFH_MIN_BAR_SIZE SZ_128K
@ -43,6 +44,7 @@ enum cmd_id {
enum amd_mp2_version {
MP2_VER_V2 = 1,
MP2_VER_1_1 = 2,
MP2_VER_1_2 = 3,
};
struct amd_mp2_sensor_info {

View File

@ -20,9 +20,9 @@
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string_choices.h>
#include "amd_sfh_pcie.h"
#include "amd_sfh_sysfs.h"
#include "sfh1_1/amd_sfh_init.h"
#define DRIVER_NAME "pcie_mp2_amd"
@ -41,6 +41,8 @@ module_param_named(sensor_mask, sensor_mask_override, int, 0444);
MODULE_PARM_DESC(sensor_mask, "override the detected sensors mask");
static bool intr_disable = true;
module_param_named(intr_disable, intr_disable, bool, 0444);
MODULE_PARM_DESC(intr_disable, "override the interrupt disable sensor bit");
static int amd_sfh_wait_response_v2(struct amd_mp2_dev *mp2, u8 sid, u32 sensor_sts)
{
@ -313,6 +315,13 @@ static const struct dmi_system_id dmi_sfh_table[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook x360 435 G7"),
},
},
{
.callback = mp2_disable_intr,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "VivoBook_ASUSLaptop TP420UA_TM420UA"),
},
},
{}
};
@ -331,57 +340,6 @@ 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 DEFINE_IDA(sfh_tm_ida);
static void amd_sfh_maybe_register_tm(struct amd_mp2_dev *mp2)
@ -439,7 +397,7 @@ 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);
rc = amd_sfh_update_sysfs(mp2);
if (rc)
dev_warn(&mp2->pdev->dev, "failed to update sysfs group\n");
@ -598,6 +556,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_DEVICE_DATA(AMD, MP2_1_1, MP2_VER_1_1) },
{ PCI_DEVICE_DATA(AMD, MP2_1_2, MP2_VER_1_2) },
{ }
};
MODULE_DEVICE_TABLE(pci, amd_mp2_pci_tbl);

View File

@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD SFH sysfs attributes
*
* Copyright (c) 2026, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#include <linux/device.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/string_choices.h>
#include <linux/sysfs.h>
#include "amd_sfh_common.h"
#include "amd_sfh_sysfs.h"
#include "sfh1_1/amd_sfh_init.h"
#include "sfh1_1/amd_sfh_interface.h"
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);
/* Operating-mode telemetry reported by the SRA sensor, exposed read-only. */
static int amd_sfh_read_op_mode(struct amd_mp2_dev *mp2, struct sfh_op_mode *mode)
{
struct sfh_accel_data accel_data;
void __iomem *sensoraddr;
if (!mp2->sfh1_1_ops || !mp2->dev_en.is_sra_present)
return -ENODEV;
if (mp2->mp2_ver >= MP2_VER_1_2) {
sensoraddr = mp2->vsbase +
(SRA_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&accel_data, sensoraddr, sizeof(struct sfh_accel_data));
mode->val = accel_data.c2p3_data;
} else {
mode->val = readl(mp2->mmio + amd_get_c2p_val(mp2, 3));
}
return 0;
}
#define SFH_OP_MODE_ATTR(_name, _field) \
static ssize_t _name##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct amd_mp2_dev *mp2 = dev_get_drvdata(dev); \
struct sfh_op_mode mode; \
int ret; \
\
ret = amd_sfh_read_op_mode(mp2, &mode); \
if (ret) \
return ret; \
\
return sysfs_emit(buf, "%u\n", mode.op_mode._field); \
} \
static DEVICE_ATTR_RO(_name)
SFH_OP_MODE_ATTR(in_bag_state, inbagstate);
SFH_OP_MODE_ATTR(out_bag_state, outbagstate);
SFH_OP_MODE_ATTR(on_table_state, ontablestate);
SFH_OP_MODE_ATTR(hinge_angle, angle);
SFH_OP_MODE_ATTR(lid_status, lidstatus);
SFH_OP_MODE_ATTR(device_mode, devicemode);
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)
return 0;
if (attr == &dev_attr_hpd.attr) {
if (!mp2->dev_en.is_hpd_present)
return 0;
} else if (!mp2->dev_en.is_sra_present) {
/* operating-mode telemetry requires the SRA sensor */
return 0;
}
return attr->mode;
}
static struct attribute *sfh_attrs[] = {
&dev_attr_hpd.attr,
&dev_attr_in_bag_state.attr,
&dev_attr_out_bag_state.attr,
&dev_attr_on_table_state.attr,
&dev_attr_hinge_angle.attr,
&dev_attr_lid_status.attr,
&dev_attr_device_mode.attr,
NULL,
};
static struct attribute_group sfh_attr_group = {
.attrs = sfh_attrs,
.is_visible = sfh_attr_is_visible,
};
const struct attribute_group *amd_sfh_groups[] = {
&sfh_attr_group,
NULL,
};
int amd_sfh_update_sysfs(struct amd_mp2_dev *mp2)
{
return sysfs_update_group(&mp2->pdev->dev.kobj, &sfh_attr_group);
}

View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* AMD SFH sysfs attributes
*
* Copyright (c) 2026, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
*/
#ifndef AMD_SFH_SYSFS_H
#define AMD_SFH_SYSFS_H
#include "amd_sfh_common.h"
extern const struct attribute_group *amd_sfh_groups[];
int amd_sfh_update_sysfs(struct amd_mp2_dev *mp2);
#endif

View File

@ -48,8 +48,6 @@ struct accel3_input_report {
int in_accel_x_value;
int in_accel_y_value;
int in_accel_z_value;
/* include if required to support the "shake" event */
u8 in_accel_shake_detection;
} __packed;
struct gyro_feature_report {

View File

@ -169,13 +169,6 @@ static const u8 accel3_report_descriptor[] = {
0x95, 1, /* HID report count (1) */
0x55, 0x0E, /* HID unit exponent(0x0E) */
0X81, 0x02, /* HID Input (Data_Arr_Abs) */
0x0A, 0x51, 0x04, /* HID usage sensor data motion state */
0x15, 0, /* HID logical Min_8(0) False = Still*/
0x25, 1, /* HID logical Min_8(1) True = In motion */
0x75, 8, /* HID report size(8) */
0x95, 1, /* HID report count (1) */
0X81, 0x02, /* HID Input (Data_Arr_Abs) */
0xC0 /* HID end collection */
};

View File

@ -187,6 +187,7 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
struct sfh_gyro_data gyro_data;
struct sfh_mag_data mag_data;
struct sfh_als_data als_data;
struct sfh_hpd_data hpd_data;
struct hpd_status hpdstatus;
struct sfh_base_info binfo;
void __iomem *sensoraddr;
@ -251,8 +252,16 @@ static u8 get_input_rep(u8 current_index, int sensor_idx, int report_id,
break;
case HPD_IDX:
get_common_inputs(&hpd_input.common_property, report_id);
hpdstatus.val = readl(mp2->mmio + amd_get_c2p_val(mp2, 4));
hpd_input.human_presence = hpdstatus.shpd.presence;
if (mp2->mp2_ver >= MP2_VER_1_2) {
sensoraddr = mp2->vsbase +
(HPD_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&hpd_data, sensoraddr, sizeof(struct sfh_hpd_data));
hpd_input.human_presence = hpd_data.status.shpd.presence;
} else {
hpdstatus.val = readl(mp2->mmio + amd_get_c2p_val(mp2, 4));
hpd_input.human_presence = hpdstatus.shpd.presence;
}
report_size = sizeof(hpd_input);
memcpy(input_report, &hpd_input, sizeof(hpd_input));
break;

View File

@ -98,6 +98,25 @@ void sfh_interface_init(struct amd_mp2_dev *mp2)
emp2 = mp2;
}
static unsigned int amd_sfh_read_c2p3(struct amd_mp2_dev *mp2)
{
struct sfh_accel_data accel_data;
void __iomem *sensoraddr;
u32 val;
if (mp2->mp2_ver >= MP2_VER_1_2) {
sensoraddr = mp2->vsbase +
(SRA_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&accel_data, sensoraddr, sizeof(struct sfh_accel_data));
val = accel_data.c2p3_data;
} else {
val = readl(mp2->mmio + amd_get_c2p_val(mp2, 3));
}
return val;
}
static int amd_sfh_op_mode_info(u32 *op_mode)
{
struct sfh_op_mode mode;
@ -114,7 +133,7 @@ static int amd_sfh_op_mode_info(u32 *op_mode)
if (!present)
return -ENODEV;
mode.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 3));
mode.val = amd_sfh_read_c2p3(emp2);
dev_dbg(&emp2->pdev->dev, "op-mode: %s (mode=%u)\n",
mode.op_mode.mode == SFH_MODE_TABLET ? "tablet" : "laptop",
mode.op_mode.mode);
@ -133,7 +152,7 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement)
if (!emp2 || !emp2->dev_en.is_sra_present)
return -ENODEV;
mode.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 3));
mode.val = amd_sfh_read_c2p3(emp2);
*platform_type = mode.op_mode.devicemode;
@ -160,7 +179,9 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement)
static int amd_sfh_hpd_info(u8 *user_present)
{
struct sfh_hpd_data hpd_data;
struct hpd_status hpdstatus;
void __iomem *sensoraddr;
if (!user_present)
return -EINVAL;
@ -168,8 +189,16 @@ static int amd_sfh_hpd_info(u8 *user_present)
if (!emp2 || !emp2->dev_en.is_hpd_present || !emp2->dev_en.is_hpd_enabled)
return -ENODEV;
hpdstatus.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 4));
*user_present = hpdstatus.shpd.presence;
if (emp2->mp2_ver >= MP2_VER_1_2) {
sensoraddr = emp2->vsbase +
(HPD_IDX * SENSOR_DATA_MEM_SIZE_DEFAULT) +
OFFSET_SENSOR_DATA_DEFAULT;
memcpy_fromio(&hpd_data, sensoraddr, sizeof(struct sfh_hpd_data));
*user_present = hpd_data.status.shpd.presence;
} else {
hpdstatus.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 4));
*user_present = hpdstatus.shpd.presence;
}
return 0;
}

View File

@ -130,6 +130,7 @@ struct sfh_accel_data {
struct sfh_common_data commondata;
struct sfh_float32 acceldata;
u32 accelstatus;
u32 c2p3_data;
};
struct sfh_gyro_data {
@ -165,6 +166,12 @@ struct hpd_status {
};
};
struct sfh_hpd_data {
struct sfh_common_data commondata;
u32 reserved;
struct hpd_status status;
};
struct sfh_op_mode {
union {
u32 val;