diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-28 07:36:32 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-28 07:36:32 -1000 |
| commit | c339f4010dea8b0d754f84ace70cd1b089878ef8 (patch) | |
| tree | 7494ea078a05b0b824c3f263260e45c1f78bcfd3 | |
| parent | d738fbd1a8b9d82ef5ea0d54a9e2b3ab7f1c24dd (diff) | |
| parent | 9d3475065167197742b0882aff21cf8e78393277 (diff) | |
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
...
133 files changed, 6805 insertions, 2856 deletions
diff --git a/Documentation/ABI/removed/sysfs-driver-uniwill-laptop b/Documentation/ABI/removed/sysfs-driver-uniwill-laptop new file mode 100644 index 000000000000..c226efc23b59 --- /dev/null +++ b/Documentation/ABI/removed/sysfs-driver-uniwill-laptop @@ -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. diff --git a/Documentation/ABI/testing/debugfs-tpmi b/Documentation/ABI/testing/debugfs-tpmi index c493a1403d2f..09212978fc41 100644 --- a/Documentation/ABI/testing/debugfs-tpmi +++ b/Documentation/ABI/testing/debugfs-tpmi @@ -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 diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec b/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec new file mode 100644 index 000000000000..80d01fc32abb --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yoga-slim7x-gen11-ec @@ -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 diff --git a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop index 943f92c6b561..f3060aaf8a76 100644 --- a/Documentation/ABI/testing/sysfs-driver-uniwill-laptop +++ b/Documentation/ABI/testing/sysfs-driver-uniwill-laptop @@ -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. diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst index db4588af0278..60bf6868b849 100644 --- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst +++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst @@ -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 ------------------------------------ diff --git a/Documentation/admin-guide/laptops/uniwill-laptop.rst b/Documentation/admin-guide/laptops/uniwill-laptop.rst index be50b45b82ef..d8ce30f10d7c 100644 --- a/Documentation/admin-guide/laptops/uniwill-laptop.rst +++ b/Documentation/admin-guide/laptops/uniwill-laptop.rst @@ -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. diff --git a/Documentation/arch/x86/amd_hsmp.rst b/Documentation/arch/x86/amd_hsmp.rst index fa1fc240e212..c483c68dcfad 100644 --- a/Documentation/arch/x86/amd_hsmp.rst +++ b/Documentation/arch/x86/amd_hsmp.rst @@ -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 diff --git a/Documentation/wmi/devices/acer-wmi-battery.rst b/Documentation/wmi/devices/acer-wmi-battery.rst new file mode 100644 index 000000000000..8aa3e1334490 --- /dev/null +++ b/Documentation/wmi/devices/acer-wmi-battery.rst @@ -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. diff --git a/Documentation/wmi/devices/bitland-mifs-wmi.rst b/Documentation/wmi/devices/bitland-mifs-wmi.rst index 9e86ecc2993c..27c3fd248279 100644 --- a/Documentation/wmi/devices/bitland-mifs-wmi.rst +++ b/Documentation/wmi/devices/bitland-mifs-wmi.rst @@ -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. diff --git a/MAINTAINERS b/MAINTAINERS index 81fcd0bb9dbe..748ce4d1c6a2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h index eda336bfd3e9..dcbe89dc4d2b 100644 --- a/arch/x86/include/uapi/asm/amd_hsmp.h +++ b/arch/x86/include/uapi/asm/amd_hsmp.h @@ -10,6 +10,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 */ enum hsmp_message_ids { @@ -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_*/ diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index f165d14cf61a..cc1e58fa664b 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -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 diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 326d4c6d132a..2050b8ff5384 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -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 */ diff --git a/drivers/gpu/drm/amd/amdgpu/Kconfig b/drivers/gpu/drm/amd/amdgpu/Kconfig index 12e4a41bf1f0..50305630f533 100644 --- a/drivers/gpu/drm/amd/amdgpu/Kconfig +++ b/drivers/gpu/drm/amd/amdgpu/Kconfig @@ -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. diff --git a/drivers/gpu/drm/gma500/Kconfig b/drivers/gpu/drm/gma500/Kconfig index a2acaa699dd5..dc4c56638819 100644 --- a/drivers/gpu/drm/gma500/Kconfig +++ b/drivers/gpu/drm/gma500/Kconfig @@ -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 diff --git a/drivers/gpu/drm/i915/Kconfig b/drivers/gpu/drm/i915/Kconfig index 5e939004b646..71426b9c91f0 100644 --- a/drivers/gpu/drm/i915/Kconfig +++ b/drivers/gpu/drm/i915/Kconfig @@ -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 diff --git a/drivers/gpu/drm/radeon/Kconfig b/drivers/gpu/drm/radeon/Kconfig index c479f0c0dd5c..3cd70aef770e 100644 --- a/drivers/gpu/drm/radeon/Kconfig +++ b/drivers/gpu/drm/radeon/Kconfig @@ -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 diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig index 4d7dcaff2b91..502c7b3c1f16 100644 --- a/drivers/gpu/drm/xe/Kconfig +++ b/drivers/gpu/drm/xe/Kconfig @@ -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 diff --git a/drivers/platform/arm64/Kconfig b/drivers/platform/arm64/Kconfig index e32e01b2a9bd..2a84775a13a9 100644 --- a/drivers/platform/arm64/Kconfig +++ b/drivers/platform/arm64/Kconfig @@ -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 diff --git a/drivers/platform/arm64/Makefile b/drivers/platform/arm64/Makefile index 7681be4a46e9..3bc3d4669c27 100644 --- a/drivers/platform/arm64/Makefile +++ b/drivers/platform/arm64/Makefile @@ -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 diff --git a/drivers/platform/arm64/acer-aspire1-ec.c b/drivers/platform/arm64/acer-aspire1-ec.c index 08d0b155a197..501c62a096f8 100644 --- a/drivers/platform/arm64/acer-aspire1-ec.c +++ b/drivers/platform/arm64/acer-aspire1-ec.c @@ -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; } diff --git a/drivers/platform/arm64/asus-glymur-ec.c b/drivers/platform/arm64/asus-glymur-ec.c new file mode 100644 index 000000000000..d75923fac9c4 --- /dev/null +++ b/drivers/platform/arm64/asus-glymur-ec.c @@ -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"); diff --git a/drivers/platform/arm64/huawei-gaokun-ec.c b/drivers/platform/arm64/huawei-gaokun-ec.c index 80a8ba8b8dda..d4ce8ad6426c 100644 --- a/drivers/platform/arm64/huawei-gaokun-ec.c +++ b/drivers/platform/arm64/huawei-gaokun-ec.c @@ -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); diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c index e7acb66b77f2..7a875877c8f3 100644 --- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c +++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c @@ -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 diff --git a/drivers/platform/arm64/lenovo-yoga-c630.c b/drivers/platform/arm64/lenovo-yoga-c630.c index a8600a977fbc..cad40c58c55b 100644 --- a/drivers/platform/arm64/lenovo-yoga-c630.c +++ b/drivers/platform/arm64/lenovo-yoga-c630.c @@ -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) diff --git a/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c b/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c new file mode 100644 index 000000000000..257d64eb0e56 --- /dev/null +++ b/drivers/platform/arm64/lenovo-yoga-slim7x-gen11-ec.c @@ -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"); diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c index 4d2ad042a7f8..d34afd901324 100644 --- a/drivers/platform/arm64/qcom-hamoa-ec.c +++ b/drivers/platform/arm64/qcom-hamoa-ec.c @@ -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; + + ret = i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, + EC_MODERN_STANDBY_EXIT); + enable_irq(client->irq); - return i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, - EC_MODERN_STANDBY_EXIT); + return ret; } static int qcom_ec_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); + int ret; + + /* 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 i2c_smbus_write_byte_data(client, EC_MODERN_STANDBY_CMD, - EC_MODERN_STANDBY_ENTER); + return ret; } static int qcom_ec_probe(struct i2c_client *client) diff --git a/drivers/platform/loongarch/Kconfig b/drivers/platform/loongarch/Kconfig index 447528797d07..db7833842142 100644 --- a/drivers/platform/loongarch/Kconfig +++ b/drivers/platform/loongarch/Kconfig @@ -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 diff --git a/drivers/platform/mellanox/mlxbf-tmfifo.c b/drivers/platform/mellanox/mlxbf-tmfifo.c index 664bec20a7ae..d67215df7371 100644 --- a/drivers/platform/mellanox/mlxbf-tmfifo.c +++ b/drivers/platform/mellanox/mlxbf-tmfifo.c @@ -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; } diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c index d246772aafd6..d6549dd21772 100644 --- a/drivers/platform/mellanox/mlxreg-hotplug.c +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -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); diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 4a2d36f7331e..063f7e55c421 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -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], diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index d37a7e4c6b02..cf3ae252b1c4 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -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 }, { }, }; diff --git a/drivers/platform/wmi/Kconfig b/drivers/platform/wmi/Kconfig index d62f51ff3b7f..11c221a58e04 100644 --- a/drivers/platform/wmi/Kconfig +++ b/drivers/platform/wmi/Kconfig @@ -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 diff --git a/drivers/platform/wmi/Makefile b/drivers/platform/wmi/Makefile index 2feff94a5594..ee2f27ea20d1 100644 --- a/drivers/platform/wmi/Makefile +++ b/drivers/platform/wmi/Makefile @@ -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 diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/wmi/bmof.c index 6623cf60e4b4..6623cf60e4b4 100644 --- a/drivers/platform/x86/wmi-bmof.c +++ b/drivers/platform/wmi/bmof.c diff --git a/drivers/platform/wmi/core.c b/drivers/platform/wmi/core.c index 529825dcfbfe..a99da9b7039d 100644 --- a/drivers/platform/wmi/core.c +++ b/drivers/platform/wmi/core.c @@ -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 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 4851bf45157d..9ce384e0475e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index cab20a4145e3..229057234d00 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -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/ diff --git a/drivers/platform/x86/acer-wmi-battery.c b/drivers/platform/x86/acer-wmi-battery.c new file mode 100644 index 000000000000..10a34d4ab058 --- /dev/null +++ b/drivers/platform/x86/acer-wmi-battery.c @@ -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"); diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 61ae622c93d9..3a018fdf5138 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -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 */ @@ -637,6 +656,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = { }, { .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", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), @@ -655,6 +683,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = { }, { .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", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), @@ -664,6 +701,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = { }, { .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", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), @@ -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; diff --git a/drivers/platform/x86/amd/hfi/hfi.c b/drivers/platform/x86/amd/hfi/hfi.c index e0ebcb0c4acd..a923345dd1cf 100644 --- a/drivers/platform/x86/amd/hfi/hfi.c +++ b/drivers/platform/x86/amd/hfi/hfi.c @@ -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); diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index ddd7a04ee753..167c76c13ad0 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -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]); diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 5e123a4ecea9..45d20beda946 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -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; + + 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; + } - /* msg_id is a reserved message ID */ - if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD) + /* 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; - - /* - * 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)) - return -EPERM; - break; - case FMODE_READ: - /* - * Device is opened in O_RDONLY mode - * Execute only get/monitor commands - */ - if (!is_get_msg(&msg)) + if (plat_desc->msg_desc) { + /* Rejects out-of-range and reserved msg_ids */ + desc = get_msg_desc(msg.msg_id); + if (!desc) + return -ENOMSG; + + 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 | 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); } diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 8dbff16a87b1..3302c1517369 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -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; diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c index 09a30f333759..fed6c51879fc 100644 --- a/drivers/platform/x86/amd/pmc/pmc-quirks.c +++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c @@ -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", diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index 6792aa2c6187..ef07b24bfeba 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -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[] = { diff --git a/drivers/platform/x86/amd/pmc/pmc.h b/drivers/platform/x86/amd/pmc/pmc.h index 6973a639d7e3..4633abf2cca1 100644 --- a/drivers/platform/x86/amd/pmc/pmc.h +++ b/drivers/platform/x86/amd/pmc/pmc.h @@ -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); diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c index 3d94b03cf794..4392bf0bc0bf 100644 --- a/drivers/platform/x86/amd/pmf/acpi.c +++ b/drivers/platform/x86/amd/pmf/acpi.c @@ -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 @@ out: 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, diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index b4eae65e675b..ba9324448586 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -138,14 +138,6 @@ static void amd_pmf_dbgfs_register(struct amd_pmf_dev *dev) ¤t_power_limits_fops); } -int amd_pmf_get_power_source(void) -{ - if (power_supply_is_system_supplied() > 0) - return POWER_SOURCE_AC; - else - return POWER_SOURCE_DC; -} - static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset) { return ioread32(dev->regbase + reg_offset); @@ -176,20 +168,6 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); } -/** - * fixp_q88_fromint: Convert integer to Q8.8 - * @val: input value - * - * Converts an integer into binary fixed point format where 8 bits - * are used for integer and 8 bits are used for the decimal. - * - * Return: unsigned integer converted to Q8.8 format - */ -u32 fixp_q88_fromint(u32 val) -{ - return val << 8; -} - int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data) { int rc; @@ -454,6 +432,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { {"AMDI0107", 0}, {"AMDI0108", 0}, {"AMDI0109", 0}, + {"AMDI0112", 0}, { } }; MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 07ec00684233..9f64ab833c08 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -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); diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index 592ba4de4c7f..a12b4ee965a2 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -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) diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c index 0b70a5153f46..b0031afae33b 100644 --- a/drivers/platform/x86/amd/pmf/sps.c +++ b/drivers/platform/x86/amd/pmf/sps.c @@ -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) { diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c index 4d1a61e4f1b9..2befc1b14842 100644 --- a/drivers/platform/x86/amd/pmf/util.c +++ b/drivers/platform/x86/amd/pmf/util.c @@ -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 = { diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index fb382de5b57b..a9926c94eea4 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -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; - } - } - - 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); - } + 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; - 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 @@ err_free_tunables: 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); diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 0e301d663d0c..331c3bddf013 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -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 */ @@ -715,6 +789,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"), }, .driver_data = &(struct power_data) { @@ -751,6 +889,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"), }, .driver_data = &(struct power_data) { @@ -1058,6 +1229,46 @@ static const struct dmi_system_id power_limits[] = { }, { .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"), }, .driver_data = &(struct power_data) { @@ -1104,6 +1315,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. DMI_MATCH(DMI_BOARD_NAME, "GA402R"), }, @@ -1489,6 +1731,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"), }, .driver_data = &(struct power_data) { @@ -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"), @@ -1990,6 +2290,38 @@ static const struct dmi_system_id power_limits[] = { }, { .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"), }, .driver_data = &(struct power_data) { @@ -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, + }, + }, + }, {} }; diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 79a575d0f5b4..e4f20ab5250a 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -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. diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index aeb461b1644d..3b0f3346dc60 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -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, @@ -501,6 +505,16 @@ static const struct dmi_system_id asus_quirks[] = { }, { .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", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), @@ -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 diff --git a/drivers/platform/x86/asus-tf103c-dock.c b/drivers/platform/x86/asus-tf103c-dock.c index 92466477de9a..3be5b96de548 100644 --- a/drivers/platform/x86/asus-tf103c-dock.c +++ b/drivers/platform/x86/asus-tf103c-dock.c @@ -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)) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index a65090429ca7..b33ebdb21aa2 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -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; - } - - if (!bd->props.power) { - err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL); - if (err < 0) - return err; - } + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, 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) diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h index 5cd4392b964e..b45f81b38a47 100644 --- a/drivers/platform/x86/asus-wmi.h +++ b/drivers/platform/x86/asus-wmi.h @@ -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; diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c index 41a24e091248..81dac81262e7 100644 --- a/drivers/platform/x86/ayaneo-ec.c +++ b/drivers/platform/x86/ayaneo-ec.c @@ -79,6 +79,13 @@ 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"), DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"), }, .driver_data = (void *)&quirk_fan, diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86/bitland-mifs-wmi.c index 3a373184519d..dd1648b7de77 100644 --- a/drivers/platform/x86/bitland-mifs-wmi.c +++ b/drivers/platform/x86/bitland-mifs-wmi.c @@ -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; diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c index d1b4df91401b..2b0ffcb56a95 100644 --- a/drivers/platform/x86/dell/alienware-wmi-wmax.c +++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c @@ -122,6 +122,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 = { DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), diff --git a/drivers/platform/x86/dell/dell-dw5826e-reset.c b/drivers/platform/x86/dell/dell-dw5826e-reset.c index 64e6e9b1d7a0..c72c243b460e 100644 --- a/drivers/platform/x86/dell/dell-dw5826e-reset.c +++ b/drivers/platform/x86/dell/dell-dw5826e-reset.c @@ -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; + + 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 0; + return -ENODEV; } static const struct acpi_device_id palc_acpi_ids[] = { diff --git a/drivers/platform/x86/dell/dell-wmi-aio.c b/drivers/platform/x86/dell/dell-wmi-aio.c index 54096495719b..42c0032991d2 100644 --- a/drivers/platform/x86/dell/dell-wmi-aio.c +++ b/drivers/platform/x86/dell/dell-wmi-aio.c @@ -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_event { - u16 length; - /* 0x000: A hot key pressed or an event occurred - * 0x00F: A sequence of hot keys are pressed */ - u16 type; - u16 event[]; +struct dell_wmi_aio_data { + struct input_dev *input_device; + /* Protects the input sequence */ + struct mutex input_lock; }; -static const char *dell_wmi_aio_guids[] = { - EVENT_GUID1, - EVENT_GUID2, - NULL -}; +/* 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 -MODULE_ALIAS("wmi:"EVENT_GUID1); -MODULE_ALIAS("wmi:"EVENT_GUID2); +struct dell_wmi_event { + __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) +static bool dell_wmi_aio_event_check(const struct wmi_buffer *buffer) { - struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; + struct dell_wmi_event *event; + u16 length, type; - if (event == NULL || length < 6) + if (buffer->length < struct_size(event, event, 1)) return false; - if ((event->type == 0 || event->type == 0xf) && - event->length >= 2) - return true; + event = buffer->data; + length = le16_to_cpu(event->length); + type = le16_to_cpu(event->type); - return false; -} + if (type != DELL_AIO_EVENT_TYPE_SINGLE && type != DELL_AIO_EVENT_TYPE_SEQUENCE) + return false; -static void dell_wmi_aio_notify(union acpi_object *obj, void *context) -{ - struct dell_wmi_event *event; + if (length < 2) + return false; - if (obj) { - unsigned int scancode = 0; - - 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; - } - } + 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; - - dell_wmi_aio_input_dev = input_allocate_device(); - - if (!dell_wmi_aio_input_dev) - 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; - - 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; + 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; + + 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]; } - return 0; -err_free_dev: - input_free_device(dell_wmi_aio_input_dev); - return err; -} + guard(mutex)(&drvdata->input_lock); -static const char *dell_wmi_aio_find(void) -{ - int i; - - 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; + sparse_keymap_report_event(drvdata->input_device, scancode, 1, true); } -static int __init dell_wmi_aio_init(void) +static int dell_wmi_aio_probe(struct wmi_device *wdev, const void *context) { - int err; - const char *guid; + struct dell_wmi_aio_data *data; + int ret; - guid = dell_wmi_aio_find(); - if (!guid) { - pr_warn("No known WMI GUID found\n"); - return -ENXIO; - } + data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; - err = dell_wmi_aio_input_setup(); - if (err) - return err; + dev_set_drvdata(&wdev->dev, data); + ret = devm_mutex_init(&wdev->dev, &data->input_lock); + if (ret < 0) + return ret; - 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; - } + data->input_device = devm_input_allocate_device(&wdev->dev); + if (!data->input_device) + return -ENOMEM; - return 0; -} + data->input_device->name = "Dell AIO WMI hotkeys"; + data->input_device->phys = "wmi/input0"; + data->input_device->id.bustype = BUS_HOST; -static void __exit dell_wmi_aio_exit(void) -{ - const char *guid; + ret = sparse_keymap_setup(data->input_device, dell_wmi_aio_keymap, NULL); + if (ret < 0) + return ret; - guid = dell_wmi_aio_find(); - wmi_remove_notify_handler(guid); - input_unregister_device(dell_wmi_aio_input_dev); + return input_register_device(data->input_device); } -module_init(dell_wmi_aio_init); -module_exit(dell_wmi_aio_exit); +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); + +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); diff --git a/drivers/platform/x86/gpd-pocket-fan.c b/drivers/platform/x86/gpd-pocket-fan.c index c9236738f896..ced93795c5d4 100644 --- a/drivers/platform/x86/gpd-pocket-fan.c +++ b/drivers/platform/x86/gpd-pocket-fan.c @@ -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); diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index d5a2d0963bbf..8b24e52653d0 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -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; - - for (i = 0; i < conv_dst_size; i++) { - if (*src == '\\' || - *src == '\r' || - *src == '\n' || - *src == '\t') { - dst[i++] = '\\'; - if (i == conv_dst_size) - break; - } + 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, '"', '\''); - 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); + *buffer += sizeof(u16) + src_size; + *buffer_size -= sizeof(u16) + src_size; - 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) { diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h index ac57d6eab4c3..94619c034187 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h @@ -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 diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c index 72a4c2107d7e..cc18cd7902de 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c @@ -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: diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c index a27907066448..07654e805791 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c @@ -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); diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c index 5bf8d40bdf81..ccb1ea03ef64 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c @@ -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; } diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c index a9e178637416..e7921d9295e0 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c @@ -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; diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c index 4d94e48c1a4c..343b2356943e 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c @@ -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; diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c index f3dfba270f7e..c433b67870bd 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c @@ -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; } diff --git a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c index b57e42f29282..d689ee844be2 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c +++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c @@ -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; diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 615b4cf6fc45..52077833ac89 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -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. */ @@ -324,10 +330,22 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .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, }, @@ -336,13 +354,29 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .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); diff --git a/drivers/platform/x86/hp/hp_accel.c b/drivers/platform/x86/hp/hp_accel.c index 39b73dc473f1..40e1b9fb1b6d 100644 --- a/drivers/platform/x86/hp/hp_accel.c +++ b/drivers/platform/x86/hp/hp_accel.c @@ -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 }; diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index d6aaf14d66a5..96ee9a97c9b8 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -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 } }, diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c index 139956168cf9..82fda8d9178b 100644 --- a/drivers/platform/x86/ibm_rtl.c +++ b/drivers/platform/x86/ibm_rtl.c @@ -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; diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 2900407d6095..ea6dc7b50d90 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -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. diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 138b13756158..d2c63a136e51 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -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 diff --git a/drivers/platform/x86/intel/bxtwc_tmu.c b/drivers/platform/x86/intel/bxtwc_tmu.c index b3666704d85b..73d7f71b2fd0 100644 --- a/drivers/platform/x86/intel/bxtwc_tmu.c +++ b/drivers/platform/x86/intel/bxtwc_tmu.c @@ -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, diff --git a/drivers/platform/x86/intel/bytcrc_pwrsrc.c b/drivers/platform/x86/intel/bytcrc_pwrsrc.c index 68ac040082df..1f39850b80fd 100644 --- a/drivers/platform/x86/intel/bytcrc_pwrsrc.c +++ b/drivers/platform/x86/intel/bytcrc_pwrsrc.c @@ -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); diff --git a/drivers/platform/x86/intel/crystal_cove_charger.c b/drivers/platform/x86/intel/crystal_cove_charger.c index e4299cfa2205..425c7eb32442 100644 --- a/drivers/platform/x86/intel/crystal_cove_charger.c +++ b/drivers/platform/x86/intel/crystal_cove_charger.c @@ -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; } diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c index acd42e639fa4..6024bf29c4df 100644 --- a/drivers/platform/x86/intel/hid.c +++ b/drivers/platform/x86/intel/hid.c @@ -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"), + }, + }, { } }; diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c index 562e88025643..96b36cfcd9e4 100644 --- a/drivers/platform/x86/intel/int0002_vgpio.c +++ b/drivers/platform/x86/intel/int0002_vgpio.c @@ -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); diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c index 9f77c0716e59..71be313bdf0e 100644 --- a/drivers/platform/x86/intel/pmc/core.c +++ b/drivers/platform/x86/intel/pmc/core.c @@ -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), diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h index b4c7399f8369..e55164e5807f 100644 --- a/drivers/platform/x86/intel/pmc/core.h +++ b/drivers/platform/x86/intel/pmc/core.h @@ -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 diff --git a/drivers/platform/x86/intel/pmc/nvl.c b/drivers/platform/x86/intel/pmc/nvl.c index 8dabf2511dd9..1ed02332dbfc 100644 --- a/drivers/platform/x86/intel/pmc/nvl.c +++ b/drivers/platform/x86/intel/pmc/nvl.c @@ -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. diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c index d0ab8e33c62a..2b11c4012f6a 100644 --- a/drivers/platform/x86/intel/pmt/class.c +++ b/drivers/platform/x86/intel/pmt/class.c @@ -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; diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h index a0ece4fc3837..aeb04afb67cf 100644 --- a/drivers/platform/x86/intel/pmt/class.h +++ b/drivers/platform/x86/intel/pmt/class.h @@ -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; diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c index f936daf99e4d..d7018b495217 100644 --- a/drivers/platform/x86/intel/pmt/crashlog.c +++ b/drivers/platform/x86/intel/pmt/crashlog.c @@ -11,10 +11,12 @@ #include <linux/auxiliary_bus.h> #include <linux/cleanup.h> #include <linux/intel_vsec.h> +#include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/pci.h> +#include <linux/printk.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/overflow.h> @@ -124,12 +126,36 @@ struct pmt_crashlog_priv { * I/O */ +static int pmt_crashlog_read_reg(struct intel_pmt_entry *entry, u32 *reg, u32 offset) +{ + int ret; + + *reg = 0; + + if (entry->cb && entry->cb->read_reg) { + ret = entry->cb->read_reg(entry->dev, entry->header.guid, reg, offset); + if (ret) { + dev_err_ratelimited(entry->dev, "failed to read reg: %d\n", ret); + return ret; + } + } else { + *reg = readl(entry->disc_table + offset); + } + + return 0; +} + /* Read, modify, write the control register, setting or clearing @bit based on @set */ -static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) +static int pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) { const struct crashlog_control *control = &crashlog->info->control; struct intel_pmt_entry *entry = &crashlog->entry; - u32 reg = readl(entry->disc_table + control->offset); + u32 reg; + int ret; + + ret = pmt_crashlog_read_reg(entry, ®, control->offset); + if (ret) + return ret; reg &= ~control->trigger_mask; @@ -138,28 +164,46 @@ static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set) else reg &= ~bit; - writel(reg, entry->disc_table + control->offset); + if (entry->cb && entry->cb->write_reg) { + ret = entry->cb->write_reg(entry->dev, entry->header.guid, reg, control->offset); + if (ret) { + dev_err_ratelimited(entry->dev, "failed to write reg: %d\n", ret); + return ret; + } + } else { + writel(reg, entry->disc_table + control->offset); + } + + return 0; } /* Read the status register and see if the specified @bit is set */ -static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit) +static int pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit, bool *state) { const struct crashlog_status *status = &crashlog->info->status; - u32 reg = readl(crashlog->entry.disc_table + status->offset); + struct intel_pmt_entry *entry = &crashlog->entry; + u32 reg; + int ret; + + ret = pmt_crashlog_read_reg(entry, ®, status->offset); + if (ret) + return ret; + + *state = !!(reg & bit); - return !!(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; + + ret = pmt_crashlog_disabled(crashlog, &disabled); + if (ret) + return ret; - return sprintf(buf, "%d\n", enabled); + 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); diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c index c482368bfaae..f4203d240f54 100644 --- a/drivers/platform/x86/intel/pmt/discovery.c +++ b/drivers/platform/x86/intel/pmt/discovery.c @@ -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); diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c index 953f35b6daec..5423a4dfce08 100644 --- a/drivers/platform/x86/intel/pmt/telemetry.c +++ b/drivers/platform/x86/intel/pmt/telemetry.c @@ -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; diff --git a/drivers/platform/x86/intel/punit_ipc.c b/drivers/platform/x86/intel/punit_ipc.c index 6d770b950dfb..a3d2b0b64135 100644 --- a/drivers/platform/x86/intel/punit_ipc.c +++ b/drivers/platform/x86/intel/punit_ipc.c @@ -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; } diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c index 1c48bf6d5457..4c04f0bc9b47 100644 --- a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c @@ -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) diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c index f312864b8d07..62299524a07b 100644 --- a/drivers/platform/x86/intel/telemetry/core.c +++ b/drivers/platform/x86/intel/telemetry/core.c @@ -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; } diff --git a/drivers/platform/x86/intel/tpmi_common.c b/drivers/platform/x86/intel/tpmi_common.c new file mode 100644 index 000000000000..b6b811016dfd --- /dev/null +++ b/drivers/platform/x86/intel/tpmi_common.c @@ -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"); diff --git a/drivers/platform/x86/intel/tpmi_common.h b/drivers/platform/x86/intel/tpmi_common.h new file mode 100644 index 000000000000..dd4bbe517e1c --- /dev/null +++ b/drivers/platform/x86/intel/tpmi_common.h @@ -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_ */ diff --git a/drivers/platform/x86/intel/tpmi_power_domains.c b/drivers/platform/x86/intel/tpmi_power_domains.c index 833052dc34d2..d89b9894c42a 100644 --- a/drivers/platform/x86/intel/tpmi_power_domains.c +++ b/drivers/platform/x86/intel/tpmi_power_domains.c @@ -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) diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c index 667f2c8b9594..3602b370177e 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -211,7 +211,7 @@ static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, struct uncore_data *data = &uncore_instances[i]; if (!data || !data->valid || !data->stored_uncore_data) - return 0; + continue; wrmsrq_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, data->stored_uncore_data); @@ -257,6 +257,7 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = { X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL), X86_MATCH_VFM(INTEL_LUNARLAKE_M, NULL), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL), + X86_MATCH_VFM(INTEL_PANTHERLAKE_R, NULL), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL), X86_MATCH_VFM(INTEL_NOVALAKE, NULL), X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL), diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 5ab2215fdd7f..380d8231549f 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -51,6 +51,13 @@ struct vsec_priv { unsigned long found_caps; }; +static void intel_vsec_reset_state(struct vsec_priv *priv) +{ + memset(priv->suppliers, 0, sizeof(priv->suppliers)); + memset(priv->state, 0, sizeof(priv->state)); + priv->found_caps = 0; +} + static const char *intel_vsec_name(enum intel_vsec_id id) { switch (id) { @@ -641,7 +648,7 @@ static bool intel_vsec_get_features(struct pci_dev *pdev, found = true; if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) && - intel_vsec_walk_header(&pdev->dev, info)) + !intel_vsec_walk_header(&pdev->dev, info)) found = true; return found; @@ -669,8 +676,8 @@ static int intel_vsec_pci_init(struct pci_dev *pdev) { struct vsec_priv *priv = pci_get_drvdata(pdev); const struct intel_vsec_platform_info *info = priv->info; - int run_once = 0; bool found_any = false; + bool run_once = false; int num_caps; num_caps = hweight_long(info->caps); @@ -682,7 +689,7 @@ static int intel_vsec_pci_init(struct pci_dev *pdev) if (!run_once) { intel_vsec_skip_missing_dependencies(pdev); - run_once = 1; + run_once = true; } } @@ -717,15 +724,14 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id return intel_vsec_pci_init(pdev); } -int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, - struct intel_vsec_device *vsec_dev) +int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, struct device *dev) { struct vsec_priv *priv; - if (!dev_is_pci(vsec_dev->dev)) + if (!dev_is_pci(dev)) return -ENODEV; - priv = pci_get_drvdata(to_pci_dev(vsec_dev->dev)); + priv = pci_get_drvdata(to_pci_dev(dev)); if (!priv) return -EINVAL; @@ -857,6 +863,7 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev) { struct intel_vsec_device *intel_vsec_dev; pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT; + struct vsec_priv *priv = pci_get_drvdata(pdev); unsigned long index; dev_info(&pdev->dev, "Resetting PCI slot\n"); @@ -877,6 +884,13 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev) devm_release_action(&pdev->dev, intel_vsec_remove_aux, &intel_vsec_dev->auxdev); } + + /* + * intel_vsec_pci_init() reuses priv. Reset the registration state + * after tearing down old aux devices or the rewalk will skip them as + * already registered. + */ + intel_vsec_reset_state(priv); pci_restore_state(pdev); intel_vsec_pci_init(pdev); diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c index 0153dd57838e..053fe0f58641 100644 --- a/drivers/platform/x86/intel/vsec_tpmi.c +++ b/drivers/platform/x86/intel/vsec_tpmi.c @@ -1,21 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Driver to enumerate TPMI features and create devices + * Enumerate TPMI features from a PCI VSEC structure * * Copyright (c) 2023, 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 interface uses a PCI VSEC structure to expose the location of * MMIO region. * @@ -29,823 +18,38 @@ * This TPMI driver will bind to the TPMI auxiliary device object created * by the Intel VSEC driver. * - * The TPMI specification defines a PFS (PM Feature Structure) table. - * This table is present in the TPMI MMIO region. The starting address - * of PFS is derived from the tBIR (Bar Indicator Register) and "Address" - * field from the VSEC header. - * - * Each TPMI PM feature has one entry in the PFS with a unique TPMI - * ID and its access details. The TPMI driver creates device nodes - * for the supported PM features. - * - * The names of the devices created by the TPMI driver 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. + * The starting address of the PFS table in the TPMI MMIO region is derived + * from the tBIR (Bar Indicator Register) and "Address" field from the VSEC + * header by the Intel VSEC driver, which passes the result on as the + * resources of the auxiliary device. Everything that happens from there on + * is enumeration independent and lives in tpmi_common.c. */ -#include <linux/align.h> #include <linux/auxiliary_bus.h> -#include <linux/bitfield.h> -#include <linux/debugfs.h> -#include <linux/cleanup.h> -#include <linux/delay.h> -#include <linux/intel_tpmi.h> #include <linux/intel_vsec.h> -#include <linux/io.h> -#include <linux/iopoll.h> #include <linux/module.h> -#include <linux/notifier.h> -#include <linux/pci.h> -#include <linux/security.h> -#include <linux/sizes.h> -#include <linux/string_helpers.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. - * @vsec_dev: Pointer to intel_vsec_device structure for this TPMI device - * - * Represents TPMI instance information for one TPMI ID. - */ -struct intel_tpmi_pm_feature { - struct intel_tpmi_pfs_entry pfs_header; - u64 vsec_offset; - struct intel_vsec_device *vsec_dev; -}; - -/** - * struct intel_tpmi_info - TPMI information for all IDs in an instance - * @tpmi_features: Pointer to a list of TPMI feature instances - * @vsec_dev: Pointer to intel_vsec_device structure for this TPMI device - * @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 - * - * Stores the information for all TPMI devices enumerated from a single PCI device. - */ -struct intel_tpmi_info { - struct intel_tpmi_pm_feature *tpmi_features; - struct intel_vsec_device *vsec_dev; - int feature_count; - u64 pfs_start; - struct oobmsm_plat_info plat_info; - void __iomem *tpmi_control_mem; - struct dentry *dbgfs_dir; -}; - -/** - * 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; - -/* - * The size from hardware is in u32 units. This size is from a trusted hardware, - * but better to verify for pre silicon platforms. Set size to 0, when invalid. - */ -#define TPMI_GET_SINGLE_ENTRY_SIZE(pfs) \ -({ \ - pfs->pfs_header.entry_size > SZ_1K ? 0 : pfs->pfs_header.entry_size << 2; \ -}) - -/* 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; - - mutex_lock(&tpmi_dev_lock); - - /* Wait for owner bit set to 0 (none) */ - ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE); - if (ret) - goto err_unlock; - - /* set command id to 0x10 for TPMI_GET_STATE */ - data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD); - - /* 32 bits for DATA offset and +8 for feature_id field */ - data |= 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) - goto err_unlock; - - /* Set Run Busy and packet length of 2 dwords */ - control = TPMI_CONTROL_STATUS_RB; - control |= 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); - -err_unlock: - mutex_unlock(&tpmi_dev_lock); - - return ret; -} - -int tpmi_get_feature_status(struct auxiliary_device *auxdev, - int feature_id, bool *read_blocked, bool *write_blocked) -{ - struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(auxdev->dev.parent); - struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(&intel_vsec_dev->auxdev); - 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_vsec_device *intel_vsec_dev = dev_to_ivdev(auxdev->dev.parent); - struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(&intel_vsec_dev->auxdev); - - 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, i; - - - 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 (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; - int count, ret = 0; - void __iomem *mem; - u32 size; - u64 off; - u8 *buffer; - - size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); - if (!size) - return -EIO; - - buffer = kmalloc(size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - off = pfs->vsec_offset; - - mutex_lock(&tpmi_dev_lock); - - for (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) { - ret = -ENOMEM; - break; - } - - memcpy_fromio(buffer, mem, size); - seq_hex_dump(s, " ", DUMP_PREFIX_OFFSET, row_size, sizeof(u32), buffer, size, - false); +#include "tpmi_common.h" - iounmap(mem); - - off += size; - } - - mutex_unlock(&tpmi_dev_lock); - - kfree(buffer); - - return ret; -} -DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump); - -static ssize_t mem_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; -} - -static int mem_write_open(struct inode *inode, struct file *file) -{ - return single_open(file, mem_write_show, inode->i_private); -} - -static const struct file_operations mem_write_ops = { - .open = mem_write_open, - .read = seq_read, - .write = mem_write, - .llseek = seq_lseek, - .release = single_release, -}; - -#define tpmi_to_dev(info) ((info)->vsec_dev->dev) - -static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) -{ - char name[64]; - int i; - - snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_to_dev(tpmi_info))); - 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 (i = 0; i < tpmi_info->feature_count; ++i) { - struct intel_tpmi_pm_feature *pfs; - struct dentry *dir; - - pfs = &tpmi_info->tpmi_features[i]; - snprintf(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_ops); - } -} - -static void tpmi_set_control_base(struct auxiliary_device *auxdev, - 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(&auxdev->dev, pfs->vsec_offset, size); - if (!mem) - return; - - /* mem is pointing to TPMI CONTROL base */ - tpmi_info->tpmi_control_mem = mem; -} - -static const char *intel_tpmi_name(enum intel_tpmi_id id) -{ - switch (id) { - case TPMI_ID_RAPL: - return "rapl"; - case TPMI_ID_PEM: - return "pem"; - case TPMI_ID_UNCORE: - return "uncore"; - case TPMI_ID_SST: - return "sst"; - case TPMI_ID_PLR: - return "plr"; - default: - return NULL; - } -} - -/* String Length for tpmi-"feature_name(upto 8 bytes)" */ -#define TPMI_FEATURE_NAME_LEN 14 - -static int tpmi_create_device(struct intel_tpmi_info *tpmi_info, - struct intel_tpmi_pm_feature *pfs, - u64 pfs_start) -{ - struct intel_vsec_device *vsec_dev = tpmi_info->vsec_dev; - char feature_id_name[TPMI_FEATURE_NAME_LEN]; - struct intel_vsec_device *feature_vsec_dev; - struct tpmi_feature_state feature_state; - struct resource *res, *tmp; - const char *name; - int i, 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; - - snprintf(feature_id_name, sizeof(feature_id_name), "tpmi-%s", name); - - for (i = 0, tmp = res; i < pfs->pfs_header.num_entries; i++, tmp++) { - u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32); - - tmp->start = pfs->vsec_offset + entry_size_bytes * i; - tmp->end = tmp->start + entry_size_bytes - 1; - tmp->flags = IORESOURCE_MEM; - } - - feature_vsec_dev->dev = vsec_dev->dev; - 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. - */ - return intel_vsec_add_aux(&vsec_dev->auxdev.dev, - feature_vsec_dev, feature_id_name); -} - -static int tpmi_create_devices(struct intel_tpmi_info *tpmi_info) -{ - struct intel_vsec_device *vsec_dev = tpmi_info->vsec_dev; - int ret, i; - - for (i = 0; i < vsec_dev->num_resources; i++) { - ret = tpmi_create_device(tpmi_info, &tpmi_info->tpmi_features[i], - tpmi_info->pfs_start); - /* - * Fail, if the supported features fails to create device, - * otherwise, continue. Even if one device failed to create, - * fail the loading of driver. Since intel_vsec_add_aux() - * is resource managed, no clean up is required for the - * successfully created devices. - */ - if (ret && ret != -EOPNOTSUPP) - 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 - -static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev) +static int tpmi_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) { struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev); - struct pci_dev *pci_dev = to_pci_dev(vsec_dev->dev); struct intel_tpmi_info *tpmi_info; - u64 pfs_start = 0; - int ret, i; tpmi_info = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_info), GFP_KERNEL); if (!tpmi_info) return -ENOMEM; - tpmi_info->vsec_dev = vsec_dev; - tpmi_info->feature_count = vsec_dev->num_resources; - tpmi_info->plat_info.bus_number = pci_dev->bus->number; - - tpmi_info->tpmi_features = devm_kcalloc(&auxdev->dev, vsec_dev->num_resources, - sizeof(*tpmi_info->tpmi_features), - GFP_KERNEL); - if (!tpmi_info->tpmi_features) - return -ENOMEM; - - for (i = 0; i < vsec_dev->num_resources; i++) { - struct intel_tpmi_pm_feature *pfs; - struct resource *res; - u64 res_start; - int size, ret; - - pfs = &tpmi_info->tpmi_features[i]; - pfs->vsec_dev = vsec_dev; - - res = &vsec_dev->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; - - ret = intel_vsec_set_mapping(&tpmi_info->plat_info, vsec_dev); - if (ret) - return ret; - } - - if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID) - tpmi_set_control_base(auxdev, tpmi_info, pfs); - } - - tpmi_info->pfs_start = pfs_start; - - auxiliary_set_drvdata(auxdev, 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, auxdev); - - return 0; -} - -static int tpmi_probe(struct auxiliary_device *auxdev, - const struct auxiliary_device_id *id) -{ - return intel_vsec_tpmi_init(auxdev); + return intel_tpmi_init(tpmi_info, &auxdev->dev, vsec_dev->dev, + vsec_dev->resource, vsec_dev->num_resources); } static void tpmi_remove(struct auxiliary_device *auxdev) { struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(auxdev); - blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, auxdev); - - debugfs_remove_recursive(tpmi_info->dbgfs_dir); + intel_tpmi_deinit(tpmi_info); } static const struct auxiliary_device_id tpmi_id_table[] = { @@ -862,6 +66,6 @@ static struct auxiliary_driver tpmi_aux_driver = { module_auxiliary_driver(tpmi_aux_driver); -MODULE_IMPORT_NS("INTEL_VSEC"); +MODULE_IMPORT_NS("INTEL_TPMI"); MODULE_DESCRIPTION("Intel TPMI enumeration module"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c index 62c9c7f1842b..7e07cfab227c 100644 --- a/drivers/platform/x86/intel/wmi/sbl-fw-update.c +++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c @@ -61,7 +61,7 @@ static ssize_t firmware_update_request_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%d\n", val); + return sysfs_emit(buf, "%u\n", val); } static ssize_t firmware_update_request_store(struct device *dev, @@ -93,18 +93,6 @@ static struct attribute *firmware_update_attrs[] = { }; ATTRIBUTE_GROUPS(firmware_update); -static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev, - const void *context) -{ - dev_info(&wdev->dev, "Slim Bootloader signaling driver attached\n"); - return 0; -} - -static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev) -{ - dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n"); -} - static const struct wmi_device_id intel_wmi_sbl_id_table[] = { { .guid_string = INTEL_WMI_SBL_GUID }, {} @@ -116,8 +104,6 @@ static struct wmi_driver intel_wmi_sbl_fw_update_driver = { .name = "intel-wmi-sbl-fw-update", .dev_groups = firmware_update_groups, }, - .probe = intel_wmi_sbl_fw_update_probe, - .remove = intel_wmi_sbl_fw_update_remove, .id_table = intel_wmi_sbl_id_table, .no_singleton = true, }; diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c index 8213524504ee..fcb5ed7035f5 100644 --- a/drivers/platform/x86/lenovo/ideapad-laptop.c +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c @@ -1358,13 +1358,29 @@ static const struct key_entry ideapad_keymap[] = { { KE_KEY, 0x44 | IDEAPAD_WMI_KEY, { KEY_PROG1 } }, /* Eye */ { KE_KEY, 0x45 | IDEAPAD_WMI_KEY, { KEY_PROG3 } }, + /* Lenovo Smart Connect */ + { KE_KEY, 0x48 | IDEAPAD_WMI_KEY, { KEY_LINK_PHONE } }, /* Performance toggle also Fn+Q, handled inside ideapad_wmi_notify() */ { KE_KEY, 0x3d | IDEAPAD_WMI_KEY, { KEY_PROG4 } }, + /* Mode, opens a profile chooser; the Fn+Q button cycles the profile */ + { KE_KEY, 0x46 | IDEAPAD_WMI_KEY, { KEY_PROG4 } }, /* shift + prtsc */ { KE_KEY, 0x2d | IDEAPAD_WMI_KEY, { KEY_SELECTIVE_SCREENSHOT } }, { KE_KEY, 0x29 | IDEAPAD_WMI_KEY, { KEY_TOUCHPAD_TOGGLE } }, { KE_KEY, 0x2a | IDEAPAD_WMI_KEY, { KEY_ROOT_MENU } }, + /* + * WMI switches + * + * Some devices come with physical switches. Whenever a switch is + * toggled, an WMI event is emitted to inform the software about the + * switch's latest state. + */ + + /* Camera switch */ + { KE_SW, 0x0c | IDEAPAD_WMI_KEY, .sw = { SW_CAMERA_LENS_COVER, 0 } }, + { KE_SW, 0x0d | IDEAPAD_WMI_KEY, .sw = { SW_CAMERA_LENS_COVER, 1 } }, + { KE_END }, }; diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index ebfc6182986e..2c45c98ec0ec 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -249,7 +249,7 @@ static int tlmi_errstr_to_err(const char *errstr) { int i; - for (i = 0; i < sizeof(tlmi_errs)/sizeof(struct tlmi_err_codes); i++) { + for (i = 0; i < ARRAY_SIZE(tlmi_errs); i++) { if (!strcmp(tlmi_errs[i].err_str, errstr)) return tlmi_errs[i].err_code; } @@ -487,16 +487,16 @@ static ssize_t new_password_store(struct kobject *kobj, /* Special handling required for HDD and NVMe passwords */ if (setting == tlmi_priv.pwd_hdd) { if (setting->level == TLMI_LEVEL_USER) - sprintf(pwd_type, "uhdp%d", setting->index); + scnprintf(pwd_type, sizeof(pwd_type), "uhdp%d", setting->index); else - sprintf(pwd_type, "mhdp%d", setting->index); + scnprintf(pwd_type, sizeof(pwd_type), "mhdp%d", setting->index); } else if (setting == tlmi_priv.pwd_nvme) { if (setting->level == TLMI_LEVEL_USER) - sprintf(pwd_type, "udrp%d", setting->index); + scnprintf(pwd_type, sizeof(pwd_type), "udrp%d", setting->index); else - sprintf(pwd_type, "adrp%d", setting->index); + scnprintf(pwd_type, sizeof(pwd_type), "adrp%d", setting->index); } else { - sprintf(pwd_type, "%s", setting->pwd_type); + scnprintf(pwd_type, sizeof(pwd_type), "%s", setting->pwd_type); } ret = tlmi_opcode_setting("WmiOpcodePasswordType", pwd_type); @@ -1281,7 +1281,7 @@ static const struct kobj_type tlmi_pwd_setting_ktype = { static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", tlmi_priv.pending_changes); + return sysfs_emit(buf, "%d\n", tlmi_priv.pending_changes); } static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index 1661f070c571..4f81855ad721 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -222,8 +222,10 @@ enum tpacpi_hkey_event_t { TP_HKEY_EV_LID_OPEN = 0x5002, /* laptop lid opened */ TP_HKEY_EV_TABLET_TABLET = 0x5009, /* tablet swivel up */ TP_HKEY_EV_TABLET_NOTEBOOK = 0x500a, /* tablet swivel down */ - TP_HKEY_EV_TABLET_CHANGED = 0x60c0, /* X1 Yoga (2016): - * enter/leave tablet mode + TP_HKEY_EV_TABLET_CHANGED = 0x60c0, /* posture change event: + * X1 Yoga (2016): enter/leave tablet mode + * X1 Fold 16 Gen 1: keyboard + * attachment state changed */ TP_HKEY_EV_PEN_INSERTED = 0x500b, /* tablet pen inserted */ TP_HKEY_EV_PEN_REMOVED = 0x500c, /* tablet pen removed */ @@ -379,6 +381,7 @@ static struct { u32 kbd_lang:1; u32 trackpoint_doubletap_enable:1; u32 usbc_security_supported:1; + u32 has_keyboard_attached_on_screen:1; bool usbc_security_enabled; struct quirk_entry *quirks; } tp_features; @@ -456,9 +459,7 @@ do { \ #ifdef CONFIG_THINKPAD_ACPI_DEBUG #define vdbg_printk dbg_printk -static const char *str_supported(int is_supported); #else -static inline const char *str_supported(int is_supported) { return ""; } #define vdbg_printk(a_dbg_level, format, arg...) \ do { if (0) no_printk(format, ##arg); } while (0) #endif @@ -890,7 +891,6 @@ static ssize_t dispatch_proc_write(struct file *file, size_t count, loff_t *pos) { struct ibm_struct *ibm = pde_data(file_inode(file)); - char *kernbuf; int ret; if (!ibm || !ibm->write) @@ -898,16 +898,15 @@ static ssize_t dispatch_proc_write(struct file *file, if (count > PAGE_SIZE - 1) return -EINVAL; - kernbuf = memdup_user_nul(userbuf, count); + char *kernbuf __free(kfree) = memdup_user_nul(userbuf, count); if (IS_ERR(kernbuf)) return PTR_ERR(kernbuf); - ret = ibm->write(kernbuf); - if (ret == 0) - ret = count; - kfree(kernbuf); + ret = ibm->write(kernbuf); + if (ret) + return ret; - return ret; + return count; } static const struct proc_ops dispatch_proc_ops = { @@ -1886,6 +1885,7 @@ static u32 hotkey_reserved_mask; /* events better left disabled */ static u32 hotkey_driver_mask; /* events needed by the driver */ static u32 hotkey_user_mask; /* events visible to userspace */ static u32 hotkey_acpi_mask; /* events enabled in firmware */ +static bool hotkey_input_enabled; static bool tpacpi_driver_event(const unsigned int hkey_event); static void hotkey_poll_setup(const bool may_warn); @@ -2107,7 +2107,7 @@ static int hotkey_mask_set(u32 mask) } /* - * We *must* make an inconditional call to hotkey_mask_get to + * We *must* make an unconditional call to hotkey_mask_get to * refresh hotkey_acpi_mask and update hotkey_user_mask * * Take the opportunity to also log when we cannot _enable_ @@ -2169,7 +2169,7 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask) return 0; } - mutex_lock(&hotkey_mutex); + guard(mutex)(&hotkey_mutex); HOTKEY_CONFIG_CRITICAL_START hotkey_driver_mask = mask; @@ -2182,8 +2182,6 @@ static int tpacpi_hotkey_driver_mask_set(const u32 mask) ~hotkey_source_mask); hotkey_poll_setup(true); - mutex_unlock(&hotkey_mutex); - return rc; } @@ -2207,15 +2205,12 @@ static void tpacpi_input_send_tabletsw(void) { int state; - if (tp_features.hotkey_tablet && - !hotkey_get_tablet_mode(&state)) { - mutex_lock(&tpacpi_inputdev_send_mutex); + if (tp_features.hotkey_tablet && !hotkey_get_tablet_mode(&state)) { + guard(mutex)(&tpacpi_inputdev_send_mutex); input_report_switch(tpacpi_inputdev, SW_TABLET_MODE, !!state); input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); } } @@ -2240,7 +2235,6 @@ static int get_camera_shutter(void) static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) { - bool known_ev; u32 scancode; if (tpacpi_driver_event(hkey)) @@ -2283,11 +2277,8 @@ static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) scancode = hkey; } - mutex_lock(&tpacpi_inputdev_send_mutex); - known_ev = sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true); - mutex_unlock(&tpacpi_inputdev_send_mutex); - - return known_ev; + guard(mutex)(&tpacpi_inputdev_send_mutex); + return sparse_keymap_report_event(tpacpi_inputdev, scancode, 1, true); } #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -2553,8 +2544,7 @@ static void hotkey_poll_setup(const bool may_warn) lockdep_assert_held(&hotkey_mutex); if (hotkey_poll_freq > 0 && - (poll_driver_mask || - (poll_user_mask && tpacpi_inputdev->users > 0))) { + (poll_driver_mask || (poll_user_mask && hotkey_input_enabled))) { if (!tpacpi_hotkey_task) { tpacpi_hotkey_task = kthread_run(hotkey_kthread, NULL, TPACPI_NVRAM_KTHREAD_NAME); @@ -2575,9 +2565,8 @@ static void hotkey_poll_setup(const bool may_warn) static void hotkey_poll_setup_safe(const bool may_warn) { - mutex_lock(&hotkey_mutex); + guard(mutex)(&hotkey_mutex); hotkey_poll_setup(may_warn); - mutex_unlock(&hotkey_mutex); } static void hotkey_poll_set_freq(unsigned int freq) @@ -2610,7 +2599,10 @@ static int hotkey_inputdev_open(struct input_dev *dev) switch (tpacpi_lifecycle) { case TPACPI_LIFE_INIT: case TPACPI_LIFE_RUNNING: - hotkey_poll_setup_safe(false); + scoped_guard(mutex, &hotkey_mutex) { + hotkey_input_enabled = true; + hotkey_poll_setup(false); + } return 0; case TPACPI_LIFE_EXITING: return -EBUSY; @@ -2623,10 +2615,14 @@ static int hotkey_inputdev_open(struct input_dev *dev) static void hotkey_inputdev_close(struct input_dev *dev) { + guard(mutex)(&hotkey_mutex); + + hotkey_input_enabled = false; + /* disable hotkey polling when possible */ if (tpacpi_lifecycle != TPACPI_LIFE_EXITING && !(hotkey_source_mask & hotkey_driver_mask)) - hotkey_poll_setup_safe(false); + hotkey_poll_setup(false); } /* sysfs hotkey enable ------------------------------------------------- */ @@ -2684,8 +2680,10 @@ static ssize_t hotkey_mask_store(struct device *dev, if (parse_strtoul(buf, 0xffffffffUL, &t)) return -EINVAL; - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + res = ACQUIRE_ERR(mutex_kill, &guard); + if (res) + return res; res = hotkey_user_mask_set(t); @@ -2693,8 +2691,6 @@ static ssize_t hotkey_mask_store(struct device *dev, hotkey_poll_setup(true); #endif - mutex_unlock(&hotkey_mutex); - tpacpi_disclose_usertask("hotkey_mask", "set to 0x%08lx\n", t); return (res) ? res : count; @@ -2780,8 +2776,10 @@ static ssize_t hotkey_source_mask_store(struct device *dev, ((t & ~TPACPI_HKEY_NVRAM_KNOWN_MASK) != 0)) return -EINVAL; - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; HOTKEY_CONFIG_CRITICAL_START hotkey_source_mask = t; @@ -2795,8 +2793,6 @@ static ssize_t hotkey_source_mask_store(struct device *dev, r_ev = hotkey_driver_mask & ~(hotkey_acpi_mask & hotkey_all_mask) & ~hotkey_source_mask & TPACPI_HKEY_NVRAM_KNOWN_MASK; - mutex_unlock(&hotkey_mutex); - if (rc < 0) pr_err("hotkey_source_mask: failed to update the firmware event mask!\n"); @@ -2824,18 +2820,19 @@ static ssize_t hotkey_poll_freq_store(struct device *dev, const char *buf, size_t count) { unsigned long t; + int err; if (parse_strtoul(buf, 25, &t)) return -EINVAL; - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + err = ACQUIRE_ERR(mutex_kill, &guard); + if (err) + return err; hotkey_poll_set_freq(t); hotkey_poll_setup(true); - mutex_unlock(&hotkey_mutex); - tpacpi_disclose_usertask("hotkey_poll_freq", "set to %lu\n", t); return count; @@ -2893,6 +2890,63 @@ static void hotkey_tablet_mode_notify_change(void) "hotkey_tablet_mode"); } +static bool keyboard_attached_on_screen; +static bool keyboard_attached_on_screen_initialized; + +static int x1_fold_keyboard_attached_on_screen_get(bool *attached) +{ + int state; + + if (!tp_features.has_keyboard_attached_on_screen) + return -ENODEV; + + if (!acpi_evalf(NULL, &state, "\\_SB.DEVD.GDST", "d")) + return -EIO; + + *attached = state != 0; + return 0; +} + +static ssize_t keyboard_attached_on_screen_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + bool attached; + int res; + + res = x1_fold_keyboard_attached_on_screen_get(&attached); + if (res) + return res; + + return sysfs_emit(buf, "%d\n", attached); +} + +static DEVICE_ATTR_RO(keyboard_attached_on_screen); + +static void keyboard_attached_on_screen_notify_change(void) +{ + if (tp_features.has_keyboard_attached_on_screen) + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, + "keyboard_attached_on_screen"); +} + +static bool keyboard_attached_on_screen_update(void) +{ + bool attached; + + if (x1_fold_keyboard_attached_on_screen_get(&attached)) + return false; + + if (keyboard_attached_on_screen_initialized && + keyboard_attached_on_screen == attached) + return false; + + keyboard_attached_on_screen = attached; + keyboard_attached_on_screen_initialized = true; + + return true; +} + /* sysfs wakeup reason (pollable) -------------------------------------- */ static ssize_t hotkey_wakeup_reason_show(struct device *dev, struct device_attribute *attr, @@ -3022,6 +3076,7 @@ static struct attribute *hotkey_attributes[] = { &dev_attr_hotkey_adaptive_all_mask.attr, &dev_attr_hotkey_recommended_mask.attr, &dev_attr_hotkey_tablet_mode.attr, + &dev_attr_keyboard_attached_on_screen.attr, &dev_attr_hotkey_radio_sw.attr, &dev_attr_doubletap_enable.attr, #ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL @@ -3037,6 +3092,9 @@ static umode_t hotkey_attr_is_visible(struct kobject *kobj, if (attr == &dev_attr_hotkey_tablet_mode.attr) { if (!tp_features.hotkey_tablet) return 0; + } else if (attr == &dev_attr_keyboard_attached_on_screen.attr) { + if (!tp_features.has_keyboard_attached_on_screen) + return 0; } else if (attr == &dev_attr_hotkey_radio_sw.attr) { if (!tp_features.hotkey_wlsw) return 0; @@ -3080,13 +3138,11 @@ static void tpacpi_send_radiosw_update(void) /* Issue rfkill input event for WLSW switch */ if (!(wlsw < 0)) { - mutex_lock(&tpacpi_inputdev_send_mutex); + guard(mutex)(&tpacpi_inputdev_send_mutex); input_report_switch(tpacpi_inputdev, SW_RFKILL_ALL, (wlsw > 0)); input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); } /* @@ -3098,7 +3154,7 @@ static void tpacpi_send_radiosw_update(void) static void hotkey_exit(void) { - mutex_lock(&hotkey_mutex); + guard(mutex)(&hotkey_mutex); hotkey_poll_stop_sync(); dbg_printk(TPACPI_DBG_EXIT | TPACPI_DBG_HKEY, "restoring original HKEY status and mask\n"); @@ -3108,8 +3164,6 @@ static void hotkey_exit(void) hotkey_mask_set(hotkey_orig_mask)) | hotkey_status_set(false)) != 0) pr_err("failed to restore hot key mask to BIOS defaults\n"); - - mutex_unlock(&hotkey_mutex); } /* @@ -3338,7 +3392,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "hotkeys are %s\n", - str_supported(tp_features.hotkey)); + str_supported_unsupported(tp_features.hotkey)); if (!tp_features.hotkey) return -ENODEV; @@ -3415,7 +3469,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "hotkey masks are %s\n", - str_supported(tp_features.hotkey_mask)); + str_supported_unsupported(tp_features.hotkey_mask)); /* Init hotkey_all_mask if not initialized yet */ if (!tp_features.hotkey_mask && !hotkey_all_mask && @@ -3426,11 +3480,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (tp_features.hotkey_mask) { /* hotkey_source_mask *must* be zero for * the first hotkey_mask_get to return hotkey_orig_mask */ - mutex_lock(&hotkey_mutex); - res = hotkey_mask_get(); - mutex_unlock(&hotkey_mutex); - if (res) - return res; + scoped_guard(mutex, &hotkey_mutex) { + res = hotkey_mask_get(); + if (res) + return res; + } hotkey_orig_mask = hotkey_acpi_mask; } else { @@ -3453,6 +3507,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) } tabletsw_state = hotkey_init_tablet_mode(); + keyboard_attached_on_screen_update(); /* Set up key map */ keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable, @@ -3529,11 +3584,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) hotkey_exit(); return res; } - mutex_lock(&hotkey_mutex); - res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask) - | hotkey_driver_mask) - & ~hotkey_source_mask); - mutex_unlock(&hotkey_mutex); + scoped_guard(mutex, &hotkey_mutex) { + res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask) + | hotkey_driver_mask) + & ~hotkey_source_mask); + } if (res < 0 && res != -ENXIO) { hotkey_exit(); return res; @@ -3720,7 +3775,7 @@ static bool hotkey_notify_dockevent(const u32 hkey, bool *send_acpi_ev) return true; /* - * Deliberately ignore attaching and detaching the keybord cover to avoid + * Deliberately ignore attaching and detaching the keyboard cover to avoid * duplicates from intel-vbtn, which already emits SW_TABLET_MODE events * to userspace. * @@ -3833,6 +3888,8 @@ static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev) case TP_HKEY_EV_TABLET_CHANGED: tpacpi_input_send_tabletsw(); hotkey_tablet_mode_notify_change(); + if (keyboard_attached_on_screen_update()) + keyboard_attached_on_screen_notify_change(); *send_acpi_ev = false; return true; @@ -3980,20 +4037,22 @@ static void hotkey_resume(void) { tpacpi_disable_brightness_delay(); - mutex_lock(&hotkey_mutex); - if (hotkey_status_set(true) < 0 || - hotkey_mask_set(hotkey_acpi_mask) < 0) - pr_err("error while attempting to reset the event firmware interface\n"); - mutex_unlock(&hotkey_mutex); + scoped_guard(mutex, &hotkey_mutex) { + if (hotkey_status_set(true) < 0 || + hotkey_mask_set(hotkey_acpi_mask) < 0) + pr_err("error while attempting to reset the event firmware interface\n"); + } tpacpi_send_radiosw_update(); tpacpi_input_send_tabletsw(); hotkey_tablet_mode_notify_change(); + keyboard_attached_on_screen_update(); + keyboard_attached_on_screen_notify_change(); hotkey_wakeup_reason_notify_change(); hotkey_wakeup_hotunplug_complete_notify_change(); hotkey_poll_setup_safe(false); - /* restore previous mode of adapive keyboard of X1 Carbon */ + /* restore previous mode of adaptive keyboard of X1 Carbon */ if (tp_features.has_adaptive_kbd) { if (!acpi_evalf(hkey_handle, NULL, "STRW", "vd", adaptive_keyboard_prev_mode)) { @@ -4012,12 +4071,13 @@ static int hotkey_read(struct seq_file *m) return 0; } - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + res = ACQUIRE_ERR(mutex_kill, &guard); + if (res) + return res; res = hotkey_status_get(&status); if (!res) res = hotkey_mask_get(); - mutex_unlock(&hotkey_mutex); if (res) return res; @@ -4050,8 +4110,10 @@ static int hotkey_write(char *buf) if (!tp_features.hotkey) return -ENODEV; - if (mutex_lock_killable(&hotkey_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&hotkey_mutex); + res = ACQUIRE_ERR(mutex_kill, &guard); + if (res) + return res; mask = hotkey_user_mask; @@ -4070,8 +4132,7 @@ static int hotkey_write(char *buf) } else if (sscanf(cmd, "%x", &mask) == 1) { /* mask set */ } else { - res = -EINVAL; - goto errexit; + return -EINVAL; } } @@ -4081,8 +4142,6 @@ static int hotkey_write(char *buf) res = hotkey_user_mask_set(mask); } -errexit: - mutex_unlock(&hotkey_mutex); return res; } @@ -4287,6 +4346,17 @@ static const struct dmi_system_id fwbug_list[] __initconst = { {} }; +static const struct dmi_system_id keyboard_attached_on_screen_list[] __initconst = { + { + .ident = "ThinkPad X1 Fold 16 Gen 1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Fold 16 Gen 1"), + }, + }, + {} +}; + static const struct pci_device_id fwbug_cards_ids[] __initconst = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x24F3) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x24FD) }, @@ -4327,7 +4397,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "bluetooth is %s, status 0x%02x\n", - str_supported(tp_features.bluetooth), + str_supported_unsupported(tp_features.bluetooth), status); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES @@ -4506,7 +4576,7 @@ static int __init wan_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "wan is %s, status 0x%02x\n", - str_supported(tp_features.wan), + str_supported_unsupported(tp_features.wan), status); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES @@ -4634,7 +4704,7 @@ static int __init uwb_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, "uwb is %s, status 0x%02x\n", - str_supported(tp_features.uwb), + str_supported_unsupported(tp_features.uwb), status); #ifdef CONFIG_THINKPAD_ACPI_DEBUGFACILITIES @@ -4742,7 +4812,7 @@ static int __init video_init(struct ibm_init_struct *iibm) video_supported = TPACPI_VIDEO_NEW; vdbg_printk(TPACPI_DBG_INIT, "video is %s, mode %d\n", - str_supported(video_supported != TPACPI_VIDEO_NONE), + str_supported_unsupported(video_supported != TPACPI_VIDEO_NONE), video_supported); return (video_supported != TPACPI_VIDEO_NONE) ? 0 : -ENODEV; @@ -5037,21 +5107,16 @@ static DEFINE_MUTEX(kbdlight_mutex); static int kbdlight_set_level(int level) { - int ret = 0; - if (!hkey_handle) return -ENXIO; - mutex_lock(&kbdlight_mutex); + guard(mutex)(&kbdlight_mutex); if (!acpi_evalf(hkey_handle, NULL, "MLCS", "dd", level)) - ret = -EIO; - else - kbdlight_brightness = level; - - mutex_unlock(&kbdlight_mutex); + return -EIO; - return ret; + kbdlight_brightness = level; + return 0; } static int kbdlight_get_level(void) @@ -5340,8 +5405,8 @@ static int __init light_init(struct ibm_init_struct *iibm) acpi_evalf(ec_handle, NULL, "KBLT", "qv"); vdbg_printk(TPACPI_DBG_INIT, "light is %s, light status is %s\n", - str_supported(tp_features.light), - str_supported(tp_features.light_status)); + str_supported_unsupported(tp_features.light), + str_supported_unsupported(tp_features.light_status)); if (!tp_features.light) return -ENODEV; @@ -5458,7 +5523,7 @@ static int __init cmos_init(struct ibm_init_struct *iibm) TPACPI_ACPIHANDLE_INIT(cmos); vdbg_printk(TPACPI_DBG_INIT, "cmos commands are %s\n", - str_supported(cmos_handle != NULL)); + str_supported_unsupported(cmos_handle)); return cmos_handle ? 0 : -ENODEV; } @@ -5803,7 +5868,8 @@ static int __init led_init(struct ibm_init_struct *iibm) } vdbg_printk(TPACPI_DBG_INIT, "LED commands are %s, mode %d\n", - str_supported(led_supported), led_supported); + str_supported_unsupported(led_supported != TPACPI_LED_NONE), + led_supported); if (led_supported == TPACPI_LED_NONE) return -ENODEV; @@ -5924,7 +5990,7 @@ static int __init beep_init(struct ibm_init_struct *iibm) TPACPI_ACPIHANDLE_INIT(beep); vdbg_printk(TPACPI_DBG_INIT, "beep is %s\n", - str_supported(beep_handle != NULL)); + str_supported_unsupported(beep_handle)); quirks = tpacpi_check_quirks(beep_quirk_table, ARRAY_SIZE(beep_quirk_table)); @@ -5936,12 +6002,8 @@ static int __init beep_init(struct ibm_init_struct *iibm) static int beep_read(struct seq_file *m) { - if (!beep_handle) - seq_puts(m, "status:\t\tnot supported\n"); - else { - seq_puts(m, "status:\t\tsupported\n"); - seq_puts(m, "commands:\t<cmd> (<cmd> is 0-17)\n"); - } + seq_puts(m, "status:\t\tsupported\n"); + seq_puts(m, "commands:\t<cmd> (<cmd> is 0-17)\n"); return 0; } @@ -6374,7 +6436,7 @@ static int __init thermal_init(struct ibm_init_struct *iibm) thermal_read_mode = thermal_read_mode_check(); vdbg_printk(TPACPI_DBG_INIT, "thermal is %s, mode %d\n", - str_supported(thermal_read_mode != TPACPI_THERMAL_NONE), + str_supported_unsupported(thermal_read_mode != TPACPI_THERMAL_NONE), thermal_read_mode); return thermal_read_mode != TPACPI_THERMAL_NONE ? 0 : -ENODEV; @@ -6482,11 +6544,12 @@ static void tpacpi_brightness_checkpoint_nvram(void) vdbg_printk(TPACPI_DBG_BRGHT, "trying to checkpoint backlight level to NVRAM...\n"); - if (mutex_lock_killable(&brightness_mutex) < 0) + ACQUIRE(mutex_kill, guard)(&brightness_mutex); + if (ACQUIRE_ERR(mutex_kill, &guard)) return; if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec))) - goto unlock; + return; lec &= TP_EC_BACKLIGHT_LVLMSK; b_nvram = nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS); @@ -6504,9 +6567,6 @@ static void tpacpi_brightness_checkpoint_nvram(void) vdbg_printk(TPACPI_DBG_BRGHT, "NVRAM backlight level already is %u (0x%02x)\n", (unsigned int) lec, (unsigned int) b_nvram); - -unlock: - mutex_unlock(&brightness_mutex); } @@ -6584,8 +6644,9 @@ static int brightness_set(unsigned int value) vdbg_printk(TPACPI_DBG_BRGHT, "set backlight level to %d\n", value); - res = mutex_lock_killable(&brightness_mutex); - if (res < 0) + ACQUIRE(mutex_kill, guard)(&brightness_mutex); + res = ACQUIRE_ERR(mutex_kill, &guard); + if (res) return res; switch (brightness_mode) { @@ -6600,7 +6661,6 @@ static int brightness_set(unsigned int value) res = -ENXIO; } - mutex_unlock(&brightness_mutex); return res; } @@ -6623,14 +6683,11 @@ static int brightness_get(struct backlight_device *bd) { int status, res; - res = mutex_lock_killable(&brightness_mutex); - if (res < 0) + ACQUIRE(mutex_kill, guard)(&brightness_mutex); + if (ACQUIRE_ERR(mutex_kill, &guard)) return 0; res = tpacpi_brightness_get_raw(&status); - - mutex_unlock(&brightness_mutex); - if (res < 0) return 0; @@ -6653,26 +6710,21 @@ static const struct backlight_ops ibm_backlight_data = { static int __init tpacpi_evaluate_bcl(struct acpi_device *adev, void *not_used) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; acpi_status status; - int rc; status = acpi_evaluate_object(adev->handle, "_BCL", NULL, &buffer); if (ACPI_FAILURE(status)) return 0; - obj = buffer.pointer; + union acpi_object *obj __free(kfree) = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE) { acpi_handle_info(adev->handle, "Unknown _BCL data, please report this to %s\n", TPACPI_MAIL); - rc = 0; - } else { - rc = obj->package.count; + return 0; } - kfree(obj); - return rc; + return obj->package.count; } /* @@ -7069,7 +7121,7 @@ static bool software_mute_active; static int software_mute_orig_mode; /* - * Used to syncronize writers to TP_EC_AUDIO and + * Used to synchronize writers to TP_EC_AUDIO and * TP_NVRAM_ADDR_MIXER, as we need to do read-modify-write */ static struct mutex volume_mutex; @@ -7095,11 +7147,12 @@ static void tpacpi_volume_checkpoint_nvram(void) else ec_mask = TP_EC_AUDIO_MUTESW_MSK | TP_EC_AUDIO_LVL_MSK; - if (mutex_lock_killable(&volume_mutex) < 0) + ACQUIRE(mutex_kill, guard)(&volume_mutex); + if (ACQUIRE_ERR(mutex_kill, &guard)) return; if (unlikely(!acpi_ec_read(TP_EC_AUDIO, &lec))) - goto unlock; + return; lec &= ec_mask; b_nvram = nvram_read_byte(TP_NVRAM_ADDR_MIXER); @@ -7116,9 +7169,6 @@ static void tpacpi_volume_checkpoint_nvram(void) "NVRAM mixer status already is 0x%02x (0x%02x)\n", (unsigned int) lec, (unsigned int) b_nvram); } - -unlock: - mutex_unlock(&volume_mutex); } static int volume_get_status_ec(u8 *status) @@ -7167,12 +7217,14 @@ static int __volume_set_mute_ec(const bool mute) int rc; u8 s, n; - if (mutex_lock_killable(&volume_mutex) < 0) - return -EINTR; + ACQUIRE(mutex_kill, guard)(&volume_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = volume_get_status_ec(&s); if (rc) - goto unlock; + return rc; n = (mute) ? s | TP_EC_AUDIO_MUTESW_MSK : s & ~TP_EC_AUDIO_MUTESW_MSK; @@ -7183,8 +7235,6 @@ static int __volume_set_mute_ec(const bool mute) rc = 1; } -unlock: - mutex_unlock(&volume_mutex); return rc; } @@ -7215,12 +7265,14 @@ static int __volume_set_volume_ec(const u8 vol) if (vol > TP_EC_VOLUME_MAX) return -EINVAL; - if (mutex_lock_killable(&volume_mutex) < 0) - return -EINTR; + ACQUIRE(mutex_kill, guard)(&volume_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = volume_get_status_ec(&s); if (rc) - goto unlock; + return rc; n = (s & ~TP_EC_AUDIO_LVL_MSK) | vol; @@ -7230,8 +7282,6 @@ static int __volume_set_volume_ec(const u8 vol) rc = 1; } -unlock: - mutex_unlock(&volume_mutex); return rc; } @@ -7262,7 +7312,7 @@ static int volume_set_software_mute(bool startup) /* * In software mute mode, the standard codec controls take - * precendence, so we unmute the ThinkPad HW switch at + * precedence, so we unmute the ThinkPad HW switch at * startup. Just on case there are SAUM-capable ThinkPads * with level controls, set max HW volume as well. */ @@ -7596,7 +7646,7 @@ static int __init volume_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_MIXER, "mute is supported, volume control is %s\n", - str_supported(!tp_features.mixer_no_level_control)); + str_supported_unsupported(!tp_features.mixer_no_level_control)); if (software_mute_requested && volume_set_software_mute(true) == 0) { software_mute_active = true; @@ -7981,7 +8031,7 @@ TPACPI_HANDLE(fanw, ec, "FANW", /* E531 */ ); /* all others */ /* - * Unitialized HFSP quirk: ACPI DSDT and EC fail to initialize the + * Uninitialized HFSP quirk: ACPI DSDT and EC fail to initialize the * HFSP register at boot, so it contains 0x07 but the Thinkpad could * be in auto mode (0x80). * @@ -8135,13 +8185,14 @@ static int fan_get_status_safe(u8 *status) int rc; u8 s; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = fan_get_status(&s); /* NS EC doesn't have register with level settings */ if (!rc && !fan_with_ns_addr) fan_update_desired_level(s); - mutex_unlock(&fan_mutex); if (rc) return rc; @@ -8334,8 +8385,10 @@ static int fan_set_level_safe(int level) if (!fan_control_allowed) return -EPERM; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; if (level == TPACPI_FAN_LAST_LEVEL) level = fan_control_desired_level; @@ -8344,7 +8397,6 @@ static int fan_set_level_safe(int level) if (!rc) fan_update_desired_level(level); - mutex_unlock(&fan_mutex); return rc; } @@ -8356,8 +8408,10 @@ static int fan_set_enable(void) if (!fan_control_allowed) return -EPERM; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; switch (fan_control_access_mode) { case TPACPI_FAN_WR_ACPI_FANS: @@ -8413,8 +8467,6 @@ static int fan_set_enable(void) rc = -ENXIO; } - mutex_unlock(&fan_mutex); - if (!rc) vdbg_printk(TPACPI_DBG_FAN, "fan control: set fan control register to 0x%02x\n", @@ -8429,8 +8481,10 @@ static int fan_set_disable(void) if (!fan_control_allowed) return -EPERM; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = 0; switch (fan_control_access_mode) { @@ -8475,7 +8529,6 @@ static int fan_set_disable(void) vdbg_printk(TPACPI_DBG_FAN, "fan control: set fan control register to 0\n"); - mutex_unlock(&fan_mutex); return rc; } @@ -8486,8 +8539,10 @@ static int fan_set_speed(int speed) if (!fan_control_allowed) return -EPERM; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = 0; switch (fan_control_access_mode) { @@ -8521,7 +8576,6 @@ static int fan_set_speed(int speed) rc = -ENXIO; } - mutex_unlock(&fan_mutex); return rc; } @@ -8681,8 +8735,10 @@ static ssize_t fan_pwm1_store(struct device *dev, /* scale down from 0-255 to 0-7 */ newlevel = (s >> 5) & 0x07; - if (mutex_lock_killable(&fan_mutex)) - return -ERESTARTSYS; + ACQUIRE(mutex_kill, guard)(&fan_mutex); + rc = ACQUIRE_ERR(mutex_kill, &guard); + if (rc) + return rc; rc = fan_get_status(&status); if (!rc && (status & @@ -8696,7 +8752,6 @@ static ssize_t fan_pwm1_store(struct device *dev, } } - mutex_unlock(&fan_mutex); return (rc) ? rc : count; } @@ -8846,6 +8901,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */ TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ + TPACPI_Q_LNV('J', '4', TPACPI_FAN_NS), /* L440 / L540 */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */ TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */ @@ -8993,8 +9049,8 @@ static int __init fan_init(struct ibm_init_struct *iibm) vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_FAN, "fan is %s, modes %d, %d\n", - str_supported(fan_status_access_mode != TPACPI_FAN_NONE || - fan_control_access_mode != TPACPI_FAN_WR_NONE), + str_supported_unsupported(fan_status_access_mode != TPACPI_FAN_NONE || + fan_control_access_mode != TPACPI_FAN_WR_NONE), fan_status_access_mode, fan_control_access_mode); /* fan control master switch */ @@ -10107,9 +10163,8 @@ static void lcdshadow_resume(void) if (!lcdshadow_dev) return; - mutex_lock(&lcdshadow_dev->lock); + guard(mutex)(&lcdshadow_dev->lock); lcdshadow_set_sw_state(lcdshadow_dev, lcdshadow_dev->sw_state); - mutex_unlock(&lcdshadow_dev->lock); } static int lcdshadow_read(struct seq_file *m) @@ -10141,9 +10196,8 @@ static int lcdshadow_write(char *buf) if (state >= 2 || state < 0) return -EINVAL; - mutex_lock(&lcdshadow_dev->lock); - res = lcdshadow_set_sw_state(lcdshadow_dev, state); - mutex_unlock(&lcdshadow_dev->lock); + scoped_guard(mutex, &lcdshadow_dev->lock) + res = lcdshadow_set_sw_state(lcdshadow_dev, state); drm_privacy_screen_call_notifier_chain(lcdshadow_dev); @@ -10547,13 +10601,14 @@ static int dytc_profile_set(struct device *dev, int output; int err; - err = mutex_lock_interruptible(&dytc_mutex); + ACQUIRE(mutex_intr, guard)(&dytc_mutex); + err = ACQUIRE_ERR(mutex_intr, &guard); if (err) return err; err = convert_profile_to_dytc(profile, &perfmode); if (err) - goto unlock; + return err; if (dytc_capabilities & BIT(DYTC_FC_MMC)) { if (profile == PLATFORM_PROFILE_BALANCED) { @@ -10561,22 +10616,22 @@ static int dytc_profile_set(struct device *dev, * To get back to balanced mode we need to issue a reset command. * Note we still need to disable CQL mode before hand and re-enable * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays - * stuck at 0 for aprox. 30 minutes. + * stuck at 0 for approx. 30 minutes. */ err = dytc_cql_command(DYTC_CMD_RESET, &output); if (err) - goto unlock; + return err; } else { /* Determine if we are in CQL mode. This alters the commands we do */ err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output); if (err) - goto unlock; + return err; } } else if (dytc_capabilities & BIT(DYTC_FC_PSC)) { err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output); if (err) - goto unlock; + return err; /* system supports AMT, activate it when on balanced */ if (dytc_capabilities & BIT(DYTC_FC_AMT)) @@ -10584,8 +10639,6 @@ static int dytc_profile_set(struct device *dev, } /* Success - update current profile */ dytc_current_profile = profile; -unlock: - mutex_unlock(&dytc_mutex); return err; } @@ -10607,26 +10660,26 @@ static const struct platform_profile_ops dytc_profile_ops = { static void dytc_profile_refresh(void) { enum platform_profile_option profile; - int output = 0, err = 0; + int output = 0, err; int perfmode, funcmode = 0; - mutex_lock(&dytc_mutex); - if (dytc_capabilities & BIT(DYTC_FC_MMC)) { - if (dytc_mmc_get_available) - err = dytc_command(DYTC_CMD_MMC_GET, &output); - else - err = dytc_cql_command(DYTC_CMD_GET, &output); - funcmode = DYTC_FUNCTION_MMC; - } else if (dytc_capabilities & BIT(DYTC_FC_PSC)) { - err = dytc_command(DYTC_CMD_GET, &output); - /* Check if we are PSC mode, or have AMT enabled */ - funcmode = (output >> DYTC_GET_FUNCTION_BIT) & 0xF; - } else { /* Unknown profile mode */ - err = -ENODEV; + scoped_guard(mutex, &dytc_mutex) { + if (dytc_capabilities & BIT(DYTC_FC_MMC)) { + if (dytc_mmc_get_available) + err = dytc_command(DYTC_CMD_MMC_GET, &output); + else + err = dytc_cql_command(DYTC_CMD_GET, &output); + funcmode = DYTC_FUNCTION_MMC; + } else if (dytc_capabilities & BIT(DYTC_FC_PSC)) { + err = dytc_command(DYTC_CMD_GET, &output); + /* Check if we are PSC mode, or have AMT enabled */ + funcmode = (output >> DYTC_GET_FUNCTION_BIT) & 0xF; + } else { /* Unknown profile mode */ + err = -ENODEV; + } + if (err) + return; } - mutex_unlock(&dytc_mutex); - if (err) - return; perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; err = convert_dytc_to_profile(funcmode, perfmode, &profile); @@ -11015,24 +11068,23 @@ static int auxmac_init(struct ibm_init_struct *iibm) { acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; status = acpi_evaluate_object(NULL, "\\MACA", NULL, &buffer); - if (ACPI_FAILURE(status)) return -ENODEV; - obj = buffer.pointer; - - if (obj->type != ACPI_TYPE_STRING || obj->string.length != AUXMAC_STRLEN) { + union acpi_object *obj __free(kfree) = buffer.pointer; + if (!obj || obj->type != ACPI_TYPE_STRING || obj->string.length != AUXMAC_STRLEN) { pr_info("Invalid buffer for MAC address pass-through.\n"); - goto auxmacinvalid; + strscpy(auxmac, "unavailable", sizeof(auxmac)); + return 0; } if (obj->string.pointer[AUXMAC_BEGIN_MARKER] != '#' || obj->string.pointer[AUXMAC_END_MARKER] != '#') { pr_info("Invalid header for MAC address pass-through.\n"); - goto auxmacinvalid; + strscpy(auxmac, "unavailable", sizeof(auxmac)); + return 0; } if (strncmp(obj->string.pointer + AUXMAC_START, "XXXXXXXXXXXX", AUXMAC_LEN) != 0) @@ -11040,13 +11092,7 @@ static int auxmac_init(struct ibm_init_struct *iibm) else strscpy(auxmac, "disabled", sizeof(auxmac)); -free: - kfree(obj); return 0; - -auxmacinvalid: - strscpy(auxmac, "unavailable", sizeof(auxmac)); - goto free; } static struct ibm_struct auxmac_data = { @@ -11538,7 +11584,7 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) if (tp_features.kbdlight) { enum led_brightness brightness; - mutex_lock(&kbdlight_mutex); + guard(mutex)(&kbdlight_mutex); /* * Check the brightness actually changed, setting the brightness @@ -11550,8 +11596,6 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) led_classdev_notify_brightness_hw_changed( &tpacpi_led_kbdlight.led_classdev, brightness); } - - mutex_unlock(&kbdlight_mutex); } /* Key events are suppressed by default hotkey_user_mask */ return false; @@ -11573,11 +11617,11 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) enum drm_privacy_screen_status old_hw_state; bool changed; - mutex_lock(&lcdshadow_dev->lock); - old_hw_state = lcdshadow_dev->hw_state; - lcdshadow_get_hw_state(lcdshadow_dev); - changed = lcdshadow_dev->hw_state != old_hw_state; - mutex_unlock(&lcdshadow_dev->lock); + scoped_guard(mutex, &lcdshadow_dev->lock) { + old_hw_state = lcdshadow_dev->hw_state; + lcdshadow_get_hw_state(lcdshadow_dev); + changed = lcdshadow_dev->hw_state != old_hw_state; + } if (changed) drm_privacy_screen_call_notifier_chain(lcdshadow_dev); @@ -11598,12 +11642,10 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) pr_err("Error retrieving camera shutter state after shutter event\n"); return true; } - mutex_lock(&tpacpi_inputdev_send_mutex); - - input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state); - input_sync(tpacpi_inputdev); - - mutex_unlock(&tpacpi_inputdev_send_mutex); + scoped_guard(mutex, &tpacpi_inputdev_send_mutex) { + input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state); + input_sync(tpacpi_inputdev); + } return true; case TP_HKEY_EV_DOUBLETAP_TOGGLE: /* Toggle kernel-level doubletap event filtering */ @@ -11627,20 +11669,11 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) static struct proc_dir_entry *proc_dir; /* - * Module and infrastructure proble, init and exit handling + * Module and infrastructure probe, init and exit handling */ static bool force_load; -#ifdef CONFIG_THINKPAD_ACPI_DEBUG -static const char * __init str_supported(int is_supported) -{ - static char text_unsupported[] __initdata = "not supported"; - - return (is_supported) ? &text_unsupported[4] : &text_unsupported[0]; -} -#endif /* CONFIG_THINKPAD_ACPI_DEBUG */ - static struct dentry *tpacpi_dbg; static void tpacpi_debugfs_init(void) { @@ -11925,7 +11958,7 @@ static int __must_check __init get_thinkpad_model_data( static int __init probe_for_thinkpad(void) { - int is_thinkpad; + bool is_thinkpad; if (acpi_disabled) return -ENODEV; @@ -12360,6 +12393,8 @@ static int __init thinkpad_acpi_module_init(void) dmi_id = dmi_first_match(fwbug_list); if (dmi_id) tp_features.quirks = dmi_id->driver_data; + tp_features.has_keyboard_attached_on_screen = + dmi_check_system(keyboard_attached_on_screen_list); /* Device initialization */ tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, PLATFORM_DEVID_NONE, diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index d5e961566136..1d44ff83e157 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -29,6 +29,7 @@ #include <linux/acpi.h> #include <linux/bug.h> #include <linux/cleanup.h> +#include <linux/compiler.h> #include <linux/component.h> #include <linux/container_of.h> #include <linux/debugfs.h> @@ -38,6 +39,7 @@ #include <linux/export.h> #include <linux/gfp_types.h> #include <linux/limits.h> +#include <linux/lockdep.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/mutex_types.h> @@ -45,9 +47,13 @@ #include <linux/overflow.h> #include <linux/seq_file.h> #include <linux/stddef.h> +#include <linux/string.h> #include <linux/types.h> +#include <linux/uuid.h> #include <linux/wmi.h> +#include <asm/barrier.h> + #include "wmi-capdata.h" #include "wmi-helpers.h" @@ -70,14 +76,19 @@ enum lwmi_cd_type { CD_TYPE_NONE = -1, }; -#define LWMI_CD_TABLE_ITEM(_type) \ - [_type] = { \ - .name = #_type, \ - .type = _type, \ +static const struct wmi_device_id lwmi_cd_id_table[]; + +/* Save some .rodata space by reusing the GUID strings from the id table. */ +#define LWMI_CD_TABLE_ITEM(_type) \ + [_type] = { \ + .name = #_type, \ + .guid_string = lwmi_cd_id_table[_type].guid_string, \ + .type = _type, \ } static const struct lwmi_cd_info { const char *name; + const char *guid_string; enum lwmi_cd_type type; } lwmi_cd_table[] = { LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), @@ -87,9 +98,11 @@ static const struct lwmi_cd_info { struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ + const struct lwmi_cd_info *info; struct wmi_device *wdev; struct cd_list *list; struct dentry *debugfs_dir; + bool initialized; /* * A capdata device may be a component master of another capdata device. @@ -101,14 +114,13 @@ struct lwmi_cd_priv { struct lwmi_cd_sub_master_priv { struct device *master_dev; cd_list_cb_t master_cb; - struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */ + struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV): stubbed sub-component. */ bool registered; /* Has the sub-master been registered? */ - } *sub_master; + } *sub_master; /* NULL: no sub-component. */ }; struct cd_list { struct mutex list_mutex; /* list R/W mutex */ - enum lwmi_cd_type type; u8 count; union { @@ -137,7 +149,7 @@ static int lwmi_cd_match(struct device *dev, void *type) return false; priv = dev_get_drvdata(dev); - return priv->list->type == *(enum lwmi_cd_type *)type; + return priv->info->type == *(enum lwmi_cd_type *)type; } /** @@ -147,24 +159,49 @@ static int lwmi_cd_match(struct device *dev, void *type) * * Adds all component matches to the list stored in @matchptr for the @master * device. @matchptr must be initialized to NULL. + * + * Missing components are not added to the match list, or else the master waits + * for them forever. For some components, their existence can be determined by + * querying other components, so they are handled as sub-components and thus not + * matched here. For components with no existence declaration, wmidev_exists() + * is used to determine their existence. + * + * If all components are missing, ERR_PTR(-ENXIO) is returned through @matchptr. */ void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr) { - int i; + bool matched = false; + int ret, i; if (WARN_ON(*matchptr)) return; for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + guid_t guid; + /* Skip sub-components. */ if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA) continue; + ret = guid_parse(lwmi_cd_table[i].guid_string, &guid); + if (ret < 0) { + *matchptr = ERR_PTR(ret); + return; + } + + if (!wmidev_exists(&guid)) + continue; + component_match_add(master, matchptr, lwmi_cd_match, (void *)&lwmi_cd_table[i].type); if (IS_ERR(*matchptr)) return; + + matched = true; } + + if (!matched) + *matchptr = ERR_PTR(-ENXIO); } EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA"); @@ -254,10 +291,16 @@ static int lwmi_cd_component_bind(struct device *cd_dev, struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); struct lwmi_cd_binder *binder = data; - switch (priv->list->type) { + switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: binder->cd00_list = priv->list; + /* Missing sub-master info implies missing sub-component. */ + if (!priv->sub_master) { + binder->cd_fan_list_cb(om_dev, NULL); + break; + } + priv->sub_master->master_dev = om_dev; priv->sub_master->master_cb = binder->cd_fan_list_cb; lwmi_cd_call_master_cb(priv); @@ -288,13 +331,9 @@ static void lwmi_cd_component_unbind(struct device *cd_dev, { struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); - switch (priv->list->type) { - case LENOVO_CAPABILITY_DATA_00: + if (priv->sub_master) { priv->sub_master->master_dev = NULL; priv->sub_master->master_cb = NULL; - return; - default: - return; } } @@ -308,8 +347,8 @@ static const struct component_ops lwmi_cd_component_ops = { * @dev: The sub-master capdata basic device. * * Call component_bind_all to bind the sub-component device to the sub-master - * device. On success, collect the pointer to the sub-component list and try - * to call the master callback. + * device. On success, collect the pointer (or ERR_PTR(-ENODEV) if it's stubbed) + * to the sub-component list and try to call the master callback. * * Return: 0 on success, or an error code. */ @@ -323,7 +362,7 @@ static int lwmi_cd_sub_master_bind(struct device *dev) if (ret) return ret; - priv->sub_master->sub_component_list = sub_component_list; + priv->sub_master->sub_component_list = sub_component_list ?: ERR_PTR(-ENODEV); lwmi_cd_call_master_cb(priv); return 0; @@ -369,17 +408,19 @@ static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, struct component_match *master_match = NULL; int ret; + /* + * If the sub-master has no sub-component, there is no need to allocate + * or register the sub-master. The master callback will be called with + * NULL on master <-> sub-master bind to inform the master about the + * absence of sub-component. + */ + if (sub_component_type == CD_TYPE_NONE) + return 0; + priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL); if (!priv->sub_master) return -ENOMEM; - if (sub_component_type == CD_TYPE_NONE) { - /* The master callback will be called with NULL on bind. */ - priv->sub_master->sub_component_list = ERR_PTR(-ENODEV); - priv->sub_master->registered = false; - return 0; - } - /* * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack * data cannot be used here. Steal one from lwmi_cd_table. @@ -404,7 +445,7 @@ static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, */ static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv) { - if (!priv->sub_master->registered) + if (!priv->sub_master || !priv->sub_master->registered) return; component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops); @@ -440,7 +481,7 @@ static const struct component_ops lwmi_cd_sub_component_ops = { /* * lwmi_cd*_get_data - Get the data of the specified attribute * @list: The lenovo-wmi-capdata pointer to its cd_list struct. - * @attribute_id: The capdata attribute ID to be found. + * @attribute_id: The capdata attribute ID (non-zero) to be found. * @output: Pointer to a capdata* struct to return the data. * * Retrieves the capability data struct pointer for the given @@ -448,12 +489,12 @@ static const struct component_ops lwmi_cd_sub_component_ops = { * * Return: 0 on success, or -EINVAL. */ -#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \ +#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _output_t) \ int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \ { \ u8 idx; \ \ - if (WARN_ON(list->type != _cd_type)) \ + if (WARN_ON(!list || !attribute_id)) \ return -EINVAL; \ \ guard(mutex)(&list->list_mutex); \ @@ -466,13 +507,13 @@ static const struct component_ops lwmi_cd_sub_component_ops = { return -EINVAL; \ } -DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00); +DEF_LWMI_CDXX_GET_DATA(cd00, struct capdata00); EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA"); -DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); +DEF_LWMI_CDXX_GET_DATA(cd01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); -DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan); +DEF_LWMI_CDXX_GET_DATA(cd_fan, struct capdata_fan); EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA"); /* ======== debugfs ======== */ @@ -546,15 +587,14 @@ static int lwmi_cd_debugfs_show(struct seq_file *s, void *data) guard(mutex)(&priv->list->list_mutex); - /* lwmi_cd_alloc() ensured priv->list->type must be a valid type. */ for (idx = 0; idx < priv->list->count; idx++) { - seq_printf(s, "%s[%u]:\n", lwmi_cd_table[priv->list->type].name, idx); + seq_printf(s, "%s[%u]:\n", priv->info->name, idx); - if (priv->list->type == LENOVO_CAPABILITY_DATA_00) + if (priv->info->type == LENOVO_CAPABILITY_DATA_00) lwmi_cd00_show(s, &priv->list->cd00[idx]); - else if (priv->list->type == LENOVO_CAPABILITY_DATA_01) + else if (priv->info->type == LENOVO_CAPABILITY_DATA_01) lwmi_cd01_show(s, &priv->list->cd01[idx]); - else if (priv->list->type == LENOVO_FAN_TEST_DATA) + else if (priv->info->type == LENOVO_FAN_TEST_DATA) lwmi_cd_fan_show(s, &priv->list->cd_fan[idx]); } @@ -568,6 +608,9 @@ DEFINE_SHOW_ATTRIBUTE(lwmi_cd_debugfs); */ static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv) { + if (!priv->list) + return; + priv->debugfs_dir = lwmi_debugfs_create_dir(priv->wdev); debugfs_create_file("capdata", 0444, priv->debugfs_dir, priv, &lwmi_cd_debugfs_fops); @@ -579,6 +622,7 @@ static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv) */ static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv) { + /* Debugfs can handle NULL dir, no need to check. */ debugfs_remove_recursive(priv->debugfs_dir); priv->debugfs_dir = NULL; } @@ -586,20 +630,104 @@ static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv) /* ======== WMI interface ======== */ /** - * lwmi_cd_cache() - Cache all WMI data block information + * lwmi_cd_stub() - Stub the device by not providing any capability data * @priv: lenovo-wmi-capdata driver data. + * @err: The occurred error. * - * Loop through each WMI data block and cache the data. + * The Other Mode driver binds to both Capability Data 00 and 01. If either 00 + * or 01 fails to probe, the Other Mode device will fail to provide fw-attr or + * hwmon/power_supply_ext devices for the other functional capdata device. + * + * Therefore, errors must be non-fatal in order not to break the assumptions + * made by the device component framework, so that the Other Mode device can + * provide whatever is functional. + * + * After stubbing the device, a NULL list will be passed to the Other Mode + * device on bind. + * + * Return: 0 if the @err is suppressed, otherwise its propagated as is. + */ +static int lwmi_cd_stub(struct lwmi_cd_priv *priv, int err) +{ + /* + * No barrier is required in any possible code paths: + * - called from lwmi_cd_cache(): a proper barrier has been employed + * - called from lwmi_cd_fan_list_alloc_cache(): early setup does not + * require synchronization, and priv->initialized must be false + */ + bool initialized = READ_ONCE(priv->initialized); + + dev_warn(&priv->wdev->dev, "%s %s (%u items) due to error: %d\n", + initialized ? "clearing" : "stubbing", priv->info->name, + priv->list ? priv->list->count : 0, err); + + /* Simply print the warning message. */ + if (!priv->list) + return initialized ? err : 0; + + /* + * Stub the device on initialization errors. + * + * Note that if a transient error occurs during initialization, there is + * nothing we can do to re-enable the functionalities of the Other Mode + * driver even if a successful runtime notifier call making the relevant + * capdata emerge, as registering functionalities without the protection + * of the component framework is unsafe. Therefore, registering the + * notifier for a stubbed device is meaningless, and we don't need to + * distinguish transient and pernament errors during initialization. + * + * For runtime errors (see below), the registered functionalities + * (fw-attr) become temporarily unavailable due to the unavailablilty of + * the relevant capdata, and will recover after a successful runtime + * notifier call. + */ + if (!initialized) { + devm_kfree(&priv->wdev->dev, priv->list); + priv->list = NULL; + + return 0; + } + + /* + * Runtime errors are transient. Simply clear all cached data and + * propagate the error. + * + * Since a valid attribute id is never 0 (the firmware also untilize the + * fact to stub some capabilities according to platform metadata), + * clearing the cached data effectively makes all attributes temporarily + * unavailable until the next notifier call. + */ + + lockdep_assert_held(&priv->list->list_mutex); + + switch (priv->info->type) { + case LENOVO_CAPABILITY_DATA_01: + memset(priv->list->cd01, 0, + flex_array_size(priv->list, cd01, priv->list->count)); + break; + default: + unreachable(); + } + + return err; +} + +/** + * __lwmi_cd_cache() - Cache all WMI data block information locklessly + * @priv: lenovo-wmi-capdata driver data. + * + * Loop through each WMI data block and cache the data locklessly. * * Return: 0 on success, or an error. */ -static int lwmi_cd_cache(struct lwmi_cd_priv *priv) +static int __lwmi_cd_cache(struct lwmi_cd_priv *priv) { + bool got_data = false; size_t size; int idx; void *p; - switch (priv->list->type) { + switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: p = &priv->list->cd00[0]; size = sizeof(priv->list->cd00[0]); @@ -615,109 +743,165 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return -EINVAL; } - guard(mutex)(&priv->list->list_mutex); for (idx = 0; idx < priv->list->count; idx++, p += size) { - union acpi_object *ret_obj __free(kfree) = NULL; + struct wmi_buffer wbuf; + int ret; - ret_obj = wmidev_block_query(priv->wdev, idx); - if (!ret_obj) - return -ENODEV; - - if (ret_obj->type != ACPI_TYPE_BUFFER || - ret_obj->buffer.length < size) + ret = wmidev_query_block(priv->wdev, idx, &wbuf, size); + if (ret == -ENODATA) /* The block is too short, probably stubbed. */ continue; + if (ret) + return lwmi_cd_stub(priv, ret); - memcpy(p, ret_obj->buffer.pointer, size); + /* Capdata 01 is an extension to capdata 00. */ + struct capdata00 *capdata __free(kfree) = wbuf.data; + + memcpy(p, capdata, size); + + /* + * A valid attribute always has a non-zero id. IOW, a zero id + * implies a stubbed item (or the query method being stubbed). + * + * The stubbed item is still copied to the cache slot anyway, in + * case the firmware dynamically stubs the capdata item after + * switching Game Zone mode. In this manner, the previously + * exposed capdata can be void. + */ + if (capdata->id) + got_data = true; } + /* + * Some devices may stub the query method because it doesn't support the + * relevant interfaces at all. For example, most ThinkBook devices + * doesn't support Game Zone or Other Mode thermal tuning, so capdata 01 + * is useless and stubbed. + * + * Stub the device and release needless resources. + */ + if (!got_data) + return lwmi_cd_stub(priv, -ENODATA); + return 0; } /** + * lwmi_cd_cache() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata driver data. + * + * Loop through each WMI data block and cache the data. + * + * Return: 0 on success, or an error. + */ +static int lwmi_cd_cache(struct lwmi_cd_priv *priv) +{ + if (!priv->list) + return 0; + + /* Synchronize mutex initialization. Paired with lwmi_cd_finalize(). */ + if (!smp_load_acquire(&priv->initialized)) + return __lwmi_cd_cache(priv); + + switch (priv->info->type) { + case LENOVO_CAPABILITY_DATA_01: + break; + default: + return -EINVAL; + } + + guard(mutex)(&priv->list->list_mutex); + return __lwmi_cd_cache(priv); +} + +struct lwmi_cd_fan_block { + u32 nr; + u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */ +} __packed; + +/** * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list * @priv: lenovo-wmi-capdata driver data. - * @listptr: Pointer to returned cd_list pointer. * - * Return: count of fans found, or an error. + * Return: 0 on success, or an error. */ -static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr) +static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv) { + struct wmi_buffer wbuf; + bool got_data = false; struct cd_list *list; - size_t size; + int ret, idx; u32 count; - int idx; - /* Emit unaligned access to u8 buffer with __packed. */ - struct cd_fan_block { - u32 nr; - u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */ - } __packed * block; + ret = wmidev_query_block(priv->wdev, 0, &wbuf, sizeof(struct lwmi_cd_fan_block)); + if (ret == -ENODATA) /* The block is too short, probably stubbed. */ + return 0; + if (ret) + return lwmi_cd_stub(priv, ret); /* Print the warning message. */ - union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0); - if (!ret_obj) - return -ENODEV; + struct lwmi_cd_fan_block *block __free(kfree) = wbuf.data; - if (ret_obj->type == ACPI_TYPE_BUFFER) { - block = (struct cd_fan_block *)ret_obj->buffer.pointer; - size = ret_obj->buffer.length; + count = block->nr; - count = size >= sizeof(*block) ? block->nr : 0; - if (size < struct_size(block, data, count * 3)) { - dev_warn(&priv->wdev->dev, - "incomplete fan test data block: %zu < %zu, ignoring\n", - size, struct_size(block, data, count * 3)); - count = 0; - } else if (count > U8_MAX) { - dev_warn(&priv->wdev->dev, - "too many fans reported: %u > %u, truncating\n", - count, U8_MAX); - count = U8_MAX; - } - } else { - /* - * This is usually caused by a dummy ACPI method. Do not return an error - * as failing to probe this device will result in sub-master device being - * unbound. This behavior aligns with lwmi_cd_cache(). - */ + if (count > U8_MAX) { + dev_warn(&priv->wdev->dev, + "too many fans reported: %u > %u, ignoring\n", count, U8_MAX); + count = 0; + } else if (wbuf.length < struct_size(block, data, count * 3)) { + dev_warn(&priv->wdev->dev, + "incomplete fan test data block: %zu < %zu (%u fans), ignoring\n", + wbuf.length, struct_size(block, data, count * 3), count); count = 0; } + if (!count) + return 0; + list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL); if (!list) return -ENOMEM; + list->count = count; + priv->list = list; + for (idx = 0; idx < count; idx++) { - /* Do not calculate array index using count, as it may be truncated. */ + if (!block->data[idx]) /* Ignore stub. */ + continue; + list->cd_fan[idx] = (struct capdata_fan) { .id = block->data[idx], - .max_rpm = block->data[idx + block->nr], - .min_rpm = block->data[idx + (2 * block->nr)], + .max_rpm = block->data[idx + count], + .min_rpm = block->data[idx + (2 * count)], }; + + got_data = true; } - *listptr = list; - return count; + if (!got_data) + return lwmi_cd_stub(priv, -ENODATA); + + return 0; } /** * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata * @priv: lenovo-wmi-capdata driver data. - * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. * * Return: 0 on success, or an error. */ -static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) { struct cd_list *list; size_t list_size; - int count, ret; + int count; count = wmidev_instance_count(priv->wdev); + if (!count) + return 0; - switch (type) { + switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: list_size = struct_size(list, cd00, count); break; @@ -725,11 +909,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) list_size = struct_size(list, cd01, count); break; case LENOVO_FAN_TEST_DATA: - count = lwmi_cd_fan_list_alloc_cache(priv, &list); - if (count < 0) - return count; - - goto got_list; + return lwmi_cd_fan_list_alloc_cache(priv); default: return -EINVAL; } @@ -738,12 +918,6 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) if (!list) return -ENOMEM; -got_list: - ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex); - if (ret) - return ret; - - list->type = type; list->count = count; priv->list = list; @@ -751,9 +925,29 @@ got_list: } /** + * lwmi_cd_finalize() - Finalize the capability data initialization + * @priv: lenovo-wmi-capdata driver data. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd_finalize(struct lwmi_cd_priv *priv) +{ + int ret; + + if (priv->list) { + ret = devm_mutex_init(&priv->wdev->dev, &priv->list->list_mutex); + if (ret) + return ret; + } + + /* Synchronize mutex initialization. Paired with lwmi_cd_cache(). */ + smp_store_release(&priv->initialized, 1); + return 0; +} + +/** * lwmi_cd_setup() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. - * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. Then loop through each data block and @@ -761,26 +955,28 @@ got_list: * * Return: 0 on success, or an error code. */ -static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) +static int lwmi_cd_setup(struct lwmi_cd_priv *priv) { int ret; - ret = lwmi_cd_alloc(priv, type); + ret = lwmi_cd_alloc(priv); + if (ret) + return ret; + + ret = lwmi_cd_cache(priv); if (ret) return ret; - return lwmi_cd_cache(priv); + return lwmi_cd_finalize(priv); } /** * lwmi_cd01_notifier_call() - Call method for cd01 notifier. - * block call chain. - * @nb: The notifier_block registered to lenovo-wmi-events driver. + * @nb: The cd01 ACPI notifier_block. * @action: Unused. * @data: The ACPI event. * - * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile - * of a change. + * Refresh cached capdata on AC status change. * * Return: notifier_block status. */ @@ -799,8 +995,16 @@ static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long acti switch (event->type) { case ACPI_AC_NOTIFY_STATUS: ret = lwmi_cd_cache(priv); - if (ret) - return NOTIFY_BAD; + if (ret) { + dev_warn(&priv->wdev->dev, + "failed to refresh capdata on AC change: %d\n", ret); + + /* + * Our error is non-fatal for others, and we still want + * the other event consumers to process it. + */ + return NOTIFY_DONE; + } return NOTIFY_OK; default: @@ -832,10 +1036,11 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) if (!priv) return -ENOMEM; + priv->info = info; priv->wdev = wdev; dev_set_drvdata(&wdev->dev, priv); - ret = lwmi_cd_setup(priv, info->type); + ret = lwmi_cd_setup(priv); if (ret) goto out; @@ -844,7 +1049,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA; struct capdata00 capdata00; - ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00); + ret = priv->list ? lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, + &capdata00) : -ENODATA; if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) { dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n"); sub_component_type = CD_TYPE_NONE; @@ -865,14 +1071,16 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) case LENOVO_CAPABILITY_DATA_01: priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; - ret = register_acpi_notifier(&priv->acpi_nb); - if (ret) - goto out; + if (priv->list) { + ret = register_acpi_notifier(&priv->acpi_nb); + if (ret) + goto out; - ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, - &priv->acpi_nb); - if (ret) - goto out; + ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, + &priv->acpi_nb); + if (ret) + goto out; + } ret = component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; @@ -890,7 +1098,7 @@ out: lwmi_cd_debugfs_add(priv); dev_dbg(&wdev->dev, "registered %s with %u items\n", - info->name, priv->list->count); + info->name, priv->list ? priv->list->count : 0); } return ret; } @@ -901,7 +1109,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) lwmi_cd_debugfs_remove(priv); - switch (priv->list->type) { + switch (priv->info->type) { case LENOVO_CAPABILITY_DATA_00: lwmi_cd_sub_master_del(priv); fallthrough; diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c index 8f5766c391eb..687caa226263 100644 --- a/drivers/platform/x86/lenovo/wmi-helpers.c +++ b/drivers/platform/x86/lenovo/wmi-helpers.c @@ -24,9 +24,10 @@ #include <linux/export.h> #include <linux/module.h> #include <linux/notifier.h> -#include <linux/unaligned.h> #include <linux/wmi.h> +#include <asm/byteorder.h> + #include "wmi-helpers.h" /* Thermal mode notifier chain. */ @@ -42,49 +43,33 @@ static BLOCKING_NOTIFIER_HEAD(tm_chain_head); * @size: Length of the buffer. * @retval: Pointer for the return value to be assigned. * - * Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI - * integer. Validates the return value type and assigns the value to the - * retval pointer. + * Calls the specified WMI method for Lenovo WMI devices that return a WMI + * integer. Validates the return value and assigns it to @retval when it's not + * NULL, otherwise the return value is not validated. * * Return: 0 on success, or an error code. */ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, unsigned char *buf, size_t size, u32 *retval) { - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_buffer input = { size, buf }; - acpi_status status; - - status = wmidev_evaluate_method(wdev, instance, method_id, &input, - &output); - if (ACPI_FAILURE(status)) - return -EIO; - - union acpi_object *ret_obj __free(kfree) = output.pointer; - - if (retval) { - if (!ret_obj) - return -ENODATA; - - switch (ret_obj->type) { - /* - * The ACPI method may simply return a buffer when a u32 - * is expected. This is valid on Windows as its WMI-ACPI - * driver converts everything to a common buffer. - */ - case ACPI_TYPE_BUFFER: - if (ret_obj->buffer.length < sizeof(u32)) - return -ENXIO; - - *retval = get_unaligned_le32(ret_obj->buffer.pointer); - return 0; - case ACPI_TYPE_INTEGER: - *retval = (u32)ret_obj->integer.value; - return 0; - default: - return -ENXIO; - } - } + struct wmi_buffer input = { + .length = size, + .data = buf, + }; + struct wmi_buffer output; + int ret; + + if (!retval) + return wmidev_invoke_procedure(wdev, instance, method_id, &input); + + ret = wmidev_invoke_method(wdev, instance, method_id, &input, + &output, sizeof(__le32)); + if (ret) + return ret; + + __le32 *valp __free(kfree) = output.data; + + *retval = le32_to_cpu(*valp); return 0; }; diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index b175971bb308..cecb89bb08eb 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -1619,11 +1619,14 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) * lenovo-wmi-other master driver, with a callback to collect fan info from * capdata_fan. On success, assign the capability data list pointers to the * driver data struct for later access. These pointers are only valid while the - * capdata interfaces exist. Finally, collect fan info from capdata00 and - * register all firmware attribute groups. Note that the HWMON device is - * registered only if all fan info is collected. Hence, it is not registered - * here. See lwmi_om_fan_info_collect_cd00() and - * lwmi_om_fan_info_collect_cd_fan(). + * capdata interfaces exist. + * + * If capdata00 is bound, collect fan info from capdata00 and register the power + * supply extension. Note that the HWMON device is registered only if all fan + * info is collected. Hence, it is not registered here. See + * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan(). + * + * If capdata01 is bound, register all firmware attribute groups. * * Return: 0 on success, or an error code. */ @@ -1643,16 +1646,19 @@ static int lwmi_om_master_bind(struct device *dev) priv->cd00_list = binder.cd00_list; priv->cd01_list = binder.cd01_list; - if (!priv->cd00_list || !priv->cd01_list) { + if (!priv->cd00_list && !priv->cd01_list) { component_unbind_all(dev, NULL); return -ENODEV; } - lwmi_om_fan_info_collect_cd00(priv); - lwmi_om_psy_ext_init(priv); + if (priv->cd00_list) { + lwmi_om_fan_info_collect_cd00(priv); + lwmi_om_psy_ext_init(priv); + } - lwmi_om_fw_attr_add(priv); + if (priv->cd01_list) + lwmi_om_fw_attr_add(priv); return 0; } diff --git a/drivers/platform/x86/lenovo/yogabook.c b/drivers/platform/x86/lenovo/yogabook.c index 8a91d440724b..f35c307300c2 100644 --- a/drivers/platform/x86/lenovo/yogabook.c +++ b/drivers/platform/x86/lenovo/yogabook.c @@ -120,10 +120,7 @@ static void yogabook_toggle_digitizer_mode(struct yogabook_data *data) if (test_bit(YB_SUSPENDED, &data->flags)) return; - if (test_bit(YB_DIGITIZER_MODE, &data->flags)) - clear_bit(YB_DIGITIZER_MODE, &data->flags); - else - set_bit(YB_DIGITIZER_MODE, &data->flags); + change_bit(YB_DIGITIZER_MODE, &data->flags); /* * We are called from the ACPI core and the driver [un]binding which is @@ -136,10 +133,8 @@ static irqreturn_t yogabook_backside_hall_irq(int irq, void *_data) { struct yogabook_data *data = _data; - if (gpiod_get_value(data->backside_hall_gpio)) - set_bit(YB_TABLET_MODE, &data->flags); - else - clear_bit(YB_TABLET_MODE, &data->flags); + assign_bit(YB_TABLET_MODE, &data->flags, + gpiod_get_value(data->backside_hall_gpio)); schedule_work(&data->work); diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c index 566dfc73cb67..5247c25fcdf6 100644 --- a/drivers/platform/x86/msi-ec.c +++ b/drivers/platform/x86/msi-ec.c @@ -122,6 +122,7 @@ static const char * const ALLOWED_FW_1[] __initconst = { "17F2EMS1.104", "17F2EMS1.106", "17F2EMS1.107", + "16U8EMS2.100", // MSI GL65 Leopard 9SCXK NULL }; @@ -825,6 +826,7 @@ static struct msi_ec_conf CONF9 __initdata = { static const char * const ALLOWED_FW_10[] __initconst = { "1582EMS1.107", // GF66 11UC "1583EMS1.109", // Pulse GL66 12UEK + "1585EMS1.115", // Katana 15 B13VFK NULL }; @@ -1132,6 +1134,7 @@ static struct msi_ec_conf CONF13 __initdata = { }; static const char * const ALLOWED_FW_14[] __initconst = { + "1571EMS1.106", // Creator Z16 A11UE "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) "182LIMS1.108", // Vector A18 HX A9WHG "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG @@ -1279,6 +1282,15 @@ static ssize_t charge_control_threshold_show(u8 offset, if (result < 0) return result; + /* + * The EC may hold an out-of-range value (e.g. 0x80) when no charge + * limit has been set. Report the maximum threshold instead of a + * meaningless (possibly negative) percentage. + */ + if (rdata < conf.charge_control.range_min || + rdata > conf.charge_control.range_max) + rdata = conf.charge_control.range_max; + return sysfs_emit(buf, "%i\n", rdata - offset); } diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index ddef6b78d2fa..0a0c756a5598 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -117,7 +117,7 @@ struct quirk_entry { * Wlan/Bluetooth/3G, those netbook will load the SCM (windows app) to * disable the original Wlan/Bluetooth control by BIOS when user press * fn key, then control Wlan/Bluetooth/3G by SCM (software control by - * OS). Without SCM, user cann't on/off 3G module on those 3G netbook. + * OS). Without SCM, user can't on/off 3G module on those 3G netbook. * On Linux, msi-laptop driver will do the same thing to disable the * original BIOS control, then might need use HAL or other userland * application to do the software control that simulate with SCM. diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index c9db750fe5ae..dc56e8da0cdb 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -33,25 +33,27 @@ MODULE_ALIAS("wmi:" MSIWMI_MSI_EVENT_GUID); MODULE_ALIAS("wmi:" MSIWMI_WIND_EVENT_GUID); enum msi_scancodes { - /* Generic MSI keys (not present on MSI Wind) */ - MSI_KEY_BRIGHTNESSUP = 0xD0, - MSI_KEY_BRIGHTNESSDOWN, - MSI_KEY_VOLUMEUP, - MSI_KEY_VOLUMEDOWN, - MSI_KEY_MUTE, - /* MSI Wind keys */ WIND_KEY_TOUCHPAD = 0x08, /* Fn+F3 touchpad toggle */ - WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */ - WIND_KEY_CAMERA, /* Fn+F6 webcam toggle */ - WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ - WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ - WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ - /* MSI Claw keys */ + WIND_KEY_FN_NB = 0x0b, + WIND_KEY_MICMUTE_NB = 0x19, + WIND_KEY_SETTINGS_NB = 0x1d, CLAW_KEY_VOLUMEDOWN = 0x21, + WIND_KEY_MUTE_NB = 0x23, CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ CLAW_KEY_VOLUMEUP = 0x32, + WIND_KEY_BLUETOOTH = 0x56, /* Fn+F11 Bluetooth toggle */ + WIND_KEY_CAMERA = 0x57, /* Fn+F6 webcam toggle */ CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ + WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ + WIND_KEY_TURBO = 0x60, /* Fn+F10 turbo mode toggle */ + WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ + WIND_KEY_BLUETOOTH_NB = 0x78, + MSI_KEY_BRIGHTNESSUP = 0xd0, + MSI_KEY_BRIGHTNESSDOWN = 0xd1, + MSI_KEY_VOLUMEUP = 0xd2, + MSI_KEY_VOLUMEDOWN = 0xd3, + MSI_KEY_MUTE = 0xd4, }; static struct key_entry msi_wmi_keymap[] = { { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, @@ -66,6 +68,13 @@ static struct key_entry msi_wmi_keymap[] = { { KE_IGNORE, WIND_KEY_CAMERA, {KEY_CAMERA} }, { KE_IGNORE, WIND_KEY_WLAN, {KEY_WLAN} }, + /* These MSI Notebook keys work without WMI. Ignore them to avoid double keycodes */ + { KE_IGNORE, WIND_KEY_FN_NB }, + { KE_IGNORE, WIND_KEY_MICMUTE_NB }, + { KE_IGNORE, WIND_KEY_SETTINGS_NB }, + { KE_IGNORE, WIND_KEY_MUTE_NB }, + { KE_IGNORE, WIND_KEY_BLUETOOTH_NB }, + /* These are unknown WMI events found on MSI Wind */ { KE_IGNORE, 0x00 }, { KE_IGNORE, 0x62 }, @@ -236,9 +245,9 @@ static void msi_wmi_notify(union acpi_object *obj, void *context) } /* Brightness is served via acpi video driver */ - if (key->type == KE_KEY && - (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || - key->code == MSI_KEY_BRIGHTNESSDOWN))) + if (key->type == KE_KEY && !backlight && + (key->code == MSI_KEY_BRIGHTNESSUP || + key->code == MSI_KEY_BRIGHTNESSDOWN)) return; pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c index 34bb17fca148..c4c4babe8e30 100644 --- a/drivers/platform/x86/oxpec.c +++ b/drivers/platform/x86/oxpec.c @@ -50,6 +50,7 @@ enum oxp_board { oxp_x1, oxp_g1_i, oxp_g1_a, + oxp_apex, }; static enum oxp_board board; @@ -93,11 +94,14 @@ static struct device *oxp_dev; #define OXP_X1_CHARGE_LIMIT_REG 0xA3 /* X1 charge limit (%) */ #define OXP_X1_CHARGE_INHIBIT_REG 0xA4 /* X1 bypass charging */ -#define OXP_X1_CHARGE_INHIBIT_MASK_AWAKE 0x01 +#define OXP_APEX_CHARGE_LIMIT_REG 0xE5 /* Apex charge limit (%) */ +#define OXP_APEX_CHARGE_INHIBIT_REG 0xE6 /* Apex bypass charging */ + +#define OXP_CHARGE_INHIBIT_MASK_AWAKE 0x01 /* X1 Mask is 0x0A, F1Pro is 0x02 but the extra bit on the X1 does nothing. */ -#define OXP_X1_CHARGE_INHIBIT_MASK_OFF 0x02 -#define OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS (OXP_X1_CHARGE_INHIBIT_MASK_AWAKE | \ - OXP_X1_CHARGE_INHIBIT_MASK_OFF) +#define OXP_CHARGE_INHIBIT_MASK_OFF 0x02 +#define OXP_CHARGE_INHIBIT_MASK_ALWAYS (OXP_CHARGE_INHIBIT_MASK_AWAKE | \ + OXP_CHARGE_INHIBIT_MASK_OFF) static const struct dmi_system_id dmi_table[] = { { @@ -154,7 +158,7 @@ static const struct dmi_system_id dmi_table[] = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER APEX"), }, - .driver_data = (void *)oxp_fly, + .driver_data = (void *)oxp_apex, }, { .matches = { @@ -222,6 +226,13 @@ static const struct dmi_system_id dmi_table[] = { { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 3"), + }, + .driver_data = (void *)oxp_g1_i, + }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"), }, .driver_data = (void *)oxp_mini_amd_a07, @@ -352,6 +363,7 @@ static umode_t tt_toggle_is_visible(struct kobject *kobj, case oxp_x1: case oxp_g1_i: case oxp_g1_a: + case oxp_apex: return attr->mode; default: break; @@ -381,6 +393,7 @@ static ssize_t tt_toggle_store(struct device *dev, case oxp_fly: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: reg = OXP_TURBO_SWITCH_REG; mask = OXP_TURBO_TAKE_VAL; break; @@ -427,6 +440,7 @@ static ssize_t tt_toggle_show(struct device *dev, case oxp_fly: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: reg = OXP_TURBO_SWITCH_REG; mask = OXP_TURBO_TAKE_VAL; break; @@ -524,6 +538,7 @@ static bool oxp_psy_ext_supported(void) case oxp_g1_i: case oxp_g1_a: case oxp_fly: + case oxp_apex: return true; default: break; @@ -537,12 +552,24 @@ static int oxp_psy_ext_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { + u8 limit_reg, inhibit_reg; long raw_val; int ret; + switch (board) { + case oxp_apex: + limit_reg = OXP_APEX_CHARGE_LIMIT_REG; + inhibit_reg = OXP_APEX_CHARGE_INHIBIT_REG; + break; + default: + limit_reg = OXP_X1_CHARGE_LIMIT_REG; + inhibit_reg = OXP_X1_CHARGE_INHIBIT_REG; + break; + } + switch (psp) { case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: - ret = read_from_ec(OXP_X1_CHARGE_LIMIT_REG, 1, &raw_val); + ret = read_from_ec(limit_reg, 1, &raw_val); if (ret) return ret; if (raw_val < 0 || raw_val > 100) @@ -550,14 +577,14 @@ static int oxp_psy_ext_get_prop(struct power_supply *psy, val->intval = raw_val; return 0; case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: - ret = read_from_ec(OXP_X1_CHARGE_INHIBIT_REG, 1, &raw_val); + ret = read_from_ec(inhibit_reg, 1, &raw_val); if (ret) return ret; - if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS) == - OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS) + if ((raw_val & OXP_CHARGE_INHIBIT_MASK_ALWAYS) == + OXP_CHARGE_INHIBIT_MASK_ALWAYS) val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; - else if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_AWAKE) == - OXP_X1_CHARGE_INHIBIT_MASK_AWAKE) + else if ((raw_val & OXP_CHARGE_INHIBIT_MASK_AWAKE) == + OXP_CHARGE_INHIBIT_MASK_AWAKE) val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE; else val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; @@ -573,29 +600,41 @@ static int oxp_psy_ext_set_prop(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { + u8 limit_reg, inhibit_reg; long raw_val; + switch (board) { + case oxp_apex: + limit_reg = OXP_APEX_CHARGE_LIMIT_REG; + inhibit_reg = OXP_APEX_CHARGE_INHIBIT_REG; + break; + default: + limit_reg = OXP_X1_CHARGE_LIMIT_REG; + inhibit_reg = OXP_X1_CHARGE_INHIBIT_REG; + break; + } + switch (psp) { case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: if (val->intval < 0 || val->intval > 100) return -EINVAL; - return write_to_ec(OXP_X1_CHARGE_LIMIT_REG, val->intval); + return write_to_ec(limit_reg, val->intval); case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: switch (val->intval) { case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: raw_val = 0; break; case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE: - raw_val = OXP_X1_CHARGE_INHIBIT_MASK_AWAKE; + raw_val = OXP_CHARGE_INHIBIT_MASK_AWAKE; break; case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: - raw_val = OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS; + raw_val = OXP_CHARGE_INHIBIT_MASK_ALWAYS; break; default: return -EINVAL; } - return write_to_ec(OXP_X1_CHARGE_INHIBIT_REG, raw_val); + return write_to_ec(inhibit_reg, raw_val); default: return -EINVAL; } @@ -656,6 +695,7 @@ static int oxp_pwm_enable(void) case oxp_x1: case oxp_g1_i: case oxp_g1_a: + case oxp_apex: return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL); default: return -EINVAL; @@ -676,6 +716,7 @@ static int oxp_pwm_disable(void) case oxp_x1: case oxp_g1_i: case oxp_g1_a: + case oxp_apex: return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO); default: return -EINVAL; @@ -696,6 +737,7 @@ static int oxp_pwm_read(long *val) case oxp_x1: case oxp_g1_i: case oxp_g1_a: + case oxp_apex: return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val); default: return -EOPNOTSUPP; @@ -732,6 +774,7 @@ static int oxp_pwm_fan_speed(long *val) case oxp_mini_amd_a07: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: return read_from_ec(OXP_SENSOR_FAN_REG, 2, val); default: return -EOPNOTSUPP; @@ -764,6 +807,7 @@ static int oxp_pwm_input_write(long val) case oxp_fly: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: return write_to_ec(OXP_SENSOR_PWM_REG, val); default: return -EOPNOTSUPP; @@ -803,6 +847,7 @@ static int oxp_pwm_input_read(long *val) case oxp_fly: case oxp_mini_amd_pro: case oxp_g1_a: + case oxp_apex: default: ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val); if (ret) diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 19d194ff37ca..2b1d3c2eb2e5 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -107,7 +107,7 @@ * * Jul.25, 2004 Hiroshi Miura <miura@da-cha.org> * - v0.4 first post version - * add function to retrive SIFR + * add function to retrieve SIFR * * Jul.24, 2004 Hiroshi Miura <miura@da-cha.org> * - v0.3 get proper status of hotkey @@ -232,6 +232,7 @@ static const struct key_entry panasonic_keymap[] = { { KE_KEY, 41, { KEY_MACRO9 } }, { KE_KEY, 42, { KEY_MACRO10 } }, { KE_KEY, 43, { KEY_MACRO11 } }, + { KE_KEY, 48, { KEY_FN } }, { KE_END, 0 } }; @@ -864,6 +865,17 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) } /* + * Let's Note models report both FN key down and key up + * without the expected 0x80 key up/down bit + */ + if (key == 48) { + updown = 1; + } else if (key == 49) { + key = 48; + updown = 0; + } + + /* * Don't report brightness key-presses if they are also reported * by the ACPI video bus. */ diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index 710f3d5bf84c..18631e96bb17 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -295,7 +295,7 @@ static const struct sabi_config sabi_configs[] = { * d0, d1, d2, d3 - data fields * call - call SABI using command and data * - * This allow to call arbitrary sabi commands wihout + * This allow to call arbitrary sabi commands without * modifying the driver at all. * For example, setting the keyboard backlight brightness to 5 * diff --git a/drivers/platform/x86/siemens/simatic-ipc.c b/drivers/platform/x86/siemens/simatic-ipc.c index 7039874d8f11..4a4509fc9263 100644 --- a/drivers/platform/x86/siemens/simatic-ipc.c +++ b/drivers/platform/x86/siemens/simatic-ipc.c @@ -92,6 +92,7 @@ static int register_platform_devices(u32 station_id) u8 wdtmode = SIMATIC_IPC_DEVICE_NONE; u8 battmode = SIMATIC_IPC_DEVICE_NONE; char *pdevname; + int ret; int i; for (i = 0; i < ARRAY_SIZE(device_modes); i++) { @@ -139,8 +140,10 @@ static int register_platform_devices(u32 station_id) pdevname, PLATFORM_DEVID_NONE, &platform_data, sizeof(struct simatic_ipc_platform)); - if (IS_ERR(ipc_led_platform_device)) - return PTR_ERR(ipc_led_platform_device); + if (IS_ERR(ipc_led_platform_device)) { + ret = PTR_ERR(ipc_led_platform_device); + goto err_unregister_batt; + } pr_debug("device=%s created\n", ipc_led_platform_device->name); @@ -153,8 +156,10 @@ static int register_platform_devices(u32 station_id) KBUILD_MODNAME "_wdt", PLATFORM_DEVID_NONE, &platform_data, sizeof(struct simatic_ipc_platform)); - if (IS_ERR(ipc_wdt_platform_device)) - return PTR_ERR(ipc_wdt_platform_device); + if (IS_ERR(ipc_wdt_platform_device)) { + ret = PTR_ERR(ipc_wdt_platform_device); + goto err_unregister_led; + } pr_debug("device=%s created\n", ipc_wdt_platform_device->name); @@ -169,6 +174,14 @@ static int register_platform_devices(u32 station_id) } return 0; + +err_unregister_led: + if (ledmode != SIMATIC_IPC_DEVICE_NONE) + platform_device_unregister(ipc_led_platform_device); +err_unregister_batt: + if (battmode != SIMATIC_IPC_DEVICE_NONE) + platform_device_unregister(ipc_batt_platform_device); + return ret; } static void request_additional_modules(u32 station_id) diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c index 0e842c55dcc5..730a599abf0f 100644 --- a/drivers/platform/x86/topstar-laptop.c +++ b/drivers/platform/x86/topstar-laptop.c @@ -287,10 +287,14 @@ static const struct dmi_system_id topstar_dmi_ids[] = { static int topstar_acpi_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + struct acpi_device *device; struct topstar_laptop *topstar; int err; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + dmi_check_system(topstar_dmi_ids); topstar = kzalloc_obj(struct topstar_laptop); diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index c6633b74029f..b2eb91929d1d 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -244,18 +244,13 @@ static int toshiba_haps_suspend(struct device *device) static int toshiba_haps_resume(struct device *device) { struct toshiba_haps_dev *haps = dev_get_drvdata(device); - int ret; /* Set the stored protection level */ - ret = toshiba_haps_protection_level(haps->acpi_dev->handle, - haps->protection_level); + toshiba_haps_protection_level(haps->acpi_dev->handle, + haps->protection_level); /* Reset the protection on resume */ - ret = toshiba_haps_reset_protection(haps->acpi_dev->handle); - if (ret != 0) - return ret; - - return ret; + return toshiba_haps_reset_protection(haps->acpi_dev->handle); } #endif diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c index 32d7756022c2..3e5ba524fe58 100644 --- a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c +++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c @@ -1,9 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * This driver implements the WMI AB device found on TUXEDO notebooks with board - * vendor NB04. + * vendor NB04. This enables keyboard backlight control via a virtual HID + * LampArray device. + * + * The device will be available through the regular HID interfaces, such as + * hidraw and can be used by any userspace program that implements the HID + * LampArray standard. * * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com> + * Copyright (C) 2026 Aaron Erhardt <aer@tuxedocomputers.com> */ #include <linux/dmi.h> @@ -232,8 +238,8 @@ static const struct tux_kbl_map_entry_t sirius_16_ansii_kbl_map[] = { { 0x2e, { 246000, 67500, 5250 } }, { 0x2a, { 269500, 67500, 5250 } }, { 0x53, { 294500, 67500, 5250 } }, - { 0x55, { 311200, 67500, 5250 } }, - { 0x54, { 327900, 67500, 5250 } }, + { 0x54, { 311200, 67500, 5250 } }, + { 0x55, { 327900, 67500, 5250 } }, { 0x56, { 344600, 67500, 5250 } }, { 0x2b, { 31000, 85500, 5500 } }, { 0x14, { 51500, 85500, 5500 } }, @@ -337,8 +343,8 @@ static const struct tux_kbl_map_entry_t sirius_16_iso_kbl_map[] = { { 0x2e, { 246000, 67500, 5250 } }, { 0x2a, { 269500, 67500, 5250 } }, { 0x53, { 294500, 67500, 5250 } }, - { 0x55, { 311200, 67500, 5250 } }, - { 0x54, { 327900, 67500, 5250 } }, + { 0x54, { 311200, 67500, 5250 } }, + { 0x55, { 327900, 67500, 5250 } }, { 0x56, { 344600, 67500, 5250 } }, { 0x2b, { 31000, 85500, 5500 } }, { 0x14, { 51500, 85500, 5500 } }, @@ -448,7 +454,7 @@ static int tux_ll_start(struct hid_device *hdev) } driver_data->next_lamp_id = 0; - dev_set_drvdata(&hdev->dev, driver_data); + hdev->driver_data = driver_data; return ret; } @@ -485,15 +491,17 @@ struct __packed lamp_array_attributes_report_t { static int handle_lamp_array_attributes_report(struct hid_device *hdev, struct lamp_array_attributes_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; rep->lamp_count = driver_data->lamp_count; + + // Physical dimensions of the Sirius 16 keyboard rep->bounding_box_width_in_micrometers = 368000; rep->bounding_box_height_in_micrometers = 266000; rep->bounding_box_depth_in_micrometers = 30000; /* * LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of - * "HID Usage Tables v1.5" + * "HID Usage Tables v1.7" */ rep->lamp_array_kind = 1; // Some guessed value for interval microseconds @@ -510,7 +518,7 @@ struct __packed lamp_attributes_request_report_t { static int handle_lamp_attributes_request_report(struct hid_device *hdev, struct lamp_attributes_request_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; if (rep->lamp_id < driver_data->lamp_count) driver_data->next_lamp_id = rep->lamp_id; @@ -539,7 +547,7 @@ struct __packed lamp_attributes_response_report_t { static int handle_lamp_attributes_response_report(struct hid_device *hdev, struct lamp_attributes_response_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; u16 lamp_id = driver_data->next_lamp_id; rep->lamp_id = lamp_id; @@ -547,21 +555,21 @@ static int handle_lamp_attributes_response_report(struct hid_device *hdev, rep->update_latency_in_microseconds = 100; /* * LampPurposeControl, see "26.3.1 LampPurposes Flags" of - * "HID Usage Tables v1.5" + * "HID Usage Tables v1.7" */ rep->lamp_purpose = 1; rep->red_level_count = 0xff; rep->green_level_count = 0xff; rep->blue_level_count = 0xff; - rep->intensity_level_count = 0xff; + rep->intensity_level_count = 0x1; rep->is_programmable = 1; if (driver_data->kbl_map[lamp_id].code <= 0xe8) { rep->input_binding = driver_data->kbl_map[lamp_id].code; } else { /* - * Everything bigger is reserved/undefined, see - * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.5" + * Everything bigger than 0xe8 is reserved/undefined, see + * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.7" * and should return 0, see "26.8.3 Lamp Attributes" of the same * document. */ @@ -598,7 +606,7 @@ struct __packed lamp_multi_update_report_t { static int handle_lamp_multi_update_report(struct hid_device *hdev, struct lamp_multi_update_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; union tux_wmi_xx_496in_80out_in_t *next = &driver_data->next_kbl_set_multiple_keys_in; struct tux_kbl_set_multiple_keys_in_rgb_config_t *rgb_configs_j; struct wmi_device *wdev = to_wmi_device(hdev->dev.parent); @@ -606,10 +614,7 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev, u8 key_id, key_id_j, intensity_i, red_i, green_i, blue_i; int ret; - /* - * Catching misformatted lamp_multi_update_report and fail silently - * according to "HID Usage Tables v1.5" - */ + // Catch bad reports and fail silently according to "HID Usage Tables v1.7" for (unsigned int i = 0; i < rep->lamp_count; ++i) { if (rep->lamp_id[i] > driver_data->lamp_count) { hid_dbg(hdev, "Out of bounds lamp_id in lamp_multi_update_report. Skipping whole report!\n"); @@ -624,6 +629,7 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev, } } + // Fill kbl_set_multiple_keys_in update buffer for (unsigned int i = 0; i < rep->lamp_count; ++i) { key_id = driver_data->kbl_map[rep->lamp_id[i]].code; @@ -632,6 +638,8 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev, ++j) { rgb_configs_j = &next->kbl_set_multiple_keys_in.rgb_configs[j]; key_id_j = rgb_configs_j->key_id; + + // Search for existing or empty entry if (key_id_j != 0x00 && key_id_j != key_id) continue; @@ -640,22 +648,23 @@ static int handle_lamp_multi_update_report(struct hid_device *hdev, j + 1; rgb_configs_j->key_id = key_id; /* - * While this driver respects update_channel.intensity - * according to "HID Usage Tables v1.5" also on RGB - * leds, the Microsoft MacroPad reference implementation + * This driver uses update_channel.intensity according to + * "Color Attributes Examples" in "HID Usage Tables v1.7". + * Only two intensity values are allowed for turning LEDs + * on or off, while color and brightness can be controlled + * through the RGB values. This is also identical to the + * Microsoft MacroPad reference implementation * (https://github.com/microsoft/RP2040MacropadHidSample - * 1d6c3ad) does not and ignores it. If it turns out - * that Windows writes intensity = 0 for RGB leds - * instead of intensity = 255, this driver should also - * ignore the update_channel.intensity. + * 1d6c3ad). */ - intensity_i = rep->update_channels[i].intensity; + intensity_i = min(1, rep->update_channels[i].intensity); red_i = rep->update_channels[i].red; green_i = rep->update_channels[i].green; blue_i = rep->update_channels[i].blue; - rgb_configs_j->red = red_i * intensity_i / 0xff; - rgb_configs_j->green = green_i * intensity_i / 0xff; - rgb_configs_j->blue = blue_i * intensity_i / 0xff; + + rgb_configs_j->red = red_i * intensity_i; + rgb_configs_j->green = green_i * intensity_i; + rgb_configs_j->blue = blue_i * intensity_i; break; } @@ -683,17 +692,14 @@ struct __packed lamp_range_update_report_t { static int handle_lamp_range_update_report(struct hid_device *hdev, struct lamp_range_update_report_t *rep) { - struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev); + struct tux_hdev_driver_data_t *driver_data = hdev->driver_data; struct lamp_multi_update_report_t lamp_multi_update_report = { .report_id = LAMP_MULTI_UPDATE_REPORT_ID, }; struct lamp_rgbi_tuple_t *update_channels_j; int ret; - /* - * Catching misformatted lamp_range_update_report and fail silently - * according to "HID Usage Tables v1.5" - */ + // Catch bad reports and fail silently according to "HID Usage Tables v1.7" if (rep->lamp_id_start > rep->lamp_id_end) { hid_dbg(hdev, "lamp_id_start > lamp_id_end in lamp_range_update_report. Skipping whole report!\n"); return sizeof(*rep); @@ -705,8 +711,8 @@ static int handle_lamp_range_update_report(struct hid_device *hdev, } /* - * Break handle_lamp_range_update_report call down to multiple - * handle_lamp_multi_update_report calls to easily ensure that mixing + * Break handle_lamp_range_update_report call down into multiple + * handle_lamp_multi_update_report calls to ensure that mixing * handle_lamp_range_update_report and handle_lamp_multi_update_report * does not break things. */ @@ -749,15 +755,12 @@ static int handle_lamp_array_control_report(struct hid_device *hdev __always_unu struct lamp_array_control_report_t *rep) { /* - * The keyboards firmware doesn't have any built in controls and the - * built in effects are not implemented so this is a NOOP. - * According to the HID Documentation (HID Usage Tables v1.5) this + * The keyboard's firmware doesn't have any built-in controls and the + * built-in effects are not implemented so this is a NOOP. + * According to the HID Documentation (HID Usage Tables v1.7) this * function is optional and can be removed from the HID Report * Descriptor, but it should first be confirmed that userspace respects - * this possibility too. The Microsoft MacroPad reference implementation - * (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad) - * already deviates from the spec at another point, see - * handle_lamp_*_update_report. + * this possibility too. */ return sizeof(*rep); @@ -766,6 +769,8 @@ static int handle_lamp_array_control_report(struct hid_device *hdev __always_unu static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf, size_t len, unsigned char rtype, int reqtype) { + int ret = -EINVAL; + if (rtype != HID_FEATURE_REPORT) return -EINVAL; @@ -775,13 +780,15 @@ static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf, case LAMP_ARRAY_ATTRIBUTES_REPORT_ID: if (len != sizeof(struct lamp_array_attributes_report_t)) return -EINVAL; - return handle_lamp_array_attributes_report(hdev, + ret = handle_lamp_array_attributes_report(hdev, (struct lamp_array_attributes_report_t *)buf); + break; case LAMP_ATTRIBUTES_RESPONSE_REPORT_ID: if (len != sizeof(struct lamp_attributes_response_report_t)) return -EINVAL; - return handle_lamp_attributes_response_report(hdev, + ret = handle_lamp_attributes_response_report(hdev, (struct lamp_attributes_response_report_t *)buf); + break; } break; case HID_REQ_SET_REPORT: @@ -789,28 +796,36 @@ static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf, case LAMP_ATTRIBUTES_REQUEST_REPORT_ID: if (len != sizeof(struct lamp_attributes_request_report_t)) return -EINVAL; - return handle_lamp_attributes_request_report(hdev, + ret = handle_lamp_attributes_request_report(hdev, (struct lamp_attributes_request_report_t *)buf); + break; case LAMP_MULTI_UPDATE_REPORT_ID: if (len != sizeof(struct lamp_multi_update_report_t)) return -EINVAL; - return handle_lamp_multi_update_report(hdev, + ret = handle_lamp_multi_update_report(hdev, (struct lamp_multi_update_report_t *)buf); + break; case LAMP_RANGE_UPDATE_REPORT_ID: if (len != sizeof(struct lamp_range_update_report_t)) return -EINVAL; - return handle_lamp_range_update_report(hdev, + ret = handle_lamp_range_update_report(hdev, (struct lamp_range_update_report_t *)buf); + break; case LAMP_ARRAY_CONTROL_REPORT_ID: if (len != sizeof(struct lamp_array_control_report_t)) return -EINVAL; - return handle_lamp_array_control_report(hdev, + ret = handle_lamp_array_control_report(hdev, (struct lamp_array_control_report_t *)buf); + break; } break; } - return -EINVAL; + /* Set report number on success */ + if (ret > 0) + buf[0] = reportnum; + + return ret; } static const struct hid_ll_driver tux_ll_driver = { @@ -881,8 +896,8 @@ static struct wmi_driver tuxedo_nb04_wmi_tux_driver = { }; /* - * We don't know if the WMI API is stable and how unique the GUID is for this - * ODM. To be on the safe side we therefore only run this driver on tested + * We don't know whether the WMI API is stable and how unique the GUID is for + * this ODM. To be on the safe side we therefore only run this driver on tested * devices defined by this list. */ static const struct dmi_system_id tested_devices_dmi_table[] __initconst = { @@ -920,4 +935,5 @@ module_exit(tuxedo_nb04_wmi_tux_exit); MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices"); MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>"); +MODULE_AUTHOR("Aaron Erhardt <aer@tuxedocomputers.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/uniwill/Kconfig b/drivers/platform/x86/uniwill/Kconfig index d07cc8440188..f80794bdfaef 100644 --- a/drivers/platform/x86/uniwill/Kconfig +++ b/drivers/platform/x86/uniwill/Kconfig @@ -28,6 +28,7 @@ config UNIWILL_LAPTOP depends on DMI select REGMAP select INPUT_SPARSEKMAP + select LEDS_TRIGGERS help This driver adds support for various extra features found on Uniwill laptops, like the lightbar, hwmon sensors and hotkeys. It also supports many OEM laptops diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 7a2eeaec4c96..f62d59f29676 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -165,9 +165,10 @@ #define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748 #define LIGHTBAR_APP_EXISTS BIT(0) #define LIGHTBAR_POWER_SAVE BIT(1) -#define LIGHTBAR_S0_OFF BIT(2) -#define LIGHTBAR_S3_OFF BIT(3) // Breathing animation when suspended -#define LIGHTBAR_WELCOME BIT(7) // Rainbow animation +#define LIGHTBAR_S0_OFF BIT(2) /* Also disables animations */ +#define LIGHTBAR_S3_OFF BIT(3) /* No breathing animation when in s2ram */ +#define LIGHTBAR_MODERN_STANDBY_ON BIT(6) /* Breathing animation when in s2idle */ +#define LIGHTBAR_WELCOME BIT(7) /* Rainbow animation */ #define EC_ADDR_LIGHTBAR_AC_RED 0x0749 @@ -306,7 +307,7 @@ #define EC_ADDR_USB_C_POWER_PRIORITY 0x07CC #define USB_C_POWER_PRIORITY BIT(7) -/* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF */ +/* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF and LIGHTBAR_MODERN_STANDBY_ON */ #define EC_ADDR_LIGHTBAR_BAT_CTRL 0x07E2 #define EC_ADDR_LIGHTBAR_BAT_RED 0x07E3 @@ -398,7 +399,7 @@ struct uniwill_data { struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ struct list_head batteries; struct mutex led_lock; /* Protects writes to the lightbar registers */ - u8 lightbar_max_brightness; + u8 lightbar_max_intensity; struct led_classdev_mc led_mc_cdev; struct mc_subled led_mc_subled_info[LED_CHANNELS]; bool kbd_led_single_color; @@ -429,7 +430,7 @@ struct uniwill_device_descriptor { unsigned int features; bool kbd_led_single_color; u8 kbd_led_max_brightness; - u8 lightbar_max_brightness; + u8 lightbar_max_intensity; /* Executed during driver probing */ int (*probe)(struct uniwill_data *data); }; @@ -506,6 +507,9 @@ static const struct key_entry uniwill_keymap[] = { /* Reported when the user wants to toggle the webcam */ { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, + /* Reported when the user wants to rotate the (lay-flat) screen by 180 degrees */ + { KE_KEY, UNIWILL_OSD_ROTATE_SCREEN, { KEY_ROTATE_DISPLAY }}, + { KE_END } }; @@ -645,6 +649,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_1: case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: @@ -684,6 +689,7 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) case EC_ADDR_BIOS_OEM: case EC_ADDR_PWM_1: case EC_ADDR_PWM_2: + case EC_ADDR_SUPPORT_1: case EC_ADDR_SUPPORT_2: case EC_ADDR_TRIGGER: case EC_ADDR_SWITCH_STATUS: @@ -898,51 +904,6 @@ static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_att static DEVICE_ATTR_RW(touchpad_toggle_enable); -static ssize_t rainbow_animation_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct uniwill_data *data = dev_get_drvdata(dev); - unsigned int value; - bool enable; - int ret; - - ret = kstrtobool(buf, &enable); - if (ret < 0) - return ret; - - if (enable) - value = LIGHTBAR_WELCOME; - else - value = 0; - - guard(mutex)(&data->led_lock); - - ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_WELCOME, value); - if (ret < 0) - return ret; - - ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_WELCOME, value); - if (ret < 0) - return ret; - - return count; -} - -static ssize_t rainbow_animation_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct uniwill_data *data = dev_get_drvdata(dev); - unsigned int value; - int ret; - - ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value); - if (ret < 0) - return ret; - - return sysfs_emit(buf, "%d\n", !!(value & LIGHTBAR_WELCOME)); -} - -static DEVICE_ATTR_RW(rainbow_animation); - static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -956,12 +917,13 @@ static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attr return ret; if (enable) - value = 0; + value = LIGHTBAR_MODERN_STANDBY_ON; else value = LIGHTBAR_S3_OFF; /* We only access a single register here, so we do not need to use data->led_lock */ - ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S3_OFF, value); + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, + LIGHTBAR_S3_OFF | LIGHTBAR_MODERN_STANDBY_ON, value); if (ret < 0) return ret; @@ -979,6 +941,10 @@ static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attri if (ret < 0) return ret; + /* + * We only test LIGHTBAR_S3_OFF here, because LIGHTBAR_MODERN_STANDBY_ON + * should have the exact opposite value. + */ return sysfs_emit(buf, "%d\n", !(value & LIGHTBAR_S3_OFF)); } @@ -1256,7 +1222,6 @@ static struct attribute *uniwill_attrs[] = { &dev_attr_super_key_enable.attr, &dev_attr_touchpad_toggle_enable.attr, /* Lightbar-related */ - &dev_attr_rainbow_animation.attr, &dev_attr_breathing_in_suspend.attr, /* Power-management-related */ &dev_attr_ctgp_offset.attr, @@ -1286,8 +1251,7 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a return attr->mode; } - if (attr == &dev_attr_rainbow_animation.attr || - attr == &dev_attr_breathing_in_suspend.attr) { + if (attr == &dev_attr_breathing_in_suspend.attr) { if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR)) return attr->mode; } @@ -1509,20 +1473,16 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br unsigned int value; int ret; - ret = led_mc_calc_color_components(led_mc_cdev, brightness); - if (ret < 0) - return ret; - guard(mutex)(&data->led_lock); for (int i = 0; i < LED_CHANNELS; i++) { - /* Prevent the brightness values from overflowing */ - value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); - ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], + data->led_mc_subled_info[i].intensity); if (ret < 0) return ret; - ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value); + ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], + data->led_mc_subled_info[i].intensity); if (ret < 0) return ret; } @@ -1539,13 +1499,67 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br return regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_S0_OFF, value); } -#define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | LIGHTBAR_WELCOME) +static int uniwill_led_hw_control_set(struct led_classdev *led_cdev, unsigned long flags) +{ + struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); + struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, led_mc_cdev); + unsigned int value; + int ret; + + guard(mutex)(&data->led_lock); + + if (flags) + value = LIGHTBAR_WELCOME; + else + value = LIGHTBAR_S0_OFF; + + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, + LIGHTBAR_S0_OFF | LIGHTBAR_WELCOME, value); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, + LIGHTBAR_S0_OFF | LIGHTBAR_WELCOME, value); + if (ret < 0) + return ret; + + /* The LED is always on during hw control */ + led_cdev->brightness = flags; + + return 0; +} + +static int uniwill_lightbar_trigger_activate(struct led_classdev *led_cdev) +{ + return led_cdev->hw_control_set(led_cdev, 1); +} + +static void uniwill_lightbar_trigger_deactivate(struct led_classdev *led_cdev) +{ + int ret; + + ret = led_cdev->hw_control_set(led_cdev, 0); + if (ret < 0) + dev_err(led_cdev->dev, "Failed to stop rainbow animation: %d\n", ret); +} + +static struct led_hw_trigger_type uniwill_lightbar_trigger_type; + +static struct led_trigger uniwill_lightbar_trigger = { + .name = "uniwill-rainbow", + .activate = uniwill_lightbar_trigger_activate, + .deactivate = uniwill_lightbar_trigger_deactivate, + .trigger_type = &uniwill_lightbar_trigger_type, +}; + +#define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | \ + LIGHTBAR_MODERN_STANDBY_ON | LIGHTBAR_WELCOME) static int uniwill_led_init(struct uniwill_data *data) { struct led_init_data init_data = { .devicename = DRIVER_NAME, - .default_label = "multicolor:" LED_FUNCTION_STATUS, + .default_label = "rgb:" LED_FUNCTION_STATUS, .devname_mandatory = true, }; unsigned int color_indices[3] = { @@ -1572,6 +1586,11 @@ static int uniwill_led_init(struct uniwill_data *data) return ret; value |= LIGHTBAR_APP_EXISTS; + if (value & LIGHTBAR_S3_OFF) + value &= ~LIGHTBAR_MODERN_STANDBY_ON; + else + value |= LIGHTBAR_MODERN_STANDBY_ON; + ret = regmap_write(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, value); if (ret < 0) return ret; @@ -1581,19 +1600,26 @@ static int uniwill_led_init(struct uniwill_data *data) * running on battery power. */ value |= LIGHTBAR_S3_OFF; + value &= ~LIGHTBAR_MODERN_STANDBY_ON; ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_MASK, value); if (ret < 0) return ret; - data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; - data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; + data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_RGB; + data->led_mc_cdev.led_cdev.max_brightness = 1; data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; + data->led_mc_cdev.led_cdev.trigger_type = &uniwill_lightbar_trigger_type; + data->led_mc_cdev.led_cdev.hw_control_trigger = uniwill_lightbar_trigger.name; + data->led_mc_cdev.led_cdev.hw_control_set = uniwill_led_hw_control_set; if (value & LIGHTBAR_S0_OFF) data->led_mc_cdev.led_cdev.brightness = 0; else - data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; + data->led_mc_cdev.led_cdev.brightness = 1; + + if (value & LIGHTBAR_WELCOME) + data->led_mc_cdev.led_cdev.default_trigger = uniwill_lightbar_trigger.name; for (int i = 0; i < LED_CHANNELS; i++) { data->led_mc_subled_info[i].color_index = color_indices[i]; @@ -1604,9 +1630,9 @@ static int uniwill_led_init(struct uniwill_data *data) /* * Make sure that the initial intensity value is not greater than - * the maximum brightness. + * the maximum intensity. */ - value = min(data->lightbar_max_brightness, value); + value = min(data->lightbar_max_intensity, value); ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); if (ret < 0) return ret; @@ -1616,6 +1642,7 @@ static int uniwill_led_init(struct uniwill_data *data) return ret; data->led_mc_subled_info[i].intensity = value; + data->led_mc_subled_info[i].max_intensity = data->lightbar_max_intensity; data->led_mc_subled_info[i].channel = i; } @@ -1631,6 +1658,32 @@ static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) struct led_classdev *led_cdev; int ret; + /* + * The devices with 3 brightness level keyboards send + * UNIWILL_OSD_KB_LED_LEVEL0, UNIWILL_OSD_KB_LED_LEVEL2, and + * UNIWILL_OSD_KB_LED_LEVEL4 which need to be mapped to 0, 1, and 2. + */ + switch (data->kbd_led_max_brightness) { + case 4: + break; + case 2: + switch (brightness) { + case 0: + break; + case 2: + brightness = 1; + break; + case 4: + brightness = 2; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + if (data->kbd_led_single_color) led_cdev = &data->kbd_led_cdev; else @@ -1705,7 +1758,7 @@ static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, /* * The EC interprets a RGB value of 0x000000 as a command to restore - * the device-specfic default RGB value. Work around this by writing + * the device-specific default RGB value. Work around this by writing * a RGB value of 0x010101 (faint white) instead. */ if (data->kbd_led_mc_subled_info[0].intensity == 0 && @@ -1760,7 +1813,7 @@ static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) LED_COLOR_ID_BLUE, }; struct led_init_data init_data = { - .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT, + .default_label = "rgb:" LED_FUNCTION_KBD_BACKLIGHT, .devicename = DRIVER_NAME, .devname_mandatory = true, }; @@ -1821,7 +1874,7 @@ static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) return ret; data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; - data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; + data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_RGB; data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; @@ -2338,7 +2391,7 @@ static int uniwill_probe(struct platform_device *pdev) data->features = device_descriptor.features; data->kbd_led_single_color = device_descriptor.kbd_led_single_color; data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; - data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; + data->lightbar_max_intensity = device_descriptor.lightbar_max_intensity; /* * Some devices might need to perform some device-specific initialization steps @@ -2685,7 +2738,7 @@ static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, - .lightbar_max_brightness = 36, + .lightbar_max_intensity = 36, }; static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { @@ -2699,6 +2752,20 @@ static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { UNIWILL_FEATURE_SECONDARY_FAN, }; +static struct uniwill_device_descriptor lapkc71e_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_TOUCHPAD_TOGGLE | + UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + .lightbar_max_intensity = 200, +}; + static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | @@ -2709,40 +2776,98 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN, - .lightbar_max_brightness = 200, + .lightbar_max_intensity = 200, }; -/* - * The featuresets below reflect somewhat chronological changes: - * 1 -> 2: UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL is added to the EC firmware. - * 2 -> 3: UNIWILL_FEATURE_USB_C_POWER_PRIORITY is removed from the EC firmware. - * Some devices might divert from this timeline. - */ +static struct uniwill_device_descriptor pfxnuxx_pfxluxx_phxprxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, +}; -static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = { +static struct uniwill_device_descriptor gkxmrxx_gkxnpxx_gkxnrxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | - UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, }; -static struct uniwill_device_descriptor tux_featureset_1_nvidia_descriptor __initdata = { +static struct uniwill_device_descriptor gmxmpxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + .lightbar_max_intensity = 36, +}; + +static struct uniwill_device_descriptor phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, +}; + +static struct uniwill_device_descriptor gmxmgxx_gmxngxx_gmxxgxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, +}; + +static struct uniwill_device_descriptor gmxzgxx_gmxtgxx_pol_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, }; -static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __initdata = { +static struct uniwill_device_descriptor gmxzgxx_gmxtgxx_ste_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_GPU_TEMP | @@ -2750,20 +2875,73 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + .lightbar_max_intensity = 36, }; -static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = { +static int gmxrgxx_probe(struct uniwill_data *data) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_1, &value); + if (ret < 0) + return ret; + + if (value & LIGHTBAR) { + data->features |= UNIWILL_FEATURE_LIGHTBAR; + data->lightbar_max_intensity = 36; + } + + ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, &value); + if (ret < 0) + return ret; + + if (value & RGB_KEYBOARD) { + data->features |= UNIWILL_FEATURE_KEYBOARD_BACKLIGHT; + data->kbd_led_single_color = false; + data->kbd_led_max_brightness = 4; + } + + return 0; +}; + +static struct uniwill_device_descriptor gmxrgxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + .probe = gmxrgxx_probe, +}; + +static struct uniwill_device_descriptor gmxagxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_LIGHTBAR | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_GPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + .lightbar_max_intensity = 36, }; -static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = { +static struct uniwill_device_descriptor gmxxgxx_gmxpxxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, +}; + +static struct uniwill_device_descriptor gxxhrxx_gxxmrxx_xxxx4nxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | @@ -2771,10 +2949,29 @@ static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | UNIWILL_FEATURE_AC_AUTO_BOOT | - UNIWILL_FEATURE_USB_POWERSHARE, + UNIWILL_FEATURE_USB_POWERSHARE | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 4, }; -static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = { +static struct uniwill_device_descriptor gmxhgxx_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | + UNIWILL_FEATURE_BATTERY_CHARGE_MODES | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN | + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | + UNIWILL_FEATURE_AC_AUTO_BOOT | + UNIWILL_FEATURE_USB_POWERSHARE | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = false, + .kbd_led_max_brightness = 4, +}; + +static struct uniwill_device_descriptor gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | @@ -2787,7 +2984,7 @@ static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __ini UNIWILL_FEATURE_USB_POWERSHARE, }; -static int phxtxx1_probe(struct uniwill_data *data) +static int phxtuxx_phxtrxx_probe(struct uniwill_data *data) { unsigned int value; int ret; @@ -2802,17 +2999,20 @@ static int phxtxx1_probe(struct uniwill_data *data) return 0; }; -static struct uniwill_device_descriptor phxtxx1_descriptor __initdata = { +static struct uniwill_device_descriptor phxtuxx_phxtrxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | - UNIWILL_FEATURE_USB_C_POWER_PRIORITY, - .probe = phxtxx1_probe, + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, + .probe = phxtuxx_phxtrxx_probe, }; -static int phxarx1_phxaqf1_probe(struct uniwill_data *data) +static int phxarxx_phxaqxx_probe(struct uniwill_data *data) { unsigned int value; int ret; @@ -2828,22 +3028,28 @@ static int phxarx1_phxaqf1_probe(struct uniwill_data *data) return 0; }; -static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = { +static struct uniwill_device_descriptor phxarxx_phxaqxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_BATTERY_CHARGE_MODES | UNIWILL_FEATURE_CPU_TEMP | UNIWILL_FEATURE_PRIMARY_FAN | UNIWILL_FEATURE_SECONDARY_FAN | - UNIWILL_FEATURE_USB_C_POWER_PRIORITY, - .probe = phxarx1_phxaqf1_probe, + UNIWILL_FEATURE_USB_C_POWER_PRIORITY | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, + .probe = phxarxx_phxaqxx_probe, }; -static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { +static struct uniwill_device_descriptor pfxpuxx_descriptor __initdata = { .features = UNIWILL_FEATURE_FN_LOCK | UNIWILL_FEATURE_SUPER_KEY | UNIWILL_FEATURE_CPU_TEMP | - UNIWILL_FEATURE_PRIMARY_FAN, + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_KEYBOARD_BACKLIGHT, + .kbd_led_single_color = true, + .kbd_led_max_brightness = 2, }; static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { @@ -2928,6 +3134,14 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { .ident = "Intel NUC x15", .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71E"), + }, + .driver_data = &lapkc71e_descriptor, + }, + { + .ident = "Intel NUC x15", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"), }, .driver_data = &lapkc71f_descriptor, @@ -2938,7 +3152,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"), }, - .driver_data = &phxtxx1_descriptor, + .driver_data = &phxtuxx_phxtrxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel", @@ -2946,7 +3160,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel", @@ -2954,7 +3168,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"), }, - .driver_data = &phxarx1_phxaqf1_descriptor, + .driver_data = &phxarxx_phxaqxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7", @@ -2962,7 +3176,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", @@ -2970,7 +3184,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8", @@ -2978,7 +3192,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel", @@ -2986,7 +3200,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &phxtqxx_phxagxx_phxaqxx_phxpgxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD", @@ -2994,7 +3208,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", @@ -3002,7 +3216,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", @@ -3010,7 +3224,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", @@ -3018,7 +3232,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", @@ -3026,7 +3240,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), }, - .driver_data = &tux_featureset_4_descriptor, + .driver_data = &gxxhrxx_gxxmrxx_xxxx4nxx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", @@ -3034,7 +3248,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", @@ -3042,7 +3256,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", @@ -3050,7 +3264,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", @@ -3058,7 +3272,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", @@ -3066,7 +3280,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", @@ -3074,7 +3288,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 AMD", @@ -3082,7 +3296,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 AMD", @@ -3090,7 +3304,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 AMD", @@ -3098,7 +3312,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 Intel", @@ -3106,7 +3320,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 15 Gen1 Intel", @@ -3114,7 +3328,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 Intel", @@ -3122,7 +3336,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Polaris 17 Gen1 Intel", @@ -3130,7 +3344,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gkxmrxx_gkxnpxx_gkxnrxx_descriptor, }, { .ident = "TUXEDO Trinity 15 Intel Gen1", @@ -3138,7 +3352,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gmxmpxx_descriptor, }, { .ident = "TUXEDO Trinity 17 Intel Gen1", @@ -3146,7 +3360,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"), }, - .driver_data = &tux_featureset_1_nvidia_descriptor, + .driver_data = &gmxmpxx_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen2 AMD", @@ -3154,7 +3368,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen2 Intel", @@ -3162,23 +3376,39 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor, + }, + { + .ident = "TUXEDO Polaris 15/17 Gen3 AMD", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "POLARIS1XA03"), + }, + .driver_data = &gmxzgxx_gmxtgxx_pol_descriptor, + }, + { + .ident = "TUXEDO Polaris 15/17 Gen3 Intel", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "POLARIS1XI03"), + }, + .driver_data = &gmxzgxx_gmxtgxx_pol_descriptor, }, { - .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD", + .ident = "TUXEDO Stellaris 15/17 Gen3 AMD", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "STELLARIS1XA03"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxzgxx_gmxtgxx_ste_descriptor, }, { - .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel", + .ident = "TUXEDO Stellaris 15/17 Gen3 Intel", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), - DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "STELLARIS1XI03"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxzgxx_gmxtgxx_ste_descriptor, }, { .ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD", @@ -3186,7 +3416,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"), }, - .driver_data = &tux_featureset_3_nvidia_descriptor, + .driver_data = &gmxrgxx_descriptor, }, { .ident = "TUXEDO Stellaris 15 Gen4 Intel", @@ -3194,7 +3424,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"), }, - .driver_data = &tux_featureset_3_nvidia_descriptor, + .driver_data = &gmxagxx_descriptor, }, { .ident = "TUXEDO Polaris 15/17 Gen5 AMD", @@ -3202,7 +3432,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"), }, - .driver_data = &tux_featureset_2_nvidia_descriptor, + .driver_data = &gmxmgxx_gmxngxx_gmxxgxx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen5 AMD", @@ -3210,7 +3440,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"), }, - .driver_data = &tux_featureset_3_nvidia_descriptor, + .driver_data = &gmxxgxx_gmxpxxx_descriptor, }, { .ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5", @@ -3218,7 +3448,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"), }, - .driver_data = &tux_featureset_3_nvidia_descriptor, + .driver_data = &gmxxgxx_gmxpxxx_descriptor, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 AMD", @@ -3226,7 +3456,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxhgxx_descriptor, }, { .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", @@ -3234,7 +3464,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", @@ -3242,7 +3472,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", @@ -3250,7 +3480,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", @@ -3258,7 +3488,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 AMD", @@ -3266,7 +3496,15 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, + }, + { + .ident = "PCSpecialist Recoil 16 AMD", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "PCSpecialist"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR57TY"), + }, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", @@ -3274,7 +3512,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Stellaris 16 Gen7 Intel", @@ -3282,7 +3520,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), }, - .driver_data = &tux_featureset_4_nvidia_descriptor, + .driver_data = &gmxixxx_xxxx45xx_xxxx55xx_xxxx57xx_descriptor, }, { .ident = "TUXEDO Book BA15 Gen10 AMD", @@ -3290,7 +3528,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"), }, - .driver_data = &pf5pu1g_descriptor, + .driver_data = &pfxpuxx_descriptor, }, { .ident = "TUXEDO Pulse 14 Gen1 AMD", @@ -3298,7 +3536,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor, }, { .ident = "TUXEDO Pulse 15 Gen1 AMD", @@ -3306,7 +3544,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor, }, { .ident = "TUXEDO Pulse 15 Gen2 AMD", @@ -3314,7 +3552,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"), }, - .driver_data = &tux_featureset_1_descriptor, + .driver_data = &pfxnuxx_pfxluxx_phxprxx_descriptor, }, { } }; @@ -3355,22 +3593,36 @@ static int __init uniwill_init(void) device_descriptor.kbd_led_single_color = false; /* Some models only support 3 brightness levels */ device_descriptor.kbd_led_max_brightness = 4; - /* Some models only support 36 brightness levels per color component */ - device_descriptor.lightbar_max_brightness = 200; + /* Some models only support 36 intensity levels per color component */ + device_descriptor.lightbar_max_intensity = 200; pr_warn("Enabling potentially unsupported features\n"); } - ret = platform_driver_register(&uniwill_driver); + /* + * We cannot register the trigger inside the .probe callback of the + * platform driver, because each trigger needs a unique name. + */ + ret = led_trigger_register(&uniwill_lightbar_trigger); if (ret < 0) return ret; + ret = platform_driver_register(&uniwill_driver); + if (ret < 0) + goto err_platform; + ret = uniwill_wmi_register_driver(); - if (ret < 0) { - platform_driver_unregister(&uniwill_driver); - return ret; - } + if (ret < 0) + goto err_wmi; return 0; + +err_wmi: + platform_driver_unregister(&uniwill_driver); + +err_platform: + led_trigger_unregister(&uniwill_lightbar_trigger); + + return ret; } module_init(uniwill_init); @@ -3378,6 +3630,7 @@ static void __exit uniwill_exit(void) { uniwill_wmi_unregister_driver(); platform_driver_unregister(&uniwill_driver); + led_trigger_unregister(&uniwill_lightbar_trigger); } module_exit(uniwill_exit); diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h index b25b2f31211c..1b6f650c40e5 100644 --- a/drivers/platform/x86/uniwill/uniwill-wmi.h +++ b/drivers/platform/x86/uniwill/uniwill-wmi.h @@ -117,6 +117,9 @@ #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF +/* "Sc" key next to F12 on the PCSpecialist Recoil 16 / Stellaris 16 Gen7 */ +#define UNIWILL_OSD_ROTATE_SCREEN 0xD0 + #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 struct device; diff --git a/include/linux/intel_vsec.h b/include/linux/intel_vsec.h index 843cda8f8644..8686e79c4f75 100644 --- a/include/linux/intel_vsec.h +++ b/include/linux/intel_vsec.h @@ -90,13 +90,25 @@ enum intel_vsec_quirks { * @read_telem: when specified, called by client driver to access PMT * data (instead of direct copy). * * dev: device reference for the callback's use - * * guid: ID of data to acccss + * * guid: ID of data to access * * data: buffer for the data to be copied * * off: offset into the requested buffer * * count: size of buffer + * @read_reg: when specified called by client driver to read PMT state + * * dev: device reference for the callback's use + * * guid: ID of data to access + * * reg_data: register data + * * offset: offset of register to read + * @write_reg: when specified called by client driver to write PMT state + * * dev: device reference for the callback's use + * * guid: ID of data to access + * * reg_data: register data + * * offset: offset of register to write */ struct pmt_callbacks { int (*read_telem)(struct device *dev, u32 guid, u64 *data, loff_t off, u32 count); + int (*read_reg)(struct device *dev, u32 guid, u32 *reg_data, u32 offset); + int (*write_reg)(struct device *dev, u32 guid, u32 reg_data, u32 offset); }; struct vsec_feature_dependency { @@ -221,8 +233,7 @@ static inline struct intel_vsec_device *auxdev_to_ivdev(struct auxiliary_device #if IS_ENABLED(CONFIG_INTEL_VSEC) int intel_vsec_register(struct device *dev, const struct intel_vsec_platform_info *info); -int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, - struct intel_vsec_device *vsec_dev); +int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, struct device *dev); struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev); #else static inline int intel_vsec_register(struct device *dev, @@ -231,7 +242,7 @@ static inline int intel_vsec_register(struct device *dev, return -ENODEV; } static inline int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, - struct intel_vsec_device *vsec_dev) + struct device *dev) { return -ENODEV; } diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index b5ed8c83ace1..66c7ab35d6c8 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -54,6 +54,7 @@ #define ASUS_WMI_DEVID_LED5 0x00020015 #define ASUS_WMI_DEVID_LED6 0x00020016 #define ASUS_WMI_DEVID_MICMUTE_LED 0x00040017 +#define ASUS_WMI_DEVID_MUTE_LED 0x0004001C /* Disable Camera LED */ #define ASUS_WMI_DEVID_CAMERA_LED_NEG 0x00060078 /* 0 = on (unused) */ @@ -138,10 +139,14 @@ /* dgpu on/off */ #define ASUS_WMI_DEVID_DGPU 0x00090020 +/* dgpu on/off - alternative to ASUS_WMI_DEVID_DGPU */ +#define ASUS_WMI_DEVID_GPU_MODE 0x00090120 + #define ASUS_WMI_DEVID_APU_MEM 0x000600C1 -#define ASUS_WMI_DEVID_DGPU_BASE_TGP 0x00120099 +#define ASUS_WMI_DEVID_DGPU_POWER_STATE 0x00120097 #define ASUS_WMI_DEVID_DGPU_SET_TGP 0x00120098 +#define ASUS_WMI_DEVID_DGPU_BASE_TGP 0x00120099 /* gpu mux switch, 0 = dGPU, 1 = Optimus */ #define ASUS_WMI_DEVID_GPU_MUX 0x00090016 @@ -159,7 +164,7 @@ #define ASUS_WMI_DEVID_TUF_RGB_MODE2 0x0010005A /* TUF laptop RGB power/state */ -#define ASUS_WMI_DEVID_TUF_RGB_STATE 0x00100057 +#define ASUS_WMI_DEVID_TUF_RGB_CMD 0x00100057 /* Bootup sound control */ #define ASUS_WMI_DEVID_BOOT_SOUND 0x00130022 diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h index ee84087d4b26..a2eba454004f 100644 --- a/include/linux/string_choices.h +++ b/include/linux/string_choices.h @@ -65,6 +65,12 @@ static inline const char *str_read_write(bool v) } #define str_write_read(v) str_read_write(!(v)) +static inline const char *str_supported_unsupported(bool v) +{ + return v ? "supported" : "unsupported"; +} +#define str_unsupported_supported(v) str_supported_unsupported(!(v)) + static inline const char *str_true_false(bool v) { return v ? "true" : "false"; diff --git a/include/linux/wmi.h b/include/linux/wmi.h index defcb624a7e2..3dd223f2ff18 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -13,6 +13,7 @@ #include <linux/acpi.h> #include <linux/device-id/wmi.h> #include <linux/types.h> +#include <linux/uuid.h> /** * struct wmi_device - WMI device structure @@ -83,6 +84,8 @@ acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct u8 wmidev_instance_count(struct wmi_device *wdev); +bool wmidev_exists(const guid_t *guid); + /** * struct wmi_driver - WMI driver structure * @driver: Driver model structure diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h index 24cf9926dc59..151f264cd7d6 100644 --- a/include/uapi/linux/amd-pmf.h +++ b/include/uapi/linux/amd-pmf.h @@ -199,6 +199,9 @@ struct amd_pmf_info { /* BIOS parameters */ __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; __u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX]; + + /* align to 64 bit boundary */ + __u32 __pad; }; #endif /* _UAPI_LINUX_AMD_PMF_H */ diff --git a/tools/power/x86/intel-speed-select/Makefile b/tools/power/x86/intel-speed-select/Makefile index 6b299aae2ded..6f896f40c1a8 100644 --- a/tools/power/x86/intel-speed-select/Makefile +++ b/tools/power/x86/intel-speed-select/Makefile @@ -52,8 +52,8 @@ $(OUTPUT)intel-speed-select: $(ISST_IN) clean: rm -f $(ALL_PROGRAMS) - rm -rf $(OUTPUT)include/linux/isst_if.h - find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete + rm -rf $(OUTPUT)include/linux/isst_if.h $(OUTPUT)include/linux/thermal.h + find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '.*.o.d' -delete -o -name '.*.o.cmd' -delete install: $(ALL_PROGRAMS) install -d -m 755 $(DESTDIR)$(bindir); \ diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c index 2faff1aead52..c0e9e5f81337 100644 --- a/tools/power/x86/intel-speed-select/isst-config.c +++ b/tools/power/x86/intel-speed-select/isst-config.c @@ -16,7 +16,7 @@ struct process_cmd_struct { int arg; }; -static const char *version_str = "v1.26"; +static const char *version_str = "v1.27"; static const int supported_api_ver = 3; static struct isst_if_platform_info isst_platform_info; @@ -1545,7 +1545,7 @@ display_result: struct isst_pkg_ctdp_level_info ctdp_level; /* Wait for updated base frequencies */ - usleep(2000); + usleep(10000); /* Adjusting uncore freq */ if (!is_dmr_plus_platform()) diff --git a/tools/power/x86/intel-speed-select/isst-core-mbox.c b/tools/power/x86/intel-speed-select/isst-core-mbox.c index c81ecd602bcf..baadeb75376c 100644 --- a/tools/power/x86/intel-speed-select/isst-core-mbox.c +++ b/tools/power/x86/intel-speed-select/isst-core-mbox.c @@ -561,7 +561,7 @@ static int mbox_get_get_trl(struct isst_id *id, int level, int avx_level, int *t if (ret) return ret; - debug_printf("cpu:%d CONFIG_TDP_GET_TURBO_LIMIT req:%x resp:%x\n", id->cpu, + debug_printf("cpu:%d CONFIG_TDP_GET_TURBO_LIMIT_RATIOS req:%x resp:%x\n", id->cpu, req, resp); trl[4] = resp & GENMASK(7, 0); diff --git a/tools/power/x86/intel-speed-select/isst.h b/tools/power/x86/intel-speed-select/isst.h index 960f647cfc2d..2e66bbf34791 100644 --- a/tools/power/x86/intel-speed-select/isst.h +++ b/tools/power/x86/intel-speed-select/isst.h @@ -30,7 +30,7 @@ #include <linux/isst_if.h> -#define BIT(x) (1 << (x)) +#define BIT(x) (1U << (x)) #define BIT_ULL(nr) (1ULL << (nr)) #define GENMASK(h, l) (((~0UL) << (l)) & (~0UL >> (sizeof(long) * 8 - 1 - (h)))) #define GENMASK_ULL(h, l) \ |
