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:
Linus Torvalds 2026-10-28 07:36:32 -10:00
commit c339f4010d
133 changed files with 6813 additions and 2864 deletions

View 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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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
------------------------------------

View File

@ -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.

View File

@ -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

View 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.

View File

@ -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.

View File

@ -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

View File

@ -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_*/

View File

@ -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

View File

@ -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 */

View File

@ -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.

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View 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");

View File

@ -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);

View File

@ -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

View File

@ -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)

View 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");

View File

@ -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)

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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],

View File

@ -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 },
{ },
};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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/

View 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");

View File

@ -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;

View File

@ -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);

View File

@ -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]);

View File

@ -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);
}

View File

@ -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;

View File

@ -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",

View File

@ -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[] = {

View File

@ -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);

View File

@ -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,

View File

@ -138,14 +138,6 @@ static void amd_pmf_dbgfs_register(struct amd_pmf_dev *dev)
&current_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);

View File

@ -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);

View File

@ -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)

View File

@ -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)
{

View File

@ -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 = {

View File

@ -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);

View File

@ -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,
},
},
},
{}
};

View File

@ -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.

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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;

View File

@ -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"),

View File

@ -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;

View File

@ -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 = {

View File

@ -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[] = {

View File

@ -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);

View File

@ -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);

View File

@ -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)
{

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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 };

View File

@ -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 } },

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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;
}

View File

@ -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"),
},
},
{ }
};

View File

@ -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);

View File

@ -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),

View File

@ -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

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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, &reg, 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, &reg, 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);

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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)

View File

@ -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;
}

View 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");

View 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_ */

View File

@ -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