mirror of
https://github.com/torvalds/linux.git
synced 2026-09-13 15:40:03 +02:00
platform-drivers-x86 for v7.3-1
Highlights:
- amd/halo: Add Halo RGB LED driver
- amd/hsmp:
- Properly serialize probe, remove, and data paths
- Add support for protocol v7 used by Family 1AH Model 80H
- Fix error checking corner cases (largely from AI review)
- Reject negative power cap
- amd/pmc:
- Improve behavior on platforms that do not support STB
- Add T14 Gen2 AMD (20XL) to s2idle quirk list
- amd/pmf:
- Add ioctl interface to retrieve device metrics
- Add support for new metrics tables used by Family 1AH Model 80H
- arm64: qcom-hamoa-ec: Reject short responses
- asus-nb-wmi: Support ProArt key on ASUS ProArt PX13
- asus-armoury:
- Gate PPT writes behind active fan curve
- Add power limits for more models
- dell-wmi-base: Fix handling of ultra performance key
- dell-wmi-sysman: Don't hex dump attribute security buffer
- hp-bioscfg:
- Various fixes
- Improve reduced ACPI packages support (necessary for HP EliteBook 840 G2)
- lg-laptop:
- Fix LED resource handling
- Add support for events used in newer models
- Fix keyboard backlight support on LG Gram 16T90SP
- hp-wmi:
- Generalize thermal params to board params
- Manage CPU and GPU PWM independently
- Add GPU MUX switch support
- Add Victus 15-fb0xxx support
- Add OMEN MAX 16-ak0xxx, OMEN 16-n0xxx, OMEN 16-wd0xxx, OMEN
16-wf0xxx, and OMEN board ID 8D88 support
- Add OMEN Transcend 16-u0xxx support
- huawei: Add support for Fn-lock ACPI interface found on newer Huawei
laptops such as MateBook 14 2024
- ISST:
- Improve input validation (many fixes)
- Disallow SST-CP (core-power) feature if perf profile add fails
- lenovo/yb9-kbdock: Add driver for Yoga Book 9 14IAH10
- lenovo/ymc:
- Extend hinge switch query to support Yoga 9 2-in-1 14IPH11
- Prevent loading on Yoga Book 9 14IAH10 to avoid duplicated input
nodes
- msi-ec: Add MSI Raider A18 HX A9WJG and MSI Katana GF76 11UEK support
- msi-wmi: Add MSI Claw M-Center keys support
- oxpec: Add support for OneXPlayer X2 Mini Pro
- redmi-wmi: Report kbd backlight cycle, OEM preset power mode, and FnLock
toggle events to userspace
- samsung-galaxybook: Add Samsung Galaxy Book6 Pro support
- thinkpad_acpi: Add USB-C Security support
- uniwill-laptop:
- Add keyboard backlight, AC auto boot, and USB powershare support
- Add MACHENIKE L16 Pro, AiStone X4SP4NAL, and Avell A60 MUV support
- Make lightbar max brightness configurable and add support for
LAPQC71A/B
- Major refactoring efforts:
- Stop setting acpi_device_name/class() and pnp.device_class to
faciliate their eventual removal
- Many rollback/remove path fixes (presumably mostly found by AI)
- Miscellaneous cleanups / refactoring / improvements
The following is an automated shortlog grouped by driver:
acer-wmi:
- reject missing gaming WMI results
amd/hsmp:
- ACPI HSMP refcounted sockets and coordinated release
- Add HSMP messages for Family 1Ah, Model 50h-5Fh
- Add IOCTL_GET_TELEMETRY_DATA for metric table reads
- Clear mdev.this_device on deregister
- Enable protocol version 7 metric tables on the ACPI driver
- Gate the data plane on a fully initialized socket
- Map the metric table with ioremap() and unmap it explicitly
- Pass struct device explicitly to ACPI mailbox parsers
- Reject negative power cap writes in hwmon
- Serialize ACPI HSMP probe and remove with an rwsem
- Serialize per-socket metric table reads with a mutex
- Serialize the data plane against socket teardown
- Source metric-table size from firmware
- Unify response_sz validation to an upper-bound check
- Validate ACPI UID before parsing socket index
- Validate _DSD mailbox sub-package element count
amd:
- Introduce Halo Box RGB LED driver
amd/pmc:
- Add T14 Gen2 AMD (20XL) to s2idle quirk list
- Do not fail probe when STB init fails
- Fix LPS0 and debugfs leaks when STB init fails
- Fix msg_port restoration in amd_stb_debugfs_open_v2()
- Only expose stb_read after telemetry buffer is mapped
- Propagate SMU errors and validate S2D address
- Restore msg_port on amd_stb_s2d_init() error paths
amd/pmf:
- Add 1AH_M80H device IDs and extended SMU mailbox registers
- Add 1AH_M80H metrics table and NPU metrics support
- Add missing newline in dev_err message
- Add util layer and userspace character device interface
- Implement util layer ioctl handler
- Introduce AMD PMF testing tool for driver metrics and features
- Move debug helper functions to UAPI header
- Move metrics code to dedicated file
- Refactor NPU metrics for platform extensibility
- store BIOS output values for user-space metrics via util IOCTL
- Store commonly used enums in the header file
- Use per-SoC smu_regs struct for SMU mailbox registers
- Use upper/lower_32_bits() in amd_pmf_set_dram_addr()
arm64: qcom-hamoa-ec:
- reject incomplete responses
asus-armoury:
- Add power limits for ROG Strix SCAR 16 (G635LX)
- Add power limits quirk for FA401KM
- Add power limits quirk for FA608WV
- add support for FX517ZR
- add support for HN7306EA and HN7306EAC
- fix Use-After-Free and memory leak in driver init
- gate PPT writes behind active fan curve
- use cleanup.h to manage tunables
asus-laptop:
- Stop setting acpi_device_name/class()
asus-nb-wmi:
- map ProArt key (0x8b) to KEY_PROG3
asus-wireless:
- Fail probe when there is no ACPI match
asus-wmi:
- fix resource leaks on probe failure
dell-ddv:
- Use no_free_ptr() to simplify error handling
dell-privacy:
- Fix race condition
dell-smbios:
- Pass device to callbacks
dell-smbios-wmi:
- Fix chardev resource management
- Replace global list with single item
dell-wmi-base:
- Fix handling of ultra performance key
- Fix resource leak on module load failure
dell-wmi-sysman:
- Don't hex dump attribute security buffer
- Fix instance ID bounds
Documentation/ABI:
- add testing entry for AMD PMF character device interface
eeepc-laptop:
- Stop setting acpi_device_name/class()
fujitsu-laptop:
- Stop setting acpi_device_name/class()
fujitsu-tablet:
- Stop setting acpi_device_name/class()
hp-bioscfg:
- accept reduced ACPI packages from older HP BIOS
- advance elem past consumed array elements
- bound ordered-list parsing by the package count
- fix heap OOB read in sk_store() and kek_store()
- fix heap OOB read on empty password write
- fix new_password_store() overwriting current_password
- fix off-by-one write in hp_get_string_from_buffer()
- fix ORD_LIST_ELEMENTS never being parsed
- fix password encoding bounds check
- pass validated element count to package parsers
- warn on element type mismatch instead of failing
hp-wmi:
- Add dual-channel PWM fan control
- Add GPU MUX switch support
- Add OMEN board 8A43 thermal profile support
- Add OMEN board 8BA9 thermal profile support
- Add OMEN board 8BAA thermal profile support
- Add OMEN board 8D88 thermal profile support
- Add OMEN Transcend 16 8BB3 support
- Add support for OMEN MAX 16-ak0xxx (8DD6)
- Add Victus 15-fb0xxx support
- Drive fan control from board data
- Introduce board-specific feature data
huawei-wmi:
- add ACPI fallback for Fn-lock on newer models
ideapad-laptop:
- Fix driver unregistration order
int1092:
- Fix info leak in parse_package()
- Fix potential memory leak in sar_probe()
intel/pmc:
- initialize empty PMT read result
ishtp_eclite:
- Fix ACPI device reference leak in probe error path
ISST:
- Add a NULL check for sst_inst[]
- Just allow 2 bits for SST feature enable
- Return error during profile addition
- Use PP level enable mask
- Validate level in perf mask ioctls
- Validate logical CPU id and clos id
- Validate max level for set feature
- Validate parameter for core power state
- Validate parameter for frequency and priority
- Validate socket ID in clos_assoc ioctl
lenovo:
- Add Yoga Book 9 keyboard dock detection driver
lenovo: lenovo-ymc:
- Suppress probe on Yoga Book 9 14IAH10
lenovo/ymc:
- Only match lower byte in WMI lid switch query response
lg-laptop:
- Add support for additional events
- Add support for native ACPI events
- Fix keyboard backlight support on LG Gram 16T90SP
- Fix LED resource handling
- Improve WMAB control method support
MAINTAINERS:
- update Intel PMC Core maintainer contact
mlxbf-bootctl:
- fix the build error with FIELD_PREP()
mlxbf-pmc:
- Check ACPI_COMPANION() against NULL
msi-ec:
- Add MSI Katana GF76 11UEK EC firmware
- Add MSI Raider A18 HX A9WJG EC firmware
msi-wmi:
- Add MSI Claw M-Center keys
- Reformat msi_wmi_notify()
oxpec:
- Add support for OneXPlayer X2 Mini Pro
panasonic-laptop:
- Fix sentinel write past pcc->sinf[]
- Stop setting acpi_device_name/class()
power: supply: surface_{battery,charger}:
- Consistently define ssam_device_ids using named initializers
redmi-wmi:
- report EC state change events
samsung-galaxybook:
- Add SAMB430 device ID
sony-laptop:
- Stop setting acpi_device_class()
sonypi:
- Stop setting acpi_device_name/class()
surface: acpi-notify:
- Check ACPI companion before use
surface: aggregator:
- Consistently define ssam_device_ids using named initializers
surface: surfacepro3_button:
- Stop setting acpi_device_name()
think-lmi:
- Fix certificate thumbprint sysfs output
- Fix current password length check
- Free system certificate signatures
thinkpad_acpi:
- Add USB-C Security (USCS) support
- Fix fan speed reporting on Edge E330
- Fix USB-C Security probe failure on unsupported platforms
- Stop setting acpi_device_class()
topstar-laptop:
- Stop setting acpi_device_name/class()
toshiba_acpi:
- Do not use uninitialized device_class
toshiba_bluetooth:
- Use more common error handling code in toshiba_bt_rfkill_probe()
toshiba_haps:
- Do not use uninitialized device_class
uniwill-laptop:
- Add 2 new feature defines for TUXEDO devices
- Add AC auto boot support
- Add Avell A60 MUV support
- Add keyboard backlight support
- Add lightbar support for LAPQC71A/B
- Add support for the AiStone X4SP4NAL
- Add support for the MACHENIKE L16 Pro
- Add support for USB powershare
- Handle screen-related events
- Remove single color keyboard detection
- Split uniwill_kbd_led_init()
x86/platform/olpc: xo15:
- Stop setting acpi_device_name/class()
xo15-ebook:
- Stop setting acpi_device_name/class()
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQSCSUwRdwTNL2MhaBlZrE9hU+XOMQUCaow0EgAKCRBZrE9hU+XO
MRvYAP412gjYokIoj6ncE2OD897gX2d5BwUVKkraQHoevxLm8wEAikY+iYbdOv3a
fqhPeCDZSRBn6yEBHewY7cdotNDn2go=
=d/Uf
-----END PGP SIGNATURE-----
Merge tag 'platform-drivers-x86-v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86
Pull x86 platform driver updates from Ilpo Järvinen
"Highlights:
Major refactoring effort: stop setting acpi_device_name/class() and
pnp.device_class to facilitate their eventual removal
Many rollback/remove path fixes (presumably mostly found by AI)
Miscellaneous cleanups / refactoring / improvements
amd/halo:
- Add Halo RGB LED driver
amd/hsmp:
- Properly serialize probe, remove, and data paths
- Add support for protocol v7 used by Family 1AH Model 80H
- Fix error checking corner cases (largely from AI review)
- Reject negative power cap
amd/pmc:
- Improve behavior on platforms that do not support STB
- Add T14 Gen2 AMD (20XL) to s2idle quirk list
amd/pmf:
- Add ioctl interface to retrieve device metrics
- Add support for new metrics tables used by Family 1AH Model 80H
qcom-hamoa-ec (arm64):
- Reject short responses
asus-nb-wmi:
- Support ProArt key on ASUS ProArt PX13
asus-armoury:
- Gate PPT writes behind active fan curve
- Add power limits for more models
dell-wmi-base:
- Fix handling of ultra performance key
dell-wmi-sysman:
- Don't hex dump attribute security buffer
hp-bioscfg:
- Various fixes
- Improve reduced ACPI packages support (necessary for HP EliteBook 840 G2)
lg-laptop:
- Fix LED resource handling
- Add support for events used in newer models
- Fix keyboard backlight support on LG Gram 16T90SP
hp-wmi:
- Generalize thermal params to board params
- Manage CPU and GPU PWM independently
- Add GPU MUX switch support
- Add Victus 15-fb0xxx support
- Add OMEN MAX 16-ak0xxx, OMEN 16-n0xxx, OMEN 16-wd0xxx, OMEN
16-wf0xxx, and OMEN board ID 8D88 support
- Add OMEN Transcend 16-u0xxx support
huawei:
- Add support for Fn-lock ACPI interface found on newer Huawei
laptops such as MateBook 14 2024
ISST:
- Improve input validation (many fixes)
- Disallow SST-CP (core-power) feature if perf profile add fails
lenovo/yb9-kbdock:
- Add driver for Yoga Book 9 14IAH10
lenovo/ymc:
- Extend hinge switch query to support Yoga 9 2-in-1 14IPH11
- Prevent loading on Yoga Book 9 14IAH10 to avoid duplicated input
nodes
msi-ec:
- Add MSI Raider A18 HX A9WJG and MSI Katana GF76 11UEK support
msi-wmi:
- Add MSI Claw M-Center keys support
oxpec:
- Add support for OneXPlayer X2 Mini Pro
redmi-wmi:
- Report kbd backlight cycle, OEM preset power mode, and FnLock
toggle events to userspace
samsung-galaxybook:
- Add Samsung Galaxy Book6 Pro support
thinkpad_acpi:
- Add USB-C Security support
uniwill-laptop:
- Add keyboard backlight, AC auto boot, and USB powershare support
- Add MACHENIKE L16 Pro, AiStone X4SP4NAL, and Avell A60 MUV support
- Make lightbar max brightness configurable and add support for
LAPQC71A/B"
* tag 'platform-drivers-x86-v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (152 commits)
platform/x86: think-lmi: Fix current password length check
platform/x86: redmi-wmi: report EC state change events
MAINTAINERS: update Intel PMC Core maintainer contact
platform/x86: oxpec: Add support for OneXPlayer X2 Mini Pro
platform/x86: thinkpad_acpi: Fix fan speed reporting on Edge E330
platform/x86: msi-ec: Add MSI Katana GF76 11UEK EC firmware
platform/x86: think-lmi: Fix certificate thumbprint sysfs output
mlxbf-bootctl: fix the build error with FIELD_PREP()
platform/x86: think-lmi: Free system certificate signatures
platform/x86: ISST: Add a NULL check for sst_inst[]
platform/x86: ISST: Return error during profile addition
platform/x86: ISST: Just allow 2 bits for SST feature enable
platform/x86: ISST: Use PP level enable mask
platform/x86: ISST: Validate parameter for frequency and priority
platform/x86: ISST: Validate parameter for core power state
platform/x86: ISST: Validate max level for set feature
platform/x86: ISST: Validate logical CPU id and clos id
platform/x86: ISST: Validate level in perf mask ioctls
platform/x86: ISST: Validate socket ID in clos_assoc ioctl
platform/x86/amd/hsmp: Reject negative power cap writes in hwmon
...
This commit is contained in:
commit
5b05bb3f6c
73
Documentation/ABI/testing/amdpmf-interface
Normal file
73
Documentation/ABI/testing/amdpmf-interface
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
What: /dev/amdpmf_interface
|
||||
Date: June 2026
|
||||
KernelVersion: 7.2
|
||||
Contact: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
Description:
|
||||
The AMD Platform Management Framework (PMF) util layer exposes a
|
||||
minimal user-space interface via a character device for feature
|
||||
discovery and metrics monitoring.
|
||||
|
||||
When CONFIG_AMD_PMF_UTIL_SUPPORT is enabled, the driver creates
|
||||
a character device:
|
||||
|
||||
======================
|
||||
/dev/amdpmf_interface
|
||||
======================
|
||||
|
||||
The interface supports a single ioctl:
|
||||
|
||||
============================ =======================================
|
||||
IOCTL Usage
|
||||
IOCTL_AMD_PMF_POPULATE_DATA User passes a struct amd_pmf_info with
|
||||
the size field set to sizeof(struct
|
||||
amd_pmf_info). The driver returns all
|
||||
available metrics and feature status
|
||||
in the structure.
|
||||
============================ =======================================
|
||||
|
||||
struct amd_pmf_info layout:
|
||||
|
||||
======================= ========== ================================
|
||||
Field Type Description
|
||||
size __u64 Structure size for versioning
|
||||
features_supported __u32 Bitmask of supported features
|
||||
platform_type __u32 Platform form factor orientation
|
||||
power_source __u32 AC/DC power source
|
||||
laptop_placement __u32 Device placement state
|
||||
lid_state __u32 Lid open/closed status
|
||||
user_presence __u32 User presence detection
|
||||
slider_position __u32 Current power slider position
|
||||
skin_temp __s32 Skin temperature (centidegrees)
|
||||
gfx_busy __u32 Graphics workload percentage
|
||||
ambient_light __s32 Ambient light sensor reading
|
||||
avg_c0_residency __u32 Average C0 state residency
|
||||
max_c0_residency __u32 Maximum C0 state residency
|
||||
socket_power __u32 Socket power consumption
|
||||
bios_input[10] __u32 Custom BIOS input parameters
|
||||
bios_output[10] __u32 Custom BIOS output parameters
|
||||
======================= ========== ================================
|
||||
|
||||
Feature Support Flags (features_supported bitmask):
|
||||
|
||||
===================================== ==== =============================
|
||||
Flag Bit Description
|
||||
AMD_PMF_FEAT_AUTO_MODE 0 Auto Mode feature support
|
||||
AMD_PMF_FEAT_STATIC_POWER_SLIDER 1 Static Power Slider support
|
||||
AMD_PMF_FEAT_POLICY_BUILDER 2 Policy Builder (Smart PC)
|
||||
AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC 3 Dynamic slider on AC
|
||||
AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC 4 Dynamic slider on DC
|
||||
===================================== ==== =============================
|
||||
|
||||
Return codes:
|
||||
|
||||
============= ============================================================
|
||||
Return code Description
|
||||
0 Success
|
||||
EINVAL Invalid size or parameter
|
||||
EFAULT copy_to_user/copy_from_user failures
|
||||
ENODEV PMF device not available
|
||||
ENOTTY Unknown ioctl command
|
||||
============= ============================================================
|
||||
|
||||
User-space tools integrating with AMD PMF to discover capabilities and
|
||||
monitor real-time metrics for thermal and power management validation.
|
||||
19
Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
Normal file
19
Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
|
||||
Date: June 2027
|
||||
KernelVersion: 7.3
|
||||
Contact: Dave Carey <carvsdriver@gmail.com>
|
||||
Description:
|
||||
Read-only attribute reporting the current keyboard dock position
|
||||
as reported by the Embedded Controller on the Lenovo Yoga Book 9
|
||||
14IAH10.
|
||||
|
||||
Possible values:
|
||||
|
||||
== =============================================================
|
||||
0 keyboard is not docked to any screen (detached)
|
||||
1 keyboard docked on the top half of the bottom screen
|
||||
2 keyboard docked on the bottom half of the bottom screen
|
||||
== =============================================================
|
||||
|
||||
SW_TABLET_MODE input events are also emitted: 0 when the keyboard
|
||||
is docked (either position), 1 when detached.
|
||||
|
|
@ -78,3 +78,28 @@ Description:
|
|||
|
||||
Reading this file returns the profile names with the currently active one in
|
||||
brackets.
|
||||
|
||||
What: /sys/bus/platform/devices/INOU0000:XX/ac_auto_boot
|
||||
Date: March 2026
|
||||
KernelVersion: 7.1
|
||||
Contact: Armin Wolf <W_Armin@gmx.de>
|
||||
Description:
|
||||
Allows userspace applications to configure if the device should boot automatically
|
||||
when being connected to a power source. Writing "1"/"0" into this file
|
||||
enables/disables this functionality. Enabling both AC auto boot and USB powershare
|
||||
at the same time is not supported.
|
||||
|
||||
Reading this file returns the current status of the AC auto boot functionality.
|
||||
|
||||
What: /sys/bus/platform/devices/INOU0000:XX/usb_powershare_high
|
||||
Date: March 2026
|
||||
KernelVersion: 7.1
|
||||
Contact: Armin Wolf <W_Armin@gmx.de>
|
||||
Description:
|
||||
Allows userspace applications to configure if the device should continue to provide
|
||||
power via the USB ports when hibernating or powered off. Might also increase the
|
||||
power budget available to USB ports on some devices. Writing "1"/"0" into this
|
||||
file enables/disables this functionality. Enabling both USB powershare and AC auto
|
||||
boot at the same time is not supported.
|
||||
|
||||
Reading this file returns the current status of the USB powershare functionality.
|
||||
|
|
|
|||
|
|
@ -1543,6 +1543,30 @@ Values:
|
|||
|
||||
This setting can also be toggled via the Fn+doubletap hotkey.
|
||||
|
||||
USB-C Security
|
||||
--------------
|
||||
|
||||
sysfs: usb_c_security
|
||||
|
||||
Reports the current state of the USB-C Security (Restricted Mode) feature
|
||||
on supported ThinkPad systems. When enabled, USB-C data connections are
|
||||
disabled while power delivery is preserved.
|
||||
|
||||
The available command is::
|
||||
|
||||
cat /sys/devices/platform/thinkpad_acpi/usb_c_security
|
||||
|
||||
Values:
|
||||
|
||||
* ``enabled`` - USB-C data connections are currently blocked
|
||||
* ``disabled`` - USB-C data connections are currently allowed
|
||||
|
||||
The attribute is read-only. The USB-C Security state can only be toggled
|
||||
via the Fn+U followed by Fn+S hotkey chord.
|
||||
|
||||
The sysfs attribute is not created on platforms that do not support this
|
||||
feature.
|
||||
|
||||
Auxmac
|
||||
------
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,19 @@ LED class device. The default name of this LED class device is ``uniwill:multico
|
|||
See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details on how to control the various
|
||||
animation modes of the lightbar.
|
||||
|
||||
Keyboard Backlight
|
||||
------------------
|
||||
|
||||
The ``uniwill-laptop`` driver supports controlling the keyboard backlight using the standard
|
||||
LED class interface. The default name of this LED class device is ``uniwill:white:kbd_backlight``
|
||||
when the keyboard backlight supports only a single color, or ``uniwill:multicolor:kbd_backlight``
|
||||
when the keyboard backlight supports RGB colors. The maximum intensity for each color channel
|
||||
in RGB mode is 50.
|
||||
|
||||
Keep in mind that due to hardware design choices, the driver does not support the RGB value
|
||||
``0x000000`` (black), instead it will fall back to ``0x010101`` (faint white). In order to
|
||||
disable the keyboard backlight, the standard LED brightness setting has to be used instead.
|
||||
|
||||
Configurable TGP
|
||||
----------------
|
||||
|
||||
|
|
@ -85,6 +98,20 @@ allow it.
|
|||
|
||||
See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details.
|
||||
|
||||
AC Auto Boot
|
||||
------------
|
||||
|
||||
The ``uniwill-laptop`` driver allows the user to configure if the system should automatically
|
||||
boot when being connected to a power source, see
|
||||
Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details.
|
||||
|
||||
USB Powershare
|
||||
--------------
|
||||
|
||||
The ``uniwill-laptop`` driver allows the user to configure if the system should continue to
|
||||
provide power via the USB ports when hibernating or powered off, see
|
||||
Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,13 @@ under per socket sysfs directory created at
|
|||
|
||||
Note: lseek() is not supported as entire metrics table is read.
|
||||
|
||||
The sysfs metrics_bin path supports only HSMP protocol version 6 and,
|
||||
because it is a file read, can return a torn snapshot if userspace
|
||||
reads in pieces. The protocol version 7 metric table (~13 KB) also
|
||||
exceeds PAGE_SIZE, so a read returns ``-EOPNOTSUPP`` there. For
|
||||
atomic reads on any protocol version, use the
|
||||
``HSMP_IOCTL_GET_TELEMETRY_DATA`` ioctl on /dev/hsmp (see below).
|
||||
|
||||
Metrics table definitions will be documented as part of Public PPR.
|
||||
The same is defined in the amd_hsmp.h header.
|
||||
|
||||
|
|
@ -167,7 +174,7 @@ Next thing, open the device file, as follows::
|
|||
exit(1);
|
||||
}
|
||||
|
||||
The following IOCTL is defined:
|
||||
The following IOCTLs are defined:
|
||||
|
||||
``ioctl(file, HSMP_IOCTL_CMD, struct hsmp_message *msg)``
|
||||
The argument is a pointer to a::
|
||||
|
|
@ -180,6 +187,32 @@ The following IOCTL is defined:
|
|||
__u16 sock_ind; /* socket number */
|
||||
};
|
||||
|
||||
``ioctl(file, HSMP_IOCTL_GET_TELEMETRY_DATA, struct hsmp_telemetry_data *req)``
|
||||
Atomically fetch the firmware metric (telemetry) table for a socket.
|
||||
The ioctl copies the table in one shot, so unlike the metrics_bin
|
||||
sysfs path it cannot return a torn snapshot and is not bounded by
|
||||
PAGE_SIZE. Required for HSMP protocol version 7+ (e.g. Family 1Ah
|
||||
Model 50h-5Fh, whose table is ~13 KB). Argument::
|
||||
|
||||
struct hsmp_telemetry_data {
|
||||
__u64 buf; /* User pointer to destination buffer */
|
||||
__u32 size; /* Size of @buf in bytes */
|
||||
__u16 sock_ind; /* Socket index */
|
||||
__u16 reserved; /* Reserved, must be zero */
|
||||
};
|
||||
|
||||
``size`` must be non-zero and no larger than the table size firmware
|
||||
reports for that socket; a larger value is rejected with ``-EINVAL``
|
||||
rather than short-written, and a smaller one returns the leading
|
||||
``size`` bytes of the snapshot. A non-zero ``reserved`` is also
|
||||
rejected with ``-EINVAL``.
|
||||
|
||||
The table layout depends on the protocol version, which userspace
|
||||
reads from the ``protocol_version`` sysfs attribute. On version 6
|
||||
the table is ``struct hsmp_metric_table``, so callers pass
|
||||
``sizeof(struct hsmp_metric_table)``. Later version metrics table
|
||||
layout is documented in the Public PPR.
|
||||
|
||||
The ioctl would return a non-zero on failure; you can read errno to see
|
||||
what happened. The transaction returns 0 on success.
|
||||
|
||||
|
|
|
|||
17
MAINTAINERS
17
MAINTAINERS
|
|
@ -1145,6 +1145,13 @@ F: drivers/char/hw_random/geode-rng.c
|
|||
F: drivers/crypto/geode*
|
||||
F: drivers/video/fbdev/geode/
|
||||
|
||||
AMD HALO BOX RGB LED DRIVER
|
||||
M: Mario Limonciello (AMD) <superm1@kernel.org>
|
||||
R: Yo-Jung Leo Lin (AMD) <Leo.Lin@amd.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/platform/x86/amd/amd_halo_led.c
|
||||
|
||||
AMD HSMP DRIVER
|
||||
M: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>
|
||||
R: Carlos Bilbao <carlos.bilbao@kernel.org>
|
||||
|
|
@ -1263,6 +1270,7 @@ L: platform-driver-x86@vger.kernel.org
|
|||
S: Supported
|
||||
F: Documentation/ABI/testing/sysfs-amd-pmf
|
||||
F: drivers/platform/x86/amd/pmf/
|
||||
F: include/uapi/linux/amd-pmf.h
|
||||
|
||||
AMD POWERPLAY AND SWSMU
|
||||
M: Kenneth Feng <kenneth.feng@amd.com>
|
||||
|
|
@ -13350,7 +13358,7 @@ F: drivers/platform/x86/intel/punit_ipc.c
|
|||
|
||||
INTEL PMC CORE DRIVER
|
||||
M: Rajneesh Bhardwaj <irenic.rajneesh@gmail.com>
|
||||
M: David E Box <david.e.box@intel.com>
|
||||
M: Xi Pardee <xi.pardee@linux.intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-platform-intel-pmc
|
||||
|
|
@ -14783,6 +14791,13 @@ F: drivers/hid/hid-lenovo-go-s.c
|
|||
F: drivers/hid/hid-lenovo-go.c
|
||||
F: drivers/hid/hid-lenovo.c
|
||||
|
||||
LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
|
||||
M: Dave Carey <carvsdriver@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
|
||||
F: drivers/platform/x86/lenovo/yb9-kbdock.c
|
||||
|
||||
LETSKETCH HID TABLET DRIVER
|
||||
M: Hans de Goede <hansg@kernel.org>
|
||||
L: linux-input@vger.kernel.org
|
||||
|
|
|
|||
|
|
@ -53,9 +53,21 @@ enum hsmp_message_ids {
|
|||
HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */
|
||||
HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */
|
||||
HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */
|
||||
HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */
|
||||
HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */
|
||||
HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */
|
||||
HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */
|
||||
HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */
|
||||
HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */
|
||||
HSMP_READ_CCD_POWER, /* 34h Get average CCD power */
|
||||
HSMP_READ_TDELTA, /* 35h Get thermal behaviour */
|
||||
HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */
|
||||
HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */
|
||||
HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */
|
||||
HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */
|
||||
HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */
|
||||
HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */
|
||||
HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */
|
||||
HSMP_MSG_ID_MAX,
|
||||
};
|
||||
|
||||
|
|
@ -170,16 +182,27 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
|
|||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0
|
||||
* input: args[0] = min link width[15:8] + max link width[7:0]
|
||||
* HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1
|
||||
* input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) +
|
||||
* min link width[15:8] + max link width[7:0]
|
||||
* Link width encoding: 0 = x4, 1 = x8, 2 = x16.
|
||||
* On SET, max must be >= min. On GET, [15:0] are reserved.
|
||||
* output: args[0] = reserved[31:16] + min link width[15:8] +
|
||||
* max link width[7:0]
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0
|
||||
* input: args[0] = df pstate[7:0]
|
||||
* HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1
|
||||
* input: args[0] = set APB_DISABLE / get APB state[31]
|
||||
* (0 = set & lock DF P-state, 1 = get) +
|
||||
* reserved[30:8] +
|
||||
* DF P-state[7:0] (0..2; reserved on GET)
|
||||
* output: args[0] = reserved[31:9] +
|
||||
* APB state[8] (1 = disabled, 0 = enabled) +
|
||||
* locked DF P-state[7:0] if [8] = 1, else reserved
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */
|
||||
{0, 0, HSMP_SET},
|
||||
|
|
@ -305,16 +328,32 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
|
|||
{1, 1, HSMP_SET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0
|
||||
* input: args[0] = power efficiency mode[2:0]
|
||||
* HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection),
|
||||
* num_args = 1, response_sz = 1
|
||||
* input: args[0] = set/get policy[31] (0 = set, 1 = get) +
|
||||
* high util point[30:24] +
|
||||
* low util point[23:17] +
|
||||
* PPT limit[16:5] +
|
||||
* reserved[4:3] + mode selection[2:0]
|
||||
* [30:5] are valid only when [2:0] is a balanced core mode
|
||||
* (4 or 5). [2:0] is reserved when getting (bit[31] = 1).
|
||||
* output: args[0] same layout, [31] reserved, [2:0] = arbitrated
|
||||
* current efficiency mode.
|
||||
*/
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0
|
||||
* input: args[0] = min df pstate[15:8] + max df pstate[7:0]
|
||||
* HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1
|
||||
* input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) +
|
||||
* reserved[30:16] +
|
||||
* min DF P-state[15:8] + max DF P-state[7:0]
|
||||
* DF P-state encoding: 0 = DFP0 (high performance),
|
||||
* 1 = DFP1, 2 = DFP2 (low performance).
|
||||
* [15:0] are reserved when getting (args[0] bit[31] = 1).
|
||||
* output: args[0] = reserved[31:16] + min DF P-state[15:8] +
|
||||
* max DF P-state[7:0]
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_METRIC_TABLE_VER, num_args = 0, response_sz = 1
|
||||
|
|
@ -328,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
|
|||
{0, 0, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2
|
||||
* HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3
|
||||
* output: args[0] = lower 32 bits of the address
|
||||
* output: args[1] = upper 32 bits of the address
|
||||
* output: args[2] = DRAM region size in bytes
|
||||
*/
|
||||
{0, 2, HSMP_GET},
|
||||
{0, 3, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0
|
||||
|
|
@ -355,9 +395,31 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
|
|||
*/
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/* RESERVED(0x29-0x2f) */
|
||||
{0, 0, HSMP_RSVD},
|
||||
{0, 0, HSMP_RSVD},
|
||||
/*
|
||||
* HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1
|
||||
* input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) +
|
||||
* reserved[30:1] +
|
||||
* enable PC6[0] (0 = disable, 1 = enable;
|
||||
* reserved on GET)
|
||||
* output: args[0] = reserved[31:1] + current PC6 control[0]
|
||||
* (last value configured via HSMP or APML)
|
||||
*/
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/*
|
||||
* HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1
|
||||
* Configures CC6 enable for all cores; changing the setting does
|
||||
* not by itself transition cores in or out of CC6.
|
||||
* input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) +
|
||||
* reserved[30:1] +
|
||||
* enable CC6[0] (0 = disable, 1 = enable;
|
||||
* reserved on GET)
|
||||
* output: args[0] = reserved[31:1] + current CC6 control[0]
|
||||
* (last value configured via HSMP or APML)
|
||||
*/
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/* RESERVED(0x2B-0x2F) */
|
||||
{0, 0, HSMP_RSVD},
|
||||
{0, 0, HSMP_RSVD},
|
||||
{0, 0, HSMP_RSVD},
|
||||
|
|
@ -385,6 +447,89 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[]
|
|||
*/
|
||||
{0, 2, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1
|
||||
* input: args[0] = reg space[23] + reg offset[22:12] +
|
||||
* device LID[11:8] + DIMM address[7:0]
|
||||
* output: args[0] = read data byte[3:0]
|
||||
*/
|
||||
{1, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1
|
||||
* input: args[0] = apic id of core[15:0]
|
||||
* output: args[0] = CCD power(mWatts)[31:0]
|
||||
*/
|
||||
{1, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_READ_TDELTA, num_args = 0, response_sz = 1
|
||||
* output: args[0] = thermal behaviour[31:0]
|
||||
*/
|
||||
{0, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1
|
||||
* input: args[0] = SVI3 rail index[3:1] + read temperature[0]
|
||||
* output: args[0] = SVI3 rail index[30:28] +
|
||||
* rail temperature in degree C[27:0]
|
||||
*/
|
||||
{1, 1, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3
|
||||
* input: args[0] = HSMP command mask[0]
|
||||
* output: status of HSMP command = args[0], args[1], args[2]
|
||||
*/
|
||||
{1, 3, HSMP_GET},
|
||||
|
||||
/*
|
||||
* HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1
|
||||
* input: args[0] = op[31:30] + reserved[29:28] +
|
||||
* apic id[27:16] + floor frequency MHz[15:0]
|
||||
* op encoding: 00 = set per-core floor,
|
||||
* 01 = set all-cores floor (apic id reserved),
|
||||
* 10 = get per-core floor,
|
||||
* 11 = get per-core effective floor.
|
||||
* Floor frequency field is reserved on GET (bit[31] = 1).
|
||||
* output: args[0] = floor frequency MHz[15:0]
|
||||
* (effective for op 11, configured for op 10;
|
||||
* reserved on SET)
|
||||
*/
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/*
|
||||
* HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0
|
||||
* input: args[0] = write data[31:24] + reg space[23] +
|
||||
* reg offset[22:12] + device LID[11:8] +
|
||||
* DIMM address[7:0]
|
||||
*/
|
||||
{1, 0, HSMP_SET},
|
||||
|
||||
/*
|
||||
* HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1
|
||||
* input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) +
|
||||
* SDPS limit[30:0]
|
||||
* output: args[0] = SDPS limit[30:0]
|
||||
*/
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/*
|
||||
* HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1
|
||||
* input: args[0] = op[31:30] + priority sel[27:26] +
|
||||
* priority val[21:20] + input[19:0]
|
||||
* output: args[0] = supported priorities or priority val[1:0]
|
||||
*/
|
||||
{1, 1, HSMP_SET_GET},
|
||||
|
||||
/*
|
||||
* HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2
|
||||
* input: args[0] = op[31] + sub-op[30:29] + params[28:0]
|
||||
* output: args[0] = discovery bits or floating/global memory BW (Gbps)
|
||||
* output: args[1] = reserved or config (drop adj, sampling delay,
|
||||
* hysteresis)
|
||||
*/
|
||||
{1, 2, HSMP_SET_GET},
|
||||
};
|
||||
|
||||
/* Metrics table (supported only with proto version 6) */
|
||||
|
|
@ -467,6 +612,39 @@ struct hsmp_metric_table {
|
|||
__u32 gfxclk_frequency[8];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL
|
||||
* @buf: Input. Userspace pointer (encoded as __u64 to keep the layout
|
||||
* stable between 32-bit and 64-bit callers) to the destination
|
||||
* buffer that receives the metric table.
|
||||
* @size: Input. Size in bytes of the buffer pointed to by @buf, and the
|
||||
* number of bytes copied out on success. Must be non-zero and no
|
||||
* larger than the metric table size firmware reports for this
|
||||
* socket; a larger value is rejected with -EINVAL rather than
|
||||
* short-written. A smaller value returns the leading @size bytes
|
||||
* of the snapshot. The kernel does not write this field back.
|
||||
* @sock_ind: Input. Socket index from which the metric table is read.
|
||||
* @reserved: Reserved for future use. Callers should set this to zero;
|
||||
* future kernels may begin interpreting the field, so passing
|
||||
* a non-zero value today is not forwards compatible.
|
||||
*
|
||||
* Placing @buf first lets all fields fall on their natural alignment under
|
||||
* the surrounding #pragma pack(4), so the struct is a tight 16 bytes with
|
||||
* the same wire layout on 32-bit and 64-bit userspace.
|
||||
*
|
||||
* The metric table layout depends on the HSMP protocol version reported by
|
||||
* firmware, which userspace can read from the protocol_version sysfs
|
||||
* attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on
|
||||
* that version pass sizeof(struct hsmp_metric_table). Later version metrics
|
||||
* table layout is documented in the Public PPR.
|
||||
*/
|
||||
struct hsmp_telemetry_data {
|
||||
__u64 buf;
|
||||
__u32 size;
|
||||
__u16 sock_ind;
|
||||
__u16 reserved;
|
||||
};
|
||||
|
||||
/* Reset to default packing */
|
||||
#pragma pack()
|
||||
|
||||
|
|
@ -474,4 +652,16 @@ struct hsmp_metric_table {
|
|||
#define HSMP_BASE_IOCTL_NR 0xF8
|
||||
#define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message)
|
||||
|
||||
/*
|
||||
* Fetch the firmware metric (telemetry) table for a given socket via the
|
||||
* HSMP character device. This avoids the PAGE_SIZE limitation of the
|
||||
* sysfs binary attribute path for tables larger than one page (such as the
|
||||
* ~13 KB table used by HSMP protocol version 7).
|
||||
*
|
||||
* The direction is _IOW because the kernel only reads the request struct;
|
||||
* the table itself is written to the buffer that @buf points at.
|
||||
*/
|
||||
#define HSMP_IOCTL_GET_TELEMETRY_DATA \
|
||||
_IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data)
|
||||
|
||||
#endif /*_ASM_X86_AMD_HSMP_H_*/
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
#define DRV_NAME "olpc-xo15-sci"
|
||||
#define PFX DRV_NAME ": "
|
||||
#define XO15_SCI_CLASS DRV_NAME
|
||||
#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI"
|
||||
|
||||
static unsigned long xo15_sci_gpe;
|
||||
static bool lid_wake_on_close;
|
||||
|
|
@ -148,9 +146,6 @@ static int xo15_sci_probe(struct platform_device *pdev)
|
|||
if (!device)
|
||||
return -ENODEV;
|
||||
|
||||
strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), XO15_SCI_CLASS);
|
||||
|
||||
/* Get GPE bit assignment (EC events). */
|
||||
status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp);
|
||||
if (ACPI_FAILURE(status))
|
||||
|
|
|
|||
|
|
@ -1125,8 +1125,6 @@ static int sonypi_acpi_probe(struct platform_device *pdev)
|
|||
return -ENODEV;
|
||||
|
||||
sonypi_acpi_device = device;
|
||||
strscpy(acpi_device_name(device), "Sony laptop hotkeys");
|
||||
strscpy(acpi_device_class(device), "sony/hotkey");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
*
|
||||
* Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
|
||||
*/
|
||||
#include <linux/amd-pmf.h>
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/io-64-nonatomic-lo-hi.h>
|
||||
|
|
@ -137,20 +138,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement)
|
|||
*platform_type = mode.op_mode.devicemode;
|
||||
|
||||
if (mode.op_mode.ontablestate == 1) {
|
||||
*laptop_placement = ON_TABLE;
|
||||
*laptop_placement = AMD_PMF_ON_TABLE;
|
||||
} else if (mode.op_mode.ontablestate == 2) {
|
||||
*laptop_placement = ON_LAP_MOTION;
|
||||
*laptop_placement = AMD_PMF_ON_LAP_MOTION;
|
||||
} else if (mode.op_mode.inbagstate == 1) {
|
||||
*laptop_placement = IN_BAG;
|
||||
*laptop_placement = AMD_PMF_IN_BAG;
|
||||
} else if (mode.op_mode.outbagstate == 1) {
|
||||
*laptop_placement = OUT_OF_BAG;
|
||||
*laptop_placement = AMD_PMF_OUT_OF_BAG;
|
||||
} else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 ||
|
||||
mode.op_mode.outbagstate == 0) {
|
||||
*laptop_placement = LP_UNKNOWN;
|
||||
*laptop_placement = AMD_PMF_LP_UNKNOWN;
|
||||
pr_warn_once("Unknown laptop placement\n");
|
||||
} else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 ||
|
||||
mode.op_mode.outbagstate == 3) {
|
||||
*laptop_placement = LP_UNDEFINED;
|
||||
*laptop_placement = AMD_PMF_LP_UNDEFINED;
|
||||
pr_warn_once("Undefined laptop placement\n");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,8 +92,10 @@ static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, u8 resp_len, u8 *resp)
|
|||
return ret;
|
||||
else if (ret == 0 || ret == 0xff)
|
||||
return -EOPNOTSUPP;
|
||||
else if (ret != resp_len)
|
||||
return -EIO;
|
||||
|
||||
if (resp[0] >= resp_len)
|
||||
if (resp[0] != resp_len - 1)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/arm-smccc.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
|
|
|||
|
|
@ -2262,13 +2262,19 @@ static int mlxbf_pmc_map_counters(struct device *dev)
|
|||
|
||||
static int mlxbf_pmc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev);
|
||||
const char *hid = acpi_device_hid(acpi_dev);
|
||||
struct device *dev = &pdev->dev;
|
||||
struct acpi_device *acpi_dev;
|
||||
struct arm_smccc_res res;
|
||||
const char *hid;
|
||||
guid_t guid;
|
||||
int ret;
|
||||
|
||||
acpi_dev = ACPI_COMPANION(&pdev->dev);
|
||||
if (!acpi_dev)
|
||||
return -ENODEV;
|
||||
|
||||
hid = acpi_device_hid(acpi_dev);
|
||||
|
||||
/* Ensure we have the UUID we expect for this service. */
|
||||
arm_smccc_smc(MLXBF_PMC_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res);
|
||||
guid_parse(mlxbf_pmc_svc_uuid_str, &guid);
|
||||
|
|
|
|||
|
|
@ -777,12 +777,16 @@ static int san_consumer_links_setup(struct platform_device *pdev)
|
|||
|
||||
static int san_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct acpi_device *san = ACPI_COMPANION(&pdev->dev);
|
||||
struct ssam_controller *ctrl;
|
||||
struct acpi_device *san;
|
||||
struct san_data *data;
|
||||
acpi_status astatus;
|
||||
int status;
|
||||
|
||||
san = ACPI_COMPANION(&pdev->dev);
|
||||
if (!san)
|
||||
return -ENODEV;
|
||||
|
||||
ctrl = ssam_client_bind(&pdev->dev);
|
||||
if (IS_ERR(ctrl))
|
||||
return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
|
||||
|
|
|
|||
|
|
@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = {
|
|||
/* -- Driver registration. -------------------------------------------------- */
|
||||
|
||||
static const struct ssam_device_id ssam_hub_match[] = {
|
||||
{ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub },
|
||||
{ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub },
|
||||
{
|
||||
SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00),
|
||||
.driver_data = (unsigned long)&kip_hub,
|
||||
}, {
|
||||
SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00),
|
||||
.driver_data = (unsigned long)&base_hub,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ssam, ssam_hub_match);
|
||||
|
|
|
|||
|
|
@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = {
|
|||
/* -- Driver registration. -------------------------------------------------- */
|
||||
|
||||
static const struct ssam_device_id ssam_tablet_sw_match[] = {
|
||||
{ SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc },
|
||||
{ SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc },
|
||||
{
|
||||
SSAM_SDEV(KIP, SAM, 0x00, 0x01),
|
||||
.driver_data = (unsigned long)&ssam_kip_sw_desc,
|
||||
}, {
|
||||
SSAM_SDEV(POS, SAM, 0x00, 0x01),
|
||||
.driver_data = (unsigned long)&ssam_pos_sw_desc,
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
#define SURFACE_PRO3_BUTTON_HID "MSHW0028"
|
||||
#define SURFACE_PRO4_BUTTON_HID "MSHW0040"
|
||||
#define SURFACE_BUTTON_OBJ_NAME "VGBI"
|
||||
#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons"
|
||||
|
||||
#define MSHW0040_DSM_REVISION 0x01
|
||||
#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision
|
||||
|
|
@ -212,11 +211,10 @@ static int surface_button_probe(struct platform_device *pdev)
|
|||
goto err_free_button;
|
||||
}
|
||||
|
||||
strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME);
|
||||
snprintf(button->phys, sizeof(button->phys), "%s/buttons",
|
||||
acpi_device_hid(device));
|
||||
|
||||
input->name = acpi_device_name(device);
|
||||
input->name = "Surface Pro 3/4 Buttons";
|
||||
input->phys = button->phys;
|
||||
input->id.bustype = BUS_HOST;
|
||||
input->dev.parent = &pdev->dev;
|
||||
|
|
@ -239,8 +237,8 @@ static int surface_button_probe(struct platform_device *pdev)
|
|||
goto err_free_button;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "%s [%s]\n", acpi_device_name(device),
|
||||
acpi_device_bid(device));
|
||||
dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device));
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_input:
|
||||
|
|
|
|||
|
|
@ -802,7 +802,6 @@ config LG_LAPTOP
|
|||
tristate "LG Laptop Extras"
|
||||
depends on ACPI
|
||||
depends on ACPI_BATTERY
|
||||
depends on ACPI_WMI
|
||||
depends on INPUT
|
||||
select INPUT_SPARSEKMAP
|
||||
select NEW_LEDS
|
||||
|
|
|
|||
|
|
@ -1581,7 +1581,9 @@ static int WMI_gaming_execute_u32_u64(u32 method_id, u32 in, u64 *out)
|
|||
return -EIO;
|
||||
|
||||
obj = result.pointer;
|
||||
if (obj && out) {
|
||||
if (!obj && out) {
|
||||
ret = -ENOMSG;
|
||||
} else if (obj && out) {
|
||||
switch (obj->type) {
|
||||
case ACPI_TYPE_INTEGER:
|
||||
*out = obj->integer.value;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,17 @@ config AMD_WBRF
|
|||
This mechanism will only be activated on platforms that advertise a
|
||||
need for it.
|
||||
|
||||
config AMD_HALO_LED
|
||||
tristate "AMD Halo Box RGB LED Driver"
|
||||
depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR
|
||||
help
|
||||
This driver provides RGB LED control for AMD Halo Box devices
|
||||
through the LED multicolor subsystem. The Halo Box light bar can
|
||||
be controlled via sysfs to display any RGB color combination.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called amd_halo_led.
|
||||
|
||||
config AMD_ISP_PLATFORM
|
||||
tristate "AMD ISP4 platform driver"
|
||||
depends on I2C && X86_64 && ACPI
|
||||
|
|
|
|||
|
|
@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/
|
|||
obj-$(CONFIG_AMD_HSMP) += hsmp/
|
||||
obj-$(CONFIG_AMD_PMF) += pmf/
|
||||
obj-$(CONFIG_AMD_WBRF) += wbrf.o
|
||||
obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o
|
||||
obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o
|
||||
obj-$(CONFIG_AMD_HFI) += hfi/
|
||||
|
|
|
|||
315
drivers/platform/x86/amd/amd_halo_led.c
Normal file
315
drivers/platform/x86/amd/amd_halo_led.c
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* AMD Halo Box RGB LED Driver
|
||||
*
|
||||
* Copyright (C) 2026 Advanced Micro Devices, Inc.
|
||||
*
|
||||
* This driver provides RGB LED control for AMD Halo Box devices through
|
||||
* the LED multicolor subsystem. The Halo Box light bar can be controlled
|
||||
* via sysfs to display any RGB color combination.
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/device/devres.h>
|
||||
#include <linux/led-class-multicolor.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include <linux/byteorder/generic.h>
|
||||
|
||||
#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86"
|
||||
|
||||
/* WMI method IDs from MOF */
|
||||
enum {
|
||||
AMD_HALO_WMI_TURN_OFF = 0x04,
|
||||
AMD_HALO_WMI_RGB = 0x07,
|
||||
};
|
||||
|
||||
/* Arg0 of the AMD_HALO_WMI_RGB Method */
|
||||
enum {
|
||||
AMD_HALO_RGB_CMD_GET = 0x0,
|
||||
AMD_HALO_RGB_CMD_SET = 0x1,
|
||||
};
|
||||
|
||||
/* Status codes from spec */
|
||||
#define AMD_HALO_STATUS_SUCCESS 0x0000
|
||||
#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD
|
||||
|
||||
/* Brightness uses 0-100 range */
|
||||
#define AMD_HALO_MAX_HW_BRIGHTNESS 100
|
||||
|
||||
/**
|
||||
* struct amd_halo_led_data - Driver private data
|
||||
* @wdev: WMI device pointer
|
||||
* @led_mc: LED multicolor class device
|
||||
* @subled_info: RGB channel information
|
||||
* @lock: Mutex to protect WMI calls
|
||||
*/
|
||||
struct amd_halo_led_data {
|
||||
struct wmi_device *wdev;
|
||||
struct led_classdev_mc led_mc;
|
||||
struct mc_subled subled_info[3];
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
struct amd_halo_wmi_args {
|
||||
__le32 arg0;
|
||||
__le32 arg1;
|
||||
};
|
||||
|
||||
struct amd_halo_wmi_args_rgb {
|
||||
__le32 cmd;
|
||||
__le32 red;
|
||||
__le32 green;
|
||||
__le32 blue;
|
||||
};
|
||||
|
||||
struct amd_halo_wmi_output_rgb {
|
||||
__le16 status;
|
||||
u8 red;
|
||||
u8 green;
|
||||
u8 blue;
|
||||
} __packed;
|
||||
|
||||
static inline int wmi_status_to_err(u16 status)
|
||||
{
|
||||
switch (status) {
|
||||
case AMD_HALO_STATUS_SUCCESS:
|
||||
return 0;
|
||||
case AMD_HALO_STATUS_INVALID_PARAM:
|
||||
return -EINVAL;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static int __amd_halo_wmi_call(struct wmi_device *wdev,
|
||||
u32 method_id, void *data, size_t length)
|
||||
{
|
||||
struct wmi_buffer input = {
|
||||
.length = length,
|
||||
.data = data,
|
||||
};
|
||||
struct wmi_buffer output = { };
|
||||
int ret;
|
||||
|
||||
/* Return buffer per spec: Bytes[0:1] = Status (little-endian) */
|
||||
ret = wmidev_invoke_method(wdev, 0, method_id,
|
||||
&input, &output, sizeof(__le16));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
__le16 *result_status __free(kfree) = output.data;
|
||||
|
||||
return wmi_status_to_err(le16_to_cpu(*result_status));
|
||||
}
|
||||
|
||||
/**
|
||||
* amd_halo_wmi_turn_off - Turn off all LED channels
|
||||
* @wdev: WMI device pointer
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int amd_halo_wmi_turn_off(struct wmi_device *wdev)
|
||||
{
|
||||
struct amd_halo_wmi_args args = { };
|
||||
|
||||
return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args));
|
||||
}
|
||||
|
||||
/**
|
||||
* amd_halo_wmi_set_rgb - Set all RGB channels atomically
|
||||
* @wdev: WMI device pointer
|
||||
* @r: brightness for red channel (0 - 100)
|
||||
* @g: brightness for green channel (0 - 100)
|
||||
* @b: brightness for blue channel (0 - 100)
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b)
|
||||
{
|
||||
struct amd_halo_wmi_args_rgb args = {
|
||||
.cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET),
|
||||
.red = cpu_to_le32(r),
|
||||
.green = cpu_to_le32(g),
|
||||
.blue = cpu_to_le32(b),
|
||||
};
|
||||
|
||||
if (r > AMD_HALO_MAX_HW_BRIGHTNESS ||
|
||||
g > AMD_HALO_MAX_HW_BRIGHTNESS ||
|
||||
b > AMD_HALO_MAX_HW_BRIGHTNESS) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args));
|
||||
}
|
||||
|
||||
/**
|
||||
* amd_halo_wmi_get_rgb - Get RGB values
|
||||
* @wdev: WMI device pointer
|
||||
* @r: output buffer for red value
|
||||
* @g: output buffer for green value
|
||||
* @b: output buffer for blue value
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b)
|
||||
{
|
||||
struct amd_halo_wmi_args_rgb args = {
|
||||
.cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET),
|
||||
};
|
||||
struct wmi_buffer input = {
|
||||
.length = sizeof(args),
|
||||
.data = &args,
|
||||
};
|
||||
struct wmi_buffer output = { };
|
||||
int ret;
|
||||
|
||||
ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB,
|
||||
&input, &output, sizeof(struct amd_halo_wmi_output_rgb));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data;
|
||||
|
||||
ret = wmi_status_to_err(le16_to_cpu(data->status));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS ||
|
||||
data->green > AMD_HALO_MAX_HW_BRIGHTNESS ||
|
||||
data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) {
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
*r = data->red;
|
||||
*g = data->green;
|
||||
*b = data->blue;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* amd_halo_brightness_set - Set LED brightness
|
||||
* @cdev: LED class device
|
||||
* @brightness: Brightness value
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int amd_halo_brightness_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
|
||||
struct amd_halo_led_data *data = container_of(mc_cdev,
|
||||
struct amd_halo_led_data,
|
||||
led_mc);
|
||||
u32 red_hw, green_hw, blue_hw;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&data->lock);
|
||||
|
||||
led_mc_calc_color_components(mc_cdev, brightness);
|
||||
|
||||
if (brightness == 0)
|
||||
return amd_halo_wmi_turn_off(data->wdev);
|
||||
|
||||
red_hw = mc_cdev->subled_info[0].brightness;
|
||||
green_hw = mc_cdev->subled_info[1].brightness;
|
||||
blue_hw = mc_cdev->subled_info[2].brightness;
|
||||
|
||||
ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
/*
|
||||
* Consider the light bar non-functional if AMD_HALO_WMI_RGB failed.
|
||||
* Attempt to turn the LED off completely as clean-up.
|
||||
*/
|
||||
if (amd_halo_wmi_turn_off(data->wdev))
|
||||
dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int amd_halo_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct led_init_data led_init_data = {
|
||||
.devicename = "amd_halo",
|
||||
.default_label = "multicolor:" LED_FUNCTION_STATUS,
|
||||
.devname_mandatory = true,
|
||||
};
|
||||
struct amd_halo_led_data *data;
|
||||
u8 r, g, b;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_mutex_init(&wdev->dev, &data->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->wdev = wdev;
|
||||
dev_set_drvdata(&wdev->dev, data);
|
||||
|
||||
data->subled_info[0].color_index = LED_COLOR_ID_RED;
|
||||
data->subled_info[1].color_index = LED_COLOR_ID_GREEN;
|
||||
data->subled_info[2].color_index = LED_COLOR_ID_BLUE;
|
||||
|
||||
data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS;
|
||||
data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS;
|
||||
data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set;
|
||||
data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
|
||||
data->led_mc.num_colors = ARRAY_SIZE(data->subled_info);
|
||||
data->led_mc.subled_info = data->subled_info;
|
||||
|
||||
ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->subled_info[0].intensity = r;
|
||||
data->subled_info[1].intensity = g;
|
||||
data->subled_info[2].intensity = b;
|
||||
|
||||
ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc,
|
||||
&led_init_data);
|
||||
if (ret)
|
||||
return dev_err_probe(&wdev->dev, ret,
|
||||
"Failed to register multicolor LED\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct wmi_device_id amd_halo_id_table[] = {
|
||||
{ .guid_string = AMD_HALO_GUID },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(wmi, amd_halo_id_table);
|
||||
|
||||
static struct wmi_driver amd_halo_driver = {
|
||||
.driver = {
|
||||
.name = "amd_halo_led",
|
||||
},
|
||||
.id_table = amd_halo_id_table,
|
||||
.probe = amd_halo_probe,
|
||||
.no_singleton = true,
|
||||
};
|
||||
|
||||
module_wmi_driver(amd_halo_driver);
|
||||
|
||||
MODULE_AUTHOR("Mario Limonciello (AMD) <superm1@kernel.org>");
|
||||
MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) <Leo.Lin@amd.com>");
|
||||
MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -15,12 +15,18 @@
|
|||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/kstrtox.h>
|
||||
#include <linux/lockdep.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/topology.h>
|
||||
#include <linux/uuid.h>
|
||||
|
|
@ -38,6 +44,17 @@
|
|||
|
||||
static struct hsmp_plat_device *hsmp_pdev;
|
||||
|
||||
/*
|
||||
* Tracks the ACPI socket platform devices that share the socket array and the
|
||||
* /dev/hsmp misc device. The first probe initializes it, each further probe
|
||||
* takes a reference and every remove (or probe failure) drops one; the last
|
||||
* put frees the shared state via hsmp_acpi_sock_release(). All get/put run
|
||||
* under hsmp_sock_rwsem held for write, so the counting is already serialized
|
||||
* and the atomic in kref is not strictly needed; kref is used for the clearer
|
||||
* get/put interface and its release callback.
|
||||
*/
|
||||
static struct kref hsmp_acpi_sock_kref;
|
||||
|
||||
struct hsmp_sys_attr {
|
||||
struct device_attribute dattr;
|
||||
u32 msg_id;
|
||||
|
|
@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
|
|||
* bytes to integer.
|
||||
*/
|
||||
uid = acpi_device_uid(ACPI_COMPANION(dev));
|
||||
if (!uid || strlen(uid) < 3)
|
||||
return -EINVAL;
|
||||
|
||||
return kstrtou16(uid + 2, 10, sock_ind);
|
||||
}
|
||||
|
|
@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
|
|||
return AE_OK;
|
||||
}
|
||||
|
||||
static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
|
||||
static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock)
|
||||
{
|
||||
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *guid, *mailbox_package;
|
||||
|
|
@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
|
|||
int ret = 0;
|
||||
int j;
|
||||
|
||||
status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL,
|
||||
status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL,
|
||||
&buf, ACPI_TYPE_PACKAGE);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
|
||||
dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -ENODEV;
|
||||
}
|
||||
|
|
@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
|
|||
guid = &dsd->package.elements[0];
|
||||
mailbox_package = &dsd->package.elements[1];
|
||||
if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) {
|
||||
dev_err(sock->dev, "Invalid hsmp _DSD table data\n");
|
||||
dev_err(dev, "Invalid hsmp _DSD table data\n");
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
}
|
||||
|
|
@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
|
|||
union acpi_object *msgobj, *msgstr, *msgint;
|
||||
|
||||
msgobj = &mailbox_package->package.elements[j];
|
||||
msgstr = &msgobj->package.elements[0];
|
||||
msgint = &msgobj->package.elements[1];
|
||||
|
||||
/* package should have 1 string and 1 integer object */
|
||||
if (msgobj->type != ACPI_TYPE_PACKAGE ||
|
||||
msgstr->type != ACPI_TYPE_STRING ||
|
||||
msgobj->package.count < 2) {
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
}
|
||||
|
||||
msgstr = &msgobj->package.elements[0];
|
||||
msgint = &msgobj->package.elements[1];
|
||||
|
||||
if (msgstr->type != ACPI_TYPE_STRING ||
|
||||
msgint->type != ACPI_TYPE_INTEGER) {
|
||||
ret = -EINVAL;
|
||||
goto free_buf;
|
||||
|
|
@ -183,14 +208,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
|
||||
static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock)
|
||||
{
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS,
|
||||
status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS,
|
||||
hsmp_resource, sock);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
|
||||
dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
|
|||
return -EINVAL;
|
||||
|
||||
/* The mapped region should be un-cached */
|
||||
sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
|
||||
sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr,
|
||||
sock->mbinfo.size);
|
||||
if (!sock->virt_base_addr) {
|
||||
dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
|
||||
dev_err(dev, "Failed to ioremap MP1 base address\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
|
|||
int ret;
|
||||
|
||||
sock->sock_ind = sock_ind;
|
||||
sock->dev = dev;
|
||||
sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr;
|
||||
|
||||
sema_init(&sock->hsmp_sem, 1);
|
||||
|
|
@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
|
|||
dev_set_drvdata(dev, sock);
|
||||
|
||||
/* Read MP1 base address from CRS method */
|
||||
ret = hsmp_read_acpi_crs(sock);
|
||||
ret = hsmp_read_acpi_crs(dev, sock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read mailbox offsets from DSD table */
|
||||
return hsmp_read_acpi_dsd(sock);
|
||||
ret = hsmp_read_acpi_dsd(dev, sock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Publish sock->dev last. hsmp_send_message() uses it (via
|
||||
* smp_load_acquire()) as the readiness gate for the lock-free data
|
||||
* plane, so it must become visible only after virt_base_addr, the
|
||||
* mailbox offsets and the semaphore are fully initialized. On a
|
||||
* multi-socket system socket 0 exposes /dev/hsmp before later sockets
|
||||
* finish probing, so without this an ioctl aimed at a socket still in
|
||||
* bring-up could pass the gate and dereference a NULL virt_base_addr.
|
||||
*/
|
||||
smp_store_release(&sock->dev, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj,
|
||||
|
|
@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj
|
|||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct hsmp_socket *sock = dev_get_drvdata(dev);
|
||||
|
||||
/*
|
||||
* metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE.
|
||||
* It can therefore only carry the protocol version 6 metric table
|
||||
* (struct hsmp_metric_table). The larger tables defined from protocol
|
||||
* version 7 onwards do not fit; userspace on those systems must read
|
||||
* the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp.
|
||||
* Surface the unsupported case here as -EOPNOTSUPP rather than
|
||||
* silently truncating the snapshot.
|
||||
*/
|
||||
if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return hsmp_metric_tbl_read(sock, buf, count);
|
||||
}
|
||||
|
||||
static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *battr, int id)
|
||||
{
|
||||
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
|
||||
/*
|
||||
* Keep metrics_bin visible on protocol version 7 and later as well,
|
||||
* so that userspace which expects the file to exist gets a clear
|
||||
* -EOPNOTSUPP from the read handler instead of -ENOENT, and is
|
||||
* pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path.
|
||||
*/
|
||||
if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)
|
||||
return battr->attr.mode;
|
||||
|
||||
return 0;
|
||||
|
|
@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att
|
|||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox
|
||||
* handshake and register its sysfs/hwmon interfaces.
|
||||
*
|
||||
* Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the
|
||||
* per-socket bring-up cannot race a concurrent probe or remove.
|
||||
*/
|
||||
static int init_acpi(struct device *dev)
|
||||
{
|
||||
u16 sock_ind;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held_write(&hsmp_sock_rwsem);
|
||||
|
||||
ret = hsmp_get_uid(dev, &sock_ind);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -491,7 +557,7 @@ static int init_acpi(struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
|
||||
if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) {
|
||||
ret = hsmp_get_tbl_dram_base(sock_ind);
|
||||
if (ret)
|
||||
dev_info(dev, "Failed to init metric table\n");
|
||||
|
|
@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
|
||||
|
||||
/*
|
||||
* kref release: tear down the shared ACPI socket state once the last socket
|
||||
* drops its reference. Deregister /dev/hsmp if it was registered, unmap any
|
||||
* metric-table DRAM, destroy the per-socket mutexes and free the socket array.
|
||||
*
|
||||
* Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove
|
||||
* and probe-failure paths both drop their reference under that lock. The write
|
||||
* lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox
|
||||
* and freeing the array cannot race the data plane.
|
||||
*/
|
||||
static void hsmp_acpi_sock_release(struct kref *kref)
|
||||
{
|
||||
lockdep_assert_held_write(&hsmp_sock_rwsem);
|
||||
|
||||
if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device))
|
||||
hsmp_misc_deregister();
|
||||
hsmp_unmap_metric_tbls(hsmp_pdev);
|
||||
hsmp_destroy_metric_read_locks(hsmp_pdev);
|
||||
kfree(hsmp_pdev->sock);
|
||||
hsmp_pdev->sock = NULL;
|
||||
hsmp_pdev->num_sockets = 0;
|
||||
hsmp_pdev->proto_ver = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe.
|
||||
* @dev: ACPI companion device whose probe failed.
|
||||
*
|
||||
* This device already took a reference on entry to hsmp_acpi_probe(), so clear
|
||||
* its sock->dev and drop that reference; the shared state is released if it was
|
||||
* the last one.
|
||||
*
|
||||
* Clearing sock->dev matters on multi-socket systems: when a non-first socket
|
||||
* fails, the array stays alive (owned by an already-probed socket) and
|
||||
* remove() is never called for this device, yet devres unmaps its mailbox once
|
||||
* probe() returns. Without clearing dev, a later message to this index would
|
||||
* pass every gate in hsmp_send_message() and reach the unmapped mailbox.
|
||||
*
|
||||
* sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata.
|
||||
*
|
||||
* Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write.
|
||||
*/
|
||||
static void hsmp_acpi_probe_failure_cleanup(struct device *dev)
|
||||
{
|
||||
struct hsmp_socket *sock = dev_get_drvdata(dev);
|
||||
|
||||
lockdep_assert_held_write(&hsmp_sock_rwsem);
|
||||
|
||||
if (sock)
|
||||
sock->dev = NULL;
|
||||
|
||||
kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release);
|
||||
}
|
||||
|
||||
static int hsmp_acpi_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
|
@ -584,34 +704,61 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
|
|||
if (!hsmp_pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!hsmp_pdev->is_probed) {
|
||||
/*
|
||||
* Multiple ACPI socket devices probe in parallel, but the one-time
|
||||
* socket-array allocation and /dev/hsmp registration below must run
|
||||
* exactly once. Hold the socket rwsem for write across the whole
|
||||
* bring-up so it cannot race a concurrent probe or remove, and so the
|
||||
* probe-failure teardown drains the data plane.
|
||||
*/
|
||||
guard(rwsem_write)(&hsmp_sock_rwsem);
|
||||
|
||||
if (!hsmp_pdev->sock) {
|
||||
hsmp_pdev->num_sockets = topology_max_packages();
|
||||
if (!hsmp_pdev->num_sockets) {
|
||||
dev_err(&pdev->dev, "No CPU sockets detected\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
|
||||
sizeof(*hsmp_pdev->sock),
|
||||
GFP_KERNEL);
|
||||
hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets,
|
||||
sizeof(*hsmp_pdev->sock),
|
||||
GFP_KERNEL);
|
||||
if (!hsmp_pdev->sock)
|
||||
return -ENOMEM;
|
||||
|
||||
hsmp_init_metric_read_locks(hsmp_pdev);
|
||||
kref_init(&hsmp_acpi_sock_kref);
|
||||
} else {
|
||||
kref_get(&hsmp_acpi_sock_kref);
|
||||
}
|
||||
|
||||
/*
|
||||
* This socket now holds a reference (kref_init on the first socket,
|
||||
* kref_get afterwards). Every failure path below drops it via
|
||||
* hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to
|
||||
* hsmp_acpi_remove().
|
||||
*/
|
||||
ret = init_acpi(&pdev->dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n");
|
||||
hsmp_acpi_probe_failure_cleanup(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!hsmp_pdev->is_probed) {
|
||||
ret = hsmp_misc_register(&pdev->dev);
|
||||
if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) {
|
||||
/*
|
||||
* Register /dev/hsmp unparented. It is a singleton shared by all
|
||||
* ACPI sockets and outlives all but the last of them, so
|
||||
* parenting it to this socket's device would leave a dangling
|
||||
* parent once that socket is unbound.
|
||||
*/
|
||||
ret = hsmp_misc_register(NULL);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to register misc device\n");
|
||||
hsmp_acpi_probe_failure_cleanup(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
hsmp_pdev->is_probed = true;
|
||||
dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n");
|
||||
dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -619,14 +766,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
|
|||
|
||||
static void hsmp_acpi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
/*
|
||||
* We register only one misc_device even on multi-socket system.
|
||||
* So, deregister should happen only once.
|
||||
* Serialize the kref_put() and any release it triggers against a
|
||||
* concurrent probe, and drain the data plane for the whole
|
||||
* teardown: this covers the per-socket unbind, whose mailbox devres
|
||||
* unmaps once we return, and the last unbind that frees the socket
|
||||
* array in hsmp_acpi_sock_release().
|
||||
*/
|
||||
if (hsmp_pdev->is_probed) {
|
||||
hsmp_misc_deregister();
|
||||
hsmp_pdev->is_probed = false;
|
||||
}
|
||||
guard(rwsem_write)(&hsmp_sock_rwsem);
|
||||
|
||||
/*
|
||||
* Clear this socket's dev so hsmp_send_message() rejects it before
|
||||
* devres unmaps the mailbox. On a non-final unbind the socket array
|
||||
* stays alive, so without this a later message to this index would
|
||||
* reach an unmapped iomem region.
|
||||
*/
|
||||
sock->dev = NULL;
|
||||
|
||||
kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release);
|
||||
}
|
||||
|
||||
static struct platform_driver amd_hsmp_driver = {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,17 @@
|
|||
#include <asm/amd/hsmp.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/nospec.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "hsmp.h"
|
||||
|
||||
|
|
@ -40,6 +47,17 @@
|
|||
|
||||
static struct hsmp_plat_device hsmp_pdev;
|
||||
|
||||
/*
|
||||
* Gates the AMD HSMP data plane against socket bring-up and teardown.
|
||||
*
|
||||
* hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads
|
||||
* run concurrently. Probe and remove take it for write: probe brings sockets
|
||||
* up (running the mailbox handshake via hsmp_send_message_locked()) and remove
|
||||
* tears them down, both excluding and draining the data plane.
|
||||
*/
|
||||
DECLARE_RWSEM(hsmp_sock_rwsem);
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP");
|
||||
|
||||
/*
|
||||
* Send a message to the HSMP port via PCI-e config space registers
|
||||
* or by writing to MMIO space.
|
||||
|
|
@ -182,29 +200,33 @@ static int validate_message(struct hsmp_message *msg)
|
|||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Some older HSMP SET messages are updated to add GET in the same message.
|
||||
* In these messages, GET returns the current value and SET also returns
|
||||
* the successfully set value. To support this GET and SET in same message
|
||||
* while maintaining backward compatibility for the HSMP users,
|
||||
* hsmp_msg_desc_table[] indicates only maximum allowed response_sz.
|
||||
* As the HSMP protocol evolves, newer platforms may define more
|
||||
* response arguments for existing messages. Use an upper-bound
|
||||
* check so that older userspace callers requesting fewer response
|
||||
* words than what the current hsmp_msg_desc_table[] defines are
|
||||
* still accepted, while rejecting requests that exceed the
|
||||
* hardware capability.
|
||||
*/
|
||||
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) {
|
||||
if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
/* only HSMP_SET or HSMP_GET messages go through this strict check */
|
||||
if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
|
||||
return -EINVAL;
|
||||
}
|
||||
if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hsmp_send_message(struct hsmp_message *msg)
|
||||
/*
|
||||
* Core message send. The caller must hold hsmp_sock_rwsem: the data plane
|
||||
* takes it for read so many messages run concurrently, while the probe-time
|
||||
* senders run under the write lock taken by probe. Holding it here serializes
|
||||
* every message against socket teardown, which also holds it for write.
|
||||
*/
|
||||
static int hsmp_send_message_locked(struct hsmp_message *msg)
|
||||
{
|
||||
struct hsmp_socket *sock;
|
||||
unsigned int sock_ind;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&hsmp_sock_rwsem);
|
||||
|
||||
if (!msg)
|
||||
return -EINVAL;
|
||||
ret = validate_message(msg);
|
||||
|
|
@ -223,6 +245,20 @@ int hsmp_send_message(struct hsmp_message *msg)
|
|||
sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets);
|
||||
sock = &hsmp_pdev.sock[sock_ind];
|
||||
|
||||
/*
|
||||
* A slot exists for every possible socket, but it is only usable once
|
||||
* that socket has actually been probed. Reject messages aimed at a
|
||||
* socket that was never brought up or is still in bring-up, so we never
|
||||
* operate on a zero-initialized semaphore or an unmapped mailbox. A
|
||||
* non-NULL dev also guarantees virt_base_addr, the mailbox offsets and
|
||||
* the semaphore are visible.
|
||||
*
|
||||
* Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev)
|
||||
* in hsmp_parse_acpi_table().
|
||||
*/
|
||||
if (!smp_load_acquire(&sock->dev))
|
||||
return -ENODEV;
|
||||
|
||||
ret = down_interruptible(&sock->hsmp_sem);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
|
@ -233,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg)
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int hsmp_send_message(struct hsmp_message *msg)
|
||||
{
|
||||
/*
|
||||
* Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue
|
||||
* messages from here. Take hsmp_sock_rwsem for read so messages run
|
||||
* concurrently with each other but are drained and kept out while
|
||||
* probe/remove hold it for write to tear a socket down.
|
||||
*/
|
||||
guard(rwsem_read)(&hsmp_sock_rwsem);
|
||||
|
||||
return hsmp_send_message_locked(msg);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP");
|
||||
|
||||
int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args)
|
||||
|
|
@ -273,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value)
|
|||
msg.args[0] = value;
|
||||
msg.sock_ind = sock_ind;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
ret = hsmp_send_message_locked(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -301,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg)
|
|||
return false;
|
||||
}
|
||||
|
||||
long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
|
||||
static long hsmp_ioctl_msg(struct file *fp, unsigned long arg)
|
||||
{
|
||||
int __user *arguser = (int __user *)arg;
|
||||
struct hsmp_message msg = { 0 };
|
||||
|
|
@ -370,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
|
|||
return 0;
|
||||
}
|
||||
|
||||
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
|
||||
static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf,
|
||||
size_t size);
|
||||
|
||||
/*
|
||||
* Fetch the firmware metric (telemetry) table for the requested socket and
|
||||
* copy it to the userspace buffer described by the request.
|
||||
*
|
||||
* The metric table size is variable across HSMP protocol versions and on
|
||||
* Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer
|
||||
* size, which may be anything up to the size firmware reported for this
|
||||
* socket's table.
|
||||
*/
|
||||
static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg)
|
||||
{
|
||||
void *kbuf __free(kvfree) = NULL;
|
||||
void __user *arguser = (void __user *)arg;
|
||||
struct hsmp_telemetry_data req;
|
||||
struct hsmp_socket *sock;
|
||||
void __user *user_buf;
|
||||
size_t tbl_size;
|
||||
unsigned int sock_ind;
|
||||
int ret;
|
||||
|
||||
/* Telemetry data is read-only; require read access on the fd. */
|
||||
if (!(fp->f_mode & FMODE_READ))
|
||||
return -EPERM;
|
||||
|
||||
if (copy_from_user(&req, arguser, sizeof(req)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Reserved fields must be zero so future kernels can safely
|
||||
* repurpose them without breaking already-deployed userspace.
|
||||
*/
|
||||
if (req.reserved)
|
||||
return -EINVAL;
|
||||
|
||||
user_buf = u64_to_user_ptr(req.buf);
|
||||
|
||||
/*
|
||||
* /dev/hsmp is a singleton character device that outlives an individual
|
||||
* socket unbind, so an ioctl on an already-open fd can run concurrently
|
||||
* with socket teardown. Hold hsmp_sock_rwsem for read across the socket
|
||||
* lookup, the checks on its metric-table state and the read itself:
|
||||
* probe and remove take the same lock for write, so they cannot free the
|
||||
* socket array, unmap the table or destroy the per-socket mutex while
|
||||
* this runs.
|
||||
*
|
||||
* The lock is dropped before the copy_to_user() below. Faulting in the
|
||||
* destination can block indefinitely on a userfaultfd-backed buffer,
|
||||
* which would leave a socket unbind waiting for the write lock.
|
||||
*/
|
||||
scoped_guard(rwsem_read, &hsmp_sock_rwsem) {
|
||||
if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Sanitize the user-controlled socket index against speculative
|
||||
* execution. The bounds check above retires the out-of-range
|
||||
* case with -ENODEV, but a mispredicted branch can still let the
|
||||
* CPU speculatively use sock_ind as an index into
|
||||
* hsmp_pdev.sock[] and pull arbitrary kernel memory into the
|
||||
* cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns
|
||||
* the bounds check into a data-flow clamp so the speculative
|
||||
* load is in-range too.
|
||||
*/
|
||||
sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets);
|
||||
sock = &hsmp_pdev.sock[sock_ind];
|
||||
if (!sock->metric_tbl_addr)
|
||||
return -ENODEV;
|
||||
|
||||
tbl_size = sock->metric_tbl_size;
|
||||
if (!tbl_size)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* A request shorter than the firmware table is served with the
|
||||
* leading @size bytes of the snapshot, so userspace built
|
||||
* against an older table layout keeps working on firmware that
|
||||
* grew the table. Asking for more than firmware provides is
|
||||
* rejected rather than short-written, so a caller can never
|
||||
* mistake a partial copy for a full one.
|
||||
*/
|
||||
if (!req.size || req.size > tbl_size)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The bounce buffer is overwritten in full by memcpy_fromio()
|
||||
* inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid
|
||||
* the zeroing cost of kvzalloc() on the ~13 KB allocation done
|
||||
* on every ioctl call.
|
||||
*/
|
||||
kbuf = kvmalloc(tbl_size, GFP_KERNEL);
|
||||
if (!kbuf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size);
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (copy_to_user(user_buf, kbuf, req.size))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case HSMP_IOCTL_CMD:
|
||||
return hsmp_ioctl_msg(fp, arg);
|
||||
case HSMP_IOCTL_GET_TELEMETRY_DATA:
|
||||
return hsmp_ioctl_get_telemetry(fp, arg);
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping
|
||||
* and its metric_read_lock alive: probe and remove take the same lock for
|
||||
* write while they bring sockets up and tear them down.
|
||||
*/
|
||||
static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf,
|
||||
size_t size)
|
||||
{
|
||||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&hsmp_sock_rwsem);
|
||||
|
||||
if (!sock || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -383,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Do not support lseek(), also don't allow more than the size of metric table */
|
||||
if (size != sizeof(struct hsmp_metric_table)) {
|
||||
if (size != sock->metric_tbl_size) {
|
||||
dev_err(sock->dev, "Wrong buffer size\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -392,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
|
|||
msg.msg_id = HSMP_GET_METRIC_TABLE;
|
||||
msg.sock_ind = sock->sock_ind;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
/*
|
||||
* HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared
|
||||
* metric DRAM region, which is then copied out below. Hold the
|
||||
* per-socket lock across the fill-and-copy so concurrent readers of the
|
||||
* same socket cannot return a torn snapshot.
|
||||
*/
|
||||
guard(mutex)(&sock->metric_read_lock);
|
||||
|
||||
ret = hsmp_send_message_locked(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
memcpy_fromio(buf, sock->metric_tbl_addr, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
|
||||
{
|
||||
guard(rwsem_read)(&hsmp_sock_rwsem);
|
||||
|
||||
return hsmp_metric_tbl_read_locked(sock, buf, size);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP");
|
||||
|
||||
void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev)
|
||||
{
|
||||
u16 i;
|
||||
|
||||
for (i = 0; i < pdev->num_sockets; i++)
|
||||
mutex_init(&pdev->sock[i].metric_read_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP");
|
||||
|
||||
void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev)
|
||||
{
|
||||
u16 i;
|
||||
|
||||
for (i = 0; i < pdev->num_sockets; i++)
|
||||
mutex_destroy(&pdev->sock[i].metric_read_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP");
|
||||
|
||||
void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev)
|
||||
{
|
||||
struct hsmp_socket *sock;
|
||||
u16 i;
|
||||
|
||||
for (i = 0; i < pdev->num_sockets; i++) {
|
||||
sock = &pdev->sock[i];
|
||||
if (sock->metric_tbl_addr) {
|
||||
iounmap(sock->metric_tbl_addr);
|
||||
sock->metric_tbl_addr = NULL;
|
||||
}
|
||||
sock->metric_tbl_size = 0;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP");
|
||||
|
||||
int hsmp_get_tbl_dram_base(u16 sock_ind)
|
||||
{
|
||||
struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind];
|
||||
struct hsmp_message msg = { 0 };
|
||||
phys_addr_t dram_addr;
|
||||
size_t tbl_size;
|
||||
int ret;
|
||||
|
||||
msg.sock_ind = sock_ind;
|
||||
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
|
||||
msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
ret = hsmp_send_message_locked(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -425,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind)
|
|||
dev_err(sock->dev, "Invalid DRAM address for metric table\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr,
|
||||
sizeof(struct hsmp_metric_table));
|
||||
/*
|
||||
* The ACPI socket array is shared across sockets and outlives a
|
||||
* per-socket unbind, so metric_tbl_addr may hold a mapping from an
|
||||
* earlier bind of this socket. Unmap it before remapping so an
|
||||
* unbind/rebind cycle does not leak a metric-table mapping. This runs
|
||||
* during probe before the metric sysfs attribute is exposed, so no
|
||||
* reader can be using it.
|
||||
*/
|
||||
if (sock->metric_tbl_addr) {
|
||||
iounmap(sock->metric_tbl_addr);
|
||||
sock->metric_tbl_addr = NULL;
|
||||
}
|
||||
sock->metric_tbl_size = 0;
|
||||
|
||||
/* SMU returns table size from Family 1Ah Model 50h and forward */
|
||||
if (msg.args[2])
|
||||
tbl_size = msg.args[2];
|
||||
else
|
||||
tbl_size = sizeof(struct hsmp_metric_table);
|
||||
|
||||
sock->metric_tbl_addr = ioremap(dram_addr, tbl_size);
|
||||
if (!sock->metric_tbl_addr) {
|
||||
dev_err(sock->dev, "Failed to ioremap metric table addr\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
sock->metric_tbl_size = tbl_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP");
|
||||
|
|
@ -444,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind)
|
|||
msg.sock_ind = sock_ind;
|
||||
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
ret = hsmp_send_message_locked(&msg);
|
||||
if (!ret)
|
||||
hsmp_pdev.proto_ver = msg.args[0];
|
||||
|
||||
|
|
@ -463,6 +710,14 @@ int hsmp_misc_register(struct device *dev)
|
|||
hsmp_pdev.mdev.name = HSMP_CDEV_NAME;
|
||||
hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR;
|
||||
hsmp_pdev.mdev.fops = &hsmp_fops;
|
||||
/*
|
||||
* The caller chooses the parent. The platform driver has a single
|
||||
* device whose lifetime matches /dev/hsmp and parents it there. The
|
||||
* ACPI driver passes NULL: its /dev/hsmp is a singleton shared by
|
||||
* per-socket devices that can be unbound individually and out of order,
|
||||
* so parenting it to one would leave it attached to an already-removed
|
||||
* device.
|
||||
*/
|
||||
hsmp_pdev.mdev.parent = dev;
|
||||
hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME;
|
||||
hsmp_pdev.mdev.mode = 0644;
|
||||
|
|
@ -474,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP");
|
|||
void hsmp_misc_deregister(void)
|
||||
{
|
||||
misc_deregister(&hsmp_pdev.mdev);
|
||||
hsmp_pdev.mdev.this_device = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP");
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,12 @@
|
|||
#include <linux/hwmon.h>
|
||||
#include <linux/kconfig.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define HSMP_METRICS_TABLE_NAME "metrics_bin"
|
||||
|
||||
|
|
@ -27,7 +30,7 @@
|
|||
#define HSMP_DEVNODE_NAME "hsmp"
|
||||
#define ACPI_HSMP_DEVICE_HID "AMDI0097"
|
||||
|
||||
#define DRIVER_VERSION "2.5"
|
||||
#define DRIVER_VERSION "2.6"
|
||||
|
||||
struct hsmp_mbaddr_info {
|
||||
u32 base_addr;
|
||||
|
|
@ -41,8 +44,12 @@ struct hsmp_socket {
|
|||
struct bin_attribute hsmp_attr;
|
||||
struct hsmp_mbaddr_info mbinfo;
|
||||
void __iomem *metric_tbl_addr;
|
||||
/* Size of the region mapped at @metric_tbl_addr, as reported by SMU */
|
||||
size_t metric_tbl_size;
|
||||
void __iomem *virt_base_addr;
|
||||
struct semaphore hsmp_sem;
|
||||
/* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */
|
||||
struct mutex metric_read_lock;
|
||||
char name[HSMP_ATTR_GRP_NAME_SIZE];
|
||||
struct device *dev;
|
||||
u16 sock_ind;
|
||||
|
|
@ -54,7 +61,6 @@ struct hsmp_plat_device {
|
|||
struct hsmp_socket *sock;
|
||||
u32 proto_ver;
|
||||
u16 num_sockets;
|
||||
bool is_probed;
|
||||
};
|
||||
|
||||
int hsmp_cache_proto_ver(u16 sock_ind);
|
||||
|
|
@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg);
|
|||
void hsmp_misc_deregister(void);
|
||||
int hsmp_misc_register(struct device *dev);
|
||||
int hsmp_get_tbl_dram_base(u16 sock_ind);
|
||||
void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev);
|
||||
void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev);
|
||||
void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev);
|
||||
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size);
|
||||
struct hsmp_plat_device *get_hsmp_pdev(void);
|
||||
#if IS_ENABLED(CONFIG_HWMON)
|
||||
|
|
@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind);
|
|||
static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; }
|
||||
#endif
|
||||
int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args);
|
||||
|
||||
/*
|
||||
* Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and
|
||||
* remove take it for write to bring sockets up and tear them down.
|
||||
*/
|
||||
extern struct rw_semaphore hsmp_sock_rwsem;
|
||||
#endif /* HSMP_H */
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
if (attr != hwmon_power_cap)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
msg.num_args = 1;
|
||||
msg.args[0] = val / MICROWATT_PER_MILLIWATT;
|
||||
msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT;
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/build_bug.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/kconfig.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include <asm/amd/node.h>
|
||||
|
|
@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The socket array is devm-managed and freed by the driver core, but the
|
||||
* metric-table DRAM regions are mapped with plain ioremap() during probe and
|
||||
* the per-socket mutexes need an explicit mutex_destroy(), neither of which
|
||||
* devres covers.
|
||||
*
|
||||
* Take the data-plane rwsem for write to drain any in-flight
|
||||
* hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop
|
||||
* the global socket pointer, all before devres frees the array. Registered as
|
||||
* a devres action so it runs on both remove and probe failure.
|
||||
*/
|
||||
static void hsmp_pltdrv_release(void *data)
|
||||
{
|
||||
guard(rwsem_write)(&hsmp_sock_rwsem);
|
||||
hsmp_unmap_metric_tbls(hsmp_pdev);
|
||||
hsmp_destroy_metric_read_locks(hsmp_pdev);
|
||||
hsmp_pdev->sock = NULL;
|
||||
}
|
||||
|
||||
static int hsmp_pltdrv_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
|
@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev)
|
|||
if (!hsmp_pdev->sock)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = init_platform_device(&pdev->dev);
|
||||
hsmp_init_metric_read_locks(hsmp_pdev);
|
||||
|
||||
ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* init_platform_device() runs the mailbox handshake via the probe-only
|
||||
* senders, which issue messages through hsmp_send_message_locked() and
|
||||
* so require hsmp_sock_rwsem held. Hold it for write, matching probe's
|
||||
* role as a socket bring-up path. The lock is not held across
|
||||
* devm_add_action_or_reset() above so the release action, which also
|
||||
* takes it for write, does not deadlock if that registration fails.
|
||||
*/
|
||||
scoped_guard(rwsem_write, &hsmp_sock_rwsem)
|
||||
ret = init_platform_device(&pdev->dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
|
|||
struct amd_pmc_dev *dev = filp->f_inode->i_private;
|
||||
u32 fsize, num_samples, val, stb_rdptr_offset = 0;
|
||||
struct amd_stb_v2_data *stb_data_arr;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
/* Write dummy postcode while reading the STB buffer */
|
||||
ret = amd_stb_write(dev, AMD_PMC_STB_DUMMY_PC);
|
||||
|
|
@ -176,22 +176,24 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
|
|||
* the enhanced dram size. Note that we land here only for the
|
||||
* platforms that support enhanced dram size reporting.
|
||||
*/
|
||||
if (dump_custom_stb)
|
||||
return amd_stb_handle_efr(filp);
|
||||
if (dump_custom_stb) {
|
||||
ret = amd_stb_handle_efr(filp);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Get the num_samples to calculate the last push location */
|
||||
ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->stb_arg.s2d_msg_id, true);
|
||||
/* Clear msg_port for other SMU operation */
|
||||
dev->msg_port = MSG_PORT_PMC;
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret);
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX);
|
||||
stb_data_arr = kmalloc_flex(*stb_data_arr, data, fsize);
|
||||
if (!stb_data_arr)
|
||||
return -ENOMEM;
|
||||
if (!stb_data_arr) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
stb_data_arr->size = fsize;
|
||||
|
||||
|
|
@ -214,7 +216,10 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
|
|||
|
||||
filp->private_data = stb_data_arr;
|
||||
|
||||
return 0;
|
||||
out:
|
||||
/* Restore the default message port for subsequent SMU operations */
|
||||
dev->msg_port = MSG_PORT_PMC;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t amd_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size,
|
||||
|
|
@ -289,15 +294,12 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev)
|
|||
u32 phys_addr_low, phys_addr_hi;
|
||||
u64 stb_phys_addr;
|
||||
u32 size = 0;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
if (!enable_stb)
|
||||
return 0;
|
||||
|
||||
if (amd_is_stb_supported(dev)) {
|
||||
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
|
||||
&amd_stb_debugfs_fops_v2);
|
||||
} else {
|
||||
if (!amd_is_stb_supported(dev)) {
|
||||
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
|
||||
&amd_stb_debugfs_fops);
|
||||
return 0;
|
||||
|
|
@ -306,27 +308,52 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev)
|
|||
/* Spill to DRAM feature uses separate SMU message port */
|
||||
dev->msg_port = MSG_PORT_S2D;
|
||||
|
||||
amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true);
|
||||
if (size != S2D_TELEMETRY_BYTES_MAX)
|
||||
return -EIO;
|
||||
ret = amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true);
|
||||
if (ret)
|
||||
goto out;
|
||||
if (size != S2D_TELEMETRY_BYTES_MAX) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Get DRAM size */
|
||||
ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true);
|
||||
if (ret || !dev->dram_size)
|
||||
/* Get DRAM size; fall back to the default if the query fails */
|
||||
if (amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true) ||
|
||||
!dev->dram_size)
|
||||
dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX;
|
||||
|
||||
/* Get STB DRAM address */
|
||||
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->stb_arg.s2d_msg_id, true);
|
||||
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->stb_arg.s2d_msg_id, true);
|
||||
ret = amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low,
|
||||
dev->stb_arg.s2d_msg_id, true);
|
||||
if (ret)
|
||||
goto out;
|
||||
ret = amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi,
|
||||
dev->stb_arg.s2d_msg_id, true);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
|
||||
|
||||
/* Clear msg_port for other SMU operation */
|
||||
dev->msg_port = MSG_PORT_PMC;
|
||||
if (!stb_phys_addr) {
|
||||
dev_err(dev->dev, "S2D phys addr query returned invalid address\n");
|
||||
ret = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size);
|
||||
if (!dev->stb_virt_addr)
|
||||
return -ENOMEM;
|
||||
if (!dev->stb_virt_addr) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
/*
|
||||
* Only expose stb_read once the buffer is mapped; otherwise a read
|
||||
* faults on a NULL dev->stb_virt_addr, now that a failed STB init no
|
||||
* longer aborts probe.
|
||||
*/
|
||||
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
|
||||
&amd_stb_debugfs_fops_v2);
|
||||
|
||||
out:
|
||||
/* Restore the default message port for subsequent SMU operations */
|
||||
dev->msg_port = MSG_PORT_PMC;
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ static const struct dmi_system_id fwbug_list[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_NAME, "20XK"),
|
||||
}
|
||||
},
|
||||
{
|
||||
.ident = "T14 Gen2 AMD",
|
||||
.driver_data = &quirk_s2idle_spurious_8042,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "20XL"),
|
||||
}
|
||||
},
|
||||
{
|
||||
.ident = "T14 Gen1 AMD",
|
||||
.driver_data = &quirk_s2idle_spurious_8042,
|
||||
|
|
|
|||
|
|
@ -939,9 +939,16 @@ static int amd_pmc_probe(struct platform_device *pdev)
|
|||
}
|
||||
|
||||
amd_pmc_dbgfs_register(dev);
|
||||
|
||||
/*
|
||||
* STB is an optional debug facility (enable_stb); a failure to set it
|
||||
* up must not stop the rest of the driver - most importantly the s0i3
|
||||
* LPS0 handler - from working, so treat it as non-fatal.
|
||||
*/
|
||||
err = amd_stb_s2d_init(dev);
|
||||
if (err)
|
||||
goto err_pci_dev_put;
|
||||
dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n",
|
||||
err);
|
||||
|
||||
if (IS_ENABLED(CONFIG_AMD_MP2_STB))
|
||||
amd_mp2_stb_init(dev);
|
||||
|
|
|
|||
|
|
@ -30,3 +30,13 @@ config AMD_PMF_DEBUG
|
|||
in the PMF config store.
|
||||
|
||||
Say Y here to enable more debug logs and Say N here if you are not sure.
|
||||
|
||||
config AMD_PMF_UTIL_SUPPORT
|
||||
bool "AMD PMF Util layer support"
|
||||
depends on AMD_PMF
|
||||
help
|
||||
Enabling this option provides a character device for userspace to capture
|
||||
PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic
|
||||
Power Slider AC/DC) along with PMF metrics from the AMD PMF driver.
|
||||
|
||||
Say Y here to enable it and Say N here if you are not sure.
|
||||
|
|
|
|||
|
|
@ -7,4 +7,6 @@
|
|||
obj-$(CONFIG_AMD_PMF) += amd-pmf.o
|
||||
amd-pmf-y := core.o acpi.o sps.o \
|
||||
auto-mode.o cnqf.o \
|
||||
tee-if.o spc.o
|
||||
tee-if.o spc.o metrics.o
|
||||
# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled
|
||||
amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@
|
|||
#include <linux/array_size.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/string.h>
|
||||
#include <asm/amd/node.h>
|
||||
#include "pmf.h"
|
||||
|
||||
|
|
@ -26,6 +26,13 @@
|
|||
#define AMD_PMF_REGISTER_RESPONSE 0xA78
|
||||
#define AMD_PMF_REGISTER_ARGUMENT 0xA58
|
||||
|
||||
/* PMF-SMU communication registers for 1AH_M80H */
|
||||
#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04
|
||||
#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08
|
||||
#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C
|
||||
#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC
|
||||
#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0
|
||||
|
||||
/* Base address of SMU for mapping physical address to virtual address */
|
||||
#define AMD_PMF_MAPPING_SIZE 0x01000
|
||||
#define AMD_PMF_BASE_ADDR_OFFSET 0x10000
|
||||
|
|
@ -48,7 +55,7 @@
|
|||
#define DELAY_MAX_US 3000
|
||||
|
||||
/* override Metrics Table sample size time (in ms) */
|
||||
static int metrics_table_loop_ms = 1000;
|
||||
int metrics_table_loop_ms = 1000;
|
||||
module_param(metrics_table_loop_ms, int, 0644);
|
||||
MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)");
|
||||
|
||||
|
|
@ -61,7 +68,15 @@ static bool smart_pc_support = true;
|
|||
module_param(smart_pc_support, bool, 0444);
|
||||
MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
|
||||
|
||||
static struct device *pmf_device;
|
||||
static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
switch (pdev->cpu_id) {
|
||||
case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
|
||||
{
|
||||
|
|
@ -131,37 +146,6 @@ int amd_pmf_get_power_source(void)
|
|||
return POWER_SOURCE_DC;
|
||||
}
|
||||
|
||||
static void amd_pmf_get_metrics(struct work_struct *work)
|
||||
{
|
||||
struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
|
||||
ktime_t time_elapsed_ms;
|
||||
int socket_power;
|
||||
|
||||
guard(mutex)(&dev->update_mutex);
|
||||
|
||||
/* Transfer table contents */
|
||||
memset(dev->buf, 0, sizeof(dev->m_table));
|
||||
amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
|
||||
memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
|
||||
|
||||
time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
|
||||
/* Calculate the avg SoC power consumption */
|
||||
socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
|
||||
|
||||
if (dev->amt_enabled) {
|
||||
/* Apply the Auto Mode transition */
|
||||
amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
|
||||
}
|
||||
|
||||
if (dev->cnqf_enabled) {
|
||||
/* Apply the CnQF transition */
|
||||
amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
|
||||
}
|
||||
|
||||
dev->start_time = ktime_to_ms(ktime_get());
|
||||
schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
|
||||
}
|
||||
|
||||
static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
|
||||
{
|
||||
return ioread32(dev->regbase + reg_offset);
|
||||
|
|
@ -176,13 +160,19 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
|
|||
{
|
||||
u32 value;
|
||||
|
||||
value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE);
|
||||
value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg);
|
||||
dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value);
|
||||
|
||||
value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
|
||||
value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]);
|
||||
dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value);
|
||||
if (amd_pmf_supports_accumulator_metrics(dev)) {
|
||||
value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]);
|
||||
dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value);
|
||||
value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]);
|
||||
dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value);
|
||||
}
|
||||
|
||||
value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE);
|
||||
value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg);
|
||||
dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +198,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
|
|||
guard(mutex)(&dev->lock);
|
||||
|
||||
/* Wait until we get a valid response */
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg,
|
||||
val, val != 0, PMF_MSG_DELAY_MIN_US,
|
||||
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
|
||||
if (rc) {
|
||||
|
|
@ -217,16 +207,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
|
|||
}
|
||||
|
||||
/* Write zero to response register */
|
||||
amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0);
|
||||
amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0);
|
||||
|
||||
/* Write argument into argument register */
|
||||
amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg);
|
||||
amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg);
|
||||
|
||||
/* Write message ID to message ID register */
|
||||
amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message);
|
||||
amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message);
|
||||
|
||||
/* Wait until we get a valid response */
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
|
||||
rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg,
|
||||
val, val != 0, PMF_MSG_DELAY_MIN_US,
|
||||
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
|
||||
if (rc) {
|
||||
|
|
@ -239,7 +229,14 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
|
|||
if (get) {
|
||||
/* PMFW may take longer time to return back the data */
|
||||
usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US);
|
||||
*data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
|
||||
*data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]);
|
||||
if (amd_pmf_supports_accumulator_metrics(dev) &&
|
||||
message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) {
|
||||
dev->dram_addr.hi = amd_pmf_reg_read(dev,
|
||||
dev->smu_regs->arg_reg[1]);
|
||||
dev->dram_addr.size = amd_pmf_reg_read(dev,
|
||||
dev->smu_regs->arg_reg[2]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AMD_PMF_RESULT_CMD_REJECT_BUSY:
|
||||
|
|
@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
|
|||
return rc;
|
||||
}
|
||||
|
||||
static const struct pci_device_id pmf_pci_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) },
|
||||
{ }
|
||||
/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */
|
||||
static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = {
|
||||
.msg_reg = AMD_PMF_REGISTER_MESSAGE,
|
||||
.resp_reg = AMD_PMF_REGISTER_RESPONSE,
|
||||
.arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 },
|
||||
};
|
||||
|
||||
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
|
||||
{
|
||||
u64 phys_addr;
|
||||
u32 hi, low;
|
||||
/* 1AH_M80H uses an extended mailbox with three argument registers */
|
||||
static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = {
|
||||
.msg_reg = AMD_PMF_REGISTER_MESSAGE_V2,
|
||||
.resp_reg = AMD_PMF_REGISTER_RESPONSE_V2,
|
||||
.arg_reg = {
|
||||
AMD_PMF_REGISTER_ARGUMENT0_V2,
|
||||
AMD_PMF_REGISTER_ARGUMENT1_V2,
|
||||
AMD_PMF_REGISTER_ARGUMENT2_V2,
|
||||
},
|
||||
};
|
||||
|
||||
/* Get Metrics Table Address */
|
||||
if (alloc_buffer) {
|
||||
switch (dev->cpu_id) {
|
||||
case AMD_CPU_ID_PS:
|
||||
case AMD_CPU_ID_RMB:
|
||||
dev->mtable_size = sizeof(dev->m_table);
|
||||
break;
|
||||
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
|
||||
dev->mtable_size = sizeof(dev->m_table_v2);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id);
|
||||
}
|
||||
|
||||
dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
|
||||
if (!dev->buf)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
phys_addr = virt_to_phys(dev->buf);
|
||||
hi = phys_addr >> 32;
|
||||
low = phys_addr & GENMASK(31, 0);
|
||||
|
||||
amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
|
||||
amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
|
||||
|
||||
ret = amd_pmf_set_dram_addr(dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Start collecting the metrics data after a small delay
|
||||
* or else, we might end up getting stale values from PMFW.
|
||||
*/
|
||||
schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
switch (pdev->cpu_id) {
|
||||
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
guard(mutex)(&dev->metrics_mutex);
|
||||
|
||||
ret = is_npu_metrics_supported(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = amd_pmf_set_dram_addr(dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(dev->buf, 0, dev->mtable_size);
|
||||
|
||||
/* Send SMU command to get NPU metrics */
|
||||
ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
|
||||
|
||||
data->npuclk_freq = dev->m_table_v2.npuclk_freq;
|
||||
for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
|
||||
data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
|
||||
data->npu_power = dev->m_table_v2.npu_power;
|
||||
data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
|
||||
data->npu_reads = dev->m_table_v2.npu_reads;
|
||||
data->npu_writes = dev->m_table_v2.npu_writes;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
|
||||
{
|
||||
struct amd_pmf_dev *pdev;
|
||||
|
||||
if (!info)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pmf_device)
|
||||
return -ENODEV;
|
||||
|
||||
pdev = dev_get_drvdata(pmf_device);
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
return amd_pmf_get_smu_metrics(pdev, info);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
|
||||
static const struct pci_device_id pmf_pci_ids[] = {
|
||||
{ PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) },
|
||||
{ PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) },
|
||||
{ PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) },
|
||||
{ PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) },
|
||||
{ PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
|
|
@ -536,6 +433,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
|
|||
}
|
||||
}
|
||||
|
||||
static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev)
|
||||
{
|
||||
const struct pci_device_id *id;
|
||||
|
||||
id = pci_match_id(pmf_pci_ids, rdev);
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
|
||||
pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id amd_pmf_acpi_ids[] = {
|
||||
{"AMDI0100", 0x100},
|
||||
{"AMDI0102", 0},
|
||||
|
|
@ -543,6 +453,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = {
|
|||
{"AMDI0105", 0},
|
||||
{"AMDI0107", 0},
|
||||
{"AMDI0108", 0},
|
||||
{"AMDI0109", 0},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
|
||||
|
|
@ -624,6 +535,17 @@ static int amd_pmf_probe(struct platform_device *pdev)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
/* Populate smu_regs with SoC-specific SMU mailbox register offsets */
|
||||
err = amd_pmf_get_smu_mb_offset(dev, rdev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (amd_pmf_supports_accumulator_metrics(dev)) {
|
||||
err = amd_pmf_get_tbl_dram_addr(dev);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
apmf_acpi_init(dev);
|
||||
platform_set_drvdata(pdev, dev);
|
||||
amd_pmf_dbgfs_register(dev);
|
||||
|
|
@ -632,7 +554,11 @@ static int amd_pmf_probe(struct platform_device *pdev)
|
|||
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
|
||||
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
|
||||
|
||||
pmf_device = dev->dev;
|
||||
amd_pmf_set_device(dev->dev);
|
||||
|
||||
err = amd_pmf_cdev_register(dev);
|
||||
if (err)
|
||||
dev_warn(dev->dev, "failed to register util interface: %d\n", err);
|
||||
|
||||
dev_info(dev->dev, "registered PMF device successfully\n");
|
||||
|
||||
|
|
@ -643,6 +569,7 @@ static void amd_pmf_remove(struct platform_device *pdev)
|
|||
{
|
||||
struct amd_pmf_dev *dev = platform_get_drvdata(pdev);
|
||||
|
||||
amd_pmf_cdev_unregister();
|
||||
amd_pmf_deinit_features(dev);
|
||||
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
|
||||
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD);
|
||||
|
|
|
|||
330
drivers/platform/x86/amd/pmf/metrics.c
Normal file
330
drivers/platform/x86/amd/pmf/metrics.c
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* AMD Platform Management Framework Driver - Metrics Support
|
||||
*
|
||||
* Copyright (c) 2026, Advanced Micro Devices, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
* Patil Rajesh Reddy <Patil.Reddy@amd.com>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/device/devres.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/units.h>
|
||||
#include <linux/wordpart.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "pmf.h"
|
||||
|
||||
static struct device *pmf_device;
|
||||
|
||||
static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc)
|
||||
{
|
||||
u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT;
|
||||
|
||||
return min_t(u16, val, U16_MAX);
|
||||
}
|
||||
|
||||
static u16 amd_pmf_q10_acc_to_int(u64 avg_acc)
|
||||
{
|
||||
u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024);
|
||||
|
||||
return min_t(u16, val, U16_MAX);
|
||||
}
|
||||
|
||||
static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter,
|
||||
u64 curr_value, u64 prev_value)
|
||||
{
|
||||
u32 counter_diff;
|
||||
u64 val_diff;
|
||||
|
||||
/* Check for counter reset */
|
||||
if (curr_counter <= prev_counter)
|
||||
return 0;
|
||||
|
||||
counter_diff = curr_counter - prev_counter;
|
||||
|
||||
if (curr_value < prev_value)
|
||||
return 0;
|
||||
|
||||
val_diff = curr_value - prev_value;
|
||||
|
||||
return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff);
|
||||
}
|
||||
|
||||
int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD,
|
||||
ARG_NONE, &dev->dram_addr.lo);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo;
|
||||
dev->mtable_size = dev->dram_addr.size;
|
||||
|
||||
if (dev->mtable_size != sizeof(dev->mtable_v3)) {
|
||||
dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n",
|
||||
dev->dram_addr.size, sizeof(dev->mtable_v3));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size);
|
||||
if (!dev->metrics_table_virt) {
|
||||
dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n");
|
||||
dev->metrics_table_phys = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!dev->metrics_table_virt) {
|
||||
dev_err(dev->dev, "Metrics DRAM not mapped\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Send command to PMFW to update metrics table log sample */
|
||||
ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_pmf_get_metrics(struct work_struct *work)
|
||||
{
|
||||
struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
|
||||
ktime_t time_elapsed_ms;
|
||||
int socket_power;
|
||||
|
||||
guard(mutex)(&dev->update_mutex);
|
||||
|
||||
/* Transfer table contents */
|
||||
memset(dev->buf, 0, sizeof(dev->m_table));
|
||||
amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
|
||||
memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
|
||||
|
||||
time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
|
||||
/* Calculate the avg SoC power consumption */
|
||||
socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
|
||||
|
||||
if (dev->amt_enabled) {
|
||||
/* Apply the Auto Mode transition */
|
||||
amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
|
||||
}
|
||||
|
||||
if (dev->cnqf_enabled) {
|
||||
/* Apply the CnQF transition */
|
||||
amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
|
||||
}
|
||||
|
||||
dev->start_time = ktime_to_ms(ktime_get());
|
||||
schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
|
||||
}
|
||||
|
||||
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
|
||||
{
|
||||
u64 phys_addr;
|
||||
u32 hi, low;
|
||||
|
||||
/* Get Metrics Table Address */
|
||||
if (alloc_buffer) {
|
||||
switch (dev->cpu_id) {
|
||||
case AMD_CPU_ID_PS:
|
||||
case AMD_CPU_ID_RMB:
|
||||
dev->mtable_size = sizeof(dev->m_table);
|
||||
break;
|
||||
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
|
||||
dev->mtable_size = sizeof(dev->m_table_v2);
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id);
|
||||
}
|
||||
|
||||
dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
|
||||
if (!dev->buf)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
phys_addr = virt_to_phys(dev->buf);
|
||||
hi = upper_32_bits(phys_addr);
|
||||
low = lower_32_bits(phys_addr);
|
||||
|
||||
amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
|
||||
amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
|
||||
|
||||
ret = amd_pmf_set_dram_addr(dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Start collecting the metrics data after a small delay
|
||||
* or else, we might end up getting stale values from PMFW.
|
||||
*/
|
||||
schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void amd_pmf_set_device(struct device *p_device)
|
||||
{
|
||||
pmf_device = p_device;
|
||||
}
|
||||
|
||||
static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
switch (pdev->cpu_id) {
|
||||
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev,
|
||||
struct amd_pmf_npu_metrics *data)
|
||||
{
|
||||
struct amd_pmf_metrics_iod *curr, *prev;
|
||||
u64 val;
|
||||
int i;
|
||||
|
||||
curr = &dev->mtable_v3.iod;
|
||||
prev = &dev->prev_metrics.iod;
|
||||
|
||||
val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
|
||||
curr->npu_temp_acc, prev->npu_temp_acc);
|
||||
data->npu_temp = amd_pmf_q10_acc_to_int(val);
|
||||
val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
|
||||
curr->npu_power_acc, prev->npu_power_acc);
|
||||
data->npu_power = amd_pmf_q10_acc_to_mW(val);
|
||||
val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
|
||||
curr->npuhclk_freq_eff_acc,
|
||||
prev->npuhclk_freq_eff_acc);
|
||||
data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val);
|
||||
val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
|
||||
curr->aieclk_freq_eff_acc,
|
||||
prev->aieclk_freq_eff_acc);
|
||||
data->npuclk_freq = amd_pmf_q10_acc_to_int(val);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) {
|
||||
val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
|
||||
curr->npu_busy_acc[i],
|
||||
prev->npu_busy_acc[i]);
|
||||
data->npu_busy[i] = amd_pmf_q10_acc_to_int(val);
|
||||
}
|
||||
}
|
||||
|
||||
static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
guard(mutex)(&dev->metrics_mutex);
|
||||
|
||||
ret = is_npu_metrics_supported(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(data, 0, sizeof(*data));
|
||||
|
||||
switch (dev->cpu_id) {
|
||||
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
|
||||
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
|
||||
ret = amd_pmf_set_dram_addr(dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(dev->buf, 0, dev->mtable_size);
|
||||
|
||||
/* Send SMU command to get NPU metrics */
|
||||
ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
|
||||
|
||||
data->npuclk_freq = dev->m_table_v2.npuclk_freq;
|
||||
for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
|
||||
data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
|
||||
data->npu_power = dev->m_table_v2.npu_power;
|
||||
data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
|
||||
data->npu_reads = dev->m_table_v2.npu_reads;
|
||||
data->npu_writes = dev->m_table_v2.npu_writes;
|
||||
break;
|
||||
case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
|
||||
ret = amd_pmf_get_metrics_table_log_sample(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3));
|
||||
|
||||
/*
|
||||
* Ignore the first sample, as previous metrics is uninitialized (zero)
|
||||
* Metrics are calculated as:
|
||||
* metrics = current_metrics - previous_metrics
|
||||
* Skipping the initial sample ensures accurate delta calculations.
|
||||
*/
|
||||
if (!dev->npu_metrics_have_prev) {
|
||||
memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
|
||||
dev->npu_metrics_have_prev = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
amd_pmf_calculate_acc_npu_metrics(dev, data);
|
||||
memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
|
||||
{
|
||||
struct amd_pmf_dev *pdev;
|
||||
|
||||
if (!info)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pmf_device)
|
||||
return -ENODEV;
|
||||
|
||||
pdev = dev_get_drvdata(pmf_device);
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
|
||||
return amd_pmf_get_smu_metrics(pdev, info);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
|
||||
|
|
@ -14,10 +14,13 @@
|
|||
#include <linux/acpi.h>
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/compiler_types.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/mutex_types.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_profile.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define POLICY_BUF_MAX_SZ 0x4b000
|
||||
#define POLICY_SIGN_COOKIE 0x31535024
|
||||
|
|
@ -28,6 +31,11 @@
|
|||
#define AMD_CPU_ID_PS 0x14e8
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
|
||||
#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b
|
||||
|
||||
/* Aliases required by PCI_DEVICE_DATA() macro naming convention */
|
||||
#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB
|
||||
#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS
|
||||
|
||||
struct cookie_header {
|
||||
u32 sign;
|
||||
|
|
@ -70,6 +78,10 @@ struct cookie_header {
|
|||
#define SET_PMF_PPT 0x25
|
||||
#define SET_PMF_PPT_APU_ONLY 0x26
|
||||
|
||||
/* Message IDs for 1AH_M80H platform */
|
||||
#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E
|
||||
#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F
|
||||
|
||||
/* OS slider update notification */
|
||||
#define DC_BEST_PERF 0
|
||||
#define DC_BETTER_PERF 1
|
||||
|
|
@ -130,6 +142,14 @@ struct cookie_header {
|
|||
#define GET_CMD true
|
||||
|
||||
#define METRICS_TABLE_ID 7
|
||||
#define BIOS_OUTPUT_MAX 10
|
||||
|
||||
extern int metrics_table_loop_ms;
|
||||
|
||||
#define AMD_PMF_METRIC_CORE_TYPE_MAX 4
|
||||
#define AMD_PMF_METRIC_CCX_MAX 4
|
||||
#define AMD_PMF_NUM_CLK_DPM_LEVELS 8
|
||||
#define AMD_PMF_NUM_MAX_CORES 12
|
||||
|
||||
typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
|
||||
|
||||
|
|
@ -242,6 +262,199 @@ struct apmf_fan_idx {
|
|||
u32 fan_ctl_idx;
|
||||
} __packed;
|
||||
|
||||
struct amd_pmf_metrics_iod {
|
||||
u32 counter_acc;
|
||||
/* Set Voltage */
|
||||
u64 vddcr_set_voltage;
|
||||
u64 vddcr_soc_set_voltage;
|
||||
u64 vddcr_npu_set_voltage;
|
||||
u64 vddcr_lp_set_voltage;
|
||||
u64 vddcr_gfx_set_voltage;
|
||||
u64 vdd_misc_set_voltage;
|
||||
/* Telemetry Voltages */
|
||||
u64 vddcr_telemetry_voltage;
|
||||
u64 vddcr_soc_telemetry_voltage;
|
||||
u64 vddcr_npu_telemetry_voltage;
|
||||
u64 vddcr_lp_telemetry_voltage;
|
||||
u64 vddcr_gfx_telemetry_voltage;
|
||||
u64 vdd_misc_telemetry_voltage;
|
||||
/* Telemetry Powers */
|
||||
u64 vddcr_telemetry_power;
|
||||
u64 vddcr_soc_telemetry_power;
|
||||
u64 vddcr_npu_telemetry_power;
|
||||
u64 vddcr_lp_telemetry_power;
|
||||
u64 vddcr_gfx_telemetry_power;
|
||||
u64 vdd_misc_telemetry_power;
|
||||
/* Throttlers - Fast PPT */
|
||||
u32 fppt_fused_limit;
|
||||
u32 fppt_max_irm_limit;
|
||||
u32 fppt_max_pbo_limit;
|
||||
u32 fppt_limit;
|
||||
u64 fppt_value_acc;
|
||||
u32 fppt_residency_acc;
|
||||
/* Throttlers - Slow PPT */
|
||||
u32 sppt_fused_limit;
|
||||
u32 sppt_max_irm_limit;
|
||||
u32 sppt_max_pbo_limit;
|
||||
u32 sppt_limit;
|
||||
u64 sppt_value_acc;
|
||||
u32 sppt_residency_acc;
|
||||
/* Throttlers - STAPM */
|
||||
u32 spl_fused_limit;
|
||||
u32 spl_max_irm_limit;
|
||||
u32 spl_max_pbo_limit;
|
||||
u32 spl_limit;
|
||||
u64 spl_value_acc;
|
||||
u32 spl_residency_acc;
|
||||
/* Throttlers - TDC VDDCR */
|
||||
u32 tdc_vddcr_fused_limit;
|
||||
u32 tdc_vddcr_max_irm_limit;
|
||||
u32 tdc_vddcr_max_pbo_limit;
|
||||
u32 tdc_vddcr_limit;
|
||||
u64 tdc_vddcr_value_acc;
|
||||
u32 tdc_vddcr_residency_acc;
|
||||
/* Throttlers - TDC VDDCR_SOC */
|
||||
u32 tdc_vddcr_soc_fused_limit;
|
||||
u32 tdc_vddcr_soc_max_irm_limit;
|
||||
u32 tdc_vddcr_soc_max_pbo_limit;
|
||||
u32 tdc_vddcr_soc_limit;
|
||||
u64 tdc_vddcr_soc_value_acc;
|
||||
u32 tdc_vddcr_soc_residency_acc;
|
||||
/* Throttlers - TDC VDDCR_NPU */
|
||||
u32 tdc_vddcr_npu_fused_limit;
|
||||
u32 tdc_vddcr_npu_max_irm_limit;
|
||||
u32 tdc_vddcr_npu_max_pbo_limit;
|
||||
u32 tdc_vddcr_npu_limit;
|
||||
u64 tdc_vddcr_npu_value_acc;
|
||||
u32 tdc_vddcr_npu_residency_acc;
|
||||
/* Throttlers - TDC VDDCR_LP */
|
||||
u32 tdc_vddcr_lp_fused_limit;
|
||||
u32 tdc_vddcr_lp_max_irm_limit;
|
||||
u32 tdc_vddcr_lp_max_pbo_limit;
|
||||
u32 tdc_vddcr_lp_limit;
|
||||
u64 tdc_vddcr_lp_value_acc;
|
||||
u32 tdc_vddcr_lp_residency_acc;
|
||||
/* Throttlers - TDC VDDCR_GFX */
|
||||
u32 tdc_vddcr_gfx_fused_limit;
|
||||
u32 tdc_vddcr_gfx_max_irm_limit;
|
||||
u32 tdc_vddcr_gfx_max_pbo_limit;
|
||||
u32 tdc_vddcr_gfx_limit;
|
||||
u64 tdc_vddcr_gfx_value_acc;
|
||||
u32 tdc_vddcr_gfx_residency_acc;
|
||||
/* Throttlers - EDC VDDCR */
|
||||
u32 edc_vddcr_fused_limit;
|
||||
u32 edc_vddcr_max_irm_limit;
|
||||
u32 edc_vddcr_max_pbo_limit;
|
||||
u32 edc_vddcr_limit;
|
||||
/* Throttlers - Thermal */
|
||||
u32 thm_fused_limit;
|
||||
u32 thm_limit;
|
||||
u64 thm_value_acc;
|
||||
u32 thm_residency_acc;
|
||||
u32 prochot_residency_acc;
|
||||
u64 gfx_temp_acc;
|
||||
u64 soc_temp_acc;
|
||||
u32 p3t_fused_limit;
|
||||
u64 p3t_value_acc;
|
||||
/* Power */
|
||||
u64 system_power_acc;
|
||||
u64 apu_power_acc;
|
||||
u64 dgpu_power_acc;
|
||||
u64 npu_power_acc;
|
||||
/* Frequencies */
|
||||
u64 fclk_freq_eff_acc;
|
||||
u64 memclk_freq_eff_acc;
|
||||
u64 lclk_freq_eff_acc;
|
||||
u64 gfxclk_freq_eff_acc;
|
||||
u64 socclk_freq_eff_acc;
|
||||
u64 vclk_freq_eff_acc;
|
||||
u64 vpeclk_freq_eff_acc;
|
||||
u64 aieclk_freq_eff_acc;
|
||||
u64 npuhclk_freq_eff_acc;
|
||||
/* Bandwidth */
|
||||
u64 dram_read_bandwidth;
|
||||
u64 dram_write_bandwidth;
|
||||
/* Activity Monitors */
|
||||
u64 gfx_busy_acc;
|
||||
u64 vcn_busy_acc;
|
||||
u64 npu_busy_acc[3];
|
||||
/* STT Limits */
|
||||
u32 stt_min_limit;
|
||||
u64 stt_apu_hotspot_temp_acc;
|
||||
u64 stt_hs2_hotspot_temp_acc;
|
||||
u32 stt_apu_temp_limit;
|
||||
u64 stt_apu_skin_temp_acc;
|
||||
/* Residencies */
|
||||
u64 cpuoff_residency_ccx0;
|
||||
u64 cpuoff_residency_ccx1;
|
||||
u64 cpuoff_residency_ccx2;
|
||||
u64 cpuoff_residency_ccx3;
|
||||
/* DF-pstates */
|
||||
u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
|
||||
u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
|
||||
u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS];
|
||||
u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS];
|
||||
/* System */
|
||||
u8 gfx_disabled;
|
||||
u8 spare2[3];
|
||||
u32 gfxclk_fmax;
|
||||
u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
|
||||
u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
|
||||
u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES];
|
||||
/* Overclock Capable */
|
||||
u8 cpu_precise_and_direct_oc_capable;
|
||||
u8 gfx_precise_and_direct_oc_capable;
|
||||
u8 pbo_basic_oc_capable;
|
||||
u8 pbo_advanced_oc_capable;
|
||||
u8 pbo_nitro_oc_capable;
|
||||
u8 memory_and_fabric_oc_capable;
|
||||
u8 misc_oc_capable;
|
||||
u8 extreme_cold_oc_capable;
|
||||
u8 down_config_control_capable;
|
||||
u8 spare0[3];
|
||||
/* Overclock Status */
|
||||
u32 fit_limit_scalar;
|
||||
u8 ln2_enabled;
|
||||
u8 cpu_precise_and_direct_oc_enabled;
|
||||
u8 gfx_precise_and_direct_oc_enabled;
|
||||
u8 spare1[2];
|
||||
/* Voltage Guardband in PSM count */
|
||||
s8 psm_guardband[5][5][3];
|
||||
s32 core_power_limit_offset;
|
||||
u32 max_freq_offset[5];
|
||||
u64 npu_temp_acc;
|
||||
u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS];
|
||||
u32 cclk_fboost;
|
||||
/* PMF */
|
||||
u32 pmf_fast_apu_ppt_limit;
|
||||
u64 pmf_fast_apu_ppt_value_acc;
|
||||
u32 pmf_fast_apu_ppt_residency_acc;
|
||||
u32 pmf_slow_apu_ppt_limit;
|
||||
u64 pmf_slow_apu_ppt_value_acc;
|
||||
u32 pmf_slow_apu_ppt_residency_acc;
|
||||
u32 pmf_fast_spm_limit;
|
||||
u64 pmf_fast_spm_value_acc;
|
||||
u32 pmf_fast_spm_residency_acc;
|
||||
u32 pmf_slow_spm_limit;
|
||||
u64 pmf_slow_spm_value_acc;
|
||||
u32 pmf_slow_spm_residency_acc;
|
||||
u32 spare3[5];
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct amd_pmf_metrics_ccx {
|
||||
u64 core_c0[AMD_PMF_NUM_MAX_CORES];
|
||||
u64 core_cc6[AMD_PMF_NUM_MAX_CORES];
|
||||
u64 core_freq[AMD_PMF_NUM_MAX_CORES];
|
||||
u64 core_freqeff[AMD_PMF_NUM_MAX_CORES];
|
||||
u64 core_temp[AMD_PMF_NUM_MAX_CORES];
|
||||
u64 core_power[AMD_PMF_NUM_MAX_CORES];
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct amd_pmf_metrics_v3 {
|
||||
struct amd_pmf_metrics_iod iod;
|
||||
struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX];
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct smu_pmf_metrics_v2 {
|
||||
u16 core_frequency[16]; /* MHz */
|
||||
u16 core_power[16]; /* mW */
|
||||
|
|
@ -391,6 +604,19 @@ struct pmf_cbi_ring_buffer {
|
|||
int tail;
|
||||
};
|
||||
|
||||
/* SoC-specific SMU mailbox register offsets */
|
||||
struct amd_pmf_smu_regs {
|
||||
u32 msg_reg;
|
||||
u32 resp_reg;
|
||||
u32 arg_reg[3];
|
||||
};
|
||||
|
||||
struct amd_pmf_arg_data {
|
||||
u32 lo;
|
||||
u32 hi;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct amd_pmf_dev {
|
||||
void __iomem *regbase;
|
||||
void __iomem *smu_virt_addr;
|
||||
|
|
@ -442,6 +668,14 @@ struct amd_pmf_dev {
|
|||
struct pmf_cbi_ring_buffer cbi_buf;
|
||||
struct mutex cbi_mutex; /* Protects ring buffer access */
|
||||
struct mutex metrics_mutex;
|
||||
u32 bios_output[BIOS_OUTPUT_MAX];
|
||||
const struct amd_pmf_smu_regs *smu_regs;
|
||||
void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */
|
||||
phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */
|
||||
struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */
|
||||
struct amd_pmf_arg_data dram_addr;
|
||||
struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */
|
||||
bool npu_metrics_have_prev;
|
||||
};
|
||||
|
||||
struct apmf_sps_prop_granular_v2 {
|
||||
|
|
@ -680,14 +914,6 @@ enum system_state {
|
|||
SYSTEM_STATE_MAX,
|
||||
};
|
||||
|
||||
enum ta_slider {
|
||||
TA_BEST_BATTERY,
|
||||
TA_BETTER_BATTERY,
|
||||
TA_BETTER_PERFORMANCE,
|
||||
TA_BEST_PERFORMANCE,
|
||||
TA_MAX,
|
||||
};
|
||||
|
||||
struct amd_pmf_pb_bitmap {
|
||||
const char *name;
|
||||
u32 bit_mask;
|
||||
|
|
@ -719,20 +945,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = {
|
|||
{"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)},
|
||||
};
|
||||
|
||||
enum platform_type {
|
||||
PTYPE_UNKNOWN = 0,
|
||||
LID_CLOSE,
|
||||
CLAMSHELL,
|
||||
FLAT,
|
||||
TENT,
|
||||
STAND,
|
||||
TABLET,
|
||||
BOOK,
|
||||
PRESENTATION,
|
||||
PULL_FWD,
|
||||
PTYPE_INVALID = 0xf,
|
||||
};
|
||||
|
||||
/* Command ids for TA communication */
|
||||
enum ta_pmf_command {
|
||||
TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE,
|
||||
|
|
@ -871,6 +1083,10 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer);
|
|||
int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag);
|
||||
u32 fixp_q88_fromint(u32 val);
|
||||
int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev);
|
||||
void amd_pmf_set_device(struct device *p_device);
|
||||
|
||||
/* Metrics layer */
|
||||
int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev);
|
||||
|
||||
/* SPS Layer */
|
||||
int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
|
||||
|
|
@ -923,9 +1139,19 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq
|
|||
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
|
||||
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
|
||||
int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
|
||||
u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index);
|
||||
|
||||
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
|
||||
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
|
||||
int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
|
||||
|
||||
/* Util Layer */
|
||||
#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT)
|
||||
int amd_pmf_cdev_register(struct amd_pmf_dev *dev);
|
||||
void amd_pmf_cdev_unregister(void);
|
||||
#else
|
||||
static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; }
|
||||
static inline void amd_pmf_cdev_unregister(void) {}
|
||||
#endif
|
||||
|
||||
#endif /* PMF_H */
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
*/
|
||||
|
||||
#include <acpi/button.h>
|
||||
#include <linux/amd-pmf.h>
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/power_supply.h>
|
||||
|
|
@ -17,61 +18,7 @@
|
|||
#include "pmf.h"
|
||||
|
||||
#ifdef CONFIG_AMD_PMF_DEBUG
|
||||
static const char *platform_type_as_str(u16 platform_type)
|
||||
{
|
||||
switch (platform_type) {
|
||||
case CLAMSHELL:
|
||||
return "CLAMSHELL";
|
||||
case FLAT:
|
||||
return "FLAT";
|
||||
case TENT:
|
||||
return "TENT";
|
||||
case STAND:
|
||||
return "STAND";
|
||||
case TABLET:
|
||||
return "TABLET";
|
||||
case BOOK:
|
||||
return "BOOK";
|
||||
case PRESENTATION:
|
||||
return "PRESENTATION";
|
||||
case PULL_FWD:
|
||||
return "PULL_FWD";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *laptop_placement_as_str(u16 device_state)
|
||||
{
|
||||
switch (device_state) {
|
||||
case ON_TABLE:
|
||||
return "ON_TABLE";
|
||||
case ON_LAP_MOTION:
|
||||
return "ON_LAP_MOTION";
|
||||
case IN_BAG:
|
||||
return "IN_BAG";
|
||||
case OUT_OF_BAG:
|
||||
return "OUT_OF_BAG";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *ta_slider_as_str(unsigned int state)
|
||||
{
|
||||
switch (state) {
|
||||
case TA_BEST_PERFORMANCE:
|
||||
return "PERFORMANCE";
|
||||
case TA_BETTER_PERFORMANCE:
|
||||
return "BALANCED";
|
||||
case TA_BEST_BATTERY:
|
||||
return "POWER_SAVER";
|
||||
default:
|
||||
return "Unknown TA Slider State";
|
||||
}
|
||||
}
|
||||
|
||||
static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
|
||||
u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
|
||||
{
|
||||
switch (index) {
|
||||
case 0 ... 1:
|
||||
|
|
@ -82,13 +29,15 @@ static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs);
|
||||
|
||||
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
||||
{
|
||||
int i;
|
||||
|
||||
dev_dbg(dev->dev, "==== TA inputs START ====\n");
|
||||
dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider));
|
||||
dev_dbg(dev->dev, "Slider State: %s\n",
|
||||
amd_pmf_get_slider_position(in->ev_info.power_slider));
|
||||
dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source));
|
||||
dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage);
|
||||
dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design);
|
||||
|
|
@ -102,9 +51,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *
|
|||
dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open");
|
||||
dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away");
|
||||
dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light);
|
||||
dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type));
|
||||
dev_dbg(dev->dev, "Platform type: %s\n",
|
||||
amd_pmf_get_platform_type(in->ev_info.platform_type));
|
||||
dev_dbg(dev->dev, "Laptop placement: %s\n",
|
||||
laptop_placement_as_str(in->ev_info.device_state));
|
||||
amd_pmf_get_laptop_placement(in->ev_info.device_state));
|
||||
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
|
||||
dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1,
|
||||
amd_pmf_get_ta_custom_bios_inputs(in, i));
|
||||
|
|
@ -287,14 +237,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_
|
|||
switch (dev->current_profile) {
|
||||
case PLATFORM_PROFILE_PERFORMANCE:
|
||||
case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
|
||||
val = TA_BEST_PERFORMANCE;
|
||||
val = AMD_PMF_TA_BEST_PERFORMANCE;
|
||||
break;
|
||||
case PLATFORM_PROFILE_BALANCED:
|
||||
val = TA_BETTER_PERFORMANCE;
|
||||
val = AMD_PMF_TA_BETTER_PERFORMANCE;
|
||||
break;
|
||||
case PLATFORM_PROFILE_LOW_POWER:
|
||||
case PLATFORM_PROFILE_QUIET:
|
||||
val = TA_BEST_BATTERY;
|
||||
val = AMD_PMF_TA_BEST_BATTERY;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "Unknown Platform Profile.\n");
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/tee_drv.h>
|
||||
#include <linux/uuid.h>
|
||||
#include "pmf.h"
|
||||
|
|
@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx)
|
|||
|
||||
static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action)
|
||||
{
|
||||
u32 bios_idx;
|
||||
int bios_idx;
|
||||
int ret;
|
||||
|
||||
bios_idx = amd_pmf_get_bios_output_idx(action->action_index);
|
||||
if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) {
|
||||
dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx);
|
||||
ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx);
|
||||
if (!ret)
|
||||
pdev->bios_output[bios_idx] = action->value;
|
||||
}
|
||||
|
||||
static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out)
|
||||
|
|
|
|||
153
drivers/platform/x86/amd/pmf/util.c
Normal file
153
drivers/platform/x86/amd/pmf/util.c
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* AMD Platform Management Framework Util Layer
|
||||
*
|
||||
* Copyright (c) 2026, Advanced Micro Devices, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
* Sanket Goswami <Sanket.Goswami@amd.com>
|
||||
*/
|
||||
|
||||
#include <linux/amd-pmf.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "pmf.h"
|
||||
|
||||
static struct amd_pmf_dev *pmf_dev_handle;
|
||||
static DEFINE_MUTEX(pmf_util_lock);
|
||||
|
||||
static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info)
|
||||
{
|
||||
struct ta_pmf_shared_memory *ta_sm = NULL;
|
||||
struct ta_pmf_enact_table *in = NULL;
|
||||
int idx;
|
||||
|
||||
if (!pdev || !info)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pdev->shbuf)
|
||||
return -EINVAL;
|
||||
|
||||
ta_sm = pdev->shbuf;
|
||||
in = &ta_sm->pmf_input.enact_table;
|
||||
|
||||
/* Set size */
|
||||
info->size = sizeof(*info);
|
||||
|
||||
/* PMF Feature support flags */
|
||||
if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE))
|
||||
info->features_supported |= AMD_PMF_FEAT_AUTO_MODE;
|
||||
if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
|
||||
info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER;
|
||||
if (pdev->smart_pc_enabled)
|
||||
info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER;
|
||||
if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC))
|
||||
info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC;
|
||||
if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC))
|
||||
info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC;
|
||||
|
||||
/* Device States */
|
||||
info->platform_type = in->ev_info.platform_type;
|
||||
info->laptop_placement = in->ev_info.device_state;
|
||||
info->lid_state = in->ev_info.lid_state;
|
||||
info->user_presence = in->ev_info.user_present;
|
||||
info->slider_position = in->ev_info.power_slider;
|
||||
|
||||
/* Thermal and Power Metrics */
|
||||
info->power_source = in->ev_info.power_source;
|
||||
info->skin_temp = in->ev_info.skin_temperature;
|
||||
info->gfx_busy = in->ev_info.gfx_busy;
|
||||
info->ambient_light = in->ev_info.ambient_light;
|
||||
info->avg_c0_residency = in->ev_info.avg_c0residency;
|
||||
info->max_c0_residency = in->ev_info.max_c0residency;
|
||||
info->socket_power = in->ev_info.socket_power;
|
||||
|
||||
/* Custom BIOS input parameters */
|
||||
for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++)
|
||||
info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx);
|
||||
|
||||
/* BIOS output parameters */
|
||||
for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++)
|
||||
info->bios_output[idx] = pdev->bios_output[idx];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct amd_pmf_dev *pdev = filp->private_data;
|
||||
void __user *argp = (void __user *)arg;
|
||||
struct amd_pmf_info info = {};
|
||||
size_t copy_size;
|
||||
__u64 user_size;
|
||||
int ret;
|
||||
|
||||
if (cmd != IOCTL_AMD_PMF_POPULATE_DATA)
|
||||
return -ENOTTY;
|
||||
|
||||
/* First read just the size field from userspace */
|
||||
if (copy_from_user(&user_size, argp, sizeof(user_size)))
|
||||
return -EFAULT;
|
||||
|
||||
guard(mutex)(&pmf_util_lock);
|
||||
ret = amd_pmf_populate_data(pdev, &info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
copy_size = min_t(size_t, user_size, sizeof(info));
|
||||
|
||||
/* Set actual size being copied */
|
||||
info.size = copy_size;
|
||||
|
||||
if (copy_to_user(argp, &info, copy_size))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pmf_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
guard(mutex)(&pmf_util_lock);
|
||||
if (!pmf_dev_handle)
|
||||
return -ENODEV;
|
||||
|
||||
filp->private_data = pmf_dev_handle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations pmf_if_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = amd_pmf_open,
|
||||
.unlocked_ioctl = amd_pmf_set_ioctl,
|
||||
};
|
||||
|
||||
static struct miscdevice amd_pmf_util_if = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "amdpmf_interface",
|
||||
.fops = &pmf_if_ops,
|
||||
};
|
||||
|
||||
int amd_pmf_cdev_register(struct amd_pmf_dev *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&pmf_util_lock);
|
||||
pmf_dev_handle = dev;
|
||||
ret = misc_register(&amd_pmf_util_if);
|
||||
if (ret)
|
||||
pmf_dev_handle = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void amd_pmf_cdev_unregister(void)
|
||||
{
|
||||
guard(mutex)(&pmf_util_lock);
|
||||
if (pmf_dev_handle)
|
||||
misc_deregister(&amd_pmf_util_if);
|
||||
pmf_dev_handle = NULL;
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/err.h>
|
||||
|
|
@ -93,6 +94,8 @@ struct asus_armoury_priv {
|
|||
|
||||
u32 mini_led_dev_id;
|
||||
u32 gpu_mux_dev_id;
|
||||
|
||||
bool requires_fan_curve;
|
||||
};
|
||||
|
||||
static struct asus_armoury_priv asus_armoury = {
|
||||
|
|
@ -216,6 +219,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr,
|
|||
u32 result;
|
||||
int err;
|
||||
|
||||
/* On some models, PPT changes require an active fan curve */
|
||||
if (asus_armoury.requires_fan_curve) {
|
||||
switch (dev_id) {
|
||||
case ASUS_WMI_DEVID_PPT_PL1_SPL:
|
||||
case ASUS_WMI_DEVID_PPT_PL2_SPPT:
|
||||
case ASUS_WMI_DEVID_PPT_PL3_FPPT:
|
||||
case ASUS_WMI_DEVID_PPT_APU_SPPT:
|
||||
case ASUS_WMI_DEVID_PPT_PLAT_SPPT:
|
||||
if (!asus_wmi_custom_fan_curve_is_enabled()) {
|
||||
pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevent developers from bricking devices or issuing dangerous
|
||||
* commands that can be difficult or impossible to recover from.
|
||||
|
|
@ -989,10 +1008,11 @@ static int asus_fw_attr_add(void)
|
|||
/* Init / exit ****************************************************************/
|
||||
|
||||
/* Set up the min/max and defaults for ROG tunables */
|
||||
static void init_rog_tunables(void)
|
||||
static int init_rog_tunables(void)
|
||||
{
|
||||
const struct power_limits *ac_limits, *dc_limits;
|
||||
struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL;
|
||||
struct rog_tunables *ac_rog_tunables __free(kfree) = NULL;
|
||||
struct rog_tunables *dc_rog_tunables __free(kfree) = NULL;
|
||||
const struct power_data *power_data;
|
||||
const struct dmi_system_id *dmi_id;
|
||||
|
||||
|
|
@ -1000,24 +1020,25 @@ static void init_rog_tunables(void)
|
|||
dmi_id = dmi_first_match(power_limits);
|
||||
if (!dmi_id) {
|
||||
pr_warn("No matching power limits found for this system\n");
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get the power data for this system */
|
||||
power_data = dmi_id->driver_data;
|
||||
if (!power_data) {
|
||||
pr_info("No power data available for this system\n");
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
asus_armoury.requires_fan_curve = power_data->requires_fan_curve;
|
||||
|
||||
/* Initialize AC power tunables */
|
||||
ac_limits = power_data->ac_data;
|
||||
if (ac_limits) {
|
||||
ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]);
|
||||
ac_rog_tunables = kzalloc_obj(*ac_rog_tunables);
|
||||
if (!ac_rog_tunables)
|
||||
goto err_nomem;
|
||||
return -ENOMEM;
|
||||
|
||||
asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables;
|
||||
ac_rog_tunables->power_limits = ac_limits;
|
||||
|
||||
/* Set initial AC values */
|
||||
|
|
@ -1060,13 +1081,10 @@ static void init_rog_tunables(void)
|
|||
/* Initialize DC power tunables */
|
||||
dc_limits = power_data->dc_data;
|
||||
if (dc_limits) {
|
||||
dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]);
|
||||
if (!dc_rog_tunables) {
|
||||
kfree(ac_rog_tunables);
|
||||
goto err_nomem;
|
||||
}
|
||||
dc_rog_tunables = kzalloc_obj(*dc_rog_tunables);
|
||||
if (!dc_rog_tunables)
|
||||
return -ENOMEM;
|
||||
|
||||
asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables;
|
||||
dc_rog_tunables->power_limits = dc_limits;
|
||||
|
||||
/* Set initial DC values */
|
||||
|
|
@ -1106,15 +1124,16 @@ static void init_rog_tunables(void)
|
|||
pr_debug("No DC PPT limits defined\n");
|
||||
}
|
||||
|
||||
return;
|
||||
asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = no_free_ptr(ac_rog_tunables);
|
||||
asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(dc_rog_tunables);
|
||||
|
||||
err_nomem:
|
||||
pr_err("Failed to allocate memory for tunables\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init asus_fw_init(void)
|
||||
{
|
||||
char *wmi_uid;
|
||||
int err;
|
||||
|
||||
wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID);
|
||||
if (!wmi_uid)
|
||||
|
|
@ -1127,10 +1146,21 @@ static int __init asus_fw_init(void)
|
|||
if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI))
|
||||
return -ENODEV;
|
||||
|
||||
init_rog_tunables();
|
||||
err = init_rog_tunables();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Must always be last step to ensure data is available */
|
||||
return asus_fw_attr_add();
|
||||
err = asus_fw_attr_add();
|
||||
if (err)
|
||||
goto err_free_tunables;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_tunables:
|
||||
kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]);
|
||||
kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit asus_fw_exit(void)
|
||||
|
|
|
|||
|
|
@ -369,6 +369,38 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA401KM"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.nv_dynamic_boost_max = 15,
|
||||
.nv_dynamic_boost_min = 5,
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_tgp_max = 95,
|
||||
.nv_tgp_min = 55,
|
||||
.ppt_pl1_spl_max = 80,
|
||||
.ppt_pl1_spl_min = 15,
|
||||
.ppt_pl2_sppt_max = 80,
|
||||
.ppt_pl2_sppt_min = 35,
|
||||
.ppt_pl3_fppt_max = 80,
|
||||
.ppt_pl3_fppt_min = 35,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.ppt_pl1_spl_max = 35,
|
||||
.ppt_pl1_spl_min = 25,
|
||||
.ppt_pl2_sppt_max = 44,
|
||||
.ppt_pl2_sppt_min = 31,
|
||||
.ppt_pl3_fppt_max = 65,
|
||||
.ppt_pl3_fppt_min = 45,
|
||||
},
|
||||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA401UM"),
|
||||
|
|
@ -754,6 +786,40 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA608WV"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.nv_dynamic_boost_max = 25,
|
||||
.nv_dynamic_boost_min = 5,
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_tgp_max = 115,
|
||||
.nv_tgp_min = 55,
|
||||
.ppt_pl1_spl_max = 90,
|
||||
.ppt_pl1_spl_min = 15,
|
||||
.ppt_pl2_sppt_max = 90,
|
||||
.ppt_pl2_sppt_min = 35,
|
||||
.ppt_pl3_fppt_max = 90,
|
||||
.ppt_pl3_fppt_min = 35,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.ppt_pl1_spl_def = 45,
|
||||
.ppt_pl1_spl_max = 65,
|
||||
.ppt_pl1_spl_min = 15,
|
||||
.ppt_pl2_sppt_def = 54,
|
||||
.ppt_pl2_sppt_max = 65,
|
||||
.ppt_pl2_sppt_min = 35,
|
||||
.ppt_pl3_fppt_max = 65,
|
||||
.ppt_pl3_fppt_min = 35,
|
||||
},
|
||||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA617NS"),
|
||||
|
|
@ -909,6 +975,32 @@ static const struct dmi_system_id power_limits[] = {
|
|||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 28,
|
||||
.ppt_pl1_spl_max = 85,
|
||||
.ppt_pl2_sppt_min = 28,
|
||||
.ppt_pl2_sppt_max = 135,
|
||||
.nv_dynamic_boost_min = 5,
|
||||
.nv_dynamic_boost_max = 25,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_temp_target_max = 87,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 25,
|
||||
.ppt_pl1_spl_max = 45,
|
||||
.ppt_pl2_sppt_min = 35,
|
||||
.ppt_pl2_sppt_max = 60,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_temp_target_max = 87,
|
||||
},
|
||||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FX607VU"),
|
||||
|
|
@ -2079,6 +2171,35 @@ static const struct dmi_system_id power_limits[] = {
|
|||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "G635LX"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 28,
|
||||
.ppt_pl1_spl_def = 140,
|
||||
.ppt_pl1_spl_max = 175,
|
||||
.ppt_pl2_sppt_min = 28,
|
||||
.ppt_pl2_sppt_max = 175,
|
||||
.nv_dynamic_boost_min = 5,
|
||||
.nv_dynamic_boost_max = 25,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_tgp_min = 80,
|
||||
.nv_tgp_max = 150,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 25,
|
||||
.ppt_pl1_spl_max = 55,
|
||||
.ppt_pl2_sppt_min = 25,
|
||||
.ppt_pl2_sppt_max = 70,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_temp_target_max = 87,
|
||||
},
|
||||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "G713PV"),
|
||||
|
|
@ -2322,6 +2443,35 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "HN7306EA"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 35,
|
||||
.ppt_pl1_spl_def = 60,
|
||||
.ppt_pl1_spl_max = 85,
|
||||
.ppt_pl2_sppt_min = 40,
|
||||
.ppt_pl2_sppt_def = 70,
|
||||
.ppt_pl2_sppt_max = 95,
|
||||
.ppt_pl3_fppt_min = 50,
|
||||
.ppt_pl3_fppt_def = 85,
|
||||
.ppt_pl3_fppt_max = 115,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 35,
|
||||
.ppt_pl1_spl_def = 45,
|
||||
.ppt_pl1_spl_max = 60,
|
||||
.ppt_pl2_sppt_min = 40,
|
||||
.ppt_pl2_sppt_def = 55,
|
||||
.ppt_pl2_sppt_max = 70,
|
||||
.ppt_pl3_fppt_min = 50,
|
||||
.ppt_pl3_fppt_def = 65,
|
||||
.ppt_pl3_fppt_max = 85,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "RC71"),
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@
|
|||
#define ASUS_LAPTOP_VERSION "0.42"
|
||||
|
||||
#define ASUS_LAPTOP_NAME "Asus Laptop Support"
|
||||
#define ASUS_LAPTOP_CLASS "hotkey"
|
||||
#define ASUS_LAPTOP_DEVICE_NAME "Hotkey"
|
||||
#define ASUS_LAPTOP_FILE KBUILD_MODNAME
|
||||
#define ASUS_LAPTOP_PREFIX "\\_SB.ATKD."
|
||||
|
||||
|
|
@ -1524,9 +1522,8 @@ static void asus_acpi_notify(acpi_handle handle, u32 event, void *data)
|
|||
|
||||
/* TODO Find a better way to handle events count. */
|
||||
count = asus->event_count[event % 128]++;
|
||||
acpi_bus_generate_netlink_event(asus->device->pnp.device_class,
|
||||
dev_name(&asus->device->dev), event,
|
||||
count);
|
||||
acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev),
|
||||
event, count);
|
||||
|
||||
if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX)
|
||||
event = ATKD_BRNUP;
|
||||
|
|
@ -1840,8 +1837,6 @@ static int asus_acpi_probe(struct platform_device *pdev)
|
|||
if (!asus)
|
||||
return -ENOMEM;
|
||||
asus->handle = device->handle;
|
||||
strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
|
||||
asus->device = device;
|
||||
|
||||
asus_dmi_check();
|
||||
|
|
|
|||
|
|
@ -632,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
|
|||
{ KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */
|
||||
{ KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */
|
||||
{ KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */
|
||||
{ KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */
|
||||
{ KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */
|
||||
{ KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */
|
||||
{ KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */
|
||||
|
|
|
|||
|
|
@ -132,6 +132,10 @@ static int asus_wireless_probe(struct platform_device *pdev)
|
|||
const struct acpi_device_id *id;
|
||||
int err;
|
||||
|
||||
id = acpi_match_acpi_device(device_ids, adev);
|
||||
if (!id)
|
||||
return -ENODEV;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
|
@ -139,6 +143,7 @@ static int asus_wireless_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, data);
|
||||
|
||||
data->adev = adev;
|
||||
data->hswc_params = (const struct hswc_params *)id->driver_data;
|
||||
|
||||
data->idev = devm_input_allocate_device(&pdev->dev);
|
||||
if (!data->idev)
|
||||
|
|
@ -153,12 +158,6 @@ static int asus_wireless_probe(struct platform_device *pdev)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
id = acpi_match_acpi_device(device_ids, adev);
|
||||
if (!id)
|
||||
return 0;
|
||||
|
||||
data->hswc_params = (const struct hswc_params *)id->driver_data;
|
||||
|
||||
data->wq = create_singlethread_workqueue("asus_wireless_workqueue");
|
||||
if (!data->wq)
|
||||
return -ENOMEM;
|
||||
|
|
|
|||
|
|
@ -4073,6 +4073,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if at least one custom fan curve is active
|
||||
*
|
||||
* Used by asus-armoury to check if PPT writes will be accepted by the BIOS
|
||||
* on models that require an active fan curve for TDP changes.
|
||||
*/
|
||||
bool asus_wmi_custom_fan_curve_is_enabled(void)
|
||||
{
|
||||
struct fan_curve_data *curves;
|
||||
struct asus_wmi *asus;
|
||||
|
||||
guard(spinlock_irqsave)(&asus_ref.lock);
|
||||
asus = asus_ref.asus;
|
||||
if (!asus)
|
||||
return false;
|
||||
|
||||
curves = asus->custom_fan_curves;
|
||||
|
||||
return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) ||
|
||||
(asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) ||
|
||||
(asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI");
|
||||
|
||||
/* Throttle thermal policy ****************************************************/
|
||||
static int throttle_thermal_policy_write(struct asus_wmi *asus)
|
||||
{
|
||||
|
|
@ -5244,20 +5268,20 @@ static int asus_wmi_add(struct platform_device *pdev)
|
|||
return 0;
|
||||
|
||||
fail_wmi_handler:
|
||||
asus_screenpad_exit(asus);
|
||||
fail_screenpad:
|
||||
asus_wmi_backlight_exit(asus);
|
||||
fail_backlight:
|
||||
asus_wmi_rfkill_exit(asus);
|
||||
fail_screenpad:
|
||||
asus_screenpad_exit(asus);
|
||||
fail_rfkill:
|
||||
asus_wmi_led_exit(asus);
|
||||
fail_leds:
|
||||
fail_custom_fan_curve:
|
||||
fail_hwmon:
|
||||
asus_wmi_input_exit(asus);
|
||||
fail_input:
|
||||
asus_wmi_sysfs_exit(asus->platform_device);
|
||||
fail_sysfs:
|
||||
fail_custom_fan_curve:
|
||||
fail_platform_profile_setup:
|
||||
fail_fan_boost_mode:
|
||||
fail_platform:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ struct smbios_device {
|
|||
struct list_head list;
|
||||
struct device *device;
|
||||
int priority;
|
||||
int (*call_fn)(struct calling_interface_buffer *arg);
|
||||
smbios_callback_fn_t call_fn;
|
||||
};
|
||||
|
||||
struct smbios_call {
|
||||
|
|
@ -146,7 +146,7 @@ int dell_smbios_error(int value)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(dell_smbios_error);
|
||||
|
||||
int dell_smbios_register_device(struct device *d, int priority, void *call_fn)
|
||||
int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn)
|
||||
{
|
||||
struct smbios_device *priv;
|
||||
|
||||
|
|
@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer)
|
|||
goto out_smbios_call;
|
||||
}
|
||||
|
||||
ret = selected->call_fn(buffer);
|
||||
ret = selected->call_fn(selected->device, buffer);
|
||||
|
||||
out_smbios_call:
|
||||
mutex_unlock(&smbios_mutex);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy)
|
|||
}
|
||||
}
|
||||
|
||||
static int dell_smbios_smm_call(struct calling_interface_buffer *input)
|
||||
static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input)
|
||||
{
|
||||
struct smi_cmd command;
|
||||
size_t size;
|
||||
|
|
@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input)
|
|||
}
|
||||
|
||||
/* When enabled this indicates that SMM won't work */
|
||||
static bool test_wsmt_enabled(void)
|
||||
static bool test_wsmt_enabled(struct device *dev)
|
||||
{
|
||||
struct calling_interface_token *wsmt;
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void)
|
|||
memset(buffer, 0, sizeof(struct calling_interface_buffer));
|
||||
buffer->input[0] = wsmt->location;
|
||||
buffer->output[0] = 99;
|
||||
dell_smbios_smm_call(buffer);
|
||||
dell_smbios_smm_call(dev, buffer);
|
||||
if (buffer->output[0] == 99)
|
||||
return true;
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ int init_dell_smbios_smm(void)
|
|||
|
||||
dmi_walk(find_cmd_address, NULL);
|
||||
|
||||
if (test_wsmt_enabled()) {
|
||||
if (test_wsmt_enabled(&platform_device->dev)) {
|
||||
pr_debug("Disabling due to WSMT enabled\n");
|
||||
ret = -ENODEV;
|
||||
goto fail_wsmt;
|
||||
|
|
|
|||
|
|
@ -6,21 +6,20 @@
|
|||
*/
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/wmi.h>
|
||||
#include <uapi/linux/wmi.h>
|
||||
#include "dell-smbios.h"
|
||||
#include "dell-wmi-descriptor.h"
|
||||
|
||||
static DEFINE_MUTEX(call_mutex);
|
||||
static DEFINE_MUTEX(list_mutex);
|
||||
static int wmi_supported;
|
||||
|
||||
struct misc_bios_flags_structure {
|
||||
|
|
@ -32,21 +31,16 @@ struct misc_bios_flags_structure {
|
|||
#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
|
||||
|
||||
struct wmi_smbios_priv {
|
||||
struct mutex call_lock; /* Protects the content of the SMBIOS buffer */
|
||||
struct dell_wmi_smbios_buffer *buf;
|
||||
struct list_head list;
|
||||
struct wmi_device *wdev;
|
||||
struct device *child;
|
||||
u64 req_buf_size;
|
||||
struct miscdevice char_dev;
|
||||
};
|
||||
static LIST_HEAD(wmi_list);
|
||||
|
||||
static inline struct wmi_smbios_priv *get_first_smbios_priv(void)
|
||||
{
|
||||
return list_first_entry_or_null(&wmi_list,
|
||||
struct wmi_smbios_priv,
|
||||
list);
|
||||
}
|
||||
static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */
|
||||
static struct wmi_smbios_priv *chardev_priv;
|
||||
|
||||
static int run_smbios_call(struct wmi_device *wdev)
|
||||
{
|
||||
|
|
@ -80,50 +74,36 @@ static int run_smbios_call(struct wmi_device *wdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer)
|
||||
static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer)
|
||||
{
|
||||
struct wmi_smbios_priv *priv;
|
||||
struct wmi_smbios_priv *priv = dev_get_drvdata(dev);
|
||||
size_t difference;
|
||||
size_t size;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&call_mutex);
|
||||
priv = get_first_smbios_priv();
|
||||
if (!priv) {
|
||||
ret = -ENODEV;
|
||||
goto out_wmi_call;
|
||||
}
|
||||
|
||||
size = sizeof(struct calling_interface_buffer);
|
||||
difference = priv->req_buf_size - sizeof(u64) - size;
|
||||
|
||||
guard(mutex)(&priv->call_lock);
|
||||
|
||||
memset(&priv->buf->ext, 0, difference);
|
||||
memcpy(&priv->buf->std, buffer, size);
|
||||
ret = run_smbios_call(priv->wdev);
|
||||
memcpy(buffer, &priv->buf->std, size);
|
||||
out_wmi_call:
|
||||
mutex_unlock(&call_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dell_smbios_wmi_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct wmi_smbios_priv *priv;
|
||||
|
||||
priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev);
|
||||
filp->private_data = priv;
|
||||
|
||||
return nonseekable_open(inode, filp);
|
||||
}
|
||||
|
||||
static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct wmi_smbios_priv *priv = filp->private_data;
|
||||
guard(rwsem_read)(&chardev_lock);
|
||||
|
||||
return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size,
|
||||
sizeof(priv->req_buf_size));
|
||||
if (!chardev_priv)
|
||||
return -ENODEV;
|
||||
|
||||
return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size,
|
||||
sizeof(chardev_priv->req_buf_size));
|
||||
}
|
||||
|
||||
static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv,
|
||||
|
|
@ -167,22 +147,22 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv,
|
|||
static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg;
|
||||
struct wmi_smbios_priv *priv = filp->private_data;
|
||||
long ret;
|
||||
|
||||
if (cmd != DELL_WMI_SMBIOS_CMD)
|
||||
return -ENOIOCTLCMD;
|
||||
|
||||
mutex_lock(&call_mutex);
|
||||
ret = dell_smbios_wmi_do_ioctl(priv, input);
|
||||
mutex_unlock(&call_mutex);
|
||||
guard(rwsem_read)(&chardev_lock);
|
||||
|
||||
return ret;
|
||||
if (!chardev_priv)
|
||||
return -ENODEV;
|
||||
|
||||
guard(mutex)(&chardev_priv->call_lock);
|
||||
|
||||
return dell_smbios_wmi_do_ioctl(chardev_priv, input);
|
||||
}
|
||||
|
||||
static const struct file_operations dell_smbios_wmi_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = dell_smbios_wmi_open,
|
||||
.read = dell_smbios_wmi_read,
|
||||
.unlocked_ioctl = dell_smbios_wmi_ioctl,
|
||||
.compat_ioctl = compat_ptr_ioctl,
|
||||
|
|
@ -195,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data)
|
|||
misc_deregister(char_dev);
|
||||
}
|
||||
|
||||
static void dell_smbios_wmi_clear_chardev(void *data)
|
||||
{
|
||||
guard(rwsem_write)(&chardev_lock);
|
||||
|
||||
chardev_priv = NULL;
|
||||
}
|
||||
|
||||
static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
scoped_guard(rwsem_write, &chardev_lock) {
|
||||
/* We can only have a single chardev at a time */
|
||||
if (chardev_priv)
|
||||
return -EBUSY;
|
||||
|
||||
chardev_priv = priv;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->char_dev.minor = MISC_DYNAMIC_MINOR;
|
||||
priv->char_dev.name = "wmi/dell-smbios";
|
||||
priv->char_dev.fops = &dell_smbios_wmi_fops;
|
||||
|
|
@ -254,31 +253,20 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context)
|
|||
if (!priv->buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_mutex_init(&wdev->dev, &priv->call_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dell_smbios_wmi_register_chardev(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&list_mutex);
|
||||
list_add_tail(&priv->list, &wmi_list);
|
||||
mutex_unlock(&list_mutex);
|
||||
|
||||
return 0;
|
||||
return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call);
|
||||
}
|
||||
|
||||
static void dell_smbios_wmi_remove(struct wmi_device *wdev)
|
||||
{
|
||||
struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev);
|
||||
|
||||
mutex_lock(&call_mutex);
|
||||
mutex_lock(&list_mutex);
|
||||
list_del(&priv->list);
|
||||
mutex_unlock(&list_mutex);
|
||||
dell_smbios_unregister_device(&wdev->dev);
|
||||
mutex_unlock(&call_mutex);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id dell_smbios_wmi_id_table[] = {
|
||||
|
|
@ -316,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = {
|
|||
.probe = dell_smbios_wmi_probe,
|
||||
.remove = dell_smbios_wmi_remove,
|
||||
.id_table = dell_smbios_wmi_id_table,
|
||||
.no_singleton = true,
|
||||
};
|
||||
|
||||
int init_dell_smbios_wmi(void)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@
|
|||
|
||||
struct notifier_block;
|
||||
|
||||
typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer);
|
||||
|
||||
struct calling_interface_token {
|
||||
u16 tokenID;
|
||||
u16 location;
|
||||
|
|
@ -64,7 +66,7 @@ struct calling_interface_structure {
|
|||
struct calling_interface_token tokens[];
|
||||
} __packed;
|
||||
|
||||
int dell_smbios_register_device(struct device *d, int priority, void *call_fn);
|
||||
int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn);
|
||||
void dell_smbios_unregister_device(struct device *d);
|
||||
|
||||
int dell_smbios_error(int value);
|
||||
|
|
|
|||
|
|
@ -456,7 +456,7 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, __l
|
|||
key++;
|
||||
used = 1;
|
||||
} else if (type == 0x0012 && code == 0x000d && remaining > 0) {
|
||||
value = (le16_to_cpu(buffer[2]) == 2);
|
||||
value = (le16_to_cpu(buffer[0]) == 2);
|
||||
used = 1;
|
||||
}
|
||||
|
||||
|
|
@ -843,9 +843,22 @@ static int __init dell_wmi_init(void)
|
|||
|
||||
err = dell_privacy_register_driver();
|
||||
if (err)
|
||||
return err;
|
||||
goto out_smbios;
|
||||
|
||||
return wmi_driver_register(&dell_wmi_driver);
|
||||
err = wmi_driver_register(&dell_wmi_driver);
|
||||
if (err)
|
||||
goto out_privacy;
|
||||
|
||||
return 0;
|
||||
|
||||
out_privacy:
|
||||
dell_privacy_unregister_driver();
|
||||
|
||||
out_smbios:
|
||||
if (wmi_requires_smbios_request)
|
||||
dell_wmi_events_set_enabled(false);
|
||||
|
||||
return err;
|
||||
}
|
||||
late_initcall(dell_wmi_init);
|
||||
|
||||
|
|
|
|||
|
|
@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met
|
|||
static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method,
|
||||
u32 arg, struct dell_wmi_buffer **result)
|
||||
{
|
||||
struct dell_wmi_buffer *buffer;
|
||||
struct wmi_buffer output;
|
||||
size_t buffer_size;
|
||||
int ret;
|
||||
|
||||
ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer));
|
||||
ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
buffer = output.data;
|
||||
if (!le32_to_cpu(buffer->raw_size)) {
|
||||
ret = -ENODATA;
|
||||
struct dell_wmi_buffer *buffer __free(kfree) = output.data;
|
||||
|
||||
goto err_free;
|
||||
}
|
||||
if (!le32_to_cpu(buffer->raw_size))
|
||||
return -ENODATA;
|
||||
|
||||
buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size));
|
||||
if (buffer_size > output.length) {
|
||||
dev_warn(&wdev->dev,
|
||||
FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n",
|
||||
buffer_size, output.length);
|
||||
ret = -EMSGSIZE;
|
||||
|
||||
goto err_free;
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
*result = buffer;
|
||||
*result = no_free_ptr(buffer);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
kfree(output.data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method,
|
||||
|
|
|
|||
|
|
@ -92,11 +92,11 @@ bool dell_privacy_has_mic_mute(void)
|
|||
{
|
||||
struct privacy_wmi_data *priv;
|
||||
|
||||
mutex_lock(&list_mutex);
|
||||
guard(mutex)(&list_mutex);
|
||||
|
||||
priv = list_first_entry_or_null(&wmi_list,
|
||||
struct privacy_wmi_data,
|
||||
list);
|
||||
mutex_unlock(&list_mutex);
|
||||
|
||||
return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ int set_attribute(const char *a_name, const char *a_value)
|
|||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
|
||||
ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev,
|
||||
buffer, buffer_size,
|
||||
SETATTRIBUTE_METHOD_ID);
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ enum {
|
|||
static int get_##type##_instance_id(struct kobject *kobj) \
|
||||
{ \
|
||||
int i; \
|
||||
for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \
|
||||
for (i = 0; i < wmi_priv.type##_instances_count; i++) { \
|
||||
if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\
|
||||
return i; \
|
||||
} \
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@
|
|||
#define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver"
|
||||
#define EEEPC_LAPTOP_FILE "eeepc"
|
||||
|
||||
#define EEEPC_ACPI_CLASS "hotkey"
|
||||
#define EEEPC_ACPI_DEVICE_NAME "Hotkey"
|
||||
#define EEEPC_ACPI_HID "ASUS010"
|
||||
|
||||
MODULE_AUTHOR("Corentin Chary, Eric Cooper");
|
||||
|
|
@ -1214,8 +1212,7 @@ static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data)
|
|||
if (event > ACPI_MAX_SYS_NOTIFY)
|
||||
return;
|
||||
count = eeepc->event_count[event % 128]++;
|
||||
acpi_bus_generate_netlink_event(device->pnp.device_class,
|
||||
dev_name(&device->dev), event,
|
||||
acpi_bus_generate_netlink_event("hotkey", dev_name(&device->dev), event,
|
||||
count);
|
||||
|
||||
/* Brightness events are special */
|
||||
|
|
@ -1376,8 +1373,6 @@ static int eeepc_acpi_probe(struct platform_device *pdev)
|
|||
if (!eeepc)
|
||||
return -ENOMEM;
|
||||
eeepc->handle = device->handle;
|
||||
strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
|
||||
eeepc->device = device;
|
||||
|
||||
platform_set_drvdata(pdev, eeepc);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@
|
|||
|
||||
#define FUJITSU_LCD_N_LEVELS 8
|
||||
|
||||
#define ACPI_FUJITSU_CLASS "fujitsu"
|
||||
#define ACPI_FUJITSU_BL_HID "FUJ02B1"
|
||||
#define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver"
|
||||
#define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1"
|
||||
|
|
@ -466,7 +465,7 @@ static int acpi_fujitsu_bl_input_setup(struct device *dev)
|
|||
snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0",
|
||||
acpi_device_hid(device));
|
||||
|
||||
priv->input->name = acpi_device_name(device);
|
||||
priv->input->name = ACPI_FUJITSU_BL_DEVICE_NAME;
|
||||
priv->input->phys = priv->phys;
|
||||
priv->input->id.bustype = BUS_HOST;
|
||||
priv->input->id.product = 0x06;
|
||||
|
|
@ -546,13 +545,11 @@ static int acpi_fujitsu_bl_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
|
||||
fujitsu_bl = priv;
|
||||
strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
pr_info("ACPI: %s [%s]\n",
|
||||
acpi_device_name(device), acpi_device_bid(device));
|
||||
pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_BL_DEVICE_NAME,
|
||||
acpi_device_bid(device));
|
||||
|
||||
if (get_max_brightness(&pdev->dev) <= 0)
|
||||
priv->max_brightness = FUJITSU_LCD_N_LEVELS;
|
||||
|
|
@ -681,7 +678,7 @@ static int acpi_fujitsu_laptop_input_setup(struct device *dev)
|
|||
snprintf(priv->phys, sizeof(priv->phys), "%s/input0",
|
||||
acpi_device_hid(device));
|
||||
|
||||
priv->input->name = acpi_device_name(device);
|
||||
priv->input->name = ACPI_FUJITSU_LAPTOP_DEVICE_NAME;
|
||||
priv->input->phys = priv->phys;
|
||||
priv->input->id.bustype = BUS_HOST;
|
||||
|
||||
|
|
@ -1012,9 +1009,6 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev)
|
|||
WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended.");
|
||||
fext = &pdev->dev;
|
||||
|
||||
strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
/* kfifo */
|
||||
|
|
@ -1024,8 +1018,8 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
pr_info("ACPI: %s [%s]\n",
|
||||
acpi_device_name(device), acpi_device_bid(device));
|
||||
pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_LAPTOP_DEVICE_NAME,
|
||||
acpi_device_bid(device));
|
||||
|
||||
while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 &&
|
||||
i++ < MAX_HOTKEY_RINGBUFFER_SIZE)
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@
|
|||
|
||||
#define MODULENAME "fujitsu-tablet"
|
||||
|
||||
#define ACPI_FUJITSU_CLASS "fujitsu"
|
||||
|
||||
#define INVERT_TABLET_MODE_BIT 0x01
|
||||
#define INVERT_DOCK_STATE_BIT 0x02
|
||||
#define FORCE_TABLET_MODE_IF_UNDOCK 0x04
|
||||
|
|
@ -160,6 +158,7 @@ static struct {
|
|||
struct fujitsu_config config;
|
||||
unsigned long prev_keymask;
|
||||
|
||||
char name[17];
|
||||
char phys[21];
|
||||
|
||||
int irq;
|
||||
|
|
@ -458,14 +457,10 @@ static int acpi_fujitsu_probe(struct platform_device *pdev)
|
|||
if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base)
|
||||
return -ENODEV;
|
||||
|
||||
sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev));
|
||||
sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS);
|
||||
scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev));
|
||||
scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev));
|
||||
|
||||
snprintf(fujitsu.phys, sizeof(fujitsu.phys),
|
||||
"%s/input0", acpi_device_hid(adev));
|
||||
|
||||
error = input_fujitsu_setup(&pdev->dev,
|
||||
acpi_device_name(adev), fujitsu.phys);
|
||||
error = input_fujitsu_setup(&pdev->dev, fujitsu.name, fujitsu.phys);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_
|
|||
* bytes.
|
||||
*/
|
||||
conv_dst_size = size;
|
||||
if (size > dst_size)
|
||||
if (size >= dst_size)
|
||||
conv_dst_size = dst_size - 1;
|
||||
|
||||
/*
|
||||
|
|
@ -661,12 +661,17 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type,
|
|||
int ret = 0;
|
||||
|
||||
/* Take action appropriate to each ACPI TYPE */
|
||||
if (obj->package.count < min_elements) {
|
||||
pr_err("ACPI-package does not have enough elements: %d < %d\n",
|
||||
obj->package.count, min_elements);
|
||||
if (obj->package.count < COMMON_ELEM_CNT) {
|
||||
pr_err("ACPI-package is missing common elements: %d < %d\n",
|
||||
obj->package.count, COMMON_ELEM_CNT);
|
||||
goto pack_attr_exit;
|
||||
}
|
||||
|
||||
if (obj->package.count < min_elements) {
|
||||
pr_warn("ACPI-package has fewer elements than expected: %d < %d, parsing available elements\n",
|
||||
obj->package.count, min_elements);
|
||||
}
|
||||
|
||||
elements = obj->package.elements;
|
||||
|
||||
/* sanity checking */
|
||||
|
|
@ -731,26 +736,31 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type,
|
|||
switch (attr_type) {
|
||||
case HPWMI_STRING_TYPE:
|
||||
ret = hp_populate_string_package_data(elements,
|
||||
obj->package.count,
|
||||
instance_id,
|
||||
attr_name_kobj);
|
||||
break;
|
||||
case HPWMI_INTEGER_TYPE:
|
||||
ret = hp_populate_integer_package_data(elements,
|
||||
obj->package.count,
|
||||
instance_id,
|
||||
attr_name_kobj);
|
||||
break;
|
||||
case HPWMI_ENUMERATION_TYPE:
|
||||
ret = hp_populate_enumeration_package_data(elements,
|
||||
obj->package.count,
|
||||
instance_id,
|
||||
attr_name_kobj);
|
||||
break;
|
||||
case HPWMI_ORDERED_LIST_TYPE:
|
||||
ret = hp_populate_ordered_list_package_data(elements,
|
||||
obj->package.count,
|
||||
instance_id,
|
||||
attr_name_kobj);
|
||||
break;
|
||||
case HPWMI_PASSWORD_TYPE:
|
||||
ret = hp_populate_password_package_data(elements,
|
||||
obj->package.count,
|
||||
instance_id,
|
||||
attr_name_kobj);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -279,6 +279,9 @@ enum hp_wmi_data_elements {
|
|||
PSWD_ENCODINGS = 13,
|
||||
PSWD_IS_SET = 14,
|
||||
PSWD_ELEM_CNT = 15,
|
||||
|
||||
/* Minimum elements shared by all attribute types (NAME..SECURITY_LEVEL) */
|
||||
COMMON_ELEM_CNT = SECURITY_LEVEL + 1,
|
||||
};
|
||||
|
||||
#define GET_INSTANCE_ID(type) \
|
||||
|
|
@ -401,6 +404,7 @@ int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
|||
int hp_alloc_string_data(void);
|
||||
void hp_exit_string_attributes(void);
|
||||
int hp_populate_string_package_data(union acpi_object *str_obj,
|
||||
int str_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
|
||||
|
|
@ -411,6 +415,7 @@ int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
|||
int hp_alloc_integer_data(void);
|
||||
void hp_exit_integer_attributes(void);
|
||||
int hp_populate_integer_package_data(union acpi_object *integer_obj,
|
||||
int integer_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
|
||||
|
|
@ -421,6 +426,7 @@ int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
|||
int hp_alloc_enumeration_data(void);
|
||||
void hp_exit_enumeration_attributes(void);
|
||||
int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
|
||||
int enum_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
|
||||
|
|
@ -432,6 +438,7 @@ int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr,
|
|||
int hp_alloc_ordered_list_data(void);
|
||||
void hp_exit_ordered_list_attributes(void);
|
||||
int hp_populate_ordered_list_package_data(union acpi_object *order_obj,
|
||||
int order_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
|
||||
|
|
@ -440,6 +447,7 @@ int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
|
|||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int hp_populate_password_package_data(union acpi_object *password_obj,
|
||||
int password_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
int hp_alloc_password_data(void);
|
||||
|
|
|
|||
|
|
@ -163,10 +163,11 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
|
|||
|
||||
/* Check that both expected and read object type match */
|
||||
if (expected_enum_types[eloc] != enum_obj[elem].type) {
|
||||
pr_err("Error expected type %d for elem %d, but got type %d instead\n",
|
||||
expected_enum_types[eloc], elem, enum_obj[elem].type);
|
||||
pr_warn("Unexpected element type at elem %d: expected %d, got %d, skipping\n",
|
||||
elem, expected_enum_types[eloc], enum_obj[elem].type);
|
||||
kfree(str_value);
|
||||
return -EIO;
|
||||
str_value = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Assign appropriate element value to corresponding field */
|
||||
|
|
@ -227,6 +228,8 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
|
|||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
|
|
@ -280,6 +283,8 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
|
|||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += (size < MAX_VALUES_SIZE ? size : MAX_VALUES_SIZE) - 1;
|
||||
break;
|
||||
default:
|
||||
pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
|
||||
|
|
@ -300,10 +305,12 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
|
|||
* Populate all properties of an instance under enumeration attribute
|
||||
*
|
||||
* @enum_obj: ACPI object with enumeration data
|
||||
* @enum_obj_count: Number of elements in @enum_obj
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
|
||||
int enum_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
|
|
@ -312,7 +319,7 @@ int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
|
|||
enum_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
hp_populate_enumeration_elements_from_package(enum_obj,
|
||||
enum_obj->package.count,
|
||||
enum_obj_count,
|
||||
instance_id);
|
||||
hp_update_attribute_permissions(enum_data->common.is_readonly,
|
||||
&enumeration_current_val);
|
||||
|
|
|
|||
|
|
@ -243,6 +243,8 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_
|
|||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
|
|
@ -275,10 +277,12 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_
|
|||
* Populate all properties of an instance under integer attribute
|
||||
*
|
||||
* @integer_obj: ACPI object with integer data
|
||||
* @integer_obj_count: Number of elements in @integer_obj
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_integer_package_data(union acpi_object *integer_obj,
|
||||
int integer_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
|
|
@ -286,7 +290,7 @@ int hp_populate_integer_package_data(union acpi_object *integer_obj,
|
|||
|
||||
integer_data->attr_name_kobj = attr_name_kobj;
|
||||
hp_populate_integer_elements_from_package(integer_obj,
|
||||
integer_obj->package.count,
|
||||
integer_obj_count,
|
||||
instance_id);
|
||||
hp_update_attribute_permissions(integer_data->common.is_readonly,
|
||||
&integer_current_val);
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
|
|||
if (!order_obj)
|
||||
return -EINVAL;
|
||||
|
||||
for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT; elem++, eloc++) {
|
||||
for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT && elem < order_obj_count; elem++, eloc++) {
|
||||
|
||||
switch (order_obj[elem].type) {
|
||||
case ACPI_TYPE_STRING:
|
||||
|
|
@ -232,6 +232,8 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
|
|||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
|
|
@ -261,7 +263,9 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
|
|||
* Ordered list data is stored in hex and comma separated format
|
||||
* Convert the data and split it to show each element
|
||||
*/
|
||||
ret = hp_convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len);
|
||||
ret = hp_convert_hexstr_to_str(order_obj[elem].string.pointer,
|
||||
order_obj[elem].string.length,
|
||||
&tmpstr, &tmp_len);
|
||||
if (ret)
|
||||
goto exit_list;
|
||||
|
||||
|
|
@ -298,10 +302,12 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
|
|||
* Populate all properties of an instance under ordered_list attribute
|
||||
*
|
||||
* @order_obj: ACPI object with ordered_list data
|
||||
* @order_obj_count: Number of elements in @order_obj
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id,
|
||||
int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int order_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
|
||||
|
|
@ -309,7 +315,7 @@ int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int inst
|
|||
ordered_list_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
hp_populate_ordered_list_elements_from_package(order_obj,
|
||||
order_obj->package.count,
|
||||
order_obj_count,
|
||||
instance_id);
|
||||
hp_update_attribute_permissions(ordered_list_data->common.is_readonly,
|
||||
&ordered_list_current_val);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ static int validate_password_input(int instance_id, const char *buf)
|
|||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
|
||||
length = strlen(buf);
|
||||
if (buf[length - 1] == '\n')
|
||||
if (length > 0 && buf[length - 1] == '\n')
|
||||
length--;
|
||||
|
||||
if (length > MAX_PASSWD_SIZE)
|
||||
|
|
@ -123,7 +123,7 @@ static ssize_t new_password_store(struct kobject *kobj,
|
|||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
return store_password_instance(kobj, buf, count, true);
|
||||
return store_password_instance(kobj, buf, count, false);
|
||||
}
|
||||
|
||||
static struct kobj_attribute password_new_password = __ATTR_WO(new_password);
|
||||
|
|
@ -321,6 +321,8 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
|
|||
str_value = NULL;
|
||||
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
case SECURITY_LEVEL:
|
||||
password_data->common.security_level = int_value;
|
||||
|
|
@ -351,6 +353,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
|
|||
case PSWD_ENCODINGS:
|
||||
size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE);
|
||||
for (pos_values = 0; pos_values < size; pos_values++) {
|
||||
if (elem + pos_values >= password_obj_count) {
|
||||
pr_err("Error elem-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer,
|
||||
password_obj[elem + pos_values].string.length,
|
||||
&str_value, &value_len);
|
||||
|
|
@ -362,6 +369,8 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
|
|||
str_value = NULL;
|
||||
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
case PSWD_IS_SET:
|
||||
password_data->is_enabled = int_value;
|
||||
|
|
@ -385,10 +394,12 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
|
|||
* Populate all properties for an instance under password attribute
|
||||
*
|
||||
* @password_obj: ACPI object with password data
|
||||
* @password_obj_count: Number of elements in @password_obj
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_password_package_data(union acpi_object *password_obj, int instance_id,
|
||||
int hp_populate_password_package_data(union acpi_object *password_obj, int password_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
|
|
@ -396,7 +407,7 @@ int hp_populate_password_package_data(union acpi_object *password_obj, int insta
|
|||
password_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
hp_populate_password_elements_from_package(password_obj,
|
||||
password_obj->package.count,
|
||||
password_obj_count,
|
||||
instance_id);
|
||||
|
||||
hp_friendly_user_name_update(password_data->common.path,
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ static ssize_t sk_store(struct kobject *kobj,
|
|||
ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK,
|
||||
HPWMI_SECUREPLATFORM,
|
||||
(void *)bioscfg_drv.spm_data.signing_key,
|
||||
count, 0);
|
||||
length, 0);
|
||||
|
||||
if (!ret) {
|
||||
bioscfg_drv.spm_data.mechanism = SIGNING_KEY;
|
||||
|
|
@ -274,7 +274,7 @@ static ssize_t kek_store(struct kobject *kobj,
|
|||
ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK,
|
||||
HPWMI_SECUREPLATFORM,
|
||||
(void *)bioscfg_drv.spm_data.endorsement_key,
|
||||
count, 0);
|
||||
length, 0);
|
||||
|
||||
if (!ret) {
|
||||
bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY;
|
||||
|
|
|
|||
|
|
@ -233,6 +233,8 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob
|
|||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
|
|
@ -263,10 +265,12 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob
|
|||
* Populate all properties of an instance under string attribute
|
||||
*
|
||||
* @string_obj: ACPI object with string data
|
||||
* @string_obj_count: Number of elements in @string_obj
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int hp_populate_string_package_data(union acpi_object *string_obj,
|
||||
int string_obj_count,
|
||||
int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
|
|
@ -275,7 +279,7 @@ int hp_populate_string_package_data(union acpi_object *string_obj,
|
|||
string_data->attr_name_kobj = attr_name_kobj;
|
||||
|
||||
hp_populate_string_elements_from_package(string_obj,
|
||||
string_obj->package.count,
|
||||
string_obj_count,
|
||||
instance_id);
|
||||
|
||||
hp_update_attribute_permissions(string_data->common.is_readonly,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/dmi.h>
|
||||
|
|
@ -23,6 +25,7 @@
|
|||
#include <linux/input.h>
|
||||
#include <linux/input/sparse-keymap.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kstrtox.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
|
|
@ -58,6 +61,12 @@ enum hp_ec_offsets {
|
|||
#define HP_FAN_SPEED_AUTOMATIC 0x00
|
||||
#define HP_POWER_LIMIT_DEFAULT 0x00
|
||||
#define HP_POWER_LIMIT_NO_CHANGE 0xFF
|
||||
#define HPWMI_MUX_MODE_UMA BIT(0)
|
||||
#define HPWMI_MUX_MODE_HYBRID BIT(1)
|
||||
#define HPWMI_MUX_MODE_DISCRETE BIT(2)
|
||||
#define HPWMI_MUX_MODE_OPTIMUS BIT(3)
|
||||
#define HPWMI_MUX_MODE_MASK GENMASK(6, 0)
|
||||
#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE)
|
||||
|
||||
#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
|
||||
|
||||
|
|
@ -133,11 +142,52 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = {
|
|||
.ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET,
|
||||
};
|
||||
|
||||
/*
|
||||
* A generic pointer for the currently-active board's thermal profile
|
||||
* parameters.
|
||||
*/
|
||||
static struct thermal_profile_params *active_thermal_profile_params;
|
||||
struct hp_wmi_fan_profile_params {
|
||||
int (*get_fan_speed)(int fan);
|
||||
bool fan_table;
|
||||
};
|
||||
|
||||
struct hp_wmi_board_params {
|
||||
const struct thermal_profile_params *thermal_profile;
|
||||
const struct hp_wmi_fan_profile_params *fan_profile;
|
||||
};
|
||||
|
||||
static int hp_wmi_get_fan_speed_victus_s(int fan);
|
||||
|
||||
static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = {
|
||||
.get_fan_speed = hp_wmi_get_fan_speed_victus_s,
|
||||
.fan_table = true,
|
||||
};
|
||||
|
||||
static const struct hp_wmi_board_params victus_s_board_params = {
|
||||
.thermal_profile = &victus_s_thermal_params,
|
||||
.fan_profile = &victus_s_fan_profile_params,
|
||||
};
|
||||
|
||||
static const struct hp_wmi_board_params omen_v1_board_params = {
|
||||
.thermal_profile = &omen_v1_thermal_params,
|
||||
.fan_profile = &victus_s_fan_profile_params,
|
||||
};
|
||||
|
||||
static const struct hp_wmi_board_params omen_v1_legacy_board_params = {
|
||||
.thermal_profile = &omen_v1_legacy_thermal_params,
|
||||
.fan_profile = &victus_s_fan_profile_params,
|
||||
};
|
||||
|
||||
static const struct hp_wmi_board_params omen_v1_no_ec_board_params = {
|
||||
.thermal_profile = &omen_v1_no_ec_thermal_params,
|
||||
.fan_profile = &victus_s_fan_profile_params,
|
||||
};
|
||||
|
||||
static const struct hp_wmi_board_params *active_board_params;
|
||||
|
||||
static const struct thermal_profile_params *hp_wmi_thermal_profile(void)
|
||||
{
|
||||
if (!active_board_params)
|
||||
return NULL;
|
||||
|
||||
return active_board_params->thermal_profile;
|
||||
}
|
||||
|
||||
/* DMI board names of devices that should use the omen specific path for
|
||||
* thermal profiles.
|
||||
|
|
@ -157,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = {
|
|||
"886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6",
|
||||
"88F7", "88FD", "88FE", "88FF",
|
||||
"8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB",
|
||||
"8A15", "8A42",
|
||||
"8A15", "8A42", "8A43",
|
||||
"8BAD",
|
||||
"8C58",
|
||||
"8E41",
|
||||
|
|
@ -187,87 +237,111 @@ static const char * const victus_thermal_profile_boards[] = {
|
|||
"8A25",
|
||||
};
|
||||
|
||||
/* DMI Board names of Victus 16-r and Victus 16-s laptops */
|
||||
static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = {
|
||||
/* DMI board-specific feature data for Omen and Victus laptops. */
|
||||
static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") },
|
||||
.driver_data = (void *)&omen_v1_legacy_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") },
|
||||
.driver_data = (void *)&victus_s_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") },
|
||||
.driver_data = (void *)&omen_v1_legacy_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") },
|
||||
.driver_data = (void *)&omen_v1_legacy_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") },
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") },
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") },
|
||||
.driver_data = (void *)&omen_v1_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
.driver_data = (void *)&victus_s_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
.driver_data = (void *)&victus_s_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") },
|
||||
.driver_data = (void *)&omen_v1_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") },
|
||||
.driver_data = (void *)&omen_v1_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") },
|
||||
.driver_data = (void *)&omen_v1_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
.driver_data = (void *)&victus_s_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
.driver_data = (void *)&victus_s_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") },
|
||||
.driver_data = (void *)&omen_v1_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") },
|
||||
.driver_data = (void *)&omen_v1_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") },
|
||||
.driver_data = (void *)&omen_v1_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
.driver_data = (void *)&victus_s_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") },
|
||||
.driver_data = (void *)&victus_s_thermal_params,
|
||||
.driver_data = (void *)&victus_s_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") },
|
||||
.driver_data = (void *)&omen_v1_legacy_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_thermal_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") },
|
||||
.driver_data = (void *)&omen_v1_legacy_thermal_params,
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
|
@ -332,6 +406,7 @@ enum hp_wmi_commandtype {
|
|||
HPWMI_POSTCODEERROR_QUERY = 0x2a,
|
||||
HPWMI_SYSTEM_DEVICE_MODE = 0x40,
|
||||
HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
|
||||
HPWMI_GRAPHICS_MUX_QUERY = 0x52,
|
||||
};
|
||||
|
||||
struct victus_power_limits {
|
||||
|
|
@ -500,9 +575,9 @@ struct hp_wmi_hwmon_priv {
|
|||
struct mutex lock; /* protects mode, pwm */
|
||||
u8 min_rpm;
|
||||
u8 max_rpm;
|
||||
int gpu_delta;
|
||||
u8 mode;
|
||||
u8 pwm;
|
||||
u8 cpu_pwm;
|
||||
u8 gpu_pwm;
|
||||
struct delayed_work keep_alive_dwork;
|
||||
};
|
||||
|
||||
|
|
@ -829,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled)
|
|||
return enabled;
|
||||
}
|
||||
|
||||
static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
|
||||
static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv)
|
||||
{
|
||||
u8 fan_speed[2];
|
||||
int gpu_speed, ret;
|
||||
int ret;
|
||||
|
||||
fan_speed[CPU_FAN] = speed;
|
||||
fan_speed[GPU_FAN] = speed;
|
||||
if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC)
|
||||
fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC;
|
||||
else
|
||||
fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv);
|
||||
|
||||
/*
|
||||
* GPU fan speed is always a little higher than CPU fan speed, we fetch
|
||||
* this delta value from the fan table during hwmon init.
|
||||
* Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to
|
||||
* automatic mode.
|
||||
*/
|
||||
if (speed != HP_FAN_SPEED_AUTOMATIC) {
|
||||
gpu_speed = speed + priv->gpu_delta;
|
||||
fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX);
|
||||
}
|
||||
if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC)
|
||||
fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC;
|
||||
else
|
||||
fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv);
|
||||
|
||||
ret = hp_wmi_get_fan_count_userdefine_trigger();
|
||||
if (ret < 0)
|
||||
|
|
@ -863,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
|
|||
|
||||
static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv)
|
||||
{
|
||||
return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC);
|
||||
priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC;
|
||||
priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC;
|
||||
return hp_wmi_fan_speed_set(priv);
|
||||
}
|
||||
|
||||
static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv)
|
||||
|
|
@ -1124,12 +1197,134 @@ static int camera_shutter_input_setup(void)
|
|||
return err;
|
||||
}
|
||||
|
||||
static const u8 mux_bitmask_map[] = {
|
||||
[0] = HPWMI_MUX_MODE_HYBRID,
|
||||
[1] = HPWMI_MUX_MODE_DISCRETE,
|
||||
[2] = HPWMI_MUX_MODE_OPTIMUS,
|
||||
[3] = HPWMI_MUX_MODE_UMA,
|
||||
};
|
||||
|
||||
static int hp_wmi_get_mux_supported_modes(u8 *supported)
|
||||
{
|
||||
u8 legacy_buffer[4] = {};
|
||||
u8 buffer[128] = {};
|
||||
u32 req_packet = 0;
|
||||
int ret;
|
||||
|
||||
if (!supported)
|
||||
return -EINVAL;
|
||||
|
||||
/* Try modern BIOS design data query (128-byte buffer) */
|
||||
ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM,
|
||||
buffer, zero_if_sup(req_packet), sizeof(buffer));
|
||||
if (ret == 0) {
|
||||
*supported = buffer[7];
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (Fallback): Legacy BIOS behavior based on Omen Gaming Hub.
|
||||
* If the modern query is not supported, check if the MUX query endpoint
|
||||
* responds to a read request. If it succeeds, the hardware has MUX
|
||||
* capability but lacks the mode map, defaulting to Hybrid + Discrete.
|
||||
*/
|
||||
ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
|
||||
legacy_buffer, sizeof(legacy_buffer), 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret > 0)
|
||||
return -EINVAL;
|
||||
|
||||
*supported = HPWMI_MUX_LEGACY_MASK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hp_wmi_get_mux_mode(u8 *mode)
|
||||
{
|
||||
u8 buffer[4] = {};
|
||||
int ret;
|
||||
|
||||
if (!mode)
|
||||
return -EINVAL;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ,
|
||||
buffer, sizeof(buffer), sizeof(buffer));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret > 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Mask the highest bit, which might be used as a BIOS status flag */
|
||||
*mode = buffer[0] & HPWMI_MUX_MODE_MASK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hp_wmi_set_mux_mode(u8 mode)
|
||||
{
|
||||
u8 buffer[4] = { mode, 0x00, 0x00, 0x00 };
|
||||
int ret;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE,
|
||||
buffer, sizeof(buffer), sizeof(buffer));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret > 0)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t gpu_mux_mode_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
u8 mode;
|
||||
int ret;
|
||||
|
||||
ret = hp_wmi_get_mux_mode(&mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%u\n", mode);
|
||||
}
|
||||
|
||||
static ssize_t gpu_mux_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
u32 requested;
|
||||
u8 supported;
|
||||
int ret;
|
||||
|
||||
ret = kstrtou32(buf, 0, &requested);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (requested >= ARRAY_SIZE(mux_bitmask_map))
|
||||
return -EINVAL;
|
||||
|
||||
ret = hp_wmi_get_mux_supported_modes(&supported);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Verify if the requested mode is allowed by the hardware mask */
|
||||
if (!(supported & mux_bitmask_map[requested]))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = hp_wmi_set_mux_mode(requested);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(display);
|
||||
static DEVICE_ATTR_RO(hddtemp);
|
||||
static DEVICE_ATTR_RW(als);
|
||||
static DEVICE_ATTR_RO(dock);
|
||||
static DEVICE_ATTR_RO(tablet);
|
||||
static DEVICE_ATTR_RW(postcode);
|
||||
static DEVICE_ATTR_RW(gpu_mux_mode);
|
||||
|
||||
static struct attribute *hp_wmi_attrs[] = {
|
||||
&dev_attr_display.attr,
|
||||
|
|
@ -1138,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = {
|
|||
&dev_attr_dock.attr,
|
||||
&dev_attr_tablet.attr,
|
||||
&dev_attr_postcode.attr,
|
||||
&dev_attr_gpu_mux_mode.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(hp_wmi);
|
||||
|
|
@ -1789,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void)
|
|||
return is_victus_s_board;
|
||||
}
|
||||
|
||||
static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void)
|
||||
{
|
||||
if (!active_board_params)
|
||||
return NULL;
|
||||
|
||||
return active_board_params->fan_profile;
|
||||
}
|
||||
|
||||
static bool hp_wmi_fan_control_supported(void)
|
||||
{
|
||||
const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
|
||||
|
||||
return params && params->get_fan_speed;
|
||||
}
|
||||
|
||||
static bool hp_wmi_fan_table_supported(void)
|
||||
{
|
||||
const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
|
||||
|
||||
return params && params->fan_table;
|
||||
}
|
||||
|
||||
static int hp_wmi_get_active_fan_speed(int fan)
|
||||
{
|
||||
const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile();
|
||||
|
||||
if (!params || !params->get_fan_speed)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return params->get_fan_speed(fan);
|
||||
}
|
||||
|
||||
static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable,
|
||||
bool *ppab_enable,
|
||||
u8 *dstate,
|
||||
|
|
@ -1874,7 +2102,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil
|
|||
u8 current_dstate, current_gpu_slowdown_temp, tp;
|
||||
const struct thermal_profile_params *params;
|
||||
|
||||
params = active_thermal_profile_params;
|
||||
params = hp_wmi_thermal_profile();
|
||||
if (!params)
|
||||
return -ENODEV;
|
||||
|
||||
if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN ||
|
||||
params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) {
|
||||
*profile = active_platform_profile;
|
||||
|
|
@ -1886,10 +2117,10 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil
|
|||
return ret;
|
||||
|
||||
/*
|
||||
* We cannot use active_thermal_profile_params here, because boards
|
||||
* like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically
|
||||
* it should have tp == 0x30 || tp == 0x31, as corrected by the Omen
|
||||
* Gaming Hub on windows. Hence accept both of these values.
|
||||
* Boards like 8C78 have tp == 0x0 || tp == 0x1 after cold boot,
|
||||
* but logically it should have tp == 0x30 || tp == 0x31, as
|
||||
* corrected by the Omen Gaming Hub on windows. Hence accept both
|
||||
* of these values.
|
||||
*/
|
||||
if (tp == victus_s_thermal_params.performance ||
|
||||
tp == omen_v1_thermal_params.performance) {
|
||||
|
|
@ -1924,12 +2155,12 @@ static int platform_profile_victus_s_get_ec(enum platform_profile_option *profil
|
|||
|
||||
static int platform_profile_victus_s_set_ec(enum platform_profile_option profile)
|
||||
{
|
||||
struct thermal_profile_params *params;
|
||||
const struct thermal_profile_params *params;
|
||||
bool gpu_ctgp_enable, gpu_ppab_enable;
|
||||
u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */
|
||||
int err, tp;
|
||||
|
||||
params = active_thermal_profile_params;
|
||||
params = hp_wmi_thermal_profile();
|
||||
if (!params)
|
||||
return -ENODEV;
|
||||
|
||||
|
|
@ -2195,6 +2426,7 @@ static const struct platform_profile_ops hp_wmi_platform_profile_ops = {
|
|||
static int thermal_profile_setup(struct platform_device *device)
|
||||
{
|
||||
const struct platform_profile_ops *ops;
|
||||
const struct thermal_profile_params *params;
|
||||
int err, tp;
|
||||
|
||||
if (is_omen_thermal_profile()) {
|
||||
|
|
@ -2226,13 +2458,17 @@ static int thermal_profile_setup(struct platform_device *device)
|
|||
|
||||
ops = &platform_profile_victus_ops;
|
||||
} else if (is_victus_s_thermal_profile()) {
|
||||
params = hp_wmi_thermal_profile();
|
||||
if (!params)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* For an unknown EC layout board, platform_profile_victus_s_get_ec(),
|
||||
* behaves like a wrapper around active_platform_profile, to avoid using
|
||||
* uninitialized data, we default to PLATFORM_PROFILE_BALANCED.
|
||||
*/
|
||||
if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN ||
|
||||
active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) {
|
||||
if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN ||
|
||||
params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) {
|
||||
active_platform_profile = PLATFORM_PROFILE_BALANCED;
|
||||
} else {
|
||||
err = platform_profile_victus_s_get_ec(&active_platform_profile);
|
||||
|
|
@ -2400,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
|
|||
|
||||
switch (priv->mode) {
|
||||
case PWM_MODE_MAX:
|
||||
if (is_victus_s_thermal_profile()) {
|
||||
if (hp_wmi_fan_control_supported()) {
|
||||
ret = hp_wmi_get_fan_count_userdefine_trigger();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
|
@ -2412,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
|
|||
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
|
||||
return 0;
|
||||
case PWM_MODE_MANUAL:
|
||||
if (!is_victus_s_thermal_profile())
|
||||
if (!hp_wmi_fan_control_supported())
|
||||
return -EOPNOTSUPP;
|
||||
ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
|
||||
ret = hp_wmi_fan_speed_set(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork,
|
||||
secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
|
||||
return 0;
|
||||
case PWM_MODE_AUTO:
|
||||
if (is_victus_s_thermal_profile()) {
|
||||
if (hp_wmi_fan_control_supported()) {
|
||||
ret = hp_wmi_get_fan_count_userdefine_trigger();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
|
@ -2445,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
|
|||
{
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile())
|
||||
if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported())
|
||||
return 0;
|
||||
return 0644;
|
||||
case hwmon_fan:
|
||||
if (is_victus_s_thermal_profile()) {
|
||||
if (hp_wmi_get_fan_speed_victus_s(channel) >= 0)
|
||||
if (hp_wmi_fan_control_supported()) {
|
||||
if (hp_wmi_get_active_fan_speed(channel) >= 0)
|
||||
return 0444;
|
||||
} else {
|
||||
if (hp_wmi_get_fan_speed(channel) >= 0)
|
||||
|
|
@ -2474,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
priv = dev_get_drvdata(dev);
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
if (is_victus_s_thermal_profile())
|
||||
ret = hp_wmi_get_fan_speed_victus_s(channel);
|
||||
if (hp_wmi_fan_control_supported())
|
||||
ret = hp_wmi_get_active_fan_speed(channel);
|
||||
else
|
||||
ret = hp_wmi_get_fan_speed(channel);
|
||||
if (ret < 0)
|
||||
|
|
@ -2484,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
return 0;
|
||||
case hwmon_pwm:
|
||||
if (attr == hwmon_pwm_input) {
|
||||
if (!is_victus_s_thermal_profile())
|
||||
if (!hp_wmi_fan_control_supported())
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
rpm = hp_wmi_get_fan_speed_victus_s(channel);
|
||||
rpm = hp_wmi_get_active_fan_speed(channel);
|
||||
if (rpm < 0)
|
||||
return rpm;
|
||||
*val = rpm_to_pwm(rpm / 100, priv);
|
||||
|
|
@ -2514,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct hp_wmi_hwmon_priv *priv;
|
||||
int rpm;
|
||||
int cpu_rpm, gpu_rpm;
|
||||
|
||||
priv = dev_get_drvdata(dev);
|
||||
guard(mutex)(&priv->lock);
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
if (attr == hwmon_pwm_input) {
|
||||
if (!is_victus_s_thermal_profile())
|
||||
int rpm;
|
||||
if (!hp_wmi_fan_control_supported())
|
||||
return -EOPNOTSUPP;
|
||||
/* PWM input is invalid when not in manual mode */
|
||||
if (priv->mode != PWM_MODE_MANUAL)
|
||||
|
|
@ -2530,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
/* ensure PWM input is within valid fan speeds */
|
||||
rpm = pwm_to_rpm(val, priv);
|
||||
rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm);
|
||||
priv->pwm = rpm_to_pwm(rpm, priv);
|
||||
if (channel == CPU_FAN)
|
||||
priv->cpu_pwm = rpm_to_pwm(rpm, priv);
|
||||
else if (channel == GPU_FAN)
|
||||
priv->gpu_pwm = rpm_to_pwm(rpm, priv);
|
||||
return hp_wmi_apply_fan_settings(priv);
|
||||
}
|
||||
switch (val) {
|
||||
|
|
@ -2538,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
priv->mode = PWM_MODE_MAX;
|
||||
return hp_wmi_apply_fan_settings(priv);
|
||||
case PWM_MODE_MANUAL:
|
||||
if (!is_victus_s_thermal_profile())
|
||||
if (!hp_wmi_fan_control_supported())
|
||||
return -EOPNOTSUPP;
|
||||
/*
|
||||
* When switching to manual mode, set fan speed to
|
||||
* current RPM values to ensure a smooth transition.
|
||||
*/
|
||||
rpm = hp_wmi_get_fan_speed_victus_s(channel);
|
||||
if (rpm < 0)
|
||||
return rpm;
|
||||
priv->pwm = rpm_to_pwm(rpm / 100, priv);
|
||||
cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN);
|
||||
if (cpu_rpm < 0)
|
||||
return cpu_rpm;
|
||||
gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN);
|
||||
if (gpu_rpm < 0)
|
||||
return gpu_rpm;
|
||||
priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv);
|
||||
priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv);
|
||||
priv->mode = PWM_MODE_MANUAL;
|
||||
return hp_wmi_apply_fan_settings(priv);
|
||||
case PWM_MODE_AUTO:
|
||||
|
|
@ -2563,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
|||
|
||||
static const struct hwmon_channel_info * const info[] = {
|
||||
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT),
|
||||
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
@ -2604,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
|
|||
struct victus_s_fan_table *fan_table;
|
||||
u8 min_rpm, max_rpm;
|
||||
u8 cpu_rpm, gpu_rpm, noise_db;
|
||||
int gpu_delta, i, num_entries, ret;
|
||||
int i, num_entries, ret;
|
||||
size_t header_size, entry_size;
|
||||
|
||||
/* Default behaviour on hwmon init is automatic mode */
|
||||
priv->mode = PWM_MODE_AUTO;
|
||||
|
||||
/* Bypass all non-Victus S devices */
|
||||
if (!is_victus_s_thermal_profile())
|
||||
/* Bypass devices without fan control support. */
|
||||
if (!hp_wmi_fan_table_supported())
|
||||
return 0;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY,
|
||||
|
|
@ -2650,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
|
|||
if (min_rpm == U8_MAX || max_rpm == 0)
|
||||
return -EINVAL;
|
||||
|
||||
gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm;
|
||||
priv->min_rpm = min_rpm;
|
||||
priv->max_rpm = max_rpm;
|
||||
priv->gpu_delta = gpu_delta;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2693,24 +2935,25 @@ static int hp_wmi_hwmon_init(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void __init setup_active_thermal_profile_params(void)
|
||||
static void __init setup_active_board_params(void)
|
||||
{
|
||||
const struct dmi_system_id *id;
|
||||
const struct thermal_profile_params *params;
|
||||
|
||||
/*
|
||||
* Currently only victus_s devices use the
|
||||
* active_thermal_profile_params
|
||||
*/
|
||||
id = dmi_first_match(victus_s_thermal_profile_boards);
|
||||
id = dmi_first_match(hp_wmi_feature_boards);
|
||||
if (id) {
|
||||
active_board_params = id->driver_data;
|
||||
params = hp_wmi_thermal_profile();
|
||||
if (!params)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Marking this boolean is required to ensure that
|
||||
* is_victus_s_thermal_profile() behaves like a valid
|
||||
* wrapper.
|
||||
*/
|
||||
is_victus_s_board = true;
|
||||
active_thermal_profile_params = id->driver_data;
|
||||
if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
|
||||
if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
|
||||
pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n",
|
||||
dmi_get_system_info(DMI_BOARD_NAME));
|
||||
}
|
||||
|
|
@ -2745,10 +2988,10 @@ static int __init hp_wmi_init(void)
|
|||
}
|
||||
|
||||
/*
|
||||
* Setup active board's thermal profile parameters before
|
||||
* starting platform driver probe.
|
||||
* Setup active board feature data before starting platform
|
||||
* driver probe.
|
||||
*/
|
||||
setup_active_thermal_profile_params();
|
||||
setup_active_board_params();
|
||||
err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
|
||||
if (err)
|
||||
goto err_unregister_device;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dmi.h>
|
||||
|
|
@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev)
|
|||
|
||||
/* Fn lock */
|
||||
|
||||
/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */
|
||||
#define FN_LOCK_ACPI_OFF 1
|
||||
#define FN_LOCK_ACPI_ON 2
|
||||
#define FN_LOCK_ACPI_STAT_OK 0
|
||||
|
||||
/*
|
||||
* Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct
|
||||
* ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control
|
||||
* Fn-lock via EC registers 0x6B (read) and 0x6C (write).
|
||||
*
|
||||
* GFRS response buffer layout:
|
||||
* byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success)
|
||||
* byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on)
|
||||
*
|
||||
* SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)):
|
||||
* Value is read from byte[2] of the integer argument, so it must be
|
||||
* passed as (value << 16):
|
||||
* (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C)
|
||||
* (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C)
|
||||
*/
|
||||
|
||||
static int huawei_acpi_fn_lock_get(int *on)
|
||||
{
|
||||
union acpi_object acpi_arg;
|
||||
struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg };
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_status status;
|
||||
|
||||
acpi_arg.type = ACPI_TYPE_INTEGER;
|
||||
acpi_arg.integer.value = 0;
|
||||
|
||||
status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
union acpi_object *obj __free(kfree) = output.pointer;
|
||||
if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2)
|
||||
return -ENODATA;
|
||||
|
||||
/* byte[0] = STAT, byte[1] = fn-lock state */
|
||||
if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK)
|
||||
return -EIO;
|
||||
|
||||
switch (obj->buffer.pointer[1]) {
|
||||
case FN_LOCK_ACPI_OFF:
|
||||
if (on)
|
||||
*on = 0;
|
||||
break;
|
||||
case FN_LOCK_ACPI_ON:
|
||||
if (on)
|
||||
*on = 1;
|
||||
break;
|
||||
default:
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int huawei_acpi_fn_lock_set(int on)
|
||||
{
|
||||
union acpi_object acpi_arg;
|
||||
struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg };
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_status status;
|
||||
|
||||
/*
|
||||
* SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02).
|
||||
* on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off)
|
||||
* on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on)
|
||||
*/
|
||||
acpi_arg.type = ACPI_TYPE_INTEGER;
|
||||
acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16;
|
||||
|
||||
status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
union acpi_object *obj __free(kfree) = output.pointer;
|
||||
if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1)
|
||||
return -ENODATA;
|
||||
|
||||
if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int huawei_wmi_fn_lock_get(int *on)
|
||||
{
|
||||
u8 ret[0x100] = { 0 };
|
||||
int err, i;
|
||||
|
||||
/* Newer models: use direct ACPI \GFRS method */
|
||||
if (acpi_has_method(NULL, "\\GFRS"))
|
||||
return huawei_acpi_fn_lock_get(on);
|
||||
|
||||
/* WMI fallback */
|
||||
err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100);
|
||||
if (err)
|
||||
return err;
|
||||
|
|
@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on)
|
|||
{
|
||||
union hwmi_arg arg;
|
||||
|
||||
/* Newer models: use direct ACPI \SFRS method */
|
||||
if (acpi_has_method(NULL, "\\SFRS"))
|
||||
return huawei_acpi_fn_lock_set(on);
|
||||
|
||||
/* WMI fallback */
|
||||
arg.cmd = FN_LOCK_SET;
|
||||
arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on.
|
||||
|
||||
|
|
|
|||
|
|
@ -91,8 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob
|
|||
item->package.count <= data->total_dev_mode)
|
||||
return AE_ERROR;
|
||||
|
||||
data->device_mode_info = kmalloc_objs(struct wwan_device_mode_info,
|
||||
data->total_dev_mode);
|
||||
data->device_mode_info = devm_kcalloc(&context->sar_device->dev,
|
||||
data->total_dev_mode,
|
||||
sizeof(*data->device_mode_info),
|
||||
GFP_KERNEL);
|
||||
if (!data->device_mode_info)
|
||||
return AE_ERROR;
|
||||
|
||||
|
|
@ -253,7 +255,7 @@ static int sar_probe(struct platform_device *device)
|
|||
if (!handle)
|
||||
return -ENODEV;
|
||||
|
||||
context = kzalloc_obj(*context);
|
||||
context = devm_kzalloc(&device->dev, sizeof(*context), GFP_KERNEL);
|
||||
if (!context)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -264,7 +266,7 @@ static int sar_probe(struct platform_device *device)
|
|||
result = guid_parse(SAR_DSM_UUID, &context->guid);
|
||||
if (result) {
|
||||
dev_err(&device->dev, "SAR UUID parse error: %d\n", result);
|
||||
goto r_free;
|
||||
return result;
|
||||
}
|
||||
|
||||
for (reg = 0; reg < MAX_REGULATORY; reg++)
|
||||
|
|
@ -272,43 +274,29 @@ static int sar_probe(struct platform_device *device)
|
|||
|
||||
if (sar_get_device_mode(device) != AE_OK) {
|
||||
dev_err(&device->dev, "Failed to get device mode\n");
|
||||
result = -EIO;
|
||||
goto r_free;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
result = sysfs_create_group(&device->dev.kobj, &intcsar_group);
|
||||
if (result) {
|
||||
dev_err(&device->dev, "sysfs creation failed\n");
|
||||
goto r_free;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (acpi_install_notify_handler(ACPI_HANDLE(&device->dev), ACPI_DEVICE_NOTIFY,
|
||||
sar_notify, (void *)device) != AE_OK) {
|
||||
dev_err(&device->dev, "Failed acpi_install_notify_handler\n");
|
||||
result = -EIO;
|
||||
goto r_sys;
|
||||
sysfs_remove_group(&device->dev.kobj, &intcsar_group);
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
|
||||
r_sys:
|
||||
sysfs_remove_group(&device->dev.kobj, &intcsar_group);
|
||||
r_free:
|
||||
kfree(context);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void sar_remove(struct platform_device *device)
|
||||
{
|
||||
struct wwan_sar_context *context = dev_get_drvdata(&device->dev);
|
||||
int reg;
|
||||
|
||||
acpi_remove_notify_handler(ACPI_HANDLE(&device->dev),
|
||||
ACPI_DEVICE_NOTIFY, sar_notify);
|
||||
sysfs_remove_group(&device->dev.kobj, &intcsar_group);
|
||||
for (reg = 0; reg < MAX_REGULATORY; reg++)
|
||||
kfree(context->config_data[reg].device_mode_info);
|
||||
|
||||
kfree(context);
|
||||
}
|
||||
|
||||
static struct platform_driver sar_driver = {
|
||||
|
|
|
|||
|
|
@ -600,13 +600,16 @@ static int ecl_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
|
|||
rv = acpi_opregion_init(opr_dev);
|
||||
if (rv) {
|
||||
dev_err(cl_data_to_dev(opr_dev), "ACPI opregion init failed\n");
|
||||
goto err_exit;
|
||||
goto err_put;
|
||||
}
|
||||
|
||||
/* Reprobe devices depending on ECLite - battery, fan, etc. */
|
||||
acpi_dev_clear_dependencies(opr_dev->adev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_put:
|
||||
acpi_dev_put(opr_dev->adev);
|
||||
err_exit:
|
||||
ishtp_set_connection_state(ecl_ishtp_cl, ISHTP_CL_DISCONNECTING);
|
||||
ishtp_cl_disconnect(ecl_ishtp_cl);
|
||||
|
|
|
|||
|
|
@ -1583,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel
|
|||
{
|
||||
const u8 *lpm_indices;
|
||||
int num_maps, mode_offset = 0;
|
||||
int ret, lpm_size;
|
||||
int ret = 0, lpm_size;
|
||||
u8 mode;
|
||||
|
||||
lpm_indices = pmc->map->lpm_reg_index;
|
||||
|
|
|
|||
|
|
@ -336,8 +336,11 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev,
|
|||
int i;
|
||||
|
||||
pd_info->perf_levels = devm_kcalloc(dev, levels, sizeof(struct perf_level), GFP_KERNEL);
|
||||
if (!pd_info->perf_levels)
|
||||
return 0;
|
||||
if (!pd_info->perf_levels) {
|
||||
pd_info->pp_header.allowed_level_mask = 0;
|
||||
pd_info->pp_header.level_en_mask = 0;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pd_info->ratio_unit = pd_info->pp_header.ratio_unit;
|
||||
pd_info->avx_levels = SST_MAX_AVX_LEVELS;
|
||||
|
|
@ -367,7 +370,7 @@ static int sst_add_perf_profiles(struct auxiliary_device *auxdev,
|
|||
static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domain_info *pd_info)
|
||||
{
|
||||
struct device *dev = &auxdev->dev;
|
||||
int i, mask, levels;
|
||||
int i, ret, mask, levels;
|
||||
|
||||
*((u64 *)&pd_info->sst_header) = readq(pd_info->sst_base);
|
||||
pd_info->sst_header.cp_offset *= 8;
|
||||
|
|
@ -399,8 +402,12 @@ static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domai
|
|||
levels = i;
|
||||
mask <<= 1;
|
||||
}
|
||||
|
||||
ret = sst_add_perf_profiles(auxdev, pd_info, levels + 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pd_info->max_level = levels;
|
||||
sst_add_perf_profiles(auxdev, pd_info, levels + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -599,6 +606,9 @@ static bool disable_dynamic_sst_features(void)
|
|||
#define SST_CP_PRIORITY_TYPE_START 1
|
||||
#define SST_CP_PRIORITY_TYPE_WIDTH 1
|
||||
|
||||
#define SST_CP_MAX_ENABLE 1
|
||||
#define SST_CP_MAX_PRIORITY_TYPE 1
|
||||
|
||||
static long isst_if_core_power_state(void __user *argp)
|
||||
{
|
||||
struct tpmi_per_power_domain_info *power_domain_info;
|
||||
|
|
@ -618,6 +628,10 @@ static long isst_if_core_power_state(void __user *argp)
|
|||
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (core_power.enable > SST_CP_MAX_ENABLE ||
|
||||
core_power.priority_type > SST_CP_MAX_PRIORITY_TYPE)
|
||||
return -EINVAL;
|
||||
|
||||
_write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET,
|
||||
SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
|
||||
_write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET,
|
||||
|
|
@ -649,6 +663,11 @@ static long isst_if_core_power_state(void __user *argp)
|
|||
#define SST_CLOS_CONFIG_MAX_START 16
|
||||
#define SST_CLOS_CONFIG_MAX_WIDTH 8
|
||||
|
||||
#define SST_MAX_CLOS 3
|
||||
|
||||
#define SST_MAX_FREQ 0xff
|
||||
#define SST_CLOS_MAX_PRIORITY 0x0f
|
||||
|
||||
static long isst_if_clos_param(void __user *argp)
|
||||
{
|
||||
struct tpmi_per_power_domain_info *power_domain_info;
|
||||
|
|
@ -657,6 +676,9 @@ static long isst_if_clos_param(void __user *argp)
|
|||
if (copy_from_user(&clos_param, argp, sizeof(clos_param)))
|
||||
return -EFAULT;
|
||||
|
||||
if (clos_param.clos > SST_MAX_CLOS)
|
||||
return -EINVAL;
|
||||
|
||||
power_domain_info = get_instance(clos_param.socket_id, clos_param.power_domain_id);
|
||||
if (!power_domain_info)
|
||||
return -EINVAL;
|
||||
|
|
@ -665,6 +687,15 @@ static long isst_if_clos_param(void __user *argp)
|
|||
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!in_range(clos_param.min_freq_mhz / SST_MUL_FACTOR_FREQ, 0, SST_MAX_FREQ + 1))
|
||||
return -EINVAL;
|
||||
|
||||
if (!in_range(clos_param.max_freq_mhz / SST_MUL_FACTOR_FREQ, 0, SST_MAX_FREQ + 1))
|
||||
return -EINVAL;
|
||||
|
||||
if (!in_range(clos_param.prop_prio, 0, SST_CLOS_MAX_PRIORITY + 1))
|
||||
return -EINVAL;
|
||||
|
||||
_write_cp_info("clos.min_freq", clos_param.min_freq_mhz,
|
||||
(SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE),
|
||||
SST_CLOS_CONFIG_MIN_START, SST_CLOS_CONFIG_MIN_WIDTH,
|
||||
|
|
@ -703,6 +734,8 @@ static long isst_if_clos_param(void __user *argp)
|
|||
#define SST_CLOS_ASSOC_CPUS_PER_REG 16
|
||||
#define SST_CLOS_ASSOC_BITS_PER_CPU 4
|
||||
|
||||
#define SST_CLOS_ASSOC_MAX_LOGICAL_CPU 63
|
||||
|
||||
static long isst_if_clos_assoc(void __user *argp)
|
||||
{
|
||||
struct isst_if_clos_assoc_cmds assoc_cmds;
|
||||
|
|
@ -729,7 +762,13 @@ static long isst_if_clos_assoc(void __user *argp)
|
|||
if (copy_from_user(&clos_assoc, ptr, sizeof(clos_assoc)))
|
||||
return -EFAULT;
|
||||
|
||||
if (clos_assoc.socket_id > topology_max_packages())
|
||||
if (clos_assoc.clos > SST_MAX_CLOS)
|
||||
return -EINVAL;
|
||||
|
||||
if (clos_assoc.socket_id >= topology_max_packages())
|
||||
return -EINVAL;
|
||||
|
||||
if (clos_assoc.logical_cpu > SST_CLOS_ASSOC_MAX_LOGICAL_CPU)
|
||||
return -EINVAL;
|
||||
|
||||
cpu = clos_assoc.logical_cpu;
|
||||
|
|
@ -747,6 +786,8 @@ static long isst_if_clos_assoc(void __user *argp)
|
|||
pkg_id = clos_assoc.socket_id;
|
||||
|
||||
sst_inst = isst_common.sst_inst[pkg_id];
|
||||
if (!sst_inst)
|
||||
return -EINVAL;
|
||||
|
||||
punit_id = map_partition_power_domain_id(sst_inst, punit_id, &part);
|
||||
if (punit_id < 0)
|
||||
|
|
@ -845,6 +886,7 @@ static long isst_if_clos_assoc(void __user *argp)
|
|||
|
||||
#define SST_PP_FEATURE_STATE_START 8
|
||||
#define SST_PP_FEATURE_STATE_WIDTH 8
|
||||
#define SST_PP_FEATURE_STATE_VALID_MASK GENMASK(1, 0)
|
||||
|
||||
#define SST_BF_FEATURE_SUPPORTED_START 12
|
||||
#define SST_BF_FEATURE_SUPPORTED_WIDTH 1
|
||||
|
|
@ -877,7 +919,7 @@ static int isst_if_get_perf_level(void __user *argp)
|
|||
SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE)
|
||||
perf_level.enabled = !!(power_domain_info->sst_header.cap_mask & BIT(1));
|
||||
|
||||
level_mask = perf_level.level_mask;
|
||||
level_mask = perf_level.level_mask & power_domain_info->pp_header.level_en_mask;
|
||||
perf_level.sst_bf_support = 0;
|
||||
for_each_set_bit(level, &level_mask, BITS_PER_BYTE) {
|
||||
/*
|
||||
|
|
@ -932,6 +974,9 @@ static int isst_if_set_perf_level(void __user *argp)
|
|||
if (!power_domain_info)
|
||||
return -EINVAL;
|
||||
|
||||
if (perf_level.level > power_domain_info->max_level)
|
||||
return -EINVAL;
|
||||
|
||||
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
|
|
@ -995,6 +1040,9 @@ static int isst_if_set_perf_feature(void __user *argp)
|
|||
if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (perf_feature.feature & ~SST_PP_FEATURE_STATE_VALID_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
_write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET,
|
||||
SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH,
|
||||
SST_MUL_FACTOR_NONE)
|
||||
|
|
@ -1261,6 +1309,12 @@ static int isst_if_get_perf_level_mask(void __user *argp)
|
|||
if (!power_domain_info)
|
||||
return -EINVAL;
|
||||
|
||||
if (cpumask.level > power_domain_info->max_level)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(power_domain_info->pp_header.level_en_mask & BIT(cpumask.level)))
|
||||
return -EINVAL;
|
||||
|
||||
_read_pp_level_info("mask", mask, cpumask.level, SST_PP_INFO_2_OFFSET,
|
||||
SST_PP_RSLVD_CORE_MASK_START, SST_PP_RSLVD_CORE_MASK_WIDTH,
|
||||
SST_MUL_FACTOR_NONE)
|
||||
|
|
@ -1306,6 +1360,9 @@ static int isst_if_get_base_freq_info(void __user *argp)
|
|||
if (base_freq.level > power_domain_info->max_level)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(power_domain_info->pp_header.level_en_mask & BIT(base_freq.level)))
|
||||
return -EINVAL;
|
||||
|
||||
_read_bf_level_info("p1_high", base_freq.high_base_freq_mhz, base_freq.level,
|
||||
SST_BF_INFO_0_OFFSET, SST_BF_P1_HIGH_START, SST_BF_P1_HIGH_WIDTH,
|
||||
SST_MUL_FACTOR_FREQ)
|
||||
|
|
@ -1342,6 +1399,12 @@ static int isst_if_get_base_freq_mask(void __user *argp)
|
|||
if (!power_domain_info)
|
||||
return -EINVAL;
|
||||
|
||||
if (cpumask.level > power_domain_info->max_level)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(power_domain_info->pp_header.level_en_mask & BIT(cpumask.level)))
|
||||
return -EINVAL;
|
||||
|
||||
_read_bf_level_info("BF-cpumask", mask, cpumask.level, SST_BF_INFO_1_OFFSET,
|
||||
P1_HI_CORE_MASK_START, P1_HI_CORE_MASK_WIDTH,
|
||||
SST_MUL_FACTOR_NONE)
|
||||
|
|
@ -1370,6 +1433,8 @@ static int isst_if_get_tpmi_instance_count(void __user *argp)
|
|||
return -EINVAL;
|
||||
|
||||
sst_inst = isst_common.sst_inst[tpmi_inst.socket_id];
|
||||
if (!sst_inst)
|
||||
return -EINVAL;
|
||||
|
||||
tpmi_inst.count = isst_instance_count(sst_inst);
|
||||
|
||||
|
|
@ -1437,6 +1502,9 @@ static int isst_if_get_turbo_freq_info(void __user *argp)
|
|||
if (turbo_freq.level > power_domain_info->max_level)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(power_domain_info->pp_header.level_en_mask & BIT(turbo_freq.level)))
|
||||
return -EINVAL;
|
||||
|
||||
turbo_freq.max_buckets = TRL_MAX_BUCKETS;
|
||||
turbo_freq.max_trl_levels = TRL_MAX_LEVELS;
|
||||
turbo_freq.max_clip_freqs = SST_TF_MAX_LP_CLIP_RATIOS;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called lenovo-wmi-camera.
|
||||
|
||||
config LENOVO_YB9_KBDOCK
|
||||
tristate "Lenovo Yoga Book 9 keyboard dock detection"
|
||||
depends on ACPI_WMI
|
||||
depends on DMI
|
||||
depends on INPUT
|
||||
help
|
||||
Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
|
||||
14IAH10. The detachable Bluetooth keyboard magnetically attaches to
|
||||
either screen; this driver reports SW_TABLET_MODE input events based
|
||||
on the attachment state and exposes the raw position in sysfs.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called lenovo-yb9-kbdock.
|
||||
|
||||
config LENOVO_YMC
|
||||
tristate "Lenovo Yoga Tablet Mode Control"
|
||||
depends on ACPI_WMI
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
|
|||
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
|
||||
|
||||
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
|
||||
lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
|
||||
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
|
||||
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
|
||||
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
|
||||
|
|
|
|||
|
|
@ -2564,8 +2564,8 @@ module_init(ideapad_laptop_init)
|
|||
|
||||
static void __exit ideapad_laptop_exit(void)
|
||||
{
|
||||
ideapad_wmi_driver_unregister();
|
||||
platform_driver_unregister(&ideapad_acpi_driver);
|
||||
ideapad_wmi_driver_unregister();
|
||||
}
|
||||
module_exit(ideapad_laptop_exit)
|
||||
|
||||
|
|
|
|||
|
|
@ -438,14 +438,13 @@ static ssize_t current_password_store(struct kobject *kobj,
|
|||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
size_t pwdlen;
|
||||
|
||||
pwdlen = strlen(buf);
|
||||
/* Strip newline; setting password won't work if one is present. */
|
||||
pwdlen = strchrnul(buf, '\n') - buf;
|
||||
/* pwdlen == 0 is allowed to clear the password */
|
||||
if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen)))
|
||||
return -EINVAL;
|
||||
|
||||
strscpy(setting->password, buf, setting->maxlen);
|
||||
/* Strip out CR if one is present, setting password won't work if it is present */
|
||||
strreplace(setting->password, '\n', '\0');
|
||||
strscpy(setting->password, buf, pwdlen + 1);
|
||||
return count;
|
||||
}
|
||||
|
||||
|
|
@ -745,6 +744,8 @@ static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_att
|
|||
return -EOPNOTSUPP;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(thumbtypes); i++) {
|
||||
ssize_t ret;
|
||||
|
||||
if (tlmi_priv.pwdcfg.core.password_mode >= TLMI_PWDCFG_MODE_MULTICERT) {
|
||||
/* Format: 'SVC | SMC, Thumbtype' */
|
||||
wmistr = kasprintf(GFP_KERNEL, "%s,%s",
|
||||
|
|
@ -756,8 +757,12 @@ static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_att
|
|||
}
|
||||
if (!wmistr)
|
||||
return -ENOMEM;
|
||||
count += cert_thumbprint(buf, wmistr, count);
|
||||
|
||||
ret = cert_thumbprint(buf, wmistr, count);
|
||||
kfree(wmistr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
count = ret;
|
||||
}
|
||||
|
||||
return count;
|
||||
|
|
@ -1456,6 +1461,10 @@ static void tlmi_release_attr(void)
|
|||
/* Free up any saved signatures */
|
||||
kfree(tlmi_priv.pwd_admin->signature);
|
||||
kfree(tlmi_priv.pwd_admin->save_signature);
|
||||
if (tlmi_priv.pwd_system) {
|
||||
kfree(tlmi_priv.pwd_system->signature);
|
||||
kfree(tlmi_priv.pwd_system->save_signature);
|
||||
}
|
||||
|
||||
/* Authentication structures */
|
||||
list_for_each_entry_safe(pos, n, &tlmi_priv.authentication_kset->list, entry)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
#include <linux/backlight.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dmi.h>
|
||||
|
|
@ -67,6 +68,7 @@
|
|||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/string_helpers.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
|
|
@ -186,6 +188,7 @@ enum tpacpi_hkey_event_t {
|
|||
TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */
|
||||
TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */
|
||||
TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */
|
||||
TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */
|
||||
TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */
|
||||
TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */
|
||||
|
||||
|
|
@ -309,6 +312,7 @@ struct tp_acpi_drv_struct {
|
|||
|
||||
struct ibm_struct {
|
||||
char *name;
|
||||
acpi_device_class device_class;
|
||||
|
||||
int (*read) (struct seq_file *);
|
||||
int (*write) (char *);
|
||||
|
|
@ -374,6 +378,8 @@ static struct {
|
|||
u32 has_adaptive_kbd:1;
|
||||
u32 kbd_lang:1;
|
||||
u32 trackpoint_doubletap_enable:1;
|
||||
u32 usbc_security_supported:1;
|
||||
bool usbc_security_enabled;
|
||||
struct quirk_entry *quirks;
|
||||
} tp_features;
|
||||
|
||||
|
|
@ -838,9 +844,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
|
|||
}
|
||||
|
||||
ibm->acpi->device->driver_data = ibm;
|
||||
scnprintf(acpi_device_class(ibm->acpi->device),
|
||||
sizeof(acpi_device_class(ibm->acpi->device)),
|
||||
"%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name);
|
||||
scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s",
|
||||
TPACPI_ACPI_EVENT_PREFIX, ibm->name);
|
||||
|
||||
status = acpi_install_notify_handler(*ibm->acpi->handle,
|
||||
ibm->acpi->type, dispatch_acpi_notify, ibm);
|
||||
|
|
@ -3869,7 +3874,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
|
|||
pr_err("unknown HKEY notification event %d\n", event);
|
||||
/* forward it to userspace, maybe it knows how to handle it */
|
||||
acpi_bus_generate_netlink_event(
|
||||
ibm->acpi->device->pnp.device_class,
|
||||
ibm->device_class,
|
||||
dev_name(&ibm->acpi->device->dev),
|
||||
event, 0);
|
||||
return;
|
||||
|
|
@ -3949,7 +3954,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event)
|
|||
/* netlink events */
|
||||
if (send_acpi_ev) {
|
||||
acpi_bus_generate_netlink_event(
|
||||
ibm->acpi->device->pnp.device_class,
|
||||
ibm->device_class,
|
||||
dev_name(&ibm->acpi->device->dev),
|
||||
event, hkey);
|
||||
}
|
||||
|
|
@ -8840,6 +8845,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
|
|||
TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */
|
||||
TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */
|
||||
TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */
|
||||
TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */
|
||||
TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */
|
||||
TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */
|
||||
TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */
|
||||
|
|
@ -11285,6 +11291,114 @@ static struct ibm_struct hwdd_driver_data = {
|
|||
.name = "hwdd",
|
||||
};
|
||||
|
||||
/*************************************************************************
|
||||
* USB-C Security subdriver
|
||||
*
|
||||
* HKEY.USCS(0) is a read-only ACPI method; its argument is ignored.
|
||||
* It always returns:
|
||||
* bit 16 - USB-C security capability present on this SKU or not
|
||||
* bit 0 - USB-C Security state (enable or disable)
|
||||
*
|
||||
* Hotkey
|
||||
* ------
|
||||
* 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event.
|
||||
* The driver reads back the new state and notifies the sysfs attribute.
|
||||
*/
|
||||
|
||||
/* USCS() return word bit layout */
|
||||
#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */
|
||||
#define USCS_STATUS_BIT BIT(0) /* current security state */
|
||||
|
||||
/* Protects USCS() ACPI method calls in usbc_security_query() */
|
||||
static DEFINE_MUTEX(usbc_security_mutex);
|
||||
|
||||
/**
|
||||
* usbc_security_query - read current USB-C security state via USCS()
|
||||
* @enabled: out - true when security is ON (data connections blocked)
|
||||
*
|
||||
* Returns:
|
||||
* 0 success, @enabled contains the current state
|
||||
* -EIO ACPI evaluation failed
|
||||
* -ENODEV capability bit absent; feature not present on this SKU*
|
||||
*/
|
||||
static int usbc_security_query(bool *enabled)
|
||||
{
|
||||
int status;
|
||||
|
||||
guard(mutex)(&usbc_security_mutex);
|
||||
if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0))
|
||||
return -EIO;
|
||||
|
||||
if (!(status & USCS_CAP_BIT)) {
|
||||
pr_debug("USCS cap bit absent (raw=0x%x)\n", status);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
*enabled = status & USCS_STATUS_BIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */
|
||||
static ssize_t usb_c_security_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n",
|
||||
str_enabled_disabled(tp_features.usbc_security_enabled));
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(usb_c_security);
|
||||
|
||||
static struct attribute *usbc_security_attributes[] = {
|
||||
&dev_attr_usb_c_security.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t usbc_security_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
{
|
||||
return tp_features.usbc_security_supported ? attr->mode : 0;
|
||||
}
|
||||
|
||||
static const struct attribute_group usbc_security_attr_group = {
|
||||
.is_visible = usbc_security_attr_is_visible,
|
||||
.attrs = usbc_security_attributes,
|
||||
};
|
||||
|
||||
static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!acpi_has_method(hkey_handle, "USCS"))
|
||||
return -ENODEV;
|
||||
|
||||
err = usbc_security_query(&tp_features.usbc_security_enabled);
|
||||
if (err == -ENODEV)
|
||||
return 0;
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
tp_features.usbc_security_supported = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */
|
||||
static bool tpacpi_usbc_security_hotkey(void)
|
||||
{
|
||||
if (!tp_features.usbc_security_supported)
|
||||
return false;
|
||||
|
||||
if (usbc_security_query(&tp_features.usbc_security_enabled))
|
||||
return false;
|
||||
|
||||
sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security");
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct ibm_struct usbc_security_driver_data = {
|
||||
.name = "usbc_security",
|
||||
};
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static struct attribute *tpacpi_driver_attributes[] = {
|
||||
|
|
@ -11345,6 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = {
|
|||
&dprc_attr_group,
|
||||
&auxmac_attr_group,
|
||||
&hwdd_attr_group,
|
||||
&usbc_security_attr_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
|
@ -11499,6 +11614,8 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
|
|||
case TP_HKEY_EV_PROFILE_TOGGLE2:
|
||||
platform_profile_cycle();
|
||||
return true;
|
||||
case TP_HKEY_EV_USB_C_SECURITY:
|
||||
return tpacpi_usbc_security_hotkey();
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -11964,6 +12081,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
|
|||
.init = tpacpi_hwdd_init,
|
||||
.data = &hwdd_driver_data,
|
||||
},
|
||||
{
|
||||
.init = tpacpi_usbc_security_init,
|
||||
.data = &usbc_security_driver_data,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
|
||||
|
|
|
|||
323
drivers/platform/x86/lenovo/yb9-kbdock.c
Normal file
323
drivers/platform/x86/lenovo/yb9-kbdock.c
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Lenovo Yoga Book 9 keyboard-dock detection
|
||||
*
|
||||
* The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
|
||||
* attaches to the bottom screen in one of two positions. The EC tracks
|
||||
* attachment state in a 2-bit field called BKBD and signals changes via WMI
|
||||
* event 0xEB on the WM10 ACPI device (_UID "GMZN").
|
||||
*
|
||||
* BKBD values:
|
||||
* 0 = keyboard detached
|
||||
* 1 = keyboard docked on the top half of the bottom screen
|
||||
* 2 = keyboard docked on the bottom half of the bottom screen
|
||||
* 3 = reserved / not observed
|
||||
*
|
||||
* Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
|
||||
*
|
||||
* LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
|
||||
* WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
|
||||
* The notify callback receives BKBD as an integer; no separate query needed.
|
||||
*
|
||||
* LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
|
||||
* WmiDataId(1) uint32 IDs = 0x00060000 (feature selector)
|
||||
* WmiDataId(2) uint32 Status = BKBD value
|
||||
* Used on probe and resume to read initial state.
|
||||
*
|
||||
* The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI
|
||||
* event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev
|
||||
* and registers a notifier_block to receive those events, eliminating the
|
||||
* need for shared global state or a mutex.
|
||||
*
|
||||
* SW_TABLET_MODE=1 is reported when the keyboard is detached;
|
||||
* SW_TABLET_MODE=0 when docked in either position (keyboard present).
|
||||
* The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
|
||||
*
|
||||
* Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
|
||||
#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
|
||||
|
||||
/* BKBD encoding */
|
||||
#define BKBD_DETACHED 0
|
||||
|
||||
/* LENOVO_FEATURE_STATUS_DATA feature selector */
|
||||
#define YB9_FEATURE_STATUS_ID 0x00060000u
|
||||
|
||||
/*
|
||||
* LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}.
|
||||
* IDs is always 0x00060000; Status holds the BKBD value (0–3).
|
||||
*/
|
||||
struct lenovo_feature_status {
|
||||
__le32 id;
|
||||
__le32 status;
|
||||
} __packed;
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Notifier chain — event driver fires it, block driver listens
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head);
|
||||
|
||||
static void devm_yb9_kbdock_unregister_notifier(void *data)
|
||||
{
|
||||
struct notifier_block *nb = data;
|
||||
|
||||
blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb);
|
||||
}
|
||||
|
||||
static int devm_yb9_kbdock_register_notifier(struct device *dev,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Block WMI driver — LENOVO_FEATURE_STATUS_DATA
|
||||
* (owns input_dev, sysfs, PM resume)
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
struct yb9_kbdock_data {
|
||||
struct wmi_device *wdev;
|
||||
struct input_dev *input_dev;
|
||||
struct notifier_block nb;
|
||||
spinlock_t lock; /* protects input_report_switch + input_sync */
|
||||
};
|
||||
|
||||
static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd)
|
||||
{
|
||||
struct wmi_buffer out;
|
||||
int ret;
|
||||
|
||||
ret = wmidev_query_block(d->wdev, 0, &out,
|
||||
sizeof(struct lenovo_feature_status));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct lenovo_feature_status *fs __free(kfree) = out.data;
|
||||
|
||||
if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID)
|
||||
return -EIO;
|
||||
|
||||
*bkbd = le32_to_cpu(fs->status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd)
|
||||
{
|
||||
int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
|
||||
|
||||
spin_lock(&d->lock);
|
||||
input_report_switch(d->input_dev, SW_TABLET_MODE, tablet);
|
||||
input_sync(d->input_dev);
|
||||
spin_unlock(&d->lock);
|
||||
dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet);
|
||||
}
|
||||
|
||||
static int yb9_kbdock_sync(struct yb9_kbdock_data *d)
|
||||
{
|
||||
u32 bkbd;
|
||||
int ret;
|
||||
|
||||
ret = yb9_kbdock_query(d, &bkbd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
yb9_kbdock_report(d, bkbd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int yb9_kbdock_nb_call(struct notifier_block *nb,
|
||||
unsigned long bkbd, void *unused)
|
||||
{
|
||||
struct yb9_kbdock_data *d =
|
||||
container_of(nb, struct yb9_kbdock_data, nb);
|
||||
|
||||
yb9_kbdock_report(d, bkbd);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static ssize_t keyboard_position_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct yb9_kbdock_data *d = dev_get_drvdata(dev);
|
||||
u32 bkbd;
|
||||
int ret;
|
||||
|
||||
ret = yb9_kbdock_query(d, &bkbd);
|
||||
if (ret)
|
||||
return ret;
|
||||
return sysfs_emit(buf, "%u\n", bkbd);
|
||||
}
|
||||
static DEVICE_ATTR_RO(keyboard_position);
|
||||
|
||||
static const struct attribute * const yb9_kbdock_attrs[] = {
|
||||
&dev_attr_keyboard_position.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(yb9_kbdock);
|
||||
|
||||
static int yb9_kbdock_resume(struct device *dev)
|
||||
{
|
||||
struct yb9_kbdock_data *d = dev_get_drvdata(dev);
|
||||
|
||||
return yb9_kbdock_sync(d);
|
||||
}
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
|
||||
|
||||
static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
|
||||
{
|
||||
struct yb9_kbdock_data *d;
|
||||
struct input_dev *input_dev;
|
||||
int ret;
|
||||
|
||||
d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
d->wdev = wdev;
|
||||
spin_lock_init(&d->lock);
|
||||
|
||||
input_dev = devm_input_allocate_device(&wdev->dev);
|
||||
if (!input_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
|
||||
input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0";
|
||||
input_dev->id.bustype = BUS_HOST;
|
||||
input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
|
||||
|
||||
ret = input_register_device(input_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
d->input_dev = input_dev;
|
||||
d->nb.notifier_call = yb9_kbdock_nb_call;
|
||||
|
||||
ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_set_drvdata(&wdev->dev, d);
|
||||
return yb9_kbdock_sync(d);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
|
||||
{ .guid_string = YB9_KBDOCK_QUERY_GUID },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct wmi_driver yb9_kbdock_block_driver = {
|
||||
.driver = {
|
||||
.name = "lenovo-yb9-kbdock",
|
||||
.dev_groups = yb9_kbdock_groups,
|
||||
.pm = pm_sleep_ptr(&yb9_kbdock_pm_ops),
|
||||
},
|
||||
.id_table = yb9_kbdock_block_id_table,
|
||||
.no_singleton = true,
|
||||
.probe = yb9_kbdock_block_probe,
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Event WMI driver — LENOVO_BTKBD_EVENT
|
||||
* (fires the notifier chain on each WMI event)
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static void yb9_kbdock_notify_new(struct wmi_device *wdev,
|
||||
const struct wmi_buffer *data)
|
||||
{
|
||||
/*
|
||||
* _WED(0xEB) returns EC.BKBD directly as a 32-bit integer
|
||||
* (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
|
||||
* Short-buffer guard is handled by .min_event_size below.
|
||||
*/
|
||||
u32 bkbd = le32_to_cpu(*(const __le32 *)data->data);
|
||||
|
||||
blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
|
||||
{ .guid_string = YB9_KBDOCK_EVENT_GUID },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
|
||||
|
||||
static struct wmi_driver yb9_kbdock_event_driver = {
|
||||
.driver = {
|
||||
.name = "lenovo-yb9-kbdock-event",
|
||||
},
|
||||
.id_table = yb9_kbdock_event_id_table,
|
||||
.no_singleton = true,
|
||||
.notify_new = yb9_kbdock_notify_new,
|
||||
.min_event_size = sizeof(__le32),
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Module init / exit
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = {
|
||||
{
|
||||
/* Lenovo Yoga Book 9 14IAH10 */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
static int __init yb9_kbdock_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!dmi_check_system(yb9_kbdock_dmi_table))
|
||||
return -ENODEV;
|
||||
|
||||
ret = wmi_driver_register(&yb9_kbdock_event_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = wmi_driver_register(&yb9_kbdock_block_driver);
|
||||
if (ret) {
|
||||
wmi_driver_unregister(&yb9_kbdock_event_driver);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(yb9_kbdock_init);
|
||||
|
||||
static void __exit yb9_kbdock_exit(void)
|
||||
{
|
||||
wmi_driver_unregister(&yb9_kbdock_block_driver);
|
||||
wmi_driver_unregister(&yb9_kbdock_event_driver);
|
||||
}
|
||||
module_exit(yb9_kbdock_exit);
|
||||
|
||||
MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
|
||||
MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input/sparse-keymap.h>
|
||||
|
|
@ -20,10 +22,28 @@
|
|||
#define LENOVO_YMC_QUERY_INSTANCE 0
|
||||
#define LENOVO_YMC_QUERY_METHOD 0x01
|
||||
|
||||
#define LENOVO_YMC_STATE_MASK GENMASK(7, 0)
|
||||
|
||||
static bool force;
|
||||
module_param(force, bool, 0444);
|
||||
MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type");
|
||||
|
||||
static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = {
|
||||
{
|
||||
/*
|
||||
* Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the
|
||||
* yb9-kbdock driver. Suppress lenovo-ymc on this machine to
|
||||
* avoid userspace seeing two input nodes that both advertise
|
||||
* the SW_TABLET_MODE capability.
|
||||
*/
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct dmi_system_id allowed_chasis_types_dmi_table[] = {
|
||||
{
|
||||
.matches = {
|
||||
|
|
@ -85,7 +105,9 @@ static void lenovo_ymc_notify(struct wmi_device *wdev, union acpi_object *data)
|
|||
"WMI event data is not an integer\n");
|
||||
goto free_obj;
|
||||
}
|
||||
code = obj->integer.value;
|
||||
|
||||
/* strip upper bits (e.g. 0x50000) on newer devices */
|
||||
code = FIELD_GET(LENOVO_YMC_STATE_MASK, obj->integer.value);
|
||||
|
||||
if (!sparse_keymap_report_event(priv->input_dev, code, 1, true))
|
||||
dev_warn(&wdev->dev, "Unknown key %d pressed\n", code);
|
||||
|
|
@ -101,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx)
|
|||
struct input_dev *input_dev;
|
||||
int err;
|
||||
|
||||
if (dmi_check_system(lenovo_ymc_nosupport_dmi_table))
|
||||
return -ENODEV;
|
||||
|
||||
if (!dmi_check_system(allowed_chasis_types_dmi_table)) {
|
||||
if (force)
|
||||
dev_info(&wdev->dev, "Force loading Lenovo YMC support\n");
|
||||
|
|
|
|||
|
|
@ -8,8 +8,11 @@
|
|||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/dmi.h>
|
||||
|
|
@ -17,10 +20,12 @@
|
|||
#include <linux/input/sparse-keymap.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#include <acpi/battery.h>
|
||||
|
||||
|
|
@ -57,13 +62,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages");
|
|||
#define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E8
|
||||
#define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E8
|
||||
|
||||
#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248"
|
||||
#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2"
|
||||
#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6"
|
||||
#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B"
|
||||
#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D"
|
||||
#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210"
|
||||
#define WMI_EVENT_GUID WMI_EVENT_GUID0
|
||||
#define LG_NOTIFY_TABLET_MODE_OFF 0x50
|
||||
#define LG_NOTIFY_TABLET_MODE_ON 0x51
|
||||
#define LG_NOTIFY_HOTKEY 0x80
|
||||
#define LG_NOTIFY_THERMAL 0x81
|
||||
#define LG_NOTIFY_MISC 0x82
|
||||
|
||||
#define LG_OREP_READ_EC 0
|
||||
#define LG_OREP_WRITE_EC 1
|
||||
#define LG_OREP_DEBUG 2
|
||||
#define LG_OREP_UPDATE_SYSTEM_STATE 3
|
||||
#define LG_OREP_INTERCEPT_WMI_EVENTS 4
|
||||
#define LG_OREP_WAKE_ON_LAN 6
|
||||
|
||||
#define SB_GGOV_METHOD "\\_SB.GGOV"
|
||||
#define GOV_TLED 0x2020008
|
||||
|
|
@ -78,42 +88,95 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages");
|
|||
#define WMBB_USB_CHARGE 0x10B
|
||||
#define WMBB_BATT_LIMIT 0x10C
|
||||
|
||||
#define KBD_LED_BRIGHTNESS_MASK GENMASK(4, 0)
|
||||
#define KBD_LED_BRIGHTNESS_OFF 0x0
|
||||
#define KBD_LED_BRIGHTNESS_HALF 0x2
|
||||
#define KBD_LED_BRIGHTNESS_FULL 0x4
|
||||
#define KBD_LED_MODE_MASK GENMASK(6, 5)
|
||||
#define KBD_LED_MODE_OFF 0x0
|
||||
#define KBD_LED_MODE_ON 0x1
|
||||
#define KBD_LED_STATUS BIT(7)
|
||||
#define KBD_LED_MAGIC_MASK GENMASK(15, 8)
|
||||
/* Exact purpose is unknown, maybe some sort of brightness limit? */
|
||||
#define KBD_LED_MAGIC 0x05
|
||||
|
||||
#define FAN_MODE_LOWER GENMASK(1, 0)
|
||||
#define FAN_MODE_UPPER GENMASK(5, 4)
|
||||
|
||||
#define PLATFORM_NAME "lg-laptop"
|
||||
|
||||
MODULE_ALIAS("wmi:" WMI_EVENT_GUID0);
|
||||
MODULE_ALIAS("wmi:" WMI_EVENT_GUID1);
|
||||
MODULE_ALIAS("wmi:" WMI_EVENT_GUID2);
|
||||
MODULE_ALIAS("wmi:" WMI_EVENT_GUID3);
|
||||
MODULE_ALIAS("wmi:" WMI_METHOD_WMAB);
|
||||
MODULE_ALIAS("wmi:" WMI_METHOD_WMBB);
|
||||
struct lg_wmab_buffer_result {
|
||||
__le32 value;
|
||||
__le32 status;
|
||||
} __packed;
|
||||
|
||||
static struct platform_device *pf_device;
|
||||
static struct input_dev *wmi_input_dev;
|
||||
|
||||
static u32 inited;
|
||||
#define INIT_INPUT_WMI_0 0x01
|
||||
#define INIT_INPUT_WMI_2 0x02
|
||||
#define INIT_INPUT_ACPI 0x04
|
||||
#define INIT_SPARSE_KEYMAP 0x80
|
||||
|
||||
static int battery_limit_use_wmbb;
|
||||
static bool kbd_backlight_available;
|
||||
static struct led_classdev kbd_backlight;
|
||||
static enum led_brightness get_kbd_backlight_level(struct device *dev);
|
||||
|
||||
static const struct key_entry wmi_keymap[] = {
|
||||
{KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */
|
||||
{KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */
|
||||
{KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */
|
||||
{KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing
|
||||
* this key both sends an event and
|
||||
* changes backlight level.
|
||||
*/
|
||||
/* Placeholder value send by multiple hotkeys handled by ACPI */
|
||||
{ KE_IGNORE, 0x0, { KEY_UNKNOWN }},
|
||||
/* LG control panel */
|
||||
{ KE_KEY, 0x70, { KEY_F15 }},
|
||||
/* Touchpad toggle */
|
||||
{ KE_KEY, 0x74, { KEY_F21 }},
|
||||
/* Mute Audio, already handled by ACPI */
|
||||
{ KE_IGNORE, 0x78, { KEY_MUTE }},
|
||||
/* Read mode */
|
||||
{ KE_KEY, 0xf020000, { KEY_F14 }},
|
||||
/* Open settings */
|
||||
{ KE_KEY, 0xf070002, { KEY_CONFIG }},
|
||||
/* Keyboard backlight - pressing this key both sends an event and changes backlight level */
|
||||
{ KE_KEY, 0x10000000, { KEY_F16 }},
|
||||
/* Hotkey combination pressed */
|
||||
{ KE_IGNORE, 0x30010000, { KEY_UNKNOWN }},
|
||||
/* Hotkey combination released */
|
||||
{ KE_IGNORE, 0x30010001, { KEY_UNKNOWN }},
|
||||
/* Change fan mode */
|
||||
{ KE_KEY, 0x30010051, { KEY_PERFORMANCE }},
|
||||
/* Disable camera */
|
||||
{ KE_KEY, 0x40020000, { KEY_CAMERA_ACCESS_TOGGLE }},
|
||||
/* Mute microphone */
|
||||
{ KE_KEY, 0x40020001, { KEY_MICMUTE }},
|
||||
/* Fn-Lock */
|
||||
{ KE_KEY, 0x40030001, { KEY_FN_ESC }},
|
||||
{KE_END, 0}
|
||||
};
|
||||
|
||||
static int lg_laptop_execute_orep(acpi_handle handle, u64 command, u64 value,
|
||||
unsigned long long *result)
|
||||
{
|
||||
union acpi_object objs[] = {
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = command,
|
||||
},
|
||||
},
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = value,
|
||||
},
|
||||
}
|
||||
};
|
||||
struct acpi_object_list args = {
|
||||
.count = ARRAY_SIZE(objs),
|
||||
.pointer = objs,
|
||||
};
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_integer(handle, "OREP", &args, result);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ggov(u32 arg0)
|
||||
{
|
||||
union acpi_object args[1];
|
||||
|
|
@ -154,30 +217,97 @@ static int ggov(u32 arg0)
|
|||
return res;
|
||||
}
|
||||
|
||||
static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2)
|
||||
static int lg_wmab_get(struct device *dev, u32 method, u32 *result)
|
||||
{
|
||||
union acpi_object args[3];
|
||||
acpi_status status;
|
||||
struct acpi_object_list arg;
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object in[] = {
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = method,
|
||||
},
|
||||
},
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = WM_GET,
|
||||
},
|
||||
},
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
struct lg_wmab_buffer_result *buffer_result;
|
||||
struct acpi_object_list input = {
|
||||
.count = ARRAY_SIZE(in),
|
||||
.pointer = in,
|
||||
};
|
||||
acpi_status status;
|
||||
|
||||
args[0].type = ACPI_TYPE_INTEGER;
|
||||
args[0].integer.value = method;
|
||||
args[1].type = ACPI_TYPE_INTEGER;
|
||||
args[1].integer.value = arg1;
|
||||
args[2].type = ACPI_TYPE_INTEGER;
|
||||
args[2].integer.value = arg2;
|
||||
status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, &buffer);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
arg.count = 3;
|
||||
arg.pointer = args;
|
||||
union acpi_object *obj __free(kfree) = buffer.pointer;
|
||||
|
||||
status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(dev, "WMAB: call failed.\n");
|
||||
return NULL;
|
||||
if (!obj)
|
||||
return -ENODATA;
|
||||
|
||||
switch (obj->type) {
|
||||
case ACPI_TYPE_INTEGER:
|
||||
*result = obj->integer.value;
|
||||
return 0;
|
||||
case ACPI_TYPE_BUFFER:
|
||||
if (obj->buffer.length != sizeof(*buffer_result))
|
||||
return -EPROTO;
|
||||
|
||||
buffer_result = (struct lg_wmab_buffer_result *)obj->buffer.pointer;
|
||||
if (get_unaligned_le32(&buffer_result->status))
|
||||
return -EIO;
|
||||
|
||||
*result = get_unaligned_le32(&buffer_result->value);
|
||||
return 0;
|
||||
default:
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.pointer;
|
||||
static int lg_wmab_set(struct device *dev, u32 method, u32 arg)
|
||||
{
|
||||
union acpi_object in[] = {
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = method,
|
||||
},
|
||||
},
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = WM_SET,
|
||||
},
|
||||
},
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = arg,
|
||||
},
|
||||
},
|
||||
};
|
||||
struct acpi_object_list input = {
|
||||
.count = ARRAY_SIZE(in),
|
||||
.pointer = in,
|
||||
};
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, NULL);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2)
|
||||
|
|
@ -211,72 +341,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u
|
|||
return (union acpi_object *)buffer.pointer;
|
||||
}
|
||||
|
||||
static void wmi_notify(union acpi_object *obj, void *context)
|
||||
{
|
||||
long data = (long)context;
|
||||
|
||||
pr_debug("event guid %li\n", data);
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
if (obj->type == ACPI_TYPE_INTEGER) {
|
||||
int eventcode = obj->integer.value;
|
||||
struct key_entry *key;
|
||||
|
||||
if (eventcode == 0x10000000) {
|
||||
led_classdev_notify_brightness_hw_changed(
|
||||
&kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent));
|
||||
} else {
|
||||
key = sparse_keymap_entry_from_scancode(
|
||||
wmi_input_dev, eventcode);
|
||||
if (key && key->type == KE_KEY)
|
||||
sparse_keymap_report_entry(wmi_input_dev,
|
||||
key, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type,
|
||||
obj->integer.value);
|
||||
}
|
||||
|
||||
static void wmi_input_setup(void)
|
||||
{
|
||||
acpi_status status;
|
||||
|
||||
wmi_input_dev = input_allocate_device();
|
||||
if (wmi_input_dev) {
|
||||
wmi_input_dev->name = "LG WMI hotkeys";
|
||||
wmi_input_dev->phys = "wmi/input0";
|
||||
wmi_input_dev->id.bustype = BUS_HOST;
|
||||
|
||||
if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) ||
|
||||
input_register_device(wmi_input_dev)) {
|
||||
pr_info("Cannot initialize input device");
|
||||
input_free_device(wmi_input_dev);
|
||||
return;
|
||||
}
|
||||
|
||||
inited |= INIT_SPARSE_KEYMAP;
|
||||
status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify,
|
||||
(void *)0);
|
||||
if (ACPI_SUCCESS(status))
|
||||
inited |= INIT_INPUT_WMI_0;
|
||||
|
||||
status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify,
|
||||
(void *)2);
|
||||
if (ACPI_SUCCESS(status))
|
||||
inited |= INIT_INPUT_WMI_2;
|
||||
} else {
|
||||
pr_info("Cannot allocate input device");
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t fan_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buffer, size_t count)
|
||||
{
|
||||
unsigned long value;
|
||||
union acpi_object *r;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buffer, 10, &value);
|
||||
|
|
@ -285,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev,
|
|||
if (value >= 3)
|
||||
return -EINVAL;
|
||||
|
||||
r = lg_wmab(dev, WM_FAN_MODE, WM_SET,
|
||||
FIELD_PREP(FAN_MODE_LOWER, value) |
|
||||
FIELD_PREP(FAN_MODE_UPPER, value));
|
||||
kfree(r);
|
||||
ret = lg_wmab_set(dev, WM_FAN_MODE, FIELD_PREP(FAN_MODE_LOWER, value) |
|
||||
FIELD_PREP(FAN_MODE_UPPER, value));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
|
@ -296,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev,
|
|||
static ssize_t fan_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buffer)
|
||||
{
|
||||
unsigned int mode;
|
||||
union acpi_object *r;
|
||||
u32 mode;
|
||||
int ret;
|
||||
|
||||
r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);
|
||||
if (!r)
|
||||
return -EIO;
|
||||
ret = lg_wmab_get(dev, WM_FAN_MODE, &mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (r->type != ACPI_TYPE_INTEGER) {
|
||||
kfree(r);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value);
|
||||
kfree(r);
|
||||
|
||||
return sysfs_emit(buffer, "%d\n", mode);
|
||||
return sysfs_emit(buffer, "%lu\n", FIELD_GET(FAN_MODE_LOWER, mode));
|
||||
}
|
||||
|
||||
static ssize_t usb_charge_store(struct device *dev,
|
||||
|
|
@ -361,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev,
|
|||
const char *buffer, size_t count)
|
||||
{
|
||||
bool value;
|
||||
union acpi_object *r;
|
||||
int ret;
|
||||
|
||||
ret = kstrtobool(buffer, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
r = lg_wmab(dev, WM_READER_MODE, WM_SET, value);
|
||||
if (!r)
|
||||
return -EIO;
|
||||
ret = lg_wmab_set(dev, WM_READER_MODE, value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
kfree(r);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t reader_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buffer)
|
||||
{
|
||||
unsigned int status;
|
||||
union acpi_object *r;
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0);
|
||||
if (!r)
|
||||
return -EIO;
|
||||
ret = lg_wmab_get(dev, WM_READER_MODE, &status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (r->type != ACPI_TYPE_INTEGER) {
|
||||
kfree(r);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
status = !!r->integer.value;
|
||||
|
||||
kfree(r);
|
||||
|
||||
return sysfs_emit(buffer, "%d\n", status);
|
||||
return sysfs_emit(buffer, "%d\n", !!status);
|
||||
}
|
||||
|
||||
static ssize_t fn_lock_store(struct device *dev,
|
||||
|
|
@ -403,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev,
|
|||
const char *buffer, size_t count)
|
||||
{
|
||||
bool value;
|
||||
union acpi_object *r;
|
||||
int ret;
|
||||
|
||||
ret = kstrtobool(buffer, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value);
|
||||
if (!r)
|
||||
return -EIO;
|
||||
ret = lg_wmab_set(dev, WM_FN_LOCK, value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
kfree(r);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t fn_lock_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buffer)
|
||||
{
|
||||
unsigned int status;
|
||||
union acpi_object *r;
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0);
|
||||
if (!r)
|
||||
return -EIO;
|
||||
ret = lg_wmab_get(dev, WM_FN_LOCK, &status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (r->type != ACPI_TYPE_BUFFER) {
|
||||
kfree(r);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
status = !!r->buffer.pointer[0];
|
||||
kfree(r);
|
||||
|
||||
return sysfs_emit(buffer, "%d\n", status);
|
||||
return sysfs_emit(buffer, "%d\n", !!status);
|
||||
}
|
||||
|
||||
static ssize_t charge_control_end_threshold_store(struct device *dev,
|
||||
|
|
@ -453,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev,
|
|||
if (value == 100 || value == 80) {
|
||||
union acpi_object *r;
|
||||
|
||||
if (battery_limit_use_wmbb)
|
||||
if (battery_limit_use_wmbb) {
|
||||
r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value);
|
||||
else
|
||||
r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value);
|
||||
if (!r)
|
||||
return -EIO;
|
||||
if (!r)
|
||||
return -EIO;
|
||||
|
||||
kfree(r);
|
||||
} else {
|
||||
ret = lg_wmab_set(&pf_device->dev, WM_BATT_LIMIT, value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
kfree(r);
|
||||
return count;
|
||||
}
|
||||
|
||||
|
|
@ -471,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device,
|
|||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
unsigned int status;
|
||||
union acpi_object *r;
|
||||
u32 status;
|
||||
int ret;
|
||||
|
||||
if (battery_limit_use_wmbb) {
|
||||
r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0);
|
||||
|
|
@ -485,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device,
|
|||
}
|
||||
|
||||
status = r->buffer.pointer[0x10];
|
||||
kfree(r);
|
||||
} else {
|
||||
r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0);
|
||||
if (!r)
|
||||
return -EIO;
|
||||
|
||||
if (r->type != ACPI_TYPE_INTEGER) {
|
||||
kfree(r);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
status = r->integer.value;
|
||||
ret = lg_wmab_get(&pf_device->dev, WM_BATT_LIMIT, &status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
kfree(r);
|
||||
|
||||
if (status != 80 && status != 100)
|
||||
status = 0;
|
||||
|
||||
|
|
@ -563,10 +602,7 @@ static const struct attribute_group dev_attribute_group = {
|
|||
static void tpad_led_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
union acpi_object *r;
|
||||
|
||||
r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF);
|
||||
kfree(r);
|
||||
lg_wmab_set(cdev->dev->parent, WM_TLED, brightness > LED_OFF);
|
||||
}
|
||||
|
||||
static enum led_brightness tpad_led_get(struct led_classdev *cdev)
|
||||
|
|
@ -579,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0);
|
|||
static void kbd_backlight_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
u32 val;
|
||||
union acpi_object *r;
|
||||
/* Must always be written */
|
||||
u32 value = KBD_LED_STATUS;
|
||||
u32 mode, bright;
|
||||
|
||||
val = 0x22;
|
||||
if (brightness <= LED_OFF)
|
||||
val = 0;
|
||||
if (brightness >= LED_FULL)
|
||||
val = 0x24;
|
||||
r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val);
|
||||
kfree(r);
|
||||
if (brightness <= LED_OFF) {
|
||||
mode = KBD_LED_MODE_OFF;
|
||||
bright = KBD_LED_BRIGHTNESS_OFF;
|
||||
} else {
|
||||
mode = KBD_LED_MODE_ON;
|
||||
if (brightness >= LED_FULL)
|
||||
bright = KBD_LED_BRIGHTNESS_FULL;
|
||||
else
|
||||
bright = KBD_LED_BRIGHTNESS_HALF;
|
||||
}
|
||||
|
||||
value |= FIELD_PREP(KBD_LED_BRIGHTNESS_MASK, bright);
|
||||
value |= FIELD_PREP(KBD_LED_MODE_MASK, mode);
|
||||
|
||||
lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, value);
|
||||
}
|
||||
|
||||
static enum led_brightness get_kbd_backlight_level(struct device *dev)
|
||||
{
|
||||
union acpi_object *r;
|
||||
int val;
|
||||
u32 value;
|
||||
int ret;
|
||||
|
||||
r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0);
|
||||
|
||||
if (!r)
|
||||
ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value);
|
||||
if (ret < 0)
|
||||
return LED_OFF;
|
||||
|
||||
if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) {
|
||||
kfree(r);
|
||||
if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC)
|
||||
return LED_OFF;
|
||||
}
|
||||
|
||||
switch (r->buffer.pointer[0] & 0x27) {
|
||||
case 0x24:
|
||||
val = LED_FULL;
|
||||
break;
|
||||
case 0x22:
|
||||
val = LED_HALF;
|
||||
break;
|
||||
if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF)
|
||||
return LED_OFF;
|
||||
|
||||
switch (FIELD_GET(KBD_LED_BRIGHTNESS_MASK, value)) {
|
||||
case KBD_LED_BRIGHTNESS_FULL:
|
||||
return LED_FULL;
|
||||
case KBD_LED_BRIGHTNESS_HALF:
|
||||
return LED_HALF;
|
||||
default:
|
||||
val = LED_OFF;
|
||||
return LED_OFF;
|
||||
}
|
||||
|
||||
kfree(r);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static enum led_brightness kbd_backlight_get(struct led_classdev *cdev)
|
||||
|
|
@ -629,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev)
|
|||
|
||||
static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED);
|
||||
|
||||
static void wmi_input_destroy(void)
|
||||
{
|
||||
if (inited & INIT_INPUT_WMI_2)
|
||||
wmi_remove_notify_handler(WMI_EVENT_GUID2);
|
||||
|
||||
if (inited & INIT_INPUT_WMI_0)
|
||||
wmi_remove_notify_handler(WMI_EVENT_GUID0);
|
||||
|
||||
if (inited & INIT_SPARSE_KEYMAP)
|
||||
input_unregister_device(wmi_input_dev);
|
||||
|
||||
inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP);
|
||||
}
|
||||
|
||||
static struct platform_driver pf_driver = {
|
||||
.driver = {
|
||||
.name = PLATFORM_NAME,
|
||||
}
|
||||
};
|
||||
|
||||
static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data)
|
||||
{
|
||||
union acpi_object objs[] = {
|
||||
{
|
||||
.integer = {
|
||||
.type = ACPI_TYPE_INTEGER,
|
||||
.value = value,
|
||||
},
|
||||
}
|
||||
};
|
||||
struct acpi_object_list args = {
|
||||
.count = ARRAY_SIZE(objs),
|
||||
.pointer = objs,
|
||||
};
|
||||
unsigned long long result;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_integer(handle, "_WED", &args, &result);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
if (result > U32_MAX)
|
||||
return -EPROTO;
|
||||
|
||||
*data = result;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lg_laptop_handle_input_event(struct input_dev *input_dev, u32 value, u32 data)
|
||||
{
|
||||
unsigned int kbd_brightness;
|
||||
|
||||
switch (value) {
|
||||
case LG_NOTIFY_HOTKEY:
|
||||
sparse_keymap_report_event(input_dev, data, 1, true);
|
||||
break;
|
||||
case LG_NOTIFY_THERMAL:
|
||||
/* Currently not supported */
|
||||
break;
|
||||
case LG_NOTIFY_MISC:
|
||||
switch (data) {
|
||||
case 0x10000000:
|
||||
if (!kbd_backlight_available)
|
||||
break;
|
||||
|
||||
kbd_brightness = get_kbd_backlight_level(kbd_backlight.dev->parent);
|
||||
led_classdev_notify_brightness_hw_changed(&kbd_backlight, kbd_brightness);
|
||||
break;
|
||||
default:
|
||||
sparse_keymap_report_event(input_dev, data, 1, true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void lg_laptop_notify_handler(acpi_handle handle, u32 value, void *context)
|
||||
{
|
||||
struct input_dev *input_dev = context;
|
||||
u32 data;
|
||||
int ret;
|
||||
|
||||
switch (value) {
|
||||
case LG_NOTIFY_TABLET_MODE_OFF:
|
||||
case LG_NOTIFY_TABLET_MODE_ON:
|
||||
/* Already handled by intel-hid */
|
||||
return;
|
||||
case LG_NOTIFY_HOTKEY:
|
||||
case LG_NOTIFY_THERMAL:
|
||||
case LG_NOTIFY_MISC:
|
||||
ret = lg_laptop_get_event_data(handle, value, &data);
|
||||
if (ret < 0) {
|
||||
dev_notice(input_dev->dev.parent, "Failed to get event data: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
dev_dbg(input_dev->dev.parent, "Received event %u (%u)\n", value, data);
|
||||
|
||||
lg_laptop_handle_input_event(input_dev, value, data);
|
||||
return;
|
||||
default:
|
||||
dev_notice(input_dev->dev.parent, "Received unknown event %u\n", value);
|
||||
}
|
||||
};
|
||||
|
||||
static void lg_laptop_remove_notify_handler(void *context)
|
||||
{
|
||||
acpi_handle handle = context;
|
||||
|
||||
acpi_remove_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler);
|
||||
}
|
||||
|
||||
static void lg_laptop_reenable_wmi_events(void *context)
|
||||
{
|
||||
acpi_handle handle = context;
|
||||
unsigned long long dummy;
|
||||
|
||||
lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 0, &dummy);
|
||||
}
|
||||
|
||||
static int lg_laptop_input_init(struct device *dev, acpi_handle handle)
|
||||
{
|
||||
struct input_dev *input_dev;
|
||||
unsigned long long result;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
if (!acpi_has_method(handle, "_WED"))
|
||||
return 0;
|
||||
|
||||
input_dev = devm_input_allocate_device(dev);
|
||||
if (!input_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
input_dev->name = "LG WMI hotkeys";
|
||||
input_dev->phys = "wmi/input0";
|
||||
input_dev->id.bustype = BUS_HOST;
|
||||
ret = sparse_keymap_setup(input_dev, wmi_keymap, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = input_register_device(input_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
status = acpi_install_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler,
|
||||
input_dev);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, lg_laptop_remove_notify_handler, handle);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (acpi_has_method(handle, "OREP")) {
|
||||
ret = lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 1, &result);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (result)
|
||||
return -EIO;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, lg_laptop_reenable_wmi_events, handle);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address,
|
||||
size_t size, u64 value)
|
||||
{
|
||||
|
|
@ -860,15 +1036,23 @@ static int acpi_probe(struct platform_device *pdev)
|
|||
if (year >= 2019)
|
||||
battery_limit_use_wmbb = 1;
|
||||
|
||||
/* LEDs are optional */
|
||||
ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight);
|
||||
if (ret < 0)
|
||||
kbd_backlight_available = false;
|
||||
else
|
||||
kbd_backlight_available = true;
|
||||
|
||||
devm_led_classdev_register(&pdev->dev, &tpad_led);
|
||||
|
||||
ret = lg_laptop_input_init(&pdev->dev, device->handle);
|
||||
if (ret < 0)
|
||||
goto out_platform_device;
|
||||
|
||||
ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group);
|
||||
if (ret)
|
||||
goto out_platform_device;
|
||||
|
||||
/* LEDs are optional */
|
||||
led_classdev_register(&pf_device->dev, &kbd_backlight);
|
||||
led_classdev_register(&pf_device->dev, &tpad_led);
|
||||
|
||||
wmi_input_setup();
|
||||
battery_hook_register(&battery_hook);
|
||||
|
||||
return 0;
|
||||
|
|
@ -884,11 +1068,7 @@ static void acpi_remove(struct platform_device *pdev)
|
|||
{
|
||||
sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);
|
||||
|
||||
led_classdev_unregister(&tpad_led);
|
||||
led_classdev_unregister(&kbd_backlight);
|
||||
|
||||
battery_hook_unregister(&battery_hook);
|
||||
wmi_input_destroy();
|
||||
platform_device_unregister(pf_device);
|
||||
pf_device = NULL;
|
||||
platform_driver_unregister(&pf_driver);
|
||||
|
|
|
|||
|
|
@ -1055,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = {
|
|||
|
||||
static const char * const ALLOWED_FW_13[] __initconst = {
|
||||
"1594EMS1.109", // MSI Prestige 16 Studio A13VE
|
||||
"17L1EMS1.107", // MSI Katana GF76 11UEK
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
@ -1130,6 +1131,86 @@ static struct msi_ec_conf CONF13 __initdata = {
|
|||
},
|
||||
};
|
||||
|
||||
static const char * const ALLOWED_FW_14[] __initconst = {
|
||||
"1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824)
|
||||
"182LIMS1.108", // Vector A18 HX A9WHG
|
||||
"182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG
|
||||
"182KIMS1.113", // Raider A18 HX A7VIG
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct msi_ec_conf CONF14 __initdata = {
|
||||
.allowed_fw = ALLOWED_FW_14,
|
||||
.charge_control = {
|
||||
.address = 0xd7,
|
||||
.offset_start = 0x8a,
|
||||
.offset_end = 0x80,
|
||||
.range_min = 0x8a,
|
||||
.range_max = 0xe4,
|
||||
},
|
||||
.webcam = {
|
||||
.address = 0x2e,
|
||||
.block_address = 0x2f,
|
||||
.bit = 1,
|
||||
},
|
||||
.fn_win_swap = {
|
||||
.address = 0xe8,
|
||||
.bit = 4,
|
||||
},
|
||||
.cooler_boost = {
|
||||
.address = 0x98,
|
||||
.bit = 7,
|
||||
},
|
||||
.shift_mode = {
|
||||
.address = 0xd2,
|
||||
.modes = {
|
||||
{ SM_ECO_NAME, 0xc2 },
|
||||
{ SM_COMFORT_NAME, 0xc1 },
|
||||
{ SM_TURBO_NAME, 0xc4 },
|
||||
MSI_EC_MODE_NULL
|
||||
},
|
||||
},
|
||||
.super_battery = {
|
||||
.address = 0xeb,
|
||||
.mask = 0x0f,
|
||||
},
|
||||
.fan_mode = {
|
||||
.address = 0xd4,
|
||||
.modes = {
|
||||
{ FM_AUTO_NAME, 0x0d },
|
||||
{ FM_SILENT_NAME, 0x1d },
|
||||
{ FM_ADVANCED_NAME, 0x8d },
|
||||
MSI_EC_MODE_NULL
|
||||
},
|
||||
},
|
||||
.cpu = {
|
||||
.rt_temp_address = 0x68,
|
||||
.rt_fan_speed_address = 0x71,
|
||||
.rt_fan_speed_base_min = 0x00,
|
||||
.rt_fan_speed_base_max = 0x96,
|
||||
.bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
|
||||
.bs_fan_speed_base_min = 0x00,
|
||||
.bs_fan_speed_base_max = 0x0f,
|
||||
},
|
||||
.gpu = {
|
||||
.rt_temp_address = 0x80,
|
||||
.rt_fan_speed_address = 0x89,
|
||||
},
|
||||
.leds = {
|
||||
.micmute_led_address = 0x2c,
|
||||
.mute_led_address = 0x2d,
|
||||
.bit = 1,
|
||||
},
|
||||
.kbd_bl = {
|
||||
.bl_mode_address = MSI_EC_ADDR_UNSUPP,
|
||||
.bl_modes = { 0x00, 0x08 },
|
||||
.max_mode = 1,
|
||||
.bl_state_address = MSI_EC_ADDR_UNSUPP,
|
||||
.state_base_value = 0x80,
|
||||
.max_state = 3,
|
||||
},
|
||||
};
|
||||
|
||||
static struct msi_ec_conf *CONFIGS[] __initdata = {
|
||||
&CONF0,
|
||||
&CONF1,
|
||||
|
|
@ -1145,6 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = {
|
|||
&CONF11,
|
||||
&CONF12,
|
||||
&CONF13,
|
||||
&CONF14,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@ enum msi_scancodes {
|
|||
WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */
|
||||
WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */
|
||||
WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */
|
||||
/* MSI Claw keys */
|
||||
CLAW_KEY_VOLUMEDOWN = 0x21,
|
||||
CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */
|
||||
CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */
|
||||
CLAW_KEY_VOLUMEUP = 0x32,
|
||||
CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */
|
||||
};
|
||||
static struct key_entry msi_wmi_keymap[] = {
|
||||
{ KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} },
|
||||
|
|
@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = {
|
|||
{ KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} },
|
||||
{ KE_KEY, WIND_KEY_ECO, {KEY_PROG2} },
|
||||
|
||||
/* These are MSI Claw keys, used for MSI M-Center in Windows */
|
||||
{ KE_KEY, CLAW_KEY_CENTER, {KEY_F15} },
|
||||
|
||||
/* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */
|
||||
{ KE_IGNORE, CLAW_KEY_QUICK_SHORT },
|
||||
{ KE_IGNORE, CLAW_KEY_QUICK_LONG },
|
||||
{ KE_IGNORE, CLAW_KEY_VOLUMEUP },
|
||||
{ KE_IGNORE, CLAW_KEY_VOLUMEDOWN },
|
||||
|
||||
{ KE_END, 0 }
|
||||
};
|
||||
|
||||
|
|
@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = {
|
|||
|
||||
static void msi_wmi_notify(union acpi_object *obj, void *context)
|
||||
{
|
||||
struct key_entry *key;
|
||||
struct key_entry *key = NULL;
|
||||
int eventcode = 0;
|
||||
|
||||
if (obj && obj->type == ACPI_TYPE_INTEGER) {
|
||||
int eventcode = obj->integer.value;
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
switch (obj->type) {
|
||||
case ACPI_TYPE_INTEGER:
|
||||
eventcode = obj->integer.value;
|
||||
pr_debug("Eventcode: 0x%x\n", eventcode);
|
||||
key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev,
|
||||
eventcode);
|
||||
if (!key) {
|
||||
pr_info("Unknown key pressed - %x\n", eventcode);
|
||||
break;
|
||||
case ACPI_TYPE_BUFFER:
|
||||
/* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */
|
||||
if (obj->buffer.length < 2)
|
||||
return;
|
||||
|
||||
/* pointer[0] is key ID, pointer[1] is active state. We don't get release
|
||||
* events, so ignore the active state and treat as autorelease.
|
||||
*/
|
||||
eventcode = obj->buffer.pointer[0];
|
||||
pr_debug("Eventcode: 0x%x\n", eventcode);
|
||||
break;
|
||||
default:
|
||||
pr_info("Unknown event received\n");
|
||||
return;
|
||||
}
|
||||
|
||||
key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode);
|
||||
|
||||
if (!key) {
|
||||
pr_info("Unknown key pressed - 0x%x\n", eventcode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_wmi->quirk_last_pressed) {
|
||||
ktime_t cur = ktime_get_real();
|
||||
ktime_t diff = ktime_sub(cur, last_pressed);
|
||||
/* Ignore event if any event happened in a 50 ms
|
||||
* timeframe -> Key press may result in 10-20 GPEs
|
||||
*/
|
||||
if (ktime_to_us(diff) < 1000 * 50) {
|
||||
pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n",
|
||||
key->code, ktime_to_us(diff));
|
||||
return;
|
||||
}
|
||||
last_pressed = cur;
|
||||
}
|
||||
|
||||
if (event_wmi->quirk_last_pressed) {
|
||||
ktime_t cur = ktime_get_real();
|
||||
ktime_t diff = ktime_sub(cur, last_pressed);
|
||||
/* Ignore event if any event happened in a 50 ms
|
||||
timeframe -> Key press may result in 10-20 GPEs */
|
||||
if (ktime_to_us(diff) < 1000 * 50) {
|
||||
pr_debug("Suppressed key event 0x%X - "
|
||||
"Last press was %lld us ago\n",
|
||||
key->code, ktime_to_us(diff));
|
||||
return;
|
||||
}
|
||||
last_pressed = cur;
|
||||
}
|
||||
/* Brightness is served via acpi video driver */
|
||||
if (key->type == KE_KEY &&
|
||||
(backlight || (key->code == MSI_KEY_BRIGHTNESSUP ||
|
||||
key->code == MSI_KEY_BRIGHTNESSDOWN)))
|
||||
return;
|
||||
|
||||
if (key->type == KE_KEY &&
|
||||
/* Brightness is served via acpi video driver */
|
||||
(backlight ||
|
||||
(key->code != MSI_KEY_BRIGHTNESSUP &&
|
||||
key->code != MSI_KEY_BRIGHTNESSDOWN))) {
|
||||
pr_debug("Send key: 0x%X - Input layer keycode: %d\n",
|
||||
key->code, key->keycode);
|
||||
sparse_keymap_report_entry(msi_wmi_input_dev, key, 1,
|
||||
true);
|
||||
}
|
||||
} else
|
||||
pr_info("Unknown event received\n");
|
||||
pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode);
|
||||
sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true);
|
||||
}
|
||||
|
||||
static int __init msi_wmi_backlight_setup(void)
|
||||
|
|
|
|||
|
|
@ -289,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = {
|
|||
},
|
||||
.driver_data = (void *)oxp_x1,
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"),
|
||||
},
|
||||
.driver_data = (void *)oxp_fly,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -159,8 +159,6 @@ MODULE_LICENSE("GPL");
|
|||
#define ECO_MODE_ON 0x80
|
||||
|
||||
#define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support"
|
||||
#define ACPI_PCC_DEVICE_NAME "Hotkey"
|
||||
#define ACPI_PCC_CLASS "pcc"
|
||||
|
||||
#define ACPI_PCC_INPUT_PHYS "panasonic/hkey0"
|
||||
|
||||
|
|
@ -360,7 +358,16 @@ static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc)
|
|||
} else
|
||||
pr_err("Invalid HKEY.SINF data\n");
|
||||
}
|
||||
pcc->sinf[hkey->package.count] = -1;
|
||||
/*
|
||||
* pcc->sinf[] has pcc->num_sifr elements (valid indices
|
||||
* 0..num_sifr-1). On DSDTs where SINF's package count equals
|
||||
* num_sifr exactly -- the off-by-one case probe()'s num_sifr++
|
||||
* already allocates a spare element for -- there is no room left
|
||||
* for this trailing sentinel; nothing reads it back, so just skip
|
||||
* the write rather than running one element past the flex array.
|
||||
*/
|
||||
if (hkey->package.count < pcc->num_sifr)
|
||||
pcc->sinf[hkey->package.count] = -1;
|
||||
|
||||
end:
|
||||
kfree(buffer.pointer);
|
||||
|
|
@ -1017,8 +1024,6 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev)
|
|||
pcc->device = device;
|
||||
pcc->handle = device->handle;
|
||||
device->driver_data = pcc;
|
||||
strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), ACPI_PCC_CLASS);
|
||||
|
||||
result = acpi_pcc_init_input(pcc);
|
||||
if (result) {
|
||||
|
|
|
|||
|
|
@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = {
|
|||
{KE_KEY, 0x00011801, {KEY_ASSISTANT}},
|
||||
{KE_KEY, 0x00011901, {KEY_ASSISTANT}},
|
||||
|
||||
/* Keyboard backlight */
|
||||
{KE_IGNORE, 0x00000501, {}},
|
||||
{KE_IGNORE, 0x00800501, {}},
|
||||
{KE_IGNORE, 0x00050501, {}},
|
||||
{KE_IGNORE, 0x000a0501, {}},
|
||||
/* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */
|
||||
{KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}},
|
||||
{KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}},
|
||||
{KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}},
|
||||
{KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}},
|
||||
|
||||
/* Xiaomi G Command Center */
|
||||
{KE_KEY, 0x00010a01, {KEY_VENDOR}},
|
||||
|
||||
/* OEM preset power mode */
|
||||
{KE_IGNORE, 0x00011601, {}},
|
||||
{KE_IGNORE, 0x00021601, {}},
|
||||
{KE_IGNORE, 0x00031601, {}},
|
||||
{KE_IGNORE, 0x00041601, {}},
|
||||
/* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */
|
||||
{KE_KEY, 0x00011601, {KEY_PERFORMANCE}},
|
||||
{KE_KEY, 0x00021601, {KEY_PERFORMANCE}},
|
||||
{KE_KEY, 0x00031601, {KEY_PERFORMANCE}},
|
||||
{KE_KEY, 0x00041601, {KEY_PERFORMANCE}},
|
||||
|
||||
/* Fn Lock state */
|
||||
{KE_IGNORE, 0x00000701, {}},
|
||||
{KE_IGNORE, 0x00010701, {}},
|
||||
/* Fn Lock state: 1=on 0=off */
|
||||
{KE_KEY, 0x00000701, {KEY_FN_ESC}},
|
||||
{KE_KEY, 0x00010701, {KEY_FN_ESC}},
|
||||
|
||||
/* Fn+`/1/2/3/4 */
|
||||
{KE_KEY, 0x00011101, {KEY_F13}},
|
||||
|
|
|
|||
|
|
@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = {
|
|||
{ "SAM0428" },
|
||||
{ "SAM0429" },
|
||||
{ "SAM0430" },
|
||||
{ "SAMB430" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids);
|
||||
|
|
|
|||
|
|
@ -1264,7 +1264,7 @@ static void sony_nc_notify(acpi_handle ah, u32 event, void *data)
|
|||
ev_type = HOTKEY;
|
||||
sony_laptop_report_input_event(real_ev);
|
||||
}
|
||||
acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class,
|
||||
acpi_bus_generate_netlink_event("sony/hotkey",
|
||||
dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev);
|
||||
}
|
||||
|
||||
|
|
@ -3157,8 +3157,6 @@ static int sony_nc_probe(struct platform_device *pdev)
|
|||
return -ENODEV;
|
||||
|
||||
sony_nc_acpi_device = device;
|
||||
strscpy(acpi_device_class(device), "sony/hotkey");
|
||||
|
||||
sony_nc_acpi_handle = device->handle;
|
||||
|
||||
/* read device status */
|
||||
|
|
@ -4523,7 +4521,6 @@ static int sony_pic_probe(struct platform_device *pdev)
|
|||
return -ENODEV;
|
||||
|
||||
spic_dev.acpi_dev = device;
|
||||
strscpy(acpi_device_class(device), "sony/hotkey");
|
||||
sony_pic_detect_device_type(&spic_dev);
|
||||
mutex_init(&spic_dev.lock);
|
||||
|
||||
|
|
|
|||
|
|
@ -299,8 +299,6 @@ static int topstar_acpi_probe(struct platform_device *pdev)
|
|||
|
||||
platform_set_drvdata(pdev, topstar);
|
||||
|
||||
strscpy(acpi_device_name(device), "Topstar TPSACPI");
|
||||
strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS);
|
||||
topstar->device = device;
|
||||
|
||||
err = topstar_acpi_init(topstar);
|
||||
|
|
|
|||
|
|
@ -2505,8 +2505,7 @@ static void toshiba_acpi_kbd_bl_work(struct work_struct *work)
|
|||
LED_FULL : LED_OFF);
|
||||
|
||||
/* Emulate the keyboard backlight event */
|
||||
acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class,
|
||||
dev_name(&toshiba_acpi->acpi_dev->dev),
|
||||
acpi_bus_generate_netlink_event("", dev_name(&toshiba_acpi->acpi_dev->dev),
|
||||
0x92, 0);
|
||||
}
|
||||
|
||||
|
|
@ -3250,8 +3249,7 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data)
|
|||
break;
|
||||
}
|
||||
|
||||
acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
|
||||
dev_name(&acpi_dev->dev),
|
||||
acpi_bus_generate_netlink_event("", dev_name(&acpi_dev->dev),
|
||||
event, (event == 0x80) ?
|
||||
dev->last_key_event : 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, bt_dev);
|
||||
|
||||
result = toshiba_bluetooth_sync_status(bt_dev);
|
||||
if (result) {
|
||||
kfree(bt_dev);
|
||||
return result;
|
||||
}
|
||||
if (result)
|
||||
goto err_free_bt_dev;
|
||||
|
||||
bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth",
|
||||
&pdev->dev,
|
||||
|
|
|
|||
|
|
@ -136,9 +136,7 @@ static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data)
|
|||
|
||||
pr_debug("Received event: 0x%x\n", event);
|
||||
|
||||
acpi_bus_generate_netlink_event(device->pnp.device_class,
|
||||
dev_name(&device->dev),
|
||||
event, 0);
|
||||
acpi_bus_generate_netlink_event("", dev_name(&device->dev), event, 0);
|
||||
}
|
||||
|
||||
static void toshiba_haps_remove(struct platform_device *pdev)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -113,6 +113,8 @@
|
|||
|
||||
#define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0
|
||||
|
||||
#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC
|
||||
|
||||
#define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF
|
||||
|
||||
#define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0
|
||||
|
|
|
|||
|
|
@ -19,13 +19,10 @@
|
|||
|
||||
#define MODULE_NAME "xo15-ebook"
|
||||
|
||||
#define XO15_EBOOK_CLASS MODULE_NAME
|
||||
#define XO15_EBOOK_TYPE_UNKNOWN 0x00
|
||||
#define XO15_EBOOK_NOTIFY_STATUS 0x80
|
||||
|
||||
#define XO15_EBOOK_SUBCLASS "ebook"
|
||||
#define XO15_EBOOK_HID "XO15EBK"
|
||||
#define XO15_EBOOK_DEVICE_NAME "EBook Switch"
|
||||
|
||||
MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -105,12 +102,9 @@ static int ebook_switch_probe(struct platform_device *pdev)
|
|||
if (!id)
|
||||
return dev_err_probe(dev, -ENODEV, "Unsupported hid\n");
|
||||
|
||||
strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME);
|
||||
strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS);
|
||||
|
||||
snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id);
|
||||
|
||||
input->name = acpi_device_name(device);
|
||||
input->name = "EBook Switch";
|
||||
input->phys = button->phys;
|
||||
input->id.bustype = BUS_HOST;
|
||||
|
||||
|
|
|
|||
|
|
@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = {
|
|||
};
|
||||
|
||||
static const struct ssam_device_id surface_battery_match[] = {
|
||||
{ SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 },
|
||||
{ SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 },
|
||||
{ },
|
||||
{
|
||||
SSAM_SDEV(BAT, SAM, 0x01, 0x00),
|
||||
.driver_data = (unsigned long)&spwr_psy_props_bat1,
|
||||
}, {
|
||||
SSAM_SDEV(BAT, KIP, 0x01, 0x00),
|
||||
.driver_data = (unsigned long)&spwr_psy_props_bat2_sb3,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ssam, surface_battery_match);
|
||||
|
||||
|
|
|
|||
|
|
@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = {
|
|||
};
|
||||
|
||||
static const struct ssam_device_id surface_ac_match[] = {
|
||||
{ SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 },
|
||||
{ },
|
||||
{
|
||||
SSAM_SDEV(BAT, SAM, 0x01, 0x01),
|
||||
.driver_data = (unsigned long)&spwr_psy_props_adp1,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ssam, surface_ac_match);
|
||||
|
||||
|
|
|
|||
|
|
@ -66,15 +66,6 @@ enum sfh_dev_mode {
|
|||
SFH_MODE_TABLET = 3,
|
||||
};
|
||||
|
||||
enum laptop_placement {
|
||||
LP_UNKNOWN = 0,
|
||||
ON_TABLE,
|
||||
ON_LAP_MOTION,
|
||||
IN_BAG,
|
||||
OUT_OF_BAG,
|
||||
LP_UNDEFINED,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver
|
||||
* @npuclk_freq: NPU clock frequency [MHz]
|
||||
|
|
@ -83,6 +74,7 @@ enum laptop_placement {
|
|||
* @mpnpuclk_freq: MPNPU [MHz]
|
||||
* @npu_reads: NPU read bandwidth [MB/sec]
|
||||
* @npu_writes: NPU write bandwidth [MB/sec]
|
||||
* @npu_temp: NPU temperature [C]
|
||||
*/
|
||||
struct amd_pmf_npu_metrics {
|
||||
u16 npuclk_freq;
|
||||
|
|
@ -91,6 +83,7 @@ struct amd_pmf_npu_metrics {
|
|||
u16 mpnpuclk_freq;
|
||||
u16 npu_reads;
|
||||
u16 npu_writes;
|
||||
u16 npu_temp;
|
||||
};
|
||||
|
||||
int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op);
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
|
|||
int asus_hid_register_listener(struct asus_hid_listener *cdev);
|
||||
void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
|
||||
int asus_hid_event(enum asus_hid_event event);
|
||||
bool asus_wmi_custom_fan_curve_is_enabled(void);
|
||||
#else
|
||||
static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
|
||||
{
|
||||
|
|
@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event)
|
|||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
static inline bool asus_wmi_custom_fan_curve_is_enabled(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */
|
||||
|
|
|
|||
204
include/uapi/linux/amd-pmf.h
Normal file
204
include/uapi/linux/amd-pmf.h
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */
|
||||
/*
|
||||
* AMD Platform Management Framework (PMF) UAPI Header
|
||||
*
|
||||
* Copyright (c) 2026, Advanced Micro Devices, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This file defines the user-space API for interacting with the AMD PMF
|
||||
* driver. It provides ioctl interfaces to query platform-specific metrics
|
||||
* such as power source, slider position, platform type, laptop placement,
|
||||
* and various BIOS input/output parameters.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_LINUX_AMD_PMF_H
|
||||
#define _UAPI_LINUX_AMD_PMF_H
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* AMD_PMF_IOC_MAGIC - Magic number for AMD PMF ioctl commands
|
||||
*
|
||||
* This magic number uniquely identifies AMD PMF ioctl operations.
|
||||
*/
|
||||
#define AMD_PMF_IOC_MAGIC 'p'
|
||||
|
||||
/**
|
||||
* IOCTL_AMD_PMF_POPULATE_DATA - ioctl command to retrieve PMF metrics data
|
||||
*
|
||||
* This ioctl command is used to populate the amd_pmf_info structure
|
||||
* with the requested PMF metrics information.
|
||||
*/
|
||||
#define IOCTL_AMD_PMF_POPULATE_DATA _IOWR(AMD_PMF_IOC_MAGIC, 0x00, __u64)
|
||||
|
||||
#define AMD_PMF_BIOS_PARAMS_MAX 10
|
||||
|
||||
/* AMD PMF feature flags - bitmask indicating supported features */
|
||||
#define AMD_PMF_FEAT_AUTO_MODE BIT(0)
|
||||
#define AMD_PMF_FEAT_STATIC_POWER_SLIDER BIT(1)
|
||||
#define AMD_PMF_FEAT_POLICY_BUILDER BIT(2)
|
||||
#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3)
|
||||
#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4)
|
||||
|
||||
/**
|
||||
* enum amd_pmf_laptop_placement - Describes the physical placement of the laptop
|
||||
* @AMD_PMF_LP_UNKNOWN: Placement cannot be determined
|
||||
* @AMD_PMF_ON_TABLE: Laptop is placed on a stable surface like a table or desk
|
||||
* @AMD_PMF_ON_LAP_MOTION: Laptop is on a lap with detected motion
|
||||
* @AMD_PMF_IN_BAG: Laptop is detected to be inside a bag or case
|
||||
* @AMD_PMF_OUT_OF_BAG: Laptop has been removed from bag or case
|
||||
* @AMD_PMF_LP_UNDEFINED: Placement state is undefined
|
||||
*
|
||||
* This enumeration represents the physical placement state of the laptop
|
||||
* as detected by platform sensors. Used for adaptive power management
|
||||
* and thermal policies.
|
||||
*/
|
||||
enum amd_pmf_laptop_placement {
|
||||
AMD_PMF_LP_UNKNOWN,
|
||||
AMD_PMF_ON_TABLE,
|
||||
AMD_PMF_ON_LAP_MOTION,
|
||||
AMD_PMF_IN_BAG,
|
||||
AMD_PMF_OUT_OF_BAG,
|
||||
AMD_PMF_LP_UNDEFINED,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum amd_pmf_ta_slider - Trusted Application power slider positions
|
||||
* @AMD_PMF_TA_BEST_BATTERY: Maximum battery savings, minimal performance
|
||||
* @AMD_PMF_TA_BETTER_BATTERY: Balanced towards battery life
|
||||
* @AMD_PMF_TA_BETTER_PERFORMANCE: Balanced towards performance
|
||||
* @AMD_PMF_TA_BEST_PERFORMANCE: Maximum performance, higher power consumption
|
||||
* @AMD_PMF_TA_MAX: Sentinel value indicating maximum enum value
|
||||
*
|
||||
* This enumeration defines the power slider positions used by the
|
||||
* AMD PMF Trusted Application for dynamic power management decisions.
|
||||
* These correspond to the Windows power slider UI positions.
|
||||
*/
|
||||
enum amd_pmf_ta_slider {
|
||||
AMD_PMF_TA_BEST_BATTERY,
|
||||
AMD_PMF_TA_BETTER_BATTERY,
|
||||
AMD_PMF_TA_BETTER_PERFORMANCE,
|
||||
AMD_PMF_TA_BEST_PERFORMANCE,
|
||||
AMD_PMF_TA_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum amd_pmf_platform_type - Describes the physical form factor orientation
|
||||
* @AMD_PMF_PTYPE_UNKNOWN: Platform type cannot be determined
|
||||
* @AMD_PMF_LID_CLOSE: Laptop lid is closed
|
||||
* @AMD_PMF_CLAMSHELL: Traditional laptop mode with keyboard and screen
|
||||
* @AMD_PMF_FLAT: Device is lying flat on a surface
|
||||
* @AMD_PMF_TENT: Device is in tent mode (keyboard folded back, standing)
|
||||
* @AMD_PMF_STAND: Device is propped up in stand orientation
|
||||
* @AMD_PMF_TABLET: Device is in tablet mode with keyboard hidden
|
||||
* @AMD_PMF_BOOK: Device is in book reading orientation
|
||||
* @AMD_PMF_PRESENTATION: Device is in presentation mode
|
||||
* @AMD_PMF_PULL_FWD: Screen is pulled forward towards user
|
||||
* @AMD_PMF_PTYPE_INVALID: Invalid platform type marker
|
||||
*
|
||||
* This enumeration describes the current physical orientation or form
|
||||
* factor of convertible/2-in-1 devices. Used for optimizing power and
|
||||
* thermal management based on device posture.
|
||||
*/
|
||||
enum amd_pmf_platform_type {
|
||||
AMD_PMF_PTYPE_UNKNOWN,
|
||||
AMD_PMF_LID_CLOSE,
|
||||
AMD_PMF_CLAMSHELL,
|
||||
AMD_PMF_FLAT,
|
||||
AMD_PMF_TENT,
|
||||
AMD_PMF_STAND,
|
||||
AMD_PMF_TABLET,
|
||||
AMD_PMF_BOOK,
|
||||
AMD_PMF_PRESENTATION,
|
||||
AMD_PMF_PULL_FWD,
|
||||
AMD_PMF_PTYPE_INVALID = 0xf,
|
||||
};
|
||||
|
||||
static inline const char *amd_pmf_get_platform_type(unsigned int platform_type)
|
||||
{
|
||||
switch (platform_type) {
|
||||
case AMD_PMF_CLAMSHELL:
|
||||
return "CLAMSHELL";
|
||||
case AMD_PMF_LID_CLOSE:
|
||||
return "LID_CLOSE";
|
||||
case AMD_PMF_FLAT:
|
||||
return "FLAT";
|
||||
case AMD_PMF_TENT:
|
||||
return "TENT";
|
||||
case AMD_PMF_STAND:
|
||||
return "STAND";
|
||||
case AMD_PMF_TABLET:
|
||||
return "TABLET";
|
||||
case AMD_PMF_BOOK:
|
||||
return "BOOK";
|
||||
case AMD_PMF_PRESENTATION:
|
||||
return "PRESENTATION";
|
||||
case AMD_PMF_PULL_FWD:
|
||||
return "PULL_FWD";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *amd_pmf_get_laptop_placement(unsigned int device_state)
|
||||
{
|
||||
switch (device_state) {
|
||||
case AMD_PMF_ON_TABLE:
|
||||
return "ON_TABLE";
|
||||
case AMD_PMF_ON_LAP_MOTION:
|
||||
return "ON_LAP_MOTION";
|
||||
case AMD_PMF_IN_BAG:
|
||||
return "IN_BAG";
|
||||
case AMD_PMF_OUT_OF_BAG:
|
||||
return "OUT_OF_BAG";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *amd_pmf_get_slider_position(unsigned int state)
|
||||
{
|
||||
switch (state) {
|
||||
case AMD_PMF_TA_BEST_PERFORMANCE:
|
||||
return "PERFORMANCE";
|
||||
case AMD_PMF_TA_BETTER_PERFORMANCE:
|
||||
return "BALANCED";
|
||||
case AMD_PMF_TA_BEST_BATTERY:
|
||||
return "POWER_SAVER";
|
||||
case AMD_PMF_TA_BETTER_BATTERY:
|
||||
return "BALANCED_BATTERY";
|
||||
default:
|
||||
return "Unknown TA Slider State";
|
||||
}
|
||||
}
|
||||
|
||||
struct amd_pmf_info {
|
||||
__u64 size;
|
||||
|
||||
/* Feature info */
|
||||
__u32 features_supported;
|
||||
|
||||
/* Power and state info */
|
||||
__u32 platform_type;
|
||||
__u32 power_source;
|
||||
__u32 laptop_placement;
|
||||
__u32 lid_state;
|
||||
__u32 user_presence;
|
||||
__u32 slider_position;
|
||||
|
||||
/* Thermal and power metrics */
|
||||
__s32 skin_temp;
|
||||
__u32 gfx_busy;
|
||||
__s32 ambient_light;
|
||||
__u32 avg_c0_residency;
|
||||
__u32 max_c0_residency;
|
||||
__u32 socket_power;
|
||||
|
||||
/* BIOS parameters */
|
||||
__u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX];
|
||||
__u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX];
|
||||
};
|
||||
|
||||
#endif /* _UAPI_LINUX_AMD_PMF_H */
|
||||
60
tools/platform/x86/amd/Makefile
Normal file
60
tools/platform/x86/amd/Makefile
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
ifeq ($(srctree),)
|
||||
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
|
||||
srctree := $(patsubst %/,%,$(dir $(srctree)))
|
||||
srctree := $(patsubst %/,%,$(dir $(srctree)))
|
||||
srctree := $(patsubst %/,%,$(dir $(srctree)))
|
||||
endif
|
||||
|
||||
# Include common tools build infrastructure
|
||||
include $(srctree)/tools/scripts/Makefile.include
|
||||
|
||||
CC = $(CROSS_COMPILE)gcc
|
||||
BUILD_OUTPUT := $(CURDIR)
|
||||
PREFIX ?= /usr
|
||||
DESTDIR ?=
|
||||
|
||||
ifeq ("$(origin O)", "command line")
|
||||
BUILD_OUTPUT := $(O)
|
||||
endif
|
||||
|
||||
# Include paths: tools/include has linux/kernel.h with ARRAY_SIZE
|
||||
INCLUDES = -I$(srctree)/tools/include
|
||||
INCLUDES += -I$(srctree)/tools/include/uapi
|
||||
INCLUDES += -I$(srctree)/include/uapi
|
||||
INCLUDES += -I$(srctree)/include
|
||||
|
||||
override CFLAGS += -O2 -Wall -Wextra -D_GNU_SOURCE $(INCLUDES)
|
||||
override CFLAGS += -D_FILE_OFFSET_BITS=64
|
||||
override CFLAGS += -D__EXPORTED_HEADERS__
|
||||
|
||||
TARGETS = test_amd_pmf
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
test_amd_pmf: test-pmf.c
|
||||
$(QUIET_CC)$(CC) $(CFLAGS) $< -o $(BUILD_OUTPUT)/$@ $(LDFLAGS)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(call QUIET_CLEAN, test_amd_pmf)$(RM) $(BUILD_OUTPUT)/test_amd_pmf
|
||||
|
||||
.PHONY: install
|
||||
install: $(TARGETS)
|
||||
$(INSTALL) -d $(DESTDIR)$(PREFIX)/bin
|
||||
$(INSTALL) $(BUILD_OUTPUT)/test_amd_pmf $(DESTDIR)$(PREFIX)/bin/test_amd_pmf
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "AMD Platform Tools Makefile"
|
||||
@echo ""
|
||||
@echo "Targets:"
|
||||
@echo " all - Build all tools (default)"
|
||||
@echo " test_amd_pmf - Build the PMF test tool"
|
||||
@echo " clean - Remove built files"
|
||||
@echo " install - Install tools to $(PREFIX)/bin"
|
||||
@echo ""
|
||||
@echo "Variables:"
|
||||
@echo " O=<dir> - Build output directory"
|
||||
@echo " PREFIX - Installation prefix (default: /usr)"
|
||||
@echo " DESTDIR - Destination directory for install"
|
||||
142
tools/platform/x86/amd/test-pmf.c
Normal file
142
tools/platform/x86/amd/test-pmf.c
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* AMD Platform Management Framework Test Tool
|
||||
*
|
||||
* Copyright (c) 2026, Advanced Micro Devices, Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
||||
* Sanket Goswami <Sanket.Goswami@amd.com>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/amd-pmf.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#define DEVICE_NODE "/dev/amdpmf_interface"
|
||||
|
||||
/* Feature flag names */
|
||||
static const char * const feature_names[] = {
|
||||
"Auto Mode",
|
||||
"Static Power Slider",
|
||||
"Policy Builder (Smart PC)",
|
||||
"Dynamic Power Slider AC",
|
||||
"Dynamic Power Slider DC",
|
||||
};
|
||||
|
||||
static const char *banner =
|
||||
"====================================================\n"
|
||||
" AMD PMF Metrics info and Feature Status\n"
|
||||
"====================================================\n\n";
|
||||
|
||||
/* Print feature flags */
|
||||
static void pmf_print_features(uint32_t flags)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(feature_names); i++)
|
||||
printf(" [%c] %s\n", (flags & (1U << i)) ? 'x' : ' ', feature_names[i]);
|
||||
}
|
||||
|
||||
/* Print BIOS parameters */
|
||||
static void pmf_print_bios_params(const char *type, const __u32 *params)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < AMD_PMF_BIOS_PARAMS_MAX; i++)
|
||||
printf(" Custom BIOS %s%d: %u\n", type, i + 1, params[i]);
|
||||
}
|
||||
|
||||
/* Open the PMF device */
|
||||
static int pmf_open_device(void)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = open(DEVICE_NODE, O_RDONLY);
|
||||
if (fd < 0)
|
||||
fprintf(stderr, "Error: Cannot open %s: %s\n", DEVICE_NODE, strerror(errno));
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* Query PMF info using the single IOCTL */
|
||||
static int pmf_get_info(int fd, struct amd_pmf_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Zero-initialize and set size for versioning */
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->size = sizeof(*info);
|
||||
|
||||
ret = ioctl(fd, IOCTL_AMD_PMF_POPULATE_DATA, info);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Error: IOCTL_AMD_PMF_POPULATE_DATA failed: %s\n", strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pmf_print_info(const struct amd_pmf_info *info)
|
||||
{
|
||||
printf("%s", banner);
|
||||
|
||||
/* Feature status */
|
||||
printf("Feature Status:\n");
|
||||
pmf_print_features(info->features_supported);
|
||||
|
||||
/* Device states */
|
||||
printf("\nDevice States:\n");
|
||||
printf(" Platform Type: %s\n", amd_pmf_get_platform_type(info->platform_type));
|
||||
printf(" Laptop Placement: %s\n", amd_pmf_get_laptop_placement(info->laptop_placement));
|
||||
printf(" Lid State: %s\n", info->lid_state ? "Closed" : "Open");
|
||||
printf(" User Presence: %s\n", info->user_presence ? "Present" : "Away");
|
||||
printf(" Slider Position: %s\n", amd_pmf_get_slider_position(info->slider_position));
|
||||
|
||||
/* Thermal and power metrics */
|
||||
printf("\nThermal/Power Metrics:\n");
|
||||
printf(" Skin Temperature: %d\n", info->skin_temp / 100);
|
||||
printf(" GFX Busy: %u\n", info->gfx_busy);
|
||||
printf(" Ambient Light: %d\n", info->ambient_light);
|
||||
printf(" Avg C0 Residency: %u\n", info->avg_c0_residency);
|
||||
printf(" Max C0 Residency: %u\n", info->max_c0_residency);
|
||||
printf(" Socket Power: %u\n", info->socket_power);
|
||||
|
||||
/* BIOS parameters */
|
||||
printf("\nCustom BIOS Input Parameters:\n");
|
||||
pmf_print_bios_params("Input", info->bios_input);
|
||||
printf("\nCustom BIOS Output Parameters:\n");
|
||||
pmf_print_bios_params("Output", info->bios_output);
|
||||
|
||||
printf("\n=================================================\n");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct amd_pmf_info info;
|
||||
int fd, ret;
|
||||
|
||||
fd = pmf_open_device();
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
/* Query all info with single IOCTL */
|
||||
ret = pmf_get_info(fd, &info);
|
||||
close(fd);
|
||||
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
pmf_print_info(&info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user