mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
platform-drivers-x86 for v7.4-1
Highlights:
- acer-wmi:
- Add HWMON support for Aspire A315-58G
- Add platform profile, HWMON, and fan control support for Nitro
ANV15-51
- Add support for mode key on some models (e.g. Nitro AN16-41)
- acer-wmi-battery: Add battery control driver
- ACPI: video: Remove CONFIG_X86 dependency as WMI now supports non-X86
archs
- alienware-wmi-wmax: Add support for Alienware Area-51 AAT2250
- amd/hsmp: Add support for Family 1Ah client platforms
- amd/pmc:
- Do not rely on intermediate cycle counting in the wakeup delay logic
- Make HP Laptop 14-fq1xxxx and Thinkpad L14 Gen3 (21C5) use s2idle
quirk
- Make Asus ROG Flow Z13 use spurious_8042 quirk
- Add support for AMD 1Ah Family DCh SoC
- amd/pmf:
- Add support for AMDI0112
- Fix compat ioctl handling
- arm64: asus-glymur-ec: Add support
- arm64: lenovo-yoga-slim7x-gen11-ec: Add support
- arm64: qcom-hamoa-ec: Disable SCI IRQ during suspend
- arm64: thinkpad-t14s-ec: Do not send KEY_KBDILLUMTOGGLE on FN + Space
as EC already steps keyboard backlight
- asus-armoury:
- Add discrete GPU power state attribute
- Add support for discrete GPU disable on ProArt H7606
- Support tunables with more models
- asus-nw-wmi: Make ROG Flow X13 GV302X use the tablet-mode switch quirk
- asus-wmi:
- Make ASUS ProArt PX13 use the tablet-mode switch quirk
- Add support for mute LED
- Fix screenpad backlight problems
- Correct kbd_rgb_state_index description (keyboard -> shutdown)
- ayaneo-ec: Add support for AYANEO 2S
- bitland-mifs-wmi: Correct interpretation of WMI call output
- dell-dw5826e: Add support for _DSM with revision 0 (Slate PTL CS 14,
Yukon PTL CSLW 14, Yukon PTL 360 14)
- hp-bioscfg: Many fixes
- hp-wmi:
- Add change control support for HP Spectre x360 16-aa0xxx
- Do not show any fan attribute when no fans are available
- Add support for OMEN 16-am0xxx, OMEN 16-am2xxx, OMEN 17-ck1xxx, OMEN
MAX 16-ah0xxx, OMEN Slim 16t-an000, OMEN Transcend 14-fb1xxx
- Add HP Laptop 15-fd1xxx support
- Add Victus 15-fb3xxx, Victus 16-d1xxx (8A26), Victus 16-e0xxx support
- huawei-wmi: Add support for Honor MagicBook camera privacy toggle key
- ideapad-laptop:
- Add support for Mode key and Phone Link key found on some Yoga Pro
7 models
- Add support for camera switch on some Yoga Pro 7 and Legion Pro 7
models
- intel/hid: Support 5-button array on HP EliteBook x360 1030 G2
- intel/pmc:
- Add rugged Panther Lake support
- Fix Nova Lake PCD-S telemetry constants
- intel/pmt:
- Block mmap when read_telem() callback is present
- Fix PCI device to device generalization
- intel/speed-select-if: Always enumerate on recent server CPUs
(Family 19, i.e., Diamond Rapids and onwards)
- intel/tpmi: Prepare for enumerating non-PCI devices
- intel-uncore-freq:
- Restore limits properly also on sparse setups
- Add rugged Panther Lake support
- intel/vsec:
- Reset state properly before re-enumeration
- Fix inverted logic in get features function
- lenovo-wmi-capdata: Support non-supported, stubbed WMI query method
for capdata device
- msi-ec:
- Prevent out of bounds charge thresholds
- Add support for MSI Creator Z16 A11UE, MSI GL65 Leopard 9SCXK,
MSI Katana 15 B13VFK
- msi-wmi: Ignore key events for keys already handled by atkbd
- oxpec:
- Add support for charge limit on OneXPlayer Apex
- Add OneXPlayer 3 support
- panasonic-laptop:
- Add support for FN key
- surface: aggregator_registry:
- Remove non-existing sensors device and fix tablet-mode detection on
Surface Pro 11
- Add performance profile and fan support for Surface Pro 11
- thinkpad_acpi:
- Add keyboard attachment detection and reporting found on Thinkpad
X1 Fold
- Add support for Thinkpad L440 fan speed reporting
- tools/power/x86/intel-speed-select:
- Correct make clean
- Increase wait time after SST PP level switch to prevent stale
capability readings
- tuxedo:
- Fix keyboard LED map ordering
- Correct interpreting intensity vs RGB levels
- uniwill-laptop:
- Correct brightness handling for 3 level keyboard
- Add keyboard backlight support for TUXEDO devices
- Add support for PCSpecialist Recoil 16 AMD
- Add support for screen rotation key found on PCSpecialist Recoil 16
AMD and TUXEDO Stellaris 16 Gen7
- Implement rainbow animation as trigger
- Correct led labels to match LED subsystem conventions
- Add support for LAPKC71E Intel NUC x15
- wmi:
- Remove X86 dependency to allow supporting arm64 laptops in future
- wmi-bmof: Move under drivers/platform/wmi/
- Miscellaneous cleanups / refactoring / improvements
The following is an automated shortlog grouped by driver:
acer-wmi:
- Add support for Acer Nitro AN16-41
- Add support for Acer Nitro ANV15-51
- Cycle platform profile on mode key 0x4
- enable hwmon on Aspire A315-58G
- Remove unused platform_profile_support variable
- split hwmon from platform profile quirk
- use bool for boolean quirk fields
ACPI: video:
- Remove CONFIG_X86 handling from nvidia_wmi_ec_supported()
add Acer battery control driver:
- add Acer battery control driver
Add ACPI_COMPANION() NULL check in topstar_acpi_probe:
- Add ACPI_COMPANION() NULL check in topstar_acpi_probe
amd: hfi:
- Fix a use-after-free when unloading the driver
- Fix out-of-bounds reads when parsing ranking data
- Rely on ACPI enumeration instead of a dummy device
amd/hsmp:
- Add HSMP client support for Family 1Ah
- Document and expose client telemetry table in UAPI
- Recognize Family 1Ah client platforms and hide server-only paths
- Route metric table through the client messages
amd/pmc:
- Add HP Laptop 14-fq1xxx to s2idle quirk list
- Add PMC driver support for AMD 1Ah Family DCh SoC
- Add spurious_8042 to ASUS ROG Flow Z13 GZ302EA
- Add Thinkpad L14 Gen3 (21C5) to quirk_s2idle_bug
- clear devm-managed pointers on remove to prevent stale reuse
amd: pmc:
- Drop scratch_reg from amd_1ah_m80_cpu_info
- Only create amd_pmc_idlemask if scratch defined
amd/pmf:
- Add AMDI0112 ACPI ID
- fix compat ioctl handling
- Fix power_supply refcount leak in amd_pmf_get_battery_prop()
- Inline !AMD_PMF_DEBUG stub
- Inline simple helper function of Smart PC TA interfaces
- Inline simple helper function of SPS Layer
- Inline simple helper functions of Core Layer
- Inline wrappers of ap{mf,ts}_if_call_store_buffer()
- Validate the APTS state index
amd:
- Stop counting intermediate cycles with metrics table
arm64:
- Add a driver for the EC found on ASUS Glymur machines
- add Lenovo Yoga Slim 7x Gen 11 EC driver
arm64: qcom-hamoa-ec:
- Disable the SCI IRQ during suspend
arm64:
- Remove redundant dev_err_probe()
arm64: thinkpad-t14s-ec:
- don't send KEY_KBDILLUMTOGGLE on Fn+Space
asus-armoury:
- add dGPU disable fallback DEVID for ProArt H7606 series
- Add dgpu_power_state attribute
- add power limits quirk for FA608PM
- add power limits quirk for FA608UH
- add power limits quirk for FA608UP
- add power limits quirk for G533QS
- add power limits quirk for G614FM
- add power limits quirk for G713QR
- Add power limits quirk for GA401IHR
- add power limits quirk for GA402NV
- add power limits quirk for GU605CM
- add support for GX651AR
- Add XBOX ROG Ally X 20 Tunables
- let attribute groups decide their own visibility
- move has_valid_limit() above the attribute declarations
- Prevent disabling dGPU when in use
- use sysfs_create_groups() and sysfs_remove_groups()
asus-laptop:
- Fix typo in infos_show() comment
asus-nb-wmi:
- add tablet mode quirk for ROG Flow X13 GV302X
- Fix typo in asus_nb_wmi_quirks() comment
asus-wmi:
- Add ASUS_WMI_DEVID_DGPU_POWER_STATE define
- Add mute LED support
- add tablet-mode switch quirk for ASUS ProArt PX13
- don't warn when LPS0 is unavailable
- fix screenpad power state detection
- fix TUF keyboard power state shutdown naming
- fix unclear usage of bd->props.power
- fix use-after-free unregistering kbd_led led
- name the TUF keyboard RGB state and speed codes
- Remove redundant per-device wmi_lock and duplicate rfkill ops
- Remove unused platform_profile_support member
- remove unused screenpad_brightness
- rename TUF RGB state DEVID to _CMD
- Serialize WMI method evaluations with a mutex
- use backlight_is_blank() for screenpad power
- validate custom fan curves on enable
ayaneo-ec:
- Add AYANEO 2S charge control
bitland-mifs-wmi:
- Detect failed function calls
- Treat WMI_FN_SYSTEM_AC_TYPE as optional
dell: alienware-wmi-wmax:
- add Alienware Area-51 AAT2250
dell-dw5826e:
- Extend _DSM support to revision 0
dell-wmi-aio:
- Convert to use the bus-based WMI API
- Use named defines for event types
docs: TPMI:
- use literal block for commands
gpd pocket fan:
- Cancel work before releasing GPIOs
hp_accel:
- Fix typo in lis3lv02d_acpi_write() comment
hp-bioscfg:
- allow clearing current_password
- consolidate common package-element parsing
- fix 16-byte heap overflow for empty auth token
- fix non-ASCII truncation in hp_get_string_from_buffer()
- fix off-by-one in password length check
- fix OOB reads in hp_get_string_from_buffer()
- fix oops on short integer attribute buffer
- fix slab-out-of-bounds write in hp_convert_hexstr_to_str()
- NUL-terminate the SPM auth token
- remove dead bounds check in audit_log_entries_show
- return -E2BIG from validate_password_input()
- Support allocating large number of BIOS vars
- validate SPM state returned by firmware
- zero the hex-string decode buffer in hp_convert_hexstr_to_str()
hp-wmi:
- Add board 8E41 (OMEN Transcend 14) to feature board list
- add charge_behaviour support for Spectre 8C15/8C17
- Add OMEN board 8A17 thermal profile support
- Add OMEN board 8A18 thermal profile support
- Add OMEN board 8D40 thermal profile support
- Add OMEN board 8D42 thermal profile support
- Add support for HP Laptop 15-fd1xxx (board 8DE1)
- Add support for Omen 16-am0xxx (8D3F)
- add support for OMEN 16-am2xxx boards
- Add Victus 15-fb3xxx support
- Add Victus 16-d1xxx board 8A26 support
- Add Victus 16-e0xxx board 88EE support
- Disable hwmon attributes when no fans are usable
- fix heap OOB read and memory corruption in hp_wmi_perform_query()
- Fix hwmon keep-alive teardown
- Remove unused platform_profile_support variable
huawei-wmi:
- add camera toggle keycode
ibm_rtl:
- Fix potential OOB read at signature search
ideapad-laptop:
- Add Yoga Pro 7 14IAH10 Mode key
- Map Smart Connect hotkey to KEY_LINK_PHONE
- Report camera switch as SW_CAMERA_LENS_COVER
int1092:
- Mark as orphan
intel/hid:
- Enable 5 button array on HP EliteBook x360 1030 G2
intel/pmc:
- Add rugged Panther Lake support to intel_pmc_core
- Fix stale NVL PCD-S S0ix_LPM telemetry constants
intel/pmt:
- Add register access callbacks
- Block mmap when callback is present
- complete pcidev to device update
- Do not remap when using callbacks
- refactor rc with a return value
- refactor rmw with a return value
intel: power-domains:
- Handle failed init in tpmi_get_linux_die_id()
- Handle failed init in tpmi_get_power_domain_mask()
intel_telemetry:
- initialize default trace verbosity
intel/tpmi:
- Build the TPMI control words in one expression
- Declare the loop counters in their loops
- Describe a TPMI instance by its two devices
- Describe the device creation policy on the function
- Do not assume TPMI is enumerated from PCI
- Drop unused arg from tpmi_set_control_base()
- Fix typo in TPMI_FEATURE_NAME_LEN comment
- Get tpmi_info directly from the parent device
- Include the headers tpmi_common.c relies on
- Keep the feature resources in intel_tpmi_info
- Remove unused vsec_dev from intel_tpmi_pm_feature
- Split off the PCI VSEC enumeration
- Split out enumeration independent init and exit
- Turn TPMI_GET_SINGLE_ENTRY_SIZE() into a function
- Use DEFINE_RES_MEM() for the feature resources
- Use DEFINE_SHOW_STORE_ATTRIBUTE() for mem_write
- Use guard() for tpmi_dev_lock in the status read
- Use scnprintf() to build the debugfs names
- Use scope based cleanup in the memory dump
- Use static strings for the feature device names
intel-uncore-freq:
- Add support for rugged Panther Lake
- Continue restoring limits on resume
intel/vsec:
- Fix inverted walk_header() test in get_features()
- Pass a struct device to intel_vsec_set_mapping()
- reset state before re-enumerating
- Use bool for run_once
intel: wmi:
- Remove redundant callbacks
- Use sysfs_emit in sbl-fw-update
ISST:
- Avoid model check for recent servers
lenovo-wmi-capdata:
- Adopt new WMI API
- Defer mutex initialization
- Detect stubbed capdata device
- Do not match missing components
- Do not stop the AC notifier chain on error
- Only allocate sub-master info when necessary
lenovo-wmi-{capdata,other}:
- Only allocate capdata list when necessary
lenovo-wmi-capdata:
- Register component even on WMI error
- Store a pointer to component info
lenovo-wmi-helpers:
- Adopt new WMI API
lib/string_choices:
- Add str_supported_unsupported() helper
MAINTAINERS:
- Add Aaron Erhardt as maintainer of TUXEDO DRIVERS
- Add myself as a LENOVO drivers co-maintainer
mellanox:
- Remove redundant dev_err()
msi-ec:
- Add MSI Creator Z16 A11UE EC firmware
- Add MSI GL65 Leopard 9SCXK EC firmware
- Add MSI Katana 15 B13VFK EC firmware
- Report valid charge thresholds when unset
msi-laptop:
- Fix typo in struct quirk_entry comment
msi-wmi:
- Fix hotkeys dropped with vendor backlight
- Ignore WMI events for keys also handled by atkbd
- Sort scancodes numerically
olpc:
- Reserve space for a string terminator
oxpec:
- Add OneXPlayer 3 quirk
- Fix Apex charge limit control
panasonic-laptop:
- Fix typo in the file history comment
- register FN key up/down events
Remove redundant dev_err()/dev_err_probe():
- Remove redundant dev_err()/dev_err_probe()
platform/x86: thinkpad_acpi:
- Revert: Add X9-14 Gen 1 to non-standard thermal/fan quirk tables
samsung-laptop:
- Fix typo in sabi_configs comment
simatic-ipc:
- fix platform device leak on registration failure
surface: aggregator_registry:
- Add performance profile and fan support for Surface Pro 11
- Add Surface Laptop 8th Edition
- Fix tablet-mode detection on Surface Pro 11
- Remove sensors device for Surface Pro 11
think-lmi:
- Use ARRAY_SIZE() in tlmi_errstr_to_err()
- Use scnprintf() in new_password_store()
- Use sysfs_emit() in pending_reboot_show()
thinkpad-acpi:
- Add X1 Fold keyboard attachment detection
thinkpad_acpi:
- Add X9-14 Gen 1 to non-standard thermal/fan quirk tables
- convert conditional mutex locks to ACQUIRE_ERR()
- convert mutex_lock() to guard(mutex)
- fix deadlock in hotkey input open/close
- Fix fan speed reporting on ThinkPad L440
- fix typos in comments
- Remove unreachable code in beep_read()
- Use bool for local is_thinkpad variable
- use __free(kfree) for automatic cleanup
- Use input_device_enabled()
- Use str_supported_unsupported() helper
tools/power/x86/intel-speed-select:
- Clean all the produced files
- Correct macro name in debug output
- Fix signed shift UB in BIT() macro
- Increase wait time after SST PP level switch
- v1.27 release
toshiba_haps:
- Remove unnecessary ret in toshiba_haps_resume()
tuxedo:
- Don't use device driver data
- Fix keyboard LED map ordering
- Set HID report ID on success
- Update and extend documentation
- Use intensity according to HID spec
uniwill-laptop:
- Add PCSpecialist Recoil 16 AMD
- Enable kb backlight and lightbar for TUXEDO
- Extend support for the Intel NUC x15
- Fix breathing animation on Intel QC
- Fix brightness notify for 3 level keyboards
- Fix typo in the kbd LED brightness comment
- Implement rainbow animation as trigger
- Label multicolor LEDs correctly
- Map the screen rotation key
- Report correct lightbar brightness
wmi-bmof:
- Move to generic WMI code
wmi:
- Introduce wmidev_exists()
- Remove dependency on CONFIG_X86
yogabook:
- use assign_bit()/change_bit() where applicable
Merges:
- Merge branch 'fixes' into review-ilpo-next
- Merge branch 'platform-drivers-x86-intel-pmt' into for-next
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQSCSUwRdwTNL2MhaBlZrE9hU+XOMQUCauHB9QAKCRBZrE9hU+XO
MZV2AQCkkN0AWkpcntUWH2Ocw4fxIkGGGit7runnoFMsO0OApgEAtYECez8g+lbW
UHrOn4M0tqqGx6qcj0/isbJP6TApGwI=
=cGZu
-----END PGP SIGNATURE-----
Merge tag 'platform-drivers-x86-v7.4-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86
Pull x86 platform driver updates from Ilpo Järvinen:
"The usual updates for laptop quirks from HP, ASUS, Acer, Dell etc:
- new model names and recent CPU support
- more special button / function key handling
- battery and hardware monitoring quirks
Tool updates to intel-speed-select
WMI now supports non-x86 architectures
Miscellaneous cleanups / refactoring / improvements"
* tag 'platform-drivers-x86-v7.4-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (237 commits)
platform/x86: asus-wmi: name the TUF keyboard RGB state and speed codes
platform/x86: asus-wmi: fix TUF keyboard power state shutdown naming
platform/x86: asus-wmi: rename TUF RGB state DEVID to _CMD
platform/x86: asus-armoury: add power limits quirk for GU605CM
platform/x86: asus-armoury: add power limits quirk for G533QS
platform/x86: asus-armoury: add power limits quirk for GA402NV
platform/x86: asus-armoury: add power limits quirk for FA608UP
platform/x86: asus-armoury: add power limits quirk for FA608UH
platform/x86: asus-armoury: add power limits quirk for FA608PM
platform/x86: asus-armoury: add power limits quirk for G713QR
platform/x86: ibm_rtl: Fix potential OOB read at signature search
platform/x86: acer-wmi: Add support for Acer Nitro AN16-41
platform/x86: acer-wmi: Cycle platform profile on mode key 0x4
platform/x86: hp-wmi: Disable hwmon attributes when no fans are usable
platform/x86: gpd pocket fan: Cancel work before releasing GPIOs
platform/x86: asus-armoury: Add XBOX ROG Ally X 20 Tunables
platform/x86: msi-wmi: Ignore WMI events for keys also handled by atkbd
platform/x86: msi-wmi: Sort scancodes numerically
platform: arm64: Add a driver for the EC found on ASUS Glymur machines
MAINTAINERS: Add myself as a LENOVO drivers co-maintainer
...
This commit is contained in:
commit
c339f4010d
10
Documentation/ABI/removed/sysfs-driver-uniwill-laptop
Normal file
10
Documentation/ABI/removed/sysfs-driver-uniwill-laptop
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
What: /sys/bus/platform/devices/INOU0000:XX/rainbow_animation
|
||||
Date: November 2025
|
||||
KernelVersion: 6.19
|
||||
Contact: Armin Wolf <W_Armin@gmx.de>
|
||||
Description:
|
||||
Forces the integrated lightbar to display a rainbow animation when the machine
|
||||
is not suspended. Writing "1"/"0" into this file enables/disables this
|
||||
functionality.
|
||||
|
||||
Reading this file returns the current status of the rainbow animation functionality.
|
||||
|
|
@ -25,9 +25,11 @@ Description:
|
|||
Allows to write at any offset. It doesn't check for Read/Write access
|
||||
as hardware will not allow to write at read-only memory. This write is
|
||||
at offset multiples of 4. The format is instance,offset,contents.
|
||||
Example:
|
||||
echo 0,0x20,0xff > mem_write
|
||||
echo 1,64,64 > mem_write
|
||||
Example::
|
||||
|
||||
echo 0,0x20,0xff > mem_write
|
||||
echo 1,64,64 > mem_write
|
||||
|
||||
Users: Debugging, any user space test suite
|
||||
|
||||
What: /sys/kernel/debug/tpmi-<n>/plr/domain<n>/status
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
What: /sys/bus/i2c/devices/*/fn_lock
|
||||
Date: October 2026
|
||||
KernelVersion: 7.5
|
||||
Contact: Oleg Keri <okerixx@gmail.com>
|
||||
Description:
|
||||
Control fn-lock mode.
|
||||
|
||||
* 1 -> Switched On
|
||||
* 0 -> Switched Off
|
||||
|
||||
When switched on, the function keys act as F1..F12 and the
|
||||
multimedia functions require the Fn modifier.
|
||||
|
||||
The embedded controller reports the Fn+Esc key as an event but
|
||||
keeps no state of its own, so this attribute is the state: the
|
||||
driver toggles it when the key is pressed, and writing it takes
|
||||
effect immediately. A toggle from the key is signalled with
|
||||
sysfs_notify(), so the attribute can be polled.
|
||||
|
||||
For example::
|
||||
|
||||
# echo "0" > /sys/bus/i2c/devices/9-0070/fn_lock
|
||||
|
|
@ -29,25 +29,14 @@ Description:
|
|||
Reading this file returns the current enable status of the touchpad toggle
|
||||
functionality.
|
||||
|
||||
What: /sys/bus/platform/devices/INOU0000:XX/rainbow_animation
|
||||
Date: November 2025
|
||||
KernelVersion: 6.19
|
||||
Contact: Armin Wolf <W_Armin@gmx.de>
|
||||
Description:
|
||||
Forces the integrated lightbar to display a rainbow animation when the machine
|
||||
is not suspended. Writing "1"/"0" into this file enables/disables this
|
||||
functionality.
|
||||
|
||||
Reading this file returns the current status of the rainbow animation functionality.
|
||||
|
||||
What: /sys/bus/platform/devices/INOU0000:XX/breathing_in_suspend
|
||||
Date: November 2025
|
||||
KernelVersion: 6.19
|
||||
Contact: Armin Wolf <W_Armin@gmx.de>
|
||||
Description:
|
||||
Causes the integrated lightbar to display a breathing animation when the machine
|
||||
has been suspended and is running on AC power. Writing "1"/"0" into this file
|
||||
enables/disables this functionality.
|
||||
has been suspended, is running on AC power and the lightbar brightness not zero.
|
||||
Writing "1"/"0" into this file enables/disables this functionality.
|
||||
|
||||
Reading this file returns the current status of the breathing animation
|
||||
functionality.
|
||||
|
|
|
|||
|
|
@ -583,7 +583,8 @@ Events that are propagated by the driver to userspace:
|
|||
0x6030 System thermal table changed
|
||||
0x6032 Thermal Control command set completion (DYTC, Windows)
|
||||
0x6040 Nvidia Optimus/AC adapter related (TO BE VERIFIED)
|
||||
0x60C0 X1 Yoga 2016, Tablet mode status changed
|
||||
0x60C0 X1 Yoga 2016 tablet mode or X1 Fold 16 Gen 1 keyboard
|
||||
attachment state changed
|
||||
0x60F0 Thermal Transformation changed (GMTS, Windows)
|
||||
====== =====================================================
|
||||
|
||||
|
|
@ -1658,6 +1659,29 @@ if damage detected:
|
|||
The property is read-only. If feature is not supported then sysfs
|
||||
attribute is not created.
|
||||
|
||||
X1 Fold keyboard attachment
|
||||
---------------------------
|
||||
|
||||
sysfs attribute: keyboard_attached_on_screen
|
||||
|
||||
ThinkPad X1 Fold 16 Gen 1 systems can report whether the keyboard is
|
||||
currently attached on-screen.
|
||||
|
||||
This attribute is currently supported on ThinkPad X1 Fold 16 Gen 1
|
||||
systems only.
|
||||
|
||||
The command to check the keyboard attachment state is::
|
||||
|
||||
cat /sys/devices/platform/thinkpad_acpi/keyboard_attached_on_screen
|
||||
|
||||
The values are:
|
||||
|
||||
- 0 if the keyboard is not attached on-screen
|
||||
- 1 if the keyboard is attached on-screen
|
||||
|
||||
The property is read-only. If feature is not supported then the sysfs
|
||||
attribute is not created.
|
||||
|
||||
Multiple Commands, Module Parameters
|
||||
------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -72,17 +72,18 @@ Lightbar
|
|||
--------
|
||||
|
||||
The ``uniwill-laptop`` driver exposes the lightbar found on some models as a standard multicolor
|
||||
LED class device. The default name of this LED class device is ``uniwill:multicolor:status``.
|
||||
LED class device. The default name of this LED class device is ``uniwill:rgb:status``.
|
||||
|
||||
See Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details on how to control the various
|
||||
animation modes of the lightbar.
|
||||
The lightbar supports a separate rainbow animation mode that can be enabled by selecting the
|
||||
`uniwill-rainbow` LED trigger. To enable a breathing animation during suspend, see
|
||||
Documentation/ABI/testing/sysfs-driver-uniwill-laptop for details.
|
||||
|
||||
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 only a single color, or ``uniwill:rgb:kbd_backlight``
|
||||
when the keyboard backlight supports RGB colors. The maximum intensity for each color channel
|
||||
in RGB mode is 50.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ Newer Fam19h(model 0x00-0x1f, 0x30-0x3f, 0x90-0x9f, 0xa0-0xaf),
|
|||
Fam1Ah(model 0x00-0x1f) EPYC server line of processors from AMD support
|
||||
system management functionality via HSMP (Host System Management Port).
|
||||
|
||||
The Fam1Ah(model 0x80-0x8f, 0xe0-0xe3) client line of processors is
|
||||
supported as well. Those models share one mailbox and speak the Ryzen
|
||||
Master SMC message set instead of the server HSMP message set, so the
|
||||
message IDs accepted on them are the HSMP_CLIENT_* ones listed in
|
||||
arch/x86/include/uapi/asm/amd_hsmp.h. The character device and ioctl
|
||||
interface described below are the same.
|
||||
|
||||
The Host System Management Port (HSMP) is an interface to provide
|
||||
OS-level software with access to system management functions via a
|
||||
set of mailbox registers.
|
||||
|
|
@ -17,7 +24,8 @@ More details on the interface can be found in chapter
|
|||
Eg: https://docs.amd.com/v/u/en-US/55898_B1_pub_0_50
|
||||
|
||||
|
||||
HSMP interface is supported on EPYC line of server CPUs and MI300A (APU).
|
||||
HSMP interface is supported on EPYC line of server CPUs, MI300A (APU) and
|
||||
the Fam1Ah client models listed above.
|
||||
|
||||
|
||||
HSMP device
|
||||
|
|
@ -213,9 +221,23 @@ The following IOCTLs are defined:
|
|||
``sizeof(struct hsmp_metric_table)``. Later version metrics table
|
||||
layout is documented in the Public PPR.
|
||||
|
||||
The Family 1Ah client models carry their own telemetry table instead,
|
||||
described in `Family 1Ah client telemetry table`_ below.
|
||||
|
||||
The ioctl would return a non-zero on failure; you can read errno to see
|
||||
what happened. The transaction returns 0 on success.
|
||||
|
||||
Family 1Ah client telemetry table
|
||||
=================================
|
||||
On the Family 1Ah client models, ``HSMP_CLIENT_GET_METRICS_TABLE`` (05h)
|
||||
returns the Ryzen Master SMC telemetry table, table version 5 as
|
||||
reported by ``HSMP_CLIENT_GET_METRICS_TABLE_VER`` (04h). This is the
|
||||
Telemetry Table RM layout, defined as ``struct hsmp_telemetry_table_rm``
|
||||
in amd_hsmp.h. The driver copies the table out without interpreting it.
|
||||
|
||||
See the telemetry table chapter of the AMD Public PPR for the model in
|
||||
use for the units and encodings of its fields.
|
||||
|
||||
More details on the interface and message definitions can be found in chapter
|
||||
"7 Host System Management Port (HSMP)" of the respective family/model PPR
|
||||
eg: https://docs.amd.com/v/u/en-US/55898_B1_pub_0_50
|
||||
|
|
|
|||
70
Documentation/wmi/devices/acer-wmi-battery.rst
Normal file
70
Documentation/wmi/devices/acer-wmi-battery.rst
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
====================================================
|
||||
Acer WMI Battery interface driver (acer-wmi-battery)
|
||||
====================================================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Some Acer laptops support setting battery charge control limits and allow a
|
||||
user to calibrate their battery.
|
||||
|
||||
WMI interface description
|
||||
=========================
|
||||
|
||||
The WMI interface description can be decoded from the embedded binary MOF (bmof)
|
||||
data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
|
||||
|
||||
::
|
||||
|
||||
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Class used to control smart battery, Version 2.88"), guid("{79772EC5-04B1-4bfd-843C-61E7F77B6CC9}")]
|
||||
class BatteryControl {
|
||||
[key, read] string InstanceName;
|
||||
[read] boolean Active;
|
||||
|
||||
[WmiMethodId(19), Implemented, read, write, Description("Get battery Information Interface.")] void GetBattInfoInterface([in] uint32 uBatteryInfoIndex, [in] uint32 uBatteryNo, [out] uint32 uReturn);
|
||||
[WmiMethodId(20), Implemented, read, write, Description("Get Battery Health Control Status.")] void GetBatteryHealthControlStatus([in] uint8 uBatteryNo, [in] uint8 uFunctionQuery, [in] uint8 uReserved[2], [out] uint8 uFunctionList, [out] uint8 uReturn[2], [out] uint8 uFunctionStatus[5]);
|
||||
[WmiMethodId(21), Implemented, read, write, Description("Set Battery Health Control.")] void SetBatteryHealthControl([in] uint8 uBatteryNo, [in] uint8 uFunctionMask, [in] uint8 uFunctionStatus, [in] uint8 uReservedIn[5], [out] uint16 uReturn, [out] uint16 uReservedOut);
|
||||
[WmiMethodId(22), Implemented, read, write, Description("Get Battery Function Data.")] void GetBatteryFunctionData([in] uint8 uFunctionMask, [in] uint8 uReservedIn[3], [out] uint8 uReturnCode[2], [out] uint8 uBACStartTime[2], [out] uint8 uBACStopTime[2], [out] uint8 uBACStatus, [out] uint8 uReservedOut[9]);
|
||||
[WmiMethodId(23), Implemented, read, write, Description("Set Battery Function Data.")] void SetBatteryFunctionData([in] uint8 uFunctionMask, [in] uint8 uBACSwitch, [in] uint8 uReservedIn[6], [out] uint16 uReturnCode, [out] uint8 uReservedOut[2]);
|
||||
};
|
||||
|
||||
For all methods the ``uBatteryNo`` is always ``0x1``, there are no known Acer
|
||||
laptop models with two batteries.
|
||||
|
||||
WMI method GetBatteryInfoInterface()
|
||||
------------------------------------
|
||||
|
||||
Returns additional battery information, the data seems to be based on the
|
||||
"Smart Battery Data Specification". The only known value for
|
||||
``uBatteryInfoIndex`` is ``0x8``, the battery temperature.
|
||||
|
||||
WMI method GetBatteryHealthControlStatus()
|
||||
------------------------------------------
|
||||
|
||||
This interface does not exist on all Acer laptops, it returns whether battery
|
||||
charge limiting or calibration mode is enabled.
|
||||
|
||||
When calling this method, ``uFunctionList`` is a bitmask with known values:
|
||||
|
||||
+--------------------+------------------------------------+
|
||||
| Value | Description |
|
||||
+====================+====================================+
|
||||
| 0x01 | Health mode supported |
|
||||
+--------------------+------------------------------------+
|
||||
| 0x02 | Calibration mode supported |
|
||||
+--------------------+------------------------------------+
|
||||
|
||||
``uFunctionStatus`` is a 5 byte array known elements are ``0`` for health mode
|
||||
and ``1`` for calibration mode. A non-zero value means the mode is enabled.
|
||||
|
||||
WMI method SetBatteryHealthControl()
|
||||
------------------------------------
|
||||
|
||||
This interface does not exist on all Acer laptops, it enables battery
|
||||
charge limiting or battery calibration.
|
||||
|
||||
The argument ``uFunctionMask`` is known to accept either ``0x01`` for setting
|
||||
health mode or ``0x02`` for calibration mode; other values are unknown.
|
||||
``uFunctionStatus`` is an uint8 value, a non-zero value enables the given mode.
|
||||
|
|
@ -67,7 +67,7 @@ WMI Methods (MICommonInterface)
|
|||
|
||||
The ``MICommonInterface`` class (GUID: ``{b60bfb48-3e5b-49e4-a0e9-8cffe1b3434b}``)
|
||||
is the primary control interface. It uses a 32-byte buffer for both input
|
||||
(``InData``) and output (``OutData``).
|
||||
(``InData``) and output (``OutData`` + ``Reserved``).
|
||||
|
||||
Method Structure
|
||||
----------------
|
||||
|
|
@ -77,53 +77,78 @@ The data packet follows a standardized format:
|
|||
+----------+------------------------------------------------------------------+
|
||||
| Byte | Description |
|
||||
+==========+==================================================================+
|
||||
| 1 | Method Type: Get (0xFA / 250) or Set (0xFB / 251) |
|
||||
| 1 and 2 | Method Type or Return Code |
|
||||
+----------+------------------------------------------------------------------+
|
||||
| 3 | Command ID (Method Name) |
|
||||
| 3 and 4 | Command ID (Method Name) |
|
||||
+----------+------------------------------------------------------------------+
|
||||
| 4 - 31 | Arguments (for Set) or Return Data (for Get) |
|
||||
| 5 - 32 | Arguments (for Set) or Return Data (for Get) |
|
||||
+----------+------------------------------------------------------------------+
|
||||
|
||||
Method Types
|
||||
------------
|
||||
|
||||
The following Method types are understood by the underlying firmware:
|
||||
|
||||
+--------+---------+
|
||||
| Type | Meaning |
|
||||
+========+=========+
|
||||
| 0xFA00 | Read |
|
||||
+--------+---------+
|
||||
| 0xFB00 | Write |
|
||||
+--------+---------+
|
||||
|
||||
Return Codes
|
||||
------------
|
||||
|
||||
The following Return Codes are know to be returned in response to a WMI method invocation:
|
||||
|
||||
+--------+--------------------+
|
||||
| Code | Meaning |
|
||||
+========+====================+
|
||||
| 0x8000 | Success |
|
||||
+--------+--------------------+
|
||||
| 0xE000 | Invalid Command ID |
|
||||
+--------+--------------------+
|
||||
|
||||
Command IDs
|
||||
-----------
|
||||
|
||||
The following Command IDs are used in the third byte of the buffer:
|
||||
The following Command IDs know to be used on some models:
|
||||
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| ID | Name | Values / Description |
|
||||
+==========+=======================+==========================================+
|
||||
| 8 | SystemPerMode | 0: Balance, 1: Performance, 2: Quiet, |
|
||||
| 0x0800 | SystemPerMode | 0: Balance, 1: Performance, 2: Quiet, |
|
||||
| | | 3: Full-speed |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 9 | GPUMode | 0: Hybrid, 1: Discrete, 2: UMA |
|
||||
| 0x0900 | GPUMode | 0: Hybrid, 1: Discrete, 2: UMA |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 10 | KeyboardType | 0: White, 1: Single RGB, 2: Zone RGB |
|
||||
| 0x0A00 | KeyboardType | 0: White, 1: Single RGB, 2: Zone RGB |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 11 | FnLock | 0: Off, 1: On |
|
||||
| 0x0B00 | FnLock | 0: Off, 1: On |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 12 | TPLock | 0: Unlock, 1: Lock (Touchpad) |
|
||||
| 0x0C00 | TPLock | 0: Unlock, 1: Lock (Touchpad) |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 13 | CPUGPUSYSFanSpeed | Returns 12 bytes of fan data: |
|
||||
| 0x0D00 | CPUGPUSYSFanSpeed | Returns 12 bytes of fan data: |
|
||||
| | | Bytes 4-5: CPU Fan RPM (Little Endian) |
|
||||
| | | Bytes 6-7: GPU Fan RPM (Little Endian) |
|
||||
| | | Bytes 10-11: SYS Fan RPM (Little Endian) |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 16 | RGBKeyboardMode | 0: Off, 1: Auto Cyclic, 2: Fixed, |
|
||||
| 0x1000 | RGBKeyboardMode | 0: Off, 1: Auto Cyclic, 2: Fixed, |
|
||||
| | | 3: Custom |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 17 | RGBKeyboardColor | Bytes 4, 5, 6: Red, Green, Blue values |
|
||||
| 0x1100 | RGBKeyboardColor | Bytes 4, 5, 6: Red, Green, Blue values |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 18 | RGBKeyboardBrightness | 0-10: Brightness Levels, 128: Auto |
|
||||
| 0x1200 | RGBKeyboardBrightness | 0-10: Brightness Levels, 128: Auto |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 19 | SystemAcType | 1: Type-C, 2: Circular Hole (DC) |
|
||||
| 0x1300 | SystemAcType | 1: Type-C, 2: Circular Hole (DC) |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 20 | MaxFanSpeedSwitch | Byte 4: Fan Type (0: CPU/GPU, 1: SYS) |
|
||||
| 0x1400 | MaxFanSpeedSwitch | Byte 4: Fan Type (0: CPU/GPU, 1: SYS) |
|
||||
| | | Byte 5: State (0: Off, 1: On) |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 21 | MaxFanSpeed | Sets manual fan speed duty cycle |
|
||||
| 0x1500 | MaxFanSpeed | Sets manual fan speed duty cycle |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
| 22 | CPUThermometer | Returns CPU Temperature |
|
||||
| 0x1600 | CPUThermometer | Returns CPU Temperature |
|
||||
+----------+-----------------------+------------------------------------------+
|
||||
|
||||
WMI Events (HID_EVENT20)
|
||||
|
|
@ -189,7 +214,8 @@ Performance Modes
|
|||
Changing the performance mode via Command ID 0x08 (SystemPerMode) affects the
|
||||
power limits (PL1/PL2) and fan curves managed by the Embedded Controller (EC).
|
||||
Note that the "Full-speed" and "Performance" mode (1, 3) is typically only
|
||||
available when the system is connected to a DC power source (not USB-C/PD).
|
||||
available when the system is connected to a DC power source (not USB-C/PD on
|
||||
model with a dedicated DC connector).
|
||||
|
||||
In the driver implementation, switch to performance/full-speed mode without
|
||||
DC power connected will throw the EOPNOTSUPP error.
|
||||
|
|
|
|||
|
|
@ -13276,9 +13276,8 @@ S: Maintained
|
|||
F: drivers/platform/x86/intel/atomisp2/led.c
|
||||
|
||||
INTEL BIOS SAR INT1092 DRIVER
|
||||
M: Shravan Sudhakar <s.shravan@intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
S: Orphan
|
||||
F: drivers/platform/x86/intel/int1092/
|
||||
|
||||
INTEL BROXTON PMC DRIVER
|
||||
|
|
@ -13800,6 +13799,7 @@ M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
|||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/debugfs-tpmi
|
||||
F: drivers/platform/x86/intel/tpmi_common.[ch]
|
||||
F: drivers/platform/x86/intel/vsec_tpmi.c
|
||||
F: include/linux/intel_tpmi.h
|
||||
|
||||
|
|
@ -15122,6 +15122,7 @@ F: drivers/usb/misc/legousbtower.c
|
|||
LENOVO drivers
|
||||
M: Mark Pearson <mpearson-lenovo@squebb.ca>
|
||||
M: Derek J. Clark <derekjohn.clark@gmail.com>
|
||||
M: Rong Zhang <i@rong.moe>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
|
||||
|
|
@ -28517,6 +28518,7 @@ F: tools/testing/selftests/turbostat/
|
|||
|
||||
TUXEDO DRIVERS
|
||||
M: Werner Sembach <wse@tuxedocomputers.com>
|
||||
M: Aaron Erhardt <aer@tuxedocomputers.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/platform/x86/tuxedo/
|
||||
|
|
@ -28755,6 +28757,7 @@ UNIWILL LAPTOP DRIVER
|
|||
M: Armin Wolf <W_Armin@gmx.de>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/removed/sysfs-driver-uniwill-laptop
|
||||
F: Documentation/ABI/testing/sysfs-driver-uniwill-laptop
|
||||
F: Documentation/admin-guide/laptops/uniwill-laptop.rst
|
||||
F: Documentation/wmi/devices/uniwill-laptop.rst
|
||||
|
|
@ -30127,7 +30130,7 @@ L: platform-driver-x86@vger.kernel.org
|
|||
S: Maintained
|
||||
F: Documentation/ABI/stable/sysfs-platform-wmi-bmof
|
||||
F: Documentation/wmi/devices/wmi-bmof.rst
|
||||
F: drivers/platform/x86/wmi-bmof.c
|
||||
F: drivers/platform/wmi/bmof.c
|
||||
|
||||
WOLFSON MICROELECTRONICS DRIVERS
|
||||
L: patches@opensource.cirrus.com
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
|
||||
#define HSMP_MAX_MSG_LEN 8
|
||||
|
||||
/*
|
||||
* HSMP client platforms (Ryzen Master SMC) accept at most four input
|
||||
* argument words and four output/response words per message.
|
||||
*/
|
||||
#define HSMP_CLIENT_MAX_MSG_LEN 4
|
||||
|
||||
/*
|
||||
* HSMP Messages supported
|
||||
*/
|
||||
|
|
@ -71,6 +77,11 @@ enum hsmp_message_ids {
|
|||
HSMP_MSG_ID_MAX,
|
||||
};
|
||||
|
||||
/*
|
||||
* The driver validates num_args and response_sz before sending a message.
|
||||
* HSMP client platforms are bound to HSMP_CLIENT_MAX_MSG_LEN in each
|
||||
* direction; server platforms are bound per-message by hsmp_msg_desc_table[].
|
||||
*/
|
||||
struct hsmp_message {
|
||||
__u32 msg_id; /* Message ID */
|
||||
__u16 num_args; /* Number of input argument words in message */
|
||||
|
|
@ -95,6 +106,15 @@ enum hsmp_proto_versions {
|
|||
HSMP_PROTO_VER7
|
||||
};
|
||||
|
||||
/*
|
||||
* Ryzen Master SMC interface version, reported by
|
||||
* HSMP_CLIENT_GET_INTERFACE_VER. Separate numbering space from enum
|
||||
* hsmp_proto_versions, which applies to the server set only.
|
||||
*/
|
||||
enum ryzen_master_proto_versions {
|
||||
RYZEN_MASTER_PROTO_VER1 = 1,
|
||||
};
|
||||
|
||||
struct hsmp_msg_desc {
|
||||
int num_args;
|
||||
int response_sz;
|
||||
|
|
@ -664,4 +684,404 @@ struct hsmp_telemetry_data {
|
|||
#define HSMP_IOCTL_GET_TELEMETRY_DATA \
|
||||
_IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data)
|
||||
|
||||
/**
|
||||
* enum hsmp_client_message_ids - Ryzen Master SMC (RMSMC) message IDs
|
||||
* @HSMP_CLIENT_TEST: 01h. Test message. input: args[0] = xx. output:
|
||||
* args[0] = xx + 1.
|
||||
* @HSMP_CLIENT_GET_SMU_VER: 02h. Get MP1 firmware version. output:
|
||||
* args[0] = MP1 firmware version.
|
||||
* @HSMP_CLIENT_GET_INTERFACE_VER: 03h. Get interface version. output:
|
||||
* args[0] = interface version.
|
||||
* @HSMP_CLIENT_GET_METRICS_TABLE_VER: 04h. Get metrics table version.
|
||||
* output: args[0] = metrics table version.
|
||||
* @HSMP_CLIENT_GET_METRICS_TABLE: 05h. Get metrics table. No arguments.
|
||||
* Success means firmware has written the metrics table to the DRAM
|
||||
* address reported by @HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR.
|
||||
* @HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR: 06h. Get metrics table DRAM
|
||||
* address. output: args[0] = address[31:0], args[1] = address[63:32],
|
||||
* args[2] = table size in bytes.
|
||||
* @HSMP_CLIENT_SET_CORE_PSM_MARGIN: 07h. Set core voltage margin. input:
|
||||
* args[0] = ccx number[31:28] + core number[27:20] + reserved[19:16] +
|
||||
* margin in mV[15:0].
|
||||
* @HSMP_CLIENT_SET_ALL_CORE_PSM_MARGIN: 08h. Set voltage margin for all
|
||||
* cores. input: args[0] = margin in mV[15:0].
|
||||
* @HSMP_CLIENT_SET_FAST_PPT_LIMIT: 09h. Set APU fast PPT limit. input:
|
||||
* args[0] = limit in mW.
|
||||
* @HSMP_CLIENT_SET_VRM_VDD_CURRENT_LIMIT: 0Ah. Set VDDCR_VDD TDC. input:
|
||||
* args[0] = limit in mA.
|
||||
* @HSMP_CLIENT_SET_VRM_VDD_MAX_CURRENT_LIMIT: 0Bh. Set VDDCR_VDD EDC.
|
||||
* input: args[0] = limit in mA.
|
||||
* @HSMP_CLIENT_SET_TJ_MAX: 0Ch. Set maximum junction temperature. input:
|
||||
* args[0] = temperature in degrees C.
|
||||
* @HSMP_CLIENT_SET_FIT_LIMIT_SCALAR: 0Dh. Set failures-in-time limit
|
||||
* scalar. input: args[0] = scalar (0 to 100).
|
||||
* @HSMP_CLIENT_ENABLE_OVERCLOCKING: 0Eh. Enable overclocking. No
|
||||
* arguments.
|
||||
* @HSMP_CLIENT_DISABLE_OVERCLOCKING: 0Fh. Disable overclocking. No
|
||||
* arguments.
|
||||
* @HSMP_CLIENT_SET_OVERCLOCK_FREQ_ALL_CORES: 10h. Set all-core overclock
|
||||
* frequency. input: args[0] = frequency in MHz[15:0].
|
||||
* @HSMP_CLIENT_SET_OVERCLOCK_FREQ_PER_CORE: 11h. Set per-core overclock
|
||||
* frequency. input: args[0] = ccd number[31:28] + ccx number[27:24] +
|
||||
* core number[23:20] + frequency in MHz[19:0].
|
||||
* @HSMP_CLIENT_SET_OVERCLOCK_VID: 12h. Set overclock VID. input:
|
||||
* args[0] = reserved[31:17] + vid[16:8] + pstate[7:0].
|
||||
* @HSMP_CLIENT_SPARE_0X13: 13h. Reserved.
|
||||
* @HSMP_CLIENT_GET_CORE_PERF_ORDER: 14h. Get core performance order.
|
||||
* input: args[0] = core performance rank index[31:0] (0 to Enabled
|
||||
* cores-1). output: args[0] = voltage in mV[31:21] + frequency in
|
||||
* MHz[20:9] + fll[8:6] + core number[5:2] + ccx number[1:0].
|
||||
* @HSMP_CLIENT_SET_SUSTAINED_POWER_LIMIT: 15h. Set SOC sustained power
|
||||
* limit. input: args[0] = limit in mW.
|
||||
* @HSMP_CLIENT_SET_SLOW_PPT_LIMIT: 16h. Set APU slow PPT limit. input:
|
||||
* args[0] = limit in mW.
|
||||
* @HSMP_CLIENT_SET_VRM_GFX_MAX_CURRENT_LIMIT: 17h. Set VDDCR_GFX EDC.
|
||||
* input: args[0] = limit in mA.
|
||||
* @HSMP_CLIENT_SET_VRM_SOC_CURRENT_LIMIT: 18h. Set VDDCR_SOC TDC. input:
|
||||
* args[0] = limit in mA.
|
||||
* @HSMP_CLIENT_SET_FAST_SPM_LIMIT: 19h. Set fast SPM limit. input:
|
||||
* args[0] = limit in mW.
|
||||
* @HSMP_CLIENT_SET_SLOW_SPM_LIMIT: 1Ah. Set slow SPM limit. input:
|
||||
* args[0] = limit in mW.
|
||||
* @HSMP_CLIENT_GET_CORE_PSM_MARGIN: 1Bh. Get core voltage margin. input:
|
||||
* args[0] = ccx number[31:28] + core number[27:20] + reserved[19:0].
|
||||
* output: args[0] = ccx number[31:28] + core number[27:20] +
|
||||
* reserved[19:16] + margin in mV[15:0].
|
||||
* @HSMP_CLIENT_GET_GFX_PSM_MARGIN: 1Ch. Get graphics voltage margin.
|
||||
* output: args[0] = margin in mV[15:0].
|
||||
* @HSMP_CLIENT_SPARE_0X1D: 1Dh. Reserved.
|
||||
* @HSMP_CLIENT_SPARE_0X1E: 1Eh. Reserved.
|
||||
* @HSMP_CLIENT_SPARE_0X1F: 1Fh. Reserved.
|
||||
* @HSMP_CLIENT_SPARE_0X20: 20h. Reserved.
|
||||
* @HSMP_CLIENT_SET_GFXCLK_OVERDRIVE_BY_FREQ_VID: 21h. Set GfxClk
|
||||
* overdrive by frequency/VID. input: args[0] = reserved[31:25] +
|
||||
* vid[24:16] + frequency in MHz[15:0].
|
||||
* @HSMP_CLIENT_DISABLE_GFXCLK_OVERDRIVE: 22h. Disable GfxClk overdrive.
|
||||
* No arguments.
|
||||
* @HSMP_CLIENT_SET_GFX_PSM_MARGIN: 23h. Set graphics voltage margin.
|
||||
* input: args[0] = margin in mV[15:0].
|
||||
* @HSMP_CLIENT_SET_CCLK_FMAX_OFFSET: 24h. Set CCLK Fmax offset. input:
|
||||
* args[0] = maximum frequency in MHz[15:0].
|
||||
* @HSMP_CLIENT_SET_CORE_POWER_LIMIT_OFFSET: 25h. Set core power limit
|
||||
* offset. input: args[0] = limit in mW.
|
||||
* @HSMP_CLIENT_ADD_EXTRA_PSM_GUARDBAND: 26h. Add extra core PSM
|
||||
* guardband. input: args[0] = PsmGuardband[2][31:24] +
|
||||
* PsmGuardband[1][23:16] + PsmGuardband[0][15:8] + TriggerUpdate[7] +
|
||||
* AvfsType[6:4] + frequency index[3:0].
|
||||
* @HSMP_CLIENT_ADD_EXTRA_PSM_GUARDBAND_GFX: 27h. Add extra graphics PSM
|
||||
* guardband. input: args[0] = PsmGuardband[2][31:24] +
|
||||
* PsmGuardband[1][23:16] + PsmGuardband[0][15:8] + TriggerUpdate[7] +
|
||||
* frequency index[6:0].
|
||||
* @HSMP_CLIENT_SET_GFXCLK_FMAX: 28h. Set GfxClk Fmax. input: args[0] =
|
||||
* maximum frequency in MHz[15:0].
|
||||
* @HSMP_CLIENT_MSG_ID_MAX: Number of message IDs, not a valid ID itself.
|
||||
*
|
||||
* Message IDs accepted on the Family 1Ah client platforms, Models 80h-8Fh
|
||||
* and E0h-E3h. These parts drive one mailbox and speak the Ryzen Master
|
||||
* SMC message set instead of the server HSMP message set enumerated in
|
||||
* &enum hsmp_message_ids. Not all platforms support all messages; consult
|
||||
* the supported list of messages in the HSMP chapter of the respective
|
||||
* family/model PPR. Unsupported messages return -ENOMSG.
|
||||
*/
|
||||
enum hsmp_client_message_ids {
|
||||
HSMP_CLIENT_TEST = 1,
|
||||
HSMP_CLIENT_GET_SMU_VER,
|
||||
HSMP_CLIENT_GET_INTERFACE_VER,
|
||||
HSMP_CLIENT_GET_METRICS_TABLE_VER,
|
||||
HSMP_CLIENT_GET_METRICS_TABLE,
|
||||
HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR,
|
||||
HSMP_CLIENT_SET_CORE_PSM_MARGIN,
|
||||
HSMP_CLIENT_SET_ALL_CORE_PSM_MARGIN,
|
||||
HSMP_CLIENT_SET_FAST_PPT_LIMIT,
|
||||
HSMP_CLIENT_SET_VRM_VDD_CURRENT_LIMIT,
|
||||
HSMP_CLIENT_SET_VRM_VDD_MAX_CURRENT_LIMIT,
|
||||
HSMP_CLIENT_SET_TJ_MAX,
|
||||
HSMP_CLIENT_SET_FIT_LIMIT_SCALAR,
|
||||
HSMP_CLIENT_ENABLE_OVERCLOCKING,
|
||||
HSMP_CLIENT_DISABLE_OVERCLOCKING,
|
||||
HSMP_CLIENT_SET_OVERCLOCK_FREQ_ALL_CORES,
|
||||
HSMP_CLIENT_SET_OVERCLOCK_FREQ_PER_CORE,
|
||||
HSMP_CLIENT_SET_OVERCLOCK_VID,
|
||||
HSMP_CLIENT_SPARE_0X13,
|
||||
HSMP_CLIENT_GET_CORE_PERF_ORDER,
|
||||
HSMP_CLIENT_SET_SUSTAINED_POWER_LIMIT,
|
||||
HSMP_CLIENT_SET_SLOW_PPT_LIMIT,
|
||||
HSMP_CLIENT_SET_VRM_GFX_MAX_CURRENT_LIMIT,
|
||||
HSMP_CLIENT_SET_VRM_SOC_CURRENT_LIMIT,
|
||||
HSMP_CLIENT_SET_FAST_SPM_LIMIT,
|
||||
HSMP_CLIENT_SET_SLOW_SPM_LIMIT,
|
||||
HSMP_CLIENT_GET_CORE_PSM_MARGIN,
|
||||
HSMP_CLIENT_GET_GFX_PSM_MARGIN,
|
||||
HSMP_CLIENT_SPARE_0X1D,
|
||||
HSMP_CLIENT_SPARE_0X1E,
|
||||
HSMP_CLIENT_SPARE_0X1F,
|
||||
HSMP_CLIENT_SPARE_0X20,
|
||||
HSMP_CLIENT_SET_GFXCLK_OVERDRIVE_BY_FREQ_VID,
|
||||
HSMP_CLIENT_DISABLE_GFXCLK_OVERDRIVE,
|
||||
HSMP_CLIENT_SET_GFX_PSM_MARGIN,
|
||||
HSMP_CLIENT_SET_CCLK_FMAX_OFFSET,
|
||||
HSMP_CLIENT_SET_CORE_POWER_LIMIT_OFFSET,
|
||||
HSMP_CLIENT_ADD_EXTRA_PSM_GUARDBAND,
|
||||
HSMP_CLIENT_ADD_EXTRA_PSM_GUARDBAND_GFX,
|
||||
HSMP_CLIENT_SET_GFXCLK_FMAX,
|
||||
HSMP_CLIENT_MSG_ID_MAX,
|
||||
};
|
||||
|
||||
#define HSMP_TELEMETRY_RM_MAX_CCX 4
|
||||
#define HSMP_TELEMETRY_RM_CORES_PER_CCX 12
|
||||
#define HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE 8
|
||||
#define HSMP_TELEMETRY_RM_NPU_BUSY_DOMAINS 3
|
||||
#define HSMP_TELEMETRY_RM_OC_DOMAINS 5
|
||||
#define HSMP_TELEMETRY_RM_OC_SUBDOMAINS 5
|
||||
#define HSMP_TELEMETRY_RM_OC_GUARDBANDS 3
|
||||
|
||||
/* Firmware packs this table to a 4-byte boundary */
|
||||
#pragma pack(push, 4)
|
||||
|
||||
/*
|
||||
* struct hsmp_telemetry_table_rm_iod - I/O die counters for RM telemetry.
|
||||
*
|
||||
* Accumulator (*_acc) fields are monotonic counters; consumers derive
|
||||
* deltas across two snapshots and normalise by accumulation_counter.
|
||||
*/
|
||||
struct hsmp_telemetry_table_rm_iod {
|
||||
__u32 accumulation_counter;
|
||||
|
||||
/* SET VOLTAGES */
|
||||
__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 - SPL */
|
||||
__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[HSMP_TELEMETRY_RM_NPU_BUSY_DOMAINS];
|
||||
|
||||
/* STT (Skin Temperature Tracking) */
|
||||
__u32 stt_min_limit;
|
||||
__u64 stt_apu_hot_spot_temp_acc;
|
||||
__u64 stt_hs2_hot_spot_temp_acc;
|
||||
__u32 stt_apu_temp_limit;
|
||||
__u64 stt_apu_skin_temp_acc;
|
||||
|
||||
/* RESIDENCIES - per-CCX CPU off */
|
||||
__u64 cpu_off_residency[HSMP_TELEMETRY_RM_MAX_CCX];
|
||||
|
||||
/* DF P-STATES */
|
||||
__u32 fclk_freq_table[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
|
||||
__u32 uclk_freq_table[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
|
||||
__u32 ddr_rate_table[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
|
||||
__u8 df_pstate_source[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
|
||||
|
||||
/* SYSTEM */
|
||||
__u8 gfx_disabled;
|
||||
__u8 spare2[3];
|
||||
__u32 gfxclk_fmax;
|
||||
__u8 cclk_core_fuse_enable[HSMP_TELEMETRY_RM_MAX_CCX][HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
__u8 cclk_core_enabled[HSMP_TELEMETRY_RM_MAX_CCX][HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
__u32 cclk_fmax[HSMP_TELEMETRY_RM_MAX_CCX][HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
|
||||
/* 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];
|
||||
__s8 psm_guardband[HSMP_TELEMETRY_RM_OC_DOMAINS]
|
||||
[HSMP_TELEMETRY_RM_OC_SUBDOMAINS]
|
||||
[HSMP_TELEMETRY_RM_OC_GUARDBANDS];
|
||||
__s32 core_power_limit_offset;
|
||||
__u32 max_freq_offset[HSMP_TELEMETRY_RM_OC_DOMAINS];
|
||||
|
||||
__u64 npu_temp_acc;
|
||||
__u64 df_pstate_residency_acc[HSMP_TELEMETRY_RM_FREQ_TABLE_SIZE];
|
||||
__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];
|
||||
};
|
||||
|
||||
/*
|
||||
* struct hsmp_telemetry_table_rm_ccx - Per-CCX core counters for RM telemetry.
|
||||
*
|
||||
* Arrays are sized for the maximum cores per CCX; use the
|
||||
* cclk_core_enabled[] mask in struct hsmp_telemetry_table_rm_iod to tell
|
||||
* which entries are valid.
|
||||
*/
|
||||
struct hsmp_telemetry_table_rm_ccx {
|
||||
__u64 core_c0[HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
__u64 core_cc6[HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
__u64 core_freq[HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
__u64 core_freq_eff[HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
__u64 core_temp[HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
__u64 core_power[HSMP_TELEMETRY_RM_CORES_PER_CCX];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hsmp_telemetry_table_rm - Top-level RM telemetry table.
|
||||
* @iod: I/O die counters and per-rail/per-throttler/SYSTEM telemetry.
|
||||
* @ccx: Per-CCX core counters, one entry per CCX.
|
||||
*
|
||||
* Layout of the table returned by HSMP_CLIENT_GET_METRICS_TABLE on the
|
||||
* Family 1Ah client (Models 80h-8Fh and E0h-E3h), reported as table
|
||||
* version 5 by HSMP_CLIENT_GET_METRICS_TABLE_VER. Units are
|
||||
* firmware-defined; see the AMD HSMP/Ryzen Master SMC spec. Spare
|
||||
* fields are firmware padding and must be preserved.
|
||||
*/
|
||||
struct hsmp_telemetry_table_rm {
|
||||
struct hsmp_telemetry_table_rm_iod iod;
|
||||
struct hsmp_telemetry_table_rm_ccx ccx[HSMP_TELEMETRY_RM_MAX_CCX];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif /*_ASM_X86_AMD_HSMP_H_*/
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ config ACPI_VIDEO
|
|||
tristate "Video"
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
depends on INPUT
|
||||
depends on ACPI_WMI || !X86
|
||||
depends on ACPI_WMI
|
||||
select THERMAL
|
||||
help
|
||||
This driver implements the ACPI Extensions For Display Adapters
|
||||
|
|
|
|||
|
|
@ -77,8 +77,6 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv)
|
|||
return AE_OK;
|
||||
}
|
||||
|
||||
/* This depends on ACPI_WMI which is X86 only */
|
||||
#ifdef CONFIG_X86
|
||||
static bool nvidia_wmi_ec_supported(void)
|
||||
{
|
||||
struct wmi_brightness_args args = {
|
||||
|
|
@ -100,12 +98,6 @@ static bool nvidia_wmi_ec_supported(void)
|
|||
*/
|
||||
return args.ret == WMI_BRIGHTNESS_SOURCE_EC;
|
||||
}
|
||||
#else
|
||||
static bool nvidia_wmi_ec_supported(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Force to use vendor driver when the ACPI device is known to be
|
||||
* buggy */
|
||||
|
|
|
|||
|
|
@ -30,9 +30,7 @@ config DRM_AMDGPU
|
|||
# ACPI_VIDEO's dependencies must also be selected.
|
||||
select INPUT if ACPI
|
||||
select ACPI_VIDEO if ACPI
|
||||
# On x86 ACPI_VIDEO also needs ACPI_WMI
|
||||
select X86_PLATFORM_DEVICES if ACPI && X86
|
||||
select ACPI_WMI if ACPI && X86
|
||||
select ACPI_WMI if ACPI
|
||||
help
|
||||
Choose this option if you have a recent AMD Radeon graphics card.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ config DRM_GMA500
|
|||
select ACPI_VIDEO if ACPI
|
||||
select BACKLIGHT_CLASS_DEVICE if ACPI
|
||||
select INPUT if ACPI
|
||||
select X86_PLATFORM_DEVICES if ACPI
|
||||
select ACPI_WMI if ACPI
|
||||
help
|
||||
Say yes for an experimental 2D KMS framebuffer driver for the
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ config DRM_I915
|
|||
# but for select to work, need to select ACPI_VIDEO's dependencies, ick
|
||||
select BACKLIGHT_CLASS_DEVICE if ACPI
|
||||
select INPUT if ACPI
|
||||
select X86_PLATFORM_DEVICES if ACPI
|
||||
select ACPI_WMI if ACPI
|
||||
select ACPI_VIDEO if ACPI
|
||||
select SYNC_FILE
|
||||
|
|
|
|||
|
|
@ -25,9 +25,7 @@ config DRM_RADEON
|
|||
# ACPI_VIDEO's dependencies must also be selected.
|
||||
select INPUT if ACPI
|
||||
select ACPI_VIDEO if ACPI
|
||||
# On x86 ACPI_VIDEO also needs ACPI_WMI
|
||||
select X86_PLATFORM_DEVICES if ACPI && X86
|
||||
select ACPI_WMI if ACPI && X86
|
||||
select ACPI_WMI if ACPI
|
||||
help
|
||||
Choose this option if you have an ATI Radeon graphics card. There
|
||||
are both PCI and AGP versions. You don't need to choose this to
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ config DRM_XE
|
|||
depends on DRM && PCI
|
||||
depends on KUNIT || !KUNIT
|
||||
depends on INTEL_VSEC || !INTEL_VSEC
|
||||
depends on X86_PLATFORM_DEVICES || !(X86 && ACPI)
|
||||
depends on PAGE_SIZE_4KB || COMPILE_TEST || BROKEN
|
||||
select INTERVAL_TREE
|
||||
# we need shmfs for the swappable backing store, and in particular
|
||||
|
|
@ -29,8 +28,8 @@ config DRM_XE
|
|||
# but for select to work, need to select ACPI_VIDEO's dependencies, ick
|
||||
select BACKLIGHT_CLASS_DEVICE if ACPI
|
||||
select INPUT if ACPI
|
||||
select ACPI_VIDEO if X86 && ACPI
|
||||
select ACPI_WMI if X86 && ACPI
|
||||
select ACPI_VIDEO if ACPI
|
||||
select ACPI_WMI if ACPI
|
||||
select SYNC_FILE
|
||||
select CRC32
|
||||
select SND_HDA_I915 if SND_HDA_CORE
|
||||
|
|
|
|||
|
|
@ -33,6 +33,22 @@ config EC_ACER_ASPIRE1
|
|||
laptop where this information is not properly exposed via the
|
||||
standard ACPI devices.
|
||||
|
||||
config EC_ASUS_GLYMUR
|
||||
tristate "ASUS Zenbook A16 (Qualcomm) Embedded Controller driver"
|
||||
depends on ARCH_QCOM || COMPILE_TEST
|
||||
depends on I2C
|
||||
depends on HWMON
|
||||
depends on INPUT
|
||||
depends on PM
|
||||
depends on THERMAL || THERMAL=n
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
help
|
||||
Say M or Y here to enable the EC driver for the Qualcomm Snapdragon-
|
||||
based ASUS Zenbook A16 (UX3607OA) and similar machines. It provides
|
||||
temperature sensors readout, fan monitoring, keyboard and EC event
|
||||
reporting.
|
||||
|
||||
config EC_HUAWEI_GAOKUN
|
||||
tristate "Huawei Matebook E Go Embedded Controller driver"
|
||||
depends on ARCH_QCOM || COMPILE_TEST
|
||||
|
|
@ -90,6 +106,30 @@ config EC_LENOVO_THINKPAD_T14S
|
|||
|
||||
Say M or Y here to include this support.
|
||||
|
||||
config EC_LENOVO_YOGA_SLIM7X_GEN11
|
||||
tristate "Lenovo Yoga Slim 7x Gen 11 Embedded Controller driver"
|
||||
depends on ARCH_QCOM || COMPILE_TEST
|
||||
depends on I2C
|
||||
depends on INPUT
|
||||
depends on HWMON
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
help
|
||||
Say Y here to enable the EC driver for the (Snapdragon X2 Elite,
|
||||
glymur-based) Lenovo Yoga Slim 7x Gen 11 laptop. The EC provides
|
||||
thermistors and CPU fan speed as hwmon channels, the keyboard
|
||||
backlight as an LED, Fn-lock as a sysfs attribute, and the mic-mute
|
||||
and airplane-mode keys.
|
||||
|
||||
This is the Gen 11 (glymur) machine specifically. Its EC is a Compal
|
||||
design at i2c address 0x70 and speaks a different protocol from the
|
||||
Qualcomm reference EC that the reference designs place at 0x76, so
|
||||
EC_QCOM_HAMOA does not drive it - and conversely this driver does not
|
||||
cover the X1E-based Yoga Slim 7x Gen 9.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called lenovo-yoga-slim7x-gen11-ec.
|
||||
|
||||
config EC_QCOM_HAMOA
|
||||
tristate "Embedded Controller driver for Qualcomm Hamoa/Glymur reference devices"
|
||||
depends on ARCH_QCOM || COMPILE_TEST
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
#
|
||||
|
||||
obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o
|
||||
obj-$(CONFIG_EC_ASUS_GLYMUR) += asus-glymur-ec.o
|
||||
obj-$(CONFIG_EC_HUAWEI_GAOKUN) += huawei-gaokun-ec.o
|
||||
obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
|
||||
obj-$(CONFIG_EC_LENOVO_THINKPAD_T14S) += lenovo-thinkpad-t14s.o
|
||||
obj-$(CONFIG_EC_LENOVO_YOGA_SLIM7X_GEN11) += lenovo-yoga-slim7x-gen11-ec.o
|
||||
obj-$(CONFIG_EC_QCOM_HAMOA) += qcom-hamoa-ec.o
|
||||
|
|
|
|||
|
|
@ -514,7 +514,7 @@ static int aspire_ec_probe(struct i2c_client *client)
|
|||
aspire_ec_irq_handler, IRQF_ONESHOT,
|
||||
dev_name(dev), ec);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to request irq\n");
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
617
drivers/platform/arm64/asus-glymur-ec.c
Normal file
617
drivers/platform/arm64/asus-glymur-ec.c
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* The EC firmware on the ASUS Zenbook A16 looks to be derived from the
|
||||
* 'Qualcomm Hamoa EC' reference base with many major modifications. Some of
|
||||
* the original interface is reused, albeit many of its core invariants (such
|
||||
* as the capabilities bitmap) are absent.
|
||||
*
|
||||
* It presents itself as two I2C devices:
|
||||
* [0x5b] is the custom ASUS interface
|
||||
* [0x76] is the Hamoa EC-derived interface
|
||||
*/
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/lockdep.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
/* Qualcomm-derived registers (main device @ 0x76) */
|
||||
#define ASUS_QCOM_EC_EVENT_CMD 0x05
|
||||
#define ASUS_QCOM_EC_EVENT_NONE 0x00
|
||||
#define ASUS_QCOM_EC_EVENT_INVALID 0xff
|
||||
|
||||
#define ASUS_QCOM_EC_SUBCMD_FULL_VER 0x0f
|
||||
#define ASUS_QCOM_EC_FULL_VER_LEN 7
|
||||
|
||||
/* This "read RAM" command isn't actually part of the Qualcomm standard */
|
||||
#define ASUS_QCOM_EC_RECM_OFFS 0x52
|
||||
|
||||
#define ASUS_QCOM_EC_RAM_FAN_CPU 0x0602
|
||||
#define ASUS_QCOM_EC_RAM_AC_STATUS 0x0609
|
||||
#define ASUS_QCOM_EC_RAM_FAN_GPU 0x0624
|
||||
|
||||
#define ASUS_QCOM_EC_MODERN_STANDBY_CMD 0x23
|
||||
#define ASUS_QCOM_EC_MODERN_STANDBY_ENTER 0x07
|
||||
#define ASUS_QCOM_EC_MODERN_STANDBY_EXIT 0x08
|
||||
|
||||
/* ASUS-specific registers (secondary device @ 0x5b) */
|
||||
#define ASUS_EC_SUBDEV_ADDR 0x5b
|
||||
|
||||
#define ASUS_EC_REQ_REG 0x10
|
||||
#define ASUS_EC_RESP_REG 0x11
|
||||
|
||||
#define ASUS_EC_PAGE_MAILBOX 0xc4
|
||||
#define ASUS_EC_MBOX_REG_CMD 0x30
|
||||
#define ASUS_EC_MBOX_REG_SUBCMD 0x31
|
||||
#define ASUS_EC_MBOX_REG_DATA 0x32
|
||||
|
||||
#define ASUS_EC_MBOX_CMD_KBD 0x01
|
||||
#define ASUS_EC_MBOX_SUBCMD_KBD_EN 0x81
|
||||
#define ASUS_EC_MBOX_DATA_KBD_EN 0x04
|
||||
#define ASUS_EC_MBOX_SUBCMD_KBD_LVL 0x87
|
||||
|
||||
#define ASUS_EC_MBOX_CMD_MISC 0x02
|
||||
#define ASUS_EC_MBOX_SUBCMD_MISC_ENABLE 0x83
|
||||
|
||||
#define ASUS_EC_PAGE_TEMPERATURE 0xc6
|
||||
#define ASUS_EC_TEMP_REG_CPU 0xa6
|
||||
#define ASUS_EC_TEMP_REG_SOC 0x2a
|
||||
#define ASUS_EC_TEMP_MAX 0x7f
|
||||
|
||||
#define ASUS_EC_KBD_MAX_BRIGHTNESS 3
|
||||
|
||||
static const u8 asus_ec_kbd_levels[] = {
|
||||
[0] = 0x00,
|
||||
[1] = 0x55,
|
||||
[2] = 0xaa,
|
||||
[3] = 0xff,
|
||||
};
|
||||
|
||||
enum asus_ec_event {
|
||||
ASUS_EC_EVT_CHARGE_STATE = 0x1c,
|
||||
ASUS_EC_EVT_AC_STATE = 0x1d,
|
||||
ASUS_EC_EVT_CRIT_TRIP = 0x1e,
|
||||
ASUS_EC_EVT_THERMISTOR = 0x4f,
|
||||
ASUS_EC_EVT_HOTKEY = 0xda,
|
||||
ASUS_EC_EVT_FAN_STATUS = 0xdb,
|
||||
ASUS_EC_EVT_THERMAL_TRIP = 0xe0,
|
||||
ASUS_EC_EVT_CHARGE_COMMIT = 0xe4,
|
||||
};
|
||||
|
||||
struct asus_ec {
|
||||
struct i2c_client *client;
|
||||
struct i2c_client *subdev_client;
|
||||
/* Serializes all EC access across both I2C devices */
|
||||
struct mutex lock;
|
||||
struct device *hwmon_dev;
|
||||
struct led_classdev kbd_led;
|
||||
};
|
||||
|
||||
/* RAM addresses are sent MSB first, while returned data is LSB first. */
|
||||
struct asus_qcom_ec_ram_request {
|
||||
__be16 addr;
|
||||
u8 count;
|
||||
u8 reserved[2];
|
||||
} __packed;
|
||||
|
||||
struct asus_qcom_ec_ram_response {
|
||||
__le16 data;
|
||||
u8 unused[3];
|
||||
} __packed;
|
||||
|
||||
static int asus_qcom_ec_write(struct asus_ec *ec, u8 offset,
|
||||
const u8 *buf, u8 len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&ec->lock);
|
||||
|
||||
ret = i2c_smbus_write_i2c_block_data(ec->client, offset, len, buf);
|
||||
if (ret < 0) {
|
||||
dev_dbg(&ec->client->dev,
|
||||
"Main device write off %#x len %u failed: %d\n",
|
||||
offset, len, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int asus_qcom_ec_read(struct asus_ec *ec, u8 offset, u8 *buf, u8 len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&ec->lock);
|
||||
|
||||
ret = i2c_smbus_read_i2c_block_data(ec->client, offset, len, buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret < len) {
|
||||
dev_err(&ec->client->dev,
|
||||
"Short read off %#x: got %d of %u bytes\n",
|
||||
offset, ret, len);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_qcom_ec_read_ram(struct asus_ec *ec, u16 addr, u8 count, u16 *out)
|
||||
{
|
||||
struct asus_qcom_ec_ram_request req = {
|
||||
.addr = cpu_to_be16(addr),
|
||||
.count = count,
|
||||
.reserved = {},
|
||||
};
|
||||
struct asus_qcom_ec_ram_response resp = { };
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&ec->lock);
|
||||
|
||||
if (count != 1 && count != 2)
|
||||
return -EINVAL;
|
||||
|
||||
ret = asus_qcom_ec_write(ec, ASUS_QCOM_EC_RECM_OFFS,
|
||||
(const u8 *)&req, sizeof(req));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = asus_qcom_ec_read(ec, ASUS_QCOM_EC_RECM_OFFS,
|
||||
(u8 *)&resp, sizeof(resp));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (count == 2)
|
||||
*out = le16_to_cpu(resp.data);
|
||||
else
|
||||
*out = (u8)le16_to_cpu(resp.data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_ec_readb(struct asus_ec *ec, u8 page, u8 reg, u8 *out)
|
||||
{
|
||||
u8 sel[2] = { page, reg };
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&ec->lock);
|
||||
|
||||
ret = i2c_smbus_write_i2c_block_data(ec->subdev_client, ASUS_EC_REQ_REG,
|
||||
sizeof(sel), sel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(ec->subdev_client, ASUS_EC_RESP_REG);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*out = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_ec_writeb(struct asus_ec *ec, u8 page, u8 reg, u8 data)
|
||||
{
|
||||
u8 sel[2] = { page, reg };
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&ec->lock);
|
||||
|
||||
ret = i2c_smbus_write_i2c_block_data(ec->subdev_client, ASUS_EC_REQ_REG,
|
||||
sizeof(sel), sel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return i2c_smbus_write_byte_data(ec->subdev_client,
|
||||
ASUS_EC_RESP_REG, data);
|
||||
}
|
||||
|
||||
static int asus_ec_wait_mailbox(struct asus_ec *ec)
|
||||
{
|
||||
int ret, tries;
|
||||
u8 status;
|
||||
|
||||
lockdep_assert_held(&ec->lock);
|
||||
|
||||
for (tries = 5; tries > 0; tries--) {
|
||||
ret = asus_ec_readb(ec, ASUS_EC_PAGE_MAILBOX,
|
||||
ASUS_EC_MBOX_REG_CMD, &status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!status)
|
||||
return 0;
|
||||
usleep_range(5000, 6000);
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int asus_ec_mailbox_cmd(struct asus_ec *ec, u8 cmd, u8 sub, u8 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&ec->lock);
|
||||
|
||||
ret = asus_ec_wait_mailbox(ec);
|
||||
if (ret < 0)
|
||||
return ret == -ETIMEDOUT ? -EBUSY : ret;
|
||||
|
||||
ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_SUBCMD, sub);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_DATA, data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = asus_ec_writeb(ec, ASUS_EC_PAGE_MAILBOX, ASUS_EC_MBOX_REG_CMD, cmd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return asus_ec_wait_mailbox(ec);
|
||||
}
|
||||
|
||||
static int asus_ec_enable_writes(struct asus_ec *ec)
|
||||
{
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
return asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_MISC,
|
||||
ASUS_EC_MBOX_SUBCMD_MISC_ENABLE, 1);
|
||||
}
|
||||
|
||||
static int asus_ec_read_version(struct asus_ec *ec, u8 *major, u8 *minor, u8 *patch)
|
||||
{
|
||||
u8 resp[ASUS_QCOM_EC_FULL_VER_LEN + 1];
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
ret = asus_qcom_ec_read(ec, ASUS_QCOM_EC_SUBCMD_FULL_VER,
|
||||
resp, sizeof(resp));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (resp[0] < ASUS_QCOM_EC_FULL_VER_LEN)
|
||||
return -EIO;
|
||||
|
||||
*major = resp[3];
|
||||
*minor = resp[2];
|
||||
*patch = resp[1];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_ec_get_temp(struct asus_ec *ec, u8 reg, long *millideg)
|
||||
{
|
||||
u8 raw;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
ret = asus_ec_readb(ec, ASUS_EC_PAGE_TEMPERATURE, reg, &raw);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (raw > ASUS_EC_TEMP_MAX)
|
||||
return -ENODATA;
|
||||
|
||||
*millideg = raw * MILLIDEGREE_PER_DEGREE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_ec_get_fan_rpm(struct asus_ec *ec, u16 base, long *rpm)
|
||||
{
|
||||
u16 val;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
ret = asus_qcom_ec_read_ram(ec, base, 2, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*rpm = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t asus_ec_irq(int irq, void *data)
|
||||
{
|
||||
struct asus_ec *ec = data;
|
||||
struct device *dev = &ec->client->dev;
|
||||
int code;
|
||||
|
||||
scoped_guard(mutex, &ec->lock)
|
||||
code = i2c_smbus_read_byte_data(ec->client, ASUS_QCOM_EC_EVENT_CMD);
|
||||
|
||||
if (code < 0) {
|
||||
dev_err_ratelimited(dev, "Failed to read EC event: %d\n", code);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
if (code == ASUS_QCOM_EC_EVENT_NONE ||
|
||||
code == ASUS_QCOM_EC_EVENT_INVALID)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
switch (code) {
|
||||
case ASUS_EC_EVT_HOTKEY:
|
||||
/* Present in DSDT, don't know what triggers it */
|
||||
dev_dbg_ratelimited(dev, "EC hotkey event\n");
|
||||
break;
|
||||
|
||||
case ASUS_EC_EVT_FAN_STATUS:
|
||||
dev_dbg_ratelimited(dev, "Fan status changed\n");
|
||||
break;
|
||||
|
||||
/* Maybe the temperature of the EC itself? */
|
||||
case ASUS_EC_EVT_THERMAL_TRIP:
|
||||
case ASUS_EC_EVT_CRIT_TRIP:
|
||||
/* ASUS_EC_TEMP_SOC climbing above 91 *C or falling below 92 *C */
|
||||
case ASUS_EC_EVT_THERMISTOR:
|
||||
dev_dbg_ratelimited(dev, "Thermal event %#x\n", code);
|
||||
break;
|
||||
|
||||
case ASUS_EC_EVT_AC_STATE: {
|
||||
u16 ac = 0;
|
||||
int ret;
|
||||
|
||||
/* TODO: ping SoCCP over the pmic_glink OEM-specific interface */
|
||||
scoped_guard(mutex, &ec->lock)
|
||||
ret = asus_qcom_ec_read_ram(ec, ASUS_QCOM_EC_RAM_AC_STATUS, 1, &ac);
|
||||
if (ret < 0) {
|
||||
dev_warn_ratelimited(dev,
|
||||
"Failed to read AC status: %d\n",
|
||||
ret);
|
||||
break;
|
||||
}
|
||||
dev_dbg_ratelimited(dev, "AC event: 0x%x\n", ac);
|
||||
break;
|
||||
}
|
||||
case ASUS_EC_EVT_CHARGE_STATE:
|
||||
case ASUS_EC_EVT_CHARGE_COMMIT:
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_info_ratelimited(dev, "unknown EC event: %#x\n", code);
|
||||
break;
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int asus_ec_kbd_led_set(struct led_classdev *led,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct asus_ec *ec = container_of(led, struct asus_ec, kbd_led);
|
||||
int ret;
|
||||
|
||||
if (brightness > ASUS_EC_KBD_MAX_BRIGHTNESS)
|
||||
brightness = ASUS_EC_KBD_MAX_BRIGHTNESS;
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
ret = asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_KBD, ASUS_EC_MBOX_SUBCMD_KBD_EN,
|
||||
ASUS_EC_MBOX_DATA_KBD_EN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return asus_ec_mailbox_cmd(ec, ASUS_EC_MBOX_CMD_KBD, ASUS_EC_MBOX_SUBCMD_KBD_LVL,
|
||||
asus_ec_kbd_levels[brightness]);
|
||||
}
|
||||
|
||||
enum asus_ec_temp_channel {
|
||||
/* External sensor? */
|
||||
ASUS_EC_TEMP_CPU,
|
||||
/* Likely SoC-internal temp data communicated over a side channel */
|
||||
ASUS_EC_TEMP_SOC,
|
||||
ASUS_EC_TEMP_COUNT,
|
||||
};
|
||||
|
||||
static umode_t asus_ec_hwmon_is_visible(const void *data,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_fan:
|
||||
case hwmon_temp:
|
||||
return 0444;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int asus_ec_hwmon_read(struct device *dev,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct asus_ec *ec = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (channel) {
|
||||
case ASUS_EC_TEMP_CPU:
|
||||
return asus_ec_get_temp(ec, ASUS_EC_TEMP_REG_CPU, val);
|
||||
case ASUS_EC_TEMP_SOC:
|
||||
return asus_ec_get_temp(ec, ASUS_EC_TEMP_REG_SOC, val);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
case hwmon_fan:
|
||||
switch (channel) {
|
||||
case 0:
|
||||
return asus_ec_get_fan_rpm(ec, ASUS_QCOM_EC_RAM_FAN_CPU, val);
|
||||
case 1:
|
||||
return asus_ec_get_fan_rpm(ec, ASUS_QCOM_EC_RAM_FAN_GPU, val);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int asus_ec_hwmon_read_string(struct device *dev,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
if (type != hwmon_temp || channel >= ASUS_EC_TEMP_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
switch (channel) {
|
||||
case ASUS_EC_TEMP_CPU:
|
||||
*str = "CPU";
|
||||
break;
|
||||
case ASUS_EC_TEMP_SOC:
|
||||
*str = "SoC";
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info * const asus_ec_hwmon_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT,
|
||||
HWMON_F_INPUT),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops asus_ec_hwmon_ops = {
|
||||
.is_visible = asus_ec_hwmon_is_visible,
|
||||
.read = asus_ec_hwmon_read,
|
||||
.read_string = asus_ec_hwmon_read_string,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info asus_ec_hwmon_chip_info = {
|
||||
.ops = &asus_ec_hwmon_ops,
|
||||
.info = asus_ec_hwmon_info,
|
||||
};
|
||||
|
||||
static int asus_glymur_ec_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
u8 major, minor, patch;
|
||||
struct asus_ec *ec;
|
||||
int ret;
|
||||
|
||||
ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
|
||||
if (!ec)
|
||||
return -ENOMEM;
|
||||
|
||||
ec->client = client;
|
||||
i2c_set_clientdata(client, ec);
|
||||
|
||||
ret = devm_mutex_init(dev, &ec->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ec->subdev_client = devm_i2c_new_dummy_device(dev, client->adapter,
|
||||
ASUS_EC_SUBDEV_ADDR);
|
||||
if (IS_ERR(ec->subdev_client)) {
|
||||
return dev_err_probe(dev, PTR_ERR(ec->subdev_client),
|
||||
"Failed to register 0x5b EC device\n");
|
||||
}
|
||||
|
||||
ret = asus_ec_enable_writes(ec);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to enable EC direct access: %d\n", ret);
|
||||
|
||||
ret = asus_ec_read_version(ec, &major, &minor, &patch);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to read EC version: %d\n", ret);
|
||||
|
||||
dev_dbg(dev, "ASUS Zenbook EC version %u.%u.%u\n", major, minor, patch);
|
||||
|
||||
ec->kbd_led.name = "asus::kbd_backlight";
|
||||
ec->kbd_led.max_brightness = ASUS_EC_KBD_MAX_BRIGHTNESS;
|
||||
ec->kbd_led.brightness_set_blocking = asus_ec_kbd_led_set;
|
||||
ec->kbd_led.flags = LED_CORE_SUSPENDRESUME;
|
||||
|
||||
ret = devm_led_classdev_register(dev, &ec->kbd_led);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to register keyboard LED\n");
|
||||
|
||||
ret = devm_request_threaded_irq(dev, client->irq, NULL, asus_ec_irq,
|
||||
IRQF_ONESHOT, dev_name(dev), ec);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to request IRQ\n");
|
||||
|
||||
ec->hwmon_dev = devm_hwmon_device_register_with_info(dev,
|
||||
"asus_glymur_ec",
|
||||
ec,
|
||||
&asus_ec_hwmon_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(ec->hwmon_dev)) {
|
||||
return dev_err_probe(dev, PTR_ERR(ec->hwmon_dev),
|
||||
"Failed to register hwmon\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int asus_glymur_ec_modern_standby(struct asus_ec *ec, u8 val)
|
||||
{
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
return i2c_smbus_write_byte_data(ec->client,
|
||||
ASUS_QCOM_EC_MODERN_STANDBY_CMD, val);
|
||||
}
|
||||
|
||||
static int asus_glymur_ec_suspend(struct device *dev)
|
||||
{
|
||||
return asus_glymur_ec_modern_standby(dev_get_drvdata(dev),
|
||||
ASUS_QCOM_EC_MODERN_STANDBY_ENTER);
|
||||
}
|
||||
|
||||
static int asus_glymur_ec_resume(struct device *dev)
|
||||
{
|
||||
return asus_glymur_ec_modern_standby(dev_get_drvdata(dev),
|
||||
ASUS_QCOM_EC_MODERN_STANDBY_EXIT);
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(asus_glymur_ec_pm_ops,
|
||||
asus_glymur_ec_suspend,
|
||||
asus_glymur_ec_resume);
|
||||
|
||||
static const struct of_device_id asus_glymur_ec_of_match[] = {
|
||||
{ .compatible = "asus,zenbook-a14-ux3407na-ec" },
|
||||
{ .compatible = "asus,zenbook-a16-ux3607oa-ec" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, asus_glymur_ec_of_match);
|
||||
|
||||
static const struct i2c_device_id asus_glymur_ec_id[] = {
|
||||
{ "asus-glymur-ec" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, asus_glymur_ec_id);
|
||||
|
||||
static struct i2c_driver asus_glymur_ec_driver = {
|
||||
.driver = {
|
||||
.name = "asus-glymur-ec",
|
||||
.of_match_table = asus_glymur_ec_of_match,
|
||||
.pm = pm_sleep_ptr(&asus_glymur_ec_pm_ops),
|
||||
},
|
||||
.probe = asus_glymur_ec_probe,
|
||||
.id_table = asus_glymur_ec_id,
|
||||
};
|
||||
module_i2c_driver(asus_glymur_ec_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ASUS Zenbook A16 (Qualcomm) Embedded Controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -783,7 +783,7 @@ static int gaokun_ec_probe(struct i2c_client *client)
|
|||
gaokun_ec_irq_handler, IRQF_ONESHOT,
|
||||
dev_name(dev), ec);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to request IRQ\n");
|
||||
return ret;
|
||||
|
||||
ec->hwmon_dev = devm_hwmon_device_register_with_info(dev, "gaokun_ec_hwmon",
|
||||
ec, &gaokun_ec_hwmon_chip_info, NULL);
|
||||
|
|
|
|||
|
|
@ -488,8 +488,14 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data)
|
|||
case T14S_EC_EVT_NONE:
|
||||
break;
|
||||
case T14S_EC_EVT_KEY_FN_SPACE:
|
||||
/*
|
||||
* Firmware already cycles the keyboard backlight
|
||||
* (off / low / high). Notify userspace through the LED
|
||||
* class only. Emitting KEY_KBDILLUMTOGGLE as well makes
|
||||
* desktop environments toggle the LED a second time.
|
||||
*/
|
||||
t14s_kbd_bl_update(ec);
|
||||
fallthrough;
|
||||
break;
|
||||
case T14S_EC_EVT_KEY_FN_F4:
|
||||
case T14S_EC_EVT_KEY_FN_F7:
|
||||
case T14S_EC_EVT_KEY_FN_4:
|
||||
|
|
@ -594,7 +600,7 @@ static int t14s_ec_probe(struct i2c_client *client)
|
|||
t14s_ec_irq_handler,
|
||||
IRQF_ONESHOT, dev_name(dev), ec);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to get IRQ\n");
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Disable wakeup support by default, because the driver currently does
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ static int yoga_c630_ec_probe(struct i2c_client *client)
|
|||
NULL, yoga_c630_ec_thread_intr,
|
||||
IRQF_ONESHOT, "yoga_c630_ec", ec);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "unable to request irq\n");
|
||||
return ret;
|
||||
|
||||
ret = yoga_c630_aux_init(dev, YOGA_C630_DEV_PSY, ec);
|
||||
if (ret)
|
||||
|
|
|
|||
530
drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c
Normal file
530
drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/gfp_types.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kstrtox.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/lockdep.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#define SLIM7X11_EC_BLOCK_READ 0x01
|
||||
#define SLIM7X11_EC_BLOCK_WRITE 0x02
|
||||
#define SLIM7X11_EC_RESP_LEN 6
|
||||
#define SLIM7X11_EC_REQ_LEN 7
|
||||
|
||||
#define SLIM7X11_EC_CMD_RAM_READ 0xb0
|
||||
#define SLIM7X11_EC_CMD_RAM_WRITE 0xb1
|
||||
#define SLIM7X11_EC_CMD_FAN 0x46
|
||||
#define SLIM7X11_EC_FAN_GET_SPEED 0x81
|
||||
|
||||
#define SLIM7X11_EC_CMD_EVENT 0x84
|
||||
#define SLIM7X11_EC_EVT_MAX_DRAIN 64
|
||||
#define SLIM7X11_EC_EVT_FN_LOCK 0x0f
|
||||
#define SLIM7X11_EC_EVT_MIC_MUTE 0x18
|
||||
#define SLIM7X11_EC_EVT_FLIGHT_MODE 0x19
|
||||
|
||||
#define SLIM7X11_EC_SCUK 0x1c
|
||||
#define SLIM7X11_EC_SCUK_MEDIA BIT(7)
|
||||
#define SLIM7X11_EC_SCUK_LED_OFF BIT(5)
|
||||
#define SLIM7X11_EC_SCUK_MEDIA_MASK (SLIM7X11_EC_SCUK_MEDIA | SLIM7X11_EC_SCUK_LED_OFF)
|
||||
|
||||
#define SLIM7X11_EC_KBLT 0x03
|
||||
#define SLIM7X11_EC_KBLT_LEVEL GENMASK(1, 0)
|
||||
#define SLIM7X11_EC_KBLT_AUTO 3
|
||||
#define SLIM7X11_EC_KBLT_MAX 2
|
||||
|
||||
#define SLIM7X11_EC_TEMP_FIRST 0x12
|
||||
#define SLIM7X11_EC_NUM_TEMP 6
|
||||
#define SLIM7X11_EC_TEMP_MAX 120
|
||||
|
||||
struct slim7x11_ec {
|
||||
struct i2c_client *client;
|
||||
/* serializes all EC access */
|
||||
struct mutex lock;
|
||||
unsigned long temp_present;
|
||||
struct led_classdev kbd_led;
|
||||
u8 kblt_level;
|
||||
bool kblt_restore;
|
||||
struct input_dev *keys;
|
||||
};
|
||||
|
||||
static int slim7x11_ec_command(struct slim7x11_ec *ec, u8 cmd, u8 arg0, u8 arg1,
|
||||
u8 resp[SLIM7X11_EC_RESP_LEN])
|
||||
{
|
||||
u8 req[SLIM7X11_EC_REQ_LEN] = { cmd, arg0, arg1 };
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&ec->lock);
|
||||
|
||||
ret = i2c_smbus_write_i2c_block_data(ec->client, SLIM7X11_EC_BLOCK_WRITE, sizeof(req), req);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
usleep_range(2000, 3000);
|
||||
|
||||
ret = i2c_smbus_read_i2c_block_data(ec->client, SLIM7X11_EC_BLOCK_READ,
|
||||
SLIM7X11_EC_RESP_LEN, resp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != SLIM7X11_EC_RESP_LEN)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int slim7x11_ec_ram_read(struct slim7x11_ec *ec, u8 addr, u8 *val)
|
||||
{
|
||||
u8 resp[SLIM7X11_EC_RESP_LEN];
|
||||
int ret;
|
||||
|
||||
ret = slim7x11_ec_command(ec, SLIM7X11_EC_CMD_RAM_READ, addr, 0, resp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = resp[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int slim7x11_ec_ram_write(struct slim7x11_ec *ec, u8 addr, u8 val)
|
||||
{
|
||||
u8 resp[SLIM7X11_EC_RESP_LEN];
|
||||
|
||||
return slim7x11_ec_command(ec, SLIM7X11_EC_CMD_RAM_WRITE, addr, val, resp);
|
||||
}
|
||||
|
||||
static int slim7x11_ec_fan_read(struct slim7x11_ec *ec, long *rpm)
|
||||
{
|
||||
u8 resp[SLIM7X11_EC_RESP_LEN];
|
||||
int ret;
|
||||
|
||||
ret = slim7x11_ec_command(ec, SLIM7X11_EC_CMD_FAN, SLIM7X11_EC_FAN_GET_SPEED, 0, resp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*rpm = resp[0] * 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t slim7x11_ec_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel)
|
||||
{
|
||||
const struct slim7x11_ec *ec = drvdata;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
if (!(ec->temp_present & BIT(channel)))
|
||||
return 0;
|
||||
return 0444;
|
||||
case hwmon_fan:
|
||||
return 0444;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int slim7x11_ec_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
|
||||
long *val)
|
||||
{
|
||||
struct slim7x11_ec *ec = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
u8 raw;
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_TEMP_FIRST + channel, &raw);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!raw)
|
||||
return -ENODATA;
|
||||
if (raw > SLIM7X11_EC_TEMP_MAX)
|
||||
return -EIO;
|
||||
*val = raw * MILLIDEGREE_PER_DEGREE;
|
||||
return 0;
|
||||
case hwmon_fan:
|
||||
return slim7x11_ec_fan_read(ec, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const char * const slim7x11_ec_temp_labels[SLIM7X11_EC_NUM_TEMP] = {
|
||||
"Charger A",
|
||||
"CPU VR",
|
||||
"5V rail",
|
||||
"Ambient",
|
||||
"Charger B",
|
||||
"Thermistor 6",
|
||||
};
|
||||
|
||||
static int slim7x11_ec_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||
int channel, const char **str)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
*str = slim7x11_ec_temp_labels[channel];
|
||||
return 0;
|
||||
case hwmon_fan:
|
||||
*str = "CPU fan";
|
||||
return 0;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static enum led_brightness slim7x11_ec_kbd_led_get(struct led_classdev *led)
|
||||
{
|
||||
struct slim7x11_ec *ec = container_of(led, struct slim7x11_ec, kbd_led);
|
||||
u8 raw;
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
if (slim7x11_ec_ram_read(ec, SLIM7X11_EC_KBLT, &raw))
|
||||
return 0;
|
||||
|
||||
raw = FIELD_GET(SLIM7X11_EC_KBLT_LEVEL, raw);
|
||||
|
||||
if (raw == SLIM7X11_EC_KBLT_AUTO)
|
||||
return SLIM7X11_EC_KBLT_MAX;
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
static int slim7x11_ec_kbd_led_level(struct slim7x11_ec *ec, u8 level, u8 *old)
|
||||
{
|
||||
u8 raw;
|
||||
int ret;
|
||||
|
||||
ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_KBLT, &raw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (old)
|
||||
*old = FIELD_GET(SLIM7X11_EC_KBLT_LEVEL, raw);
|
||||
|
||||
FIELD_MODIFY(SLIM7X11_EC_KBLT_LEVEL, &raw, level);
|
||||
|
||||
return slim7x11_ec_ram_write(ec, SLIM7X11_EC_KBLT, raw);
|
||||
}
|
||||
|
||||
static int slim7x11_ec_kbd_led_set(struct led_classdev *led, enum led_brightness brightness)
|
||||
{
|
||||
struct slim7x11_ec *ec = container_of(led, struct slim7x11_ec, kbd_led);
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
return slim7x11_ec_kbd_led_level(ec, brightness, NULL);
|
||||
}
|
||||
|
||||
static const struct hwmon_ops slim7x11_ec_hwmon_ops = {
|
||||
.is_visible = slim7x11_ec_is_visible,
|
||||
.read = slim7x11_ec_read,
|
||||
.read_string = slim7x11_ec_read_string,
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info * const slim7x11_ec_hwmon_info[] = {
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(fan,
|
||||
HWMON_F_INPUT | HWMON_F_LABEL),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info slim7x11_ec_chip_info = {
|
||||
.ops = &slim7x11_ec_hwmon_ops,
|
||||
.info = slim7x11_ec_hwmon_info,
|
||||
};
|
||||
|
||||
static int slim7x11_ec_fn_lock_toggle(struct slim7x11_ec *ec)
|
||||
{
|
||||
u8 raw;
|
||||
int ret;
|
||||
|
||||
ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (raw & SLIM7X11_EC_SCUK_MEDIA)
|
||||
raw &= ~SLIM7X11_EC_SCUK_MEDIA_MASK;
|
||||
else
|
||||
raw |= SLIM7X11_EC_SCUK_MEDIA_MASK;
|
||||
|
||||
return slim7x11_ec_ram_write(ec, SLIM7X11_EC_SCUK, raw);
|
||||
}
|
||||
|
||||
static void slim7x11_ec_report_key(struct slim7x11_ec *ec, unsigned int code)
|
||||
{
|
||||
if (!ec->keys)
|
||||
return;
|
||||
|
||||
input_report_key(ec->keys, code, 1);
|
||||
input_sync(ec->keys);
|
||||
input_report_key(ec->keys, code, 0);
|
||||
input_sync(ec->keys);
|
||||
}
|
||||
|
||||
static irqreturn_t slim7x11_ec_irq(int irq, void *data)
|
||||
{
|
||||
struct slim7x11_ec *ec = data;
|
||||
unsigned int i;
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
for (i = 0; i < SLIM7X11_EC_EVT_MAX_DRAIN; i++) {
|
||||
u8 resp[SLIM7X11_EC_RESP_LEN];
|
||||
|
||||
if (slim7x11_ec_command(ec, SLIM7X11_EC_CMD_EVENT, 0, 0, resp))
|
||||
break;
|
||||
if (!resp[0])
|
||||
break;
|
||||
|
||||
switch (resp[0]) {
|
||||
case SLIM7X11_EC_EVT_FN_LOCK:
|
||||
if (!slim7x11_ec_fn_lock_toggle(ec))
|
||||
sysfs_notify(&ec->client->dev.kobj, NULL, "fn_lock");
|
||||
break;
|
||||
case SLIM7X11_EC_EVT_MIC_MUTE:
|
||||
slim7x11_ec_report_key(ec, KEY_MICMUTE);
|
||||
break;
|
||||
case SLIM7X11_EC_EVT_FLIGHT_MODE:
|
||||
slim7x11_ec_report_key(ec, KEY_RFKILL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void slim7x11_ec_register_keys(struct slim7x11_ec *ec)
|
||||
{
|
||||
struct device *dev = &ec->client->dev;
|
||||
struct input_dev *input;
|
||||
int ret;
|
||||
|
||||
input = devm_input_allocate_device(dev);
|
||||
if (!input) {
|
||||
dev_warn(dev, "no memory for the hotkey device\n");
|
||||
return;
|
||||
}
|
||||
|
||||
input->name = "Lenovo Yoga Slim 7x hotkeys";
|
||||
input->phys = "lenovo-yoga-slim7x-gen11-ec/input0";
|
||||
input->id.bustype = BUS_I2C;
|
||||
input_set_capability(input, EV_KEY, KEY_MICMUTE);
|
||||
input_set_capability(input, EV_KEY, KEY_RFKILL);
|
||||
|
||||
ret = input_register_device(input);
|
||||
if (ret) {
|
||||
dev_warn(dev, "cannot register the hotkey device: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ec->keys = input;
|
||||
}
|
||||
|
||||
static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct slim7x11_ec *ec = dev_get_drvdata(dev);
|
||||
u8 raw;
|
||||
int ret;
|
||||
|
||||
scoped_guard(mutex, &ec->lock) {
|
||||
ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return sysfs_emit(buf, "%u\n", !(raw & SLIM7X11_EC_SCUK_MEDIA));
|
||||
}
|
||||
|
||||
static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct slim7x11_ec *ec = dev_get_drvdata(dev);
|
||||
bool enable;
|
||||
u8 raw;
|
||||
int ret;
|
||||
|
||||
ret = kstrtobool(buf, &enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
|
||||
ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_SCUK, &raw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (enable)
|
||||
raw &= ~SLIM7X11_EC_SCUK_MEDIA_MASK;
|
||||
else
|
||||
raw |= SLIM7X11_EC_SCUK_MEDIA_MASK;
|
||||
|
||||
ret = slim7x11_ec_ram_write(ec, SLIM7X11_EC_SCUK, raw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(fn_lock);
|
||||
|
||||
static struct attribute *slim7x11_ec_attrs[] = {
|
||||
&dev_attr_fn_lock.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(slim7x11_ec);
|
||||
|
||||
static int slim7x11_ec_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon;
|
||||
struct slim7x11_ec *ec;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
|
||||
return dev_err_probe(dev, -ENODEV, "adapter lacks SMBus block transfers\n");
|
||||
|
||||
ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
|
||||
if (!ec)
|
||||
return -ENOMEM;
|
||||
|
||||
ec->client = client;
|
||||
i2c_set_clientdata(client, ec);
|
||||
|
||||
ret = devm_mutex_init(dev, &ec->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &ec->lock) {
|
||||
for (i = 0; i < SLIM7X11_EC_NUM_TEMP; i++) {
|
||||
u8 raw;
|
||||
|
||||
ret = slim7x11_ec_ram_read(ec, SLIM7X11_EC_TEMP_FIRST + i, &raw);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to read thermistor %u\n", i);
|
||||
|
||||
if (raw > 0 && raw <= SLIM7X11_EC_TEMP_MAX)
|
||||
ec->temp_present |= BIT(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ec->temp_present)
|
||||
return dev_err_probe(dev, -ENODEV, "no thermistor reported a usable value\n");
|
||||
|
||||
dev_dbg(dev, "thermistors present: %#lx\n", ec->temp_present);
|
||||
|
||||
hwmon = devm_hwmon_device_register_with_info(dev, "yoga_slim7x_ec", ec,
|
||||
&slim7x11_ec_chip_info, NULL);
|
||||
if (IS_ERR(hwmon))
|
||||
return PTR_ERR(hwmon);
|
||||
|
||||
ec->kbd_led.name = "platform::kbd_backlight";
|
||||
ec->kbd_led.max_brightness = SLIM7X11_EC_KBLT_MAX;
|
||||
ec->kbd_led.brightness_get = slim7x11_ec_kbd_led_get;
|
||||
ec->kbd_led.brightness_set_blocking = slim7x11_ec_kbd_led_set;
|
||||
|
||||
ret = devm_led_classdev_register(dev, &ec->kbd_led);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to register keyboard backlight\n");
|
||||
|
||||
if (client->irq) {
|
||||
slim7x11_ec_register_keys(ec);
|
||||
|
||||
ret = devm_request_threaded_irq(dev, client->irq, NULL, slim7x11_ec_irq,
|
||||
IRQF_ONESHOT, "lenovo-yoga-slim7x-gen11-ec", ec);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "cannot request the EC interrupt\n");
|
||||
|
||||
scoped_guard(mutex, &ec->lock) {
|
||||
for (i = 0; i < SLIM7X11_EC_EVT_MAX_DRAIN; i++) {
|
||||
u8 resp[SLIM7X11_EC_RESP_LEN];
|
||||
|
||||
if (slim7x11_ec_command(ec, SLIM7X11_EC_CMD_EVENT, 0, 0, resp))
|
||||
break;
|
||||
if (!resp[0])
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int slim7x11_ec_suspend(struct device *dev)
|
||||
{
|
||||
struct slim7x11_ec *ec = dev_get_drvdata(dev);
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
ec->kblt_restore = !slim7x11_ec_kbd_led_level(ec, 0, &ec->kblt_level);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int slim7x11_ec_resume(struct device *dev)
|
||||
{
|
||||
struct slim7x11_ec *ec = dev_get_drvdata(dev);
|
||||
|
||||
guard(mutex)(&ec->lock);
|
||||
if (ec->kblt_restore) {
|
||||
ec->kblt_restore = false;
|
||||
slim7x11_ec_kbd_led_level(ec, ec->kblt_level, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(slim7x11_ec_pm_ops, slim7x11_ec_suspend, slim7x11_ec_resume);
|
||||
|
||||
static const struct of_device_id slim7x11_ec_of_match[] = {
|
||||
{ .compatible = "lenovo,yoga-slim7x-gen11-ec" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, slim7x11_ec_of_match);
|
||||
|
||||
static const struct i2c_device_id slim7x11_ec_i2c_id[] = {
|
||||
{ .name = "slim7x-gen11-ec" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, slim7x11_ec_i2c_id);
|
||||
|
||||
static struct i2c_driver slim7x11_ec_driver = {
|
||||
.driver = {
|
||||
.name = "lenovo-yoga-slim7x-gen11-ec",
|
||||
.of_match_table = slim7x11_ec_of_match,
|
||||
.dev_groups = slim7x11_ec_groups,
|
||||
.pm = pm_sleep_ptr(&slim7x11_ec_pm_ops),
|
||||
},
|
||||
.probe = slim7x11_ec_probe,
|
||||
.id_table = slim7x11_ec_i2c_id,
|
||||
};
|
||||
module_i2c_driver(slim7x11_ec_driver);
|
||||
|
||||
MODULE_AUTHOR("Oleg Keri <okerixx@gmail.com>");
|
||||
MODULE_DESCRIPTION("Lenovo Yoga Slim 7x Gen 11 embedded controller");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -331,17 +331,28 @@ static const struct thermal_cooling_device_ops qcom_ec_thermal_ops = {
|
|||
static int qcom_ec_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
int ret;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
|
||||
EC_MODERN_STANDBY_EXIT);
|
||||
ret = i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
|
||||
EC_MODERN_STANDBY_EXIT);
|
||||
enable_irq(client->irq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qcom_ec_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
int ret;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
|
||||
EC_MODERN_STANDBY_ENTER);
|
||||
/* Keep SCI reads from racing the I2C controller's system sleep. */
|
||||
disable_irq(client->irq);
|
||||
ret = i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD,
|
||||
EC_MODERN_STANDBY_ENTER);
|
||||
if (ret)
|
||||
enable_irq(client->irq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qcom_ec_probe(struct i2c_client *client)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ config LOONGSON_LAPTOP
|
|||
depends on INPUT
|
||||
depends on MACH_LOONGSON64
|
||||
select ACPI_VIDEO
|
||||
select ACPI_WMI
|
||||
select INPUT_SPARSEKMAP
|
||||
default y
|
||||
help
|
||||
|
|
|
|||
|
|
@ -1405,7 +1405,6 @@ static int mlxbf_tmfifo_probe(struct platform_device *pdev)
|
|||
mlxbf_tmfifo_irq_handler, 0,
|
||||
"tmfifo", &fifo->irq_info[i]);
|
||||
if (rc) {
|
||||
dev_err(dev, "devm_request_irq failed\n");
|
||||
fifo->irq_info[i].irq = 0;
|
||||
return rc;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -740,10 +740,8 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev)
|
|||
err = devm_request_irq(&pdev->dev, priv->irq,
|
||||
mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING
|
||||
| IRQF_SHARED, "mlxreg-hotplug", priv);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Failed to request irq: %d\n", err);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
disable_irq(priv->irq);
|
||||
spin_lock_init(&priv->lock);
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ static ssize_t ec_dbgfs_cmd_write(struct file *file, const char __user *buf,
|
|||
|
||||
mutex_lock(&ec_dbgfs_lock);
|
||||
|
||||
size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf), ppos, buf, size);
|
||||
size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf) - 1, ppos, buf, size);
|
||||
|
||||
m = sscanf(cmdbuf, "%x:%u %x %x %x %x %x", &ec_cmd_int[0],
|
||||
&ec_dbgfs_resp_bytes, &ec_cmd_int[1], &ec_cmd_int[2],
|
||||
|
|
|
|||
|
|
@ -410,13 +410,14 @@ static const struct software_node *ssam_node_group_sp11[] = {
|
|||
&ssam_node_hub_kip,
|
||||
&ssam_node_bat_ac,
|
||||
&ssam_node_bat_main,
|
||||
&ssam_node_tmp_perf_profile_with_fan,
|
||||
&ssam_node_tmp_sensors,
|
||||
&ssam_node_fan_speed,
|
||||
&ssam_node_hid_kip_keyboard,
|
||||
&ssam_node_hid_kip_penstash,
|
||||
&ssam_node_hid_kip_touchpad,
|
||||
&ssam_node_hid_kip_fwupd,
|
||||
&ssam_node_hid_sam_sensors,
|
||||
&ssam_node_kip_tablet_switch,
|
||||
&ssam_node_pos_tablet_switch,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
|
@ -516,6 +517,8 @@ static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
|
|||
/* Surface Laptop 7 */
|
||||
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
|
||||
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
|
||||
/* Surface Laptop 8th Edition (13.8") */
|
||||
{ .compatible = "microsoft,surface-laptop-8-13in", (void *)ssam_node_group_sl7 },
|
||||
{ },
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
menuconfig ACPI_WMI
|
||||
tristate "ACPI-WMI support"
|
||||
depends on ACPI && X86
|
||||
depends on ACPI
|
||||
select NLS
|
||||
help
|
||||
This option enables support for the ACPI-WMI driver core.
|
||||
|
|
@ -34,4 +34,16 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES
|
|||
|
||||
source "drivers/platform/wmi/tests/Kconfig"
|
||||
|
||||
config WMI_BMOF
|
||||
tristate "WMI embedded Binary MOF driver"
|
||||
default ACPI_WMI
|
||||
help
|
||||
Say Y here if you want to be able to read the firmware-embedded
|
||||
WMI Binary MOF (Managed Object Format) data. Using this requires
|
||||
userspace tools, see Documentation/wmi/devices/wmi-bmof.rst for
|
||||
details.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called wmi-bmof.
|
||||
|
||||
endif # ACPI_WMI
|
||||
|
|
|
|||
|
|
@ -9,3 +9,7 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o
|
|||
|
||||
# Unit tests
|
||||
obj-y += tests/
|
||||
|
||||
# Binary MOF driver
|
||||
wmi-bmof-y := bmof.o
|
||||
obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ static inline void get_acpi_method_name(const struct wmi_block *wblock,
|
|||
buffer[4] = '\0';
|
||||
}
|
||||
|
||||
static int wmidev_match_guid(struct device *dev, const void *data)
|
||||
static int wmidev_match_guid_legacy(struct device *dev, const void *data)
|
||||
{
|
||||
struct wmi_block *wblock = dev_to_wblock(dev);
|
||||
const guid_t *guid = data;
|
||||
|
|
@ -218,7 +218,7 @@ static struct wmi_device *wmi_find_device_by_guid(const char *guid_string)
|
|||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
dev = bus_find_device(&wmi_bus_type, NULL, &guid, wmidev_match_guid);
|
||||
dev = bus_find_device(&wmi_bus_type, NULL, &guid, wmidev_match_guid_legacy);
|
||||
if (!dev)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
|
|
@ -800,10 +800,10 @@ acpi_status wmi_remove_notify_handler(const char *guid)
|
|||
EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
|
||||
|
||||
/**
|
||||
* wmi_has_guid - Check if a GUID is available
|
||||
* wmi_has_guid - Check if a GUID is available (deprecated)
|
||||
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
|
||||
*
|
||||
* Check if a given GUID is defined by _WDG.
|
||||
* Check if a given GUID is available and usable with the legacy GUID-based API.
|
||||
*
|
||||
* Return: True if GUID is available, false otherwise.
|
||||
*/
|
||||
|
|
@ -821,6 +821,31 @@ bool wmi_has_guid(const char *guid_string)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(wmi_has_guid);
|
||||
|
||||
static int wmidev_match_guid(struct device *dev, void *data)
|
||||
{
|
||||
struct wmi_block *wblock = dev_to_wblock(dev);
|
||||
const guid_t *guid = data;
|
||||
|
||||
if (guid_equal(guid, &wblock->gblock.guid))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* wmidev_exists - Check if a WMI device with a given GUID exists
|
||||
* @guid: GUID of the WMI device to search for
|
||||
*
|
||||
* Check if a WMI device with a given GUID exists at the given moment.
|
||||
*
|
||||
* Return: True if a matching WMI device exists, false otherwise.
|
||||
*/
|
||||
bool wmidev_exists(const guid_t *guid)
|
||||
{
|
||||
return bus_for_each_dev(&wmi_bus_type, NULL, (void *)guid, wmidev_match_guid) == 1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wmidev_exists);
|
||||
|
||||
/**
|
||||
* wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID (deprecated)
|
||||
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
|
||||
|
|
|
|||
|
|
@ -16,18 +16,6 @@ menuconfig X86_PLATFORM_DEVICES
|
|||
|
||||
if X86_PLATFORM_DEVICES
|
||||
|
||||
config WMI_BMOF
|
||||
tristate "WMI embedded Binary MOF driver"
|
||||
depends on ACPI_WMI
|
||||
default ACPI_WMI
|
||||
help
|
||||
Say Y here if you want to be able to read a firmware-embedded
|
||||
WMI Binary MOF (Managed Object Format) data. Using this requires
|
||||
userspace tools and may be rather tedious.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called wmi-bmof.
|
||||
|
||||
config HUAWEI_WMI
|
||||
tristate "Huawei WMI laptop extras driver"
|
||||
depends on ACPI_BATTERY
|
||||
|
|
@ -189,6 +177,18 @@ config ACER_WMI
|
|||
If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
|
||||
here.
|
||||
|
||||
config ACER_WMI_BATTERY
|
||||
tristate "Acer WMI Battery"
|
||||
depends on ACPI_WMI
|
||||
depends on ACPI_BATTERY
|
||||
depends on DMI
|
||||
help
|
||||
This is a driver for Acer laptops with battery health control. It
|
||||
adds charge limit control and battery temperature reporting.
|
||||
|
||||
If you have an ACPI-WMI Battery compatible Acer laptop, say Y or M
|
||||
here.
|
||||
|
||||
source "drivers/platform/x86/amd/Kconfig"
|
||||
|
||||
config ADV_SWBUTTON
|
||||
|
|
|
|||
|
|
@ -4,9 +4,6 @@
|
|||
# x86 Platform-Specific Drivers
|
||||
#
|
||||
|
||||
# Windows Management Interface
|
||||
obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o
|
||||
|
||||
# WMI drivers
|
||||
obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o
|
||||
obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
|
||||
|
|
@ -20,6 +17,7 @@ obj-$(CONFIG_BITLAND_MIFS_WMI) += bitland-mifs-wmi.o
|
|||
obj-$(CONFIG_ACERHDF) += acerhdf.o
|
||||
obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o
|
||||
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
|
||||
obj-$(CONFIG_ACER_WMI_BATTERY) += acer-wmi-battery.o
|
||||
|
||||
# AMD
|
||||
obj-y += amd/
|
||||
|
|
|
|||
450
drivers/platform/x86/acer-wmi-battery.c
Normal file
450
drivers/platform/x86/acer-wmi-battery.c
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* acer-wmi-battery.c: Acer battery health control driver
|
||||
*
|
||||
* This is a driver for the WMI battery health control interface found
|
||||
* on some Acer laptops. This interface allows to enable/disable a
|
||||
* battery charge limit ("health mode") and exposes the battery temperature.
|
||||
*
|
||||
* Based on acer-wmi-battery https://github.com/frederik-h/acer-wmi-battery/
|
||||
*
|
||||
* Copyright (C) 2022-2025 Frederik Harwath <frederik@harwath.name>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/byteorder/generic.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include <acpi/battery.h>
|
||||
|
||||
#define DRIVER_NAME "acer-wmi-battery"
|
||||
|
||||
#define ACER_BATTERY_GUID "79772EC5-04B1-4BFD-843C-61E7F77B6CC9"
|
||||
|
||||
/*
|
||||
* The Acer OEM software seems to always use this battery index,
|
||||
* so we emulate this behaviour to not confuse the underlying firmware.
|
||||
*
|
||||
* However this also means that we only fully support devices with a
|
||||
* single battery for now.
|
||||
*/
|
||||
#define ACER_BATTERY_INDEX 0x1
|
||||
|
||||
static bool force;
|
||||
module_param_unsafe(force, bool, 0);
|
||||
MODULE_PARM_DESC(force, "Enable health mode control without checking for supported devices\n");
|
||||
|
||||
struct get_battery_health_control_status_input {
|
||||
u8 battery_no;
|
||||
u8 function_query;
|
||||
u8 reserved[2];
|
||||
} __packed;
|
||||
|
||||
struct get_battery_health_control_status_output {
|
||||
u8 function_list;
|
||||
u8 ret[2];
|
||||
u8 function_status[5];
|
||||
} __packed;
|
||||
|
||||
struct set_battery_health_control_input {
|
||||
u8 battery_no;
|
||||
u8 function_mask;
|
||||
u8 function_status;
|
||||
u8 reserved_in[5];
|
||||
} __packed;
|
||||
|
||||
struct set_battery_health_control_output {
|
||||
__le16 ret;
|
||||
__le16 reserved_out;
|
||||
} __packed;
|
||||
|
||||
enum battery_mode {
|
||||
HEALTH_MODE = 1,
|
||||
CALIBRATION_MODE = 2,
|
||||
};
|
||||
|
||||
struct acer_wmi_battery_data {
|
||||
struct acpi_battery_hook hook;
|
||||
struct wmi_device *wdev;
|
||||
const struct power_supply_ext *battery_ext;
|
||||
};
|
||||
|
||||
static bool health_mode __ro_after_init;
|
||||
|
||||
static int acer_wmi_battery_get_information(struct acer_wmi_battery_data *data,
|
||||
u32 index, u32 battery, u32 *result)
|
||||
{
|
||||
u32 args[2] = { index, battery };
|
||||
struct wmi_buffer input = { .length = sizeof(args), .data = args };
|
||||
struct wmi_buffer output;
|
||||
int ret;
|
||||
|
||||
ret = wmidev_invoke_method(data->wdev, 0, 19, &input, &output, sizeof(*result));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*result = le32_to_cpup((__le32 *)output.data);
|
||||
|
||||
kfree(output.data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acer_wmi_battery_get_health_control_status(struct acer_wmi_battery_data *data,
|
||||
bool *health_mode)
|
||||
{
|
||||
/*
|
||||
* Acer Care Center seems to always call the WMI method
|
||||
* with fixed parameters. This yields information about
|
||||
* the availability and state of both health and
|
||||
* calibration mode. The modes probably apply to
|
||||
* all batteries of the system.
|
||||
*/
|
||||
struct get_battery_health_control_status_input args = {
|
||||
.battery_no = ACER_BATTERY_INDEX,
|
||||
.function_query = 0x1,
|
||||
.reserved = { 0x0, 0x0 },
|
||||
};
|
||||
struct wmi_buffer input = { .length = sizeof(args), .data = &args };
|
||||
struct wmi_buffer output;
|
||||
int ret;
|
||||
|
||||
ret = wmidev_invoke_method(data->wdev, 0, 20, &input, &output,
|
||||
sizeof(struct get_battery_health_control_status_output));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct get_battery_health_control_status_output *status_output __free(kfree) = output.data;
|
||||
|
||||
if (!(status_output->function_list & HEALTH_MODE))
|
||||
return -EINVAL;
|
||||
|
||||
*health_mode = status_output->function_status[0] > 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acer_wmi_battery_set_health_control(struct acer_wmi_battery_data *data,
|
||||
u8 function, bool function_status)
|
||||
{
|
||||
struct set_battery_health_control_input args = {
|
||||
.battery_no = ACER_BATTERY_INDEX,
|
||||
.function_mask = function,
|
||||
.function_status = function_status ? 1 : 0,
|
||||
.reserved_in = { 0x0, 0x0, 0x0, 0x0, 0x0 },
|
||||
};
|
||||
struct wmi_buffer input = { .length = sizeof(args), .data = &args };
|
||||
struct wmi_buffer output;
|
||||
int ret;
|
||||
|
||||
ret = wmidev_invoke_method(data->wdev, 0, 21, &input, &output,
|
||||
sizeof(struct set_battery_health_control_output));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct set_battery_health_control_output *status_output __free(kfree) = output.data;
|
||||
if (le16_to_cpu(status_output->ret) != 0)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acer_battery_ext_property_get(struct power_supply *psy,
|
||||
const struct power_supply_ext *ext,
|
||||
void *ext_data,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct acer_wmi_battery_data *data = ext_data;
|
||||
bool health_mode;
|
||||
u32 value;
|
||||
int ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CHARGE_TYPES:
|
||||
ret = acer_wmi_battery_get_health_control_status(data, &health_mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val->intval = health_mode ? POWER_SUPPLY_CHARGE_TYPE_LONGLIFE
|
||||
: POWER_SUPPLY_CHARGE_TYPE_STANDARD;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP:
|
||||
ret = acer_wmi_battery_get_information(data, 0x8, ACER_BATTERY_INDEX, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (value > U16_MAX)
|
||||
return -ERANGE;
|
||||
|
||||
val->intval = value + ABSOLUTE_ZERO_MILLICELSIUS / MILLIDEGREE_PER_DECIDEGREE;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acer_battery_ext_property_set(struct power_supply *psy,
|
||||
const struct power_supply_ext *ext,
|
||||
void *ext_data,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct acer_wmi_battery_data *data = ext_data;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CHARGE_TYPES:
|
||||
return acer_wmi_battery_set_health_control(data, HEALTH_MODE,
|
||||
val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int acer_battery_ext_property_is_writeable(struct power_supply *psy,
|
||||
const struct power_supply_ext *ext,
|
||||
void *ext_data,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CHARGE_TYPES:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const enum power_supply_property acer_battery_properties_v1[] = {
|
||||
POWER_SUPPLY_PROP_TEMP,
|
||||
};
|
||||
|
||||
static const enum power_supply_property acer_battery_properties_v2[] = {
|
||||
POWER_SUPPLY_PROP_TEMP,
|
||||
POWER_SUPPLY_PROP_CHARGE_TYPES,
|
||||
};
|
||||
|
||||
static const struct power_supply_ext acer_wmi_battery_extension_v1 = {
|
||||
.name = DRIVER_NAME,
|
||||
.properties = acer_battery_properties_v1,
|
||||
.num_properties = ARRAY_SIZE(acer_battery_properties_v1),
|
||||
.get_property = acer_battery_ext_property_get,
|
||||
};
|
||||
|
||||
static const struct power_supply_ext acer_wmi_battery_extension_v2 = {
|
||||
.name = DRIVER_NAME,
|
||||
.properties = acer_battery_properties_v2,
|
||||
.num_properties = ARRAY_SIZE(acer_battery_properties_v2),
|
||||
.charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
|
||||
BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE),
|
||||
.get_property = acer_battery_ext_property_get,
|
||||
.set_property = acer_battery_ext_property_set,
|
||||
.property_is_writeable = acer_battery_ext_property_is_writeable,
|
||||
};
|
||||
|
||||
static int acer_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
|
||||
{
|
||||
struct acer_wmi_battery_data *data = container_of(hook, struct acer_wmi_battery_data, hook);
|
||||
|
||||
return power_supply_register_extension(battery, data->battery_ext,
|
||||
&data->wdev->dev, data);
|
||||
}
|
||||
|
||||
static int acer_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook)
|
||||
{
|
||||
struct acer_wmi_battery_data *data = container_of(hook, struct acer_wmi_battery_data, hook);
|
||||
|
||||
power_supply_unregister_extension(battery, data->battery_ext);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int acer_wmi_battery_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct acer_wmi_battery_data *data;
|
||||
|
||||
data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(&wdev->dev, data);
|
||||
data->wdev = wdev;
|
||||
data->battery_ext = health_mode ? &acer_wmi_battery_extension_v2
|
||||
: &acer_wmi_battery_extension_v1;
|
||||
data->hook.name = "Acer Battery Extension";
|
||||
data->hook.add_battery = acer_battery_add;
|
||||
data->hook.remove_battery = acer_battery_remove;
|
||||
|
||||
return devm_battery_hook_register(&data->wdev->dev, &data->hook);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id acer_wmi_battery_id_table[] = {
|
||||
{ ACER_BATTERY_GUID, NULL },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(wmi, acer_wmi_battery_id_table);
|
||||
|
||||
static struct wmi_driver acer_wmi_battery_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
},
|
||||
.id_table = acer_wmi_battery_id_table,
|
||||
.probe = acer_wmi_battery_probe,
|
||||
.no_singleton = true,
|
||||
};
|
||||
|
||||
static const struct dmi_system_id acer_wmi_battery_health_mode_table[] __initconst = {
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-510P"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-24PT"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-44P"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-58G"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-59"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-59"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A715-42G"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-51"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-57"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-58"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN517-54"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN517-54"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Predator PHN16-71"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-34"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-43"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFE16-44"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFG16-72"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFX14-71G"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFX16-61G"),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static int __init acer_wmi_battery_module_init(void)
|
||||
{
|
||||
if (dmi_check_system(acer_wmi_battery_health_mode_table) || force)
|
||||
health_mode = true;
|
||||
else
|
||||
health_mode = false;
|
||||
|
||||
return wmi_driver_register(&acer_wmi_battery_driver);
|
||||
}
|
||||
|
||||
static void __exit acer_wmi_battery_module_exit(void)
|
||||
{
|
||||
wmi_driver_unregister(&acer_wmi_battery_driver);
|
||||
}
|
||||
|
||||
module_init(acer_wmi_battery_module_init);
|
||||
module_exit(acer_wmi_battery_module_exit);
|
||||
|
||||
MODULE_AUTHOR("Frederik Harwath <frederik@harwath.name>");
|
||||
MODULE_AUTHOR("Jelle van der Waa <jelle@vdwaa.nl>");
|
||||
MODULE_DESCRIPTION("Acer battery health control WMI driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -400,11 +400,12 @@ struct quirk_entry {
|
|||
u8 mailled;
|
||||
s8 brightness;
|
||||
u8 bluetooth;
|
||||
u8 turbo;
|
||||
bool turbo;
|
||||
u8 cpu_fans;
|
||||
u8 gpu_fans;
|
||||
u8 predator_v4;
|
||||
u8 pwm;
|
||||
bool hwmon;
|
||||
bool platform_profile;
|
||||
bool pwm;
|
||||
};
|
||||
|
||||
static struct quirk_entry *quirks;
|
||||
|
|
@ -421,9 +422,11 @@ static void __init set_quirks(void)
|
|||
interface->capability |= ACER_CAP_TURBO_OC | ACER_CAP_TURBO_LED
|
||||
| ACER_CAP_TURBO_FAN;
|
||||
|
||||
if (quirks->predator_v4)
|
||||
interface->capability |= ACER_CAP_PLATFORM_PROFILE |
|
||||
ACER_CAP_HWMON;
|
||||
if (quirks->hwmon)
|
||||
interface->capability |= ACER_CAP_HWMON;
|
||||
|
||||
if (quirks->platform_profile)
|
||||
interface->capability |= ACER_CAP_PLATFORM_PROFILE;
|
||||
|
||||
if (quirks->pwm)
|
||||
interface->capability |= ACER_CAP_PWM;
|
||||
|
|
@ -451,39 +454,55 @@ static struct quirk_entry quirk_acer_aspire_1520 = {
|
|||
.brightness = -1,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_acer_aspire_a315_58g = {
|
||||
.hwmon = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_acer_travelmate_2490 = {
|
||||
.mailled = 1,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_acer_nitro_an515_58 = {
|
||||
.predator_v4 = 1,
|
||||
.pwm = 1,
|
||||
.hwmon = true,
|
||||
.platform_profile = true,
|
||||
.pwm = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_acer_predator_ph315_53 = {
|
||||
.turbo = 1,
|
||||
.turbo = true,
|
||||
.cpu_fans = 1,
|
||||
.gpu_fans = 1,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_acer_nitro_anv15_51 = {
|
||||
.cpu_fans = 1,
|
||||
.gpu_fans = 1,
|
||||
.hwmon = true,
|
||||
.platform_profile = true,
|
||||
.pwm = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_acer_predator_ph16_72 = {
|
||||
.turbo = 1,
|
||||
.turbo = true,
|
||||
.cpu_fans = 1,
|
||||
.gpu_fans = 1,
|
||||
.predator_v4 = 1,
|
||||
.pwm = 1,
|
||||
.hwmon = true,
|
||||
.platform_profile = true,
|
||||
.pwm = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_acer_predator_pt14_51 = {
|
||||
.turbo = 1,
|
||||
.turbo = true,
|
||||
.cpu_fans = 1,
|
||||
.gpu_fans = 1,
|
||||
.predator_v4 = 1,
|
||||
.pwm = 1,
|
||||
.hwmon = true,
|
||||
.platform_profile = true,
|
||||
.pwm = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_acer_predator_v4 = {
|
||||
.predator_v4 = 1,
|
||||
.hwmon = true,
|
||||
.platform_profile = true,
|
||||
};
|
||||
|
||||
/* This AMW0 laptop has no bluetooth */
|
||||
|
|
@ -635,6 +654,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
|
|||
},
|
||||
.driver_data = &quirk_acer_travelmate_2490,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Acer Aspire A315-58G",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A315-58G"),
|
||||
},
|
||||
.driver_data = &quirk_acer_aspire_a315_58g,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Acer TravelMate 2490",
|
||||
|
|
@ -653,6 +681,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
|
|||
},
|
||||
.driver_data = &quirk_acer_travelmate_2490,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Acer Nitro AN16-41",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN16-41"),
|
||||
},
|
||||
.driver_data = &quirk_acer_nitro_an515_58,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Acer Nitro AN515-58",
|
||||
|
|
@ -662,6 +699,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
|
|||
},
|
||||
.driver_data = &quirk_acer_nitro_an515_58,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Acer Nitro ANV15-51",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-51"),
|
||||
},
|
||||
.driver_data = &quirk_acer_nitro_anv15_51,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Acer Predator PH315-53",
|
||||
|
|
@ -839,7 +885,6 @@ static const struct dmi_system_id non_acer_quirks[] __initconst = {
|
|||
};
|
||||
|
||||
static struct device *platform_profile_device;
|
||||
static bool platform_profile_support;
|
||||
|
||||
/*
|
||||
* The profile used before turbo mode. This variable is needed for
|
||||
|
|
@ -2194,13 +2239,11 @@ static const struct platform_profile_ops acer_predator_v4_platform_profile_ops =
|
|||
|
||||
static int acer_platform_profile_setup(struct platform_device *device)
|
||||
{
|
||||
if (quirks->predator_v4) {
|
||||
if (quirks->platform_profile) {
|
||||
platform_profile_device = devm_platform_profile_register(
|
||||
&device->dev, "acer-wmi", NULL, &acer_predator_v4_platform_profile_ops);
|
||||
if (IS_ERR(platform_profile_device))
|
||||
return PTR_ERR(platform_profile_device);
|
||||
|
||||
platform_profile_support = true;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2211,7 +2254,7 @@ static int acer_thermal_profile_change(void)
|
|||
* This mode key will either cycle through each mode or toggle the
|
||||
* most performant profile.
|
||||
*/
|
||||
if (quirks->predator_v4) {
|
||||
if (quirks->platform_profile) {
|
||||
u8 current_tp;
|
||||
int err, tp;
|
||||
|
||||
|
|
@ -2530,8 +2573,13 @@ static void acer_wmi_notify(union acpi_object *obj, void *context)
|
|||
acer_kbd_dock_event(&return_value);
|
||||
break;
|
||||
case WMID_GAMING_TURBO_KEY_EVENT:
|
||||
if (return_value.key_num == 0x4)
|
||||
acer_toggle_turbo();
|
||||
if (return_value.key_num == 0x4) {
|
||||
/* Models without a turbo button report their mode key as 0x4 */
|
||||
if (has_cap(ACER_CAP_TURBO_LED))
|
||||
acer_toggle_turbo();
|
||||
else if (has_cap(ACER_CAP_PLATFORM_PROFILE))
|
||||
acer_thermal_profile_change();
|
||||
}
|
||||
if (return_value.key_num == 0x5 && has_cap(ACER_CAP_PLATFORM_PROFILE))
|
||||
acer_thermal_profile_change();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/mailbox_client.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/percpu-defs.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/smp.h>
|
||||
|
|
@ -38,8 +39,6 @@
|
|||
|
||||
#define AMD_HETERO_CPUID_27 0x80000027
|
||||
|
||||
static struct platform_device *device;
|
||||
|
||||
/**
|
||||
* struct amd_shmem_info - Shared memory table for AMD HFI
|
||||
*
|
||||
|
|
@ -76,6 +75,8 @@ struct amd_hfi_data {
|
|||
struct amd_shmem_info *shmem;
|
||||
|
||||
struct dentry *dbgfs_dir;
|
||||
|
||||
int cpuhp_state;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -166,6 +167,21 @@ static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The bitmaps enumerating the APIC IDs occupy the first n_bitmaps
|
||||
* words of table_data. Reject a firmware-provided count that would
|
||||
* push those reads past the end of the shared memory region.
|
||||
*/
|
||||
if (struct_size(amd_hfi_data->shmem, table_data, amd_hfi_data->shmem->n_bitmaps) >
|
||||
pcct_ext->length) {
|
||||
dev_err(amd_hfi_data->dev, "invalid number of bitmaps: %u\n",
|
||||
amd_hfi_data->shmem->n_bitmaps);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* The ranking data for each processor follows the bitmaps */
|
||||
u32 *table = amd_hfi_data->shmem->table_data + amd_hfi_data->shmem->n_bitmaps;
|
||||
|
||||
for (unsigned int i = 0; i < amd_hfi_data->shmem->n_bitmaps; i++) {
|
||||
u32 bitmap = amd_hfi_data->shmem->table_data[i];
|
||||
|
||||
|
|
@ -190,10 +206,6 @@ static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data)
|
|||
info = per_cpu_ptr(&amd_hfi_cpuinfo, cpu_index);
|
||||
apic_index = apic_start * info->nr_class * 2;
|
||||
for (unsigned int k = 0; k < info->nr_class; k++) {
|
||||
u32 *table = amd_hfi_data->shmem->table_data +
|
||||
amd_hfi_data->shmem->n_bitmaps +
|
||||
i * info->nr_class;
|
||||
|
||||
info->amd_hfi_classes[k].eff = table[apic_index + 2 * k];
|
||||
info->amd_hfi_classes[k].perf = table[apic_index + 2 * k + 1];
|
||||
}
|
||||
|
|
@ -244,6 +256,8 @@ static void amd_hfi_remove(struct platform_device *pdev)
|
|||
{
|
||||
struct amd_hfi_data *dev = platform_get_drvdata(pdev);
|
||||
|
||||
cpuhp_remove_state(dev->cpuhp_state);
|
||||
cancel_work_sync(&sched_amd_hfi_itmt_work);
|
||||
debugfs_remove_recursive(dev->dbgfs_dir);
|
||||
}
|
||||
|
||||
|
|
@ -494,6 +508,7 @@ static int amd_hfi_probe(struct platform_device *pdev)
|
|||
amd_hfi_online, amd_hfi_offline);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
amd_hfi_data->cpuhp_state = ret;
|
||||
|
||||
schedule_work(&sched_amd_hfi_itmt_work);
|
||||
|
||||
|
|
@ -523,12 +538,6 @@ static int __init amd_hfi_init(void)
|
|||
!cpu_feature_enabled(X86_FEATURE_AMD_WORKLOAD_CLASS))
|
||||
return -ENODEV;
|
||||
|
||||
device = platform_device_register_simple(AMD_HFI_DRIVER, -1, NULL, 0);
|
||||
if (IS_ERR(device)) {
|
||||
pr_err("unable to register HFI platform device\n");
|
||||
return PTR_ERR(device);
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&amd_hfi_driver);
|
||||
if (ret)
|
||||
pr_err("failed to register HFI driver\n");
|
||||
|
|
@ -539,7 +548,6 @@ static int __init amd_hfi_init(void)
|
|||
static __exit void amd_hfi_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&amd_hfi_driver);
|
||||
platform_device_unregister(device);
|
||||
}
|
||||
module_init(amd_hfi_init);
|
||||
module_exit(amd_hfi_exit);
|
||||
|
|
|
|||
|
|
@ -300,16 +300,26 @@ static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
|
|||
* 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.
|
||||
* Hide metrics_bin on client platforms.
|
||||
*/
|
||||
if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)
|
||||
if (!is_client_platform() && hsmp_pdev->proto_ver >= HSMP_PROTO_VER6)
|
||||
return battr->attr.mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Defined below by HSMP_DEV_ATTR() */
|
||||
static struct hsmp_sys_attr hattr_smu_fw_version;
|
||||
static struct hsmp_sys_attr hattr_protocol_version;
|
||||
|
||||
static umode_t hsmp_is_sock_dev_attr_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int id)
|
||||
{
|
||||
/* Hide every attribute except smu_fw_version and protocol_version on client platforms */
|
||||
if (is_client_platform() && attr != &hattr_smu_fw_version.dattr.attr &&
|
||||
attr != &hattr_protocol_version.dattr.attr)
|
||||
return 0;
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
|
|
@ -557,15 +567,26 @@ static int init_acpi(struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) {
|
||||
/*
|
||||
* proto_ver holds the Ryzen Master interface version on client
|
||||
* platforms, a separate numbering space from enum
|
||||
* hsmp_proto_versions; gate the two platforms on their own
|
||||
* version enum rather than relying on numeric coincidence
|
||||
* between the two.
|
||||
*/
|
||||
if ((is_client_platform() && hsmp_pdev->proto_ver >= RYZEN_MASTER_PROTO_VER1) ||
|
||||
(!is_client_platform() && 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");
|
||||
}
|
||||
|
||||
ret = hsmp_create_sensor(dev, sock_ind);
|
||||
if (ret)
|
||||
dev_info(dev, "Failed to register HSMP sensors with hwmon\n");
|
||||
/* Skip hwmon registration on client platforms */
|
||||
if (!is_client_platform()) {
|
||||
ret = hsmp_create_sensor(dev, sock_ind);
|
||||
if (ret)
|
||||
dev_info(dev, "Failed to register HSMP sensors with hwmon\n");
|
||||
}
|
||||
|
||||
dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@
|
|||
#include <asm/amd/hsmp.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/build_bug.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
|
|
@ -45,8 +48,100 @@
|
|||
*/
|
||||
#define CHECK_GET_BIT BIT(31)
|
||||
|
||||
/* Catch a table left out of sync with its enum at build time */
|
||||
static_assert(ARRAY_SIZE(hsmp_msg_desc_table) == HSMP_MSG_ID_MAX);
|
||||
|
||||
/* Per-platform message set: which table to use, and driver-issued msg IDs */
|
||||
struct hsmp_plat_desc {
|
||||
const struct hsmp_msg_desc *msg_desc;
|
||||
u32 num_msgs;
|
||||
u32 test_msg;
|
||||
u32 proto_ver_msg;
|
||||
u32 metric_tbl_msg;
|
||||
u32 metric_dram_msg;
|
||||
};
|
||||
|
||||
static const struct hsmp_plat_desc hsmp_desc_server = {
|
||||
.msg_desc = hsmp_msg_desc_table,
|
||||
.num_msgs = HSMP_MSG_ID_MAX,
|
||||
.test_msg = HSMP_TEST,
|
||||
.proto_ver_msg = HSMP_GET_PROTO_VER,
|
||||
.metric_tbl_msg = HSMP_GET_METRIC_TABLE,
|
||||
.metric_dram_msg = HSMP_GET_METRIC_TABLE_DRAM_ADDR,
|
||||
};
|
||||
|
||||
/*
|
||||
* The client drives a different mailbox with the Ryzen Master SMC message
|
||||
* set. It has no per-message descriptor table: num_args/response_sz are
|
||||
* bounded generically instead (see validate_message()), and an unsupported
|
||||
* or malformed msg_id is rejected by firmware with its own SMU status code.
|
||||
*/
|
||||
static const struct hsmp_plat_desc hsmp_desc_client = {
|
||||
.msg_desc = NULL,
|
||||
.num_msgs = HSMP_CLIENT_MSG_ID_MAX,
|
||||
.test_msg = HSMP_CLIENT_TEST,
|
||||
.proto_ver_msg = HSMP_CLIENT_GET_INTERFACE_VER,
|
||||
.metric_tbl_msg = HSMP_CLIENT_GET_METRICS_TABLE,
|
||||
.metric_dram_msg = HSMP_CLIENT_GET_METRICS_TABLE_DRAM_ADDR,
|
||||
};
|
||||
|
||||
static struct hsmp_plat_device hsmp_pdev;
|
||||
|
||||
/* Cached on first use; READ_ONCE()/WRITE_ONCE() avoid a torn access */
|
||||
static const struct hsmp_plat_desc *hsmp_desc_cache;
|
||||
|
||||
static const struct hsmp_plat_desc *hsmp_desc(void)
|
||||
{
|
||||
const struct hsmp_plat_desc *desc = READ_ONCE(hsmp_desc_cache);
|
||||
|
||||
if (likely(desc))
|
||||
return desc;
|
||||
|
||||
desc = is_client_platform() ? &hsmp_desc_client : &hsmp_desc_server;
|
||||
WRITE_ONCE(hsmp_desc_cache, desc);
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
/* Returns NULL if the platform has no descriptor table, or msg_id is out of range or reserved */
|
||||
static const struct hsmp_msg_desc *get_msg_desc(u32 msg_id)
|
||||
{
|
||||
const struct hsmp_plat_desc *desc = hsmp_desc();
|
||||
|
||||
if (!desc->msg_desc)
|
||||
return NULL;
|
||||
|
||||
if (msg_id < desc->test_msg || msg_id >= desc->num_msgs)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Sanitize msg_id here rather than trusting a caller-side clamp: a
|
||||
* mispredicted branch upstream can still reach this function with
|
||||
* an unclamped msg_id (Spectre v1, CVE-2017-5753).
|
||||
*/
|
||||
msg_id = array_index_nospec(msg_id, desc->num_msgs);
|
||||
|
||||
if (desc->msg_desc[msg_id].type == HSMP_RSVD)
|
||||
return NULL;
|
||||
|
||||
return &desc->msg_desc[msg_id];
|
||||
}
|
||||
|
||||
/*
|
||||
* Response size for a message the driver issues directly, such as the
|
||||
* probe-time test, protocol-version and metric-table messages: the
|
||||
* descriptor table entry for server, since these fixed, well-known message
|
||||
* IDs always have one; the client protocol max otherwise, since the client
|
||||
* set has no descriptor table and firmware reports no response length.
|
||||
* Callers only consume the response words they expect.
|
||||
*/
|
||||
static u32 hsmp_msg_response_sz(u32 msg_id)
|
||||
{
|
||||
const struct hsmp_msg_desc *desc = get_msg_desc(msg_id);
|
||||
|
||||
return desc ? desc->response_sz : HSMP_CLIENT_MAX_MSG_LEN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gates the AMD HSMP data plane against socket bring-up and teardown.
|
||||
*
|
||||
|
|
@ -184,30 +279,46 @@ static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *ms
|
|||
|
||||
static int validate_message(struct hsmp_message *msg)
|
||||
{
|
||||
/* msg_id against valid range of message IDs */
|
||||
if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
|
||||
return -ENOMSG;
|
||||
const struct hsmp_plat_desc *plat_desc = hsmp_desc();
|
||||
const struct hsmp_msg_desc *desc;
|
||||
|
||||
/* msg_id is a reserved message ID */
|
||||
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
|
||||
if (!plat_desc->msg_desc) {
|
||||
/*
|
||||
* The client message set has no per-message descriptor
|
||||
* table and no msg_id range to validate against; cap
|
||||
* num_args/response_sz to what the Ryzen Master mailbox
|
||||
* supports in each direction and leave msg_id correctness,
|
||||
* such as an unsupported or out-of-range value, to the SMU
|
||||
* status code __hsmp_send_message() already translates.
|
||||
*/
|
||||
if (msg->num_args > HSMP_CLIENT_MAX_MSG_LEN ||
|
||||
msg->response_sz > HSMP_CLIENT_MAX_MSG_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Out-of-range or reserved message ID for this platform */
|
||||
desc = get_msg_desc(msg->msg_id);
|
||||
if (!desc)
|
||||
return -ENOMSG;
|
||||
|
||||
/*
|
||||
* num_args passed by user should match the num_args specified in
|
||||
* message description table.
|
||||
*/
|
||||
if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args)
|
||||
if (msg->num_args != desc->num_args)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* words than what the current descriptor table defines are still
|
||||
* accepted, while rejecting requests that exceed the hardware
|
||||
* capability.
|
||||
*/
|
||||
if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
|
||||
if (msg->response_sz > desc->response_sz)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
|
|
@ -316,7 +427,7 @@ int hsmp_test(u16 sock_ind, u32 value)
|
|||
* Test the hsmp port by performing TEST command. The test message
|
||||
* takes one argument and returns the value of that argument + 1.
|
||||
*/
|
||||
msg.msg_id = HSMP_TEST;
|
||||
msg.msg_id = hsmp_desc()->test_msg;
|
||||
msg.num_args = 1;
|
||||
msg.response_sz = 1;
|
||||
msg.args[0] = value;
|
||||
|
|
@ -338,13 +449,12 @@ int hsmp_test(u16 sock_ind, u32 value)
|
|||
}
|
||||
EXPORT_SYMBOL_NS_GPL(hsmp_test, "AMD_HSMP");
|
||||
|
||||
static bool is_get_msg(struct hsmp_message *msg)
|
||||
static bool is_get_msg(const struct hsmp_msg_desc *desc, struct hsmp_message *msg)
|
||||
{
|
||||
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_GET)
|
||||
if (desc->type == HSMP_GET)
|
||||
return true;
|
||||
|
||||
if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET &&
|
||||
(msg->args[0] & CHECK_GET_BIT))
|
||||
if (desc->type == HSMP_SET_GET && (msg->args[0] & CHECK_GET_BIT))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
|
@ -354,63 +464,65 @@ static long hsmp_ioctl_msg(struct file *fp, unsigned long arg)
|
|||
{
|
||||
int __user *arguser = (int __user *)arg;
|
||||
struct hsmp_message msg = { 0 };
|
||||
const struct hsmp_plat_desc *plat_desc = hsmp_desc();
|
||||
const struct hsmp_msg_desc *desc = NULL;
|
||||
int ret;
|
||||
|
||||
if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Check msg_id is within the range of supported msg ids
|
||||
* i.e within the array bounds of hsmp_msg_desc_table
|
||||
* The client message set has no per-message descriptor table to
|
||||
* bounds-check msg_id against or index into, and no per-message
|
||||
* type to gate fd mode on, so none of that applies here.
|
||||
* validate_message() (called via hsmp_send_message() below) still
|
||||
* bounds num_args/response_sz generically, and firmware is the
|
||||
* final arbiter of msg_id validity via its own response status.
|
||||
*/
|
||||
if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
|
||||
return -ENOMSG;
|
||||
if (plat_desc->msg_desc) {
|
||||
/* Rejects out-of-range and reserved msg_ids */
|
||||
desc = get_msg_desc(msg.msg_id);
|
||||
if (!desc)
|
||||
return -ENOMSG;
|
||||
|
||||
/*
|
||||
* Sanitize the user-controlled msg_id against speculative
|
||||
* execution. The bounds check above retires the out-of-range
|
||||
* case with -ENOMSG, but a mispredicted branch can still let the
|
||||
* CPU speculatively use msg_id as an index into
|
||||
* hsmp_msg_desc_table[] (here and in validate_message() /
|
||||
* is_get_msg() called downstream via hsmp_send_message()), and
|
||||
* pull arbitrary kernel memory into the cache (Spectre v1,
|
||||
* CVE-2017-5753). Clamp once into msg.msg_id so every downstream
|
||||
* dereference sees the sanitized value.
|
||||
*/
|
||||
msg.msg_id = array_index_nospec(msg.msg_id, HSMP_MSG_ID_MAX);
|
||||
|
||||
switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
|
||||
case FMODE_WRITE:
|
||||
/*
|
||||
* Device is opened in O_WRONLY mode
|
||||
* Execute only set/configure commands
|
||||
*/
|
||||
if (is_get_msg(&msg))
|
||||
switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
|
||||
case FMODE_WRITE:
|
||||
/*
|
||||
* Device is opened in O_WRONLY mode
|
||||
* Execute only set/configure commands.
|
||||
*/
|
||||
if (is_get_msg(desc, &msg))
|
||||
return -EPERM;
|
||||
break;
|
||||
case FMODE_READ:
|
||||
/*
|
||||
* Device is opened in O_RDONLY mode
|
||||
* Execute only get/monitor commands.
|
||||
*/
|
||||
if (!is_get_msg(desc, &msg))
|
||||
return -EPERM;
|
||||
break;
|
||||
case FMODE_READ | FMODE_WRITE:
|
||||
/*
|
||||
* Device is opened in O_RDWR mode
|
||||
* Execute both get/monitor and set/configure commands
|
||||
*/
|
||||
break;
|
||||
default:
|
||||
return -EPERM;
|
||||
break;
|
||||
case FMODE_READ:
|
||||
/*
|
||||
* Device is opened in O_RDONLY mode
|
||||
* Execute only get/monitor commands
|
||||
*/
|
||||
if (!is_get_msg(&msg))
|
||||
return -EPERM;
|
||||
break;
|
||||
case FMODE_READ | FMODE_WRITE:
|
||||
/*
|
||||
* Device is opened in O_RDWR mode
|
||||
* Execute both get/monitor and set/configure commands
|
||||
*/
|
||||
break;
|
||||
default:
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
|
||||
ret = hsmp_send_message(&msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
|
||||
/*
|
||||
* The client message set has no per-message response_sz to check
|
||||
* here (desc is NULL); validate_message() already bounded
|
||||
* msg.response_sz.
|
||||
*/
|
||||
if ((desc ? desc->response_sz : msg.response_sz) > 0) {
|
||||
/* Copy results back to user for get/monitor commands */
|
||||
if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
|
||||
return -EFAULT;
|
||||
|
|
@ -565,11 +677,11 @@ static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg.msg_id = HSMP_GET_METRIC_TABLE;
|
||||
msg.msg_id = hsmp_desc()->metric_tbl_msg;
|
||||
msg.sock_ind = sock->sock_ind;
|
||||
|
||||
/*
|
||||
* HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared
|
||||
* The metric table message 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.
|
||||
|
|
@ -635,8 +747,8 @@ int hsmp_get_tbl_dram_base(u16 sock_ind)
|
|||
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;
|
||||
msg.msg_id = hsmp_desc()->metric_dram_msg;
|
||||
msg.response_sz = hsmp_msg_response_sz(msg.msg_id);
|
||||
|
||||
ret = hsmp_send_message_locked(&msg);
|
||||
if (ret)
|
||||
|
|
@ -687,9 +799,9 @@ int hsmp_cache_proto_ver(u16 sock_ind)
|
|||
struct hsmp_message msg = { 0 };
|
||||
int ret;
|
||||
|
||||
msg.msg_id = HSMP_GET_PROTO_VER;
|
||||
msg.msg_id = hsmp_desc()->proto_ver_msg;
|
||||
msg.response_sz = hsmp_msg_response_sz(msg.msg_id);
|
||||
msg.sock_ind = sock_ind;
|
||||
msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
|
||||
|
||||
ret = hsmp_send_message_locked(&msg);
|
||||
if (!ret)
|
||||
|
|
@ -720,7 +832,13 @@ int hsmp_misc_register(struct device *dev)
|
|||
*/
|
||||
hsmp_pdev.mdev.parent = dev;
|
||||
hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME;
|
||||
hsmp_pdev.mdev.mode = 0644;
|
||||
/*
|
||||
* hsmp_msg_desc_table[] only describes server messages. On client
|
||||
* platforms, msg_id mappings differ, making access checks unreliable.
|
||||
* Restrict /dev/hsmp to root until client-specific validation is
|
||||
* added.
|
||||
*/
|
||||
hsmp_pdev.mdev.mode = is_client_platform() ? 0600 : 0644;
|
||||
|
||||
return misc_register(&hsmp_pdev.mdev);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@
|
|||
#ifndef HSMP_H
|
||||
#define HSMP_H
|
||||
|
||||
#include <asm/amd/hsmp.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/compiler_types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/hwmon.h>
|
||||
|
|
@ -17,6 +20,7 @@
|
|||
#include <linux/miscdevice.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/processor.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
|
@ -32,6 +36,26 @@
|
|||
|
||||
#define DRIVER_VERSION "2.6"
|
||||
|
||||
/* True when the ACPI-reported PM profile indicates a client platform */
|
||||
static inline bool is_client_platform(void)
|
||||
{
|
||||
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
|
||||
return false;
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI)
|
||||
switch (acpi_gbl_FADT.preferred_profile) {
|
||||
case PM_DESKTOP:
|
||||
case PM_MOBILE:
|
||||
case PM_TABLET:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct hsmp_mbaddr_info {
|
||||
u32 base_addr;
|
||||
u32 msg_id_off;
|
||||
|
|
|
|||
|
|
@ -143,6 +143,14 @@ static const struct dmi_system_id fwbug_list[] = {
|
|||
DMI_MATCH(DMI_BOARD_NAME, "RC73YA"),
|
||||
}
|
||||
},
|
||||
{
|
||||
.ident = "ASUS ROG Flow Z13 GZ302EA",
|
||||
.driver_data = &quirk_spurious_8042,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GZ302EA"),
|
||||
}
|
||||
},
|
||||
/* https://bugzilla.kernel.org/show_bug.cgi?id=218024 */
|
||||
{
|
||||
.ident = "V14 G4 AMN",
|
||||
|
|
@ -254,6 +262,15 @@ static const struct dmi_system_id fwbug_list[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_NAME, "21C6"),
|
||||
}
|
||||
},
|
||||
/* https://bugzilla.kernel.org/show_bug.cgi?id=221273 */
|
||||
{
|
||||
.ident = "Thinkpad L14 Gen3",
|
||||
.driver_data = &quirk_s2idle_bug,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "21C5"),
|
||||
}
|
||||
},
|
||||
/* https://gitlab.freedesktop.org/drm/amd/-/issues/4434 */
|
||||
{
|
||||
.ident = "Lenovo Yoga 6 13ALC6",
|
||||
|
|
@ -280,6 +297,15 @@ static const struct dmi_system_id fwbug_list[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_NAME, "83N1"),
|
||||
}
|
||||
},
|
||||
{
|
||||
.ident = "HP Laptop 14-fq1xxx",
|
||||
.driver_data = &quirk_s2idle_spurious_8042,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "HP Laptop 14-fq1xxx"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "887C"),
|
||||
}
|
||||
},
|
||||
/* https://gitlab.freedesktop.org/drm/amd/-/issues/2684 */
|
||||
{
|
||||
.ident = "HP Laptop 15s-eq2xxx",
|
||||
|
|
|
|||
|
|
@ -182,7 +182,6 @@ static const struct amd_pmc_cpu_info amd_1ah_m80_cpu_info = {
|
|||
.smu_arg = AMD_PMC_REGISTER_ARG_1AH_80H,
|
||||
.smu_rsp = AMD_PMC_REGISTER_RSP_1AH_80H,
|
||||
.num_ips = ARRAY_SIZE(soc15_ip_blk_v3),
|
||||
.scratch_reg = AMD_PMC_SCRATCH_REG_1AH,
|
||||
.ips_ptr = soc15_ip_blk_v3,
|
||||
.os_hint = MSG_OS_HINT_RN,
|
||||
};
|
||||
|
|
@ -199,6 +198,7 @@ static const struct pci_device_id pmc_pci_ids[] = {
|
|||
{ PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, NULL) },
|
||||
{ PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, NULL) },
|
||||
{ PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_1ah_m80_cpu_info) },
|
||||
{ PCI_DEVICE_DATA(AMD, 1AH_MDCH_ROOT, &amd_1ah_m80_cpu_info) },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
@ -517,8 +517,9 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
|
|||
&smu_fw_info_fops);
|
||||
debugfs_create_file("s0ix_stats", 0644, dev->dbgfs_dir, dev,
|
||||
&s0ix_stats_fops);
|
||||
debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev,
|
||||
&amd_pmc_idlemask_fops);
|
||||
if (dev->cpu_info->scratch_reg)
|
||||
debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev,
|
||||
&amd_pmc_idlemask_fops);
|
||||
}
|
||||
|
||||
static char *amd_pmc_get_msg_port(struct amd_pmc_dev *dev)
|
||||
|
|
@ -701,19 +702,6 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static bool amd_pmc_intermediate_wakeup_need_delay(struct amd_pmc_dev *pdev)
|
||||
{
|
||||
/*
|
||||
* Starting a new HW sleep cycle right after waking from one
|
||||
* can cause electrical problems triggering the over voltage protection.
|
||||
* That is avoided by delaying the next suspend a bit, see also
|
||||
* https://lore.kernel.org/all/20250414162446.3853194-1-superm1@kernel.org/
|
||||
*/
|
||||
struct smu_metrics table;
|
||||
|
||||
return get_metrics_table(pdev, &table) == 0 && table.s0i3_last_entry_status;
|
||||
}
|
||||
|
||||
static bool amd_pmc_want_suspend_delay(struct amd_pmc_dev *pdev)
|
||||
{
|
||||
/*
|
||||
|
|
@ -804,7 +792,7 @@ static void amd_pmc_s2idle_check(void)
|
|||
struct amd_pmc_dev *pdev = &pmc;
|
||||
int rc;
|
||||
|
||||
if (amd_pmc_intermediate_wakeup_need_delay(pdev) ||
|
||||
if (!pdev->is_first_check_after_suspend ||
|
||||
amd_pmc_want_suspend_delay(pdev))
|
||||
msleep(2500);
|
||||
|
||||
|
|
@ -970,6 +958,10 @@ static void amd_pmc_remove(struct platform_device *pdev)
|
|||
pci_dev_put(dev->rdev);
|
||||
if (IS_ENABLED(CONFIG_AMD_MP2_STB))
|
||||
amd_mp2_stb_deinit(dev);
|
||||
dev->regbase = NULL;
|
||||
dev->smu_virt_addr = NULL;
|
||||
dev->stb_virt_addr = NULL;
|
||||
dev->fch_virt_addr = NULL;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id amd_pmc_acpi_ids[] = {
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ void amd_mp2_stb_deinit(struct amd_pmc_dev *dev);
|
|||
#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
|
||||
#define PCI_DEVICE_ID_AMD_1AH_MDCH_ROOT 0x1b20
|
||||
#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c
|
||||
|
||||
int amd_stb_s2d_init(struct amd_pmc_dev *dev);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ static union acpi_object *apmf_if_call(struct amd_pmf_dev *pdev, int fn, struct
|
|||
return buffer.pointer;
|
||||
}
|
||||
|
||||
static int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz)
|
||||
int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz)
|
||||
{
|
||||
union acpi_object *info;
|
||||
size_t size;
|
||||
|
|
@ -121,8 +121,7 @@ static union acpi_object *apts_if_call(struct amd_pmf_dev *pdev, u32 state_index
|
|||
return buffer.pointer;
|
||||
}
|
||||
|
||||
static int apts_if_call_store_buffer(struct amd_pmf_dev *pdev,
|
||||
u32 index, void *data, size_t out_sz)
|
||||
int apts_if_call_store_buffer(struct amd_pmf_dev *pdev, u32 index, void *data, size_t out_sz)
|
||||
{
|
||||
union acpi_object *info;
|
||||
size_t size;
|
||||
|
|
@ -158,46 +157,6 @@ static int apts_if_call_store_buffer(struct amd_pmf_dev *pdev,
|
|||
return err;
|
||||
}
|
||||
|
||||
int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index)
|
||||
{
|
||||
/* If bit-n is set, that indicates function n+1 is supported */
|
||||
return !!(pdev->supported_func & BIT(index - 1));
|
||||
}
|
||||
|
||||
int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS);
|
||||
}
|
||||
|
||||
int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
|
||||
struct amd_pmf_apts_granular_output *data, u32 apts_idx)
|
||||
{
|
||||
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
|
||||
return -EINVAL;
|
||||
|
||||
return apts_if_call_store_buffer(pdev, apts_idx, data, sizeof(*data));
|
||||
}
|
||||
|
||||
int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
|
||||
struct apmf_static_slider_granular_output_v2 *data)
|
||||
{
|
||||
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
|
||||
return -EINVAL;
|
||||
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
|
||||
data, sizeof(*data));
|
||||
}
|
||||
|
||||
int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
|
||||
struct apmf_static_slider_granular_output *data)
|
||||
{
|
||||
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
|
||||
return -EINVAL;
|
||||
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
|
||||
data, sizeof(*data));
|
||||
}
|
||||
|
||||
int apmf_os_power_slider_update(struct amd_pmf_dev *pdev, u8 event)
|
||||
{
|
||||
struct os_power_slider args;
|
||||
|
|
@ -313,27 +272,6 @@ static int apmf_notify_smart_pc_update(struct amd_pmf_dev *pdev, u32 val, u32 pr
|
|||
return 0;
|
||||
}
|
||||
|
||||
int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data));
|
||||
}
|
||||
|
||||
int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
|
||||
}
|
||||
|
||||
int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
|
||||
}
|
||||
|
||||
int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS,
|
||||
req, sizeof(*req));
|
||||
}
|
||||
|
||||
/* Store custom BIOS inputs data in ring buffer */
|
||||
static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev)
|
||||
{
|
||||
|
|
@ -495,16 +433,6 @@ static int apmf_get_system_params(struct amd_pmf_dev *dev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_AC, data, sizeof(*data));
|
||||
}
|
||||
|
||||
int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data));
|
||||
}
|
||||
|
||||
static apmf_event_handler_t apmf_event_handlers[] = {
|
||||
[PMF_IF_V1] = apmf_event_handler_v1,
|
||||
[PMF_IF_V2] = apmf_event_handler_v2,
|
||||
|
|
|
|||
|
|
@ -138,14 +138,6 @@ static void amd_pmf_dbgfs_register(struct amd_pmf_dev *dev)
|
|||
¤t_power_limits_fops);
|
||||
}
|
||||
|
||||
int amd_pmf_get_power_source(void)
|
||||
{
|
||||
if (power_supply_is_system_supplied() > 0)
|
||||
return POWER_SOURCE_AC;
|
||||
else
|
||||
return POWER_SOURCE_DC;
|
||||
}
|
||||
|
||||
static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
|
||||
{
|
||||
return ioread32(dev->regbase + reg_offset);
|
||||
|
|
@ -176,20 +168,6 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
|
|||
dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* fixp_q88_fromint: Convert integer to Q8.8
|
||||
* @val: input value
|
||||
*
|
||||
* Converts an integer into binary fixed point format where 8 bits
|
||||
* are used for integer and 8 bits are used for the decimal.
|
||||
*
|
||||
* Return: unsigned integer converted to Q8.8 format
|
||||
*/
|
||||
u32 fixp_q88_fromint(u32 val)
|
||||
{
|
||||
return val << 8;
|
||||
}
|
||||
|
||||
int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data)
|
||||
{
|
||||
int rc;
|
||||
|
|
@ -454,6 +432,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = {
|
|||
{"AMDI0107", 0},
|
||||
{"AMDI0108", 0},
|
||||
{"AMDI0109", 0},
|
||||
{"AMDI0112", 0},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/amd-pmf-io.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/compiler_types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/mutex_types.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_profile.h>
|
||||
#include <linux/types.h>
|
||||
|
|
@ -1073,18 +1076,49 @@ struct ta_pmf_shared_memory {
|
|||
/* Core Layer */
|
||||
int apmf_acpi_init(struct amd_pmf_dev *pmf_dev);
|
||||
void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev);
|
||||
int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index);
|
||||
int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz);
|
||||
int apts_if_call_store_buffer(struct amd_pmf_dev *pdev, u32 index, void *data, size_t out_sz);
|
||||
int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data);
|
||||
int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev);
|
||||
int amd_pmf_get_power_source(void);
|
||||
int apmf_install_handler(struct amd_pmf_dev *pmf_dev);
|
||||
int apmf_os_power_slider_update(struct amd_pmf_dev *dev, u8 flag);
|
||||
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);
|
||||
|
||||
static inline int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index)
|
||||
{
|
||||
/* If bit-n is set, that indicates function n+1 is supported */
|
||||
return !!(pdev->supported_func & BIT(index - 1));
|
||||
}
|
||||
|
||||
static inline int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev)
|
||||
{
|
||||
return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS);
|
||||
}
|
||||
|
||||
static inline int amd_pmf_get_power_source(void)
|
||||
{
|
||||
if (power_supply_is_system_supplied() > 0)
|
||||
return POWER_SOURCE_AC;
|
||||
else
|
||||
return POWER_SOURCE_DC;
|
||||
}
|
||||
|
||||
/**
|
||||
* fixp_q88_fromint: Convert integer to Q8.8
|
||||
* @val: input value
|
||||
*
|
||||
* Converts an integer into binary fixed point format where 8 bits
|
||||
* are used for integer and 8 bits are used for the decimal.
|
||||
*
|
||||
* Return: unsigned integer converted to Q8.8 format
|
||||
*/
|
||||
static inline u32 fixp_q88_fromint(u32 val)
|
||||
{
|
||||
return val << 8;
|
||||
}
|
||||
|
||||
/* Metrics layer */
|
||||
int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev);
|
||||
|
||||
|
|
@ -1093,42 +1127,100 @@ int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
|
|||
void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
|
||||
struct amd_pmf_static_slider_granular *table);
|
||||
int amd_pmf_init_sps(struct amd_pmf_dev *dev);
|
||||
int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
|
||||
struct apmf_static_slider_granular_output *output);
|
||||
bool is_pprof_balanced(struct amd_pmf_dev *pmf);
|
||||
int amd_pmf_power_slider_update_event(struct amd_pmf_dev *dev);
|
||||
const char *amd_pmf_source_as_str(unsigned int state);
|
||||
|
||||
const char *amd_pmf_source_as_str(unsigned int state);
|
||||
|
||||
int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx);
|
||||
int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf);
|
||||
int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *dev,
|
||||
struct apmf_static_slider_granular_output_v2 *data);
|
||||
int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
|
||||
struct amd_pmf_apts_granular_output *data, u32 apts_idx);
|
||||
|
||||
static inline int
|
||||
apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
|
||||
struct amd_pmf_apts_granular_output *data, u32 apts_idx)
|
||||
{
|
||||
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
|
||||
return -EINVAL;
|
||||
|
||||
return apts_if_call_store_buffer(pdev, apts_idx, data, sizeof(*data));
|
||||
}
|
||||
|
||||
static inline int
|
||||
apmf_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
|
||||
struct apmf_static_slider_granular_output_v2 *data)
|
||||
{
|
||||
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
|
||||
return -EINVAL;
|
||||
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
|
||||
data, sizeof(*data));
|
||||
}
|
||||
|
||||
static inline int
|
||||
apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
|
||||
struct apmf_static_slider_granular_output *data)
|
||||
{
|
||||
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
|
||||
return -EINVAL;
|
||||
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
|
||||
data, sizeof(*data));
|
||||
}
|
||||
|
||||
static inline bool is_pprof_balanced(struct amd_pmf_dev *pmf)
|
||||
{
|
||||
return pmf->current_profile == PLATFORM_PROFILE_BALANCED;
|
||||
}
|
||||
|
||||
/* Auto Mode Layer */
|
||||
int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data);
|
||||
void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev);
|
||||
void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev);
|
||||
void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms);
|
||||
int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req);
|
||||
int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req);
|
||||
int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req);
|
||||
|
||||
void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event);
|
||||
int amd_pmf_reset_amt(struct amd_pmf_dev *dev);
|
||||
void amd_pmf_handle_amt(struct amd_pmf_dev *dev);
|
||||
|
||||
static inline int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data));
|
||||
}
|
||||
|
||||
static inline int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev,
|
||||
struct apmf_sbios_req_v2 *req)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
|
||||
}
|
||||
|
||||
static inline int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev,
|
||||
struct apmf_sbios_req_v1 *req)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
|
||||
}
|
||||
|
||||
static inline int apmf_get_sbios_requests(struct amd_pmf_dev *pdev,
|
||||
struct apmf_sbios_req *req)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS,
|
||||
req, sizeof(*req));
|
||||
}
|
||||
|
||||
/* CnQF Layer */
|
||||
int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data);
|
||||
int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data);
|
||||
int amd_pmf_init_cnqf(struct amd_pmf_dev *dev);
|
||||
void amd_pmf_deinit_cnqf(struct amd_pmf_dev *dev);
|
||||
int amd_pmf_trans_cnqf(struct amd_pmf_dev *dev, int socket_power, ktime_t time_lapsed_ms);
|
||||
extern const struct attribute_group cnqf_feature_attribute_group;
|
||||
|
||||
static inline int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev,
|
||||
struct apmf_dyn_slider_output *data)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_AC, data, sizeof(*data));
|
||||
}
|
||||
|
||||
static inline int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev,
|
||||
struct apmf_dyn_slider_output *data)
|
||||
{
|
||||
return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data));
|
||||
}
|
||||
|
||||
/* Smart PC builder Layer */
|
||||
int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev);
|
||||
void amd_pmf_deinit_smart_pc(struct amd_pmf_dev *dev);
|
||||
|
|
@ -1137,9 +1229,25 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq
|
|||
|
||||
/* Smart PC - TA interfaces */
|
||||
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);
|
||||
|
||||
static inline u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
|
||||
{
|
||||
switch (index) {
|
||||
case 0 ... 1:
|
||||
return in->ev_info.bios_input_1[index];
|
||||
case 2 ... 9:
|
||||
return in->ev_info.bios_input_2[index - 2];
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_AMD_PMF_DEBUG
|
||||
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
|
||||
#else
|
||||
static inline void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
|
||||
#endif
|
||||
|
||||
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
|
||||
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
|
||||
|
|
|
|||
|
|
@ -17,19 +17,6 @@
|
|||
#include <linux/units.h>
|
||||
#include "pmf.h"
|
||||
|
||||
u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
|
||||
{
|
||||
switch (index) {
|
||||
case 0 ... 1:
|
||||
return in->ev_info.bios_input_1[index];
|
||||
case 2 ... 9:
|
||||
return in->ev_info.bios_input_2[index - 2];
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs);
|
||||
|
||||
#ifdef CONFIG_AMD_PMF_DEBUG
|
||||
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
||||
{
|
||||
|
|
@ -60,8 +47,6 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *
|
|||
amd_pmf_get_ta_custom_bios_inputs(in, i));
|
||||
dev_dbg(dev->dev, "==== TA inputs END ====\n");
|
||||
}
|
||||
#else
|
||||
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
|
@ -199,13 +184,11 @@ static int amd_pmf_get_battery_prop(enum power_supply_property prop)
|
|||
continue;
|
||||
|
||||
ret = power_supply_get_property(psy, prop, &value);
|
||||
if (ret) {
|
||||
power_supply_put(psy);
|
||||
return ret;
|
||||
}
|
||||
power_supply_put(psy);
|
||||
return ret ? ret : value.intval;
|
||||
}
|
||||
|
||||
return value.intval;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int amd_pmf_get_battery_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
|
||||
|
|
|
|||
|
|
@ -248,20 +248,24 @@ static int amd_pmf_update_sps_power_limits_v2(struct amd_pmf_dev *pdev, int pwr_
|
|||
switch (pwr_mode) {
|
||||
case POWER_MODE_PERFORMANCE:
|
||||
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BEST_PERFORMANCE];
|
||||
amd_pmf_update_slider_v2(pdev, index);
|
||||
break;
|
||||
case POWER_MODE_BALANCED_POWER:
|
||||
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BALANCED];
|
||||
amd_pmf_update_slider_v2(pdev, index);
|
||||
break;
|
||||
case POWER_MODE_POWER_SAVER:
|
||||
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BEST_POWER_EFFICIENCY];
|
||||
amd_pmf_update_slider_v2(pdev, index);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (index >= APTS_MAX_STATES) {
|
||||
dev_err(pdev->dev, "Invalid APTS index: %d\n", index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
amd_pmf_update_slider_v2(pdev, index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -281,11 +285,6 @@ int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool is_pprof_balanced(struct amd_pmf_dev *pmf)
|
||||
{
|
||||
return pmf->current_profile == PLATFORM_PROFILE_BALANCED;
|
||||
}
|
||||
|
||||
static int amd_pmf_profile_get(struct device *dev,
|
||||
enum platform_profile_option *profile)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ static const struct file_operations pmf_if_ops = {
|
|||
.owner = THIS_MODULE,
|
||||
.open = amd_pmf_open,
|
||||
.unlocked_ioctl = amd_pmf_set_ioctl,
|
||||
.compat_ioctl = compat_ptr_ioctl,
|
||||
};
|
||||
|
||||
static struct miscdevice amd_pmf_util_if = {
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ struct asus_armoury_priv {
|
|||
|
||||
u32 mini_led_dev_id;
|
||||
u32 gpu_mux_dev_id;
|
||||
u32 dgpu_disable_dev_id;
|
||||
|
||||
bool requires_fan_curve;
|
||||
};
|
||||
|
|
@ -110,11 +111,6 @@ static struct fw_attrs_group fw_attrs = {
|
|||
.pending_reboot = false,
|
||||
};
|
||||
|
||||
struct asus_attr_group {
|
||||
const struct attribute_group *attr_group;
|
||||
u32 wmi_devid;
|
||||
};
|
||||
|
||||
static void asus_set_reboot_and_signal_event(void)
|
||||
{
|
||||
fw_attrs.pending_reboot = true;
|
||||
|
|
@ -458,6 +454,12 @@ static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj,
|
|||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mini_led_mode_group_visible(struct kobject *kobj)
|
||||
{
|
||||
return asus_armoury.mini_led_dev_id;
|
||||
}
|
||||
|
||||
ASUS_ATTR_GROUP_ENUM(mini_led_mode, "mini_led_mode", "Set the mini-LED backlight mode");
|
||||
|
||||
static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj,
|
||||
|
|
@ -471,8 +473,8 @@ static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj,
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU)) {
|
||||
err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_DGPU);
|
||||
if (asus_armoury.dgpu_disable_dev_id) {
|
||||
err = armoury_get_devstate(NULL, &result, asus_armoury.dgpu_disable_dev_id);
|
||||
if (err)
|
||||
return err;
|
||||
if (result && !optimus) {
|
||||
|
|
@ -502,6 +504,12 @@ static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj,
|
|||
return count;
|
||||
}
|
||||
ASUS_WMI_SHOW_INT(gpu_mux_mode_current_value, asus_armoury.gpu_mux_dev_id);
|
||||
|
||||
static bool gpu_mux_mode_group_visible(struct kobject *kobj)
|
||||
{
|
||||
return asus_armoury.gpu_mux_dev_id;
|
||||
}
|
||||
|
||||
ASUS_ATTR_GROUP_BOOL(gpu_mux_mode, "gpu_mux_mode", "Set the GPU display MUX mode");
|
||||
|
||||
static ssize_t dgpu_disable_current_value_store(struct kobject *kobj,
|
||||
|
|
@ -515,18 +523,31 @@ static ssize_t dgpu_disable_current_value_store(struct kobject *kobj,
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
if (asus_armoury.gpu_mux_dev_id) {
|
||||
err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id);
|
||||
if (err)
|
||||
return err;
|
||||
if (!result && disable) {
|
||||
pr_warn("Cannot disable dGPU when the MUX is in dGPU mode\n");
|
||||
return -EBUSY;
|
||||
if (disable) {
|
||||
if (asus_armoury.gpu_mux_dev_id) {
|
||||
err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id);
|
||||
if (err)
|
||||
return err;
|
||||
if (!result) {
|
||||
pr_warn("Cannot disable dGPU when the MUX is in dGPU mode\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
|
||||
if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU_POWER_STATE)) {
|
||||
err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_DGPU_POWER_STATE);
|
||||
if (err)
|
||||
return err;
|
||||
if (result) {
|
||||
pr_warn("Cannot disable dGPU when it is in use\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scoped_guard(mutex, &asus_armoury.egpu_mutex) {
|
||||
err = armoury_set_devstate(attr, disable ? 1 : 0, NULL, ASUS_WMI_DEVID_DGPU);
|
||||
err = armoury_set_devstate(attr, disable ? 1 : 0, NULL,
|
||||
asus_armoury.dgpu_disable_dev_id);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
|
@ -535,7 +556,13 @@ static ssize_t dgpu_disable_current_value_store(struct kobject *kobj,
|
|||
|
||||
return count;
|
||||
}
|
||||
ASUS_WMI_SHOW_INT(dgpu_disable_current_value, ASUS_WMI_DEVID_DGPU);
|
||||
|
||||
static bool dgpu_disable_group_visible(struct kobject *kobj)
|
||||
{
|
||||
return asus_armoury.dgpu_disable_dev_id;
|
||||
}
|
||||
|
||||
ASUS_WMI_SHOW_INT(dgpu_disable_current_value, asus_armoury.dgpu_disable_dev_id);
|
||||
ASUS_ATTR_GROUP_BOOL(dgpu_disable, "dgpu_disable", "Disable the dGPU");
|
||||
|
||||
/* Values map for eGPU activation requests. */
|
||||
|
|
@ -683,6 +710,12 @@ static ssize_t egpu_enable_possible_values_show(struct kobject *kobj, struct kob
|
|||
{
|
||||
return armoury_attr_enum_list(buf, ARRAY_SIZE(egpu_status_map));
|
||||
}
|
||||
|
||||
static bool egpu_enable_group_visible(struct kobject *kobj)
|
||||
{
|
||||
return armoury_has_devstate(ASUS_WMI_DEVID_EGPU);
|
||||
}
|
||||
|
||||
ASUS_ATTR_GROUP_ENUM(egpu_enable, "egpu_enable", "Enable the eGPU (also disables dGPU)");
|
||||
|
||||
/* Device memory available to APU */
|
||||
|
|
@ -759,6 +792,12 @@ static ssize_t apu_mem_possible_values_show(struct kobject *kobj, struct kobj_at
|
|||
{
|
||||
return armoury_attr_enum_list(buf, ARRAY_SIZE(apu_mem_map));
|
||||
}
|
||||
|
||||
static bool apu_mem_group_visible(struct kobject *kobj)
|
||||
{
|
||||
return armoury_has_devstate(ASUS_WMI_DEVID_APU_MEM);
|
||||
}
|
||||
|
||||
ASUS_ATTR_GROUP_ENUM(apu_mem, "apu_mem", "Set available system RAM (in GB) for the APU to use");
|
||||
|
||||
/* Define helper to access the current power mode tunable values */
|
||||
|
|
@ -770,92 +809,6 @@ static inline struct rog_tunables *get_current_tunables(void)
|
|||
return asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC];
|
||||
}
|
||||
|
||||
/* Simple attribute creation */
|
||||
ASUS_ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2\n",
|
||||
"Show the current mode of charging");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND,
|
||||
"Set the boot POST sound");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE,
|
||||
"Set MCU powersaving mode");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD,
|
||||
"Set the panel refresh overdrive");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(panel_hd_mode, "panel_hd_mode", ASUS_WMI_DEVID_PANEL_HD,
|
||||
"Set the panel HD mode to UHD<0> or FHD<1>");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(screen_auto_brightness, "screen_auto_brightness",
|
||||
ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS,
|
||||
"Set the panel brightness to Off<0> or On<1>");
|
||||
ASUS_ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED,
|
||||
"Show the eGPU connection status");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, ATTR_PPT_PL1_SPL, ASUS_WMI_DEVID_PPT_PL1_SPL,
|
||||
"Set the CPU slow package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, ATTR_PPT_PL2_SPPT, ASUS_WMI_DEVID_PPT_PL2_SPPT,
|
||||
"Set the CPU fast package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl3_fppt, ATTR_PPT_PL3_FPPT, ASUS_WMI_DEVID_PPT_PL3_FPPT,
|
||||
"Set the CPU fastest package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, ATTR_PPT_APU_SPPT, ASUS_WMI_DEVID_PPT_APU_SPPT,
|
||||
"Set the APU package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, ATTR_PPT_PLATFORM_SPPT, ASUS_WMI_DEVID_PPT_PLAT_SPPT,
|
||||
"Set the platform package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, ATTR_NV_DYNAMIC_BOOST, ASUS_WMI_DEVID_NV_DYN_BOOST,
|
||||
"Set the Nvidia dynamic boost limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(nv_temp_target, ATTR_NV_TEMP_TARGET, ASUS_WMI_DEVID_NV_THERM_TARGET,
|
||||
"Set the Nvidia max thermal limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(nv_tgp, "nv_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP,
|
||||
"Set the additional TGP on top of the base TGP");
|
||||
ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(nv_base_tgp, ATTR_NV_BASE_TGP, ASUS_WMI_DEVID_DGPU_BASE_TGP,
|
||||
"Read the base TGP value");
|
||||
|
||||
/* If an attribute does not require any special case handling add it here */
|
||||
static const struct asus_attr_group armoury_attr_groups[] = {
|
||||
{ &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED },
|
||||
{ &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU },
|
||||
{ &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU },
|
||||
{ &apu_mem_attr_group, ASUS_WMI_DEVID_APU_MEM },
|
||||
|
||||
{ &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL },
|
||||
{ &ppt_pl2_sppt_attr_group, ASUS_WMI_DEVID_PPT_PL2_SPPT },
|
||||
{ &ppt_pl3_fppt_attr_group, ASUS_WMI_DEVID_PPT_PL3_FPPT },
|
||||
{ &ppt_apu_sppt_attr_group, ASUS_WMI_DEVID_PPT_APU_SPPT },
|
||||
{ &ppt_platform_sppt_attr_group, ASUS_WMI_DEVID_PPT_PLAT_SPPT },
|
||||
{ &nv_dynamic_boost_attr_group, ASUS_WMI_DEVID_NV_DYN_BOOST },
|
||||
{ &nv_temp_target_attr_group, ASUS_WMI_DEVID_NV_THERM_TARGET },
|
||||
{ &nv_base_tgp_attr_group, ASUS_WMI_DEVID_DGPU_BASE_TGP },
|
||||
{ &nv_tgp_attr_group, ASUS_WMI_DEVID_DGPU_SET_TGP },
|
||||
|
||||
{ &charge_mode_attr_group, ASUS_WMI_DEVID_CHARGE_MODE },
|
||||
{ &boot_sound_attr_group, ASUS_WMI_DEVID_BOOT_SOUND },
|
||||
{ &mcu_powersave_attr_group, ASUS_WMI_DEVID_MCU_POWERSAVE },
|
||||
{ &panel_od_attr_group, ASUS_WMI_DEVID_PANEL_OD },
|
||||
{ &panel_hd_mode_attr_group, ASUS_WMI_DEVID_PANEL_HD },
|
||||
{ &screen_auto_brightness_attr_group, ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS },
|
||||
};
|
||||
|
||||
/**
|
||||
* is_power_tunable_attr - Determines if an attribute is a power-related tunable
|
||||
* @name: The name of the attribute to check
|
||||
*
|
||||
* This function checks if the given attribute name is related to power tuning.
|
||||
*
|
||||
* Return: true if the attribute is a power-related tunable, false otherwise
|
||||
*/
|
||||
static bool is_power_tunable_attr(const char *name)
|
||||
{
|
||||
static const char * const power_tunable_attrs[] = {
|
||||
ATTR_PPT_PL1_SPL, ATTR_PPT_PL2_SPPT,
|
||||
ATTR_PPT_PL3_FPPT, ATTR_PPT_APU_SPPT,
|
||||
ATTR_PPT_PLATFORM_SPPT, ATTR_NV_DYNAMIC_BOOST,
|
||||
ATTR_NV_TEMP_TARGET, ATTR_NV_BASE_TGP,
|
||||
ATTR_NV_TGP
|
||||
};
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_SIZE(power_tunable_attrs); i++) {
|
||||
if (!strcmp(name, power_tunable_attrs[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* has_valid_limit - Checks if a power-related attribute has a valid limit value
|
||||
* @name: The name of the attribute to check
|
||||
|
|
@ -894,14 +847,74 @@ static bool has_valid_limit(const char *name, const struct power_limits *limits)
|
|||
return limit_value > 0;
|
||||
}
|
||||
|
||||
/* Simple attribute creation */
|
||||
ASUS_ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2\n",
|
||||
"Show the current mode of charging");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND,
|
||||
"Set the boot POST sound");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE,
|
||||
"Set MCU powersaving mode");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD,
|
||||
"Set the panel refresh overdrive");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(panel_hd_mode, "panel_hd_mode", ASUS_WMI_DEVID_PANEL_HD,
|
||||
"Set the panel HD mode to UHD<0> or FHD<1>");
|
||||
ASUS_ATTR_GROUP_BOOL_RW(screen_auto_brightness, "screen_auto_brightness",
|
||||
ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS,
|
||||
"Set the panel brightness to Off<0> or On<1>");
|
||||
ASUS_ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED,
|
||||
"Show the eGPU connection status");
|
||||
ASUS_ATTR_GROUP_BOOL_RO(dgpu_power_state, "dgpu_power_state", ASUS_WMI_DEVID_DGPU_POWER_STATE,
|
||||
"Show the dGPU power state (0: D3 cold, 1: D0 active)");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, ATTR_PPT_PL1_SPL, ASUS_WMI_DEVID_PPT_PL1_SPL,
|
||||
"Set the CPU slow package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, ATTR_PPT_PL2_SPPT, ASUS_WMI_DEVID_PPT_PL2_SPPT,
|
||||
"Set the CPU fast package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl3_fppt, ATTR_PPT_PL3_FPPT, ASUS_WMI_DEVID_PPT_PL3_FPPT,
|
||||
"Set the CPU fastest package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, ATTR_PPT_APU_SPPT, ASUS_WMI_DEVID_PPT_APU_SPPT,
|
||||
"Set the APU package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, ATTR_PPT_PLATFORM_SPPT, ASUS_WMI_DEVID_PPT_PLAT_SPPT,
|
||||
"Set the platform package limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, ATTR_NV_DYNAMIC_BOOST, ASUS_WMI_DEVID_NV_DYN_BOOST,
|
||||
"Set the Nvidia dynamic boost limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(nv_temp_target, ATTR_NV_TEMP_TARGET, ASUS_WMI_DEVID_NV_THERM_TARGET,
|
||||
"Set the Nvidia max thermal limit");
|
||||
ASUS_ATTR_GROUP_ROG_TUNABLE(nv_tgp, "nv_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP,
|
||||
"Set the additional TGP on top of the base TGP");
|
||||
ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(nv_base_tgp, ATTR_NV_BASE_TGP, ASUS_WMI_DEVID_DGPU_BASE_TGP,
|
||||
"Read the base TGP value");
|
||||
|
||||
static const struct attribute_group *armoury_attr_groups[] = {
|
||||
&mini_led_mode_attr_group,
|
||||
&gpu_mux_mode_attr_group,
|
||||
&egpu_connected_attr_group,
|
||||
&egpu_enable_attr_group,
|
||||
&dgpu_disable_attr_group,
|
||||
&dgpu_power_state_attr_group,
|
||||
&apu_mem_attr_group,
|
||||
|
||||
&ppt_pl1_spl_attr_group,
|
||||
&ppt_pl2_sppt_attr_group,
|
||||
&ppt_pl3_fppt_attr_group,
|
||||
&ppt_apu_sppt_attr_group,
|
||||
&ppt_platform_sppt_attr_group,
|
||||
&nv_dynamic_boost_attr_group,
|
||||
&nv_temp_target_attr_group,
|
||||
&nv_base_tgp_attr_group,
|
||||
&nv_tgp_attr_group,
|
||||
|
||||
&charge_mode_attr_group,
|
||||
&boot_sound_attr_group,
|
||||
&mcu_powersave_attr_group,
|
||||
&panel_od_attr_group,
|
||||
&panel_hd_mode_attr_group,
|
||||
&screen_auto_brightness_attr_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int asus_fw_attr_add(void)
|
||||
{
|
||||
const struct rog_tunables *const ac_rog_tunables =
|
||||
asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC];
|
||||
const struct power_limits *limits;
|
||||
bool should_create;
|
||||
const char *name;
|
||||
int err, i;
|
||||
int err;
|
||||
|
||||
asus_armoury.fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
|
||||
NULL, "%s", DRIVER_NAME);
|
||||
|
|
@ -929,72 +942,27 @@ static int asus_fw_attr_add(void)
|
|||
else if (armoury_has_devstate(ASUS_WMI_DEVID_MINI_LED_MODE2))
|
||||
asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2;
|
||||
|
||||
if (asus_armoury.mini_led_dev_id) {
|
||||
err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
|
||||
&mini_led_mode_attr_group);
|
||||
if (err) {
|
||||
pr_err("Failed to create sysfs-group for mini_led\n");
|
||||
goto err_remove_file;
|
||||
}
|
||||
}
|
||||
|
||||
asus_armoury.gpu_mux_dev_id = 0;
|
||||
if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX))
|
||||
asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX;
|
||||
else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX_VIVO))
|
||||
asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO;
|
||||
|
||||
if (asus_armoury.gpu_mux_dev_id) {
|
||||
err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
|
||||
&gpu_mux_mode_attr_group);
|
||||
if (err) {
|
||||
pr_err("Failed to create sysfs-group for gpu_mux\n");
|
||||
goto err_remove_mini_led_group;
|
||||
}
|
||||
}
|
||||
asus_armoury.dgpu_disable_dev_id = 0;
|
||||
if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU))
|
||||
asus_armoury.dgpu_disable_dev_id = ASUS_WMI_DEVID_DGPU;
|
||||
else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MODE))
|
||||
asus_armoury.dgpu_disable_dev_id = ASUS_WMI_DEVID_GPU_MODE;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) {
|
||||
if (!armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
|
||||
continue;
|
||||
|
||||
/* Always create by default, unless PPT is not present */
|
||||
should_create = true;
|
||||
name = armoury_attr_groups[i].attr_group->name;
|
||||
|
||||
/* Check if this is a power-related tunable requiring limits */
|
||||
if (ac_rog_tunables && ac_rog_tunables->power_limits &&
|
||||
is_power_tunable_attr(name)) {
|
||||
limits = ac_rog_tunables->power_limits;
|
||||
/* Check only AC: if not present then DC won't be either */
|
||||
should_create = has_valid_limit(name, limits);
|
||||
if (!should_create)
|
||||
pr_debug("Missing max value for tunable %s\n", name);
|
||||
}
|
||||
|
||||
if (should_create) {
|
||||
err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
|
||||
armoury_attr_groups[i].attr_group);
|
||||
if (err) {
|
||||
pr_err("Failed to create sysfs-group for %s\n",
|
||||
armoury_attr_groups[i].attr_group->name);
|
||||
goto err_remove_groups;
|
||||
}
|
||||
}
|
||||
err = sysfs_create_groups(&asus_armoury.fw_attr_kset->kobj,
|
||||
armoury_attr_groups);
|
||||
if (err) {
|
||||
pr_err("Failed to create firmware attributes groups\n");
|
||||
goto err_remove_file;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_groups:
|
||||
while (i--) {
|
||||
if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
|
||||
sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj,
|
||||
armoury_attr_groups[i].attr_group);
|
||||
}
|
||||
if (asus_armoury.gpu_mux_dev_id)
|
||||
sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group);
|
||||
err_remove_mini_led_group:
|
||||
if (asus_armoury.mini_led_dev_id)
|
||||
sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group);
|
||||
err_remove_file:
|
||||
sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
|
||||
err_destroy_kset:
|
||||
|
|
@ -1165,19 +1133,8 @@ static int __init asus_fw_init(void)
|
|||
|
||||
static void __exit asus_fw_exit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = ARRAY_SIZE(armoury_attr_groups) - 1; i >= 0; i--) {
|
||||
if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
|
||||
sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj,
|
||||
armoury_attr_groups[i].attr_group);
|
||||
}
|
||||
|
||||
if (asus_armoury.gpu_mux_dev_id)
|
||||
sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group);
|
||||
|
||||
if (asus_armoury.mini_led_dev_id)
|
||||
sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group);
|
||||
sysfs_remove_groups(&asus_armoury.fw_attr_kset->kobj,
|
||||
armoury_attr_groups);
|
||||
|
||||
sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
|
||||
kset_unregister(asus_armoury.fw_attr_kset);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,43 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
static struct kobj_attribute attr_##_attrname##_##_prop = \
|
||||
__ASUS_ATTR_RO(_attrname, _prop)
|
||||
|
||||
/*
|
||||
* Every attribute group decides its own visibility through .is_visible():
|
||||
* sysfs hides a named group entirely when its first attribute reports
|
||||
* SYSFS_GROUP_INVISIBLE, so groups are always created and never leave
|
||||
* empty directories behind.
|
||||
*/
|
||||
#define __ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi) \
|
||||
static bool _attrname##_group_visible(struct kobject *kobj) \
|
||||
{ \
|
||||
return armoury_has_devstate(_wmi); \
|
||||
} \
|
||||
DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname)
|
||||
|
||||
/*
|
||||
* Power tunables are additionally gated on the platform limits actually
|
||||
* defining a max value for them. Only the AC limits are checked: if not
|
||||
* present then DC won't be either.
|
||||
*/
|
||||
#define __ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi) \
|
||||
static bool _attrname##_group_visible(struct kobject *kobj) \
|
||||
{ \
|
||||
const struct rog_tunables *tunables = \
|
||||
asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]; \
|
||||
\
|
||||
if (!tunables || !tunables->power_limits) \
|
||||
return armoury_has_devstate(_wmi); \
|
||||
\
|
||||
if (!has_valid_limit(_fsname, tunables->power_limits)) {\
|
||||
pr_debug("Missing max value for tunable %s\n", \
|
||||
_fsname); \
|
||||
return false; \
|
||||
} \
|
||||
\
|
||||
return armoury_has_devstate(_wmi); \
|
||||
} \
|
||||
DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname)
|
||||
|
||||
#define __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname)\
|
||||
ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \
|
||||
static struct kobj_attribute attr_##_attrname##_current_value = \
|
||||
|
|
@ -108,6 +145,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
__ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \
|
||||
static struct kobj_attribute attr_##_attrname##_type = \
|
||||
__ASUS_ATTR_RO_AS(type, enum_type_show); \
|
||||
__ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi); \
|
||||
static struct attribute *_attrname##_attrs[] = { \
|
||||
&attr_##_attrname##_current_value.attr, \
|
||||
&attr_##_attrname##_display_name.attr, \
|
||||
|
|
@ -116,7 +154,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
NULL \
|
||||
}; \
|
||||
static const struct attribute_group _attrname##_attr_group = { \
|
||||
.name = _fsname, .attrs = _attrname##_attrs \
|
||||
.name = _fsname, \
|
||||
.is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
|
||||
.attrs = _attrname##_attrs \
|
||||
}
|
||||
|
||||
#define __ATTR_RW_INT_GROUP_ENUM(_attrname, _minv, _maxv, _wmi, _fsname,\
|
||||
|
|
@ -129,6 +169,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
__ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \
|
||||
static struct kobj_attribute attr_##_attrname##_type = \
|
||||
__ASUS_ATTR_RO_AS(type, enum_type_show); \
|
||||
__ASUS_DEVSTATE_GROUP_VISIBLE(_attrname, _wmi); \
|
||||
static struct attribute *_attrname##_attrs[] = { \
|
||||
&attr_##_attrname##_current_value.attr, \
|
||||
&attr_##_attrname##_display_name.attr, \
|
||||
|
|
@ -137,7 +178,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
NULL \
|
||||
}; \
|
||||
static const struct attribute_group _attrname##_attr_group = { \
|
||||
.name = _fsname, .attrs = _attrname##_attrs \
|
||||
.name = _fsname, \
|
||||
.is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
|
||||
.attrs = _attrname##_attrs \
|
||||
}
|
||||
|
||||
/* Boolean style enumeration, base macro. Requires adding show/store */
|
||||
|
|
@ -168,37 +211,63 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
__ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname)
|
||||
|
||||
/*
|
||||
* Requires <name>_current_value_show(), <name>_current_value_show()
|
||||
* Boolean style group whose whole visibility is decided by
|
||||
* <name>_group_visible(), for attributes backed by a device ID resolved
|
||||
* at probe time.
|
||||
* Requires <name>_current_value_show(), <name>_current_value_store()
|
||||
* and <name>_group_visible()
|
||||
*/
|
||||
#define ASUS_ATTR_GROUP_BOOL(_attrname, _fsname, _dispname) \
|
||||
DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname) \
|
||||
static struct kobj_attribute attr_##_attrname##_current_value = \
|
||||
__ASUS_ATTR_RW(_attrname, current_value); \
|
||||
__ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname)
|
||||
__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
|
||||
__ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", "0;1"); \
|
||||
static struct kobj_attribute attr_##_attrname##_type = \
|
||||
__ASUS_ATTR_RO_AS(type, enum_type_show); \
|
||||
static struct attribute *_attrname##_attrs[] = { \
|
||||
&attr_##_attrname##_current_value.attr, \
|
||||
&attr_##_attrname##_display_name.attr, \
|
||||
&attr_##_attrname##_possible_values.attr, \
|
||||
&attr_##_attrname##_type.attr, \
|
||||
NULL \
|
||||
}; \
|
||||
static const struct attribute_group _attrname##_attr_group = { \
|
||||
.name = _fsname, \
|
||||
.is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
|
||||
.attrs = _attrname##_attrs \
|
||||
}
|
||||
|
||||
/*
|
||||
* Requires <name>_current_value_show(), <name>_current_value_show()
|
||||
* and <name>_possible_values_show()
|
||||
* Group whose whole visibility is decided by <name>_group_visible(),
|
||||
* for attributes backed by a device ID resolved at probe time.
|
||||
* Requires <name>_current_value_show(), <name>_current_value_store(),
|
||||
* <name>_possible_values_show() and <name>_group_visible()
|
||||
*/
|
||||
#define ASUS_ATTR_GROUP_ENUM(_attrname, _fsname, _dispname) \
|
||||
__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
|
||||
static struct kobj_attribute attr_##_attrname##_current_value = \
|
||||
__ASUS_ATTR_RW(_attrname, current_value); \
|
||||
static struct kobj_attribute attr_##_attrname##_possible_values = \
|
||||
__ASUS_ATTR_RO(_attrname, possible_values); \
|
||||
static struct kobj_attribute attr_##_attrname##_type = \
|
||||
__ASUS_ATTR_RO_AS(type, enum_type_show); \
|
||||
static struct attribute *_attrname##_attrs[] = { \
|
||||
&attr_##_attrname##_current_value.attr, \
|
||||
&attr_##_attrname##_display_name.attr, \
|
||||
&attr_##_attrname##_possible_values.attr, \
|
||||
&attr_##_attrname##_type.attr, \
|
||||
NULL \
|
||||
}; \
|
||||
static const struct attribute_group _attrname##_attr_group = { \
|
||||
.name = _fsname, .attrs = _attrname##_attrs \
|
||||
#define ASUS_ATTR_GROUP_ENUM(_attrname, _fsname, _dispname) \
|
||||
DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(_attrname) \
|
||||
static struct kobj_attribute attr_##_attrname##_current_value = \
|
||||
__ASUS_ATTR_RW(_attrname, current_value); \
|
||||
__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
|
||||
static struct kobj_attribute attr_##_attrname##_possible_values =\
|
||||
__ASUS_ATTR_RO(_attrname, possible_values); \
|
||||
static struct kobj_attribute attr_##_attrname##_type = \
|
||||
__ASUS_ATTR_RO_AS(type, enum_type_show); \
|
||||
static struct attribute *_attrname##_attrs[] = { \
|
||||
&attr_##_attrname##_current_value.attr, \
|
||||
&attr_##_attrname##_display_name.attr, \
|
||||
&attr_##_attrname##_possible_values.attr, \
|
||||
&attr_##_attrname##_type.attr, \
|
||||
NULL \
|
||||
}; \
|
||||
static const struct attribute_group _attrname##_attr_group = { \
|
||||
.name = _fsname, \
|
||||
.is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
|
||||
.attrs = _attrname##_attrs \
|
||||
}
|
||||
|
||||
#define ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(_attrname, _fsname, _wmi, _dispname) \
|
||||
__ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi); \
|
||||
ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \
|
||||
static struct kobj_attribute attr_##_attrname##_current_value = \
|
||||
__ASUS_ATTR_RO(_attrname, current_value); \
|
||||
|
|
@ -211,7 +280,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
&attr_##_attrname##_type.attr, NULL \
|
||||
}; \
|
||||
static const struct attribute_group _attrname##_attr_group = { \
|
||||
.name = _fsname, .attrs = _attrname##_attrs \
|
||||
.name = _fsname, \
|
||||
.is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
|
||||
.attrs = _attrname##_attrs \
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -284,6 +355,7 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
__ASUS_ATTR_RW(_attr, current_value)
|
||||
|
||||
#define ASUS_ATTR_GROUP_ROG_TUNABLE(_attrname, _fsname, _wmi, _dispname) \
|
||||
__ASUS_POWER_TUNABLE_GROUP_VISIBLE(_attrname, _fsname, _wmi); \
|
||||
__ROG_TUNABLE_RW(_attrname, _wmi); \
|
||||
__ROG_TUNABLE_SHOW_DEFAULT(_attrname); \
|
||||
__ROG_TUNABLE_SHOW(min_value, _attrname, _attrname##_min); \
|
||||
|
|
@ -303,7 +375,9 @@ ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr
|
|||
NULL \
|
||||
}; \
|
||||
static const struct attribute_group _attrname##_attr_group = { \
|
||||
.name = _fsname, .attrs = _attrname##_attrs \
|
||||
.name = _fsname, \
|
||||
.is_visible = SYSFS_GROUP_VISIBLE(_attrname), \
|
||||
.attrs = _attrname##_attrs \
|
||||
}
|
||||
|
||||
/* Default is always the maximum value unless *_def is specified */
|
||||
|
|
@ -713,6 +787,70 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA608PM"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.nv_dynamic_boost_max = 15,
|
||||
.nv_dynamic_boost_min = 10,
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_tgp_max = 100,
|
||||
.nv_tgp_min = 55,
|
||||
.ppt_pl1_spl_max = 100,
|
||||
.ppt_pl1_spl_min = 45,
|
||||
.ppt_pl2_sppt_max = 115,
|
||||
.ppt_pl2_sppt_min = 60,
|
||||
.ppt_pl3_fppt_max = 135,
|
||||
.ppt_pl3_fppt_min = 80,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.ppt_pl1_spl_max = 68,
|
||||
.ppt_pl1_spl_min = 35,
|
||||
.ppt_pl2_sppt_max = 71,
|
||||
.ppt_pl2_sppt_min = 36,
|
||||
.ppt_pl3_fppt_max = 74,
|
||||
.ppt_pl3_fppt_min = 70,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA608UH"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.nv_dynamic_boost_max = 15,
|
||||
.nv_dynamic_boost_min = 10,
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_tgp_max = 100,
|
||||
.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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA608UM"),
|
||||
|
|
@ -749,6 +887,39 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA608UP"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.nv_dynamic_boost_max = 15,
|
||||
.nv_dynamic_boost_min = 10,
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_tgp_max = 100,
|
||||
.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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "FA608WI"),
|
||||
|
|
@ -1056,6 +1227,46 @@ static const struct dmi_system_id power_limits[] = {
|
|||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "G533QS"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_max = 80,
|
||||
.ppt_pl1_spl_min = 15,
|
||||
.ppt_pl2_sppt_max = 80,
|
||||
.ppt_pl2_sppt_min = 15,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "G713QR"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_max = 80,
|
||||
.ppt_pl1_spl_min = 15,
|
||||
.ppt_pl2_sppt_max = 80,
|
||||
.ppt_pl2_sppt_min = 15,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GA401IHR"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_max = 80,
|
||||
.ppt_pl1_spl_min = 15,
|
||||
.ppt_pl2_sppt_max = 80,
|
||||
.ppt_pl2_sppt_min = 15,
|
||||
},
|
||||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GA401Q"),
|
||||
|
|
@ -1102,6 +1313,37 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GA402NV"),
|
||||
},
|
||||
.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,
|
||||
.ppt_pl1_spl_def = 35,
|
||||
.ppt_pl1_spl_max = 80,
|
||||
.ppt_pl1_spl_min = 15,
|
||||
.ppt_pl2_sppt_def = 65,
|
||||
.ppt_pl2_sppt_max = 80,
|
||||
.ppt_pl2_sppt_min = 25,
|
||||
.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 = 15,
|
||||
.ppt_pl2_sppt_max = 35,
|
||||
.ppt_pl2_sppt_min = 25,
|
||||
.ppt_pl3_fppt_max = 65,
|
||||
.ppt_pl3_fppt_min = 35,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
// This model is full AMD. No Nvidia dGPU.
|
||||
|
|
@ -1487,6 +1729,37 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GU605CM"),
|
||||
},
|
||||
.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_def = 85,
|
||||
.ppt_pl1_spl_max = 75,
|
||||
.ppt_pl1_spl_min = 45,
|
||||
.ppt_pl2_sppt_def = 110,
|
||||
.ppt_pl2_sppt_max = 95,
|
||||
.ppt_pl2_sppt_min = 56,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_temp_target_min = 75,
|
||||
.ppt_pl1_spl_def = 85,
|
||||
.ppt_pl1_spl_max = 75,
|
||||
.ppt_pl1_spl_min = 25,
|
||||
.ppt_pl2_sppt_def = 110,
|
||||
.ppt_pl2_sppt_max = 95,
|
||||
.ppt_pl2_sppt_min = 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GU605CP"),
|
||||
|
|
@ -1881,6 +2154,33 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GX651AR"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 30,
|
||||
.ppt_pl1_spl_max = 75,
|
||||
.ppt_pl2_sppt_min = 38,
|
||||
.ppt_pl2_sppt_max = 80,
|
||||
.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 = 115,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 30,
|
||||
.ppt_pl1_spl_max = 75,
|
||||
.ppt_pl2_sppt_min = 38,
|
||||
.ppt_pl2_sppt_max = 80,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_temp_target_max = 87,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GZ302EA"),
|
||||
|
|
@ -1988,6 +2288,38 @@ static const struct dmi_system_id power_limits[] = {
|
|||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "G614FM"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 30,
|
||||
.ppt_pl1_spl_max = 120,
|
||||
.ppt_pl2_sppt_min = 65,
|
||||
.ppt_pl2_sppt_def = 140,
|
||||
.ppt_pl2_sppt_max = 165,
|
||||
.ppt_pl3_fppt_min = 65,
|
||||
.ppt_pl3_fppt_def = 140,
|
||||
.ppt_pl3_fppt_max = 165,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_temp_target_max = 87,
|
||||
.nv_dynamic_boost_min = 5,
|
||||
.nv_dynamic_boost_max = 15,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 25,
|
||||
.ppt_pl1_spl_max = 65,
|
||||
.ppt_pl2_sppt_min = 25,
|
||||
.ppt_pl2_sppt_max = 65,
|
||||
.ppt_pl3_fppt_min = 35,
|
||||
.ppt_pl3_fppt_max = 75,
|
||||
.nv_temp_target_min = 75,
|
||||
.nv_temp_target_max = 87,
|
||||
},
|
||||
.requires_fan_curve = true,
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "G614FP"),
|
||||
|
|
@ -2550,6 +2882,32 @@ static const struct dmi_system_id power_limits[] = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_NAME, "RC74XA"),
|
||||
},
|
||||
.driver_data = &(struct power_data) {
|
||||
.ac_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 7,
|
||||
.ppt_pl1_spl_max = 35,
|
||||
.ppt_pl2_sppt_min = 13,
|
||||
.ppt_pl2_sppt_max = 45,
|
||||
.ppt_pl3_fppt_min = 19,
|
||||
.ppt_pl3_fppt_max = 55,
|
||||
},
|
||||
.dc_data = &(struct power_limits) {
|
||||
.ppt_pl1_spl_min = 7,
|
||||
.ppt_pl1_spl_def = 17,
|
||||
.ppt_pl1_spl_max = 35,
|
||||
.ppt_pl2_sppt_min = 13,
|
||||
.ppt_pl2_sppt_def = 21,
|
||||
.ppt_pl2_sppt_max = 45,
|
||||
.ppt_pl3_fppt_min = 19,
|
||||
.ppt_pl3_fppt_def = 26,
|
||||
.ppt_pl3_fppt_max = 55,
|
||||
},
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -865,7 +865,7 @@ static ssize_t infos_show(struct device *dev, struct device_attribute *attr,
|
|||
len += sysfs_emit_at(page, len, "SFUN value : %#x\n",
|
||||
(uint) temp);
|
||||
/*
|
||||
* The HWRS method return informations about the hardware.
|
||||
* The HWRS method return information about the hardware.
|
||||
* 0x80 bit is for WLAN, 0x100 for Bluetooth.
|
||||
* 0x40 for WWAN, 0x10 for WIMAX.
|
||||
* The significance of others is yet to be found.
|
||||
|
|
|
|||
|
|
@ -137,6 +137,10 @@ static struct quirk_entry quirk_asus_use_lid_flip_devid = {
|
|||
.tablet_switch_mode = asus_wmi_lid_flip_devid,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_asus_proart_px13 = {
|
||||
.tablet_switch_mode = asus_wmi_lid_flip_devid,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_asus_tablet_mode = {
|
||||
.wmi_backlight_set_devstate = true,
|
||||
.tablet_switch_mode = asus_wmi_lid_flip_rog_devid,
|
||||
|
|
@ -499,6 +503,16 @@ static const struct dmi_system_id asus_quirks[] = {
|
|||
},
|
||||
.driver_data = &quirk_asus_tablet_mode,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUS ROG FLOW X13",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
/* Match GV302X* */
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GV302X"),
|
||||
},
|
||||
.driver_data = &quirk_asus_tablet_mode,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUS ROG FLOW X16",
|
||||
|
|
@ -562,6 +576,15 @@ static const struct dmi_system_id asus_quirks[] = {
|
|||
},
|
||||
.driver_data = &quirk_asus_z13,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUS ProArt PX13",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "HN7306WV"),
|
||||
},
|
||||
.driver_data = &quirk_asus_proart_px13,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
@ -573,7 +596,7 @@ static void asus_nb_wmi_quirks(struct asus_wmi_driver *driver)
|
|||
driver->quirks = quirks;
|
||||
driver->panel_power = BACKLIGHT_POWER_ON;
|
||||
|
||||
/* overwrite the wapf setting if the wapf paramater is specified */
|
||||
/* overwrite the wapf setting if the wapf parameter is specified */
|
||||
if (wapf != -1)
|
||||
quirks->wapf = wapf;
|
||||
else
|
||||
|
|
|
|||
|
|
@ -772,7 +772,7 @@ static int tf103c_dock_probe(struct i2c_client *client)
|
|||
IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_AUTOEN,
|
||||
"dock_irq", dock);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "requesting dock IRQ");
|
||||
return ret;
|
||||
|
||||
dock->hpd_gpio = devm_gpiod_get(dev, "dock_hpd", GPIOD_IN);
|
||||
if (IS_ERR(dock->hpd_gpio))
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <linux/acpi.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dmi.h>
|
||||
|
|
@ -28,6 +29,7 @@
|
|||
#include <linux/leds.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci_hotplug.h>
|
||||
#include <linux/platform_data/x86/asus-wmi.h>
|
||||
|
|
@ -127,7 +129,6 @@ module_param(fnlock_default, bool, 0444);
|
|||
#define NVIDIA_TEMP_MAX 87
|
||||
|
||||
#define ASUS_SCREENPAD_BRIGHT_MAX 255
|
||||
#define ASUS_SCREENPAD_BRIGHT_DEFAULT 60
|
||||
|
||||
#define ASUS_MINI_LED_MODE_MASK 0x03
|
||||
/* Standard modes for devices with only on/off */
|
||||
|
|
@ -262,6 +263,7 @@ struct asus_wmi {
|
|||
struct led_classdev lightbar_led;
|
||||
int lightbar_led_wk;
|
||||
struct led_classdev micmute_led;
|
||||
struct led_classdev mute_led;
|
||||
struct led_classdev camera_led;
|
||||
struct workqueue_struct *led_workqueue;
|
||||
struct work_struct tpd_led_work;
|
||||
|
|
@ -320,7 +322,6 @@ struct asus_wmi {
|
|||
struct fan_curve_data custom_fan_curves[3];
|
||||
|
||||
struct device *ppdev;
|
||||
bool platform_profile_support;
|
||||
|
||||
// The RSOC controls the maximum charging percentage.
|
||||
bool battery_rsoc_available;
|
||||
|
|
@ -330,7 +331,6 @@ struct asus_wmi {
|
|||
|
||||
struct hotplug_slot hotplug_slot;
|
||||
struct mutex hotplug_lock;
|
||||
struct mutex wmi_lock;
|
||||
struct workqueue_struct *hotplug_workqueue;
|
||||
struct work_struct hotplug_work;
|
||||
|
||||
|
|
@ -353,6 +353,20 @@ static void asus_wmi_show_deprecated(void)
|
|||
|
||||
/* WMI ************************************************************************/
|
||||
|
||||
/*
|
||||
* Concurrent evaluations of ASUS WMI methods can re-enter firmware
|
||||
* SMI/EC mailbox handling and corrupt the mailbox.
|
||||
*/
|
||||
static DEFINE_MUTEX(asus_wmi_eval_lock);
|
||||
|
||||
static acpi_status asus_wmi_evaluate_method_locked(u32 method_id,
|
||||
struct acpi_buffer *input,
|
||||
struct acpi_buffer *output)
|
||||
{
|
||||
guard(mutex)(&asus_wmi_eval_lock);
|
||||
return wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id, input, output);
|
||||
}
|
||||
|
||||
static int asus_wmi_evaluate_method3(u32 method_id,
|
||||
u32 arg0, u32 arg1, u32 arg2, u32 *retval)
|
||||
{
|
||||
|
|
@ -367,8 +381,7 @@ static int asus_wmi_evaluate_method3(u32 method_id,
|
|||
union acpi_object *obj;
|
||||
u32 tmp = 0;
|
||||
|
||||
status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
|
||||
&input, &output);
|
||||
status = asus_wmi_evaluate_method_locked(method_id, &input, &output);
|
||||
|
||||
pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x\n",
|
||||
__func__, method_id, arg0, arg1, arg2);
|
||||
|
|
@ -419,8 +432,7 @@ static int asus_wmi_evaluate_method5(u32 method_id,
|
|||
union acpi_object *obj;
|
||||
u32 tmp = 0;
|
||||
|
||||
status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
|
||||
&input, &output);
|
||||
status = asus_wmi_evaluate_method_locked(method_id, &input, &output);
|
||||
|
||||
pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
|
||||
__func__, method_id, arg0, arg1, arg2, arg3, arg4);
|
||||
|
|
@ -467,8 +479,7 @@ static int asus_wmi_evaluate_method_buf(u32 method_id,
|
|||
union acpi_object *obj;
|
||||
int err = 0;
|
||||
|
||||
status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
|
||||
&input, &output);
|
||||
status = asus_wmi_evaluate_method_locked(method_id, &input, &output);
|
||||
|
||||
pr_debug("%s called (0x%08x) with args: 0x%08x, 0x%08x\n",
|
||||
__func__, method_id, arg0, arg1);
|
||||
|
|
@ -1047,6 +1058,10 @@ static DEVICE_ATTR_RW(gpu_mux_mode);
|
|||
#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
|
||||
|
||||
/* TUF Laptop Keyboard RGB Modes **********************************************/
|
||||
|
||||
#define TUF_KBD_RGB_SPEED_SLOW 0xe1
|
||||
#define TUF_KBD_RGB_SPEED_NORMAL 0xeb
|
||||
#define TUF_KBD_RGB_SPEED_FAST 0xf5
|
||||
static ssize_t kbd_rgb_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
|
|
@ -1080,16 +1095,16 @@ static ssize_t kbd_rgb_mode_store(struct device *dev,
|
|||
|
||||
switch (speed) {
|
||||
case 0:
|
||||
speed = 0xe1;
|
||||
speed = TUF_KBD_RGB_SPEED_SLOW;
|
||||
break;
|
||||
case 1:
|
||||
speed = 0xeb;
|
||||
speed = TUF_KBD_RGB_SPEED_NORMAL;
|
||||
break;
|
||||
case 2:
|
||||
speed = 0xf5;
|
||||
speed = TUF_KBD_RGB_SPEED_FAST;
|
||||
break;
|
||||
default:
|
||||
speed = 0xeb;
|
||||
speed = TUF_KBD_RGB_SPEED_NORMAL;
|
||||
}
|
||||
|
||||
err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS, asus->kbd_rgb_dev,
|
||||
|
|
@ -1115,14 +1130,19 @@ static const struct attribute_group kbd_rgb_mode_group = {
|
|||
};
|
||||
|
||||
/* TUF Laptop Keyboard RGB State **********************************************/
|
||||
|
||||
#define TUF_KBD_RGB_STATE_BOOT BIT(1)
|
||||
#define TUF_KBD_RGB_STATE_AWAKE BIT(3)
|
||||
#define TUF_KBD_RGB_STATE_SLEEP BIT(5)
|
||||
#define TUF_KBD_RGB_STATE_SHUTDOWN BIT(7)
|
||||
static ssize_t kbd_rgb_state_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
u32 flags, cmd, boot, awake, sleep, keyboard;
|
||||
u32 flags, cmd, boot, awake, sleep, shutdown;
|
||||
int err;
|
||||
|
||||
if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &keyboard) != 5)
|
||||
if (sscanf(buf, "%d %d %d %d %d", &cmd, &boot, &awake, &sleep, &shutdown) != 5)
|
||||
return -EINVAL;
|
||||
|
||||
if (cmd)
|
||||
|
|
@ -1130,17 +1150,17 @@ static ssize_t kbd_rgb_state_store(struct device *dev,
|
|||
|
||||
flags = 0;
|
||||
if (boot)
|
||||
flags |= BIT(1);
|
||||
flags |= TUF_KBD_RGB_STATE_BOOT;
|
||||
if (awake)
|
||||
flags |= BIT(3);
|
||||
flags |= TUF_KBD_RGB_STATE_AWAKE;
|
||||
if (sleep)
|
||||
flags |= BIT(5);
|
||||
if (keyboard)
|
||||
flags |= BIT(7);
|
||||
flags |= TUF_KBD_RGB_STATE_SLEEP;
|
||||
if (shutdown)
|
||||
flags |= TUF_KBD_RGB_STATE_SHUTDOWN;
|
||||
|
||||
/* 0xbd is the required default arg0 for the method. Nothing happens otherwise */
|
||||
err = asus_wmi_evaluate_method3(ASUS_WMI_METHODID_DEVS,
|
||||
ASUS_WMI_DEVID_TUF_RGB_STATE, 0xbd | cmd << 8 | (flags << 16), 0, NULL);
|
||||
ASUS_WMI_DEVID_TUF_RGB_CMD, 0xbd | cmd << 8 | (flags << 16), 0, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
|
@ -1149,7 +1169,7 @@ static ssize_t kbd_rgb_state_store(struct device *dev,
|
|||
static DEVICE_ATTR_WO(kbd_rgb_state);
|
||||
|
||||
static DEVICE_STRING_ATTR_RO(kbd_rgb_state_index, 0444,
|
||||
"cmd boot awake sleep keyboard");
|
||||
"cmd boot awake sleep shutdown");
|
||||
|
||||
static struct attribute *kbd_rgb_state_attrs[] = {
|
||||
&dev_attr_kbd_rgb_state.attr,
|
||||
|
|
@ -2054,6 +2074,16 @@ static int micmute_led_set(struct led_classdev *led_cdev,
|
|||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
||||
static int mute_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
int state = brightness != LED_OFF;
|
||||
int err;
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MUTE_LED, state, NULL);
|
||||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
||||
static enum led_brightness camera_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct asus_wmi *asus;
|
||||
|
|
@ -2084,10 +2114,23 @@ static void asus_wmi_led_exit(struct asus_wmi *asus)
|
|||
led_classdev_unregister(&asus->wlan_led);
|
||||
led_classdev_unregister(&asus->lightbar_led);
|
||||
led_classdev_unregister(&asus->micmute_led);
|
||||
led_classdev_unregister(&asus->mute_led);
|
||||
led_classdev_unregister(&asus->camera_led);
|
||||
|
||||
if (asus->led_workqueue)
|
||||
destroy_workqueue(asus->led_workqueue);
|
||||
|
||||
/*
|
||||
* kbd_led is registered lazily by kbd_led_work: now that the
|
||||
* workqueue is destroyed and asus_ref.asus is NULL, the work can
|
||||
* neither run nor be queued anymore, furthermore leaving it to
|
||||
* devres would run the unregister from devres_release_all(),
|
||||
* after .remove() returned and the struct asus_wmi embedding
|
||||
* kbd_led has been freed.
|
||||
*/
|
||||
if (asus->kbd_led_registered)
|
||||
devm_led_classdev_unregister(&asus->platform_device->dev,
|
||||
&asus->kbd_led);
|
||||
}
|
||||
|
||||
static int asus_wmi_led_init(struct asus_wmi *asus)
|
||||
|
|
@ -2176,7 +2219,19 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
|
|||
asus->micmute_led.default_trigger = "audio-micmute";
|
||||
|
||||
rv = led_classdev_register(&asus->platform_device->dev,
|
||||
&asus->micmute_led);
|
||||
&asus->micmute_led);
|
||||
if (rv)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MUTE_LED)) {
|
||||
asus->mute_led.name = "platform::mute";
|
||||
asus->mute_led.max_brightness = 1;
|
||||
asus->mute_led.brightness_set_blocking = mute_led_set;
|
||||
asus->mute_led.default_trigger = "audio-mute";
|
||||
|
||||
rv = led_classdev_register(&asus->platform_device->dev,
|
||||
&asus->mute_led);
|
||||
if (rv)
|
||||
goto error;
|
||||
}
|
||||
|
|
@ -2232,9 +2287,7 @@ static void asus_rfkill_hotplug(struct asus_wmi *asus)
|
|||
bool absent;
|
||||
u32 l;
|
||||
|
||||
mutex_lock(&asus->wmi_lock);
|
||||
blocked = asus_wlan_rfkill_blocked(asus);
|
||||
mutex_unlock(&asus->wmi_lock);
|
||||
|
||||
mutex_lock(&asus->hotplug_lock);
|
||||
pci_lock_rescan_remove();
|
||||
|
|
@ -2436,30 +2489,6 @@ static void asus_rfkill_query(struct rfkill *rfkill, void *data)
|
|||
rfkill_set_sw_state(priv->rfkill, !result);
|
||||
}
|
||||
|
||||
static int asus_rfkill_wlan_set(void *data, bool blocked)
|
||||
{
|
||||
struct asus_rfkill *priv = data;
|
||||
struct asus_wmi *asus = priv->asus;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This handler is enabled only if hotplug is enabled.
|
||||
* In this case, the asus_wmi_set_devstate() will
|
||||
* trigger a wmi notification and we need to wait
|
||||
* this call to finish before being able to call
|
||||
* any wmi method
|
||||
*/
|
||||
mutex_lock(&asus->wmi_lock);
|
||||
ret = asus_rfkill_set(data, blocked);
|
||||
mutex_unlock(&asus->wmi_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct rfkill_ops asus_rfkill_wlan_ops = {
|
||||
.set_block = asus_rfkill_wlan_set,
|
||||
.query = asus_rfkill_query,
|
||||
};
|
||||
|
||||
static const struct rfkill_ops asus_rfkill_ops = {
|
||||
.set_block = asus_rfkill_set,
|
||||
.query = asus_rfkill_query,
|
||||
|
|
@ -2478,13 +2507,8 @@ static int asus_new_rfkill(struct asus_wmi *asus,
|
|||
arfkill->dev_id = dev_id;
|
||||
arfkill->asus = asus;
|
||||
|
||||
if (dev_id == ASUS_WMI_DEVID_WLAN &&
|
||||
asus->driver->quirks->hotplug_wireless)
|
||||
*rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
|
||||
&asus_rfkill_wlan_ops, arfkill);
|
||||
else
|
||||
*rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
|
||||
&asus_rfkill_ops, arfkill);
|
||||
*rfkill = rfkill_alloc(name, &asus->platform_device->dev, type,
|
||||
&asus_rfkill_ops, arfkill);
|
||||
|
||||
if (!*rfkill)
|
||||
return -EINVAL;
|
||||
|
|
@ -2558,7 +2582,6 @@ static int asus_wmi_rfkill_init(struct asus_wmi *asus)
|
|||
int result = 0;
|
||||
|
||||
mutex_init(&asus->hotplug_lock);
|
||||
mutex_init(&asus->wmi_lock);
|
||||
|
||||
result = asus_new_rfkill(asus, &asus->wlan, "asus-wlan",
|
||||
RFKILL_TYPE_WLAN, ASUS_WMI_DEVID_WLAN);
|
||||
|
|
@ -3714,6 +3737,33 @@ static int fan_curve_write(struct asus_wmi *asus,
|
|||
arg1, arg2, arg3, arg4, &ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* A fan curve is a set of points the firmware interpolates between, so it
|
||||
* only makes sense if neither temperature nor PWM ever decreases along it.
|
||||
*/
|
||||
static int fan_curve_validate(struct device *dev, struct fan_curve_data *data)
|
||||
{
|
||||
u8 *percents = data->percents;
|
||||
u8 *temps = data->temps;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < FAN_CURVE_POINTS; i++) {
|
||||
if (temps[i] < temps[i - 1]) {
|
||||
dev_warn(dev, "fan curve: temperature decreases at point %d (%u < %u)\n",
|
||||
i, temps[i], temps[i - 1]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (percents[i] < percents[i - 1]) {
|
||||
dev_warn(dev, "fan curve: pwm decreases at point %d (%u < %u)\n",
|
||||
i, percents[i], percents[i - 1]);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t fan_curve_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
|
|
@ -3798,6 +3848,11 @@ static ssize_t fan_curve_enable_store(struct device *dev,
|
|||
}
|
||||
|
||||
if (data->enabled) {
|
||||
err = fan_curve_validate(dev, data);
|
||||
if (err) {
|
||||
data->enabled = false;
|
||||
return err;
|
||||
}
|
||||
err = fan_curve_write(asus, data);
|
||||
if (err)
|
||||
return err;
|
||||
|
|
@ -4297,7 +4352,6 @@ static int platform_profile_setup(struct asus_wmi *asus)
|
|||
return PTR_ERR(asus->ppdev);
|
||||
}
|
||||
|
||||
asus->platform_profile_support = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -4486,13 +4540,20 @@ static int is_display_toggle(int code)
|
|||
|
||||
static int read_screenpad_backlight_power(struct asus_wmi *asus)
|
||||
{
|
||||
int ret;
|
||||
int ret, retval;
|
||||
|
||||
ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER);
|
||||
ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_POWER, &retval);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* 1 == powered */
|
||||
return ret ? BACKLIGHT_POWER_ON : BACKLIGHT_POWER_OFF;
|
||||
|
||||
/*
|
||||
* The firmware reports the panel power in the low byte of the
|
||||
* devstate as a raw EC status byte that is non-zero when the
|
||||
* panel is powered; other models report it through
|
||||
* ASUS_WMI_DSTS_STATUS_BIT, which lies inside the same mask.
|
||||
*/
|
||||
return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK) ?
|
||||
BACKLIGHT_POWER_ON : BACKLIGHT_POWER_OFF;
|
||||
}
|
||||
|
||||
static int read_screenpad_brightness(struct backlight_device *bd)
|
||||
|
|
@ -4517,26 +4578,18 @@ static int read_screenpad_brightness(struct backlight_device *bd)
|
|||
|
||||
static int update_screenpad_bl_status(struct backlight_device *bd)
|
||||
{
|
||||
u32 ctrl_param = bd->props.brightness;
|
||||
int err = 0;
|
||||
int err;
|
||||
|
||||
if (bd->props.power) {
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (backlight_is_blank(bd))
|
||||
return asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER,
|
||||
0, NULL);
|
||||
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (!bd->props.power) {
|
||||
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
return asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT,
|
||||
backlight_get_brightness(bd), NULL);
|
||||
}
|
||||
|
||||
static const struct backlight_ops asus_screenpad_bl_ops = {
|
||||
|
|
@ -4550,16 +4603,17 @@ static int asus_screenpad_init(struct asus_wmi *asus)
|
|||
struct backlight_device *bd;
|
||||
struct backlight_properties props;
|
||||
int err, power;
|
||||
int brightness = 0;
|
||||
u32 brightness = 0;
|
||||
|
||||
power = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER);
|
||||
power = read_screenpad_backlight_power(asus);
|
||||
if (power < 0)
|
||||
return power;
|
||||
|
||||
if (power) {
|
||||
if (power == BACKLIGHT_POWER_ON) {
|
||||
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness);
|
||||
if (err < 0)
|
||||
return err;
|
||||
brightness &= ASUS_WMI_DSTS_BRIGHTNESS_MASK;
|
||||
}
|
||||
|
||||
memset(&props, 0, sizeof(struct backlight_properties));
|
||||
|
|
@ -4574,7 +4628,6 @@ static int asus_screenpad_init(struct asus_wmi *asus)
|
|||
}
|
||||
|
||||
asus->screenpad_backlight_device = bd;
|
||||
asus->driver->screenpad_brightness = brightness;
|
||||
bd->props.brightness = brightness;
|
||||
bd->props.power = power;
|
||||
backlight_update_status(bd);
|
||||
|
|
@ -5026,9 +5079,8 @@ static int show_call(struct seq_file *m, void *data)
|
|||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
|
||||
status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID,
|
||||
0, asus->debug.method_id,
|
||||
&input, &output);
|
||||
status = asus_wmi_evaluate_method_locked(asus->debug.method_id,
|
||||
&input, &output);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
|
@ -5153,7 +5205,7 @@ static int asus_wmi_add(struct platform_device *pdev)
|
|||
|
||||
asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
|
||||
asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
|
||||
asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
|
||||
asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_CMD);
|
||||
|
||||
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE))
|
||||
asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE;
|
||||
|
|
@ -5421,8 +5473,11 @@ static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = {
|
|||
|
||||
static void asus_s2idle_check_register(void)
|
||||
{
|
||||
if (acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops))
|
||||
pr_warn("failed to register LPS0 sleep handler in asus-wmi\n");
|
||||
int ret;
|
||||
|
||||
ret = acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops);
|
||||
if (ret && ret != -ENODEV)
|
||||
pr_warn("failed to register LPS0 sleep handler: %d\n", ret);
|
||||
}
|
||||
|
||||
static void asus_s2idle_check_unregister(void)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ struct quirk_entry {
|
|||
struct asus_wmi_driver {
|
||||
int brightness;
|
||||
int panel_power;
|
||||
int screenpad_brightness;
|
||||
int wlan_ctrl_by_user;
|
||||
|
||||
const char *name;
|
||||
|
|
|
|||
|
|
@ -76,6 +76,13 @@ static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
|
|||
};
|
||||
|
||||
static const struct dmi_system_id dmi_table[] = {
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 2S"),
|
||||
},
|
||||
.driver_data = (void *)&quirk_charge_limit,
|
||||
},
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include <linux/stddef.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/unaligned.h>
|
||||
#include <linux/units.h>
|
||||
#include <linux/wmi.h>
|
||||
|
|
@ -39,25 +40,30 @@
|
|||
#define BITLAND_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF"
|
||||
|
||||
enum bitland_mifs_operation {
|
||||
WMI_METHOD_GET = 250,
|
||||
WMI_METHOD_SET = 251,
|
||||
WMI_METHOD_GET = 0xFA00,
|
||||
WMI_METHOD_SET = 0xFB00,
|
||||
};
|
||||
|
||||
enum bitland_mifs_function {
|
||||
WMI_FN_SYSTEM_PER_MODE = 8,
|
||||
WMI_FN_GPU_MODE = 9,
|
||||
WMI_FN_KBD_TYPE = 10,
|
||||
WMI_FN_FN_LOCK = 11,
|
||||
WMI_FN_TP_LOCK = 12,
|
||||
WMI_FN_FAN_SPEEDS = 13,
|
||||
WMI_FN_RGB_KB_MODE = 16,
|
||||
WMI_FN_RGB_KB_COLOR = 17,
|
||||
WMI_FN_RGB_KB_BRIGHTNESS = 18,
|
||||
WMI_FN_SYSTEM_AC_TYPE = 19,
|
||||
WMI_FN_MAX_FAN_SWITCH = 20,
|
||||
WMI_FN_MAX_FAN_SPEED = 21,
|
||||
WMI_FN_CPU_THERMOMETER = 22,
|
||||
WMI_FN_CPU_POWER = 23,
|
||||
WMI_FN_SYSTEM_PER_MODE = 0x0800,
|
||||
WMI_FN_GPU_MODE = 0x0900,
|
||||
WMI_FN_KBD_TYPE = 0x0A00,
|
||||
WMI_FN_FN_LOCK = 0x0B00,
|
||||
WMI_FN_TP_LOCK = 0x0C00,
|
||||
WMI_FN_FAN_SPEEDS = 0x0D00,
|
||||
WMI_FN_RGB_KB_MODE = 0x1000,
|
||||
WMI_FN_RGB_KB_COLOR = 0x1100,
|
||||
WMI_FN_RGB_KB_BRIGHTNESS = 0x1200,
|
||||
WMI_FN_SYSTEM_AC_TYPE = 0x1300,
|
||||
WMI_FN_MAX_FAN_SWITCH = 0x1400,
|
||||
WMI_FN_MAX_FAN_SPEED = 0x1500,
|
||||
WMI_FN_CPU_THERMOMETER = 0x1600,
|
||||
WMI_FN_CPU_POWER = 0x1700,
|
||||
};
|
||||
|
||||
enum bitland_mifs_return_code {
|
||||
WMI_RETURN_CODE_SUCCESS = 0x8000,
|
||||
WMI_RETURN_CODE_UNKNOWN_FUNCTION = 0xE000,
|
||||
};
|
||||
|
||||
enum bitland_system_ac_mode {
|
||||
|
|
@ -114,18 +120,14 @@ enum bitland_wmi_device_type {
|
|||
};
|
||||
|
||||
struct bitland_mifs_input {
|
||||
u8 reserved1;
|
||||
u8 operation;
|
||||
u8 reserved2;
|
||||
u8 function;
|
||||
__le16 operation;
|
||||
__le16 function;
|
||||
u8 payload[28];
|
||||
} __packed;
|
||||
|
||||
struct bitland_mifs_output {
|
||||
u8 reserved1;
|
||||
u8 operation;
|
||||
u8 reserved2;
|
||||
u8 function;
|
||||
__le16 return_code;
|
||||
__le16 function;
|
||||
u8 data[28];
|
||||
} __packed;
|
||||
|
||||
|
|
@ -171,15 +173,27 @@ static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data,
|
|||
|
||||
guard(mutex)(&data->lock);
|
||||
|
||||
if (!output)
|
||||
return wmidev_invoke_procedure(data->wdev, 0, 1, &in_buf);
|
||||
|
||||
ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, sizeof(*output));
|
||||
ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf,
|
||||
sizeof(struct bitland_mifs_output));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memcpy(output, out_buf.data, sizeof(*output));
|
||||
kfree(out_buf.data);
|
||||
struct bitland_mifs_output *result __free(kfree) = out_buf.data;
|
||||
|
||||
switch (le16_to_cpu(result->return_code)) {
|
||||
case WMI_RETURN_CODE_SUCCESS:
|
||||
break;
|
||||
case WMI_RETURN_CODE_UNKNOWN_FUNCTION:
|
||||
return -EOPNOTSUPP;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (result->function != input->function)
|
||||
return -ENOMSG;
|
||||
|
||||
if (output)
|
||||
memcpy(output, result, sizeof(*output));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -189,10 +203,8 @@ static int laptop_profile_get(struct device *dev,
|
|||
{
|
||||
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_GET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_SYSTEM_PER_MODE,
|
||||
.operation = cpu_to_le16(WMI_METHOD_GET),
|
||||
.function = cpu_to_le16(WMI_FN_SYSTEM_PER_MODE),
|
||||
};
|
||||
struct bitland_mifs_output result;
|
||||
int ret;
|
||||
|
|
@ -223,8 +235,8 @@ static int laptop_profile_get(struct device *dev,
|
|||
static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data)
|
||||
{
|
||||
struct bitland_mifs_input input = {
|
||||
.operation = WMI_METHOD_GET,
|
||||
.function = WMI_FN_SYSTEM_AC_TYPE,
|
||||
.operation = cpu_to_le16(WMI_METHOD_GET),
|
||||
.function = cpu_to_le16(WMI_FN_SYSTEM_AC_TYPE),
|
||||
};
|
||||
struct bitland_mifs_output output;
|
||||
int ret;
|
||||
|
|
@ -234,7 +246,11 @@ static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *da
|
|||
return -EOPNOTSUPP;
|
||||
|
||||
ret = bitland_mifs_wmi_call(data, &input, &output);
|
||||
if (ret)
|
||||
/* Not all systems support this function, do not perform further checks on them */
|
||||
if (ret == -EOPNOTSUPP)
|
||||
return 0;
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (output.data[0] != WMI_SYSTEM_AC_CIRCULARHOLE)
|
||||
|
|
@ -248,10 +264,8 @@ static int laptop_profile_set(struct device *dev,
|
|||
{
|
||||
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_SET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_SYSTEM_PER_MODE,
|
||||
.operation = cpu_to_le16(WMI_METHOD_SET),
|
||||
.function = cpu_to_le16(WMI_FN_SYSTEM_PER_MODE),
|
||||
};
|
||||
int ret;
|
||||
u8 val;
|
||||
|
|
@ -344,22 +358,20 @@ static int laptop_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
|||
{
|
||||
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_GET,
|
||||
.reserved2 = 0,
|
||||
.operation = cpu_to_le16(WMI_METHOD_GET),
|
||||
};
|
||||
struct bitland_mifs_output res;
|
||||
int ret;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
input.function = WMI_FN_CPU_THERMOMETER;
|
||||
input.function = cpu_to_le16(WMI_FN_CPU_THERMOMETER);
|
||||
ret = bitland_mifs_wmi_call(data, &input, &res);
|
||||
if (!ret)
|
||||
*val = res.data[0] * MILLIDEGREE_PER_DEGREE;
|
||||
return ret;
|
||||
case hwmon_fan:
|
||||
input.function = WMI_FN_FAN_SPEEDS;
|
||||
input.function = cpu_to_le16(WMI_FN_FAN_SPEEDS);
|
||||
ret = bitland_mifs_wmi_call(data, &input, &res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -420,10 +432,8 @@ static int laptop_kbd_led_set(struct led_classdev *led_cdev,
|
|||
struct bitland_mifs_wmi_data *data =
|
||||
container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_SET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_RGB_KB_BRIGHTNESS,
|
||||
.operation = cpu_to_le16(WMI_METHOD_SET),
|
||||
.function = cpu_to_le16(WMI_FN_RGB_KB_BRIGHTNESS),
|
||||
};
|
||||
|
||||
input.payload[0] = (u8)value;
|
||||
|
|
@ -436,10 +446,8 @@ static enum led_brightness laptop_kbd_led_get(struct led_classdev *led_cdev)
|
|||
struct bitland_mifs_wmi_data *data =
|
||||
container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_GET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_RGB_KB_BRIGHTNESS,
|
||||
.operation = cpu_to_le16(WMI_METHOD_GET),
|
||||
.function = cpu_to_le16(WMI_FN_RGB_KB_BRIGHTNESS),
|
||||
};
|
||||
struct bitland_mifs_output res;
|
||||
int ret;
|
||||
|
|
@ -463,10 +471,8 @@ static ssize_t gpu_mode_show(struct device *dev, struct device_attribute *attr,
|
|||
{
|
||||
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_GET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_GPU_MODE,
|
||||
.operation = cpu_to_le16(WMI_METHOD_GET),
|
||||
.function = cpu_to_le16(WMI_FN_GPU_MODE),
|
||||
};
|
||||
struct bitland_mifs_output res;
|
||||
u8 mode_val;
|
||||
|
|
@ -488,10 +494,8 @@ static ssize_t gpu_mode_store(struct device *dev, struct device_attribute *attr,
|
|||
{
|
||||
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_SET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_GPU_MODE,
|
||||
.operation = cpu_to_le16(WMI_METHOD_SET),
|
||||
.function = cpu_to_le16(WMI_FN_GPU_MODE),
|
||||
};
|
||||
int val;
|
||||
int ret;
|
||||
|
|
@ -521,10 +525,8 @@ static ssize_t kb_mode_show(struct device *dev, struct device_attribute *attr,
|
|||
{
|
||||
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_GET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_RGB_KB_MODE,
|
||||
.operation = cpu_to_le16(WMI_METHOD_GET),
|
||||
.function = cpu_to_le16(WMI_FN_RGB_KB_MODE),
|
||||
};
|
||||
struct bitland_mifs_output res;
|
||||
u8 mode_val;
|
||||
|
|
@ -546,10 +548,8 @@ static ssize_t kb_mode_store(struct device *dev, struct device_attribute *attr,
|
|||
{
|
||||
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_SET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_RGB_KB_MODE,
|
||||
.operation = cpu_to_le16(WMI_METHOD_SET),
|
||||
.function = cpu_to_le16(WMI_FN_RGB_KB_MODE),
|
||||
};
|
||||
// the wmi value (0, 1, 2 or 3)
|
||||
int val;
|
||||
|
|
@ -575,10 +575,8 @@ static ssize_t fan_boost_store(struct device *dev,
|
|||
{
|
||||
struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
|
||||
struct bitland_mifs_input input = {
|
||||
.reserved1 = 0,
|
||||
.operation = WMI_METHOD_SET,
|
||||
.reserved2 = 0,
|
||||
.function = WMI_FN_MAX_FAN_SWITCH,
|
||||
.operation = cpu_to_le16(WMI_METHOD_SET),
|
||||
.function = cpu_to_le16(WMI_FN_MAX_FAN_SWITCH),
|
||||
};
|
||||
bool val;
|
||||
int ret;
|
||||
|
|
|
|||
|
|
@ -121,6 +121,14 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
|
|||
},
|
||||
.driver_data = &g_series_quirks,
|
||||
},
|
||||
{
|
||||
.ident = "Alienware Area-51 AAT2250",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware Area-51 AAT2250"),
|
||||
},
|
||||
.driver_data = &generic_quirks,
|
||||
},
|
||||
{
|
||||
.ident = "Alienware Area-51m",
|
||||
.matches = {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gfp_types.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
|
|
@ -15,15 +17,23 @@
|
|||
|
||||
#define PALC_DSM_FN_TRIGGER_PLDR 1
|
||||
|
||||
static const u64 palc_dsm_revs[] = { 1, 0 };
|
||||
|
||||
static guid_t palc_dsm_guid =
|
||||
GUID_INIT(0x5a1a4bba, 0x8006, 0x487e, 0xbe, 0x0a, 0xac, 0xf5, 0xd8, 0xfd, 0xfe, 0x59);
|
||||
|
||||
struct palc_data {
|
||||
u64 dsm_rev;
|
||||
};
|
||||
|
||||
static int trigger_palc_pldr(struct device *dev, acpi_handle handle)
|
||||
{
|
||||
struct palc_data *data = dev_get_drvdata(dev);
|
||||
union acpi_object *obj;
|
||||
int ret = 0;
|
||||
|
||||
obj = acpi_evaluate_dsm(handle, &palc_dsm_guid, 1, PALC_DSM_FN_TRIGGER_PLDR, NULL);
|
||||
obj = acpi_evaluate_dsm(handle, &palc_dsm_guid, data->dsm_rev,
|
||||
PALC_DSM_FN_TRIGGER_PLDR, NULL);
|
||||
if (!obj) {
|
||||
dev_err(dev, "Failed to evaluate _DSM\n");
|
||||
return -EIO;
|
||||
|
|
@ -60,16 +70,28 @@ ATTRIBUTE_GROUPS(palc);
|
|||
|
||||
static int palc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct palc_data *data;
|
||||
acpi_handle handle;
|
||||
unsigned int i;
|
||||
|
||||
handle = ACPI_HANDLE(&pdev->dev);
|
||||
if (!handle)
|
||||
return -ENODEV;
|
||||
|
||||
if (!acpi_check_dsm(handle, &palc_dsm_guid, 1, BIT(PALC_DSM_FN_TRIGGER_PLDR)))
|
||||
return -ENODEV;
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
for (i = 0; i < ARRAY_SIZE(palc_dsm_revs); i++) {
|
||||
if (acpi_check_dsm(handle, &palc_dsm_guid, palc_dsm_revs[i],
|
||||
BIT(PALC_DSM_FN_TRIGGER_PLDR))) {
|
||||
data->dsm_rev = palc_dsm_revs[i];
|
||||
platform_set_drvdata(pdev, data);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id palc_acpi_ids[] = {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,20 @@
|
|||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/device/driver.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input/sparse-keymap.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -20,22 +26,22 @@ MODULE_LICENSE("GPL");
|
|||
#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4"
|
||||
#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8"
|
||||
|
||||
struct dell_wmi_aio_data {
|
||||
struct input_dev *input_device;
|
||||
/* Protects the input sequence */
|
||||
struct mutex input_lock;
|
||||
};
|
||||
|
||||
/* A hot key was pressed or an event occurred */
|
||||
#define DELL_AIO_EVENT_TYPE_SINGLE 0x0000
|
||||
/* A sequence of hot keys was pressed */
|
||||
#define DELL_AIO_EVENT_TYPE_SEQUENCE 0x000F
|
||||
|
||||
struct dell_wmi_event {
|
||||
u16 length;
|
||||
/* 0x000: A hot key pressed or an event occurred
|
||||
* 0x00F: A sequence of hot keys are pressed */
|
||||
u16 type;
|
||||
u16 event[];
|
||||
};
|
||||
|
||||
static const char *dell_wmi_aio_guids[] = {
|
||||
EVENT_GUID1,
|
||||
EVENT_GUID2,
|
||||
NULL
|
||||
};
|
||||
|
||||
MODULE_ALIAS("wmi:"EVENT_GUID1);
|
||||
MODULE_ALIAS("wmi:"EVENT_GUID2);
|
||||
__le16 length;
|
||||
__le16 type;
|
||||
__le16 event[];
|
||||
} __packed;
|
||||
|
||||
static const struct key_entry dell_wmi_aio_keymap[] = {
|
||||
{ KE_KEY, 0xc0, { KEY_VOLUMEUP } },
|
||||
|
|
@ -50,137 +56,96 @@ static const struct key_entry dell_wmi_aio_keymap[] = {
|
|||
{ KE_END, 0 }
|
||||
};
|
||||
|
||||
static struct input_dev *dell_wmi_aio_input_dev;
|
||||
|
||||
/*
|
||||
* The new WMI event data format will follow the dell_wmi_event structure
|
||||
* So, we will check if the buffer matches the format
|
||||
*/
|
||||
static bool dell_wmi_aio_event_check(u8 *buffer, int length)
|
||||
{
|
||||
struct dell_wmi_event *event = (struct dell_wmi_event *)buffer;
|
||||
|
||||
if (event == NULL || length < 6)
|
||||
return false;
|
||||
|
||||
if ((event->type == 0 || event->type == 0xf) &&
|
||||
event->length >= 2)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void dell_wmi_aio_notify(union acpi_object *obj, void *context)
|
||||
static bool dell_wmi_aio_event_check(const struct wmi_buffer *buffer)
|
||||
{
|
||||
struct dell_wmi_event *event;
|
||||
u16 length, type;
|
||||
|
||||
if (obj) {
|
||||
unsigned int scancode = 0;
|
||||
if (buffer->length < struct_size(event, event, 1))
|
||||
return false;
|
||||
|
||||
switch (obj->type) {
|
||||
case ACPI_TYPE_INTEGER:
|
||||
/* Most All-In-One correctly return integer scancode */
|
||||
scancode = obj->integer.value;
|
||||
sparse_keymap_report_event(dell_wmi_aio_input_dev,
|
||||
scancode, 1, true);
|
||||
break;
|
||||
case ACPI_TYPE_BUFFER:
|
||||
if (dell_wmi_aio_event_check(obj->buffer.pointer,
|
||||
obj->buffer.length)) {
|
||||
event = (struct dell_wmi_event *)
|
||||
obj->buffer.pointer;
|
||||
scancode = event->event[0];
|
||||
} else {
|
||||
/* Broken machines return the scancode in a
|
||||
buffer */
|
||||
if (obj->buffer.pointer &&
|
||||
obj->buffer.length > 0)
|
||||
scancode = obj->buffer.pointer[0];
|
||||
}
|
||||
if (scancode)
|
||||
sparse_keymap_report_event(
|
||||
dell_wmi_aio_input_dev,
|
||||
scancode, 1, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
event = buffer->data;
|
||||
length = le16_to_cpu(event->length);
|
||||
type = le16_to_cpu(event->type);
|
||||
|
||||
if (type != DELL_AIO_EVENT_TYPE_SINGLE && type != DELL_AIO_EVENT_TYPE_SEQUENCE)
|
||||
return false;
|
||||
|
||||
if (length < 2)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int __init dell_wmi_aio_input_setup(void)
|
||||
static void dell_wmi_aio_notify(struct wmi_device *wdev, const struct wmi_buffer *data)
|
||||
{
|
||||
int err;
|
||||
struct dell_wmi_aio_data *drvdata = dev_get_drvdata(&wdev->dev);
|
||||
const struct dell_wmi_event *new_event;
|
||||
unsigned int scancode;
|
||||
const u8 *old_event;
|
||||
|
||||
dell_wmi_aio_input_dev = input_allocate_device();
|
||||
if (dell_wmi_aio_event_check(data)) {
|
||||
new_event = data->data;
|
||||
scancode = le16_to_cpu(new_event->event[0]);
|
||||
} else {
|
||||
old_event = data->data;
|
||||
scancode = old_event[0];
|
||||
}
|
||||
|
||||
if (!dell_wmi_aio_input_dev)
|
||||
guard(mutex)(&drvdata->input_lock);
|
||||
|
||||
sparse_keymap_report_event(drvdata->input_device, scancode, 1, true);
|
||||
}
|
||||
|
||||
static int dell_wmi_aio_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct dell_wmi_aio_data *data;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys";
|
||||
dell_wmi_aio_input_dev->phys = "wmi/input0";
|
||||
dell_wmi_aio_input_dev->id.bustype = BUS_HOST;
|
||||
dev_set_drvdata(&wdev->dev, data);
|
||||
ret = devm_mutex_init(&wdev->dev, &data->input_lock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
err = sparse_keymap_setup(dell_wmi_aio_input_dev,
|
||||
dell_wmi_aio_keymap, NULL);
|
||||
if (err) {
|
||||
pr_err("Unable to setup input device keymap\n");
|
||||
goto err_free_dev;
|
||||
}
|
||||
err = input_register_device(dell_wmi_aio_input_dev);
|
||||
if (err) {
|
||||
pr_info("Unable to register input device\n");
|
||||
goto err_free_dev;
|
||||
}
|
||||
return 0;
|
||||
data->input_device = devm_input_allocate_device(&wdev->dev);
|
||||
if (!data->input_device)
|
||||
return -ENOMEM;
|
||||
|
||||
err_free_dev:
|
||||
input_free_device(dell_wmi_aio_input_dev);
|
||||
return err;
|
||||
data->input_device->name = "Dell AIO WMI hotkeys";
|
||||
data->input_device->phys = "wmi/input0";
|
||||
data->input_device->id.bustype = BUS_HOST;
|
||||
|
||||
ret = sparse_keymap_setup(data->input_device, dell_wmi_aio_keymap, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return input_register_device(data->input_device);
|
||||
}
|
||||
|
||||
static const char *dell_wmi_aio_find(void)
|
||||
{
|
||||
int i;
|
||||
static const struct wmi_device_id dell_wmi_aio_id_table[] = {
|
||||
{ EVENT_GUID1, NULL },
|
||||
{ EVENT_GUID2, NULL },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(wmi, dell_wmi_aio_id_table);
|
||||
|
||||
for (i = 0; dell_wmi_aio_guids[i] != NULL; i++)
|
||||
if (wmi_has_guid(dell_wmi_aio_guids[i]))
|
||||
return dell_wmi_aio_guids[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int __init dell_wmi_aio_init(void)
|
||||
{
|
||||
int err;
|
||||
const char *guid;
|
||||
|
||||
guid = dell_wmi_aio_find();
|
||||
if (!guid) {
|
||||
pr_warn("No known WMI GUID found\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
err = dell_wmi_aio_input_setup();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL);
|
||||
if (err) {
|
||||
pr_err("Unable to register notify handler - %d\n", err);
|
||||
input_unregister_device(dell_wmi_aio_input_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit dell_wmi_aio_exit(void)
|
||||
{
|
||||
const char *guid;
|
||||
|
||||
guid = dell_wmi_aio_find();
|
||||
wmi_remove_notify_handler(guid);
|
||||
input_unregister_device(dell_wmi_aio_input_dev);
|
||||
}
|
||||
|
||||
module_init(dell_wmi_aio_init);
|
||||
module_exit(dell_wmi_aio_exit);
|
||||
static struct wmi_driver dell_wmi_aio_driver = {
|
||||
.driver = {
|
||||
.name = "dell-wmi-aio",
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
},
|
||||
.id_table = dell_wmi_aio_id_table,
|
||||
.probe = dell_wmi_aio_probe,
|
||||
.notify_new = dell_wmi_aio_notify,
|
||||
.min_event_size = sizeof(u8),
|
||||
.no_singleton = true,
|
||||
};
|
||||
module_wmi_driver(dell_wmi_aio_driver);
|
||||
|
|
|
|||
|
|
@ -153,10 +153,6 @@ static int gpd_pocket_fan_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
|
||||
fan->dev = &pdev->dev;
|
||||
ret = devm_delayed_work_autocancel(&pdev->dev, &fan->work,
|
||||
gpd_pocket_fan_worker);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Note this returns a "weak" reference which we don't need to free */
|
||||
fan->dts0 = thermal_zone_get_zone_by_name("soc_dts0");
|
||||
|
|
@ -175,6 +171,11 @@ static int gpd_pocket_fan_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(fan->gpio1))
|
||||
return PTR_ERR(fan->gpio1);
|
||||
|
||||
ret = devm_delayed_work_autocancel(&pdev->dev, &fan->work,
|
||||
gpd_pocket_fan_worker);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
gpd_pocket_fan_force_update(fan);
|
||||
|
||||
platform_set_drvdata(pdev, fan);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/string_helpers.h>
|
||||
#include <linux/wmi.h>
|
||||
#include "bioscfg.h"
|
||||
#include <linux/nls.h>
|
||||
|
|
@ -53,74 +53,44 @@ int hp_get_integer_from_buffer(u8 **buffer, u32 *buffer_size, u32 *integer)
|
|||
|
||||
int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size)
|
||||
{
|
||||
char utf8_buf[MAX_BUFF_SIZE];
|
||||
u16 *src = (u16 *)*buffer;
|
||||
int escaped_len;
|
||||
u16 orig_size;
|
||||
u16 src_size;
|
||||
|
||||
u16 size;
|
||||
int i;
|
||||
int conv_dst_size;
|
||||
int utf8_len;
|
||||
|
||||
if (*buffer_size < sizeof(u16))
|
||||
return -EINVAL;
|
||||
|
||||
src_size = *(src++);
|
||||
/* size value in u16 chars */
|
||||
size = src_size / sizeof(u16);
|
||||
src_size = *src;
|
||||
|
||||
/* Ensure there is enough space remaining to read and convert
|
||||
* the string
|
||||
/*
|
||||
* Ensure there is enough space remaining for the length prefix
|
||||
* just read plus the string data it describes.
|
||||
*/
|
||||
if (*buffer_size < src_size)
|
||||
if (*buffer_size < sizeof(u16) + src_size)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
if (src[i] == '\\' ||
|
||||
src[i] == '\r' ||
|
||||
src[i] == '\n' ||
|
||||
src[i] == '\t')
|
||||
size++;
|
||||
src++;
|
||||
/* size value in u16 chars */
|
||||
orig_size = src_size / sizeof(u16);
|
||||
|
||||
/*
|
||||
* Conversion is limited to destination string max number of
|
||||
* bytes.
|
||||
*/
|
||||
conv_dst_size = size;
|
||||
if (size >= dst_size)
|
||||
conv_dst_size = dst_size - 1;
|
||||
utf8_len = utf16s_to_utf8s(src, orig_size, UTF16_HOST_ENDIAN,
|
||||
utf8_buf, sizeof(utf8_buf));
|
||||
|
||||
/*
|
||||
* convert from UTF-16 unicode to ASCII
|
||||
*/
|
||||
utf16s_to_utf8s(src, src_size, UTF16_HOST_ENDIAN, dst, conv_dst_size);
|
||||
dst[conv_dst_size] = 0;
|
||||
escaped_len = string_escape_mem(utf8_buf, utf8_len, dst, dst_size - 1,
|
||||
ESCAPE_SPACE | ESCAPE_SPECIAL,
|
||||
"\\\r\n\t");
|
||||
if (escaped_len > dst_size - 1)
|
||||
escaped_len = dst_size - 1;
|
||||
dst[escaped_len] = '\0';
|
||||
strreplace(dst, '"', '\'');
|
||||
|
||||
for (i = 0; i < conv_dst_size; i++) {
|
||||
if (*src == '\\' ||
|
||||
*src == '\r' ||
|
||||
*src == '\n' ||
|
||||
*src == '\t') {
|
||||
dst[i++] = '\\';
|
||||
if (i == conv_dst_size)
|
||||
break;
|
||||
}
|
||||
*buffer += sizeof(u16) + src_size;
|
||||
*buffer_size -= sizeof(u16) + src_size;
|
||||
|
||||
if (*src == '\r')
|
||||
dst[i] = 'r';
|
||||
else if (*src == '\n')
|
||||
dst[i] = 'n';
|
||||
else if (*src == '\t')
|
||||
dst[i] = 't';
|
||||
else if (*src == '"')
|
||||
dst[i] = '\'';
|
||||
else
|
||||
dst[i] = *src;
|
||||
src++;
|
||||
}
|
||||
|
||||
*buffer = (u8 *)src;
|
||||
*buffer_size -= size * sizeof(u16);
|
||||
|
||||
return size;
|
||||
return escaped_len;
|
||||
}
|
||||
|
||||
int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size,
|
||||
|
|
@ -442,7 +412,7 @@ int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *
|
|||
*len = 0;
|
||||
*str = NULL;
|
||||
|
||||
new_str = kmalloc(input_len, GFP_KERNEL);
|
||||
new_str = kzalloc(2 * DIV_ROUND_UP(input_len, 5) + 1, GFP_KERNEL);
|
||||
if (!new_str)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -484,6 +454,103 @@ int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hp_get_common_data_from_package() - Parse the package elements shared
|
||||
* by every attribute type (PATH..SECURITY_LEVEL)
|
||||
* @obj: ACPI object package for this attribute instance
|
||||
* @obj_count: Number of elements in @obj
|
||||
* @elem: Current position in @obj; advanced in place when PREREQUISITES
|
||||
* consumes additional array elements
|
||||
* @eloc: Current common-element locator; advanced in place when
|
||||
* PREREQUISITES_SIZE is zero, since PREREQUISITES is then omitted
|
||||
* by BIOS
|
||||
* @int_value: Integer value already decoded by the caller for *@eloc,
|
||||
* when applicable
|
||||
* @str_value: String value already decoded by the caller for *@eloc, when
|
||||
* applicable; reused and reset to NULL while walking
|
||||
* PREREQUISITES, same as the caller does with its own copy
|
||||
* @common: Destination common_data struct for this attribute instance
|
||||
*
|
||||
* Called by each type's own hp_populate_*_elements_from_package()
|
||||
* instead of duplicating this parsing per type, since every BIOS
|
||||
* attribute package uses the same layout for these elements (see
|
||||
* struct common_data).
|
||||
*
|
||||
* Return: 0 on success, or -EINVAL if @obj is too small for the
|
||||
* declared PREREQUISITES_SIZE.
|
||||
*/
|
||||
int hp_get_common_data_from_package(union acpi_object *obj, int obj_count,
|
||||
int *elem, int *eloc, u32 int_value,
|
||||
char **str_value, struct common_data *common)
|
||||
{
|
||||
int value_len;
|
||||
int reqs;
|
||||
int size;
|
||||
|
||||
switch (*eloc) {
|
||||
case PATH:
|
||||
strscpy(common->path, *str_value);
|
||||
break;
|
||||
case IS_READONLY:
|
||||
common->is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
common->display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
common->requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
common->sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
common->prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (common->prerequisites_size == 0)
|
||||
(*eloc)++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, common->prerequisites_size, MAX_PREREQUISITES_SIZE);
|
||||
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (*elem + reqs >= obj_count) {
|
||||
pr_err("Error elem-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hp_convert_hexstr_to_str(obj[*elem + reqs].string.pointer,
|
||||
obj[*elem + reqs].string.length,
|
||||
str_value, &value_len))
|
||||
continue;
|
||||
|
||||
strscpy(common->prerequisites[reqs], *str_value);
|
||||
kfree(*str_value);
|
||||
*str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
*elem += size - 1;
|
||||
break;
|
||||
case SECURITY_LEVEL:
|
||||
common->security_level = int_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* map output size to the corresponding WMI method id */
|
||||
int hp_encode_outsize_for_pvsz(int outsize)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -493,5 +493,8 @@ void hp_friendly_user_name_update(char *path, const char *attr_name,
|
|||
char *attr_display, int attr_size);
|
||||
int hp_wmi_error_and_message(int error_code);
|
||||
int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size, struct common_data *common);
|
||||
int hp_get_common_data_from_package(union acpi_object *obj, int obj_count,
|
||||
int *elem, int *eloc, u32 int_value,
|
||||
char **str_value, struct common_data *common);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
|
|||
u32 size = 0;
|
||||
u32 int_value = 0;
|
||||
int elem = 0;
|
||||
int reqs;
|
||||
int pos_values;
|
||||
int ret;
|
||||
int eloc;
|
||||
|
|
@ -176,64 +175,18 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
|
|||
case VALUE:
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(enum_data->common.path, str_value);
|
||||
break;
|
||||
case IS_READONLY:
|
||||
enum_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
enum_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
enum_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
enum_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
enum_data->common.prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, enum_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE);
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (elem + reqs >= enum_obj_count) {
|
||||
pr_err("Error enum-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(enum_obj[elem + reqs].string.pointer,
|
||||
enum_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
strscpy(enum_data->common.prerequisites[reqs], str_value);
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
enum_data->common.security_level = int_value;
|
||||
ret = hp_get_common_data_from_package(enum_obj, enum_obj_count,
|
||||
&elem, &eloc, int_value, &str_value,
|
||||
&enum_data->common);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
case ENUM_CURRENT_VALUE:
|
||||
|
|
|
|||
|
|
@ -145,9 +145,7 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_
|
|||
int ret;
|
||||
u32 int_value = 0;
|
||||
int elem;
|
||||
int reqs;
|
||||
int eloc;
|
||||
int size;
|
||||
struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
|
||||
|
||||
if (!integer_obj)
|
||||
|
|
@ -192,63 +190,18 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_
|
|||
integer_data->current_value = int_value;
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(integer_data->common.path, str_value);
|
||||
break;
|
||||
case IS_READONLY:
|
||||
integer_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
integer_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
integer_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
integer_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
integer_data->common.prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (integer_data->common.prerequisites_size == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, integer_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE);
|
||||
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (elem + reqs >= integer_obj_count) {
|
||||
pr_err("Error elem-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(integer_obj[elem + reqs].string.pointer,
|
||||
integer_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
strscpy(integer_data->common.prerequisites[reqs], str_value);
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
integer_data->common.security_level = int_value;
|
||||
ret = hp_get_common_data_from_package(integer_obj, integer_obj_count,
|
||||
&elem, &eloc, int_value, &str_value,
|
||||
&integer_data->common);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case INT_LOWER_BOUND:
|
||||
integer_data->lower_bound = int_value;
|
||||
|
|
@ -331,7 +284,12 @@ static int hp_populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_
|
|||
// VALUE:
|
||||
integer_data->current_value = 0;
|
||||
|
||||
hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
|
||||
ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
|
||||
if (ret < 0) {
|
||||
kfree(dst);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kstrtoint(dst, 10, &integer_data->current_value);
|
||||
if (ret)
|
||||
pr_warn("Unable to convert string to integer: %s\n", dst);
|
||||
|
|
|
|||
|
|
@ -98,8 +98,13 @@ int hp_alloc_ordered_list_data(void)
|
|||
{
|
||||
bioscfg_drv.ordered_list_instances_count =
|
||||
hp_get_instance_count(HP_WMI_BIOS_ORDERED_LIST_GUID);
|
||||
bioscfg_drv.ordered_list_data = kzalloc_objs(*bioscfg_drv.ordered_list_data,
|
||||
bioscfg_drv.ordered_list_instances_count);
|
||||
|
||||
if (!bioscfg_drv.ordered_list_instances_count)
|
||||
return -EINVAL;
|
||||
bioscfg_drv.ordered_list_data =
|
||||
kvzalloc_objs(*bioscfg_drv.ordered_list_data,
|
||||
bioscfg_drv.ordered_list_instances_count);
|
||||
|
||||
if (!bioscfg_drv.ordered_list_data) {
|
||||
bioscfg_drv.ordered_list_instances_count = 0;
|
||||
return -ENOMEM;
|
||||
|
|
@ -130,11 +135,9 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
|
|||
char *str_value = NULL;
|
||||
int value_len = 0;
|
||||
int ret;
|
||||
u32 size;
|
||||
u32 int_value = 0;
|
||||
int elem;
|
||||
int olist_elem;
|
||||
int reqs;
|
||||
int eloc;
|
||||
char *tmpstr = NULL;
|
||||
char *part_tmp = NULL;
|
||||
|
|
@ -180,64 +183,18 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
|
|||
replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP);
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(ordered_list_data->common.path, str_value);
|
||||
break;
|
||||
case IS_READONLY:
|
||||
ordered_list_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
ordered_list_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
ordered_list_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
ordered_list_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
ordered_list_data->common.prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, ordered_list_data->common.prerequisites_size,
|
||||
MAX_PREREQUISITES_SIZE);
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (elem + reqs >= order_obj_count) {
|
||||
pr_err("Error elem-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(order_obj[elem + reqs].string.pointer,
|
||||
order_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
strscpy(ordered_list_data->common.prerequisites[reqs], str_value);
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
ordered_list_data->common.security_level = int_value;
|
||||
ret = hp_get_common_data_from_package(order_obj, order_obj_count,
|
||||
&elem, &eloc, int_value, &str_value,
|
||||
&ordered_list_data->common);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
case ORD_LIST_SIZE:
|
||||
|
|
@ -438,6 +395,6 @@ void hp_exit_ordered_list_attributes(void)
|
|||
}
|
||||
bioscfg_drv.ordered_list_instances_count = 0;
|
||||
|
||||
kfree(bioscfg_drv.ordered_list_data);
|
||||
kvfree(bioscfg_drv.ordered_list_data);
|
||||
bioscfg_drv.ordered_list_data = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ int hp_get_password_instance_for_type(const char *name)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int validate_password_input(int instance_id, const char *buf)
|
||||
static int validate_password_input(int instance_id, const char *buf,
|
||||
bool is_current)
|
||||
{
|
||||
int length;
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
|
|
@ -69,13 +70,18 @@ static int validate_password_input(int instance_id, const char *buf)
|
|||
if (length > 0 && buf[length - 1] == '\n')
|
||||
length--;
|
||||
|
||||
if (length > MAX_PASSWD_SIZE)
|
||||
return INVALID_BIOS_AUTH;
|
||||
/* An empty current_password clears the session password */
|
||||
if (is_current && !length)
|
||||
return 0;
|
||||
|
||||
if (length >= MAX_PASSWD_SIZE)
|
||||
return -E2BIG;
|
||||
|
||||
if (password_data->min_password_length > length ||
|
||||
password_data->max_password_length < length)
|
||||
return INVALID_BIOS_AUTH;
|
||||
return SUCCESS;
|
||||
return -E2BIG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
|
||||
|
|
@ -96,7 +102,7 @@ static int store_password_instance(struct kobject *kobj, const char *buf,
|
|||
id = get_password_instance_id(kobj);
|
||||
|
||||
if (id >= 0)
|
||||
ret = validate_password_input(id, buf_cp);
|
||||
ret = validate_password_input(id, buf_cp, is_current);
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
|
|
@ -224,7 +230,6 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
|
|||
u32 size;
|
||||
u32 int_value = 0;
|
||||
int elem;
|
||||
int reqs;
|
||||
int eloc;
|
||||
int pos_values;
|
||||
struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
|
||||
|
|
@ -268,64 +273,18 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
|
|||
case VALUE:
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(password_data->common.path, str_value);
|
||||
break;
|
||||
case IS_READONLY:
|
||||
password_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
password_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
password_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
password_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
password_data->common.prerequisites_size = int_value;
|
||||
|
||||
/* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (int_value == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, password_data->common.prerequisites_size,
|
||||
MAX_PREREQUISITES_SIZE);
|
||||
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (elem + reqs >= password_obj_count) {
|
||||
pr_err("Error elem-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(password_obj[elem + reqs].string.pointer,
|
||||
password_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
strscpy(password_data->common.prerequisites[reqs], str_value);
|
||||
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
case SECURITY_LEVEL:
|
||||
password_data->common.security_level = int_value;
|
||||
ret = hp_get_common_data_from_package(password_obj, password_obj_count,
|
||||
&elem, &eloc, int_value, &str_value,
|
||||
&password_data->common);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case PSWD_MIN_LENGTH:
|
||||
password_data->min_password_length = int_value;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* Copyright (c) 2022 HP Development Company, L.P.
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include "bioscfg.h"
|
||||
|
||||
static const char * const spm_state_types[] = {
|
||||
|
|
@ -47,9 +48,6 @@ size_t hp_calculate_security_buffer(const char *authentication)
|
|||
return sizeof(u16) * 2;
|
||||
|
||||
authlen = strlen(authentication);
|
||||
if (!authlen)
|
||||
return sizeof(u16) * 2;
|
||||
|
||||
size = sizeof(u16) + authlen * sizeof(u16);
|
||||
if (!strstarts(authentication, BEAM_PREFIX))
|
||||
size += strlen(UTF_PREFIX) * sizeof(u16);
|
||||
|
|
@ -118,6 +116,9 @@ static ssize_t update_spm_state(void)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (data.state >= ARRAY_SIZE(spm_mechanism_types))
|
||||
return -EIO;
|
||||
|
||||
bioscfg_drv.spm_data.mechanism = data.state;
|
||||
if (bioscfg_drv.spm_data.mechanism)
|
||||
bioscfg_drv.spm_data.is_enabled = 1;
|
||||
|
|
@ -153,6 +154,9 @@ static ssize_t status_show(struct kobject *kobj, struct kobj_attribute
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (data.state >= ARRAY_SIZE(spm_state_types))
|
||||
return -EIO;
|
||||
|
||||
/*
|
||||
* 'status' is a read-only file that returns ASCII text in
|
||||
* JSON format reporting the status information.
|
||||
|
|
@ -310,7 +314,7 @@ static ssize_t auth_token_store(struct kobject *kobj,
|
|||
length--;
|
||||
|
||||
/* allocate space and copy current auth token */
|
||||
bioscfg_drv.spm_data.auth_token = kmemdup(buf, length, GFP_KERNEL);
|
||||
bioscfg_drv.spm_data.auth_token = kstrndup(buf, length, GFP_KERNEL);
|
||||
if (!bioscfg_drv.spm_data.auth_token) {
|
||||
ret = -ENOMEM;
|
||||
goto exit_token;
|
||||
|
|
|
|||
|
|
@ -101,8 +101,13 @@ static const struct attribute_group string_attr_group = {
|
|||
int hp_alloc_string_data(void)
|
||||
{
|
||||
bioscfg_drv.string_instances_count = hp_get_instance_count(HP_WMI_BIOS_STRING_GUID);
|
||||
bioscfg_drv.string_data = kzalloc_objs(*bioscfg_drv.string_data,
|
||||
bioscfg_drv.string_instances_count);
|
||||
|
||||
if (!bioscfg_drv.string_instances_count)
|
||||
return -EINVAL;
|
||||
bioscfg_drv.string_data =
|
||||
kvzalloc_objs(*bioscfg_drv.string_data,
|
||||
bioscfg_drv.string_instances_count);
|
||||
|
||||
if (!bioscfg_drv.string_data) {
|
||||
bioscfg_drv.string_instances_count = 0;
|
||||
return -ENOMEM;
|
||||
|
|
@ -135,9 +140,7 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob
|
|||
int ret = 0;
|
||||
u32 int_value = 0;
|
||||
int elem;
|
||||
int reqs;
|
||||
int eloc;
|
||||
int size;
|
||||
struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
|
||||
|
||||
if (!string_obj)
|
||||
|
|
@ -181,64 +184,18 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob
|
|||
strscpy(string_data->current_value, str_value);
|
||||
break;
|
||||
case PATH:
|
||||
strscpy(string_data->common.path, str_value);
|
||||
break;
|
||||
case IS_READONLY:
|
||||
string_data->common.is_readonly = int_value;
|
||||
break;
|
||||
case DISPLAY_IN_UI:
|
||||
string_data->common.display_in_ui = int_value;
|
||||
break;
|
||||
case REQUIRES_PHYSICAL_PRESENCE:
|
||||
string_data->common.requires_physical_presence = int_value;
|
||||
break;
|
||||
case SEQUENCE:
|
||||
string_data->common.sequence = int_value;
|
||||
break;
|
||||
case PREREQUISITES_SIZE:
|
||||
if (int_value > MAX_PREREQUISITES_SIZE) {
|
||||
pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
|
||||
int_value = MAX_PREREQUISITES_SIZE;
|
||||
}
|
||||
string_data->common.prerequisites_size = int_value;
|
||||
|
||||
/*
|
||||
* This step is needed to keep the expected
|
||||
* element list pointing to the right obj[elem].type
|
||||
* when the size is zero. PREREQUISITES
|
||||
* object is omitted by BIOS when the size is
|
||||
* zero.
|
||||
*/
|
||||
if (string_data->common.prerequisites_size == 0)
|
||||
eloc++;
|
||||
break;
|
||||
case PREREQUISITES:
|
||||
size = min_t(u32, string_data->common.prerequisites_size,
|
||||
MAX_PREREQUISITES_SIZE);
|
||||
|
||||
for (reqs = 0; reqs < size; reqs++) {
|
||||
if (elem + reqs >= string_obj_count) {
|
||||
pr_err("Error elem-objects package is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = hp_convert_hexstr_to_str(string_obj[elem + reqs].string.pointer,
|
||||
string_obj[elem + reqs].string.length,
|
||||
&str_value, &value_len);
|
||||
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
strscpy(string_data->common.prerequisites[reqs], str_value);
|
||||
kfree(str_value);
|
||||
str_value = NULL;
|
||||
}
|
||||
if (size)
|
||||
elem += size - 1;
|
||||
break;
|
||||
|
||||
case SECURITY_LEVEL:
|
||||
string_data->common.security_level = int_value;
|
||||
ret = hp_get_common_data_from_package(string_obj, string_obj_count,
|
||||
&elem, &eloc, int_value, &str_value,
|
||||
&string_data->common);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case STR_MIN_LENGTH:
|
||||
string_data->min_length = int_value;
|
||||
|
|
@ -390,6 +347,6 @@ void hp_exit_string_attributes(void)
|
|||
}
|
||||
bioscfg_drv.string_instances_count = 0;
|
||||
|
||||
kfree(bioscfg_drv.string_data);
|
||||
kvfree(bioscfg_drv.string_data);
|
||||
bioscfg_drv.string_data = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,17 +90,15 @@ static ssize_t audit_log_entries_show(struct kobject *kobj,
|
|||
HPWMI_SURESTART,
|
||||
audit_log_buffer, 1, 128);
|
||||
|
||||
if (ret < 0 || (LOG_ENTRY_SIZE * i) > PAGE_SIZE) {
|
||||
/*
|
||||
* Encountered a failure while reading
|
||||
* individual logs. Only a partial list of
|
||||
* audit log will be returned.
|
||||
*/
|
||||
/*
|
||||
* Encountered a failure while reading individual logs.
|
||||
* Only a partial list of audit log will be returned.
|
||||
*/
|
||||
if (ret < 0)
|
||||
break;
|
||||
} else {
|
||||
memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE);
|
||||
buf += LOG_ENTRY_SIZE;
|
||||
}
|
||||
|
||||
memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE);
|
||||
buf += LOG_ENTRY_SIZE;
|
||||
}
|
||||
|
||||
return i * LOG_ENTRY_SIZE;
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/fixp-arith.h>
|
||||
#include <linux/hwmon.h>
|
||||
|
|
@ -39,6 +41,8 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <acpi/battery.h>
|
||||
|
||||
MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
|
||||
MODULE_DESCRIPTION("HP laptop WMI driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -207,7 +211,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", "8A43",
|
||||
"8A15", "8A17", "8A18", "8A42", "8A43",
|
||||
"8BAD",
|
||||
"8C58",
|
||||
"8E41",
|
||||
|
|
@ -227,14 +231,16 @@ static const char * const omen_thermal_profile_force_v0_boards[] = {
|
|||
* "balanced" when reaching zero.
|
||||
*/
|
||||
static const char * const omen_timed_thermal_profile_boards[] = {
|
||||
"8A15", "8A42",
|
||||
"8A15", "8A17", "8A42",
|
||||
"8BAD",
|
||||
};
|
||||
|
||||
/* DMI Board names of Victus 16-d laptops */
|
||||
/* DMI Board names of Victus 16-d and 16-e laptops */
|
||||
static const char * const victus_thermal_profile_boards[] = {
|
||||
"88EE",
|
||||
"88F8",
|
||||
"8A25",
|
||||
"8A26",
|
||||
};
|
||||
|
||||
/* DMI board-specific feature data for Omen and Victus laptops. */
|
||||
|
|
@ -323,10 +329,22 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
|
|||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D26") },
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D3F") },
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D40") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D42") },
|
||||
.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,
|
||||
|
|
@ -335,14 +353,30 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = {
|
|||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD0") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") },
|
||||
.driver_data = (void *)&omen_v1_no_ec_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8DE1") },
|
||||
.driver_data = (void *)&victus_s_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8E35") },
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8E41") },
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{
|
||||
.matches = { DMI_MATCH(DMI_BOARD_NAME, "8F1C") },
|
||||
.driver_data = (void *)&omen_v1_legacy_board_params,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
@ -404,6 +438,7 @@ enum hp_wmi_commandtype {
|
|||
HPWMI_FEATURE2_QUERY = 0x0d,
|
||||
HPWMI_WIRELESS2_QUERY = 0x1b,
|
||||
HPWMI_POSTCODEERROR_QUERY = 0x2a,
|
||||
HPWMI_BATTERY_CHARGE_MODE = 0x2b,
|
||||
HPWMI_SYSTEM_DEVICE_MODE = 0x40,
|
||||
HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
|
||||
HPWMI_GRAPHICS_MUX_QUERY = 0x52,
|
||||
|
|
@ -473,6 +508,30 @@ enum hp_wireless2_bits {
|
|||
#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)
|
||||
#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
|
||||
|
||||
#if IS_REACHABLE(CONFIG_ACPI_BATTERY)
|
||||
#define HPWMI_CHARGE_BEHAVIOURS \
|
||||
(BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
|
||||
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \
|
||||
BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
|
||||
|
||||
/*
|
||||
* SBCO reads the requested mode from bits 15:8 of the input value.
|
||||
* GBCO returns literal status values in bits 7:0.
|
||||
*/
|
||||
#define HPWMI_CHARGE_MODE_MASK GENMASK(15, 8)
|
||||
#define HPWMI_CHARGE_MODE_AUTO 0x00
|
||||
#define HPWMI_CHARGE_MODE_FORCE_DISCHARGE 0x02
|
||||
#define HPWMI_CHARGE_MODE_INHIBIT 0x05
|
||||
|
||||
#define HPWMI_CHARGE_MODE_READ_MASK GENMASK(7, 0)
|
||||
#define HPWMI_CHARGE_MODE_READ_AUTO 0x00
|
||||
#define HPWMI_CHARGE_MODE_READ_CHARGING 0x01
|
||||
#define HPWMI_CHARGE_MODE_READ_FORCE_DISCHARGE 0x02
|
||||
#define HPWMI_CHARGE_MODE_READ_INHIBIT 0x04
|
||||
#define HPWMI_CHARGE_MODE_READ_INHIBIT_SHPM 0x05
|
||||
#define HPWMI_CHARGE_MODE_READ_INHIBIT_EXT 0x06
|
||||
#endif
|
||||
|
||||
struct bios_rfkill2_device_state {
|
||||
u8 radio_type;
|
||||
u8 bus_type;
|
||||
|
|
@ -533,7 +592,6 @@ static struct platform_device *hp_wmi_platform_dev;
|
|||
static struct device *platform_profile_device;
|
||||
static struct notifier_block platform_power_source_nb;
|
||||
static enum platform_profile_option active_platform_profile;
|
||||
static bool platform_profile_support;
|
||||
static bool zero_insize_support;
|
||||
|
||||
static struct rfkill *wifi_rfkill;
|
||||
|
|
@ -560,6 +618,26 @@ static const char * const tablet_chassis_types[] = {
|
|||
"32" /* Detachable */
|
||||
};
|
||||
|
||||
#if IS_REACHABLE(CONFIG_ACPI_BATTERY)
|
||||
static const struct dmi_system_id hp_wmi_charge_control_quirks[] __initconst = {
|
||||
{
|
||||
/* HP Spectre x360 16-aa0xxx */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "8C17"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* HP Spectre x360 14-eu0xxx */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "HP"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "8C15"),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
#endif
|
||||
|
||||
#define DEVICE_MODE_TABLET 0x06
|
||||
|
||||
#define CPU_FAN 0
|
||||
|
|
@ -573,6 +651,7 @@ enum pwm_modes {
|
|||
|
||||
struct hp_wmi_hwmon_priv {
|
||||
struct mutex lock; /* protects mode, pwm */
|
||||
int fan_count;
|
||||
u8 min_rpm;
|
||||
u8 max_rpm;
|
||||
u8 mode;
|
||||
|
|
@ -691,8 +770,9 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
|
|||
goto out_free;
|
||||
}
|
||||
|
||||
if (obj->type != ACPI_TYPE_BUFFER) {
|
||||
pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret);
|
||||
if (obj->type != ACPI_TYPE_BUFFER ||
|
||||
obj->buffer.length < sizeof(*bios_return)) {
|
||||
pr_warn("query 0x%x returned wrong type or too small buffer\n", query);
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
|
@ -711,7 +791,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command,
|
|||
if (!outsize)
|
||||
goto out_free;
|
||||
|
||||
actual_outsize = min(outsize, (int)(obj->buffer.length - sizeof(*bios_return)));
|
||||
actual_outsize = min_t(u32, outsize, obj->buffer.length - sizeof(*bios_return));
|
||||
memcpy(buffer, obj->buffer.pointer + sizeof(*bios_return), actual_outsize);
|
||||
memset(buffer + actual_outsize, 0, outsize - actual_outsize);
|
||||
|
||||
|
|
@ -789,6 +869,200 @@ static int hp_wmi_read_int(int query)
|
|||
return val;
|
||||
}
|
||||
|
||||
#if IS_REACHABLE(CONFIG_ACPI_BATTERY)
|
||||
/* Protects hp_wmi_charge_behaviour */
|
||||
static DEFINE_MUTEX(hp_wmi_charge_lock);
|
||||
static enum power_supply_charge_behaviour hp_wmi_charge_behaviour =
|
||||
POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
|
||||
|
||||
static int hp_wmi_charge_mode_to_behaviour(int mode,
|
||||
enum power_supply_charge_behaviour *behaviour)
|
||||
{
|
||||
switch (mode) {
|
||||
case HPWMI_CHARGE_MODE_READ_AUTO:
|
||||
case HPWMI_CHARGE_MODE_READ_CHARGING:
|
||||
/*
|
||||
* GBCO 0x01 reports active charging, rather than a separate
|
||||
* charge policy. Treat it as automatic charging.
|
||||
*/
|
||||
*behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
|
||||
return 0;
|
||||
case HPWMI_CHARGE_MODE_READ_FORCE_DISCHARGE:
|
||||
/*
|
||||
* Firmware also returns 0x02 while running on battery alone.
|
||||
* Treat it as force-discharge so a persisted AC policy is not
|
||||
* lost across reboot.
|
||||
*/
|
||||
*behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
|
||||
return 0;
|
||||
case HPWMI_CHARGE_MODE_READ_INHIBIT:
|
||||
case HPWMI_CHARGE_MODE_READ_INHIBIT_SHPM:
|
||||
case HPWMI_CHARGE_MODE_READ_INHIBIT_EXT:
|
||||
*behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int hp_wmi_charge_mode_read(void)
|
||||
{
|
||||
int val = 0, ret;
|
||||
|
||||
ret = hp_wmi_perform_query(HPWMI_BATTERY_CHARGE_MODE, HPWMI_READ,
|
||||
&val, zero_if_sup(val), sizeof(val));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret > 0)
|
||||
return -EINVAL;
|
||||
|
||||
return FIELD_GET(HPWMI_CHARGE_MODE_READ_MASK, val);
|
||||
}
|
||||
|
||||
static int hp_wmi_charge_mode_write(u32 mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mode = FIELD_PREP(HPWMI_CHARGE_MODE_MASK, mode);
|
||||
ret = hp_wmi_perform_query(HPWMI_BATTERY_CHARGE_MODE, HPWMI_WRITE,
|
||||
&mode, sizeof(mode), 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret > 0)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hp_wmi_charge_set_behaviour(enum power_supply_charge_behaviour behaviour)
|
||||
{
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&hp_wmi_charge_lock);
|
||||
|
||||
switch (behaviour) {
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
|
||||
ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_AUTO);
|
||||
break;
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
|
||||
ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_INHIBIT);
|
||||
break;
|
||||
case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
|
||||
ret = hp_wmi_charge_mode_write(HPWMI_CHARGE_MODE_FORCE_DISCHARGE);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hp_wmi_charge_behaviour = behaviour;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hp_wmi_charge_get_property(struct power_supply *psy,
|
||||
const struct power_supply_ext *ext,
|
||||
void *data,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
if (psp != POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR)
|
||||
return -EINVAL;
|
||||
|
||||
guard(mutex)(&hp_wmi_charge_lock);
|
||||
val->intval = hp_wmi_charge_behaviour;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hp_wmi_charge_set_property(struct power_supply *psy,
|
||||
const struct power_supply_ext *ext,
|
||||
void *data,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
if (psp != POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR)
|
||||
return -EINVAL;
|
||||
|
||||
return hp_wmi_charge_set_behaviour(val->intval);
|
||||
}
|
||||
|
||||
static int hp_wmi_charge_property_is_writeable(struct power_supply *psy,
|
||||
const struct power_supply_ext *ext,
|
||||
void *data,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
|
||||
}
|
||||
|
||||
static const enum power_supply_property hp_wmi_charge_properties[] = {
|
||||
POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
|
||||
};
|
||||
|
||||
static const struct power_supply_ext hp_wmi_charge_extension = {
|
||||
.name = "hp-wmi-charge-control",
|
||||
.properties = hp_wmi_charge_properties,
|
||||
.num_properties = ARRAY_SIZE(hp_wmi_charge_properties),
|
||||
.charge_behaviours = HPWMI_CHARGE_BEHAVIOURS,
|
||||
.get_property = hp_wmi_charge_get_property,
|
||||
.set_property = hp_wmi_charge_set_property,
|
||||
.property_is_writeable = hp_wmi_charge_property_is_writeable,
|
||||
};
|
||||
|
||||
static int hp_wmi_charge_add_battery(struct power_supply *battery,
|
||||
struct acpi_battery_hook *hook)
|
||||
{
|
||||
return power_supply_register_extension(battery, &hp_wmi_charge_extension,
|
||||
&hp_wmi_platform_dev->dev, NULL);
|
||||
}
|
||||
|
||||
static int hp_wmi_charge_remove_battery(struct power_supply *battery,
|
||||
struct acpi_battery_hook *hook)
|
||||
{
|
||||
power_supply_unregister_extension(battery, &hp_wmi_charge_extension);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct acpi_battery_hook hp_wmi_charge_battery_hook = {
|
||||
.name = "HP WMI charge control",
|
||||
.add_battery = hp_wmi_charge_add_battery,
|
||||
.remove_battery = hp_wmi_charge_remove_battery,
|
||||
};
|
||||
|
||||
static int __init hp_wmi_charge_control_setup(struct device *dev)
|
||||
{
|
||||
enum power_supply_charge_behaviour behaviour;
|
||||
int ret;
|
||||
|
||||
if (!dmi_check_system(hp_wmi_charge_control_quirks))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Firmware retains inhibit across reboot, so seed from GBCO rather
|
||||
* than assuming auto.
|
||||
*/
|
||||
ret = hp_wmi_charge_mode_read();
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
|
||||
ret = hp_wmi_charge_mode_to_behaviour(ret, &behaviour);
|
||||
if (ret)
|
||||
return 0;
|
||||
|
||||
scoped_guard(mutex, &hp_wmi_charge_lock)
|
||||
hp_wmi_charge_behaviour = behaviour;
|
||||
|
||||
return devm_battery_hook_register(dev, &hp_wmi_charge_battery_hook);
|
||||
}
|
||||
#else
|
||||
static int __init hp_wmi_charge_control_setup(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int hp_wmi_get_dock_state(void)
|
||||
{
|
||||
int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY);
|
||||
|
|
@ -2508,7 +2782,6 @@ static int thermal_profile_setup(struct platform_device *device)
|
|||
return PTR_ERR(platform_profile_device);
|
||||
|
||||
pr_info("Registered as platform profile handler\n");
|
||||
platform_profile_support = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2540,6 +2813,10 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
|
|||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = hp_wmi_charge_control_setup(&device->dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
thermal_profile_setup(device);
|
||||
|
||||
return 0;
|
||||
|
|
@ -2548,7 +2825,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
|
|||
static void __exit hp_wmi_bios_remove(struct platform_device *device)
|
||||
{
|
||||
int i;
|
||||
struct hp_wmi_hwmon_priv *priv;
|
||||
|
||||
for (i = 0; i < rfkill2_count; i++) {
|
||||
rfkill_unregister(rfkill2[i].rfkill);
|
||||
|
|
@ -2567,10 +2843,6 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device)
|
|||
rfkill_unregister(wwan_rfkill);
|
||||
rfkill_destroy(wwan_rfkill);
|
||||
}
|
||||
|
||||
priv = platform_get_drvdata(device);
|
||||
if (priv)
|
||||
cancel_delayed_work_sync(&priv->keep_alive_dwork);
|
||||
}
|
||||
|
||||
static int hp_wmi_resume_handler(struct device *device)
|
||||
|
|
@ -2679,12 +2951,18 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
|
|||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
const struct hp_wmi_hwmon_priv *priv = data;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_pwm:
|
||||
if (priv->fan_count <= 0)
|
||||
return 0;
|
||||
if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported())
|
||||
return 0;
|
||||
return 0644;
|
||||
case hwmon_fan:
|
||||
if (priv->fan_count <= 0)
|
||||
return 0;
|
||||
if (hp_wmi_fan_control_supported()) {
|
||||
if (hp_wmi_get_active_fan_speed(channel) >= 0)
|
||||
return 0444;
|
||||
|
|
@ -2853,6 +3131,7 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
|
|||
|
||||
/* Default behaviour on hwmon init is automatic mode */
|
||||
priv->mode = PWM_MODE_AUTO;
|
||||
priv->fan_count = hp_wmi_get_fan_count_userdefine_trigger();
|
||||
|
||||
/* Bypass devices without fan control support. */
|
||||
if (!hp_wmi_fan_table_supported())
|
||||
|
|
@ -2918,6 +3197,17 @@ static int hp_wmi_hwmon_init(void)
|
|||
ret = hp_wmi_setup_fan_settings(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Set up the work before registering hwmon so that, on teardown,
|
||||
* it is cancelled only after the sysfs writers that schedule it
|
||||
* are gone.
|
||||
*/
|
||||
ret = devm_delayed_work_autocancel(dev, &priv->keep_alive_dwork,
|
||||
hp_wmi_hwmon_keep_alive_handler);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv,
|
||||
&chip_info, NULL);
|
||||
|
||||
|
|
@ -2926,8 +3216,11 @@ static int hp_wmi_hwmon_init(void)
|
|||
return PTR_ERR(hwmon);
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler);
|
||||
platform_set_drvdata(hp_wmi_platform_dev, priv);
|
||||
if (priv->fan_count <= 0) {
|
||||
dev_info(dev, "No usable fans reported by WMI, disabling fan support\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = hp_wmi_apply_fan_settings(priv);
|
||||
if (ret)
|
||||
dev_warn(dev, "Failed to apply initial fan settings: %d\n", ret);
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ static int lis3lv02d_acpi_read(struct lis3lv02d *lis3, int reg, u8 *ret)
|
|||
static int lis3lv02d_acpi_write(struct lis3lv02d *lis3, int reg, u8 val)
|
||||
{
|
||||
struct acpi_device *dev = lis3->bus_priv;
|
||||
unsigned long long ret; /* Not used when writting */
|
||||
unsigned long long ret; /* Not used when writing */
|
||||
union acpi_object in_obj[2];
|
||||
struct acpi_object_list args = { 2, in_obj };
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ static const struct key_entry huawei_wmi_keymap[] = {
|
|||
{ KE_KEY, 0x285, { KEY_VOLUMEDOWN } },
|
||||
{ KE_KEY, 0x286, { KEY_VOLUMEUP } },
|
||||
{ KE_KEY, 0x287, { KEY_MICMUTE } },
|
||||
{ KE_KEY, 0x288, { KEY_CAMERA_ACCESS_TOGGLE } },
|
||||
{ KE_KEY, 0x289, { KEY_WLAN } },
|
||||
// Huawei |M| key
|
||||
{ KE_KEY, 0x28a, { KEY_CONFIG } },
|
||||
|
|
|
|||
|
|
@ -234,9 +234,9 @@ static const struct dmi_system_id ibm_rtl_dmi_table[] __initconst = {
|
|||
};
|
||||
|
||||
static int __init ibm_rtl_init(void) {
|
||||
unsigned long ebda_addr, ebda_size;
|
||||
unsigned long ebda_addr, ebda_size, offset;
|
||||
unsigned int ebda_kb;
|
||||
int ret = -ENODEV, i;
|
||||
int ret = -ENODEV;
|
||||
|
||||
if (force)
|
||||
pr_warn("module loaded by force\n");
|
||||
|
|
@ -271,9 +271,9 @@ static int __init ibm_rtl_init(void) {
|
|||
return -ENOMEM;
|
||||
|
||||
/* search for the _RTL_ signature at the start of the table */
|
||||
for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
|
||||
for (offset = 0; offset + sizeof(*rtl_table) <= ebda_size; offset += 4) {
|
||||
struct ibm_rtl_table __iomem * tmp;
|
||||
tmp = (struct ibm_rtl_table __iomem *) (ebda_map + i*sizeof(unsigned int));
|
||||
tmp = (struct ibm_rtl_table __iomem *) (ebda_map + offset);
|
||||
if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
|
||||
phys_addr_t addr;
|
||||
unsigned int plen;
|
||||
|
|
|
|||
|
|
@ -209,11 +209,15 @@ config INTEL_SMARTCONNECT
|
|||
config INTEL_TPMI_POWER_DOMAINS
|
||||
tristate
|
||||
|
||||
config INTEL_TPMI_COMMON
|
||||
tristate
|
||||
|
||||
config INTEL_TPMI
|
||||
tristate "Intel Topology Aware Register and PM Capsule Interface (TPMI)"
|
||||
depends on INTEL_VSEC
|
||||
depends on X86_64
|
||||
select INTEL_TPMI_POWER_DOMAINS
|
||||
select INTEL_TPMI_COMMON
|
||||
help
|
||||
The Intel Topology Aware Register and PM Capsule Interface (TPMI),
|
||||
provides enumerable MMIO interface for power management features.
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ intel-target-$(CONFIG_INTEL_PUNIT_IPC) += punit_ipc.o
|
|||
# TPMI drivers
|
||||
intel-target-$(CONFIG_INTEL_PLR_TPMI) += plr_tpmi.o
|
||||
intel-target-$(CONFIG_INTEL_TPMI_POWER_DOMAINS) += tpmi_power_domains.o
|
||||
intel-target-$(CONFIG_INTEL_TPMI_COMMON) += tpmi_common.o
|
||||
intel-target-$(CONFIG_INTEL_TPMI) += vsec_tpmi.o
|
||||
|
||||
# Intel Uncore drivers
|
||||
|
|
|
|||
|
|
@ -64,11 +64,8 @@ static int bxt_wcove_tmu_probe(struct platform_device *pdev)
|
|||
ret = devm_request_threaded_irq(&pdev->dev, wctmu->irq,
|
||||
NULL, bxt_wcove_tmu_irq_handler,
|
||||
IRQF_ONESHOT, "bxt_wcove_tmu", wctmu);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "request irq failed: %d,virq: %d\n",
|
||||
ret, wctmu->irq);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Unmask TMU second level Wake & System alarm */
|
||||
regmap_update_bits(wctmu->regmap, BXTWC_MTMUIRQ_REG,
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ static int crc_pwrsrc_probe(struct platform_device *pdev)
|
|||
crc_pwrsrc_irq_handler,
|
||||
IRQF_ONESHOT, KBUILD_MODNAME, data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "requesting IRQ\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL);
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ static int crystal_cove_charger_probe(struct platform_device *pdev)
|
|||
crystal_cove_charger_irq,
|
||||
IRQF_ONESHOT, KBUILD_MODNAME, charger);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret, "requesting irq\n");
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,6 +165,13 @@ static const struct dmi_system_id button_array_table[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_NAME, "HP ProBook x360 440 G1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "HP EliteBook x360 1030 G2",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "HP EliteBook x360 1030 G2"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -208,10 +208,8 @@ static int int0002_probe(struct platform_device *pdev)
|
|||
*/
|
||||
ret = devm_request_irq(dev, irq, int0002_irq, IRQF_SHARED, "INT0002",
|
||||
chip);
|
||||
if (ret) {
|
||||
dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
girq = &chip->irq;
|
||||
gpio_irq_chip_set_chip(girq, &int0002_irqchip);
|
||||
|
|
|
|||
|
|
@ -1849,6 +1849,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
|
|||
X86_MATCH_VFM(INTEL_ARROWLAKE_U, &arl_h_pmc_dev),
|
||||
X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_pmc_dev),
|
||||
X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &ptl_pmc_dev),
|
||||
X86_MATCH_VFM(INTEL_PANTHERLAKE_R, &ptl_pmc_dev),
|
||||
X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &wcl_pmc_dev),
|
||||
X86_MATCH_VFM(INTEL_NOVALAKE, &nvl_s_pmc_dev),
|
||||
X86_MATCH_VFM(INTEL_NOVALAKE_L, &nvl_h_pmc_dev),
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ enum ppfear_regs {
|
|||
#define NVL_LPM_LIVE_STATUS_OFFSET 0x1760
|
||||
#define NVL_LPM_NUM_MAPS 15
|
||||
#define NVL_PCDH_NUM_S0IX_BLOCKER 107
|
||||
#define NVL_PCDS_NUM_S0IX_BLOCKER 71
|
||||
#define NVL_PCDS_NUM_S0IX_BLOCKER 70
|
||||
#define NVL_PCHS_NUM_S0IX_BLOCKER 54
|
||||
#define NVL_PCDS_PMC_LTR_RESERVED 0x1bac
|
||||
#define NVL_PCDH_BLK_REQ_OFFSET 53
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
/* PMC SSRAM PMT Telemetry GUIDS */
|
||||
#define PCDH_LPM_REQ_GUID 0x01093101
|
||||
#define PCHS_LPM_REQ_GUID 0x01092101
|
||||
#define PCDS_LPM_REQ_GUID 0x01091102
|
||||
#define PCDS_LPM_REQ_GUID 0x01091103
|
||||
|
||||
/*
|
||||
* Die Mapping to Product.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/log2.h>
|
||||
#include <linux/intel_vsec.h>
|
||||
#include <linux/io-64-nonatomic-lo-hi.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mm.h>
|
||||
|
|
@ -100,7 +101,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj,
|
|||
if (count > entry->size - off)
|
||||
count = entry->size - off;
|
||||
|
||||
count = pmt_telem_read_mmio(entry->ep->dev, entry->cb, entry->header.guid, buf,
|
||||
count = pmt_telem_read_mmio(entry->dev, entry->cb, entry->header.guid, buf,
|
||||
entry->base, off, count);
|
||||
|
||||
return count;
|
||||
|
|
@ -286,8 +287,6 @@ static int pmt_resolve_access_pci(struct intel_pmt_entry *entry,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
entry->pcidev = pci_dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -365,6 +364,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
|
|||
entry->guid = header->guid;
|
||||
entry->size = header->size;
|
||||
entry->cb = ivdev->priv_data;
|
||||
entry->dev = ivdev->dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -374,7 +374,6 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
|
|||
struct device *parent)
|
||||
{
|
||||
struct intel_vsec_device *ivdev = dev_to_ivdev(parent);
|
||||
struct resource res = {0};
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
|
|
@ -404,20 +403,32 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
|
|||
if (!entry->size)
|
||||
return 0;
|
||||
|
||||
res.start = entry->base_addr;
|
||||
res.end = res.start + entry->size - 1;
|
||||
res.flags = IORESOURCE_MEM;
|
||||
/*
|
||||
* The read_telem callback is responsible for this mapping, and may have
|
||||
* different requirements for use. If the callback is present do not
|
||||
* create the map.
|
||||
*/
|
||||
if (!(entry->cb && entry->cb->read_telem)) {
|
||||
struct resource res = DEFINE_RES_MEM(entry->base_addr, entry->size);
|
||||
|
||||
entry->base = devm_ioremap_resource(dev, &res);
|
||||
if (IS_ERR(entry->base)) {
|
||||
ret = PTR_ERR(entry->base);
|
||||
goto fail_ioremap;
|
||||
entry->base = devm_ioremap_resource(dev, &res);
|
||||
if (IS_ERR(entry->base)) {
|
||||
ret = PTR_ERR(entry->base);
|
||||
goto fail_ioremap;
|
||||
}
|
||||
}
|
||||
|
||||
sysfs_bin_attr_init(&entry->pmt_bin_attr);
|
||||
entry->pmt_bin_attr.attr.name = ns->name;
|
||||
entry->pmt_bin_attr.attr.mode = 0440;
|
||||
entry->pmt_bin_attr.mmap = intel_pmt_mmap;
|
||||
|
||||
/*
|
||||
* The read_telem callback controls access to the memory area. Block
|
||||
* usage if the read_telem callback is present.
|
||||
*/
|
||||
if (!(entry->cb && entry->cb->read_telem))
|
||||
entry->pmt_bin_attr.mmap = intel_pmt_mmap;
|
||||
|
||||
entry->pmt_bin_attr.read = intel_pmt_read;
|
||||
entry->pmt_bin_attr.size = entry->size;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
|
||||
|
||||
struct device;
|
||||
struct pci_dev;
|
||||
extern struct class intel_pmt_class;
|
||||
|
||||
struct telem_endpoint {
|
||||
|
|
@ -42,14 +41,14 @@ struct intel_pmt_header {
|
|||
|
||||
struct intel_pmt_entry {
|
||||
struct telem_endpoint *ep;
|
||||
struct pci_dev *pcidev;
|
||||
struct device *dev;
|
||||
struct intel_pmt_header header;
|
||||
u32 disc_header[PMT_DISC_DWORDS];
|
||||
struct bin_attribute pmt_bin_attr;
|
||||
const struct attribute_group *attr_grp;
|
||||
struct kobject *kobj;
|
||||
void __iomem *disc_table;
|
||||
void __iomem *base;
|
||||
void __iomem *base; /* unused if cb->read_telem is in use */
|
||||
struct pmt_callbacks *cb;
|
||||
unsigned long base_addr;
|
||||
size_t size;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@
|
|||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/intel_vsec.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/overflow.h>
|
||||
|
|
@ -124,12 +126,36 @@ struct pmt_crashlog_priv {
|
|||
* I/O
|
||||
*/
|
||||
|
||||
static int pmt_crashlog_read_reg(struct intel_pmt_entry *entry, u32 *reg, u32 offset)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*reg = 0;
|
||||
|
||||
if (entry->cb && entry->cb->read_reg) {
|
||||
ret = entry->cb->read_reg(entry->dev, entry->header.guid, reg, offset);
|
||||
if (ret) {
|
||||
dev_err_ratelimited(entry->dev, "failed to read reg: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
*reg = readl(entry->disc_table + offset);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read, modify, write the control register, setting or clearing @bit based on @set */
|
||||
static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
|
||||
static int pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
|
||||
{
|
||||
const struct crashlog_control *control = &crashlog->info->control;
|
||||
struct intel_pmt_entry *entry = &crashlog->entry;
|
||||
u32 reg = readl(entry->disc_table + control->offset);
|
||||
u32 reg;
|
||||
int ret;
|
||||
|
||||
ret = pmt_crashlog_read_reg(entry, ®, control->offset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg &= ~control->trigger_mask;
|
||||
|
||||
|
|
@ -138,28 +164,46 @@ static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
|
|||
else
|
||||
reg &= ~bit;
|
||||
|
||||
writel(reg, entry->disc_table + control->offset);
|
||||
if (entry->cb && entry->cb->write_reg) {
|
||||
ret = entry->cb->write_reg(entry->dev, entry->header.guid, reg, control->offset);
|
||||
if (ret) {
|
||||
dev_err_ratelimited(entry->dev, "failed to write reg: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
writel(reg, entry->disc_table + control->offset);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read the status register and see if the specified @bit is set */
|
||||
static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit)
|
||||
static int pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit, bool *state)
|
||||
{
|
||||
const struct crashlog_status *status = &crashlog->info->status;
|
||||
u32 reg = readl(crashlog->entry.disc_table + status->offset);
|
||||
struct intel_pmt_entry *entry = &crashlog->entry;
|
||||
u32 reg;
|
||||
int ret;
|
||||
|
||||
return !!(reg & bit);
|
||||
ret = pmt_crashlog_read_reg(entry, ®, status->offset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*state = !!(reg & bit);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_complete(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_complete(struct crashlog_entry *crashlog, bool *state)
|
||||
{
|
||||
/* return current value of the crashlog complete flag */
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.complete);
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.complete, state);
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_disabled(struct crashlog_entry *crashlog, bool *state)
|
||||
{
|
||||
/* return current value of the crashlog disabled flag */
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled);
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled, state);
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_type, u32 *version)
|
||||
|
|
@ -181,50 +225,50 @@ static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_typ
|
|||
return false;
|
||||
}
|
||||
|
||||
static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog,
|
||||
bool disable)
|
||||
static int pmt_crashlog_set_disable(struct crashlog_entry *crashlog,
|
||||
bool disable)
|
||||
{
|
||||
pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable);
|
||||
return pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable);
|
||||
}
|
||||
|
||||
static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_set_clear(struct crashlog_entry *crashlog)
|
||||
{
|
||||
pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true);
|
||||
return pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true);
|
||||
}
|
||||
|
||||
static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_set_execute(struct crashlog_entry *crashlog)
|
||||
{
|
||||
pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true);
|
||||
return pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true);
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_cleared(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_cleared(struct crashlog_entry *crashlog, bool *state)
|
||||
{
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared);
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared, state);
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_consumed(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_consumed(struct crashlog_entry *crashlog, bool *state)
|
||||
{
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed);
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed, state);
|
||||
}
|
||||
|
||||
static void pmt_crashlog_set_consumed(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_set_consumed(struct crashlog_entry *crashlog)
|
||||
{
|
||||
pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true);
|
||||
return pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true);
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_error(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_error(struct crashlog_entry *crashlog, bool *state)
|
||||
{
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.error);
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.error, state);
|
||||
}
|
||||
|
||||
static bool pmt_crashlog_rearm(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_rearm(struct crashlog_entry *crashlog, bool *state)
|
||||
{
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed);
|
||||
return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed, state);
|
||||
}
|
||||
|
||||
static void pmt_crashlog_set_rearm(struct crashlog_entry *crashlog)
|
||||
static int pmt_crashlog_set_rearm(struct crashlog_entry *crashlog)
|
||||
{
|
||||
pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true);
|
||||
return pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -234,7 +278,12 @@ static ssize_t
|
|||
clear_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
||||
bool cleared = pmt_crashlog_cleared(crashlog);
|
||||
bool cleared;
|
||||
int ret;
|
||||
|
||||
ret = pmt_crashlog_cleared(crashlog, &cleared);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", cleared);
|
||||
}
|
||||
|
|
@ -245,13 +294,13 @@ clear_store(struct device *dev, struct device_attribute *attr,
|
|||
{
|
||||
struct crashlog_entry *crashlog;
|
||||
bool clear;
|
||||
int result;
|
||||
int ret;
|
||||
|
||||
crashlog = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtobool(buf, &clear);
|
||||
if (result)
|
||||
return result;
|
||||
ret = kstrtobool(buf, &clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* set bit only */
|
||||
if (!clear)
|
||||
|
|
@ -259,9 +308,9 @@ clear_store(struct device *dev, struct device_attribute *attr,
|
|||
|
||||
guard(mutex)(&crashlog->control_mutex);
|
||||
|
||||
pmt_crashlog_set_clear(crashlog);
|
||||
ret = pmt_crashlog_set_clear(crashlog);
|
||||
|
||||
return count;
|
||||
return ret ?: count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(clear);
|
||||
|
||||
|
|
@ -269,7 +318,12 @@ static ssize_t
|
|||
consumed_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
||||
bool consumed = pmt_crashlog_consumed(crashlog);
|
||||
bool consumed;
|
||||
int ret;
|
||||
|
||||
ret = pmt_crashlog_consumed(crashlog, &consumed);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", consumed);
|
||||
}
|
||||
|
|
@ -279,14 +333,16 @@ consumed_store(struct device *dev, struct device_attribute *attr, const char *bu
|
|||
size_t count)
|
||||
{
|
||||
struct crashlog_entry *crashlog;
|
||||
bool complete;
|
||||
bool consumed;
|
||||
int result;
|
||||
bool disabled;
|
||||
int ret;
|
||||
|
||||
crashlog = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtobool(buf, &consumed);
|
||||
if (result)
|
||||
return result;
|
||||
ret = kstrtobool(buf, &consumed);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* set bit only */
|
||||
if (!consumed)
|
||||
|
|
@ -294,15 +350,21 @@ consumed_store(struct device *dev, struct device_attribute *attr, const char *bu
|
|||
|
||||
guard(mutex)(&crashlog->control_mutex);
|
||||
|
||||
if (pmt_crashlog_disabled(crashlog))
|
||||
ret = pmt_crashlog_disabled(crashlog, &disabled);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (disabled)
|
||||
return -EBUSY;
|
||||
|
||||
if (!pmt_crashlog_complete(crashlog))
|
||||
ret = pmt_crashlog_complete(crashlog, &complete);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!complete)
|
||||
return -EEXIST;
|
||||
|
||||
pmt_crashlog_set_consumed(crashlog);
|
||||
ret = pmt_crashlog_set_consumed(crashlog);
|
||||
|
||||
return count;
|
||||
return ret ?: count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(consumed);
|
||||
|
||||
|
|
@ -310,9 +372,14 @@ static ssize_t
|
|||
enable_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
||||
bool enabled = !pmt_crashlog_disabled(crashlog);
|
||||
bool disabled;
|
||||
int ret;
|
||||
|
||||
return sprintf(buf, "%d\n", enabled);
|
||||
ret = pmt_crashlog_disabled(crashlog, &disabled);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", !disabled);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
|
|
@ -321,19 +388,19 @@ enable_store(struct device *dev, struct device_attribute *attr,
|
|||
{
|
||||
struct crashlog_entry *crashlog;
|
||||
bool enabled;
|
||||
int result;
|
||||
int ret;
|
||||
|
||||
crashlog = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtobool(buf, &enabled);
|
||||
if (result)
|
||||
return result;
|
||||
ret = kstrtobool(buf, &enabled);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
guard(mutex)(&crashlog->control_mutex);
|
||||
|
||||
pmt_crashlog_set_disable(crashlog, !enabled);
|
||||
ret = pmt_crashlog_set_disable(crashlog, !enabled);
|
||||
|
||||
return count;
|
||||
return ret ?: count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(enable);
|
||||
|
||||
|
|
@ -341,7 +408,12 @@ static ssize_t
|
|||
error_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
||||
bool error = pmt_crashlog_error(crashlog);
|
||||
bool error;
|
||||
int ret;
|
||||
|
||||
ret = pmt_crashlog_error(crashlog, &error);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", error);
|
||||
}
|
||||
|
|
@ -351,7 +423,12 @@ static ssize_t
|
|||
rearm_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct crashlog_entry *crashlog = dev_get_drvdata(dev);
|
||||
int rearmed = pmt_crashlog_rearm(crashlog);
|
||||
bool rearmed;
|
||||
int ret;
|
||||
|
||||
ret = pmt_crashlog_rearm(crashlog, &rearmed);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", rearmed);
|
||||
}
|
||||
|
|
@ -362,13 +439,13 @@ rearm_store(struct device *dev, struct device_attribute *attr, const char *buf,
|
|||
{
|
||||
struct crashlog_entry *crashlog;
|
||||
bool rearm;
|
||||
int result;
|
||||
int ret;
|
||||
|
||||
crashlog = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtobool(buf, &rearm);
|
||||
if (result)
|
||||
return result;
|
||||
ret = kstrtobool(buf, &rearm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* set only */
|
||||
if (!rearm)
|
||||
|
|
@ -376,9 +453,9 @@ rearm_store(struct device *dev, struct device_attribute *attr, const char *buf,
|
|||
|
||||
guard(mutex)(&crashlog->control_mutex);
|
||||
|
||||
pmt_crashlog_set_rearm(crashlog);
|
||||
ret = pmt_crashlog_set_rearm(crashlog);
|
||||
|
||||
return count;
|
||||
return ret ?: count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(rearm);
|
||||
|
||||
|
|
@ -387,11 +464,15 @@ trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|||
{
|
||||
struct crashlog_entry *crashlog;
|
||||
bool trigger;
|
||||
int ret;
|
||||
|
||||
crashlog = dev_get_drvdata(dev);
|
||||
trigger = pmt_crashlog_complete(crashlog);
|
||||
|
||||
return sprintf(buf, "%d\n", trigger);
|
||||
ret = pmt_crashlog_complete(crashlog, &trigger);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%d\n", trigger);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
|
|
@ -399,33 +480,41 @@ trigger_store(struct device *dev, struct device_attribute *attr,
|
|||
const char *buf, size_t count)
|
||||
{
|
||||
struct crashlog_entry *crashlog;
|
||||
bool complete;
|
||||
bool disabled;
|
||||
bool trigger;
|
||||
int result;
|
||||
int ret;
|
||||
|
||||
crashlog = dev_get_drvdata(dev);
|
||||
|
||||
result = kstrtobool(buf, &trigger);
|
||||
if (result)
|
||||
return result;
|
||||
ret = kstrtobool(buf, &trigger);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
guard(mutex)(&crashlog->control_mutex);
|
||||
|
||||
/* if device is currently disabled, return busy */
|
||||
if (pmt_crashlog_disabled(crashlog))
|
||||
ret = pmt_crashlog_disabled(crashlog, &disabled);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (disabled)
|
||||
return -EBUSY;
|
||||
|
||||
if (!trigger) {
|
||||
pmt_crashlog_set_clear(crashlog);
|
||||
return count;
|
||||
ret = pmt_crashlog_set_clear(crashlog);
|
||||
return ret ?: count;
|
||||
}
|
||||
|
||||
/* we cannot trigger a new crash if one is still pending */
|
||||
if (pmt_crashlog_complete(crashlog))
|
||||
ret = pmt_crashlog_complete(crashlog, &complete);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (complete)
|
||||
return -EEXIST;
|
||||
|
||||
pmt_crashlog_set_execute(crashlog);
|
||||
ret = pmt_crashlog_set_execute(crashlog);
|
||||
|
||||
return count;
|
||||
return ret ?: count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(trigger);
|
||||
|
||||
|
|
|
|||
|
|
@ -609,7 +609,7 @@ void intel_pmt_get_features(struct intel_pmt_entry *entry)
|
|||
|
||||
mutex_lock(&feature_list_lock);
|
||||
list_for_each_entry(feature, &pmt_feature_list, list) {
|
||||
if (feature->priv->parent != entry->ep->dev)
|
||||
if (feature->priv->parent != entry->dev)
|
||||
continue;
|
||||
|
||||
pmt_get_features(entry, feature);
|
||||
|
|
|
|||
|
|
@ -329,6 +329,9 @@ int pmt_telem_read32(struct telem_endpoint *ep, u32 id, u32 *data, u32 count)
|
|||
if (!ep->present)
|
||||
return -ENODEV;
|
||||
|
||||
if (!ep->base)
|
||||
return -EIO;
|
||||
|
||||
offset = SAMPLE_ID_OFFSET32(id);
|
||||
size = ep->header.size;
|
||||
|
||||
|
|
|
|||
|
|
@ -250,10 +250,8 @@ static int intel_punit_ipc_probe(struct platform_device *pdev)
|
|||
ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc,
|
||||
IRQF_NO_SUSPEND, "intel_punit_ipc",
|
||||
punit_ipcdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to request irq: %d\n", irq);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
punit_ipcdev->irq = irq;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/cpufeature.h>
|
||||
#include <linux/cpuhotplug.h>
|
||||
#include <linux/fs.h>
|
||||
|
|
@ -774,6 +775,54 @@ void isst_if_cdev_unregister(int device_type)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(isst_if_cdev_unregister);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
|
||||
static bool isst_acpi_pm_profile_server(void)
|
||||
{
|
||||
if (acpi_gbl_FADT.preferred_profile == PM_ENTERPRISE_SERVER ||
|
||||
acpi_gbl_FADT.preferred_profile == PM_PERFORMANCE_SERVER)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static bool isst_acpi_pm_profile_server(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static const struct x86_cpu_id isst_allowed_families[] = {
|
||||
X86_MATCH_VENDOR_FAM(INTEL, 19, NULL),
|
||||
{}
|
||||
};
|
||||
|
||||
static bool isst_features_allowed(void)
|
||||
{
|
||||
u64 data;
|
||||
|
||||
/* For hypervisors, explicit model addition is required */
|
||||
if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR))
|
||||
return false;
|
||||
|
||||
/* SST is a server only feature */
|
||||
if (!isst_acpi_pm_profile_server())
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Family 19 and the checks above guarantee the SST enumeration
|
||||
* interfaces are present
|
||||
*/
|
||||
if (!x86_match_cpu(isst_allowed_families))
|
||||
return false;
|
||||
|
||||
/* MSR_PM_LOGICAL_ID is required to get PM logical CPU ID */
|
||||
return !rdmsrq_safe(MSR_PM_LOGICAL_ID, &data);
|
||||
}
|
||||
|
||||
#define SST_HPM_SUPPORTED 0x01
|
||||
#define SST_MBOX_SUPPORTED 0x02
|
||||
|
||||
|
|
@ -798,8 +847,13 @@ static int __init isst_if_common_init(void)
|
|||
const struct x86_cpu_id *id;
|
||||
|
||||
id = x86_match_cpu(isst_cpu_ids);
|
||||
if (!id)
|
||||
if (!id) {
|
||||
if (isst_features_allowed()) {
|
||||
isst_hpm_support = true;
|
||||
goto misc_reg;
|
||||
}
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (id->driver_data == SST_HPM_SUPPORTED) {
|
||||
isst_hpm_support = true;
|
||||
|
|
@ -812,6 +866,7 @@ static int __init isst_if_common_init(void)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
misc_reg:
|
||||
return isst_misc_reg();
|
||||
}
|
||||
module_init(isst_if_common_init)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ static struct telemetry_core_config telm_core_conf;
|
|||
static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit,
|
||||
u32 *verbosity)
|
||||
{
|
||||
if (!verbosity)
|
||||
return -EINVAL;
|
||||
|
||||
*verbosity = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
822
drivers/platform/x86/intel/tpmi_common.c
Normal file
822
drivers/platform/x86/intel/tpmi_common.c
Normal file
|
|
@ -0,0 +1,822 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Enumeration independent part of the Intel TPMI driver
|
||||
*
|
||||
* Copyright (c) 2026, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* The TPMI (Topology Aware Register and PM Capsule Interface) provides a
|
||||
* flexible, extendable and PCIe enumerable MMIO interface for PM features.
|
||||
*
|
||||
* For example Intel RAPL (Running Average Power Limit) provides a MMIO
|
||||
* interface using TPMI. This has advantage over traditional MSR
|
||||
* (Model Specific Register) interface, where a thread needs to be scheduled
|
||||
* on the target CPU to read or write. Also the RAPL features vary between
|
||||
* CPU models, and hence lot of model specific code. Here TPMI provides an
|
||||
* architectural interface by providing hierarchical tables and fields,
|
||||
* which will not need any model specific implementation.
|
||||
*
|
||||
* The TPMI specification defines a PFS (PM Feature Structure) table.
|
||||
* This table is present in the TPMI MMIO region. Each TPMI PM feature
|
||||
* has one entry in the PFS with a unique TPMI ID and its access details.
|
||||
*
|
||||
* The names of the devices created for the PM features start with the
|
||||
* "intel_vsec.tpmi-" prefix which is followed by a specific name of the
|
||||
* given PM feature (for example, "intel_vsec.tpmi-rapl.0").
|
||||
*
|
||||
* The device nodes are create by using interface "intel_vsec_add_aux()"
|
||||
* provided by the Intel VSEC driver.
|
||||
*/
|
||||
|
||||
#include <linux/align.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/compiler_attributes.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/intel_tpmi.h>
|
||||
#include <linux/intel_vsec.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string_helpers.h>
|
||||
#include <linux/time64.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "tpmi_common.h"
|
||||
|
||||
/**
|
||||
* struct intel_tpmi_pfs_entry - TPMI PM Feature Structure (PFS) entry
|
||||
* @tpmi_id: TPMI feature identifier (what the feature is and its data format).
|
||||
* @num_entries: Number of feature interface instances present in the PFS.
|
||||
* This represents the maximum number of Power domains in the SoC.
|
||||
* @entry_size: Interface instance entry size in 32-bit words.
|
||||
* @cap_offset: Offset from the PM_Features base address to the base of the PM VSEC
|
||||
* register bank in KB.
|
||||
* @attribute: Feature attribute: 0=BIOS. 1=OS. 2-3=Reserved.
|
||||
* @reserved: Bits for use in the future.
|
||||
*
|
||||
* Represents one TPMI feature entry data in the PFS retrieved as is
|
||||
* from the hardware.
|
||||
*/
|
||||
struct intel_tpmi_pfs_entry {
|
||||
u64 tpmi_id:8;
|
||||
u64 num_entries:8;
|
||||
u64 entry_size:16;
|
||||
u64 cap_offset:16;
|
||||
u64 attribute:2;
|
||||
u64 reserved:14;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* struct intel_tpmi_pm_feature - TPMI PM Feature information for a TPMI ID
|
||||
* @pfs_header: PFS header retireved from the hardware.
|
||||
* @vsec_offset: Starting MMIO address for this feature in bytes. Essentially
|
||||
* this offset = "Address" from VSEC header + PFS Capability
|
||||
* offset for this feature entry.
|
||||
*
|
||||
* Represents TPMI instance information for one TPMI ID.
|
||||
*/
|
||||
struct intel_tpmi_pm_feature {
|
||||
struct intel_tpmi_pfs_entry pfs_header;
|
||||
u64 vsec_offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct tpmi_info_header - CPU package ID to PCI device mapping information
|
||||
* @fn: PCI function number
|
||||
* @dev: PCI device number
|
||||
* @bus: PCI bus number
|
||||
* @pkg: CPU Package id
|
||||
* @segment: PCI segment id
|
||||
* @partition: Package Partition id
|
||||
* @cdie_mask: Bitmap of compute dies in the current partition
|
||||
* @reserved: Reserved for future use
|
||||
* @lock: When set to 1 the register is locked and becomes read-only
|
||||
* until next reset. Not for use by the OS driver.
|
||||
*
|
||||
* The structure to read hardware provided mapping information.
|
||||
*/
|
||||
struct tpmi_info_header {
|
||||
u64 fn:3;
|
||||
u64 dev:5;
|
||||
u64 bus:8;
|
||||
u64 pkg:8;
|
||||
u64 segment:8;
|
||||
u64 partition:2;
|
||||
u64 cdie_mask:16;
|
||||
u64 reserved:13;
|
||||
u64 lock:1;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* struct tpmi_feature_state - Structure to read hardware state of a feature
|
||||
* @enabled: Enable state of a feature, 1: enabled, 0: disabled
|
||||
* @reserved_1: Reserved for future use
|
||||
* @write_blocked: Writes are blocked means all write operations are ignored
|
||||
* @read_blocked: Reads are blocked means will read 0xFFs
|
||||
* @pcs_select: Interface used by out of band software, not used in OS
|
||||
* @reserved_2: Reserved for future use
|
||||
* @id: TPMI ID of the feature
|
||||
* @reserved_3: Reserved for future use
|
||||
* @locked: When set to 1, OS can't change this register.
|
||||
*
|
||||
* The structure is used to read hardware state of a TPMI feature. This
|
||||
* information is used for debug and restricting operations for this feature.
|
||||
*/
|
||||
struct tpmi_feature_state {
|
||||
u32 enabled:1;
|
||||
u32 reserved_1:3;
|
||||
u32 write_blocked:1;
|
||||
u32 read_blocked:1;
|
||||
u32 pcs_select:1;
|
||||
u32 reserved_2:1;
|
||||
u32 id:8;
|
||||
u32 reserved_3:15;
|
||||
u32 locked:1;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Return the size of a single feature interface instance in bytes. The
|
||||
* hardware reports it in u32 units. The value comes from trusted hardware,
|
||||
* but is still worth verifying on pre silicon platforms, so return 0 when
|
||||
* it is out of range.
|
||||
*/
|
||||
static u32 tpmi_get_single_entry_size(const struct intel_tpmi_pm_feature *pfs)
|
||||
{
|
||||
if (pfs->pfs_header.entry_size > SZ_1K)
|
||||
return 0;
|
||||
|
||||
return pfs->pfs_header.entry_size * sizeof(u32);
|
||||
}
|
||||
|
||||
/* Used during auxbus device creation */
|
||||
static DEFINE_IDA(intel_vsec_tpmi_ida);
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(tpmi_notify_list);
|
||||
|
||||
int tpmi_register_notifier(struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_register(&tpmi_notify_list, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_register_notifier, "INTEL_TPMI");
|
||||
|
||||
int tpmi_unregister_notifier(struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_unregister(&tpmi_notify_list, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_unregister_notifier, "INTEL_TPMI");
|
||||
|
||||
struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
|
||||
|
||||
return vsec_dev->priv_data;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_get_platform_data, "INTEL_TPMI");
|
||||
|
||||
int tpmi_get_resource_count(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
|
||||
|
||||
if (vsec_dev)
|
||||
return vsec_dev->num_resources;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_count, "INTEL_TPMI");
|
||||
|
||||
struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index)
|
||||
{
|
||||
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
|
||||
|
||||
if (vsec_dev && index < vsec_dev->num_resources)
|
||||
return &vsec_dev->resource[index];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_at_index, "INTEL_TPMI");
|
||||
|
||||
/* TPMI Control Interface */
|
||||
|
||||
#define TPMI_CONTROL_STATUS_OFFSET 0x00
|
||||
#define TPMI_COMMAND_OFFSET 0x08
|
||||
#define TMPI_CONTROL_DATA_VAL_OFFSET 0x0c
|
||||
|
||||
/*
|
||||
* Spec is calling for max 1 seconds to get ownership at the worst
|
||||
* case. Read at 10 ms timeouts and repeat up to 1 second.
|
||||
*/
|
||||
#define TPMI_CONTROL_TIMEOUT_US (10 * USEC_PER_MSEC)
|
||||
#define TPMI_CONTROL_TIMEOUT_MAX_US (1 * USEC_PER_SEC)
|
||||
|
||||
#define TPMI_RB_TIMEOUT_US (10 * USEC_PER_MSEC)
|
||||
#define TPMI_RB_TIMEOUT_MAX_US USEC_PER_SEC
|
||||
|
||||
/* TPMI Control status register defines */
|
||||
|
||||
#define TPMI_CONTROL_STATUS_RB BIT_ULL(0)
|
||||
|
||||
#define TPMI_CONTROL_STATUS_OWNER GENMASK_ULL(5, 4)
|
||||
#define TPMI_OWNER_NONE 0
|
||||
#define TPMI_OWNER_IN_BAND 1
|
||||
|
||||
#define TPMI_CONTROL_STATUS_CPL BIT_ULL(6)
|
||||
#define TPMI_CONTROL_STATUS_RESULT GENMASK_ULL(15, 8)
|
||||
#define TPMI_CONTROL_STATUS_LEN GENMASK_ULL(31, 16)
|
||||
|
||||
#define TPMI_CMD_PKT_LEN 2
|
||||
#define TPMI_CMD_STATUS_SUCCESS 0x40
|
||||
|
||||
/* TPMI command data registers */
|
||||
#define TMPI_CONTROL_DATA_CMD GENMASK_ULL(7, 0)
|
||||
#define TPMI_CONTROL_DATA_VAL_FEATURE GENMASK_ULL(48, 40)
|
||||
|
||||
/* Command to send via control interface */
|
||||
#define TPMI_CONTROL_GET_STATE_CMD 0x10
|
||||
|
||||
#define TPMI_CONTROL_CMD_MASK GENMASK_ULL(48, 40)
|
||||
|
||||
#define TPMI_CMD_LEN_MASK GENMASK_ULL(18, 16)
|
||||
|
||||
/* Mutex to complete get feature status without interruption */
|
||||
static DEFINE_MUTEX(tpmi_dev_lock);
|
||||
|
||||
static int tpmi_wait_for_owner(struct intel_tpmi_info *tpmi_info, u8 owner)
|
||||
{
|
||||
u64 control;
|
||||
|
||||
return readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET,
|
||||
control, owner == FIELD_GET(TPMI_CONTROL_STATUS_OWNER, control),
|
||||
TPMI_CONTROL_TIMEOUT_US, TPMI_CONTROL_TIMEOUT_MAX_US);
|
||||
}
|
||||
|
||||
static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int feature_id,
|
||||
struct tpmi_feature_state *feature_state)
|
||||
{
|
||||
u64 control, data;
|
||||
int ret;
|
||||
|
||||
if (!tpmi_info->tpmi_control_mem)
|
||||
return -EFAULT;
|
||||
|
||||
guard(mutex)(&tpmi_dev_lock);
|
||||
|
||||
/* Wait for owner bit set to 0 (none) */
|
||||
ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Set command id to 0x10 for TPMI_GET_STATE, with the feature id 32
|
||||
* bits into the DATA offset, +8 for the feature_id field.
|
||||
*/
|
||||
data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD) |
|
||||
FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id);
|
||||
|
||||
/* Write at command offset for qword access */
|
||||
writeq(data, tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET);
|
||||
|
||||
/* Wait for owner bit set to in-band */
|
||||
ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_IN_BAND);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Set Run Busy and packet length of 2 dwords */
|
||||
control = TPMI_CONTROL_STATUS_RB |
|
||||
FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN);
|
||||
|
||||
/* Write at status offset for qword access */
|
||||
writeq(control, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET);
|
||||
|
||||
/* Wait for Run Busy clear */
|
||||
ret = readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET,
|
||||
control, !(control & TPMI_CONTROL_STATUS_RB),
|
||||
TPMI_RB_TIMEOUT_US, TPMI_RB_TIMEOUT_MAX_US);
|
||||
if (ret)
|
||||
goto done_proc;
|
||||
|
||||
control = FIELD_GET(TPMI_CONTROL_STATUS_RESULT, control);
|
||||
if (control != TPMI_CMD_STATUS_SUCCESS) {
|
||||
ret = -EBUSY;
|
||||
goto done_proc;
|
||||
}
|
||||
|
||||
/* Response is ready */
|
||||
memcpy_fromio(feature_state, tpmi_info->tpmi_control_mem + TMPI_CONTROL_DATA_VAL_OFFSET,
|
||||
sizeof(*feature_state));
|
||||
|
||||
ret = 0;
|
||||
|
||||
done_proc:
|
||||
/* Set CPL "completion" bit */
|
||||
writeq(TPMI_CONTROL_STATUS_CPL, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int tpmi_get_feature_status(struct auxiliary_device *auxdev,
|
||||
int feature_id, bool *read_blocked, bool *write_blocked)
|
||||
{
|
||||
struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent);
|
||||
struct tpmi_feature_state feature_state;
|
||||
int ret;
|
||||
|
||||
ret = tpmi_read_feature_status(tpmi_info, feature_id, &feature_state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*read_blocked = feature_state.read_blocked;
|
||||
*write_blocked = feature_state.write_blocked;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, "INTEL_TPMI");
|
||||
|
||||
struct dentry *tpmi_get_debugfs_dir(struct auxiliary_device *auxdev)
|
||||
{
|
||||
struct intel_tpmi_info *tpmi_info = dev_get_drvdata(auxdev->dev.parent);
|
||||
|
||||
return tpmi_info->dbgfs_dir;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(tpmi_get_debugfs_dir, "INTEL_TPMI");
|
||||
|
||||
static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct intel_tpmi_info *tpmi_info = s->private;
|
||||
int locked, disabled, read_blocked, write_blocked;
|
||||
struct tpmi_feature_state feature_state;
|
||||
struct intel_tpmi_pm_feature *pfs;
|
||||
int ret;
|
||||
|
||||
seq_printf(s, "tpmi PFS start offset 0x:%llx\n", tpmi_info->pfs_start);
|
||||
seq_puts(s, "tpmi_id\t\tentries\t\tsize\t\tcap_offset\tattribute\tvsec_offset\tlocked\tdisabled\tread_blocked\twrite_blocked\n");
|
||||
for (int i = 0; i < tpmi_info->feature_count; i++) {
|
||||
pfs = &tpmi_info->tpmi_features[i];
|
||||
ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state);
|
||||
if (ret) {
|
||||
locked = 'U';
|
||||
disabled = 'U';
|
||||
read_blocked = 'U';
|
||||
write_blocked = 'U';
|
||||
} else {
|
||||
disabled = feature_state.enabled ? 'N' : 'Y';
|
||||
locked = feature_state.locked ? 'Y' : 'N';
|
||||
read_blocked = feature_state.read_blocked ? 'Y' : 'N';
|
||||
write_blocked = feature_state.write_blocked ? 'Y' : 'N';
|
||||
}
|
||||
seq_printf(s, "0x%02x\t\t0x%02x\t\t0x%04x\t\t0x%04x\t\t0x%02x\t\t0x%016llx\t%c\t%c\t\t%c\t\t%c\n",
|
||||
pfs->pfs_header.tpmi_id, pfs->pfs_header.num_entries,
|
||||
pfs->pfs_header.entry_size, pfs->pfs_header.cap_offset,
|
||||
pfs->pfs_header.attribute, pfs->vsec_offset, locked, disabled,
|
||||
read_blocked, write_blocked);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(tpmi_pfs_dbg);
|
||||
|
||||
#define MEM_DUMP_COLUMN_COUNT 8
|
||||
|
||||
static int tpmi_mem_dump_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32);
|
||||
struct intel_tpmi_pm_feature *pfs = s->private;
|
||||
void __iomem *mem;
|
||||
u32 size;
|
||||
u64 off;
|
||||
|
||||
size = tpmi_get_single_entry_size(pfs);
|
||||
if (!size)
|
||||
return -EIO;
|
||||
|
||||
u8 *buffer __free(kfree) = kmalloc(size, GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
off = pfs->vsec_offset;
|
||||
|
||||
guard(mutex)(&tpmi_dev_lock);
|
||||
|
||||
for (int count = 0; count < pfs->pfs_header.num_entries; count++) {
|
||||
seq_printf(s, "TPMI Instance:%d offset:0x%llx\n", count, off);
|
||||
|
||||
mem = ioremap(off, size);
|
||||
if (!mem)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy_fromio(buffer, mem, size);
|
||||
|
||||
seq_hex_dump(s, " ", DUMP_PREFIX_OFFSET, row_size, sizeof(u32), buffer, size,
|
||||
false);
|
||||
|
||||
iounmap(mem);
|
||||
|
||||
off += size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump);
|
||||
|
||||
static ssize_t mem_write_write(struct file *file, const char __user *userbuf, size_t len,
|
||||
loff_t *ppos)
|
||||
{
|
||||
struct seq_file *m = file->private_data;
|
||||
struct intel_tpmi_pm_feature *pfs = m->private;
|
||||
u32 addr, value, punit, size;
|
||||
u32 num_elems;
|
||||
void __iomem *mem;
|
||||
int ret;
|
||||
|
||||
size = tpmi_get_single_entry_size(pfs);
|
||||
if (!size)
|
||||
return -EIO;
|
||||
|
||||
u32 *array __free(kfree) = NULL;
|
||||
ret = parse_int_array_user(userbuf, len, (int **)&array);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
num_elems = *array;
|
||||
if (num_elems != 3)
|
||||
return -EINVAL;
|
||||
|
||||
punit = array[1];
|
||||
addr = array[2];
|
||||
value = array[3];
|
||||
|
||||
if (!IS_ALIGNED(addr, sizeof(u32)))
|
||||
return -EINVAL;
|
||||
|
||||
if (punit >= pfs->pfs_header.num_entries)
|
||||
return -EINVAL;
|
||||
|
||||
if (addr >= size)
|
||||
return -EINVAL;
|
||||
|
||||
guard(mutex)(&tpmi_dev_lock);
|
||||
|
||||
mem = ioremap(pfs->vsec_offset + punit * size, size);
|
||||
if (!mem)
|
||||
return -ENOMEM;
|
||||
|
||||
writel(value, mem + addr);
|
||||
|
||||
iounmap(mem);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int mem_write_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_STORE_ATTRIBUTE(mem_write);
|
||||
|
||||
static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info)
|
||||
{
|
||||
char name[64];
|
||||
|
||||
scnprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_info->parent));
|
||||
tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL);
|
||||
|
||||
debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops);
|
||||
|
||||
for (int i = 0; i < tpmi_info->feature_count; i++) {
|
||||
struct intel_tpmi_pm_feature *pfs;
|
||||
struct dentry *dir;
|
||||
|
||||
pfs = &tpmi_info->tpmi_features[i];
|
||||
scnprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id);
|
||||
dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir);
|
||||
|
||||
debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops);
|
||||
debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_fops);
|
||||
}
|
||||
}
|
||||
|
||||
static void tpmi_set_control_base(struct intel_tpmi_info *tpmi_info,
|
||||
struct intel_tpmi_pm_feature *pfs)
|
||||
{
|
||||
void __iomem *mem;
|
||||
u32 size;
|
||||
|
||||
size = tpmi_get_single_entry_size(pfs);
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
mem = devm_ioremap(tpmi_info->tpmi_dev, pfs->vsec_offset, size);
|
||||
if (!mem)
|
||||
return;
|
||||
|
||||
/* mem is pointing to TPMI CONTROL base */
|
||||
tpmi_info->tpmi_control_mem = mem;
|
||||
}
|
||||
|
||||
/*
|
||||
* The TPMI IDs are sparse, so the unused entries are left NULL and are
|
||||
* rejected by the caller like any other unsupported feature.
|
||||
*/
|
||||
static const char * const intel_tpmi_names[] = {
|
||||
[TPMI_ID_RAPL] = "tpmi-rapl",
|
||||
[TPMI_ID_PEM] = "tpmi-pem",
|
||||
[TPMI_ID_UNCORE] = "tpmi-uncore",
|
||||
[TPMI_ID_SST] = "tpmi-sst",
|
||||
[TPMI_ID_PLR] = "tpmi-plr",
|
||||
};
|
||||
|
||||
static const char *intel_tpmi_name(enum intel_tpmi_id id)
|
||||
{
|
||||
if (id >= ARRAY_SIZE(intel_tpmi_names))
|
||||
return NULL;
|
||||
|
||||
return intel_tpmi_names[id];
|
||||
}
|
||||
|
||||
static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
|
||||
struct intel_tpmi_pm_feature *pfs,
|
||||
u64 pfs_start)
|
||||
{
|
||||
struct intel_vsec_device *feature_vsec_dev;
|
||||
struct tpmi_feature_state feature_state;
|
||||
struct resource *res;
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* If not enabled, continue to look at other features in the PFS, so return -EOPNOTSUPP.
|
||||
* This will not cause failure of loading of this driver.
|
||||
*/
|
||||
if (!feature_state.enabled)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
name = intel_tpmi_name(pfs->pfs_header.tpmi_id);
|
||||
if (!name)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
feature_vsec_dev = kzalloc_flex(*feature_vsec_dev, resource, pfs->pfs_header.num_entries);
|
||||
if (!feature_vsec_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
feature_vsec_dev->num_resources = pfs->pfs_header.num_entries;
|
||||
res = feature_vsec_dev->resource;
|
||||
|
||||
for (int i = 0; i < pfs->pfs_header.num_entries; i++) {
|
||||
u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32);
|
||||
|
||||
res[i] = DEFINE_RES_MEM(pfs->vsec_offset + entry_size_bytes * i, entry_size_bytes);
|
||||
}
|
||||
|
||||
feature_vsec_dev->dev = tpmi_info->parent;
|
||||
feature_vsec_dev->priv_data = &tpmi_info->plat_info;
|
||||
feature_vsec_dev->priv_data_size = sizeof(tpmi_info->plat_info);
|
||||
feature_vsec_dev->ida = &intel_vsec_tpmi_ida;
|
||||
|
||||
/*
|
||||
* intel_vsec_add_aux() is resource managed, no explicit
|
||||
* delete is required on error or on module unload.
|
||||
* feature_vsec_dev and res memory are also freed as part of
|
||||
* device deletion.
|
||||
*
|
||||
* "name" must outlive the auxiliary device, as the auxiliary bus
|
||||
* stores the pointer rather than a copy of the string.
|
||||
*/
|
||||
return intel_vsec_add_aux(tpmi_info->tpmi_dev, feature_vsec_dev, name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a device for every feature in the PFS that has one.
|
||||
*
|
||||
* A feature that is not supported is reported as -EOPNOTSUPP and skipped.
|
||||
* Any other error fails the driver load. intel_vsec_add_aux() is resource
|
||||
* managed, so the devices created before such a failure need no clean up
|
||||
* here.
|
||||
*/
|
||||
static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
for (int i = 0; i < tpmi_info->feature_count; i++) {
|
||||
ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i],
|
||||
tpmi_info->pfs_start);
|
||||
if (ret == -EOPNOTSUPP)
|
||||
continue;
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define TPMI_INFO_BUS_INFO_OFFSET 0x08
|
||||
#define TPMI_INFO_MAJOR_VERSION 0x00
|
||||
#define TPMI_INFO_MINOR_VERSION 0x02
|
||||
|
||||
static int tpmi_process_info(struct intel_tpmi_info *tpmi_info,
|
||||
struct intel_tpmi_pm_feature *pfs)
|
||||
{
|
||||
struct tpmi_info_header header;
|
||||
void __iomem *info_mem;
|
||||
u64 feature_header;
|
||||
int ret = 0;
|
||||
|
||||
info_mem = ioremap(pfs->vsec_offset, pfs->pfs_header.entry_size * sizeof(u32));
|
||||
if (!info_mem)
|
||||
return -ENOMEM;
|
||||
|
||||
feature_header = readq(info_mem);
|
||||
if (TPMI_MAJOR_VERSION(feature_header) != TPMI_INFO_MAJOR_VERSION) {
|
||||
ret = -ENODEV;
|
||||
goto error_info_header;
|
||||
}
|
||||
|
||||
memcpy_fromio(&header, info_mem + TPMI_INFO_BUS_INFO_OFFSET, sizeof(header));
|
||||
|
||||
tpmi_info->plat_info.package_id = header.pkg;
|
||||
tpmi_info->plat_info.bus_number = header.bus;
|
||||
tpmi_info->plat_info.device_number = header.dev;
|
||||
tpmi_info->plat_info.function_number = header.fn;
|
||||
|
||||
if (TPMI_MINOR_VERSION(feature_header) >= TPMI_INFO_MINOR_VERSION) {
|
||||
tpmi_info->plat_info.cdie_mask = header.cdie_mask;
|
||||
tpmi_info->plat_info.partition = header.partition;
|
||||
tpmi_info->plat_info.segment = header.segment;
|
||||
}
|
||||
|
||||
error_info_header:
|
||||
iounmap(info_mem);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, int size)
|
||||
{
|
||||
void __iomem *pfs_mem;
|
||||
|
||||
pfs_mem = ioremap(start, size);
|
||||
if (!pfs_mem)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy_fromio(&pfs->pfs_header, pfs_mem, sizeof(pfs->pfs_header));
|
||||
|
||||
iounmap(pfs_mem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define TPMI_CAP_OFFSET_UNIT 1024
|
||||
|
||||
/**
|
||||
* intel_tpmi_init() - Bring up a TPMI instance
|
||||
* @tpmi_info: Caller allocated and zeroed state of the TPMI instance
|
||||
* @tpmi_dev: Device representing the TPMI instance. Owns the devm
|
||||
* allocations and parents the TPMI feature devices
|
||||
* @parent: Device the TPMI instance was enumerated from
|
||||
* @resource: Array of @feature_count MMIO resources, one per PFS entry
|
||||
* @feature_count: Number of entries in @resource
|
||||
*
|
||||
* Walk the PFS of the TPMI instance described by @resource, register the
|
||||
* debugfs interface and create a device for each supported PM feature.
|
||||
*
|
||||
* Return: 0 on success, negative errno otherwise.
|
||||
*/
|
||||
int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev,
|
||||
struct device *parent, struct resource *resource, int feature_count)
|
||||
{
|
||||
struct pci_dev *pci_dev = dev_is_pci(parent) ? to_pci_dev(parent) : NULL;
|
||||
u64 pfs_start = 0;
|
||||
int ret;
|
||||
|
||||
tpmi_info->tpmi_dev = tpmi_dev;
|
||||
tpmi_info->parent = parent;
|
||||
tpmi_info->feature_count = feature_count;
|
||||
tpmi_info->resource = resource;
|
||||
|
||||
/*
|
||||
* Seed the bus number from the enumerating device. It is only a
|
||||
* fallback, tpmi_process_info() replaces it with the value the
|
||||
* TPMI_INFO feature reports.
|
||||
*/
|
||||
if (pci_dev)
|
||||
tpmi_info->plat_info.bus_number = pci_dev->bus->number;
|
||||
|
||||
tpmi_info->tpmi_features = devm_kcalloc(tpmi_info->tpmi_dev, tpmi_info->feature_count,
|
||||
sizeof(*tpmi_info->tpmi_features),
|
||||
GFP_KERNEL);
|
||||
if (!tpmi_info->tpmi_features)
|
||||
return -ENOMEM;
|
||||
|
||||
for (int i = 0; i < tpmi_info->feature_count; i++) {
|
||||
struct intel_tpmi_pm_feature *pfs;
|
||||
struct resource *res;
|
||||
u64 res_start;
|
||||
int size, ret;
|
||||
|
||||
pfs = &tpmi_info->tpmi_features[i];
|
||||
|
||||
res = &tpmi_info->resource[i];
|
||||
if (!res)
|
||||
continue;
|
||||
|
||||
res_start = res->start;
|
||||
size = resource_size(res);
|
||||
if (size < 0)
|
||||
continue;
|
||||
|
||||
ret = tpmi_fetch_pfs_header(pfs, res_start, size);
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
if (!pfs_start)
|
||||
pfs_start = res_start;
|
||||
|
||||
pfs->vsec_offset = pfs_start + pfs->pfs_header.cap_offset * TPMI_CAP_OFFSET_UNIT;
|
||||
|
||||
/*
|
||||
* Process TPMI_INFO to get PCI device to CPU package ID.
|
||||
* Device nodes for TPMI features are not created in this
|
||||
* for loop. So, the mapping information will be available
|
||||
* when actual device nodes created outside this
|
||||
* loop via tpmi_create_devices().
|
||||
*/
|
||||
if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID) {
|
||||
ret = tpmi_process_info(tpmi_info, pfs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The mapping is looked up by pci_dev, so it is only
|
||||
* meaningful for PCI enumerated TPMI instances.
|
||||
*/
|
||||
if (pci_dev) {
|
||||
ret = intel_vsec_set_mapping(&tpmi_info->plat_info,
|
||||
&pci_dev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID)
|
||||
tpmi_set_control_base(tpmi_info, pfs);
|
||||
}
|
||||
|
||||
tpmi_info->pfs_start = pfs_start;
|
||||
|
||||
dev_set_drvdata(tpmi_info->tpmi_dev, tpmi_info);
|
||||
|
||||
/*
|
||||
* Allow debugfs when security policy allows. Everything this debugfs
|
||||
* interface provides, can also be done via /dev/mem access. If
|
||||
* /dev/mem interface is locked, don't allow debugfs to present any
|
||||
* information. Also check for CAP_SYS_RAWIO as /dev/mem interface.
|
||||
*/
|
||||
if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO))
|
||||
tpmi_dbgfs_register(tpmi_info);
|
||||
|
||||
ret = tpmi_create_devices(tpmi_info);
|
||||
if (ret) {
|
||||
debugfs_remove_recursive(tpmi_info->dbgfs_dir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, tpmi_info->tpmi_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(intel_tpmi_init, "INTEL_TPMI");
|
||||
|
||||
/**
|
||||
* intel_tpmi_deinit() - Tear a TPMI instance down
|
||||
* @tpmi_info: State of the TPMI instance, as passed to intel_tpmi_init()
|
||||
*
|
||||
* The TPMI feature devices and the devm allocations are released by the
|
||||
* device core when the TPMI device goes away.
|
||||
*/
|
||||
void intel_tpmi_deinit(struct intel_tpmi_info *tpmi_info)
|
||||
{
|
||||
blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, tpmi_info->tpmi_dev);
|
||||
|
||||
debugfs_remove_recursive(tpmi_info->dbgfs_dir);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(intel_tpmi_deinit, "INTEL_TPMI");
|
||||
|
||||
MODULE_IMPORT_NS("INTEL_VSEC");
|
||||
MODULE_DESCRIPTION("Intel TPMI core support");
|
||||
MODULE_LICENSE("GPL");
|
||||
52
drivers/platform/x86/intel/tpmi_common.h
Normal file
52
drivers/platform/x86/intel/tpmi_common.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Intel TPMI core interface for TPMI enumeration methods
|
||||
*
|
||||
* Copyright (c) 2026, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _TPMI_COMMON_H_
|
||||
#define _TPMI_COMMON_H_
|
||||
|
||||
#include <linux/intel_vsec.h>
|
||||
|
||||
struct intel_tpmi_pm_feature;
|
||||
|
||||
/**
|
||||
* struct intel_tpmi_info - TPMI information for all IDs in an instance
|
||||
* @tpmi_features: Pointer to a list of TPMI feature instances
|
||||
* @feature_count: Number of TPMI of TPMI instances pointed by tpmi_features
|
||||
* @pfs_start: Start of PFS offset for the TPMI instances in this device
|
||||
* @plat_info: Stores platform info which can be used by the client drivers
|
||||
* @tpmi_control_mem: Memory mapped IO for getting control information
|
||||
* @dbgfs_dir: debugfs entry pointer
|
||||
* @resource: Array of feature_count MMIO resources, one per PFS entry
|
||||
* @tpmi_dev: Device this TPMI instance is bound to. It backs the devm
|
||||
* allocations, holds this structure as its driver data and
|
||||
* the TPMI feature devices are created under it.
|
||||
* @parent: Device this TPMI instance was enumerated from. It names
|
||||
* the debugfs directory and becomes intel_vsec_device::dev
|
||||
* of every TPMI feature device, so that the enumerating bus
|
||||
* can find them again, for example during PCI error
|
||||
* recovery.
|
||||
*
|
||||
* Stores the information for all TPMI devices of one TPMI instance.
|
||||
*/
|
||||
struct intel_tpmi_info {
|
||||
struct intel_tpmi_pm_feature *tpmi_features;
|
||||
int feature_count;
|
||||
u64 pfs_start;
|
||||
struct oobmsm_plat_info plat_info;
|
||||
void __iomem *tpmi_control_mem;
|
||||
struct dentry *dbgfs_dir;
|
||||
struct resource *resource;
|
||||
struct device *tpmi_dev;
|
||||
struct device *parent;
|
||||
};
|
||||
|
||||
int intel_tpmi_init(struct intel_tpmi_info *tpmi_info, struct device *tpmi_dev,
|
||||
struct device *parent, struct resource *resource, int feature_count);
|
||||
void intel_tpmi_deinit(struct intel_tpmi_info *tpmi_info);
|
||||
|
||||
#endif /* _TPMI_COMMON_H_ */
|
||||
|
|
@ -139,6 +139,13 @@ cpumask_t *tpmi_get_power_domain_mask(int cpu_no)
|
|||
cpumask_t *mask;
|
||||
int index;
|
||||
|
||||
/*
|
||||
* When built in, this stays callable after tpmi_init() failed
|
||||
* before allocating the array.
|
||||
*/
|
||||
if (!tpmi_power_domain_mask)
|
||||
return NULL;
|
||||
|
||||
if (cpu_no >= num_possible_cpus())
|
||||
return NULL;
|
||||
|
||||
|
|
@ -156,6 +163,13 @@ EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN");
|
|||
|
||||
int tpmi_get_linux_die_id(int pkg_id, int domain_id)
|
||||
{
|
||||
/*
|
||||
* When built in, this stays callable after tpmi_init() failed
|
||||
* before allocating the map.
|
||||
*/
|
||||
if (!domain_die_map)
|
||||
return -ENODEV;
|
||||
|
||||
if (pkg_id >= topology_max_packages() || domain_id >= MAX_POWER_DOMAINS)
|
||||
return -EINVAL;
|
||||
|
||||
|
|
@ -245,9 +259,11 @@ static int __init tpmi_init(void)
|
|||
|
||||
free_domain_map:
|
||||
kfree(domain_die_map);
|
||||
domain_die_map = NULL;
|
||||
|
||||
free_domain_mask:
|
||||
kfree(tpmi_power_domain_mask);
|
||||
tpmi_power_domain_mask = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -257,7 +273,9 @@ static void __exit tpmi_exit(void)
|
|||
{
|
||||
cpuhp_remove_state(tpmi_hp_state);
|
||||
kfree(tpmi_power_domain_mask);
|
||||
tpmi_power_domain_mask = NULL;
|
||||
kfree(domain_die_map);
|
||||
domain_die_map = NULL;
|
||||
}
|
||||
module_exit(tpmi_exit)
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user