aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2023-09-01 09:16:47 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2023-09-01 09:16:47 -0700
commite2c874f999f079e3ec9b8071e92c87d57aded3b6 (patch)
tree1b43236aa03d465095117be5e95167c86b9c4ca3
parente0152e7481c6c63764d6ea8ee41af5cf9dfac5e9 (diff)
parentacce85a7dd28eac3858d44230f4c65985d0f271c (diff)
downloadlinux-e2c874f999f079e3ec9b8071e92c87d57aded3b6.tar.gz
Merge tag 'platform-drivers-x86-v6.6-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86
Pull x86 platform driver updates from Hans de Goede: - hp-bioscfg: New firmware-attributes driver for changing BIOS settings from within Linux - asus-wmi: Add charger mode, middle fan and eGPU settings support - ideapad: Support keyboard backlight control on more models - mellanox: Support for new models - sel-3350: New LED and power-supply driver for this industrial mainboard - simatic-ipc: Add RTC battery monitor and various new models support - miscellaneous other cleanups / fixes * tag 'platform-drivers-x86-v6.6-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (101 commits) platform/x86: asus-wmi: corrections to egpu safety check platform/x86: mlx-platform: Add dependency on PCI to Kconfig platform/x86: ideapad-laptop: Add support for keyboard backlights using KBLC ACPI symbol platform/x86/amd/pmc: Fix build error with randconfig platform/x86/amd/pmf: Fix a missing cleanup path watchdog: simatic: Use idiomatic selection of P2SB platform/x86: p2sb: Make the Kconfig symbol hidden Documentation/ABI: Add new attribute for mlxreg-io sysfs interfaces platform: mellanox: nvsw-sn2201: change fans i2c busses. platform: mellanox: mlxreg-hotplug: Extend condition for notification callback processing platform: mellanox: Add initial support for PCIe based programming logic device platform: mellanox: mlx-platform: Get interrupt line through ACPI platform: mellanox: mlx-platform: Introduce ACPI init flow platform: mellanox: mlx-platform: Prepare driver to allow probing through ACPI infrastructure platform: mellanox: mlx-platform: Add reset callback platform: mellanox: Cosmetic changes platform: mellanox: mlx-platform: Modify power off callback platform: mellanox: mlx-platform: add support for additional CPLD platform: mellanox: mlx-platform: Add reset cause attribute platform: mellanox: mlx-platform: Modify health and power hotplug action ...
-rw-r--r--Documentation/ABI/stable/sysfs-driver-mlxreg-io53
-rw-r--r--Documentation/ABI/testing/debugfs-tpmi31
-rw-r--r--Documentation/ABI/testing/sysfs-class-firmware-attributes101
-rw-r--r--Documentation/ABI/testing/sysfs-platform-asus-wmi88
-rw-r--r--Documentation/ABI/testing/sysfs-platform-mellanox-bootctl66
-rw-r--r--MAINTAINERS41
-rw-r--r--drivers/leds/simple/Kconfig1
-rw-r--r--drivers/platform/chrome/cros_ec.c1
-rw-r--r--drivers/platform/mellanox/mlxbf-bootctl.c505
-rw-r--r--drivers/platform/mellanox/mlxbf-bootctl.h27
-rw-r--r--drivers/platform/mellanox/mlxreg-hotplug.c3
-rw-r--r--drivers/platform/mellanox/mlxreg-io.c1
-rw-r--r--drivers/platform/mellanox/nvsw-sn2201.c12
-rw-r--r--drivers/platform/x86/Kconfig31
-rw-r--r--drivers/platform/x86/Makefile5
-rw-r--r--drivers/platform/x86/amd/Kconfig16
-rw-r--r--drivers/platform/x86/amd/Makefile3
-rw-r--r--drivers/platform/x86/amd/pmc/Kconfig20
-rw-r--r--drivers/platform/x86/amd/pmc/Makefile8
-rw-r--r--drivers/platform/x86/amd/pmc/pmc-quirks.c (renamed from drivers/platform/x86/amd/pmc-quirks.c)0
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.c (renamed from drivers/platform/x86/amd/pmc.c)0
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.h (renamed from drivers/platform/x86/amd/pmc.h)0
-rw-r--r--drivers/platform/x86/amd/pmf/cnqf.c5
-rw-r--r--drivers/platform/x86/amd/pmf/core.c3
-rw-r--r--drivers/platform/x86/asus-wmi.c646
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/sysman.c9
-rw-r--r--drivers/platform/x86/hp/Kconfig16
-rw-r--r--drivers/platform/x86/hp/Makefile1
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/Makefile11
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c312
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.c1063
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.h487
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c457
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/int-attributes.c418
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c441
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c556
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c381
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/string-attributes.c395
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c132
-rw-r--r--drivers/platform/x86/ideapad-laptop.c118
-rw-r--r--drivers/platform/x86/intel/tpmi.c399
-rw-r--r--drivers/platform/x86/mlx-platform.c376
-rw-r--r--drivers/platform/x86/sel3350-platform.c251
-rw-r--r--drivers/platform/x86/siemens/Kconfig64
-rw-r--r--drivers/platform/x86/siemens/Makefile11
-rw-r--r--drivers/platform/x86/siemens/simatic-ipc-batt-apollolake.c51
-rw-r--r--drivers/platform/x86/siemens/simatic-ipc-batt-elkhartlake.c51
-rw-r--r--drivers/platform/x86/siemens/simatic-ipc-batt-f7188x.c87
-rw-r--r--drivers/platform/x86/siemens/simatic-ipc-batt.c253
-rw-r--r--drivers/platform/x86/siemens/simatic-ipc-batt.h20
-rw-r--r--drivers/platform/x86/siemens/simatic-ipc.c236
-rw-r--r--drivers/platform/x86/simatic-ipc.c151
-rw-r--r--drivers/platform/x86/system76_acpi.c74
-rw-r--r--drivers/platform/x86/thinkpad_acpi.c58
-rw-r--r--drivers/platform/x86/wmi-bmof.c26
-rw-r--r--drivers/watchdog/Kconfig5
-rw-r--r--drivers/watchdog/simatic-ipc-wdt.c9
-rw-r--r--include/linux/intel_tpmi.h2
-rw-r--r--include/linux/platform_data/x86/asus-wmi.h19
-rw-r--r--include/linux/platform_data/x86/simatic-ipc-base.h5
-rw-r--r--include/linux/platform_data/x86/simatic-ipc.h6
-rw-r--r--tools/power/x86/intel-speed-select/isst-config.c51
-rw-r--r--tools/power/x86/intel-speed-select/isst-display.c2
-rw-r--r--tools/power/x86/intel-speed-select/isst.h2
64 files changed, 8319 insertions, 354 deletions
diff --git a/Documentation/ABI/stable/sysfs-driver-mlxreg-io b/Documentation/ABI/stable/sysfs-driver-mlxreg-io
index 60953903d0077..2cdfd09123da0 100644
--- a/Documentation/ABI/stable/sysfs-driver-mlxreg-io
+++ b/Documentation/ABI/stable/sysfs-driver-mlxreg-io
@@ -662,3 +662,56 @@ Description: This file shows the system reset cause due to AC power failure.
Value 1 in file means this is reset cause, 0 - otherwise.
The file is read only.
+
+What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/cpld5_pn
+What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/cpld5_version
+What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/cpld5_version_min
+Date: August 2023
+KernelVersion: 6.6
+Contact: Vadim Pasternak <vadimp@nvidia.com>
+Description: These files show with which CPLD part numbers, version and minor
+ versions have been burned the 5-th CPLD device equipped on a
+ system.
+
+ The files are read only.
+
+What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/jtag_cap
+Date: August 2023
+KernelVersion: 6.6
+Contact: Vadim Pasternak <vadimp@nvidia.com>
+Description: This file indicates the available method of CPLD/FPGA devices
+ field update through the JTAG chain:
+
+ b00 - field update through LPC bus register memory space.
+ b01 - Reserved.
+ b10 - Reserved.
+ b11 - field update through CPU GPIOs bit-banging.
+
+ The file is read only.
+
+What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/lid_open
+Date: August 2023
+KernelVersion: 6.6
+Contact: Vadim Pasternak <vadimp@nvidia.com>
+Description: 1 - indicates that system lid is opened, otherwise 0.
+
+ The file is read only.
+
+What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_long_pwr_pb
+Date: August 2023
+KernelVersion: 6.6
+Contact: Vadim Pasternak <vadimp@nvidia.com>
+Description: This file if set 1 indicates that system has been reset by
+ long press of power button.
+
+ The file is read only.
+
+What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_swb_dc_dc_pwr_fail
+Date: August 2023
+KernelVersion: 6.6
+Contact: Vadim Pasternak <vadimp@nvidia.com>
+Description: This file shows 1 in case the system reset happened due to the
+ failure of any DC-DC power converter devices equipped on the
+ switch board.
+
+ The file is read only.
diff --git a/Documentation/ABI/testing/debugfs-tpmi b/Documentation/ABI/testing/debugfs-tpmi
new file mode 100644
index 0000000000000..597f0475fe6eb
--- /dev/null
+++ b/Documentation/ABI/testing/debugfs-tpmi
@@ -0,0 +1,31 @@
+What: /sys/kernel/debug/tpmi-<n>/pfs_dump
+Date: November 2023
+KernelVersion: 6.6
+Contact: srinivas.pandruvada@linux.intel.com
+Description:
+The PFS (PM Feature Structure) table, shows details of each power
+management feature. This includes:
+tpmi_id, number of entries, entry size, offset, vsec offset, lock status
+and disabled status.
+Users: Debugging, any user space test suite
+
+What: /sys/kernel/debug/tpmi-<n>/tpmi-id-<n>/mem_dump
+Date: November 2023
+KernelVersion: 6.6
+Contact: srinivas.pandruvada@linux.intel.com
+Description:
+Shows the memory dump of the MMIO region for a TPMI ID.
+Users: Debugging, any user space test suite
+
+What: /sys/kernel/debug/tpmi-<n>/tpmi-id-<n>/mem_write
+Date: November 2023
+KernelVersion: 6.6
+Contact: srinivas.pandruvada@linux.intel.com
+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
+Users: Debugging, any user space test suite
diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
index 1b3ecae80b3dd..f205d39409a33 100644
--- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
+++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
@@ -22,6 +22,11 @@ Description:
- integer: a range of numerical values
- string
+ HP specific types
+ -----------------
+ - ordered-list - a set of ordered list valid values
+
+
All attribute types support the following values:
current_value:
@@ -126,6 +131,21 @@ Description:
value will not be effective through sysfs until this rule is
met.
+ HP specific class extensions
+ ------------------------------
+
+ On HP systems the following additional attributes are available:
+
+ "ordered-list"-type specific properties:
+
+ elements:
+ A file that can be read to obtain the possible
+ list of values of the <attr>. Values are separated using
+ semi-colon (``;``) and listed according to their priority.
+ An element listed first has the highest priority. Writing
+ the list in a different order to current_value alters
+ the priority order for the particular attribute.
+
What: /sys/class/firmware-attributes/*/authentication/
Date: February 2021
KernelVersion: 5.11
@@ -206,7 +226,7 @@ Description:
Drivers may emit a CHANGE uevent when a password is set or unset
userspace may check it again.
- On Dell and Lenovo systems, if Admin password is set, then all BIOS attributes
+ On Dell, Lenovo and HP systems, if Admin password is set, then all BIOS attributes
require password validation.
On Lenovo systems if you change the Admin password the new password is not active until
the next boot.
@@ -296,6 +316,15 @@ Description:
echo "signature" > authentication/Admin/signature
echo "password" > authentication/Admin/certificate_to_password
+ HP specific class extensions
+ --------------------------------
+
+ On HP systems the following additional settings are available:
+
+ role: enhanced-bios-auth:
+ This role is specific to Secure Platform Management (SPM) attribute.
+ It requires configuring an endorsement (kek) and signing certificate (sk).
+
What: /sys/class/firmware-attributes/*/attributes/pending_reboot
Date: February 2021
@@ -311,7 +340,7 @@ Description:
== =========================================
0 All BIOS attributes setting are current
1 A reboot is necessary to get pending BIOS
- attribute changes applied
+ attribute changes applied
== =========================================
Note, userspace applications need to follow below steps for efficient
@@ -364,3 +393,71 @@ Description:
use it to enable extra debug attributes or BIOS features for testing purposes.
Note that any changes to this attribute requires a reboot for changes to take effect.
+
+
+ HP specific class extensions - Secure Platform Manager (SPM)
+ --------------------------------
+
+What: /sys/class/firmware-attributes/*/authentication/SPM/kek
+Date: March 2023
+KernelVersion: 5.18
+Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
+Description:
+ 'kek' Key-Encryption-Key is a write-only file that can be used to configure the
+ RSA public key that will be used by the BIOS to verify
+ signatures when setting the signing key. When written,
+ the bytes should correspond to the KEK certificate
+ (x509 .DER format containing an OU). The size of the
+ certificate must be less than or equal to 4095 bytes.
+
+What: /sys/class/firmware-attributes/*/authentication/SPM/sk
+Date: March 2023
+KernelVersion: 5.18
+Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
+Description:
+ 'sk' Signature Key is a write-only file that can be used to configure the RSA
+ public key that will be used by the BIOS to verify signatures
+ when configuring BIOS settings and security features. When
+ written, the bytes should correspond to the modulus of the
+ public key. The exponent is assumed to be 0x10001.
+
+What: /sys/class/firmware-attributes/*/authentication/SPM/status
+Date: March 2023
+KernelVersion: 5.18
+Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
+Description:
+ 'status' is a read-only file that returns ASCII text in JSON format reporting
+ the status information.
+
+ "State": "not provisioned | provisioned | provisioning in progress",
+ "Version": "Major.Minor",
+ "Nonce": <16-bit unsigned number display in base 10>,
+ "FeaturesInUse": <16-bit unsigned number display in base 10>,
+ "EndorsementKeyMod": "<256 bytes in base64>",
+ "SigningKeyMod": "<256 bytes in base64>"
+
+What: /sys/class/firmware-attributes/*/attributes/Sure_Start/audit_log_entries
+Date: March 2023
+KernelVersion: 5.18
+Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
+Description:
+ 'audit_log_entries' is a read-only file that returns the events in the log.
+
+ Audit log entry format
+
+ Byte 0-15: Requested Audit Log entry (Each Audit log is 16 bytes)
+ Byte 16-127: Unused
+
+What: /sys/class/firmware-attributes/*/attributes/Sure_Start/audit_log_entry_count
+Date: March 2023
+KernelVersion: 5.18
+Contact: "Jorge Lopez" <jorge.lopez2@hp.com>
+Description:
+ 'audit_log_entry_count' is a read-only file that returns the number of existing
+ audit log events available to be read. Values are separated using comma. (``,``)
+
+ [No of entries],[log entry size],[Max number of entries supported]
+
+ log entry size identifies audit log size for the current BIOS version.
+ The current size is 16 bytes but it can be up to 128 bytes long in future BIOS
+ versions.
diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi
index a77a004a1baa4..8a7e25bde0853 100644
--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi
+++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi
@@ -98,3 +98,91 @@ Description:
Enable an LCD response-time boost to reduce or remove ghosting:
* 0 - Disable,
* 1 - Enable
+
+What: /sys/devices/platform/<platform>/charge_mode
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Get the current charging mode being used:
+ * 1 - Barrel connected charger,
+ * 2 - USB-C charging
+ * 3 - Both connected, barrel used for charging
+
+What: /sys/devices/platform/<platform>/egpu_connected
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Show if the egpu (XG Mobile) is correctly connected:
+ * 0 - False,
+ * 1 - True
+
+What: /sys/devices/platform/<platform>/mini_led_mode
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Change the mini-LED mode:
+ * 0 - Single-zone,
+ * 1 - Multi-zone
+
+What: /sys/devices/platform/<platform>/ppt_pl1_spl
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set the Package Power Target total of CPU: PL1 on Intel, SPL on AMD.
+ Shown on Intel+Nvidia or AMD+Nvidia based systems:
+
+ * min=5, max=250
+
+What: /sys/devices/platform/<platform>/ppt_pl2_sppt
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set the Slow Package Power Tracking Limit of CPU: PL2 on Intel, SPPT,
+ on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems:
+
+ * min=5, max=250
+
+What: /sys/devices/platform/<platform>/ppt_fppt
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set the Fast Package Power Tracking Limit of CPU. AMD+Nvidia only:
+ * min=5, max=250
+
+What: /sys/devices/platform/<platform>/ppt_apu_sppt
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set the APU SPPT limit. Shown on full AMD systems only:
+ * min=5, max=130
+
+What: /sys/devices/platform/<platform>/ppt_platform_sppt
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set the platform SPPT limit. Shown on full AMD systems only:
+ * min=5, max=130
+
+What: /sys/devices/platform/<platform>/nv_dynamic_boost
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set the dynamic boost limit of the Nvidia dGPU:
+ * min=5, max=25
+
+What: /sys/devices/platform/<platform>/nv_temp_target
+Date: Jun 2023
+KernelVersion: 6.5
+Contact: "Luke Jones" <luke@ljones.dev>
+Description:
+ Set the target temperature limit of the Nvidia dGPU:
+ * min=75, max=87
diff --git a/Documentation/ABI/testing/sysfs-platform-mellanox-bootctl b/Documentation/ABI/testing/sysfs-platform-mellanox-bootctl
index 4c5c02d8f870e..65ed3865da624 100644
--- a/Documentation/ABI/testing/sysfs-platform-mellanox-bootctl
+++ b/Documentation/ABI/testing/sysfs-platform-mellanox-bootctl
@@ -84,3 +84,69 @@ Description:
The file used to write BlueField boot log with the format
"[INFO|WARN|ERR|ASSERT ]<msg>". Log level 'INFO' is used by
default if not specified.
+
+What: /sys/bus/platform/devices/MLNXBF04:00/oob_mac
+Date: August 2023
+KernelVersion: 6.5
+Contact: "David Thompson <davthompson@nvidia.com>"
+Description:
+ The "oob_mac" sysfs attribute holds the MAC address for
+ the out-of-band 1Gbps Ethernet port. This MAC address is
+ provided on a board-level label.
+
+What: /sys/bus/platform/devices/MLNXBF04:00/opn
+Date: August 2023
+KernelVersion: 6.5
+Contact: "David Thompson <davthompson@nvidia.com>"
+Description:
+ The "opn" sysfs attribute holds the board's part number.
+ This value is provided on a board-level label.
+
+What: /sys/bus/platform/devices/MLNXBF04:00/sku
+Date: August 2023
+KernelVersion: 6.5
+Contact: "David Thompson <davthompson@nvidia.com>"
+Description:
+ The "sku" sysfs attribute holds the board's SKU number.
+ This value is provided on a board-level label.
+
+What: /sys/bus/platform/devices/MLNXBF04:00/modl
+Date: August 2023
+KernelVersion: 6.5
+Contact: "David Thompson <davthompson@nvidia.com>"
+Description:
+ The "modl" sysfs attribute holds the board's model number.
+ This value is provided on a board-level label.
+
+What: /sys/bus/platform/devices/MLNXBF04:00/sn
+Date: August 2023
+KernelVersion: 6.5
+Contact: "David Thompson <davthompson@nvidia.com>"
+Description:
+ The "sn" sysfs attribute holds the board's serial number.
+ This value is provided on a board-level label.
+
+What: /sys/bus/platform/devices/MLNXBF04:00/uuid
+Date: August 2023
+KernelVersion: 6.5
+Contact: "David Thompson <davthompson@nvidia.com>"
+Description:
+ The "uuid" sysfs attribute holds the board's UUID.
+ This value is provided by the manufacturing team.
+
+What: /sys/bus/platform/devices/MLNXBF04:00/rev
+Date: August 2023
+KernelVersion: 6.5
+Contact: "David Thompson <davthompson@nvidia.com>"
+Description:
+ The "rev" sysfs attribute holds the board's revision.
+ This value is provided on a board-level label.
+
+What: /sys/bus/platform/devices/MLNXBF04:00/mfg_lock
+Date: August 2023
+KernelVersion: 6.5
+Contact: "David Thompson <davthompson@nvidia.com>"
+Description:
+ The "mfg_lock" sysfs attribute is write-only.
+ A successful write to this attribute will latch the
+ board-level attributes into EEPROM, making them read-only.
diff --git a/MAINTAINERS b/MAINTAINERS
index 4e3419c04f278..3956f37fd715d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1018,7 +1018,7 @@ AMD PMC DRIVER
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
-F: drivers/platform/x86/amd/pmc.c
+F: drivers/platform/x86/amd/pmc/
AMD PMF DRIVER
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
@@ -9522,6 +9522,12 @@ S: Obsolete
W: http://w1.fi/hostap-driver.html
F: drivers/net/wireless/intersil/hostap/
+HP BIOSCFG DRIVER
+M: Jorge Lopez <jorge.lopez2@hp.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/x86/hp/hp-bioscfg/
+
HP COMPAQ TC1100 TABLET WMI EXTRAS DRIVER
L: platform-driver-x86@vger.kernel.org
S: Orphan
@@ -10802,6 +10808,7 @@ INTEL TPMI DRIVER
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.c
F: include/linux/intel_tpmi.h
@@ -19496,6 +19503,32 @@ F: drivers/media/mmc/siano/
F: drivers/media/usb/siano/
F: drivers/media/usb/siano/
+SIEMENS IPC LED DRIVERS
+M: Gerd Haeussler <gerd.haeussler.ext@siemens.com>
+M: Xing Tong Wu <xingtong.wu@siemens.com>
+M: Tobias Schaffner <tobias.schaffner@siemens.com>
+L: linux-leds@vger.kernel.org
+S: Maintained
+F: drivers/leds/simple/
+
+SIEMENS IPC PLATFORM DRIVERS
+M: Gerd Haeussler <gerd.haeussler.ext@siemens.com>
+M: Xing Tong Wu <xingtong.wu@siemens.com>
+M: Tobias Schaffner <tobias.schaffner@siemens.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/x86/siemens/
+F: include/linux/platform_data/x86/simatic-ipc-base.h
+F: include/linux/platform_data/x86/simatic-ipc.h
+
+SIEMENS IPC WATCHDOG DRIVERS
+M: Gerd Haeussler <gerd.haeussler.ext@siemens.com>
+M: Xing Tong Wu <xingtong.wu@siemens.com>
+M: Tobias Schaffner <tobias.schaffner@siemens.com>
+L: linux-watchdog@vger.kernel.org
+S: Maintained
+F: drivers/watchdog/simatic-ipc-wdt.c
+
SIFIVE DRIVERS
M: Palmer Dabbelt <palmer@dabbelt.com>
M: Paul Walmsley <paul.walmsley@sifive.com>
@@ -23112,8 +23145,10 @@ S: Orphan
F: drivers/net/wireless/legacy/wl3501*
WMI BINARY MOF DRIVER
-L: platform-drivers-x86@vger.kernel.org
-S: Orphan
+M: Armin Wolf <W_Armin@gmx.de>
+R: Thomas Weißschuh <linux@weissschuh.net>
+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
diff --git a/drivers/leds/simple/Kconfig b/drivers/leds/simple/Kconfig
index 44fa0f93cb3b3..609e438af9f65 100644
--- a/drivers/leds/simple/Kconfig
+++ b/drivers/leds/simple/Kconfig
@@ -2,6 +2,7 @@
config LEDS_SIEMENS_SIMATIC_IPC
tristate "LED driver for Siemens Simatic IPCs"
depends on SIEMENS_SIMATIC_IPC
+ default y
help
This option enables support for the LEDs of several Industrial PCs
from Siemens.
diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c
index 8b79492203825..5d36fbc75e1bb 100644
--- a/drivers/platform/chrome/cros_ec.c
+++ b/drivers/platform/chrome/cros_ec.c
@@ -12,6 +12,7 @@
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of_platform.h>
+#include <linux/platform_device.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/slab.h>
diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c
index fb9f7815c6cd4..4ee7bb431b7c0 100644
--- a/drivers/platform/mellanox/mlxbf-bootctl.c
+++ b/drivers/platform/mellanox/mlxbf-bootctl.c
@@ -11,6 +11,7 @@
#include <linux/acpi.h>
#include <linux/arm-smccc.h>
#include <linux/delay.h>
+#include <linux/if_ether.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -79,6 +80,52 @@ static void __iomem *mlxbf_rsh_scratch_buf_data;
static const char * const mlxbf_rsh_log_level[] = {
"INFO", "WARN", "ERR", "ASSERT"};
+static DEFINE_MUTEX(icm_ops_lock);
+static DEFINE_MUTEX(os_up_lock);
+static DEFINE_MUTEX(mfg_ops_lock);
+
+/*
+ * Objects are stored within the MFG partition per type.
+ * Type 0 is not supported.
+ */
+enum {
+ MLNX_MFG_TYPE_OOB_MAC = 1,
+ MLNX_MFG_TYPE_OPN_0,
+ MLNX_MFG_TYPE_OPN_1,
+ MLNX_MFG_TYPE_OPN_2,
+ MLNX_MFG_TYPE_SKU_0,
+ MLNX_MFG_TYPE_SKU_1,
+ MLNX_MFG_TYPE_SKU_2,
+ MLNX_MFG_TYPE_MODL_0,
+ MLNX_MFG_TYPE_MODL_1,
+ MLNX_MFG_TYPE_MODL_2,
+ MLNX_MFG_TYPE_SN_0,
+ MLNX_MFG_TYPE_SN_1,
+ MLNX_MFG_TYPE_SN_2,
+ MLNX_MFG_TYPE_UUID_0,
+ MLNX_MFG_TYPE_UUID_1,
+ MLNX_MFG_TYPE_UUID_2,
+ MLNX_MFG_TYPE_UUID_3,
+ MLNX_MFG_TYPE_UUID_4,
+ MLNX_MFG_TYPE_REV,
+};
+
+#define MLNX_MFG_OPN_VAL_LEN 24
+#define MLNX_MFG_SKU_VAL_LEN 24
+#define MLNX_MFG_MODL_VAL_LEN 24
+#define MLNX_MFG_SN_VAL_LEN 24
+#define MLNX_MFG_UUID_VAL_LEN 40
+#define MLNX_MFG_REV_VAL_LEN 8
+#define MLNX_MFG_VAL_QWORD_CNT(type) \
+ (MLNX_MFG_##type##_VAL_LEN / sizeof(u64))
+
+/*
+ * The MAC address consists of 6 bytes (2 digits each) separated by ':'.
+ * The expected format is: "XX:XX:XX:XX:XX:XX"
+ */
+#define MLNX_MFG_OOB_MAC_FORMAT_LEN \
+ ((ETH_ALEN * 2) + (ETH_ALEN - 1))
+
/* ARM SMC call which is atomic and no need for lock. */
static int mlxbf_bootctl_smc(unsigned int smc_op, int smc_arg)
{
@@ -391,6 +438,444 @@ done:
return count;
}
+static ssize_t large_icm_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arm_smccc_res res;
+
+ mutex_lock(&icm_ops_lock);
+ arm_smccc_smc(MLNX_HANDLE_GET_ICM_INFO, 0, 0, 0, 0,
+ 0, 0, 0, &res);
+ mutex_unlock(&icm_ops_lock);
+ if (res.a0)
+ return -EPERM;
+
+ return snprintf(buf, PAGE_SIZE, "0x%lx", res.a1);
+}
+
+static ssize_t large_icm_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct arm_smccc_res res;
+ unsigned long icm_data;
+ int err;
+
+ err = kstrtoul(buf, MLXBF_LARGE_ICMC_MAX_STRING_SIZE, &icm_data);
+ if (err)
+ return err;
+
+ if ((icm_data != 0 && icm_data < MLXBF_LARGE_ICMC_SIZE_MIN) ||
+ icm_data > MLXBF_LARGE_ICMC_SIZE_MAX || icm_data % MLXBF_LARGE_ICMC_GRANULARITY)
+ return -EPERM;
+
+ mutex_lock(&icm_ops_lock);
+ arm_smccc_smc(MLNX_HANDLE_SET_ICM_INFO, icm_data, 0, 0, 0, 0, 0, 0, &res);
+ mutex_unlock(&icm_ops_lock);
+
+ return res.a0 ? -EPERM : count;
+}
+
+static ssize_t os_up_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct arm_smccc_res res;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err)
+ return err;
+
+ if (val != 1)
+ return -EINVAL;
+
+ mutex_lock(&os_up_lock);
+ arm_smccc_smc(MLNX_HANDLE_OS_UP, 0, 0, 0, 0, 0, 0, 0, &res);
+ mutex_unlock(&os_up_lock);
+
+ return count;
+}
+
+static ssize_t oob_mac_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct arm_smccc_res res;
+ u8 *mac_byte_ptr;
+
+ mutex_lock(&mfg_ops_lock);
+ arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO, MLNX_MFG_TYPE_OOB_MAC, 0, 0, 0,
+ 0, 0, 0, &res);
+ mutex_unlock(&mfg_ops_lock);
+ if (res.a0)
+ return -EPERM;
+
+ mac_byte_ptr = (u8 *)&res.a1;
+
+ return sysfs_format_mac(buf, mac_byte_ptr, ETH_ALEN);
+}
+
+static ssize_t oob_mac_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int byte[MLNX_MFG_OOB_MAC_FORMAT_LEN] = { 0 };
+ struct arm_smccc_res res;
+ int byte_idx, len;
+ u64 mac_addr = 0;
+ u8 *mac_byte_ptr;
+
+ if ((count - 1) != MLNX_MFG_OOB_MAC_FORMAT_LEN)
+ return -EINVAL;
+
+ len = sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
+ &byte[0], &byte[1], &byte[2],
+ &byte[3], &byte[4], &byte[5]);
+ if (len != ETH_ALEN)
+ return -EINVAL;
+
+ mac_byte_ptr = (u8 *)&mac_addr;
+
+ for (byte_idx = 0; byte_idx < ETH_ALEN; byte_idx++)
+ mac_byte_ptr[byte_idx] = (u8)byte[byte_idx];
+
+ mutex_lock(&mfg_ops_lock);
+ arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO, MLNX_MFG_TYPE_OOB_MAC,
+ ETH_ALEN, mac_addr, 0, 0, 0, 0, &res);
+ mutex_unlock(&mfg_ops_lock);
+
+ return res.a0 ? -EPERM : count;
+}
+
+static ssize_t opn_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u64 opn_data[MLNX_MFG_VAL_QWORD_CNT(OPN) + 1] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(OPN); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
+ MLNX_MFG_TYPE_OPN_0 + word,
+ 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ opn_data[word] = res.a1;
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return snprintf(buf, PAGE_SIZE, "%s", (char *)opn_data);
+}
+
+static ssize_t opn_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u64 opn[MLNX_MFG_VAL_QWORD_CNT(OPN)] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ if (count > MLNX_MFG_OPN_VAL_LEN)
+ return -EINVAL;
+
+ memcpy(opn, buf, count);
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(OPN); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
+ MLNX_MFG_TYPE_OPN_0 + word,
+ sizeof(u64), opn[word], 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return count;
+}
+
+static ssize_t sku_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u64 sku_data[MLNX_MFG_VAL_QWORD_CNT(SKU) + 1] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SKU); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
+ MLNX_MFG_TYPE_SKU_0 + word,
+ 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ sku_data[word] = res.a1;
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return snprintf(buf, PAGE_SIZE, "%s", (char *)sku_data);
+}
+
+static ssize_t sku_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u64 sku[MLNX_MFG_VAL_QWORD_CNT(SKU)] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ if (count > MLNX_MFG_SKU_VAL_LEN)
+ return -EINVAL;
+
+ memcpy(sku, buf, count);
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SKU); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
+ MLNX_MFG_TYPE_SKU_0 + word,
+ sizeof(u64), sku[word], 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return count;
+}
+
+static ssize_t modl_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u64 modl_data[MLNX_MFG_VAL_QWORD_CNT(MODL) + 1] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(MODL); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
+ MLNX_MFG_TYPE_MODL_0 + word,
+ 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ modl_data[word] = res.a1;
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return snprintf(buf, PAGE_SIZE, "%s", (char *)modl_data);
+}
+
+static ssize_t modl_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u64 modl[MLNX_MFG_VAL_QWORD_CNT(MODL)] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ if (count > MLNX_MFG_MODL_VAL_LEN)
+ return -EINVAL;
+
+ memcpy(modl, buf, count);
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(MODL); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
+ MLNX_MFG_TYPE_MODL_0 + word,
+ sizeof(u64), modl[word], 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return count;
+}
+
+static ssize_t sn_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u64 sn_data[MLNX_MFG_VAL_QWORD_CNT(SN) + 1] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SN); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
+ MLNX_MFG_TYPE_SN_0 + word,
+ 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ sn_data[word] = res.a1;
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return snprintf(buf, PAGE_SIZE, "%s", (char *)sn_data);
+}
+
+static ssize_t sn_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u64 sn[MLNX_MFG_VAL_QWORD_CNT(SN)] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ if (count > MLNX_MFG_SN_VAL_LEN)
+ return -EINVAL;
+
+ memcpy(sn, buf, count);
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(SN); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
+ MLNX_MFG_TYPE_SN_0 + word,
+ sizeof(u64), sn[word], 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return count;
+}
+
+static ssize_t uuid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u64 uuid_data[MLNX_MFG_VAL_QWORD_CNT(UUID) + 1] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(UUID); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
+ MLNX_MFG_TYPE_UUID_0 + word,
+ 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ uuid_data[word] = res.a1;
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return snprintf(buf, PAGE_SIZE, "%s", (char *)uuid_data);
+}
+
+static ssize_t uuid_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u64 uuid[MLNX_MFG_VAL_QWORD_CNT(UUID)] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ if (count > MLNX_MFG_UUID_VAL_LEN)
+ return -EINVAL;
+
+ memcpy(uuid, buf, count);
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(UUID); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
+ MLNX_MFG_TYPE_UUID_0 + word,
+ sizeof(u64), uuid[word], 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return count;
+}
+
+static ssize_t rev_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u64 rev_data[MLNX_MFG_VAL_QWORD_CNT(REV) + 1] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(REV); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_GET_MFG_INFO,
+ MLNX_MFG_TYPE_REV + word,
+ 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ rev_data[word] = res.a1;
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return snprintf(buf, PAGE_SIZE, "%s", (char *)rev_data);
+}
+
+static ssize_t rev_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u64 rev[MLNX_MFG_VAL_QWORD_CNT(REV)] = { 0 };
+ struct arm_smccc_res res;
+ int word;
+
+ if (count > MLNX_MFG_REV_VAL_LEN)
+ return -EINVAL;
+
+ memcpy(rev, buf, count);
+
+ mutex_lock(&mfg_ops_lock);
+ for (word = 0; word < MLNX_MFG_VAL_QWORD_CNT(REV); word++) {
+ arm_smccc_smc(MLXBF_BOOTCTL_SET_MFG_INFO,
+ MLNX_MFG_TYPE_REV + word,
+ sizeof(u64), rev[word], 0, 0, 0, 0, &res);
+ if (res.a0) {
+ mutex_unlock(&mfg_ops_lock);
+ return -EPERM;
+ }
+ }
+ mutex_unlock(&mfg_ops_lock);
+
+ return count;
+}
+
+static ssize_t mfg_lock_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct arm_smccc_res res;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err)
+ return err;
+
+ if (val != 1)
+ return -EINVAL;
+
+ mutex_lock(&mfg_ops_lock);
+ arm_smccc_smc(MLXBF_BOOTCTL_LOCK_MFG_INFO, 0, 0, 0, 0, 0, 0, 0, &res);
+ mutex_unlock(&mfg_ops_lock);
+
+ return count;
+}
+
static DEVICE_ATTR_RW(post_reset_wdog);
static DEVICE_ATTR_RW(reset_action);
static DEVICE_ATTR_RW(second_reset_action);
@@ -398,6 +883,16 @@ static DEVICE_ATTR_RO(lifecycle_state);
static DEVICE_ATTR_RO(secure_boot_fuse_state);
static DEVICE_ATTR_WO(fw_reset);
static DEVICE_ATTR_WO(rsh_log);
+static DEVICE_ATTR_RW(large_icm);
+static DEVICE_ATTR_WO(os_up);
+static DEVICE_ATTR_RW(oob_mac);
+static DEVICE_ATTR_RW(opn);
+static DEVICE_ATTR_RW(sku);
+static DEVICE_ATTR_RW(modl);
+static DEVICE_ATTR_RW(sn);
+static DEVICE_ATTR_RW(uuid);
+static DEVICE_ATTR_RW(rev);
+static DEVICE_ATTR_WO(mfg_lock);
static struct attribute *mlxbf_bootctl_attrs[] = {
&dev_attr_post_reset_wdog.attr,
@@ -407,6 +902,16 @@ static struct attribute *mlxbf_bootctl_attrs[] = {
&dev_attr_secure_boot_fuse_state.attr,
&dev_attr_fw_reset.attr,
&dev_attr_rsh_log.attr,
+ &dev_attr_large_icm.attr,
+ &dev_attr_os_up.attr,
+ &dev_attr_oob_mac.attr,
+ &dev_attr_opn.attr,
+ &dev_attr_sku.attr,
+ &dev_attr_modl.attr,
+ &dev_attr_sn.attr,
+ &dev_attr_uuid.attr,
+ &dev_attr_rev.attr,
+ &dev_attr_mfg_lock.attr,
NULL
};
diff --git a/drivers/platform/mellanox/mlxbf-bootctl.h b/drivers/platform/mellanox/mlxbf-bootctl.h
index b48243f60a59f..1299750a86614 100644
--- a/drivers/platform/mellanox/mlxbf-bootctl.h
+++ b/drivers/platform/mellanox/mlxbf-bootctl.h
@@ -81,6 +81,28 @@
*/
#define MLXBF_BOOTCTL_FW_RESET 0x8200000D
+/*
+ * SMC function IDs to set, get and lock the manufacturing information
+ * stored within the eeprom.
+ */
+#define MLXBF_BOOTCTL_SET_MFG_INFO 0x8200000E
+#define MLXBF_BOOTCTL_GET_MFG_INFO 0x8200000F
+#define MLXBF_BOOTCTL_LOCK_MFG_INFO 0x82000011
+
+/*
+ * SMC function IDs to set and get the large ICM carveout size
+ * stored in the eeprom.
+ */
+#define MLNX_HANDLE_SET_ICM_INFO 0x82000012
+#define MLNX_HANDLE_GET_ICM_INFO 0x82000013
+
+#define MAX_ICM_BUFFER_SIZE 10
+
+/*
+ * SMC function ID to set the ARM boot state to up
+ */
+#define MLNX_HANDLE_OS_UP 0x82000014
+
/* SMC function IDs for SiP Service queries */
#define MLXBF_BOOTCTL_SIP_SVC_CALL_COUNT 0x8200ff00
#define MLXBF_BOOTCTL_SIP_SVC_UID 0x8200ff01
@@ -106,4 +128,9 @@
/* Additional value to disable the MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION. */
#define MLXBF_BOOTCTL_NONE 0x7fffffff /* Don't change next boot action */
+#define MLXBF_LARGE_ICMC_MAX_STRING_SIZE 16
+#define MLXBF_LARGE_ICMC_SIZE_MIN 0x80
+#define MLXBF_LARGE_ICMC_SIZE_MAX 0x100000
+#define MLXBF_LARGE_ICMC_GRANULARITY MLXBF_LARGE_ICMC_SIZE_MIN
+
#endif /* __MLXBF_BOOTCTL_H__ */
diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c
index b7dcc64cd2386..eb5ad35274dd6 100644
--- a/drivers/platform/mellanox/mlxreg-hotplug.c
+++ b/drivers/platform/mellanox/mlxreg-hotplug.c
@@ -12,7 +12,6 @@
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/module.h>
-#include <linux/of_device.h>
#include <linux/platform_data/mlxreg.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
@@ -113,7 +112,7 @@ static int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv,
* Return if adapter number is negative. It could be in case hotplug
* event is not associated with hotplug device.
*/
- if (data->hpdev.nr < 0)
+ if (data->hpdev.nr < 0 && data->hpdev.action != MLXREG_HOTPLUG_DEVICE_NO_ACTION)
return 0;
pdata = dev_get_platdata(&priv->pdev->dev);
diff --git a/drivers/platform/mellanox/mlxreg-io.c b/drivers/platform/mellanox/mlxreg-io.c
index ddc08abf398c6..83ba037408cdf 100644
--- a/drivers/platform/mellanox/mlxreg-io.c
+++ b/drivers/platform/mellanox/mlxreg-io.c
@@ -11,7 +11,6 @@
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/module.h>
-#include <linux/of_device.h>
#include <linux/platform_data/mlxreg.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
diff --git a/drivers/platform/mellanox/nvsw-sn2201.c b/drivers/platform/mellanox/nvsw-sn2201.c
index 7b9c107c17ce6..75b699676ca6d 100644
--- a/drivers/platform/mellanox/nvsw-sn2201.c
+++ b/drivers/platform/mellanox/nvsw-sn2201.c
@@ -84,6 +84,10 @@
#define NVSW_SN2201_MAIN_MUX_CH5_NR (NVSW_SN2201_MAIN_MUX_CH0_NR + 5)
#define NVSW_SN2201_MAIN_MUX_CH6_NR (NVSW_SN2201_MAIN_MUX_CH0_NR + 6)
#define NVSW_SN2201_MAIN_MUX_CH7_NR (NVSW_SN2201_MAIN_MUX_CH0_NR + 7)
+#define NVSW_SN2201_2ND_MUX_CH0_NR (NVSW_SN2201_MAIN_MUX_CH7_NR + 1)
+#define NVSW_SN2201_2ND_MUX_CH1_NR (NVSW_SN2201_MAIN_MUX_CH7_NR + 2)
+#define NVSW_SN2201_2ND_MUX_CH2_NR (NVSW_SN2201_MAIN_MUX_CH7_NR + 3)
+#define NVSW_SN2201_2ND_MUX_CH3_NR (NVSW_SN2201_MAIN_MUX_CH7_NR + 4)
#define NVSW_SN2201_CPLD_NR NVSW_SN2201_MAIN_MUX_CH0_NR
#define NVSW_SN2201_NR_NONE -1
@@ -425,28 +429,28 @@ static struct mlxreg_core_data nvsw_sn2201_fan_items_data[] = {
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
.mask = BIT(0),
.hpdev.brdinfo = &nvsw_sn2201_fan_devices[0],
- .hpdev.nr = NVSW_SN2201_NR_NONE,
+ .hpdev.nr = NVSW_SN2201_2ND_MUX_CH0_NR,
},
{
.label = "fan2",
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
.mask = BIT(1),
.hpdev.brdinfo = &nvsw_sn2201_fan_devices[1],
- .hpdev.nr = NVSW_SN2201_NR_NONE,
+ .hpdev.nr = NVSW_SN2201_2ND_MUX_CH1_NR,
},
{
.label = "fan3",
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
.mask = BIT(2),
.hpdev.brdinfo = &nvsw_sn2201_fan_devices[2],
- .hpdev.nr = NVSW_SN2201_NR_NONE,
+ .hpdev.nr = NVSW_SN2201_2ND_MUX_CH2_NR,
},
{
.label = "fan4",
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
.mask = BIT(3),
.hpdev.brdinfo = &nvsw_sn2201_fan_devices[3],
- .hpdev.nr = NVSW_SN2201_NR_NONE,
+ .hpdev.nr = NVSW_SN2201_2ND_MUX_CH3_NR,
},
};
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 49c2c4cd8d000..2a10705433911 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -965,7 +965,7 @@ config SERIAL_MULTI_INSTANTIATE
config MLX_PLATFORM
tristate "Mellanox Technologies platform support"
- depends on I2C
+ depends on ACPI && I2C && PCI
select REGMAP
help
This option enables system support for the Mellanox Technologies
@@ -1074,17 +1074,7 @@ config INTEL_SCU_IPC_UTIL
low level access for debug work and updating the firmware. Say
N unless you will be doing this on an Intel MID platform.
-config SIEMENS_SIMATIC_IPC
- tristate "Siemens Simatic IPC Class driver"
- depends on PCI
- help
- This Simatic IPC class driver is the central of several drivers. It
- is mainly used for system identification, after which drivers in other
- classes will take care of driving specifics of those machines.
- i.e. LEDs and watchdog.
-
- To compile this driver as a module, choose M here: the module
- will be called simatic-ipc.
+source "drivers/platform/x86/siemens/Kconfig"
config WINMATE_FM07_KEYS
tristate "Winmate FM07/FM07P front-panel keys driver"
@@ -1094,10 +1084,25 @@ config WINMATE_FM07_KEYS
buttons below the display. This module adds an input device
that delivers key events when these buttons are pressed.
+config SEL3350_PLATFORM
+ tristate "SEL-3350 LEDs and power supplies"
+ depends on ACPI
+ depends on GPIOLIB
+ depends on PINCTRL_BROXTON
+ select POWER_SUPPLY
+ select NEW_LEDS
+ select LEDS_CLASS
+ select LEDS_GPIO
+ help
+ Support for LEDs and power supplies on SEL-3350 computers.
+
+ To compile this driver as a module, choose M here: the module
+ will be called sel3350-platform.
+
endif # X86_PLATFORM_DEVICES
config P2SB
- bool "Primary to Sideband (P2SB) bridge access support"
+ bool
depends on PCI && X86
help
The Primary to Sideband (P2SB) bridge is an interface to some
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 52dfdf574ac2d..b457de5abf7d7 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -131,7 +131,10 @@ obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
obj-$(CONFIG_X86_INTEL_LPSS) += pmc_atom.o
# Siemens Simatic Industrial PCs
-obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += simatic-ipc.o
+obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += siemens/
# Winmate
obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o
+
+# SEL
+obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o
diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig
index d9685aef0887d..55f3a2fc6aec7 100644
--- a/drivers/platform/x86/amd/Kconfig
+++ b/drivers/platform/x86/amd/Kconfig
@@ -4,21 +4,7 @@
#
source "drivers/platform/x86/amd/pmf/Kconfig"
-
-config AMD_PMC
- tristate "AMD SoC PMC driver"
- depends on ACPI && PCI && RTC_CLASS && AMD_NB
- select SERIO
- help
- The driver provides support for AMD Power Management Controller
- primarily responsible for S2Idle transactions that are driven from
- a platform firmware running on SMU. This driver also provides a debug
- mechanism to investigate the S2Idle transactions and failures.
-
- Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU.
-
- If you choose to compile this driver as a module the module will be
- called amd-pmc.
+source "drivers/platform/x86/amd/pmc/Kconfig"
config AMD_HSMP
tristate "AMD HSMP Driver"
diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile
index 65732f0a3913f..f04932b7a7d12 100644
--- a/drivers/platform/x86/amd/Makefile
+++ b/drivers/platform/x86/amd/Makefile
@@ -4,8 +4,7 @@
# AMD x86 Platform-Specific Drivers
#
-amd-pmc-y := pmc.o pmc-quirks.o
-obj-$(CONFIG_AMD_PMC) += amd-pmc.o
+obj-$(CONFIG_AMD_PMC) += pmc/
amd_hsmp-y := hsmp.o
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
obj-$(CONFIG_AMD_PMF) += pmf/
diff --git a/drivers/platform/x86/amd/pmc/Kconfig b/drivers/platform/x86/amd/pmc/Kconfig
new file mode 100644
index 0000000000000..883c0a95ac0cf
--- /dev/null
+++ b/drivers/platform/x86/amd/pmc/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# AMD PMC Driver
+#
+
+config AMD_PMC
+ tristate "AMD SoC PMC driver"
+ depends on ACPI && PCI && RTC_CLASS && AMD_NB
+ depends on SUSPEND
+ select SERIO
+ help
+ The driver provides support for AMD Power Management Controller
+ primarily responsible for S2Idle transactions that are driven from
+ a platform firmware running on SMU. This driver also provides a debug
+ mechanism to investigate the S2Idle transactions and failures.
+
+ Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU.
+
+ If you choose to compile this driver as a module the module will be
+ called amd-pmc.
diff --git a/drivers/platform/x86/amd/pmc/Makefile b/drivers/platform/x86/amd/pmc/Makefile
new file mode 100644
index 0000000000000..4aaa29d351c90
--- /dev/null
+++ b/drivers/platform/x86/amd/pmc/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/x86/amd/pmc
+# AMD Power Management Controller Driver
+#
+
+amd-pmc-objs := pmc.o pmc-quirks.o
+obj-$(CONFIG_AMD_PMC) += amd-pmc.o
diff --git a/drivers/platform/x86/amd/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c
index ad702463a65d3..ad702463a65d3 100644
--- a/drivers/platform/x86/amd/pmc-quirks.c
+++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
diff --git a/drivers/platform/x86/amd/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index c1e788b67a748..c1e788b67a748 100644
--- a/drivers/platform/x86/amd/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
diff --git a/drivers/platform/x86/amd/pmc.h b/drivers/platform/x86/amd/pmc/pmc.h
index c27bd6a5642f4..c27bd6a5642f4 100644
--- a/drivers/platform/x86/amd/pmc.h
+++ b/drivers/platform/x86/amd/pmc/pmc.h
diff --git a/drivers/platform/x86/amd/pmf/cnqf.c b/drivers/platform/x86/amd/pmf/cnqf.c
index 539b186e90276..bc8899e15c914 100644
--- a/drivers/platform/x86/amd/pmf/cnqf.c
+++ b/drivers/platform/x86/amd/pmf/cnqf.c
@@ -8,6 +8,7 @@
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
+#include <linux/string_choices.h>
#include <linux/workqueue.h>
#include "pmf.h"
@@ -399,7 +400,7 @@ static ssize_t cnqf_enable_store(struct device *dev,
amd_pmf_set_sps_power_limits(pdev);
}
- dev_dbg(pdev->dev, "Received CnQF %s\n", input ? "on" : "off");
+ dev_dbg(pdev->dev, "Received CnQF %s\n", str_on_off(input));
return count;
}
@@ -409,7 +410,7 @@ static ssize_t cnqf_enable_show(struct device *dev,
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
- return sysfs_emit(buf, "%s\n", pdev->cnqf_enabled ? "on" : "off");
+ return sysfs_emit(buf, "%s\n", str_on_off(pdev->cnqf_enabled));
}
static DEVICE_ATTR_RW(cnqf_enable);
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index 57bf1a9f0e766..78ed3ee22555d 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -324,7 +324,8 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
{
- if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
+ if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR) ||
+ is_apmf_func_supported(dev, APMF_FUNC_OS_POWER_SLIDER_UPDATE)) {
power_supply_unreg_notifier(&dev->pwr_src_notifier);
amd_pmf_deinit_sps(dev);
}
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 8bef66a2f0ce7..9f8cea5f9615a 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -72,6 +72,7 @@ module_param(fnlock_default, bool, 0444);
#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
+#define ASUS_MID_FAN_DESC "mid_fan"
#define ASUS_GPU_FAN_DESC "gpu_fan"
#define ASUS_FAN_DESC "cpu_fan"
#define ASUS_FAN_MFUN 0x13
@@ -112,9 +113,20 @@ module_param(fnlock_default, bool, 0444);
#define FAN_CURVE_BUF_LEN 32
#define FAN_CURVE_DEV_CPU 0x00
#define FAN_CURVE_DEV_GPU 0x01
+#define FAN_CURVE_DEV_MID 0x02
/* Mask to determine if setting temperature or percentage */
#define FAN_CURVE_PWM_MASK 0x04
+/* Limits for tunables available on ASUS ROG laptops */
+#define PPT_TOTAL_MIN 5
+#define PPT_TOTAL_MAX 250
+#define PPT_CPU_MIN 5
+#define PPT_CPU_MAX 130
+#define NVIDIA_BOOST_MIN 5
+#define NVIDIA_BOOST_MAX 25
+#define NVIDIA_TEMP_MIN 75
+#define NVIDIA_TEMP_MAX 87
+
static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
static int throttle_thermal_policy_write(struct asus_wmi *);
@@ -229,18 +241,31 @@ struct asus_wmi {
enum fan_type fan_type;
enum fan_type gpu_fan_type;
+ enum fan_type mid_fan_type;
int fan_pwm_mode;
int gpu_fan_pwm_mode;
+ int mid_fan_pwm_mode;
int agfn_pwm;
bool fan_boost_mode_available;
u8 fan_boost_mode_mask;
u8 fan_boost_mode;
+ bool charge_mode_available;
bool egpu_enable_available;
+ bool egpu_connect_available;
bool dgpu_disable_available;
bool gpu_mux_mode_available;
+ /* Tunables provided by ASUS for gaming laptops */
+ bool ppt_pl2_sppt_available;
+ bool ppt_pl1_spl_available;
+ bool ppt_apu_sppt_available;
+ bool ppt_plat_sppt_available;
+ bool ppt_fppt_available;
+ bool nv_dyn_boost_available;
+ bool nv_temp_tgt_available;
+
bool kbd_rgb_mode_available;
bool kbd_rgb_state_available;
@@ -249,7 +274,8 @@ struct asus_wmi {
bool cpu_fan_curve_available;
bool gpu_fan_curve_available;
- struct fan_curve_data custom_fan_curves[2];
+ bool mid_fan_curve_available;
+ struct fan_curve_data custom_fan_curves[3];
struct platform_profile_handler platform_profile_handler;
bool platform_profile_support;
@@ -258,6 +284,7 @@ struct asus_wmi {
bool battery_rsoc_available;
bool panel_overdrive_available;
+ bool mini_led_mode_available;
struct hotplug_slot hotplug_slot;
struct mutex hotplug_lock;
@@ -586,6 +613,22 @@ static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus)
asus_wmi_tablet_sw_report(asus, result);
}
+/* Charging mode, 1=Barrel, 2=USB ******************************************/
+static ssize_t charge_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int result, value;
+
+ result = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_CHARGE_MODE, &value);
+ if (result < 0)
+ return result;
+
+ return sysfs_emit(buf, "%d\n", value & 0xff);
+}
+
+static DEVICE_ATTR_RO(charge_mode);
+
/* dGPU ********************************************************************/
static ssize_t dgpu_disable_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -622,6 +665,18 @@ static ssize_t dgpu_disable_store(struct device *dev,
if (disable > 1)
return -EINVAL;
+ if (asus->gpu_mux_mode_available) {
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+ if (result < 0)
+ /* An error here may signal greater failure of GPU handling */
+ return result;
+ if (!result && disable) {
+ err = -ENODEV;
+ pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err);
+ return err;
+ }
+ }
+
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, disable, &result);
if (err) {
pr_warn("Failed to set dgpu disable: %d\n", err);
@@ -670,14 +725,34 @@ static ssize_t egpu_enable_store(struct device *dev,
if (enable > 1)
return -EINVAL;
+ err = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
+ if (err < 0) {
+ pr_warn("Failed to get egpu connection status: %d\n", err);
+ return err;
+ }
+
+ if (asus->gpu_mux_mode_available) {
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
+ if (result < 0) {
+ /* An error here may signal greater failure of GPU handling */
+ pr_warn("Failed to get gpu mux status: %d\n", result);
+ return result;
+ }
+ if (!result && enable) {
+ err = -ENODEV;
+ pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err);
+ return err;
+ }
+ }
+
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result);
if (err) {
- pr_warn("Failed to set egpu disable: %d\n", err);
+ pr_warn("Failed to set egpu state: %d\n", err);
return err;
}
if (result > 1) {
- pr_warn("Failed to set egpu disable (retval): 0x%x\n", result);
+ pr_warn("Failed to set egpu state (retval): 0x%x\n", result);
return -EIO;
}
@@ -687,6 +762,22 @@ static ssize_t egpu_enable_store(struct device *dev,
}
static DEVICE_ATTR_RW(egpu_enable);
+/* Is eGPU connected? *********************************************************/
+static ssize_t egpu_connected_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int result;
+
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
+ if (result < 0)
+ return result;
+
+ return sysfs_emit(buf, "%d\n", result);
+}
+
+static DEVICE_ATTR_RO(egpu_connected);
+
/* gpu mux switch *************************************************************/
static ssize_t gpu_mux_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -716,6 +807,30 @@ static ssize_t gpu_mux_mode_store(struct device *dev,
if (optimus > 1)
return -EINVAL;
+ if (asus->dgpu_disable_available) {
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_DGPU);
+ if (result < 0)
+ /* An error here may signal greater failure of GPU handling */
+ return result;
+ if (result && !optimus) {
+ err = -ENODEV;
+ pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %d\n", err);
+ return err;
+ }
+ }
+
+ if (asus->egpu_enable_available) {
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU);
+ if (result < 0)
+ /* An error here may signal greater failure of GPU handling */
+ return result;
+ if (result && !optimus) {
+ err = -ENODEV;
+ pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n", err);
+ return err;
+ }
+ }
+
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_MUX, optimus, &result);
if (err) {
dev_err(dev, "Failed to set GPU MUX mode: %d\n", err);
@@ -859,6 +974,244 @@ static const struct attribute_group *kbd_rgb_mode_groups[] = {
NULL,
};
+/* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/
+static ssize_t ppt_pl2_sppt_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 value;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &value);
+ if (result)
+ return result;
+
+ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL2_SPPT, value, &result);
+ if (err) {
+ pr_warn("Failed to set ppt_pl2_sppt: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set ppt_pl2_sppt (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl2_sppt");
+
+ return count;
+}
+static DEVICE_ATTR_WO(ppt_pl2_sppt);
+
+/* Tunable: PPT, Intel=PL1, AMD=SPL ******************************************/
+static ssize_t ppt_pl1_spl_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 value;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &value);
+ if (result)
+ return result;
+
+ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL1_SPL, value, &result);
+ if (err) {
+ pr_warn("Failed to set ppt_pl1_spl: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set ppt_pl1_spl (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl1_spl");
+
+ return count;
+}
+static DEVICE_ATTR_WO(ppt_pl1_spl);
+
+/* Tunable: PPT APU FPPT ******************************************************/
+static ssize_t ppt_fppt_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 value;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &value);
+ if (result)
+ return result;
+
+ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_FPPT, value, &result);
+ if (err) {
+ pr_warn("Failed to set ppt_fppt: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set ppt_fppt (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_fpu_sppt");
+
+ return count;
+}
+static DEVICE_ATTR_WO(ppt_fppt);
+
+/* Tunable: PPT APU SPPT *****************************************************/
+static ssize_t ppt_apu_sppt_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 value;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &value);
+ if (result)
+ return result;
+
+ if (value < PPT_CPU_MIN || value > PPT_CPU_MAX)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_APU_SPPT, value, &result);
+ if (err) {
+ pr_warn("Failed to set ppt_apu_sppt: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set ppt_apu_sppt (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_apu_sppt");
+
+ return count;
+}
+static DEVICE_ATTR_WO(ppt_apu_sppt);
+
+/* Tunable: PPT platform SPPT ************************************************/
+static ssize_t ppt_platform_sppt_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 value;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &value);
+ if (result)
+ return result;
+
+ if (value < PPT_CPU_MIN || value > PPT_CPU_MAX)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PLAT_SPPT, value, &result);
+ if (err) {
+ pr_warn("Failed to set ppt_platform_sppt: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set ppt_platform_sppt (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_platform_sppt");
+
+ return count;
+}
+static DEVICE_ATTR_WO(ppt_platform_sppt);
+
+/* Tunable: NVIDIA dynamic boost *********************************************/
+static ssize_t nv_dynamic_boost_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 value;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &value);
+ if (result)
+ return result;
+
+ if (value < NVIDIA_BOOST_MIN || value > NVIDIA_BOOST_MAX)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_DYN_BOOST, value, &result);
+ if (err) {
+ pr_warn("Failed to set nv_dynamic_boost: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set nv_dynamic_boost (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_dynamic_boost");
+
+ return count;
+}
+static DEVICE_ATTR_WO(nv_dynamic_boost);
+
+/* Tunable: NVIDIA temperature target ****************************************/
+static ssize_t nv_temp_target_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 value;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &value);
+ if (result)
+ return result;
+
+ if (value < NVIDIA_TEMP_MIN || value > NVIDIA_TEMP_MAX)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_THERM_TARGET, value, &result);
+ if (err) {
+ pr_warn("Failed to set nv_temp_target: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set nv_temp_target (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_temp_target");
+
+ return count;
+}
+static DEVICE_ATTR_WO(nv_temp_target);
+
/* Battery ********************************************************************/
/* The battery maximum charging percentage */
@@ -1734,6 +2087,54 @@ static ssize_t panel_od_store(struct device *dev,
}
static DEVICE_ATTR_RW(panel_od);
+/* Mini-LED mode **************************************************************/
+static ssize_t mini_led_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int result;
+
+ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
+ if (result < 0)
+ return result;
+
+ return sysfs_emit(buf, "%d\n", result);
+}
+
+static ssize_t mini_led_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 mode;
+
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ result = kstrtou32(buf, 10, &mode);
+ if (result)
+ return result;
+
+ if (mode > 1)
+ return -EINVAL;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MINI_LED_MODE, mode, &result);
+
+ if (err) {
+ pr_warn("Failed to set mini-LED: %d\n", err);
+ return err;
+ }
+
+ if (result > 1) {
+ pr_warn("Failed to set mini-LED mode (result): 0x%x\n", result);
+ return -EIO;
+ }
+
+ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mini_led_mode");
+
+ return count;
+}
+static DEVICE_ATTR_RW(mini_led_mode);
+
/* Quirks *********************************************************************/
static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
@@ -2070,6 +2471,8 @@ static ssize_t pwm1_enable_store(struct device *dev,
asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false;
if (asus->gpu_fan_curve_available)
asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false;
+ if (asus->mid_fan_curve_available)
+ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false;
return count;
}
@@ -2122,6 +2525,31 @@ static ssize_t fan2_label_show(struct device *dev,
return sysfs_emit(buf, "%s\n", ASUS_GPU_FAN_DESC);
}
+/* Middle/Center fan on modern ROG laptops */
+static ssize_t fan3_input_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int value;
+ int ret;
+
+ ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_MID_FAN_CTRL, &value);
+ if (ret < 0)
+ return ret;
+
+ value &= 0xffff;
+
+ return sysfs_emit(buf, "%d\n", value * 100);
+}
+
+static ssize_t fan3_label_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", ASUS_MID_FAN_DESC);
+}
+
static ssize_t pwm2_enable_show(struct device *dev,
struct device_attribute *attr,
char *buf)
@@ -2168,6 +2596,52 @@ static ssize_t pwm2_enable_store(struct device *dev,
return count;
}
+static ssize_t pwm3_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", asus->mid_fan_pwm_mode);
+}
+
+static ssize_t pwm3_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct asus_wmi *asus = dev_get_drvdata(dev);
+ int state;
+ int value;
+ int ret;
+ u32 retval;
+
+ ret = kstrtouint(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ switch (state) { /* standard documented hwmon values */
+ case ASUS_FAN_CTRL_FULLSPEED:
+ value = 1;
+ break;
+ case ASUS_FAN_CTRL_AUTO:
+ value = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_MID_FAN_CTRL,
+ value, &retval);
+ if (ret)
+ return ret;
+
+ if (retval != 1)
+ return -EIO;
+
+ asus->mid_fan_pwm_mode = state;
+ return count;
+}
+
/* Fan1 */
static DEVICE_ATTR_RW(pwm1);
static DEVICE_ATTR_RW(pwm1_enable);
@@ -2177,6 +2651,10 @@ static DEVICE_ATTR_RO(fan1_label);
static DEVICE_ATTR_RW(pwm2_enable);
static DEVICE_ATTR_RO(fan2_input);
static DEVICE_ATTR_RO(fan2_label);
+/* Fan3 - Middle/center fan */
+static DEVICE_ATTR_RW(pwm3_enable);
+static DEVICE_ATTR_RO(fan3_input);
+static DEVICE_ATTR_RO(fan3_label);
/* Temperature */
static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL);
@@ -2185,10 +2663,13 @@ static struct attribute *hwmon_attributes[] = {
&dev_attr_pwm1.attr,
&dev_attr_pwm1_enable.attr,
&dev_attr_pwm2_enable.attr,
+ &dev_attr_pwm3_enable.attr,
&dev_attr_fan1_input.attr,
&dev_attr_fan1_label.attr,
&dev_attr_fan2_input.attr,
&dev_attr_fan2_label.attr,
+ &dev_attr_fan3_input.attr,
+ &dev_attr_fan3_label.attr,
&dev_attr_temp1_input.attr,
NULL
@@ -2214,6 +2695,11 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
|| attr == &dev_attr_pwm2_enable.attr) {
if (asus->gpu_fan_type == FAN_TYPE_NONE)
return 0;
+ } else if (attr == &dev_attr_fan3_input.attr
+ || attr == &dev_attr_fan3_label.attr
+ || attr == &dev_attr_pwm3_enable.attr) {
+ if (asus->mid_fan_type == FAN_TYPE_NONE)
+ return 0;
} else if (attr == &dev_attr_temp1_input.attr) {
int err = asus_wmi_get_devstate(asus,
ASUS_WMI_DEVID_THERMAL_CTRL,
@@ -2257,6 +2743,7 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus)
static int asus_wmi_fan_init(struct asus_wmi *asus)
{
asus->gpu_fan_type = FAN_TYPE_NONE;
+ asus->mid_fan_type = FAN_TYPE_NONE;
asus->fan_type = FAN_TYPE_NONE;
asus->agfn_pwm = -1;
@@ -2271,6 +2758,10 @@ static int asus_wmi_fan_init(struct asus_wmi *asus)
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_FAN_CTRL))
asus->gpu_fan_type = FAN_TYPE_SPEC83;
+ /* Some models also have a center/middle fan */
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MID_FAN_CTRL))
+ asus->mid_fan_type = FAN_TYPE_SPEC83;
+
if (asus->fan_type == FAN_TYPE_NONE)
return -ENODEV;
@@ -2418,9 +2909,8 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
{
struct fan_curve_data *curves;
u8 buf[FAN_CURVE_BUF_LEN];
- int fan_idx = 0;
+ int err, fan_idx;
u8 mode = 0;
- int err;
if (asus->throttle_thermal_policy_available)
mode = asus->throttle_thermal_policy_mode;
@@ -2430,10 +2920,6 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
else if (mode == 1)
mode = 2;
- if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE)
- fan_idx = FAN_CURVE_DEV_GPU;
-
- curves = &asus->custom_fan_curves[fan_idx];
err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf,
FAN_CURVE_BUF_LEN);
if (err) {
@@ -2441,9 +2927,17 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
return err;
}
- fan_curve_copy_from_buf(curves, buf);
+ fan_idx = FAN_CURVE_DEV_CPU;
+ if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE)
+ fan_idx = FAN_CURVE_DEV_GPU;
+
+ if (fan_dev == ASUS_WMI_DEVID_MID_FAN_CURVE)
+ fan_idx = FAN_CURVE_DEV_MID;
+
+ curves = &asus->custom_fan_curves[fan_idx];
curves->device_id = fan_dev;
+ fan_curve_copy_from_buf(curves, buf);
return 0;
}
@@ -2473,7 +2967,7 @@ static struct fan_curve_data *fan_curve_attr_select(struct asus_wmi *asus,
{
int index = to_sensor_dev_attr(attr)->index;
- return &asus->custom_fan_curves[index & FAN_CURVE_DEV_GPU];
+ return &asus->custom_fan_curves[index];
}
/* Determine which fan the attribute is for if SENSOR_ATTR_2 */
@@ -2482,7 +2976,7 @@ static struct fan_curve_data *fan_curve_attr_2_select(struct asus_wmi *asus,
{
int nr = to_sensor_dev_attr_2(attr)->nr;
- return &asus->custom_fan_curves[nr & FAN_CURVE_DEV_GPU];
+ return &asus->custom_fan_curves[nr & ~FAN_CURVE_PWM_MASK];
}
static ssize_t fan_curve_show(struct device *dev,
@@ -2491,13 +2985,13 @@ static ssize_t fan_curve_show(struct device *dev,
struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
struct asus_wmi *asus = dev_get_drvdata(dev);
struct fan_curve_data *data;
- int value, index, nr;
+ int value, pwm, index;
data = fan_curve_attr_2_select(asus, attr);
+ pwm = dev_attr->nr & FAN_CURVE_PWM_MASK;
index = dev_attr->index;
- nr = dev_attr->nr;
- if (nr & FAN_CURVE_PWM_MASK)
+ if (pwm)
value = data->percents[index];
else
value = data->temps[index];
@@ -2540,23 +3034,21 @@ static ssize_t fan_curve_store(struct device *dev,
struct sensor_device_attribute_2 *dev_attr = to_sensor_dev_attr_2(attr);
struct asus_wmi *asus = dev_get_drvdata(dev);
struct fan_curve_data *data;
+ int err, pwm, index;
u8 value;
- int err;
-
- int pwm = dev_attr->nr & FAN_CURVE_PWM_MASK;
- int index = dev_attr->index;
data = fan_curve_attr_2_select(asus, attr);
+ pwm = dev_attr->nr & FAN_CURVE_PWM_MASK;
+ index = dev_attr->index;
err = kstrtou8(buf, 10, &value);
if (err < 0)
return err;
- if (pwm) {
+ if (pwm)
data->percents[index] = value;
- } else {
+ else
data->temps[index] = value;
- }
/*
* Mark as disabled so the user has to explicitly enable to apply a
@@ -2669,7 +3161,7 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_temp, fan_curve,
FAN_CURVE_DEV_CPU, 7);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, fan_curve,
- FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0);
+ FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 0);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, fan_curve,
FAN_CURVE_DEV_CPU | FAN_CURVE_PWM_MASK, 1);
static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, fan_curve,
@@ -2721,6 +3213,42 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_pwm, fan_curve,
static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, fan_curve,
FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7);
+/* MID */
+static SENSOR_DEVICE_ATTR_RW(pwm3_enable, fan_curve_enable, FAN_CURVE_DEV_MID);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_temp, fan_curve,
+ FAN_CURVE_DEV_MID, 0);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_temp, fan_curve,
+ FAN_CURVE_DEV_MID, 1);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_temp, fan_curve,
+ FAN_CURVE_DEV_MID, 2);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_temp, fan_curve,
+ FAN_CURVE_DEV_MID, 3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_temp, fan_curve,
+ FAN_CURVE_DEV_MID, 4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_temp, fan_curve,
+ FAN_CURVE_DEV_MID, 5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_temp, fan_curve,
+ FAN_CURVE_DEV_MID, 6);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_temp, fan_curve,
+ FAN_CURVE_DEV_MID, 7);
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_pwm, fan_curve,
+ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 0);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_pwm, fan_curve,
+ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 1);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_pwm, fan_curve,
+ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 2);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_pwm, fan_curve,
+ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_pwm, fan_curve,
+ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_pwm, fan_curve,
+ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_pwm, fan_curve,
+ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 6);
+static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_pwm, fan_curve,
+ FAN_CURVE_DEV_MID | FAN_CURVE_PWM_MASK, 7);
+
static struct attribute *asus_fan_curve_attr[] = {
/* CPU */
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
@@ -2758,6 +3286,24 @@ static struct attribute *asus_fan_curve_attr[] = {
&sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point7_pwm.dev_attr.attr,
&sensor_dev_attr_pwm2_auto_point8_pwm.dev_attr.attr,
+ /* MID */
+ &sensor_dev_attr_pwm3_enable.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point5_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point6_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point7_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point8_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point5_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point6_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point7_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm3_auto_point8_pwm.dev_attr.attr,
NULL
};
@@ -2777,6 +3323,9 @@ static umode_t asus_fan_curve_is_visible(struct kobject *kobj,
if (asus->gpu_fan_curve_available && attr->name[3] == '2')
return 0644;
+ if (asus->mid_fan_curve_available && attr->name[3] == '3')
+ return 0644;
+
return 0;
}
@@ -2806,7 +3355,14 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus)
if (err)
return err;
- if (!asus->cpu_fan_curve_available && !asus->gpu_fan_curve_available)
+ err = fan_curve_check_present(asus, &asus->mid_fan_curve_available,
+ ASUS_WMI_DEVID_MID_FAN_CURVE);
+ if (err)
+ return err;
+
+ if (!asus->cpu_fan_curve_available
+ && !asus->gpu_fan_curve_available
+ && !asus->mid_fan_curve_available)
return 0;
hwmon = devm_hwmon_device_register_with_groups(
@@ -2875,6 +3431,8 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus)
asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false;
if (asus->gpu_fan_curve_available)
asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false;
+ if (asus->mid_fan_curve_available)
+ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false;
return 0;
}
@@ -3472,14 +4030,24 @@ static struct attribute *platform_attributes[] = {
&dev_attr_camera.attr,
&dev_attr_cardr.attr,
&dev_attr_touchpad.attr,
+ &dev_attr_charge_mode.attr,
&dev_attr_egpu_enable.attr,
+ &dev_attr_egpu_connected.attr,
&dev_attr_dgpu_disable.attr,
&dev_attr_gpu_mux_mode.attr,
&dev_attr_lid_resume.attr,
&dev_attr_als_enable.attr,
&dev_attr_fan_boost_mode.attr,
&dev_attr_throttle_thermal_policy.attr,
+ &dev_attr_ppt_pl2_sppt.attr,
+ &dev_attr_ppt_pl1_spl.attr,
+ &dev_attr_ppt_fppt.attr,
+ &dev_attr_ppt_apu_sppt.attr,
+ &dev_attr_ppt_platform_sppt.attr,
+ &dev_attr_nv_dynamic_boost.attr,
+ &dev_attr_nv_temp_target.attr,
&dev_attr_panel_od.attr,
+ &dev_attr_mini_led_mode.attr,
NULL
};
@@ -3501,8 +4069,12 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
devid = ASUS_WMI_DEVID_LID_RESUME;
else if (attr == &dev_attr_als_enable.attr)
devid = ASUS_WMI_DEVID_ALS_ENABLE;
+ else if (attr == &dev_attr_charge_mode.attr)
+ ok = asus->charge_mode_available;
else if (attr == &dev_attr_egpu_enable.attr)
ok = asus->egpu_enable_available;
+ else if (attr == &dev_attr_egpu_connected.attr)
+ ok = asus->egpu_connect_available;
else if (attr == &dev_attr_dgpu_disable.attr)
ok = asus->dgpu_disable_available;
else if (attr == &dev_attr_gpu_mux_mode.attr)
@@ -3511,8 +4083,24 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
ok = asus->fan_boost_mode_available;
else if (attr == &dev_attr_throttle_thermal_policy.attr)
ok = asus->throttle_thermal_policy_available;
+ else if (attr == &dev_attr_ppt_pl2_sppt.attr)
+ ok = asus->ppt_pl2_sppt_available;
+ else if (attr == &dev_attr_ppt_pl1_spl.attr)
+ ok = asus->ppt_pl1_spl_available;
+ else if (attr == &dev_attr_ppt_fppt.attr)
+ ok = asus->ppt_fppt_available;
+ else if (attr == &dev_attr_ppt_apu_sppt.attr)
+ ok = asus->ppt_apu_sppt_available;
+ else if (attr == &dev_attr_ppt_platform_sppt.attr)
+ ok = asus->ppt_plat_sppt_available;
+ else if (attr == &dev_attr_nv_dynamic_boost.attr)
+ ok = asus->nv_dyn_boost_available;
+ else if (attr == &dev_attr_nv_temp_target.attr)
+ ok = asus->nv_temp_tgt_available;
else if (attr == &dev_attr_panel_od.attr)
ok = asus->panel_overdrive_available;
+ else if (attr == &dev_attr_mini_led_mode.attr)
+ ok = asus->mini_led_mode_available;
if (devid != -1)
ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
@@ -3767,12 +4355,22 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_platform;
+ asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE);
asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
+ asus->egpu_connect_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX);
asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE);
asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
+ asus->ppt_pl2_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL2_SPPT);
+ asus->ppt_pl1_spl_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL1_SPL);
+ asus->ppt_fppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_FPPT);
+ asus->ppt_apu_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_APU_SPPT);
+ asus->ppt_plat_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PLAT_SPPT);
+ asus->nv_dyn_boost_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_DYN_BOOST);
+ asus->nv_temp_tgt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_THERM_TARGET);
asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD);
+ asus->mini_led_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
err = fan_boost_mode_check_present(asus);
if (err)
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
index b68dd11cb8924..b929b4f824205 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
@@ -393,6 +393,7 @@ static int init_bios_attributes(int attr_type, const char *guid)
struct kobject *attr_name_kobj; //individual attribute names
union acpi_object *obj = NULL;
union acpi_object *elements;
+ struct kobject *duplicate;
struct kset *tmp_set;
int min_elements;
@@ -451,9 +452,11 @@ static int init_bios_attributes(int attr_type, const char *guid)
else
tmp_set = wmi_priv.main_dir_kset;
- if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) {
- pr_debug("duplicate attribute name found - %s\n",
- elements[ATTR_NAME].string.pointer);
+ duplicate = kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer);
+ if (duplicate) {
+ pr_debug("Duplicate attribute name found - %s\n",
+ elements[ATTR_NAME].string.pointer);
+ kobject_put(duplicate);
goto nextobj;
}
diff --git a/drivers/platform/x86/hp/Kconfig b/drivers/platform/x86/hp/Kconfig
index ae165955311ce..7fef4f12e4989 100644
--- a/drivers/platform/x86/hp/Kconfig
+++ b/drivers/platform/x86/hp/Kconfig
@@ -60,4 +60,20 @@ config TC1100_WMI
This is a driver for the WMI extensions (wireless and bluetooth power
control) of the HP Compaq TC1100 tablet.
+config HP_BIOSCFG
+ tristate "HP BIOS Configuration Driver"
+ default m
+ depends on ACPI_WMI
+ select NLS
+ select FW_ATTR_CLASS
+ help
+ This driver enables administrators to securely manage BIOS settings
+ using digital certificates and public-key cryptography that eliminate
+ the need for passwords for both remote and local management. It supports
+ changing BIOS settings on many HP machines from 2018 and newer without
+ the use of any additional software.
+
+ To compile this driver as a module, choose M here: the module will
+ be called hp-bioscfg.
+
endif # X86_PLATFORM_DRIVERS_HP
diff --git a/drivers/platform/x86/hp/Makefile b/drivers/platform/x86/hp/Makefile
index db1eed4cd7c7d..e4f908a61acfa 100644
--- a/drivers/platform/x86/hp/Makefile
+++ b/drivers/platform/x86/hp/Makefile
@@ -8,3 +8,4 @@
obj-$(CONFIG_HP_ACCEL) += hp_accel.o
obj-$(CONFIG_HP_WMI) += hp-wmi.o
obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
+obj-$(CONFIG_HP_BIOSCFG) += hp-bioscfg/
diff --git a/drivers/platform/x86/hp/hp-bioscfg/Makefile b/drivers/platform/x86/hp/hp-bioscfg/Makefile
new file mode 100644
index 0000000000000..67be0d9177530
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/Makefile
@@ -0,0 +1,11 @@
+obj-$(CONFIG_HP_BIOSCFG) := hp-bioscfg.o
+
+hp-bioscfg-objs := bioscfg.o \
+ biosattr-interface.o \
+ enum-attributes.o \
+ int-attributes.o \
+ order-list-attributes.o \
+ passwdobj-attributes.o \
+ spmobj-attributes.o \
+ string-attributes.o \
+ surestart-attributes.o
diff --git a/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
new file mode 100644
index 0000000000000..dea54f35b8b5d
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/biosattr-interface.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to methods under BIOS interface GUID
+ * for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 Hewlett-Packard Inc.
+ */
+
+#include <linux/wmi.h>
+#include "bioscfg.h"
+
+/*
+ * struct bios_args buffer is dynamically allocated. New WMI command types
+ * were introduced that exceeds 128-byte data size. Changes to handle
+ * the data size allocation scheme were kept in hp_wmi_perform_query function.
+ */
+struct bios_args {
+ u32 signature;
+ u32 command;
+ u32 commandtype;
+ u32 datasize;
+ u8 data[];
+};
+
+/**
+ * hp_set_attribute
+ *
+ * @a_name: The attribute name
+ * @a_value: The attribute value
+ *
+ * Sets an attribute to new value
+ *
+ * Returns zero on success
+ * -ENODEV if device is not found
+ * -EINVAL if the instance of 'Setup Admin' password is not found.
+ * -ENOMEM unable to allocate memory
+ */
+int hp_set_attribute(const char *a_name, const char *a_value)
+{
+ int security_area_size;
+ int a_name_size, a_value_size;
+ u16 *buffer = NULL;
+ u16 *start;
+ int buffer_size, instance, ret;
+ char *auth_token_choice;
+
+ mutex_lock(&bioscfg_drv.mutex);
+
+ instance = hp_get_password_instance_for_type(SETUP_PASSWD);
+ if (instance < 0) {
+ ret = -EINVAL;
+ goto out_set_attribute;
+ }
+
+ /* Select which auth token to use; password or [auth token] */
+ if (bioscfg_drv.spm_data.auth_token)
+ auth_token_choice = bioscfg_drv.spm_data.auth_token;
+ else
+ auth_token_choice = bioscfg_drv.password_data[instance].current_password;
+
+ a_name_size = hp_calculate_string_buffer(a_name);
+ a_value_size = hp_calculate_string_buffer(a_value);
+ security_area_size = hp_calculate_security_buffer(auth_token_choice);
+ buffer_size = a_name_size + a_value_size + security_area_size;
+
+ buffer = kmalloc(buffer_size + 1, GFP_KERNEL);
+ if (!buffer) {
+ ret = -ENOMEM;
+ goto out_set_attribute;
+ }
+
+ /* build variables to set */
+ start = buffer;
+ start = hp_ascii_to_utf16_unicode(start, a_name);
+ if (!start) {
+ ret = -EINVAL;
+ goto out_set_attribute;
+ }
+
+ start = hp_ascii_to_utf16_unicode(start, a_value);
+ if (!start) {
+ ret = -EINVAL;
+ goto out_set_attribute;
+ }
+
+ ret = hp_populate_security_buffer(start, auth_token_choice);
+ if (ret < 0)
+ goto out_set_attribute;
+
+ ret = hp_wmi_set_bios_setting(buffer, buffer_size);
+
+out_set_attribute:
+ kfree(buffer);
+ mutex_unlock(&bioscfg_drv.mutex);
+ return ret;
+}
+
+/**
+ * hp_wmi_perform_query
+ *
+ * @query: The commandtype (enum hp_wmi_commandtype)
+ * @command: The command (enum hp_wmi_command)
+ * @buffer: Buffer used as input and/or output
+ * @insize: Size of input buffer
+ * @outsize: Size of output buffer
+ *
+ * returns zero on success
+ * an HP WMI query specific error code (which is positive)
+ * -EINVAL if the query was not successful at all
+ * -EINVAL if the output buffer size exceeds buffersize
+ *
+ * Note: The buffersize must at least be the maximum of the input and output
+ * size. E.g. Battery info query is defined to have 1 byte input
+ * and 128 byte output. The caller would do:
+ * buffer = kzalloc(128, GFP_KERNEL);
+ * ret = hp_wmi_perform_query(HPWMI_BATTERY_QUERY, HPWMI_READ,
+ * buffer, 1, 128)
+ */
+int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer,
+ u32 insize, u32 outsize)
+{
+ struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct bios_return *bios_return;
+ union acpi_object *obj = NULL;
+ struct bios_args *args = NULL;
+ int mid, actual_outsize, ret;
+ size_t bios_args_size;
+
+ mid = hp_encode_outsize_for_pvsz(outsize);
+ if (WARN_ON(mid < 0))
+ return mid;
+
+ bios_args_size = struct_size(args, data, insize);
+ args = kmalloc(bios_args_size, GFP_KERNEL);
+ if (!args)
+ return -ENOMEM;
+
+ input.length = bios_args_size;
+ input.pointer = args;
+
+ /* BIOS expects 'SECU' in hex as the signature value*/
+ args->signature = 0x55434553;
+ args->command = command;
+ args->commandtype = query;
+ args->datasize = insize;
+ memcpy(args->data, buffer, flex_array_size(args, data, insize));
+
+ ret = wmi_evaluate_method(HP_WMI_BIOS_GUID, 0, mid, &input, &output);
+ if (ret)
+ goto out_free;
+
+ obj = output.pointer;
+ if (!obj) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ 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;
+ }
+
+ bios_return = (struct bios_return *)obj->buffer.pointer;
+ ret = bios_return->return_code;
+ if (ret) {
+ if (ret != INVALID_CMD_VALUE && ret != INVALID_CMD_TYPE)
+ pr_warn("query 0x%x returned error 0x%x\n", query, ret);
+ goto out_free;
+ }
+
+ /* Ignore output data of zero size */
+ if (!outsize)
+ goto out_free;
+
+ actual_outsize = min_t(u32, outsize, obj->buffer.length - sizeof(*bios_return));
+ memcpy_and_pad(buffer, outsize, obj->buffer.pointer + sizeof(*bios_return),
+ actual_outsize, 0);
+
+out_free:
+ ret = hp_wmi_error_and_message(ret);
+
+ kfree(obj);
+ kfree(args);
+ return ret;
+}
+
+static void *utf16_empty_string(u16 *p)
+{
+ *p++ = 2;
+ *p++ = 0x00;
+ return p;
+}
+
+/**
+ * hp_ascii_to_utf16_unicode - Convert ascii string to UTF-16 unicode
+ *
+ * BIOS supports UTF-16 characters that are 2 bytes long. No variable
+ * multi-byte language supported.
+ *
+ * @p: Unicode buffer address
+ * @str: string to convert to unicode
+ *
+ * Returns a void pointer to the buffer string
+ */
+void *hp_ascii_to_utf16_unicode(u16 *p, const u8 *str)
+{
+ int len = strlen(str);
+ int ret;
+
+ /*
+ * Add null character when reading an empty string
+ * "02 00 00 00"
+ */
+ if (len == 0)
+ return utf16_empty_string(p);
+
+ /* Move pointer len * 2 number of bytes */
+ *p++ = len * 2;
+ ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, p, len);
+ if (ret < 0) {
+ dev_err(bioscfg_drv.class_dev, "UTF16 conversion failed\n");
+ return NULL;
+ }
+
+ if (ret * sizeof(u16) > U16_MAX) {
+ dev_err(bioscfg_drv.class_dev, "Error string too long\n");
+ return NULL;
+ }
+
+ p += len;
+ return p;
+}
+
+/**
+ * hp_wmi_set_bios_setting - Set setting's value in BIOS
+ *
+ * @input_buffer: Input buffer address
+ * @input_size: Input buffer size
+ *
+ * Returns: Count of unicode characters written to BIOS if successful, otherwise
+ * -ENOMEM unable to allocate memory
+ * -EINVAL buffer not allocated or too small
+ */
+int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size)
+{
+ union acpi_object *obj;
+ struct acpi_buffer input = {input_size, input_buffer};
+ struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
+ int ret;
+
+ ret = wmi_evaluate_method(HP_WMI_SET_BIOS_SETTING_GUID, 0, 1, &input, &output);
+
+ obj = output.pointer;
+ if (!obj)
+ return -EINVAL;
+
+ if (obj->type != ACPI_TYPE_INTEGER) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ ret = obj->integer.value;
+ if (ret) {
+ ret = hp_wmi_error_and_message(ret);
+ goto out_free;
+ }
+
+out_free:
+ kfree(obj);
+ return ret;
+}
+
+static int hp_attr_set_interface_probe(struct wmi_device *wdev, const void *context)
+{
+ mutex_lock(&bioscfg_drv.mutex);
+ mutex_unlock(&bioscfg_drv.mutex);
+ return 0;
+}
+
+static void hp_attr_set_interface_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&bioscfg_drv.mutex);
+ mutex_unlock(&bioscfg_drv.mutex);
+}
+
+static const struct wmi_device_id hp_attr_set_interface_id_table[] = {
+ { .guid_string = HP_WMI_BIOS_GUID},
+ { }
+};
+
+static struct wmi_driver hp_attr_set_interface_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+ .probe = hp_attr_set_interface_probe,
+ .remove = hp_attr_set_interface_remove,
+ .id_table = hp_attr_set_interface_id_table,
+};
+
+int hp_init_attr_set_interface(void)
+{
+ return wmi_driver_register(&hp_attr_set_interface_driver);
+}
+
+void hp_exit_attr_set_interface(void)
+{
+ wmi_driver_unregister(&hp_attr_set_interface_driver);
+}
+
+MODULE_DEVICE_TABLE(wmi, hp_attr_set_interface_id_table);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
new file mode 100644
index 0000000000000..8c4f9e12f0181
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
@@ -0,0 +1,1063 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Common methods for use with hp-bioscfg driver
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/wmi.h>
+#include "bioscfg.h"
+#include "../../firmware_attributes_class.h"
+#include <linux/nls.h>
+#include <linux/errno.h>
+
+MODULE_AUTHOR("Jorge Lopez <jorge.lopez2@hp.com>");
+MODULE_DESCRIPTION("HP BIOS Configuration Driver");
+MODULE_LICENSE("GPL");
+
+struct bioscfg_priv bioscfg_drv = {
+ .mutex = __MUTEX_INITIALIZER(bioscfg_drv.mutex),
+};
+
+static struct class *fw_attr_class;
+
+ssize_t display_name_language_code_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", LANG_CODE_STR);
+}
+
+struct kobj_attribute common_display_langcode =
+ __ATTR_RO(display_name_language_code);
+
+int hp_get_integer_from_buffer(u8 **buffer, u32 *buffer_size, u32 *integer)
+{
+ int *ptr = PTR_ALIGN((int *)*buffer, sizeof(int));
+
+ /* Ensure there is enough space remaining to read the integer */
+ if (*buffer_size < sizeof(int))
+ return -EINVAL;
+
+ *integer = *(ptr++);
+ *buffer = (u8 *)ptr;
+ *buffer_size -= sizeof(int);
+
+ return 0;
+}
+
+int hp_get_string_from_buffer(u8 **buffer, u32 *buffer_size, char *dst, u32 dst_size)
+{
+ u16 *src = (u16 *)*buffer;
+ u16 src_size;
+
+ u16 size;
+ int i;
+ int conv_dst_size;
+
+ if (*buffer_size < sizeof(u16))
+ return -EINVAL;
+
+ src_size = *(src++);
+ /* size value in u16 chars */
+ size = src_size / sizeof(u16);
+
+ /* Ensure there is enough space remaining to read and convert
+ * the string
+ */
+ if (*buffer_size < src_size)
+ return -EINVAL;
+
+ for (i = 0; i < size; i++)
+ if (src[i] == '\\' ||
+ src[i] == '\r' ||
+ src[i] == '\n' ||
+ src[i] == '\t')
+ size++;
+
+ /*
+ * Conversion is limited to destination string max number of
+ * bytes.
+ */
+ conv_dst_size = size;
+ if (size > dst_size)
+ conv_dst_size = dst_size - 1;
+
+ /*
+ * 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;
+ }
+
+ if (*src == '\r')
+ dst[i] = 'r';
+ else if (*src == '\n')
+ dst[i] = 'n';
+ else if (*src == '\t')
+ dst[i] = 't';
+ else if (*src == '"')
+ dst[i] = '\'';
+ else
+ dst[i] = *src;
+ src++;
+ }
+
+ *buffer = (u8 *)src;
+ *buffer_size -= size * sizeof(u16);
+
+ return size;
+}
+
+int hp_get_common_data_from_buffer(u8 **buffer_ptr, u32 *buffer_size,
+ struct common_data *common_data)
+{
+ int ret = 0;
+ int reqs;
+
+ // PATH:
+ ret = hp_get_string_from_buffer(buffer_ptr, buffer_size, common_data->path,
+ sizeof(common_data->path));
+ if (ret < 0)
+ goto common_exit;
+
+ // IS_READONLY:
+ ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size,
+ &common_data->is_readonly);
+ if (ret < 0)
+ goto common_exit;
+
+ //DISPLAY_IN_UI:
+ ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size,
+ &common_data->display_in_ui);
+ if (ret < 0)
+ goto common_exit;
+
+ // REQUIRES_PHYSICAL_PRESENCE:
+ ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size,
+ &common_data->requires_physical_presence);
+ if (ret < 0)
+ goto common_exit;
+
+ // SEQUENCE:
+ ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size,
+ &common_data->sequence);
+ if (ret < 0)
+ goto common_exit;
+
+ // PREREQUISITES_SIZE:
+ ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size,
+ &common_data->prerequisites_size);
+ if (ret < 0)
+ goto common_exit;
+
+ if (common_data->prerequisites_size > MAX_PREREQUISITES_SIZE) {
+ /* Report a message and limit prerequisite size to maximum value */
+ pr_warn("Prerequisites size value exceeded the maximum number of elements supported or data may be malformed\n");
+ common_data->prerequisites_size = MAX_PREREQUISITES_SIZE;
+ }
+
+ // PREREQUISITES:
+ for (reqs = 0; reqs < common_data->prerequisites_size; reqs++) {
+ ret = hp_get_string_from_buffer(buffer_ptr, buffer_size,
+ common_data->prerequisites[reqs],
+ sizeof(common_data->prerequisites[reqs]));
+ if (ret < 0)
+ break;
+ }
+
+ // SECURITY_LEVEL:
+ ret = hp_get_integer_from_buffer(buffer_ptr, buffer_size,
+ &common_data->security_level);
+
+common_exit:
+ return ret;
+}
+
+int hp_enforce_single_line_input(char *buf, size_t count)
+{
+ char *p;
+
+ p = memchr(buf, '\n', count);
+
+ if (p == buf + count - 1)
+ *p = '\0'; /* strip trailing newline */
+ else if (p)
+ return -EINVAL; /* enforce single line input */
+
+ return 0;
+}
+
+/* Set pending reboot value and generate KOBJ_NAME event */
+void hp_set_reboot_and_signal_event(void)
+{
+ bioscfg_drv.pending_reboot = true;
+ kobject_uevent(&bioscfg_drv.class_dev->kobj, KOBJ_CHANGE);
+}
+
+/**
+ * hp_calculate_string_buffer() - determines size of string buffer for
+ * use with BIOS communication
+ *
+ * @str: the string to calculate based upon
+ */
+size_t hp_calculate_string_buffer(const char *str)
+{
+ size_t length = strlen(str);
+
+ /* BIOS expects 4 bytes when an empty string is found */
+ if (length == 0)
+ return 4;
+
+ /* u16 length field + one UTF16 char for each input char */
+ return sizeof(u16) + strlen(str) * sizeof(u16);
+}
+
+int hp_wmi_error_and_message(int error_code)
+{
+ char *error_msg = NULL;
+ int ret;
+
+ switch (error_code) {
+ case SUCCESS:
+ error_msg = "Success";
+ ret = 0;
+ break;
+ case CMD_FAILED:
+ error_msg = "Command failed";
+ ret = -EINVAL;
+ break;
+ case INVALID_SIGN:
+ error_msg = "Invalid signature";
+ ret = -EINVAL;
+ break;
+ case INVALID_CMD_VALUE:
+ error_msg = "Invalid command value/Feature not supported";
+ ret = -EOPNOTSUPP;
+ break;
+ case INVALID_CMD_TYPE:
+ error_msg = "Invalid command type";
+ ret = -EINVAL;
+ break;
+ case INVALID_DATA_SIZE:
+ error_msg = "Invalid data size";
+ ret = -EINVAL;
+ break;
+ case INVALID_CMD_PARAM:
+ error_msg = "Invalid command parameter";
+ ret = -EINVAL;
+ break;
+ case ENCRYP_CMD_REQUIRED:
+ error_msg = "Secure/encrypted command required";
+ ret = -EACCES;
+ break;
+ case NO_SECURE_SESSION:
+ error_msg = "No secure session established";
+ ret = -EACCES;
+ break;
+ case SECURE_SESSION_FOUND:
+ error_msg = "Secure session already established";
+ ret = -EACCES;
+ break;
+ case SECURE_SESSION_FAILED:
+ error_msg = "Secure session failed";
+ ret = -EIO;
+ break;
+ case AUTH_FAILED:
+ error_msg = "Other permission/Authentication failed";
+ ret = -EACCES;
+ break;
+ case INVALID_BIOS_AUTH:
+ error_msg = "Invalid BIOS administrator password";
+ ret = -EINVAL;
+ break;
+ case NONCE_DID_NOT_MATCH:
+ error_msg = "Nonce did not match";
+ ret = -EINVAL;
+ break;
+ case GENERIC_ERROR:
+ error_msg = "Generic/Other error";
+ ret = -EIO;
+ break;
+ case BIOS_ADMIN_POLICY_NOT_MET:
+ error_msg = "BIOS Admin password does not meet password policy requirements";
+ ret = -EINVAL;
+ break;
+ case BIOS_ADMIN_NOT_SET:
+ error_msg = "BIOS Setup password is not set";
+ ret = -EPERM;
+ break;
+ case P21_NO_PROVISIONED:
+ error_msg = "P21 is not provisioned";
+ ret = -EPERM;
+ break;
+ case P21_PROVISION_IN_PROGRESS:
+ error_msg = "P21 is already provisioned or provisioning is in progress and a signing key has already been sent";
+ ret = -EINPROGRESS;
+ break;
+ case P21_IN_USE:
+ error_msg = "P21 in use (cannot deprovision)";
+ ret = -EPERM;
+ break;
+ case HEP_NOT_ACTIVE:
+ error_msg = "HEP not activated";
+ ret = -EPERM;
+ break;
+ case HEP_ALREADY_SET:
+ error_msg = "HEP Transport already set";
+ ret = -EINVAL;
+ break;
+ case HEP_CHECK_STATE:
+ error_msg = "Check the current HEP state";
+ ret = -EINVAL;
+ break;
+ default:
+ error_msg = "Generic/Other error";
+ ret = -EIO;
+ break;
+ }
+
+ if (error_code)
+ pr_warn_ratelimited("Returned error 0x%x, \"%s\"\n", error_code, error_msg);
+
+ return ret;
+}
+
+static ssize_t pending_reboot_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%d\n", bioscfg_drv.pending_reboot);
+}
+
+static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
+
+/*
+ * create_attributes_level_sysfs_files() - Creates pending_reboot attributes
+ */
+static int create_attributes_level_sysfs_files(void)
+{
+ return sysfs_create_file(&bioscfg_drv.main_dir_kset->kobj,
+ &pending_reboot.attr);
+}
+
+static void attr_name_release(struct kobject *kobj)
+{
+ kfree(kobj);
+}
+
+static const struct kobj_type attr_name_ktype = {
+ .release = attr_name_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+};
+
+/**
+ * hp_get_wmiobj_pointer() - Get Content of WMI block for particular instance
+ *
+ * @instance_id: WMI instance ID
+ * @guid_string: WMI GUID (in str form)
+ *
+ * Fetches the content for WMI block (instance_id) under GUID (guid_string)
+ * Caller must kfree the return
+ */
+union acpi_object *hp_get_wmiobj_pointer(int instance_id, const char *guid_string)
+{
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+
+ status = wmi_query_block(guid_string, instance_id, &out);
+ return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL;
+}
+
+/**
+ * hp_get_instance_count() - Compute total number of instances under guid_string
+ *
+ * @guid_string: WMI GUID (in string form)
+ */
+int hp_get_instance_count(const char *guid_string)
+{
+ union acpi_object *wmi_obj = NULL;
+ int i = 0;
+
+ do {
+ kfree(wmi_obj);
+ wmi_obj = hp_get_wmiobj_pointer(i, guid_string);
+ i++;
+ } while (wmi_obj);
+
+ return i - 1;
+}
+
+/**
+ * hp_alloc_attributes_data() - Allocate attributes data for a particular type
+ *
+ * @attr_type: Attribute type to allocate
+ */
+static int hp_alloc_attributes_data(int attr_type)
+{
+ switch (attr_type) {
+ case HPWMI_STRING_TYPE:
+ return hp_alloc_string_data();
+
+ case HPWMI_INTEGER_TYPE:
+ return hp_alloc_integer_data();
+
+ case HPWMI_ENUMERATION_TYPE:
+ return hp_alloc_enumeration_data();
+
+ case HPWMI_ORDERED_LIST_TYPE:
+ return hp_alloc_ordered_list_data();
+
+ case HPWMI_PASSWORD_TYPE:
+ return hp_alloc_password_data();
+
+ default:
+ return 0;
+ }
+}
+
+int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len)
+{
+ int ret = 0;
+ int new_len = 0;
+ char tmp[] = "0x00";
+ char *new_str = NULL;
+ long ch;
+ int i;
+
+ if (input_len <= 0 || !input || !str || !len)
+ return -EINVAL;
+
+ *len = 0;
+ *str = NULL;
+
+ new_str = kmalloc(input_len, GFP_KERNEL);
+ if (!new_str)
+ return -ENOMEM;
+
+ for (i = 0; i < input_len; i += 5) {
+ strncpy(tmp, input + i, strlen(tmp));
+ if (kstrtol(tmp, 16, &ch) == 0) {
+ // escape char
+ if (ch == '\\' ||
+ ch == '\r' ||
+ ch == '\n' || ch == '\t') {
+ if (ch == '\r')
+ ch = 'r';
+ else if (ch == '\n')
+ ch = 'n';
+ else if (ch == '\t')
+ ch = 't';
+ new_str[new_len++] = '\\';
+ }
+ new_str[new_len++] = ch;
+ if (ch == '\0')
+ break;
+ }
+ }
+
+ if (new_len) {
+ new_str[new_len] = '\0';
+ *str = krealloc(new_str, (new_len + 1) * sizeof(char),
+ GFP_KERNEL);
+ if (*str)
+ *len = new_len;
+ else
+ ret = -ENOMEM;
+ } else {
+ ret = -EFAULT;
+ }
+
+ if (ret)
+ kfree(new_str);
+ return ret;
+}
+
+/* map output size to the corresponding WMI method id */
+int hp_encode_outsize_for_pvsz(int outsize)
+{
+ if (outsize > 4096)
+ return -EINVAL;
+ if (outsize > 1024)
+ return 5;
+ if (outsize > 128)
+ return 4;
+ if (outsize > 4)
+ return 3;
+ if (outsize > 0)
+ return 2;
+ return 1;
+}
+
+/*
+ * Update friendly display name for several attributes associated to
+ * 'Schedule Power-On'
+ */
+void hp_friendly_user_name_update(char *path, const char *attr_name,
+ char *attr_display, int attr_size)
+{
+ if (strstr(path, SCHEDULE_POWER_ON))
+ snprintf(attr_display, attr_size, "%s - %s", SCHEDULE_POWER_ON, attr_name);
+ else
+ strscpy(attr_display, attr_name, attr_size);
+}
+
+/**
+ * hp_update_attribute_permissions() - Update attributes permissions when
+ * isReadOnly value is 1
+ *
+ * @is_readonly: bool value to indicate if it a readonly attribute.
+ * @current_val: kobj_attribute corresponding to attribute.
+ *
+ */
+void hp_update_attribute_permissions(bool is_readonly, struct kobj_attribute *current_val)
+{
+ current_val->attr.mode = is_readonly ? 0444 : 0644;
+}
+
+/**
+ * destroy_attribute_objs() - Free a kset of kobjects
+ * @kset: The kset to destroy
+ *
+ * Fress kobjects created for each attribute_name under attribute type kset
+ */
+static void destroy_attribute_objs(struct kset *kset)
+{
+ struct kobject *pos, *next;
+
+ list_for_each_entry_safe(pos, next, &kset->list, entry)
+ kobject_put(pos);
+}
+
+/**
+ * release_attributes_data() - Clean-up all sysfs directories and files created
+ */
+static void release_attributes_data(void)
+{
+ mutex_lock(&bioscfg_drv.mutex);
+
+ hp_exit_string_attributes();
+ hp_exit_integer_attributes();
+ hp_exit_enumeration_attributes();
+ hp_exit_ordered_list_attributes();
+ hp_exit_password_attributes();
+ hp_exit_sure_start_attributes();
+ hp_exit_secure_platform_attributes();
+
+ if (bioscfg_drv.authentication_dir_kset) {
+ destroy_attribute_objs(bioscfg_drv.authentication_dir_kset);
+ kset_unregister(bioscfg_drv.authentication_dir_kset);
+ bioscfg_drv.authentication_dir_kset = NULL;
+ }
+ if (bioscfg_drv.main_dir_kset) {
+ sysfs_remove_file(&bioscfg_drv.main_dir_kset->kobj, &pending_reboot.attr);
+ destroy_attribute_objs(bioscfg_drv.main_dir_kset);
+ kset_unregister(bioscfg_drv.main_dir_kset);
+ bioscfg_drv.main_dir_kset = NULL;
+ }
+ mutex_unlock(&bioscfg_drv.mutex);
+}
+
+/**
+ * hp_add_other_attributes() - Initialize HP custom attributes not
+ * reported by BIOS and required to support Secure Platform and Sure
+ * Start.
+ *
+ * @attr_type: Custom HP attribute not reported by BIOS
+ *
+ * Initialize all 2 types of attributes: Platform and Sure Start
+ * object. Populates each attribute types respective properties
+ * under sysfs files.
+ *
+ * Returns zero(0) if successful. Otherwise, a negative value.
+ */
+static int hp_add_other_attributes(int attr_type)
+{
+ struct kobject *attr_name_kobj;
+ union acpi_object *obj = NULL;
+ int ret;
+ char *attr_name;
+
+ mutex_lock(&bioscfg_drv.mutex);
+
+ attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ if (!attr_name_kobj) {
+ ret = -ENOMEM;
+ goto err_other_attr_init;
+ }
+
+ /* Check if attribute type is supported */
+ switch (attr_type) {
+ case HPWMI_SECURE_PLATFORM_TYPE:
+ attr_name_kobj->kset = bioscfg_drv.authentication_dir_kset;
+ attr_name = SPM_STR;
+ break;
+
+ case HPWMI_SURE_START_TYPE:
+ attr_name_kobj->kset = bioscfg_drv.main_dir_kset;
+ attr_name = SURE_START_STR;
+ break;
+
+ default:
+ pr_err("Error: Unknown attr_type: %d\n", attr_type);
+ ret = -EINVAL;
+ goto err_other_attr_init;
+ }
+
+ ret = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
+ NULL, "%s", attr_name);
+ if (ret) {
+ pr_err("Error encountered [%d]\n", ret);
+ kobject_put(attr_name_kobj);
+ goto err_other_attr_init;
+ }
+
+ /* Populate attribute data */
+ switch (attr_type) {
+ case HPWMI_SECURE_PLATFORM_TYPE:
+ ret = hp_populate_secure_platform_data(attr_name_kobj);
+ if (ret)
+ goto err_other_attr_init;
+ break;
+
+ case HPWMI_SURE_START_TYPE:
+ ret = hp_populate_sure_start_data(attr_name_kobj);
+ if (ret)
+ goto err_other_attr_init;
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto err_other_attr_init;
+ }
+
+ mutex_unlock(&bioscfg_drv.mutex);
+ return 0;
+
+err_other_attr_init:
+ mutex_unlock(&bioscfg_drv.mutex);
+ kfree(obj);
+ return ret;
+}
+
+static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type,
+ union acpi_object *obj,
+ const char *guid, int min_elements,
+ int instance_id)
+{
+ struct kobject *attr_name_kobj;
+ union acpi_object *elements;
+ struct kset *temp_kset;
+
+ char *str_value = NULL;
+ int str_len;
+ int ret = 0;
+
+ /* Take action appropriate to each ACPI TYPE */
+ if (obj->package.count < min_elements) {
+ pr_err("ACPI-package does not have enough elements: %d < %d\n",
+ obj->package.count, min_elements);
+ goto pack_attr_exit;
+ }
+
+ elements = obj->package.elements;
+
+ /* sanity checking */
+ if (elements[NAME].type != ACPI_TYPE_STRING) {
+ pr_debug("incorrect element type\n");
+ goto pack_attr_exit;
+ }
+ if (strlen(elements[NAME].string.pointer) == 0) {
+ pr_debug("empty attribute found\n");
+ goto pack_attr_exit;
+ }
+
+ if (attr_type == HPWMI_PASSWORD_TYPE)
+ temp_kset = bioscfg_drv.authentication_dir_kset;
+ else
+ temp_kset = bioscfg_drv.main_dir_kset;
+
+ /* convert attribute name to string */
+ ret = hp_convert_hexstr_to_str(elements[NAME].string.pointer,
+ elements[NAME].string.length,
+ &str_value, &str_len);
+
+ if (ret) {
+ pr_debug("Failed to populate integer package data. Error [0%0x]\n",
+ ret);
+ kfree(str_value);
+ return ret;
+ }
+
+ /* All duplicate attributes found are ignored */
+ if (kset_find_obj(temp_kset, str_value)) {
+ pr_debug("Duplicate attribute name found - %s\n", str_value);
+ goto pack_attr_exit;
+ }
+
+ /* build attribute */
+ attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ if (!attr_name_kobj) {
+ ret = -ENOMEM;
+ goto pack_attr_exit;
+ }
+
+ attr_name_kobj->kset = temp_kset;
+
+ ret = kobject_init_and_add(attr_name_kobj, &attr_name_ktype,
+ NULL, "%s", str_value);
+
+ if (ret) {
+ kobject_put(attr_name_kobj);
+ goto pack_attr_exit;
+ }
+
+ /* enumerate all of these attributes */
+ switch (attr_type) {
+ case HPWMI_STRING_TYPE:
+ ret = hp_populate_string_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_INTEGER_TYPE:
+ ret = hp_populate_integer_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_ENUMERATION_TYPE:
+ ret = hp_populate_enumeration_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_ORDERED_LIST_TYPE:
+ ret = hp_populate_ordered_list_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_PASSWORD_TYPE:
+ ret = hp_populate_password_package_data(elements,
+ instance_id,
+ attr_name_kobj);
+ break;
+ default:
+ pr_debug("Unknown attribute type found: 0x%x\n", attr_type);
+ break;
+ }
+
+pack_attr_exit:
+ kfree(str_value);
+ return ret;
+}
+
+static int hp_init_bios_buffer_attribute(enum hp_wmi_data_type attr_type,
+ union acpi_object *obj,
+ const char *guid, int min_elements,
+ int instance_id)
+{
+ struct kobject *attr_name_kobj;
+ struct kset *temp_kset;
+ char str[MAX_BUFF_SIZE];
+
+ char *temp_str = NULL;
+ char *str_value = NULL;
+ u8 *buffer_ptr = NULL;
+ int buffer_size;
+ int ret = 0;
+
+ buffer_size = obj->buffer.length;
+ buffer_ptr = obj->buffer.pointer;
+
+ ret = hp_get_string_from_buffer(&buffer_ptr,
+ &buffer_size, str, MAX_BUFF_SIZE);
+
+ if (ret < 0)
+ goto buff_attr_exit;
+
+ if (attr_type == HPWMI_PASSWORD_TYPE ||
+ attr_type == HPWMI_SECURE_PLATFORM_TYPE)
+ temp_kset = bioscfg_drv.authentication_dir_kset;
+ else
+ temp_kset = bioscfg_drv.main_dir_kset;
+
+ /* All duplicate attributes found are ignored */
+ if (kset_find_obj(temp_kset, str)) {
+ pr_debug("Duplicate attribute name found - %s\n", str);
+ goto buff_attr_exit;
+ }
+
+ /* build attribute */
+ attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ if (!attr_name_kobj) {
+ ret = -ENOMEM;
+ goto buff_attr_exit;
+ }
+
+ attr_name_kobj->kset = temp_kset;
+
+ temp_str = str;
+ if (attr_type == HPWMI_SECURE_PLATFORM_TYPE)
+ temp_str = "SPM";
+
+ ret = kobject_init_and_add(attr_name_kobj,
+ &attr_name_ktype, NULL, "%s", temp_str);
+ if (ret) {
+ kobject_put(attr_name_kobj);
+ goto buff_attr_exit;
+ }
+
+ /* enumerate all of these attributes */
+ switch (attr_type) {
+ case HPWMI_STRING_TYPE:
+ ret = hp_populate_string_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_INTEGER_TYPE:
+ ret = hp_populate_integer_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_ENUMERATION_TYPE:
+ ret = hp_populate_enumeration_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_ORDERED_LIST_TYPE:
+ ret = hp_populate_ordered_list_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ case HPWMI_PASSWORD_TYPE:
+ ret = hp_populate_password_buffer_data(buffer_ptr,
+ &buffer_size,
+ instance_id,
+ attr_name_kobj);
+ break;
+ default:
+ pr_debug("Unknown attribute type found: 0x%x\n", attr_type);
+ break;
+ }
+
+buff_attr_exit:
+ kfree(str_value);
+ return ret;
+}
+
+/**
+ * hp_init_bios_attributes() - Initialize all attributes for a type
+ * @attr_type: The attribute type to initialize
+ * @guid: The WMI GUID associated with this type to initialize
+ *
+ * Initialize all 5 types of attributes: enumeration, integer,
+ * string, password, ordered list object. Populates each attribute types
+ * respective properties under sysfs files
+ */
+static int hp_init_bios_attributes(enum hp_wmi_data_type attr_type, const char *guid)
+{
+ union acpi_object *obj = NULL;
+ int min_elements;
+
+ /* instance_id needs to be reset for each type GUID
+ * also, instance IDs are unique within GUID but not across
+ */
+ int instance_id = 0;
+ int cur_instance_id = instance_id;
+ int ret = 0;
+
+ ret = hp_alloc_attributes_data(attr_type);
+ if (ret)
+ return ret;
+
+ switch (attr_type) {
+ case HPWMI_STRING_TYPE:
+ min_elements = STR_ELEM_CNT;
+ break;
+ case HPWMI_INTEGER_TYPE:
+ min_elements = INT_ELEM_CNT;
+ break;
+ case HPWMI_ENUMERATION_TYPE:
+ min_elements = ENUM_ELEM_CNT;
+ break;
+ case HPWMI_ORDERED_LIST_TYPE:
+ min_elements = ORD_ELEM_CNT;
+ break;
+ case HPWMI_PASSWORD_TYPE:
+ min_elements = PSWD_ELEM_CNT;
+ break;
+ default:
+ pr_err("Error: Unknown attr_type: %d\n", attr_type);
+ return -EINVAL;
+ }
+
+ /* need to use specific instance_id and guid combination to get right data */
+ obj = hp_get_wmiobj_pointer(instance_id, guid);
+ if (!obj)
+ return -ENODEV;
+
+ mutex_lock(&bioscfg_drv.mutex);
+ while (obj) {
+ /* Take action appropriate to each ACPI TYPE */
+ if (obj->type == ACPI_TYPE_PACKAGE) {
+ ret = hp_init_bios_package_attribute(attr_type, obj,
+ guid, min_elements,
+ cur_instance_id);
+
+ } else if (obj->type == ACPI_TYPE_BUFFER) {
+ ret = hp_init_bios_buffer_attribute(attr_type, obj,
+ guid, min_elements,
+ cur_instance_id);
+
+ } else {
+ pr_err("Expected ACPI-package or buffer type, got: %d\n",
+ obj->type);
+ ret = -EIO;
+ goto err_attr_init;
+ }
+
+ /*
+ * Failure reported in one attribute must not
+ * stop process of the remaining attribute values.
+ */
+ if (ret >= 0)
+ cur_instance_id++;
+
+ kfree(obj);
+ instance_id++;
+ obj = hp_get_wmiobj_pointer(instance_id, guid);
+ }
+
+err_attr_init:
+ mutex_unlock(&bioscfg_drv.mutex);
+ kfree(obj);
+ return ret;
+}
+
+static int __init hp_init(void)
+{
+ int ret;
+ int hp_bios_capable = wmi_has_guid(HP_WMI_BIOS_GUID);
+ int set_bios_settings = wmi_has_guid(HP_WMI_SET_BIOS_SETTING_GUID);
+
+ if (!hp_bios_capable) {
+ pr_err("Unable to run on non-HP system\n");
+ return -ENODEV;
+ }
+
+ if (!set_bios_settings) {
+ pr_err("Unable to set BIOS settings on HP systems\n");
+ return -ENODEV;
+ }
+
+ ret = hp_init_attr_set_interface();
+ if (ret)
+ return ret;
+
+ ret = fw_attributes_class_get(&fw_attr_class);
+ if (ret)
+ goto err_unregister_class;
+
+ bioscfg_drv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
+ NULL, "%s", DRIVER_NAME);
+ if (IS_ERR(bioscfg_drv.class_dev)) {
+ ret = PTR_ERR(bioscfg_drv.class_dev);
+ goto err_unregister_class;
+ }
+
+ bioscfg_drv.main_dir_kset = kset_create_and_add("attributes", NULL,
+ &bioscfg_drv.class_dev->kobj);
+ if (!bioscfg_drv.main_dir_kset) {
+ ret = -ENOMEM;
+ pr_debug("Failed to create and add attributes\n");
+ goto err_destroy_classdev;
+ }
+
+ bioscfg_drv.authentication_dir_kset = kset_create_and_add("authentication", NULL,
+ &bioscfg_drv.class_dev->kobj);
+ if (!bioscfg_drv.authentication_dir_kset) {
+ ret = -ENOMEM;
+ pr_debug("Failed to create and add authentication\n");
+ goto err_release_attributes_data;
+ }
+
+ /*
+ * sysfs level attributes.
+ * - pending_reboot
+ */
+ ret = create_attributes_level_sysfs_files();
+ if (ret)
+ pr_debug("Failed to create sysfs level attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_STRING_TYPE, HP_WMI_BIOS_STRING_GUID);
+ if (ret)
+ pr_debug("Failed to populate string type attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_INTEGER_TYPE, HP_WMI_BIOS_INTEGER_GUID);
+ if (ret)
+ pr_debug("Failed to populate integer type attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_ENUMERATION_TYPE, HP_WMI_BIOS_ENUMERATION_GUID);
+ if (ret)
+ pr_debug("Failed to populate enumeration type attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_ORDERED_LIST_TYPE, HP_WMI_BIOS_ORDERED_LIST_GUID);
+ if (ret)
+ pr_debug("Failed to populate ordered list object type attributes\n");
+
+ ret = hp_init_bios_attributes(HPWMI_PASSWORD_TYPE, HP_WMI_BIOS_PASSWORD_GUID);
+ if (ret)
+ pr_debug("Failed to populate password object type attributes\n");
+
+ bioscfg_drv.spm_data.attr_name_kobj = NULL;
+ ret = hp_add_other_attributes(HPWMI_SECURE_PLATFORM_TYPE);
+ if (ret)
+ pr_debug("Failed to populate secure platform object type attribute\n");
+
+ bioscfg_drv.sure_start_attr_kobj = NULL;
+ ret = hp_add_other_attributes(HPWMI_SURE_START_TYPE);
+ if (ret)
+ pr_debug("Failed to populate sure start object type attribute\n");
+
+ return 0;
+
+err_release_attributes_data:
+ release_attributes_data();
+
+err_destroy_classdev:
+ device_destroy(fw_attr_class, MKDEV(0, 0));
+
+err_unregister_class:
+ fw_attributes_class_put();
+ hp_exit_attr_set_interface();
+
+ return ret;
+}
+
+static void __exit hp_exit(void)
+{
+ release_attributes_data();
+ device_destroy(fw_attr_class, MKDEV(0, 0));
+
+ fw_attributes_class_put();
+ hp_exit_attr_set_interface();
+}
+
+module_init(hp_init);
+module_exit(hp_exit);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
new file mode 100644
index 0000000000000..3166ef328ebae
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
@@ -0,0 +1,487 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Definitions for kernel modules using hp_bioscfg driver
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#ifndef _HP_BIOSCFG_H_
+#define _HP_BIOSCFG_H_
+
+#include <linux/wmi.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/nls.h>
+
+#define DRIVER_NAME "hp-bioscfg"
+
+#define MAX_BUFF_SIZE 512
+#define MAX_KEY_MOD_SIZE 256
+#define MAX_PASSWD_SIZE 64
+#define MAX_PREREQUISITES_SIZE 20
+#define MAX_REQ_ELEM_SIZE 128
+#define MAX_VALUES_SIZE 16
+#define MAX_ENCODINGS_SIZE 16
+#define MAX_ELEMENTS_SIZE 16
+
+#define SPM_STR_DESC "Secure Platform Management"
+#define SPM_STR "SPM"
+#define SURE_START_DESC "Sure Start"
+#define SURE_START_STR "Sure_Start"
+#define SETUP_PASSWD "Setup Password"
+#define POWER_ON_PASSWD "Power-On Password"
+
+#define LANG_CODE_STR "en_US.UTF-8"
+#define SCHEDULE_POWER_ON "Scheduled Power-On"
+
+#define COMMA_SEP ","
+#define SEMICOLON_SEP ";"
+
+/* Sure Admin Functions */
+
+#define UTF_PREFIX "<utf-16/>"
+#define BEAM_PREFIX "<BEAM/>"
+
+enum mechanism_values {
+ PASSWORD = 0x00,
+ SIGNING_KEY = 0x01,
+ ENDORSEMENT_KEY = 0x02,
+};
+
+#define BIOS_ADMIN "bios-admin"
+#define POWER_ON "power-on"
+#define BIOS_SPM "enhanced-bios-auth"
+
+#define PASSWD_MECHANISM_TYPES "password"
+
+#define HP_WMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
+
+#define HP_WMI_BIOS_STRING_GUID "988D08E3-68F4-4c35-AF3E-6A1B8106F83C"
+#define HP_WMI_BIOS_INTEGER_GUID "8232DE3D-663D-4327-A8F4-E293ADB9BF05"
+#define HP_WMI_BIOS_ENUMERATION_GUID "2D114B49-2DFB-4130-B8FE-4A3C09E75133"
+#define HP_WMI_BIOS_ORDERED_LIST_GUID "14EA9746-CE1F-4098-A0E0-7045CB4DA745"
+#define HP_WMI_BIOS_PASSWORD_GUID "322F2028-0F84-4901-988E-015176049E2D"
+#define HP_WMI_SET_BIOS_SETTING_GUID "1F4C91EB-DC5C-460b-951D-C7CB9B4B8D5E"
+
+enum hp_wmi_spm_commandtype {
+ HPWMI_SECUREPLATFORM_GET_STATE = 0x10,
+ HPWMI_SECUREPLATFORM_SET_KEK = 0x11,
+ HPWMI_SECUREPLATFORM_SET_SK = 0x12,
+};
+
+enum hp_wmi_surestart_commandtype {
+ HPWMI_SURESTART_GET_LOG_COUNT = 0x01,
+ HPWMI_SURESTART_GET_LOG = 0x02,
+};
+
+enum hp_wmi_command {
+ HPWMI_READ = 0x01,
+ HPWMI_WRITE = 0x02,
+ HPWMI_ODM = 0x03,
+ HPWMI_SURESTART = 0x20006,
+ HPWMI_GM = 0x20008,
+ HPWMI_SECUREPLATFORM = 0x20010,
+};
+
+struct bios_return {
+ u32 sigpass;
+ u32 return_code;
+};
+
+enum wmi_error_values {
+ SUCCESS = 0x00,
+ CMD_FAILED = 0x01,
+ INVALID_SIGN = 0x02,
+ INVALID_CMD_VALUE = 0x03,
+ INVALID_CMD_TYPE = 0x04,
+ INVALID_DATA_SIZE = 0x05,
+ INVALID_CMD_PARAM = 0x06,
+ ENCRYP_CMD_REQUIRED = 0x07,
+ NO_SECURE_SESSION = 0x08,
+ SECURE_SESSION_FOUND = 0x09,
+ SECURE_SESSION_FAILED = 0x0A,
+ AUTH_FAILED = 0x0B,
+ INVALID_BIOS_AUTH = 0x0E,
+ NONCE_DID_NOT_MATCH = 0x18,
+ GENERIC_ERROR = 0x1C,
+ BIOS_ADMIN_POLICY_NOT_MET = 0x28,
+ BIOS_ADMIN_NOT_SET = 0x38,
+ P21_NO_PROVISIONED = 0x1000,
+ P21_PROVISION_IN_PROGRESS = 0x1001,
+ P21_IN_USE = 0x1002,
+ HEP_NOT_ACTIVE = 0x1004,
+ HEP_ALREADY_SET = 0x1006,
+ HEP_CHECK_STATE = 0x1007,
+};
+
+struct common_data {
+ u8 display_name[MAX_BUFF_SIZE];
+ u8 path[MAX_BUFF_SIZE];
+ u32 is_readonly;
+ u32 display_in_ui;
+ u32 requires_physical_presence;
+ u32 sequence;
+ u32 prerequisites_size;
+ u8 prerequisites[MAX_PREREQUISITES_SIZE][MAX_BUFF_SIZE];
+ u32 security_level;
+};
+
+struct string_data {
+ struct common_data common;
+ struct kobject *attr_name_kobj;
+ u8 current_value[MAX_BUFF_SIZE];
+ u8 new_value[MAX_BUFF_SIZE];
+ u32 min_length;
+ u32 max_length;
+};
+
+struct integer_data {
+ struct common_data common;
+ struct kobject *attr_name_kobj;
+ u32 current_value;
+ u32 new_value;
+ u32 lower_bound;
+ u32 upper_bound;
+ u32 scalar_increment;
+};
+
+struct enumeration_data {
+ struct common_data common;
+ struct kobject *attr_name_kobj;
+ u8 current_value[MAX_BUFF_SIZE];
+ u8 new_value[MAX_BUFF_SIZE];
+ u32 possible_values_size;
+ u8 possible_values[MAX_VALUES_SIZE][MAX_BUFF_SIZE];
+};
+
+struct ordered_list_data {
+ struct common_data common;
+ struct kobject *attr_name_kobj;
+ u8 current_value[MAX_BUFF_SIZE];
+ u8 new_value[MAX_BUFF_SIZE];
+ u32 elements_size;
+ u8 elements[MAX_ELEMENTS_SIZE][MAX_BUFF_SIZE];
+};
+
+struct password_data {
+ struct common_data common;
+ struct kobject *attr_name_kobj;
+ u8 current_password[MAX_PASSWD_SIZE];
+ u8 new_password[MAX_PASSWD_SIZE];
+ u32 min_password_length;
+ u32 max_password_length;
+ u32 encodings_size;
+ u8 encodings[MAX_ENCODINGS_SIZE][MAX_BUFF_SIZE];
+ bool is_enabled;
+
+ /*
+ * 'role' identifies the type of authentication.
+ * Two known types are bios-admin and power-on.
+ * 'bios-admin' represents BIOS administrator password
+ * 'power-on' represents a password required to use the system
+ */
+ u32 role;
+
+ /*
+ * 'mechanism' represents the means of authentication.
+ * Only supported type currently is "password"
+ */
+ u32 mechanism;
+};
+
+struct secure_platform_data {
+ struct kobject *attr_name_kobj;
+ u8 attribute_name[MAX_BUFF_SIZE];
+ u8 *endorsement_key;
+ u8 *signing_key;
+ u8 *auth_token;
+ bool is_enabled;
+ u32 mechanism;
+};
+
+struct bioscfg_priv {
+ struct kset *authentication_dir_kset;
+ struct kset *main_dir_kset;
+ struct device *class_dev;
+ struct string_data *string_data;
+ u32 string_instances_count;
+ struct integer_data *integer_data;
+ u32 integer_instances_count;
+ struct enumeration_data *enumeration_data;
+ u32 enumeration_instances_count;
+ struct ordered_list_data *ordered_list_data;
+ u32 ordered_list_instances_count;
+ struct password_data *password_data;
+ u32 password_instances_count;
+
+ struct kobject *sure_start_attr_kobj;
+ struct secure_platform_data spm_data;
+ u8 display_name_language_code[MAX_BUFF_SIZE];
+ bool pending_reboot;
+ struct mutex mutex;
+};
+
+/* global structure used by multiple WMI interfaces */
+extern struct bioscfg_priv bioscfg_drv;
+
+enum hp_wmi_data_type {
+ HPWMI_STRING_TYPE,
+ HPWMI_INTEGER_TYPE,
+ HPWMI_ENUMERATION_TYPE,
+ HPWMI_ORDERED_LIST_TYPE,
+ HPWMI_PASSWORD_TYPE,
+ HPWMI_SECURE_PLATFORM_TYPE,
+ HPWMI_SURE_START_TYPE,
+};
+
+enum hp_wmi_data_elements {
+ /* Common elements */
+ NAME = 0,
+ VALUE = 1,
+ PATH = 2,
+ IS_READONLY = 3,
+ DISPLAY_IN_UI = 4,
+ REQUIRES_PHYSICAL_PRESENCE = 5,
+ SEQUENCE = 6,
+ PREREQUISITES_SIZE = 7,
+ PREREQUISITES = 8,
+ SECURITY_LEVEL = 9,
+
+ /* String elements */
+ STR_MIN_LENGTH = 10,
+ STR_MAX_LENGTH = 11,
+ STR_ELEM_CNT = 12,
+
+ /* Integer elements */
+ INT_LOWER_BOUND = 10,
+ INT_UPPER_BOUND = 11,
+ INT_SCALAR_INCREMENT = 12,
+ INT_ELEM_CNT = 13,
+
+ /* Enumeration elements */
+ ENUM_CURRENT_VALUE = 10,
+ ENUM_SIZE = 11,
+ ENUM_POSSIBLE_VALUES = 12,
+ ENUM_ELEM_CNT = 13,
+
+ /* Ordered list elements */
+ ORD_LIST_SIZE = 10,
+ ORD_LIST_ELEMENTS = 11,
+ ORD_ELEM_CNT = 12,
+
+ /* Password elements */
+ PSWD_MIN_LENGTH = 10,
+ PSWD_MAX_LENGTH = 11,
+ PSWD_SIZE = 12,
+ PSWD_ENCODINGS = 13,
+ PSWD_IS_SET = 14,
+ PSWD_ELEM_CNT = 15,
+};
+
+#define GET_INSTANCE_ID(type) \
+ static int get_##type##_instance_id(struct kobject *kobj) \
+ { \
+ int i; \
+ \
+ for (i = 0; i <= bioscfg_drv.type##_instances_count; i++) { \
+ if (!strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name)) \
+ return i; \
+ } \
+ return -EIO; \
+ }
+
+#define ATTRIBUTE_S_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+ { \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].name); \
+ return -EIO; \
+ }
+
+#define ATTRIBUTE_N_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+ { \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data[i].name); \
+ return -EIO; \
+ }
+
+#define ATTRIBUTE_PROPERTY_STORE(curr_val, type) \
+ static ssize_t curr_val##_store(struct kobject *kobj, \
+ struct kobj_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ char *attr_value = NULL; \
+ int i; \
+ int ret = -EIO; \
+ \
+ attr_value = kstrdup(buf, GFP_KERNEL); \
+ if (!attr_value) \
+ return -ENOMEM; \
+ \
+ ret = hp_enforce_single_line_input(attr_value, count); \
+ if (!ret) { \
+ i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ ret = validate_##type##_input(i, attr_value); \
+ } \
+ if (!ret) \
+ ret = hp_set_attribute(kobj->name, attr_value); \
+ if (!ret) { \
+ update_##type##_value(i, attr_value); \
+ if (bioscfg_drv.type##_data[i].common.requires_physical_presence) \
+ hp_set_reboot_and_signal_event(); \
+ } \
+ hp_clear_all_credentials(); \
+ kfree(attr_value); \
+ \
+ return ret ? ret : count; \
+ }
+
+#define ATTRIBUTE_SPM_N_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ return sysfs_emit(buf, "%d\n", bioscfg_drv.type##_data.name); \
+ }
+
+#define ATTRIBUTE_SPM_S_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data.name); \
+ }
+
+#define ATTRIBUTE_VALUES_PROPERTY_SHOW(name, type, sep) \
+ static ssize_t name##_show(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+ { \
+ int i; \
+ int len = 0; \
+ int instance_id = get_##type##_instance_id(kobj); \
+ \
+ if (instance_id < 0) \
+ return 0; \
+ \
+ for (i = 0; i < bioscfg_drv.type##_data[instance_id].name##_size; i++) { \
+ if (i) \
+ len += sysfs_emit_at(buf, len, "%s", sep); \
+ \
+ len += sysfs_emit_at(buf, len, "%s", \
+ bioscfg_drv.type##_data[instance_id].name[i]); \
+ } \
+ len += sysfs_emit_at(buf, len, "\n"); \
+ return len; \
+ }
+
+#define ATTRIBUTE_S_COMMON_PROPERTY_SHOW(name, type) \
+ static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \
+ char *buf) \
+ { \
+ int i = get_##type##_instance_id(kobj); \
+ if (i >= 0) \
+ return sysfs_emit(buf, "%s\n", bioscfg_drv.type##_data[i].common.name); \
+ return -EIO; \
+ }
+
+extern struct kobj_attribute common_display_langcode;
+
+/* Prototypes */
+
+/* String attributes */
+int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int hp_alloc_string_data(void);
+void hp_exit_string_attributes(void);
+int hp_populate_string_package_data(union acpi_object *str_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+
+/* Integer attributes */
+int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int hp_alloc_integer_data(void);
+void hp_exit_integer_attributes(void);
+int hp_populate_integer_package_data(union acpi_object *integer_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+
+/* Enumeration attributes */
+int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int hp_alloc_enumeration_data(void);
+void hp_exit_enumeration_attributes(void);
+int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+
+/* Ordered list */
+int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr,
+ u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int hp_alloc_ordered_list_data(void);
+void hp_exit_ordered_list_attributes(void);
+int hp_populate_ordered_list_package_data(union acpi_object *order_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+
+/* Password authentication attributes */
+int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int hp_populate_password_package_data(union acpi_object *password_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj);
+int hp_alloc_password_data(void);
+int hp_get_password_instance_for_type(const char *name);
+int hp_clear_all_credentials(void);
+int hp_set_attribute(const char *a_name, const char *a_value);
+
+/* SPM attributes */
+void hp_exit_password_attributes(void);
+void hp_exit_secure_platform_attributes(void);
+int hp_populate_secure_platform_data(struct kobject *attr_name_kobj);
+int hp_populate_security_buffer(u16 *buffer, const char *authentication);
+
+/* Bios Attributes interface */
+int hp_wmi_set_bios_setting(u16 *input_buffer, u32 input_size);
+int hp_wmi_perform_query(int query, enum hp_wmi_command command,
+ void *buffer, u32 insize, u32 outsize);
+
+/* Sure Start attributes */
+void hp_exit_sure_start_attributes(void);
+int hp_populate_sure_start_data(struct kobject *attr_name_kobj);
+
+/* Bioscfg */
+
+void hp_exit_attr_set_interface(void);
+int hp_init_attr_set_interface(void);
+size_t hp_calculate_string_buffer(const char *str);
+size_t hp_calculate_security_buffer(const char *authentication);
+void *hp_ascii_to_utf16_unicode(u16 *p, const u8 *str);
+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);
+int hp_convert_hexstr_to_str(const char *input, u32 input_len, char **str, int *len);
+int hp_encode_outsize_for_pvsz(int outsize);
+int hp_enforce_single_line_input(char *buf, size_t count);
+void hp_set_reboot_and_signal_event(void);
+ssize_t display_name_language_code_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf);
+union acpi_object *hp_get_wmiobj_pointer(int instance_id, const char *guid_string);
+int hp_get_instance_count(const char *guid_string);
+void hp_update_attribute_permissions(bool isreadonly, struct kobj_attribute *current_val);
+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);
+
+#endif
diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
new file mode 100644
index 0000000000000..a2402d31c1462
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to enumeration type attributes under
+ * BIOS Enumeration GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+
+GET_INSTANCE_ID(enumeration);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_enumeration_instance_id(kobj);
+
+ if (instance_id < 0)
+ return -EIO;
+
+ return sysfs_emit(buf, "%s\n",
+ bioscfg_drv.enumeration_data[instance_id].current_value);
+}
+
+/**
+ * validate_enumeration_input() -
+ * Validate input of current_value against possible values
+ *
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_enumeration_input(int instance_id, const char *buf)
+{
+ int i;
+ int found = 0;
+ struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
+
+ /* Is it a read only attribute */
+ if (enum_data->common.is_readonly)
+ return -EIO;
+
+ for (i = 0; i < enum_data->possible_values_size && !found; i++)
+ if (!strcmp(enum_data->possible_values[i], buf))
+ found = 1;
+
+ if (!found)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void update_enumeration_value(int instance_id, char *attr_value)
+{
+ struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
+
+ strscpy(enum_data->current_value,
+ attr_value,
+ sizeof(enum_data->current_value));
+}
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, enumeration);
+static struct kobj_attribute enumeration_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_PROPERTY_STORE(current_value, enumeration);
+static struct kobj_attribute enumeration_current_val =
+ __ATTR_RW(current_value);
+
+ATTRIBUTE_VALUES_PROPERTY_SHOW(possible_values, enumeration, SEMICOLON_SEP);
+static struct kobj_attribute enumeration_poss_val =
+ __ATTR_RO(possible_values);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "enumeration\n");
+}
+
+static struct kobj_attribute enumeration_type =
+ __ATTR_RO(type);
+
+static struct attribute *enumeration_attrs[] = {
+ &common_display_langcode.attr,
+ &enumeration_display_name.attr,
+ &enumeration_current_val.attr,
+ &enumeration_poss_val.attr,
+ &enumeration_type.attr,
+ NULL
+};
+
+static const struct attribute_group enumeration_attr_group = {
+ .attrs = enumeration_attrs,
+};
+
+int hp_alloc_enumeration_data(void)
+{
+ bioscfg_drv.enumeration_instances_count =
+ hp_get_instance_count(HP_WMI_BIOS_ENUMERATION_GUID);
+
+ bioscfg_drv.enumeration_data = kcalloc(bioscfg_drv.enumeration_instances_count,
+ sizeof(*bioscfg_drv.enumeration_data), GFP_KERNEL);
+ if (!bioscfg_drv.enumeration_data) {
+ bioscfg_drv.enumeration_instances_count = 0;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_enum_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [ENUM_CURRENT_VALUE] = ACPI_TYPE_STRING,
+ [ENUM_SIZE] = ACPI_TYPE_INTEGER,
+ [ENUM_POSSIBLE_VALUES] = ACPI_TYPE_STRING,
+};
+
+static int hp_populate_enumeration_elements_from_package(union acpi_object *enum_obj,
+ int enum_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ u32 size = 0;
+ u32 int_value = 0;
+ int elem = 0;
+ int reqs;
+ int pos_values;
+ int ret;
+ int eloc;
+ struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
+
+ for (elem = 1, eloc = 1; elem < enum_obj_count; elem++, eloc++) {
+ /* ONLY look at the first ENUM_ELEM_CNT elements */
+ if (eloc == ENUM_ELEM_CNT)
+ goto exit_enumeration_package;
+
+ switch (enum_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+ if (PREREQUISITES != elem && ENUM_POSSIBLE_VALUES != elem) {
+ ret = hp_convert_hexstr_to_str(enum_obj[elem].string.pointer,
+ enum_obj[elem].string.length,
+ &str_value, &value_len);
+ if (ret)
+ return -EINVAL;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)enum_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", enum_obj[elem].type);
+ continue;
+ }
+
+ /* Check that both expected and read object type match */
+ if (expected_enum_types[eloc] != enum_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_enum_types[eloc], elem, enum_obj[elem].type);
+ kfree(str_value);
+ return -EIO;
+ }
+
+ /* Assign appropriate element value to corresponding field */
+ switch (eloc) {
+ case NAME:
+ case VALUE:
+ break;
+ case PATH:
+ strscpy(enum_data->common.path, str_value,
+ sizeof(enum_data->common.path));
+ 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 >= 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,
+ sizeof(enum_data->common.prerequisites[reqs]));
+
+ kfree(str_value);
+ str_value = NULL;
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ enum_data->common.security_level = int_value;
+ break;
+
+ case ENUM_CURRENT_VALUE:
+ strscpy(enum_data->current_value,
+ str_value, sizeof(enum_data->current_value));
+ break;
+ case ENUM_SIZE:
+ if (int_value > MAX_VALUES_SIZE) {
+ pr_warn("Possible number values size value exceeded the maximum number of elements supported or data may be malformed\n");
+ int_value = MAX_VALUES_SIZE;
+ }
+ enum_data->possible_values_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. POSSIBLE_VALUES
+ * object is omitted by BIOS when the size is zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+
+ case ENUM_POSSIBLE_VALUES:
+ size = enum_data->possible_values_size;
+
+ for (pos_values = 0; pos_values < size && pos_values < MAX_VALUES_SIZE;
+ pos_values++) {
+ if (elem >= enum_obj_count) {
+ pr_err("Error enum-objects package is too small\n");
+ return -EINVAL;
+ }
+
+ ret = hp_convert_hexstr_to_str(enum_obj[elem + pos_values].string.pointer,
+ enum_obj[elem + pos_values].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ return -EINVAL;
+
+ /*
+ * ignore strings when possible values size
+ * is greater than MAX_VALUES_SIZE
+ */
+ if (size < MAX_VALUES_SIZE)
+ strscpy(enum_data->possible_values[pos_values],
+ str_value,
+ sizeof(enum_data->possible_values[pos_values]));
+
+ kfree(str_value);
+ str_value = NULL;
+ }
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Enumeration attribute or data may be malformed\n", elem);
+ break;
+ }
+
+ kfree(str_value);
+ str_value = NULL;
+ }
+
+exit_enumeration_package:
+ kfree(str_value);
+ return 0;
+}
+
+/**
+ * hp_populate_enumeration_package_data() -
+ * Populate all properties of an instance under enumeration attribute
+ *
+ * @enum_obj: ACPI object with enumeration data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_enumeration_package_data(union acpi_object *enum_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
+
+ enum_data->attr_name_kobj = attr_name_kobj;
+
+ hp_populate_enumeration_elements_from_package(enum_obj,
+ enum_obj->package.count,
+ instance_id);
+ hp_update_attribute_permissions(enum_data->common.is_readonly,
+ &enumeration_current_val);
+ /*
+ * Several attributes have names such "MONDAY". Friendly
+ * user nane is generated to make the name more descriptive
+ */
+ hp_friendly_user_name_update(enum_data->common.path,
+ attr_name_kobj->name,
+ enum_data->common.display_name,
+ sizeof(enum_data->common.display_name));
+ return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
+}
+
+static int hp_populate_enumeration_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ int values;
+ struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
+ int ret = 0;
+
+ /*
+ * Only data relevant to this driver and its functionality is
+ * read. BIOS defines the order in which each * element is
+ * read. Element 0 data is not relevant to this
+ * driver hence it is ignored. For clarity, all element names
+ * (DISPLAY_IN_UI) which defines the order in which is read
+ * and the name matches the variable where the data is stored.
+ *
+ * In earlier implementation, reported errors were ignored
+ * causing the data to remain uninitialized. It is not
+ * possible to determine if data read from BIOS is valid or
+ * not. It is for this reason functions may return a error
+ * without validating the data itself.
+ */
+
+ // VALUE:
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, enum_data->current_value,
+ sizeof(enum_data->current_value));
+ if (ret < 0)
+ goto buffer_exit;
+
+ // COMMON:
+ ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &enum_data->common);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // ENUM_CURRENT_VALUE:
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size,
+ enum_data->current_value,
+ sizeof(enum_data->current_value));
+ if (ret < 0)
+ goto buffer_exit;
+
+ // ENUM_SIZE:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &enum_data->possible_values_size);
+
+ if (enum_data->possible_values_size > MAX_VALUES_SIZE) {
+ /* Report a message and limit possible values size to maximum value */
+ pr_warn("Enum Possible size value exceeded the maximum number of elements supported or data may be malformed\n");
+ enum_data->possible_values_size = MAX_VALUES_SIZE;
+ }
+
+ // ENUM_POSSIBLE_VALUES:
+ for (values = 0; values < enum_data->possible_values_size; values++) {
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size,
+ enum_data->possible_values[values],
+ sizeof(enum_data->possible_values[values]));
+ if (ret < 0)
+ break;
+ }
+
+buffer_exit:
+ return ret;
+}
+
+/**
+ * hp_populate_enumeration_buffer_data() -
+ * Populate all properties of an instance under enumeration attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_enumeration_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
+ int ret = 0;
+
+ enum_data->attr_name_kobj = attr_name_kobj;
+
+ /* Populate enumeration elements */
+ ret = hp_populate_enumeration_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+ if (ret < 0)
+ return ret;
+
+ hp_update_attribute_permissions(enum_data->common.is_readonly,
+ &enumeration_current_val);
+ /*
+ * Several attributes have names such "MONDAY". A Friendlier
+ * user nane is generated to make the name more descriptive
+ */
+ hp_friendly_user_name_update(enum_data->common.path,
+ attr_name_kobj->name,
+ enum_data->common.display_name,
+ sizeof(enum_data->common.display_name));
+
+ return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
+}
+
+/**
+ * hp_exit_enumeration_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void hp_exit_enumeration_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.enumeration_instances_count;
+ instance_id++) {
+ struct enumeration_data *enum_data = &bioscfg_drv.enumeration_data[instance_id];
+ struct kobject *attr_name_kobj = enum_data->attr_name_kobj;
+
+ if (attr_name_kobj)
+ sysfs_remove_group(attr_name_kobj, &enumeration_attr_group);
+ }
+ bioscfg_drv.enumeration_instances_count = 0;
+
+ kfree(bioscfg_drv.enumeration_data);
+ bioscfg_drv.enumeration_data = NULL;
+}
diff --git a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
new file mode 100644
index 0000000000000..86b7ac63fec20
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to integer type attributes under
+ * BIOS Enumeration GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 Hewlett-Packard Inc.
+ */
+
+#include "bioscfg.h"
+
+GET_INSTANCE_ID(integer);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_integer_instance_id(kobj);
+
+ if (instance_id < 0)
+ return -EIO;
+
+ return sysfs_emit(buf, "%d\n",
+ bioscfg_drv.integer_data[instance_id].current_value);
+}
+
+/**
+ * validate_integer_input() -
+ * Validate input of current_value against lower and upper bound
+ *
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_integer_input(int instance_id, char *buf)
+{
+ int in_val;
+ int ret;
+ struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
+
+ /* BIOS treats it as a read only attribute */
+ if (integer_data->common.is_readonly)
+ return -EIO;
+
+ ret = kstrtoint(buf, 10, &in_val);
+ if (ret < 0)
+ return ret;
+
+ if (in_val < integer_data->lower_bound ||
+ in_val > integer_data->upper_bound)
+ return -ERANGE;
+
+ return 0;
+}
+
+static void update_integer_value(int instance_id, char *attr_value)
+{
+ int in_val;
+ int ret;
+ struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
+
+ ret = kstrtoint(attr_value, 10, &in_val);
+ if (ret == 0)
+ integer_data->current_value = in_val;
+ else
+ pr_warn("Invalid integer value found: %s\n", attr_value);
+}
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, integer);
+static struct kobj_attribute integer_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_PROPERTY_STORE(current_value, integer);
+static struct kobj_attribute integer_current_val =
+ __ATTR_RW_MODE(current_value, 0644);
+
+ATTRIBUTE_N_PROPERTY_SHOW(lower_bound, integer);
+static struct kobj_attribute integer_lower_bound =
+ __ATTR_RO(lower_bound);
+
+ATTRIBUTE_N_PROPERTY_SHOW(upper_bound, integer);
+static struct kobj_attribute integer_upper_bound =
+ __ATTR_RO(upper_bound);
+
+ATTRIBUTE_N_PROPERTY_SHOW(scalar_increment, integer);
+static struct kobj_attribute integer_scalar_increment =
+ __ATTR_RO(scalar_increment);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "integer\n");
+}
+
+static struct kobj_attribute integer_type =
+ __ATTR_RO(type);
+
+static struct attribute *integer_attrs[] = {
+ &common_display_langcode.attr,
+ &integer_display_name.attr,
+ &integer_current_val.attr,
+ &integer_lower_bound.attr,
+ &integer_upper_bound.attr,
+ &integer_scalar_increment.attr,
+ &integer_type.attr,
+ NULL
+};
+
+static const struct attribute_group integer_attr_group = {
+ .attrs = integer_attrs,
+};
+
+int hp_alloc_integer_data(void)
+{
+ bioscfg_drv.integer_instances_count = hp_get_instance_count(HP_WMI_BIOS_INTEGER_GUID);
+ bioscfg_drv.integer_data = kcalloc(bioscfg_drv.integer_instances_count,
+ sizeof(*bioscfg_drv.integer_data), GFP_KERNEL);
+
+ if (!bioscfg_drv.integer_data) {
+ bioscfg_drv.integer_instances_count = 0;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_integer_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [INT_LOWER_BOUND] = ACPI_TYPE_INTEGER,
+ [INT_UPPER_BOUND] = ACPI_TYPE_INTEGER,
+ [INT_SCALAR_INCREMENT] = ACPI_TYPE_INTEGER,
+};
+
+static int hp_populate_integer_elements_from_package(union acpi_object *integer_obj,
+ int integer_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ 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)
+ return -EINVAL;
+
+ for (elem = 1, eloc = 1; elem < integer_obj_count; elem++, eloc++) {
+ /* ONLY look at the first INTEGER_ELEM_CNT elements */
+ if (eloc == INT_ELEM_CNT)
+ goto exit_integer_package;
+
+ switch (integer_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+ if (elem != PREREQUISITES) {
+ ret = hp_convert_hexstr_to_str(integer_obj[elem].string.pointer,
+ integer_obj[elem].string.length,
+ &str_value, &value_len);
+ if (ret)
+ continue;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)integer_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", integer_obj[elem].type);
+ continue;
+ }
+ /* Check that both expected and read object type match */
+ if (expected_integer_types[eloc] != integer_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_integer_types[eloc], elem, integer_obj[elem].type);
+ kfree(str_value);
+ return -EIO;
+ }
+ /* Assign appropriate element value to corresponding field*/
+ switch (eloc) {
+ case VALUE:
+ ret = kstrtoint(str_value, 10, &int_value);
+ if (ret)
+ continue;
+
+ integer_data->current_value = int_value;
+ break;
+ case PATH:
+ strscpy(integer_data->common.path, str_value,
+ sizeof(integer_data->common.path));
+ 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 >= 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,
+ sizeof(integer_data->common.prerequisites[reqs]));
+ kfree(str_value);
+ str_value = NULL;
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ integer_data->common.security_level = int_value;
+ break;
+ case INT_LOWER_BOUND:
+ integer_data->lower_bound = int_value;
+ break;
+ case INT_UPPER_BOUND:
+ integer_data->upper_bound = int_value;
+ break;
+ case INT_SCALAR_INCREMENT:
+ integer_data->scalar_increment = int_value;
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Integer attribute or data may be malformed\n", elem);
+ break;
+ }
+
+ kfree(str_value);
+ str_value = NULL;
+ }
+exit_integer_package:
+ kfree(str_value);
+ return 0;
+}
+
+/**
+ * hp_populate_integer_package_data() -
+ * Populate all properties of an instance under integer attribute
+ *
+ * @integer_obj: ACPI object with integer data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_integer_package_data(union acpi_object *integer_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
+
+ integer_data->attr_name_kobj = attr_name_kobj;
+ hp_populate_integer_elements_from_package(integer_obj,
+ integer_obj->package.count,
+ instance_id);
+ hp_update_attribute_permissions(integer_data->common.is_readonly,
+ &integer_current_val);
+ hp_friendly_user_name_update(integer_data->common.path,
+ attr_name_kobj->name,
+ integer_data->common.display_name,
+ sizeof(integer_data->common.display_name));
+ return sysfs_create_group(attr_name_kobj, &integer_attr_group);
+}
+
+static int hp_populate_integer_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ char *dst = NULL;
+ int dst_size = *buffer_size / sizeof(u16);
+ struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
+ int ret = 0;
+
+ dst = kcalloc(dst_size, sizeof(char), GFP_KERNEL);
+ if (!dst)
+ return -ENOMEM;
+
+ /*
+ * Only data relevant to this driver and its functionality is
+ * read. BIOS defines the order in which each * element is
+ * read. Element 0 data is not relevant to this
+ * driver hence it is ignored. For clarity, all element names
+ * (DISPLAY_IN_UI) which defines the order in which is read
+ * and the name matches the variable where the data is stored.
+ *
+ * In earlier implementation, reported errors were ignored
+ * causing the data to remain uninitialized. It is not
+ * possible to determine if data read from BIOS is valid or
+ * not. It is for this reason functions may return a error
+ * without validating the data itself.
+ */
+
+ // VALUE:
+ integer_data->current_value = 0;
+
+ hp_get_string_from_buffer(&buffer_ptr, buffer_size, dst, dst_size);
+ ret = kstrtoint(dst, 10, &integer_data->current_value);
+ if (ret)
+ pr_warn("Unable to convert string to integer: %s\n", dst);
+ kfree(dst);
+
+ // COMMON:
+ ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &integer_data->common);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // INT_LOWER_BOUND:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &integer_data->lower_bound);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // INT_UPPER_BOUND:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &integer_data->upper_bound);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // INT_SCALAR_INCREMENT:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &integer_data->scalar_increment);
+
+buffer_exit:
+ return ret;
+}
+
+/**
+ * hp_populate_integer_buffer_data() -
+ * Populate all properties of an instance under integer attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_integer_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct integer_data *integer_data = &bioscfg_drv.integer_data[instance_id];
+ int ret = 0;
+
+ integer_data->attr_name_kobj = attr_name_kobj;
+
+ /* Populate integer elements */
+ ret = hp_populate_integer_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+ if (ret < 0)
+ return ret;
+
+ hp_update_attribute_permissions(integer_data->common.is_readonly,
+ &integer_current_val);
+ hp_friendly_user_name_update(integer_data->common.path,
+ attr_name_kobj->name,
+ integer_data->common.display_name,
+ sizeof(integer_data->common.display_name));
+
+ return sysfs_create_group(attr_name_kobj, &integer_attr_group);
+}
+
+/**
+ * hp_exit_integer_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void hp_exit_integer_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.integer_instances_count;
+ instance_id++) {
+ struct kobject *attr_name_kobj =
+ bioscfg_drv.integer_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj)
+ sysfs_remove_group(attr_name_kobj, &integer_attr_group);
+ }
+ bioscfg_drv.integer_instances_count = 0;
+
+ kfree(bioscfg_drv.integer_data);
+ bioscfg_drv.integer_data = NULL;
+}
diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
new file mode 100644
index 0000000000000..1ff09dfb7d7e9
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to ordered list type attributes under
+ * BIOS ORDERED LIST GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+
+GET_INSTANCE_ID(ordered_list);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_ordered_list_instance_id(kobj);
+
+ if (instance_id < 0)
+ return -EIO;
+
+ return sysfs_emit(buf, "%s\n",
+ bioscfg_drv.ordered_list_data[instance_id].current_value);
+}
+
+static int replace_char_str(u8 *buffer, char *repl_char, char *repl_with)
+{
+ char *src = buffer;
+ int buflen = strlen(buffer);
+ int item;
+
+ if (buflen < 1)
+ return -EINVAL;
+
+ for (item = 0; item < buflen; item++)
+ if (src[item] == *repl_char)
+ src[item] = *repl_with;
+
+ return 0;
+}
+
+/**
+ * validate_ordered_list_input() -
+ * Validate input of current_value against possible values
+ *
+ * @instance: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_ordered_list_input(int instance, char *buf)
+{
+ /* validation is done by BIOS. This validation function will
+ * convert semicolon to commas. BIOS uses commas as
+ * separators when reporting ordered-list values.
+ */
+ return replace_char_str(buf, SEMICOLON_SEP, COMMA_SEP);
+}
+
+static void update_ordered_list_value(int instance, char *attr_value)
+{
+ struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance];
+
+ strscpy(ordered_list_data->current_value,
+ attr_value,
+ sizeof(ordered_list_data->current_value));
+}
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, ordered_list);
+static struct kobj_attribute ordered_list_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_PROPERTY_STORE(current_value, ordered_list);
+static struct kobj_attribute ordered_list_current_val =
+ __ATTR_RW_MODE(current_value, 0644);
+
+ATTRIBUTE_VALUES_PROPERTY_SHOW(elements, ordered_list, SEMICOLON_SEP);
+static struct kobj_attribute ordered_list_elements_val =
+ __ATTR_RO(elements);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "ordered-list\n");
+}
+
+static struct kobj_attribute ordered_list_type =
+ __ATTR_RO(type);
+
+static struct attribute *ordered_list_attrs[] = {
+ &common_display_langcode.attr,
+ &ordered_list_display_name.attr,
+ &ordered_list_current_val.attr,
+ &ordered_list_elements_val.attr,
+ &ordered_list_type.attr,
+ NULL
+};
+
+static const struct attribute_group ordered_list_attr_group = {
+ .attrs = ordered_list_attrs,
+};
+
+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 = kcalloc(bioscfg_drv.ordered_list_instances_count,
+ sizeof(*bioscfg_drv.ordered_list_data),
+ GFP_KERNEL);
+ if (!bioscfg_drv.ordered_list_data) {
+ bioscfg_drv.ordered_list_instances_count = 0;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_order_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [ORD_LIST_SIZE] = ACPI_TYPE_INTEGER,
+ [ORD_LIST_ELEMENTS] = ACPI_TYPE_STRING,
+};
+
+static int hp_populate_ordered_list_elements_from_package(union acpi_object *order_obj,
+ int order_obj_count,
+ int instance_id)
+{
+ 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;
+ int tmp_len = 0;
+ char *part = NULL;
+ struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
+
+ if (!order_obj)
+ return -EINVAL;
+
+ for (elem = 1, eloc = 1; eloc < ORD_ELEM_CNT; elem++, eloc++) {
+
+ switch (order_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+ if (elem != PREREQUISITES && elem != ORD_LIST_ELEMENTS) {
+ ret = hp_convert_hexstr_to_str(order_obj[elem].string.pointer,
+ order_obj[elem].string.length,
+ &str_value, &value_len);
+ if (ret)
+ continue;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)order_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", order_obj[elem].type);
+ continue;
+ }
+
+ /* Check that both expected and read object type match */
+ if (expected_order_types[eloc] != order_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_order_types[eloc], elem, order_obj[elem].type);
+ kfree(str_value);
+ return -EIO;
+ }
+
+ /* Assign appropriate element value to corresponding field*/
+ switch (eloc) {
+ case VALUE:
+ strscpy(ordered_list_data->current_value,
+ str_value, sizeof(ordered_list_data->current_value));
+ replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP);
+ break;
+ case PATH:
+ strscpy(ordered_list_data->common.path, str_value,
+ sizeof(ordered_list_data->common.path));
+ 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++) {
+ 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,
+ sizeof(ordered_list_data->common.prerequisites[reqs]));
+
+ kfree(str_value);
+ str_value = NULL;
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ ordered_list_data->common.security_level = int_value;
+ break;
+
+ case ORD_LIST_SIZE:
+ if (int_value > MAX_ELEMENTS_SIZE) {
+ pr_warn("Order List size value exceeded the maximum number of elements supported or data may be malformed\n");
+ int_value = MAX_ELEMENTS_SIZE;
+ }
+ ordered_list_data->elements_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. ORD_LIST_ELEMENTS
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+ case ORD_LIST_ELEMENTS:
+
+ /*
+ * Ordered list data is stored in hex and comma separated format
+ * Convert the data and split it to show each element
+ */
+ ret = hp_convert_hexstr_to_str(str_value, value_len, &tmpstr, &tmp_len);
+ if (ret)
+ goto exit_list;
+
+ part_tmp = tmpstr;
+ part = strsep(&part_tmp, COMMA_SEP);
+
+ for (olist_elem = 0; olist_elem < MAX_ELEMENTS_SIZE && part; olist_elem++) {
+ strscpy(ordered_list_data->elements[olist_elem],
+ part,
+ sizeof(ordered_list_data->elements[olist_elem]));
+ part = strsep(&part_tmp, COMMA_SEP);
+ }
+ ordered_list_data->elements_size = olist_elem;
+
+ kfree(str_value);
+ str_value = NULL;
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Ordered_List attribute or data may be malformed\n", elem);
+ break;
+ }
+ kfree(tmpstr);
+ tmpstr = NULL;
+ kfree(str_value);
+ str_value = NULL;
+ }
+
+exit_list:
+ kfree(tmpstr);
+ kfree(str_value);
+ return 0;
+}
+
+/**
+ * hp_populate_ordered_list_package_data() -
+ * Populate all properties of an instance under ordered_list attribute
+ *
+ * @order_obj: ACPI object with ordered_list data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_ordered_list_package_data(union acpi_object *order_obj, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
+
+ ordered_list_data->attr_name_kobj = attr_name_kobj;
+
+ hp_populate_ordered_list_elements_from_package(order_obj,
+ order_obj->package.count,
+ instance_id);
+ hp_update_attribute_permissions(ordered_list_data->common.is_readonly,
+ &ordered_list_current_val);
+ hp_friendly_user_name_update(ordered_list_data->common.path,
+ attr_name_kobj->name,
+ ordered_list_data->common.display_name,
+ sizeof(ordered_list_data->common.display_name));
+ return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group);
+}
+
+static int hp_populate_ordered_list_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ int values;
+ struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
+ int ret = 0;
+
+ /*
+ * Only data relevant to this driver and its functionality is
+ * read. BIOS defines the order in which each * element is
+ * read. Element 0 data is not relevant to this
+ * driver hence it is ignored. For clarity, all element names
+ * (DISPLAY_IN_UI) which defines the order in which is read
+ * and the name matches the variable where the data is stored.
+ *
+ * In earlier implementation, reported errors were ignored
+ * causing the data to remain uninitialized. It is not
+ * possible to determine if data read from BIOS is valid or
+ * not. It is for this reason functions may return a error
+ * without validating the data itself.
+ */
+
+ // VALUE:
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, ordered_list_data->current_value,
+ sizeof(ordered_list_data->current_value));
+ if (ret < 0)
+ goto buffer_exit;
+
+ replace_char_str(ordered_list_data->current_value, COMMA_SEP, SEMICOLON_SEP);
+
+ // COMMON:
+ ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size,
+ &ordered_list_data->common);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // ORD_LIST_SIZE:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &ordered_list_data->elements_size);
+
+ if (ordered_list_data->elements_size > MAX_ELEMENTS_SIZE) {
+ /* Report a message and limit elements size to maximum value */
+ pr_warn("Ordered List size value exceeded the maximum number of elements supported or data may be malformed\n");
+ ordered_list_data->elements_size = MAX_ELEMENTS_SIZE;
+ }
+
+ // ORD_LIST_ELEMENTS:
+ for (values = 0; values < ordered_list_data->elements_size; values++) {
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size,
+ ordered_list_data->elements[values],
+ sizeof(ordered_list_data->elements[values]));
+ if (ret < 0)
+ break;
+ }
+
+buffer_exit:
+ return ret;
+}
+
+/**
+ * hp_populate_ordered_list_buffer_data() - Populate all properties of an
+ * instance under ordered list attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_ordered_list_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct ordered_list_data *ordered_list_data = &bioscfg_drv.ordered_list_data[instance_id];
+ int ret = 0;
+
+ ordered_list_data->attr_name_kobj = attr_name_kobj;
+
+ /* Populate ordered list elements */
+ ret = hp_populate_ordered_list_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+ if (ret < 0)
+ return ret;
+
+ hp_update_attribute_permissions(ordered_list_data->common.is_readonly,
+ &ordered_list_current_val);
+ hp_friendly_user_name_update(ordered_list_data->common.path,
+ attr_name_kobj->name,
+ ordered_list_data->common.display_name,
+ sizeof(ordered_list_data->common.display_name));
+
+ return sysfs_create_group(attr_name_kobj, &ordered_list_attr_group);
+}
+
+/**
+ * hp_exit_ordered_list_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void hp_exit_ordered_list_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.ordered_list_instances_count;
+ instance_id++) {
+ struct kobject *attr_name_kobj =
+ bioscfg_drv.ordered_list_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj)
+ sysfs_remove_group(attr_name_kobj,
+ &ordered_list_attr_group);
+ }
+ bioscfg_drv.ordered_list_instances_count = 0;
+
+ kfree(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
new file mode 100644
index 0000000000000..03d0188804baf
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
@@ -0,0 +1,556 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to password object type attributes under
+ * BIOS PASSWORD for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+#include <asm-generic/posix_types.h>
+
+GET_INSTANCE_ID(password);
+/*
+ * Clear all passwords copied to memory for a particular
+ * authentication instance
+ */
+static int clear_passwords(const int instance)
+{
+ struct password_data *password_data = &bioscfg_drv.password_data[instance];
+
+ if (!password_data->is_enabled)
+ return 0;
+
+ memset(password_data->current_password,
+ 0, sizeof(password_data->current_password));
+ memset(password_data->new_password,
+ 0, sizeof(password_data->new_password));
+
+ return 0;
+}
+
+/*
+ * Clear all credentials copied to memory for both Power-ON and Setup
+ * BIOS instances
+ */
+int hp_clear_all_credentials(void)
+{
+ int count = bioscfg_drv.password_instances_count;
+ int instance;
+
+ /* clear all passwords */
+ for (instance = 0; instance < count; instance++)
+ clear_passwords(instance);
+
+ /* clear auth_token */
+ kfree(bioscfg_drv.spm_data.auth_token);
+ bioscfg_drv.spm_data.auth_token = NULL;
+
+ return 0;
+}
+
+int hp_get_password_instance_for_type(const char *name)
+{
+ int count = bioscfg_drv.password_instances_count;
+ int instance;
+
+ for (instance = 0; instance < count; instance++)
+ if (!strcmp(bioscfg_drv.password_data[instance].common.display_name, name))
+ return instance;
+
+ return -EINVAL;
+}
+
+static int validate_password_input(int instance_id, const char *buf)
+{
+ int length;
+ struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
+
+ length = strlen(buf);
+ if (buf[length - 1] == '\n')
+ length--;
+
+ if (length > MAX_PASSWD_SIZE)
+ return INVALID_BIOS_AUTH;
+
+ if (password_data->min_password_length > length ||
+ password_data->max_password_length < length)
+ return INVALID_BIOS_AUTH;
+ return SUCCESS;
+}
+
+ATTRIBUTE_N_PROPERTY_SHOW(is_enabled, password);
+static struct kobj_attribute password_is_password_set = __ATTR_RO(is_enabled);
+
+static int store_password_instance(struct kobject *kobj, const char *buf,
+ size_t count, bool is_current)
+{
+ char *buf_cp;
+ int id, ret = 0;
+
+ buf_cp = kstrdup(buf, GFP_KERNEL);
+ if (!buf_cp)
+ return -ENOMEM;
+
+ ret = hp_enforce_single_line_input(buf_cp, count);
+ if (!ret) {
+ id = get_password_instance_id(kobj);
+
+ if (id >= 0)
+ ret = validate_password_input(id, buf_cp);
+ }
+
+ if (!ret) {
+ if (is_current)
+ strscpy(bioscfg_drv.password_data[id].current_password,
+ buf_cp,
+ sizeof(bioscfg_drv.password_data[id].current_password));
+ else
+ strscpy(bioscfg_drv.password_data[id].new_password,
+ buf_cp,
+ sizeof(bioscfg_drv.password_data[id].new_password));
+ }
+
+ kfree(buf_cp);
+ return ret < 0 ? ret : count;
+}
+
+static ssize_t current_password_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ return store_password_instance(kobj, buf, count, true);
+}
+
+static struct kobj_attribute password_current_password = __ATTR_WO(current_password);
+
+static ssize_t new_password_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ return store_password_instance(kobj, buf, count, true);
+}
+
+static struct kobj_attribute password_new_password = __ATTR_WO(new_password);
+
+ATTRIBUTE_N_PROPERTY_SHOW(min_password_length, password);
+static struct kobj_attribute password_min_password_length = __ATTR_RO(min_password_length);
+
+ATTRIBUTE_N_PROPERTY_SHOW(max_password_length, password);
+static struct kobj_attribute password_max_password_length = __ATTR_RO(max_password_length);
+
+static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ if (!strcmp(kobj->name, SETUP_PASSWD))
+ return sysfs_emit(buf, "%s\n", BIOS_ADMIN);
+
+ if (!strcmp(kobj->name, POWER_ON_PASSWD))
+ return sysfs_emit(buf, "%s\n", POWER_ON);
+
+ return -EIO;
+}
+
+static struct kobj_attribute password_role = __ATTR_RO(role);
+
+static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int i = get_password_instance_id(kobj);
+
+ if (i < 0)
+ return i;
+
+ if (bioscfg_drv.password_data[i].mechanism != PASSWORD)
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", PASSWD_MECHANISM_TYPES);
+}
+
+static struct kobj_attribute password_mechanism = __ATTR_RO(mechanism);
+
+ATTRIBUTE_VALUES_PROPERTY_SHOW(encodings, password, SEMICOLON_SEP);
+static struct kobj_attribute password_encodings_val = __ATTR_RO(encodings);
+
+static struct attribute *password_attrs[] = {
+ &password_is_password_set.attr,
+ &password_min_password_length.attr,
+ &password_max_password_length.attr,
+ &password_current_password.attr,
+ &password_new_password.attr,
+ &password_role.attr,
+ &password_mechanism.attr,
+ &password_encodings_val.attr,
+ NULL
+};
+
+static const struct attribute_group password_attr_group = {
+ .attrs = password_attrs
+};
+
+int hp_alloc_password_data(void)
+{
+ bioscfg_drv.password_instances_count = hp_get_instance_count(HP_WMI_BIOS_PASSWORD_GUID);
+ bioscfg_drv.password_data = kcalloc(bioscfg_drv.password_instances_count,
+ sizeof(*bioscfg_drv.password_data), GFP_KERNEL);
+ if (!bioscfg_drv.password_data) {
+ bioscfg_drv.password_instances_count = 0;
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_password_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [PSWD_MIN_LENGTH] = ACPI_TYPE_INTEGER,
+ [PSWD_MAX_LENGTH] = ACPI_TYPE_INTEGER,
+ [PSWD_SIZE] = ACPI_TYPE_INTEGER,
+ [PSWD_ENCODINGS] = ACPI_TYPE_STRING,
+ [PSWD_IS_SET] = ACPI_TYPE_INTEGER,
+};
+
+static int hp_populate_password_elements_from_package(union acpi_object *password_obj,
+ int password_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ int ret;
+ 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];
+
+ if (!password_obj)
+ return -EINVAL;
+
+ for (elem = 1, eloc = 1; elem < password_obj_count; elem++, eloc++) {
+ /* ONLY look at the first PASSWORD_ELEM_CNT elements */
+ if (eloc == PSWD_ELEM_CNT)
+ goto exit_package;
+
+ switch (password_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+ if (PREREQUISITES != elem && PSWD_ENCODINGS != elem) {
+ ret = hp_convert_hexstr_to_str(password_obj[elem].string.pointer,
+ password_obj[elem].string.length,
+ &str_value, &value_len);
+ if (ret)
+ continue;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)password_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", password_obj[elem].type);
+ continue;
+ }
+
+ /* Check that both expected and read object type match */
+ if (expected_password_types[eloc] != password_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_password_types[eloc], elem, password_obj[elem].type);
+ kfree(str_value);
+ return -EIO;
+ }
+
+ /* Assign appropriate element value to corresponding field*/
+ switch (eloc) {
+ case VALUE:
+ break;
+ case PATH:
+ strscpy(password_data->common.path, str_value,
+ sizeof(password_data->common.path));
+ 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++) {
+ 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,
+ sizeof(password_data->common.prerequisites[reqs]));
+
+ kfree(str_value);
+ str_value = NULL;
+
+ }
+ break;
+ case SECURITY_LEVEL:
+ password_data->common.security_level = int_value;
+ break;
+ case PSWD_MIN_LENGTH:
+ password_data->min_password_length = int_value;
+ break;
+ case PSWD_MAX_LENGTH:
+ password_data->max_password_length = int_value;
+ break;
+ case PSWD_SIZE:
+
+ if (int_value > MAX_ENCODINGS_SIZE) {
+ pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n");
+ int_value = MAX_ENCODINGS_SIZE;
+ }
+ password_data->encodings_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. PSWD_ENCODINGS
+ * object is omitted by BIOS when the size is
+ * zero.
+ */
+ if (int_value == 0)
+ eloc++;
+ break;
+ case PSWD_ENCODINGS:
+ size = min_t(u32, password_data->encodings_size, MAX_ENCODINGS_SIZE);
+ for (pos_values = 0; pos_values < size; pos_values++) {
+ ret = hp_convert_hexstr_to_str(password_obj[elem + pos_values].string.pointer,
+ password_obj[elem + pos_values].string.length,
+ &str_value, &value_len);
+ if (ret)
+ break;
+
+ strscpy(password_data->encodings[pos_values],
+ str_value,
+ sizeof(password_data->encodings[pos_values]));
+ kfree(str_value);
+ str_value = NULL;
+
+ }
+ break;
+ case PSWD_IS_SET:
+ password_data->is_enabled = int_value;
+ break;
+ default:
+ pr_warn("Invalid element: %d found in Password attribute or data may be malformed\n", elem);
+ break;
+ }
+
+ kfree(str_value);
+ str_value = NULL;
+ }
+
+exit_package:
+ kfree(str_value);
+ return 0;
+}
+
+/**
+ * hp_populate_password_package_data()
+ * Populate all properties for an instance under password attribute
+ *
+ * @password_obj: ACPI object with password data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_password_package_data(union acpi_object *password_obj, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
+
+ password_data->attr_name_kobj = attr_name_kobj;
+
+ hp_populate_password_elements_from_package(password_obj,
+ password_obj->package.count,
+ instance_id);
+
+ hp_friendly_user_name_update(password_data->common.path,
+ attr_name_kobj->name,
+ password_data->common.display_name,
+ sizeof(password_data->common.display_name));
+
+ if (!strcmp(attr_name_kobj->name, SETUP_PASSWD))
+ return sysfs_create_group(attr_name_kobj, &password_attr_group);
+
+ return sysfs_create_group(attr_name_kobj, &password_attr_group);
+}
+
+static int hp_populate_password_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ int values;
+ int isreadonly;
+ struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
+ int ret = 0;
+
+ /*
+ * Only data relevant to this driver and its functionality is
+ * read. BIOS defines the order in which each * element is
+ * read. Element 0 data is not relevant to this
+ * driver hence it is ignored. For clarity, all element names
+ * (DISPLAY_IN_UI) which defines the order in which is read
+ * and the name matches the variable where the data is stored.
+ *
+ * In earlier implementation, reported errors were ignored
+ * causing the data to remain uninitialized. It is not
+ * possible to determine if data read from BIOS is valid or
+ * not. It is for this reason functions may return a error
+ * without validating the data itself.
+ */
+
+ // VALUE:
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, password_data->current_password,
+ sizeof(password_data->current_password));
+ if (ret < 0)
+ goto buffer_exit;
+
+ // COMMON:
+ ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size,
+ &password_data->common);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // PSWD_MIN_LENGTH:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &password_data->min_password_length);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // PSWD_MAX_LENGTH:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &password_data->max_password_length);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // PSWD_SIZE:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &password_data->encodings_size);
+ if (ret < 0)
+ goto buffer_exit;
+
+ if (password_data->encodings_size > MAX_ENCODINGS_SIZE) {
+ /* Report a message and limit possible values size to maximum value */
+ pr_warn("Password Encoding size value exceeded the maximum number of elements supported or data may be malformed\n");
+ password_data->encodings_size = MAX_ENCODINGS_SIZE;
+ }
+
+ // PSWD_ENCODINGS:
+ for (values = 0; values < password_data->encodings_size; values++) {
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size,
+ password_data->encodings[values],
+ sizeof(password_data->encodings[values]));
+ if (ret < 0)
+ break;
+ }
+
+ // PSWD_IS_SET:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size, &isreadonly);
+ if (ret < 0)
+ goto buffer_exit;
+
+ password_data->is_enabled = isreadonly ? true : false;
+
+buffer_exit:
+ return ret;
+}
+
+/**
+ * hp_populate_password_buffer_data()
+ * Populate all properties for an instance under password object attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_password_buffer_data(u8 *buffer_ptr, u32 *buffer_size, int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct password_data *password_data = &bioscfg_drv.password_data[instance_id];
+ int ret = 0;
+
+ password_data->attr_name_kobj = attr_name_kobj;
+
+ /* Populate Password attributes */
+ ret = hp_populate_password_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+ if (ret < 0)
+ return ret;
+
+ hp_friendly_user_name_update(password_data->common.path,
+ attr_name_kobj->name,
+ password_data->common.display_name,
+ sizeof(password_data->common.display_name));
+ if (!strcmp(attr_name_kobj->name, SETUP_PASSWD))
+ return sysfs_create_group(attr_name_kobj, &password_attr_group);
+
+ return sysfs_create_group(attr_name_kobj, &password_attr_group);
+}
+
+/**
+ * hp_exit_password_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void hp_exit_password_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.password_instances_count;
+ instance_id++) {
+ struct kobject *attr_name_kobj =
+ bioscfg_drv.password_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj) {
+ if (!strcmp(attr_name_kobj->name, SETUP_PASSWD))
+ sysfs_remove_group(attr_name_kobj,
+ &password_attr_group);
+ else
+ sysfs_remove_group(attr_name_kobj,
+ &password_attr_group);
+ }
+ }
+ bioscfg_drv.password_instances_count = 0;
+ kfree(bioscfg_drv.password_data);
+ bioscfg_drv.password_data = NULL;
+}
diff --git a/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
new file mode 100644
index 0000000000000..86f90238750c2
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/spmobj-attributes.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to secure platform management object type
+ * attributes under BIOS PASSWORD for use with hp-bioscfg driver
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+
+static const char * const spm_state_types[] = {
+ "not provisioned",
+ "provisioned",
+ "provisioning in progress",
+};
+
+static const char * const spm_mechanism_types[] = {
+ "not provisioned",
+ "signing-key",
+ "endorsement-key",
+};
+
+struct secureplatform_provisioning_data {
+ u8 state;
+ u8 version[2];
+ u8 reserved1;
+ u32 features;
+ u32 nonce;
+ u8 reserved2[28];
+ u8 sk_mod[MAX_KEY_MOD_SIZE];
+ u8 kek_mod[MAX_KEY_MOD_SIZE];
+};
+
+/**
+ * hp_calculate_security_buffer() - determines size of security buffer
+ * for authentication scheme
+ *
+ * @authentication: the authentication content
+ *
+ * Currently only supported type is Admin password
+ */
+size_t hp_calculate_security_buffer(const char *authentication)
+{
+ size_t size, authlen;
+
+ if (!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);
+
+ return size;
+}
+
+/**
+ * hp_populate_security_buffer() - builds a security buffer for
+ * authentication scheme
+ *
+ * @authbuf: the security buffer
+ * @authentication: the authentication content
+ *
+ * Currently only supported type is PLAIN TEXT
+ */
+int hp_populate_security_buffer(u16 *authbuf, const char *authentication)
+{
+ u16 *auth = authbuf;
+ char *strprefix = NULL;
+ int ret = 0;
+
+ if (strstarts(authentication, BEAM_PREFIX)) {
+ /*
+ * BEAM_PREFIX is append to authbuf when a signature
+ * is provided and Sure Admin is enabled in BIOS
+ */
+ /* BEAM_PREFIX found, convert part to unicode */
+ auth = hp_ascii_to_utf16_unicode(auth, authentication);
+ if (!auth)
+ return -EINVAL;
+
+ } else {
+ /*
+ * UTF-16 prefix is append to the * authbuf when a BIOS
+ * admin password is configured in BIOS
+ */
+
+ /* append UTF_PREFIX to part and then convert it to unicode */
+ strprefix = kasprintf(GFP_KERNEL, "%s%s", UTF_PREFIX,
+ authentication);
+ if (!strprefix)
+ return -ENOMEM;
+
+ auth = hp_ascii_to_utf16_unicode(auth, strprefix);
+ kfree(strprefix);
+
+ if (!auth) {
+ ret = -EINVAL;
+ goto out_buffer;
+ }
+ }
+
+out_buffer:
+ return ret;
+}
+
+static ssize_t update_spm_state(void)
+{
+ struct secureplatform_provisioning_data data;
+ int ret;
+
+ ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
+ HPWMI_SECUREPLATFORM, &data, 0,
+ sizeof(data));
+ if (ret < 0)
+ return ret;
+
+ bioscfg_drv.spm_data.mechanism = data.state;
+ if (bioscfg_drv.spm_data.mechanism)
+ bioscfg_drv.spm_data.is_enabled = 1;
+
+ return 0;
+}
+
+static ssize_t statusbin(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ struct secureplatform_provisioning_data *buf)
+{
+ int ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_GET_STATE,
+ HPWMI_SECUREPLATFORM, buf, 0,
+ sizeof(*buf));
+
+ if (ret < 0)
+ return ret;
+
+ return sizeof(struct secureplatform_provisioning_data);
+}
+
+/*
+ * status_show - Reads SPM status
+ */
+static ssize_t status_show(struct kobject *kobj, struct kobj_attribute
+ *attr, char *buf)
+{
+ int ret, i;
+ int len = 0;
+ struct secureplatform_provisioning_data data;
+
+ ret = statusbin(kobj, attr, &data);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * 'status' is a read-only file that returns ASCII text in
+ * JSON format reporting the status information.
+ *
+ * "State": "not provisioned | provisioned | provisioning in progress ",
+ * "Version": " Major. Minor ",
+ * "Nonce": <16-bit unsigned number display in base 10>,
+ * "FeaturesInUse": <16-bit unsigned number display in base 10>,
+ * "EndorsementKeyMod": "<256 bytes in base64>",
+ * "SigningKeyMod": "<256 bytes in base64>"
+ */
+
+ len += sysfs_emit_at(buf, len, "{\n");
+ len += sysfs_emit_at(buf, len, "\t\"State\": \"%s\",\n",
+ spm_state_types[data.state]);
+ len += sysfs_emit_at(buf, len, "\t\"Version\": \"%d.%d\"",
+ data.version[0], data.version[1]);
+
+ /*
+ * state == 0 means secure platform management
+ * feature is not configured in BIOS.
+ */
+ if (data.state == 0) {
+ len += sysfs_emit_at(buf, len, "\n");
+ goto status_exit;
+ } else {
+ len += sysfs_emit_at(buf, len, ",\n");
+ }
+
+ len += sysfs_emit_at(buf, len, "\t\"Nonce\": %d,\n", data.nonce);
+ len += sysfs_emit_at(buf, len, "\t\"FeaturesInUse\": %d,\n", data.features);
+ len += sysfs_emit_at(buf, len, "\t\"EndorsementKeyMod\": \"");
+
+ for (i = 255; i >= 0; i--)
+ len += sysfs_emit_at(buf, len, " %u", data.kek_mod[i]);
+
+ len += sysfs_emit_at(buf, len, " \",\n");
+ len += sysfs_emit_at(buf, len, "\t\"SigningKeyMod\": \"");
+
+ for (i = 255; i >= 0; i--)
+ len += sysfs_emit_at(buf, len, " %u", data.sk_mod[i]);
+
+ /* Return buf contents */
+ len += sysfs_emit_at(buf, len, " \"\n");
+
+status_exit:
+ len += sysfs_emit_at(buf, len, "}\n");
+
+ return len;
+}
+
+static struct kobj_attribute password_spm_status = __ATTR_RO(status);
+
+ATTRIBUTE_SPM_N_PROPERTY_SHOW(is_enabled, spm);
+static struct kobj_attribute password_spm_is_key_enabled = __ATTR_RO(is_enabled);
+
+static ssize_t key_mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ spm_mechanism_types[bioscfg_drv.spm_data.mechanism]);
+}
+
+static struct kobj_attribute password_spm_key_mechanism = __ATTR_RO(key_mechanism);
+
+static ssize_t sk_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int length;
+
+ length = count;
+ if (buf[length - 1] == '\n')
+ length--;
+
+ /* allocate space and copy current signing key */
+ bioscfg_drv.spm_data.signing_key = kmemdup(buf, length, GFP_KERNEL);
+ if (!bioscfg_drv.spm_data.signing_key)
+ return -ENOMEM;
+
+ /* submit signing key payload */
+ ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_SK,
+ HPWMI_SECUREPLATFORM,
+ (void *)bioscfg_drv.spm_data.signing_key,
+ count, 0);
+
+ if (!ret) {
+ bioscfg_drv.spm_data.mechanism = SIGNING_KEY;
+ hp_set_reboot_and_signal_event();
+ }
+
+ kfree(bioscfg_drv.spm_data.signing_key);
+ bioscfg_drv.spm_data.signing_key = NULL;
+
+ return ret ? ret : count;
+}
+
+static struct kobj_attribute password_spm_signing_key = __ATTR_WO(sk);
+
+static ssize_t kek_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int length;
+
+ length = count;
+ if (buf[length - 1] == '\n')
+ length--;
+
+ /* allocate space and copy current signing key */
+ bioscfg_drv.spm_data.endorsement_key = kmemdup(buf, length, GFP_KERNEL);
+ if (!bioscfg_drv.spm_data.endorsement_key) {
+ ret = -ENOMEM;
+ goto exit_kek;
+ }
+
+ ret = hp_wmi_perform_query(HPWMI_SECUREPLATFORM_SET_KEK,
+ HPWMI_SECUREPLATFORM,
+ (void *)bioscfg_drv.spm_data.endorsement_key,
+ count, 0);
+
+ if (!ret) {
+ bioscfg_drv.spm_data.mechanism = ENDORSEMENT_KEY;
+ hp_set_reboot_and_signal_event();
+ }
+
+exit_kek:
+ kfree(bioscfg_drv.spm_data.endorsement_key);
+ bioscfg_drv.spm_data.endorsement_key = NULL;
+
+ return ret ? ret : count;
+}
+
+static struct kobj_attribute password_spm_endorsement_key = __ATTR_WO(kek);
+
+static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n", BIOS_SPM);
+}
+
+static struct kobj_attribute password_spm_role = __ATTR_RO(role);
+
+static ssize_t auth_token_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = 0;
+ int length;
+
+ length = count;
+ if (buf[length - 1] == '\n')
+ length--;
+
+ /* allocate space and copy current auth token */
+ bioscfg_drv.spm_data.auth_token = kmemdup(buf, length, GFP_KERNEL);
+ if (!bioscfg_drv.spm_data.auth_token) {
+ ret = -ENOMEM;
+ goto exit_token;
+ }
+
+ return count;
+
+exit_token:
+ kfree(bioscfg_drv.spm_data.auth_token);
+ bioscfg_drv.spm_data.auth_token = NULL;
+
+ return ret;
+}
+
+static struct kobj_attribute password_spm_auth_token = __ATTR_WO(auth_token);
+
+static struct attribute *secure_platform_attrs[] = {
+ &password_spm_is_key_enabled.attr,
+ &password_spm_signing_key.attr,
+ &password_spm_endorsement_key.attr,
+ &password_spm_key_mechanism.attr,
+ &password_spm_status.attr,
+ &password_spm_role.attr,
+ &password_spm_auth_token.attr,
+ NULL,
+};
+
+static const struct attribute_group secure_platform_attr_group = {
+ .attrs = secure_platform_attrs,
+};
+
+void hp_exit_secure_platform_attributes(void)
+{
+ /* remove secure platform sysfs entry and free key data*/
+
+ kfree(bioscfg_drv.spm_data.endorsement_key);
+ bioscfg_drv.spm_data.endorsement_key = NULL;
+
+ kfree(bioscfg_drv.spm_data.signing_key);
+ bioscfg_drv.spm_data.signing_key = NULL;
+
+ kfree(bioscfg_drv.spm_data.auth_token);
+ bioscfg_drv.spm_data.auth_token = NULL;
+
+ if (bioscfg_drv.spm_data.attr_name_kobj)
+ sysfs_remove_group(bioscfg_drv.spm_data.attr_name_kobj,
+ &secure_platform_attr_group);
+}
+
+int hp_populate_secure_platform_data(struct kobject *attr_name_kobj)
+{
+ /* Populate data for Secure Platform Management */
+ bioscfg_drv.spm_data.attr_name_kobj = attr_name_kobj;
+
+ strscpy(bioscfg_drv.spm_data.attribute_name, SPM_STR,
+ sizeof(bioscfg_drv.spm_data.attribute_name));
+
+ bioscfg_drv.spm_data.is_enabled = 0;
+ bioscfg_drv.spm_data.mechanism = 0;
+ bioscfg_drv.pending_reboot = false;
+ update_spm_state();
+
+ bioscfg_drv.spm_data.endorsement_key = NULL;
+ bioscfg_drv.spm_data.signing_key = NULL;
+ bioscfg_drv.spm_data.auth_token = NULL;
+
+ return sysfs_create_group(attr_name_kobj, &secure_platform_attr_group);
+}
diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
new file mode 100644
index 0000000000000..f0c20070094d4
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
@@ -0,0 +1,395 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to string type attributes under
+ * HP_WMI_BIOS_STRING_GUID for use with hp-bioscfg driver.
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+
+#define WMI_STRING_TYPE "HPBIOS_BIOSString"
+
+GET_INSTANCE_ID(string);
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ int instance_id = get_string_instance_id(kobj);
+
+ if (instance_id < 0)
+ return -EIO;
+
+ return sysfs_emit(buf, "%s\n",
+ bioscfg_drv.string_data[instance_id].current_value);
+}
+
+/**
+ * validate_string_input() -
+ * Validate input of current_value against min and max lengths
+ *
+ * @instance_id: The instance on which input is validated
+ * @buf: Input value
+ */
+static int validate_string_input(int instance_id, const char *buf)
+{
+ int in_len = strlen(buf);
+ struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
+
+ /* BIOS treats it as a read only attribute */
+ if (string_data->common.is_readonly)
+ return -EIO;
+
+ if (in_len < string_data->min_length || in_len > string_data->max_length)
+ return -ERANGE;
+
+ return 0;
+}
+
+static void update_string_value(int instance_id, char *attr_value)
+{
+ struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
+
+ /* Write settings to BIOS */
+ strscpy(string_data->current_value, attr_value, sizeof(string_data->current_value));
+}
+
+/*
+ * ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name_language_code, string);
+ * static struct kobj_attribute string_display_langcode =
+ * __ATTR_RO(display_name_language_code);
+ */
+
+ATTRIBUTE_S_COMMON_PROPERTY_SHOW(display_name, string);
+static struct kobj_attribute string_display_name =
+ __ATTR_RO(display_name);
+
+ATTRIBUTE_PROPERTY_STORE(current_value, string);
+static struct kobj_attribute string_current_val =
+ __ATTR_RW_MODE(current_value, 0644);
+
+ATTRIBUTE_N_PROPERTY_SHOW(min_length, string);
+static struct kobj_attribute string_min_length =
+ __ATTR_RO(min_length);
+
+ATTRIBUTE_N_PROPERTY_SHOW(max_length, string);
+static struct kobj_attribute string_max_length =
+ __ATTR_RO(max_length);
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "string\n");
+}
+
+static struct kobj_attribute string_type =
+ __ATTR_RO(type);
+
+static struct attribute *string_attrs[] = {
+ &common_display_langcode.attr,
+ &string_display_name.attr,
+ &string_current_val.attr,
+ &string_min_length.attr,
+ &string_max_length.attr,
+ &string_type.attr,
+ NULL
+};
+
+static const struct attribute_group string_attr_group = {
+ .attrs = string_attrs,
+};
+
+int hp_alloc_string_data(void)
+{
+ bioscfg_drv.string_instances_count = hp_get_instance_count(HP_WMI_BIOS_STRING_GUID);
+ bioscfg_drv.string_data = kcalloc(bioscfg_drv.string_instances_count,
+ sizeof(*bioscfg_drv.string_data), GFP_KERNEL);
+ if (!bioscfg_drv.string_data) {
+ bioscfg_drv.string_instances_count = 0;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* Expected Values types associated with each element */
+static const acpi_object_type expected_string_types[] = {
+ [NAME] = ACPI_TYPE_STRING,
+ [VALUE] = ACPI_TYPE_STRING,
+ [PATH] = ACPI_TYPE_STRING,
+ [IS_READONLY] = ACPI_TYPE_INTEGER,
+ [DISPLAY_IN_UI] = ACPI_TYPE_INTEGER,
+ [REQUIRES_PHYSICAL_PRESENCE] = ACPI_TYPE_INTEGER,
+ [SEQUENCE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES_SIZE] = ACPI_TYPE_INTEGER,
+ [PREREQUISITES] = ACPI_TYPE_STRING,
+ [SECURITY_LEVEL] = ACPI_TYPE_INTEGER,
+ [STR_MIN_LENGTH] = ACPI_TYPE_INTEGER,
+ [STR_MAX_LENGTH] = ACPI_TYPE_INTEGER,
+};
+
+static int hp_populate_string_elements_from_package(union acpi_object *string_obj,
+ int string_obj_count,
+ int instance_id)
+{
+ char *str_value = NULL;
+ int value_len;
+ 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)
+ return -EINVAL;
+
+ for (elem = 1, eloc = 1; elem < string_obj_count; elem++, eloc++) {
+ /* ONLY look at the first STRING_ELEM_CNT elements */
+ if (eloc == STR_ELEM_CNT)
+ goto exit_string_package;
+
+ switch (string_obj[elem].type) {
+ case ACPI_TYPE_STRING:
+ if (elem != PREREQUISITES) {
+ ret = hp_convert_hexstr_to_str(string_obj[elem].string.pointer,
+ string_obj[elem].string.length,
+ &str_value, &value_len);
+
+ if (ret)
+ continue;
+ }
+ break;
+ case ACPI_TYPE_INTEGER:
+ int_value = (u32)string_obj[elem].integer.value;
+ break;
+ default:
+ pr_warn("Unsupported object type [%d]\n", string_obj[elem].type);
+ continue;
+ }
+
+ /* Check that both expected and read object type match */
+ if (expected_string_types[eloc] != string_obj[elem].type) {
+ pr_err("Error expected type %d for elem %d, but got type %d instead\n",
+ expected_string_types[eloc], elem, string_obj[elem].type);
+ kfree(str_value);
+ return -EIO;
+ }
+
+ /* Assign appropriate element value to corresponding field*/
+ switch (eloc) {
+ case VALUE:
+ strscpy(string_data->current_value,
+ str_value, sizeof(string_data->current_value));
+ break;
+ case PATH:
+ strscpy(string_data->common.path, str_value,
+ sizeof(string_data->common.path));
+ 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 >= 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,
+ sizeof(string_data->common.prerequisites[reqs]));
+ kfree(str_value);
+ str_value = NULL;
+ }
+ break;
+
+ case SECURITY_LEVEL:
+ string_data->common.security_level = int_value;
+ break;
+ case STR_MIN_LENGTH:
+ string_data->min_length = int_value;
+ break;
+ case STR_MAX_LENGTH:
+ string_data->max_length = int_value;
+ break;
+ default:
+ pr_warn("Invalid element: %d found in String attribute or data may be malformed\n", elem);
+ break;
+ }
+
+ kfree(str_value);
+ str_value = NULL;
+ }
+
+exit_string_package:
+ kfree(str_value);
+ return 0;
+}
+
+/**
+ * hp_populate_string_package_data() -
+ * Populate all properties of an instance under string attribute
+ *
+ * @string_obj: ACPI object with string data
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_string_package_data(union acpi_object *string_obj,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
+
+ string_data->attr_name_kobj = attr_name_kobj;
+
+ hp_populate_string_elements_from_package(string_obj,
+ string_obj->package.count,
+ instance_id);
+
+ hp_update_attribute_permissions(string_data->common.is_readonly,
+ &string_current_val);
+ hp_friendly_user_name_update(string_data->common.path,
+ attr_name_kobj->name,
+ string_data->common.display_name,
+ sizeof(string_data->common.display_name));
+ return sysfs_create_group(attr_name_kobj, &string_attr_group);
+}
+
+static int hp_populate_string_elements_from_buffer(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id)
+{
+ int ret = 0;
+ struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
+
+ /*
+ * Only data relevant to this driver and its functionality is
+ * read. BIOS defines the order in which each * element is
+ * read. Element 0 data is not relevant to this
+ * driver hence it is ignored. For clarity, all element names
+ * (DISPLAY_IN_UI) which defines the order in which is read
+ * and the name matches the variable where the data is stored.
+ *
+ * In earlier implementation, reported errors were ignored
+ * causing the data to remain uninitialized. It is not
+ * possible to determine if data read from BIOS is valid or
+ * not. It is for this reason functions may return a error
+ * without validating the data itself.
+ */
+
+ // VALUE:
+ ret = hp_get_string_from_buffer(&buffer_ptr, buffer_size, string_data->current_value,
+ sizeof(string_data->current_value));
+ if (ret < 0)
+ goto buffer_exit;
+
+ // COMMON:
+ ret = hp_get_common_data_from_buffer(&buffer_ptr, buffer_size, &string_data->common);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // STR_MIN_LENGTH:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &string_data->min_length);
+ if (ret < 0)
+ goto buffer_exit;
+
+ // STR_MAX_LENGTH:
+ ret = hp_get_integer_from_buffer(&buffer_ptr, buffer_size,
+ &string_data->max_length);
+
+buffer_exit:
+
+ return ret;
+}
+
+/**
+ * hp_populate_string_buffer_data() -
+ * Populate all properties of an instance under string attribute
+ *
+ * @buffer_ptr: Buffer pointer
+ * @buffer_size: Buffer size
+ * @instance_id: The instance to enumerate
+ * @attr_name_kobj: The parent kernel object
+ */
+int hp_populate_string_buffer_data(u8 *buffer_ptr, u32 *buffer_size,
+ int instance_id,
+ struct kobject *attr_name_kobj)
+{
+ struct string_data *string_data = &bioscfg_drv.string_data[instance_id];
+ int ret = 0;
+
+ string_data->attr_name_kobj = attr_name_kobj;
+
+ ret = hp_populate_string_elements_from_buffer(buffer_ptr, buffer_size,
+ instance_id);
+ if (ret < 0)
+ return ret;
+
+ hp_update_attribute_permissions(string_data->common.is_readonly,
+ &string_current_val);
+ hp_friendly_user_name_update(string_data->common.path,
+ attr_name_kobj->name,
+ string_data->common.display_name,
+ sizeof(string_data->common.display_name));
+
+ return sysfs_create_group(attr_name_kobj, &string_attr_group);
+}
+
+/**
+ * hp_exit_string_attributes() - Clear all attribute data
+ *
+ * Clears all data allocated for this group of attributes
+ */
+void hp_exit_string_attributes(void)
+{
+ int instance_id;
+
+ for (instance_id = 0; instance_id < bioscfg_drv.string_instances_count;
+ instance_id++) {
+ struct kobject *attr_name_kobj =
+ bioscfg_drv.string_data[instance_id].attr_name_kobj;
+
+ if (attr_name_kobj)
+ sysfs_remove_group(attr_name_kobj, &string_attr_group);
+ }
+ bioscfg_drv.string_instances_count = 0;
+
+ kfree(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
new file mode 100644
index 0000000000000..b57e42f29282d
--- /dev/null
+++ b/drivers/platform/x86/hp/hp-bioscfg/surestart-attributes.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Functions corresponding to sure start object type attributes under
+ * BIOS for use with hp-bioscfg driver
+ *
+ * Copyright (c) 2022 HP Development Company, L.P.
+ */
+
+#include "bioscfg.h"
+#include <linux/types.h>
+
+/* Maximum number of log entries supported when log entry size is 16
+ * bytes. This value is calculated by dividing 4096 (page size) by
+ * log entry size.
+ */
+#define LOG_MAX_ENTRIES 254
+
+/*
+ * Current Log entry size. This value size will change in the
+ * future. The driver reads a total of 128 bytes for each log entry
+ * provided by BIOS but only the first 16 bytes are used/read.
+ */
+#define LOG_ENTRY_SIZE 16
+
+/*
+ * audit_log_entry_count_show - Reports the number of
+ * existing audit log entries available
+ * to be read
+ */
+static ssize_t audit_log_entry_count_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret;
+ u32 count = 0;
+
+ ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
+ HPWMI_SURESTART,
+ &count, 1, sizeof(count));
+
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d,%d,%d\n", count, LOG_ENTRY_SIZE,
+ LOG_MAX_ENTRIES);
+}
+
+/*
+ * audit_log_entries_show() - Return all entries found in log file
+ */
+static ssize_t audit_log_entries_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret;
+ int i;
+ u32 count = 0;
+ u8 audit_log_buffer[128];
+
+ // Get the number of event logs
+ ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG_COUNT,
+ HPWMI_SURESTART,
+ &count, 1, sizeof(count));
+
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The show() api will not work if the audit logs ever go
+ * beyond 4KB
+ */
+ if (count * LOG_ENTRY_SIZE > PAGE_SIZE)
+ return -EIO;
+
+ /*
+ * We are guaranteed the buffer is 4KB so today all the event
+ * logs will fit
+ */
+ for (i = 0; i < count; i++) {
+ audit_log_buffer[0] = i + 1;
+
+ /*
+ * read audit log entry at a time. 'buf' input value
+ * provides the audit log entry to be read. On
+ * input, Byte 0 = Audit Log entry number from
+ * beginning (1..254)
+ * Entry number 1 is the newest entry whereas the
+ * highest entry number (number of entries) is the
+ * oldest entry.
+ */
+ ret = hp_wmi_perform_query(HPWMI_SURESTART_GET_LOG,
+ 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.
+ */
+ break;
+ } else {
+ memcpy(buf, audit_log_buffer, LOG_ENTRY_SIZE);
+ buf += LOG_ENTRY_SIZE;
+ }
+ }
+
+ return i * LOG_ENTRY_SIZE;
+}
+
+static struct kobj_attribute sure_start_audit_log_entry_count = __ATTR_RO(audit_log_entry_count);
+static struct kobj_attribute sure_start_audit_log_entries = __ATTR_RO(audit_log_entries);
+
+static struct attribute *sure_start_attrs[] = {
+ &sure_start_audit_log_entry_count.attr,
+ &sure_start_audit_log_entries.attr,
+ NULL
+};
+
+static const struct attribute_group sure_start_attr_group = {
+ .attrs = sure_start_attrs,
+};
+
+void hp_exit_sure_start_attributes(void)
+{
+ sysfs_remove_group(bioscfg_drv.sure_start_attr_kobj,
+ &sure_start_attr_group);
+}
+
+int hp_populate_sure_start_data(struct kobject *attr_name_kobj)
+{
+ bioscfg_drv.sure_start_attr_kobj = attr_name_kobj;
+ return sysfs_create_group(attr_name_kobj, &sure_start_attr_group);
+}
diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
index 6d9297c1d96c1..ac037540acfc6 100644
--- a/drivers/platform/x86/ideapad-laptop.c
+++ b/drivers/platform/x86/ideapad-laptop.c
@@ -10,6 +10,7 @@
#include <linux/acpi.h>
#include <linux/backlight.h>
+#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/bug.h>
#include <linux/debugfs.h>
@@ -85,6 +86,31 @@ enum {
SALS_FNLOCK_OFF = 0xf,
};
+/*
+ * These correspond to the number of supported states - 1
+ * Future keyboard types may need a new system, if there's a collision
+ * KBD_BL_TRISTATE_AUTO has no way to report or set the auto state
+ * so it effectively has 3 states, but needs to handle 4
+ */
+enum {
+ KBD_BL_STANDARD = 1,
+ KBD_BL_TRISTATE = 2,
+ KBD_BL_TRISTATE_AUTO = 3,
+};
+
+#define KBD_BL_QUERY_TYPE 0x1
+#define KBD_BL_TRISTATE_TYPE 0x5
+#define KBD_BL_TRISTATE_AUTO_TYPE 0x7
+
+#define KBD_BL_COMMAND_GET 0x2
+#define KBD_BL_COMMAND_SET 0x3
+#define KBD_BL_COMMAND_TYPE GENMASK(7, 4)
+
+#define KBD_BL_GET_BRIGHTNESS GENMASK(15, 1)
+#define KBD_BL_SET_BRIGHTNESS GENMASK(19, 16)
+
+#define KBD_BL_KBLC_CHANGED_EVENT 12
+
struct ideapad_dytc_priv {
enum platform_profile_option current_profile;
struct platform_profile_handler pprof;
@@ -122,6 +148,7 @@ struct ideapad_private {
} features;
struct {
bool initialized;
+ int type;
struct led_classdev led;
unsigned int last_brightness;
} kbd_bl;
@@ -242,6 +269,16 @@ static int exec_sals(acpi_handle handle, unsigned long arg)
return exec_simple_method(handle, "SALS", arg);
}
+static int exec_kblc(acpi_handle handle, unsigned long arg)
+{
+ return exec_simple_method(handle, "KBLC", arg);
+}
+
+static int eval_kblc(acpi_handle handle, unsigned long cmd, unsigned long *res)
+{
+ return eval_int_with_arg(handle, "KBLC", cmd, res);
+}
+
static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res)
{
return eval_int_with_arg(handle, "DYTC", cmd, res);
@@ -1275,16 +1312,47 @@ static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
/*
* keyboard backlight
*/
+static int ideapad_kbd_bl_check_tristate(int type)
+{
+ return (type == KBD_BL_TRISTATE) || (type == KBD_BL_TRISTATE_AUTO);
+}
+
static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
{
- unsigned long hals;
+ unsigned long value;
int err;
- err = eval_hals(priv->adev->handle, &hals);
+ if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
+ err = eval_kblc(priv->adev->handle,
+ FIELD_PREP(KBD_BL_COMMAND_TYPE, priv->kbd_bl.type) |
+ KBD_BL_COMMAND_GET,
+ &value);
+
+ if (err)
+ return err;
+
+ /* Convert returned value to brightness level */
+ value = FIELD_GET(KBD_BL_GET_BRIGHTNESS, value);
+
+ /* Off, low or high */
+ if (value <= priv->kbd_bl.led.max_brightness)
+ return value;
+
+ /* Auto, report as off */
+ if (value == priv->kbd_bl.led.max_brightness + 1)
+ return 0;
+
+ /* Unknown value */
+ dev_warn(&priv->platform_device->dev,
+ "Unknown keyboard backlight value: %lu", value);
+ return -EINVAL;
+ }
+
+ err = eval_hals(priv->adev->handle, &value);
if (err)
return err;
- return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals);
+ return !!test_bit(HALS_KBD_BL_STATE_BIT, &value);
}
static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev)
@@ -1296,7 +1364,21 @@ static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_cla
static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness)
{
- int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF);
+ int err;
+ unsigned long value;
+ int type = priv->kbd_bl.type;
+
+ if (ideapad_kbd_bl_check_tristate(type)) {
+ if (brightness > priv->kbd_bl.led.max_brightness)
+ return -EINVAL;
+
+ value = FIELD_PREP(KBD_BL_SET_BRIGHTNESS, brightness) |
+ FIELD_PREP(KBD_BL_COMMAND_TYPE, type) |
+ KBD_BL_COMMAND_SET;
+ err = exec_kblc(priv->adev->handle, value);
+ } else {
+ err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF);
+ }
if (err)
return err;
@@ -1349,8 +1431,13 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
priv->kbd_bl.last_brightness = brightness;
+ if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
+ priv->kbd_bl.led.max_brightness = 2;
+ } else {
+ priv->kbd_bl.led.max_brightness = 1;
+ }
+
priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT;
- priv->kbd_bl.led.max_brightness = 1;
priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get;
priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set;
priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED;
@@ -1461,6 +1548,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
case 2:
ideapad_backlight_notify_power(priv);
break;
+ case KBD_BL_KBLC_CHANGED_EVENT:
case 1:
/*
* Some IdeaPads report event 1 every ~20
@@ -1562,13 +1650,31 @@ static void ideapad_check_features(struct ideapad_private *priv)
if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val))
priv->features.fn_lock = true;
- if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val))
+ if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) {
priv->features.kbd_bl = true;
+ priv->kbd_bl.type = KBD_BL_STANDARD;
+ }
if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val))
priv->features.usb_charging = true;
}
}
+
+ if (acpi_has_method(handle, "KBLC")) {
+ if (!eval_kblc(priv->adev->handle, KBD_BL_QUERY_TYPE, &val)) {
+ if (val == KBD_BL_TRISTATE_TYPE) {
+ priv->features.kbd_bl = true;
+ priv->kbd_bl.type = KBD_BL_TRISTATE;
+ } else if (val == KBD_BL_TRISTATE_AUTO_TYPE) {
+ priv->features.kbd_bl = true;
+ priv->kbd_bl.type = KBD_BL_TRISTATE_AUTO;
+ } else {
+ dev_warn(&priv->platform_device->dev,
+ "Unknown keyboard type: %lu",
+ val);
+ }
+ }
+ }
}
#if IS_ENABLED(CONFIG_ACPI_WMI)
diff --git a/drivers/platform/x86/intel/tpmi.c b/drivers/platform/x86/intel/tpmi.c
index d1fd6e69401c2..0a95736d97e4d 100644
--- a/drivers/platform/x86/intel/tpmi.c
+++ b/drivers/platform/x86/intel/tpmi.c
@@ -47,10 +47,17 @@
*/
#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
#include <linux/intel_tpmi.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/pci.h>
+#include <linux/security.h>
+#include <linux/sizes.h>
+#include <linux/string_helpers.h>
#include "vsec.h"
@@ -83,12 +90,14 @@ struct intel_tpmi_pfs_entry {
* @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;
unsigned int vsec_offset;
+ struct intel_vsec_device *vsec_dev;
};
/**
@@ -98,6 +107,8 @@ struct intel_tpmi_pm_feature {
* @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.
*/
@@ -107,6 +118,8 @@ struct intel_tpmi_info {
int feature_count;
u64 pfs_start;
struct intel_tpmi_plat_info plat_info;
+ void __iomem *tpmi_control_mem;
+ struct dentry *dbgfs_dir;
};
/**
@@ -139,9 +152,19 @@ enum intel_tpmi_id {
TPMI_ID_PEM = 1, /* Power and Perf excursion Monitor */
TPMI_ID_UNCORE = 2, /* Uncore Frequency Scaling */
TPMI_ID_SST = 5, /* Speed Select Technology */
+ TPMI_CONTROL_ID = 0x80, /* Special ID for getting feature status */
TPMI_INFO_ID = 0x81, /* Special ID for PCI BDF and Package ID information */
};
+/*
+ * 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);
@@ -175,6 +198,349 @@ struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int
}
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
+
+/*
+ * 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 TMPI_CONTROL_DATA_VAL GENMASK_ULL(63, 32)
+#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)
+
+#define TPMI_STATE_DISABLED BIT_ULL(0)
+#define TPMI_STATE_LOCKED BIT_ULL(31)
+
+/* 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,
+ int *locked, int *disabled)
+{
+ 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 */
+ data = readq(tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET);
+ data = FIELD_GET(TMPI_CONTROL_DATA_VAL, data);
+
+ *disabled = 0;
+ *locked = 0;
+
+ if (!(data & TPMI_STATE_DISABLED))
+ *disabled = 1;
+
+ if (data & TPMI_STATE_LOCKED)
+ *locked = 1;
+
+ 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,
+ int *locked, int *disabled)
+{
+ 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_read_feature_status(tpmi_info, feature_id, locked, disabled);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, INTEL_TPMI);
+
+static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused)
+{
+ struct intel_tpmi_info *tpmi_info = s->private;
+ struct intel_tpmi_pm_feature *pfs;
+ int locked, disabled, 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\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, &locked,
+ &disabled);
+ if (ret) {
+ locked = 'U';
+ disabled = 'U';
+ } else {
+ disabled = disabled ? 'Y' : 'N';
+ locked = locked ? 'Y' : 'N';
+ }
+ seq_printf(s, "0x%02x\t\t0x%02x\t\t0x%04x\t\t0x%04x\t\t0x%02x\t\t0x%08x\t%c\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);
+ }
+
+ 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 off, size;
+ 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%x\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);
+
+ 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, *array;
+ void __iomem *mem;
+ int ret;
+
+ size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs);
+ if (!size)
+ return -EIO;
+
+ ret = parse_int_array_user(userbuf, len, (int **)&array);
+ if (ret < 0)
+ return ret;
+
+ num_elems = *array;
+ if (num_elems != 3) {
+ ret = -EINVAL;
+ goto exit_write;
+ }
+
+ punit = array[1];
+ addr = array[2];
+ value = array[3];
+
+ if (punit >= pfs->pfs_header.num_entries) {
+ ret = -EINVAL;
+ goto exit_write;
+ }
+
+ if (addr >= size) {
+ ret = -EINVAL;
+ goto exit_write;
+ }
+
+ mutex_lock(&tpmi_dev_lock);
+
+ mem = ioremap(pfs->vsec_offset + punit * size, size);
+ if (!mem) {
+ ret = -ENOMEM;
+ goto unlock_mem_write;
+ }
+
+ writel(value, mem + addr);
+
+ iounmap(mem);
+
+ ret = len;
+
+unlock_mem_write:
+ mutex_unlock(&tpmi_dev_lock);
+
+exit_write:
+ kfree(array);
+
+ return ret;
+}
+
+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->pcidev->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) {
@@ -316,7 +682,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
struct pci_dev *pci_dev = vsec_dev->pcidev;
struct intel_tpmi_info *tpmi_info;
u64 pfs_start = 0;
- int i;
+ int ret, i;
tpmi_info = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_info), GFP_KERNEL);
if (!tpmi_info)
@@ -339,6 +705,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
int size, ret;
pfs = &tpmi_info->tpmi_features[i];
+ pfs->vsec_dev = vsec_dev;
res = &vsec_dev->resource[i];
if (!res)
@@ -367,13 +734,29 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
*/
if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID)
tpmi_process_info(tpmi_info, pfs);
+
+ 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);
- return tpmi_create_devices(tpmi_info);
+ ret = tpmi_create_devices(tpmi_info);
+ if (ret)
+ return ret;
+
+ /*
+ * 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);
+
+ return 0;
}
static int tpmi_probe(struct auxiliary_device *auxdev,
@@ -382,11 +765,12 @@ static int tpmi_probe(struct auxiliary_device *auxdev,
return intel_vsec_tpmi_init(auxdev);
}
-/*
- * Remove callback is not needed currently as there is no
- * cleanup required. All memory allocs are device managed. All
- * devices created by this modules are also device managed.
- */
+static void tpmi_remove(struct auxiliary_device *auxdev)
+{
+ struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(auxdev);
+
+ debugfs_remove_recursive(tpmi_info->dbgfs_dir);
+}
static const struct auxiliary_device_id tpmi_id_table[] = {
{ .name = "intel_vsec.tpmi" },
@@ -397,6 +781,7 @@ MODULE_DEVICE_TABLE(auxiliary, tpmi_id_table);
static struct auxiliary_driver tpmi_aux_driver = {
.id_table = tpmi_id_table,
.probe = tpmi_probe,
+ .remove = tpmi_remove,
};
module_auxiliary_driver(tpmi_aux_driver);
diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c
index 7d33977d9c609..3d96dbf79a725 100644
--- a/drivers/platform/x86/mlx-platform.c
+++ b/drivers/platform/x86/mlx-platform.c
@@ -12,6 +12,7 @@
#include <linux/i2c-mux.h>
#include <linux/io.h>
#include <linux/module.h>
+#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/platform_data/i2c-mux-reg.h>
#include <linux/platform_data/mlxreg.h>
@@ -35,6 +36,7 @@
#define MLXPLAT_CPLD_LPC_REG_CPLD3_PN1_OFFSET 0x09
#define MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET 0x0a
#define MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET 0x0b
+#define MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET 0x17
#define MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET 0x19
#define MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET 0x1c
#define MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET 0x1d
@@ -62,6 +64,7 @@
#define MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET 0x37
#define MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET 0x3a
#define MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET 0x3b
+#define MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET 0x3c
#define MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET 0x40
#define MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET 0x41
#define MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET 0x42
@@ -94,6 +97,9 @@
#define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET 0x88
#define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET 0x89
#define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET 0x8a
+#define MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET 0x8e
+#define MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET 0x8f
+#define MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET 0x90
#define MLXPLAT_CPLD_LPC_REG_EROT_OFFSET 0x91
#define MLXPLAT_CPLD_LPC_REG_EROT_EVENT_OFFSET 0x92
#define MLXPLAT_CPLD_LPC_REG_EROT_MASK_OFFSET 0x93
@@ -128,6 +134,7 @@
#define MLXPLAT_CPLD_LPC_REG_DBG4_OFFSET 0xb9
#define MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET 0xc2
#define MLXPLAT_CPLD_LPC_REG_SPI_CHNL_SELECT 0xc3
+#define MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET 0xc4
#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET 0xc7
#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET 0xc8
#define MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET 0xc9
@@ -236,6 +243,7 @@
#define MLXPLAT_CPLD_VOLTREG_UPD_MASK GENMASK(5, 4)
#define MLXPLAT_CPLD_GWP_MASK GENMASK(0, 0)
#define MLXPLAT_CPLD_EROT_MASK GENMASK(1, 0)
+#define MLXPLAT_CPLD_FU_CAP_MASK GENMASK(1, 0)
#define MLXPLAT_CPLD_PWR_BUTTON_MASK BIT(0)
#define MLXPLAT_CPLD_LATCH_RST_MASK BIT(6)
#define MLXPLAT_CPLD_THERMAL1_PDB_MASK BIT(3)
@@ -248,6 +256,7 @@
MLXPLAT_CPLD_PWM_PG_MASK)
#define MLXPLAT_CPLD_I2C_CAP_BIT 0x04
#define MLXPLAT_CPLD_I2C_CAP_MASK GENMASK(5, MLXPLAT_CPLD_I2C_CAP_BIT)
+#define MLXPLAT_CPLD_SYS_RESET_MASK BIT(0)
/* Masks for aggregation for comex carriers */
#define MLXPLAT_CPLD_AGGR_MASK_CARRIER BIT(1)
@@ -259,6 +268,7 @@
#define MLXPLAT_CPLD_LPC_LC_MASK GENMASK(7, 0)
#define MLXPLAT_CPLD_HALT_MASK BIT(3)
+#define MLXPLAT_CPLD_RESET_MASK GENMASK(7, 1)
/* Default I2C parent bus number */
#define MLXPLAT_CPLD_PHYS_ADAPTER_DEF_NR 1
@@ -322,6 +332,12 @@
#define MLXPLAT_I2C_MAIN_BUS_NOTIFIED 0x01
#define MLXPLAT_I2C_MAIN_BUS_HANDLE_CREATED 0x02
+/* Lattice FPGA PCI configuration */
+#define PCI_VENDOR_ID_LATTICE 0x1204
+#define PCI_DEVICE_ID_LATTICE_I2C_BRIDGE 0x9c2f
+#define PCI_DEVICE_ID_LATTICE_JTAG_BRIDGE 0x9c30
+#define PCI_DEVICE_ID_LATTICE_LPC_BRIDGE 0x9c32
+
/* mlxplat_priv - platform private data
* @pdev_i2c - i2c controller platform device
* @pdev_mux - array of mux platform devices
@@ -334,6 +350,7 @@
* @hotplug_resources: system hotplug resources
* @hotplug_resources_size: size of system hotplug resources
* @hi2c_main_init_status: init status of I2C main bus
+ * @irq_fpga: FPGA IRQ number
*/
struct mlxplat_priv {
struct platform_device *pdev_i2c;
@@ -347,10 +364,12 @@ struct mlxplat_priv {
struct resource *hotplug_resources;
unsigned int hotplug_resources_size;
u8 i2c_main_init_status;
+ int irq_fpga;
};
static struct platform_device *mlxplat_dev;
static int mlxplat_i2c_main_complition_notify(void *handle, int id);
+static void __iomem *i2c_bridge_addr, *jtag_bridge_addr;
/* Regions for LPC I2C controller and LPC base register space */
static const struct resource mlxplat_lpc_resources[] = {
@@ -435,6 +454,7 @@ static struct i2c_mux_reg_platform_data mlxplat_default_mux_data[] = {
static int mlxplat_max_adap_num;
static int mlxplat_mux_num;
static struct i2c_mux_reg_platform_data *mlxplat_mux_data;
+static struct notifier_block *mlxplat_reboot_nb;
/* Platform extended mux data */
static struct i2c_mux_reg_platform_data mlxplat_extended_mux_data[] = {
@@ -2355,8 +2375,11 @@ static int
mlxplat_mlxcpld_l1_switch_pwr_events_handler(void *handle, enum mlxreg_hotplug_kind kind,
u8 action)
{
- dev_info(&mlxplat_dev->dev, "System shutdown due to short press of power button");
- kernel_power_off();
+ if (action) {
+ dev_info(&mlxplat_dev->dev, "System shutdown due to short press of power button");
+ kernel_power_off();
+ }
+
return 0;
}
@@ -2371,6 +2394,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_pwr_events_items_data[]
.reg = MLXPLAT_CPLD_LPC_REG_PWRB_OFFSET,
.mask = MLXPLAT_CPLD_PWR_BUTTON_MASK,
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
+ .hpdev.action = MLXREG_HOTPLUG_DEVICE_NO_ACTION,
.hpdev.notifier = &mlxplat_mlxcpld_l1_switch_pwr_events_notifier,
},
};
@@ -2431,6 +2455,7 @@ static struct mlxreg_core_data mlxplat_mlxcpld_l1_switch_health_events_items_dat
.reg = MLXPLAT_CPLD_LPC_REG_BRD_OFFSET,
.mask = MLXPLAT_CPLD_INTRUSION_MASK,
.hpdev.nr = MLXPLAT_CPLD_NR_NONE,
+ .hpdev.action = MLXREG_HOTPLUG_DEVICE_NO_ACTION,
.hpdev.notifier = &mlxplat_mlxcpld_l1_switch_intrusion_events_notifier,
},
{
@@ -3428,6 +3453,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
.mode = 0444,
},
{
+ .label = "cpld5_version",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
.label = "cpld1_pn",
.reg = MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET,
.bit = GENMASK(15, 0),
@@ -3456,6 +3487,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
.regnum = 2,
},
{
+ .label = "cpld5_pn",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET,
+ .bit = GENMASK(15, 0),
+ .mode = 0444,
+ .regnum = 2,
+ },
+ {
.label = "cpld1_version_min",
.reg = MLXPLAT_CPLD_LPC_REG_CPLD1_MVER_OFFSET,
.bit = GENMASK(7, 0),
@@ -3480,6 +3518,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
.mode = 0444,
},
{
+ .label = "cpld5_version_min",
+ .reg = MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET,
+ .bit = GENMASK(7, 0),
+ .mode = 0444,
+ },
+ {
.label = "asic_reset",
.reg = MLXPLAT_CPLD_LPC_REG_RESET_GP2_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(3),
@@ -3555,9 +3599,9 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
.mode = 0444,
},
{
- .label = "reset_from_comex",
+ .label = "reset_swb_dc_dc_pwr_fail",
.reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
- .mask = GENMASK(7, 0) & ~BIT(4),
+ .mask = GENMASK(7, 0) & ~BIT(3),
.mode = 0444,
},
{
@@ -3579,6 +3623,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
.mode = 0444,
},
{
+ .label = "reset_sw_reset",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(0),
+ .mode = 0444,
+ },
+ {
.label = "reset_comex_pwr_fail",
.reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(3),
@@ -3681,6 +3731,13 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
.mode = 0200,
},
{
+ .label = "jtag_cap",
+ .reg = MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET,
+ .mask = MLXPLAT_CPLD_FU_CAP_MASK,
+ .bit = 1,
+ .mode = 0444,
+ },
+ {
.label = "jtag_enable",
.reg = MLXPLAT_CPLD_LPC_REG_GP2_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(4),
@@ -3793,6 +3850,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = {
.mode = 0444,
},
{
+ .label = "lid_open",
+ .reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(2),
+ .mode = 0444,
+ },
+ {
.label = "clk_brd1_boot_fail",
.reg = MLXPLAT_CPLD_LPC_REG_GP4_RO_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(4),
@@ -4432,6 +4495,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_chassis_blade_regs_io_data[] = {
.mode = 0444,
},
{
+ .label = "reset_long_pwr_pb",
+ .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET,
+ .mask = GENMASK(7, 0) & ~BIT(7),
+ .mode = 0444,
+ },
+ {
.label = "pwr_cycle",
.reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(2),
@@ -4905,6 +4974,7 @@ static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type3[] = {
static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
+ case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET:
case MLXPLAT_CPLD_LPC_REG_LED1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_LED2_OFFSET:
@@ -4923,6 +4993,7 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_SAFE_BIOS_OFFSET:
case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET:
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET:
@@ -5001,6 +5072,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD1_PN1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET:
@@ -5009,6 +5081,9 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_REG_CPLD3_PN1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET:
case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET:
case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET:
@@ -5034,6 +5109,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET:
case MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET:
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET:
@@ -5119,6 +5195,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_PWM2_OFFSET:
case MLXPLAT_CPLD_LPC_REG_PWM3_OFFSET:
@@ -5160,6 +5237,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_REG_CPLD2_VER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD3_VER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD4_VER_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_CPLD5_VER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD1_PN_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD1_PN1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD2_PN_OFFSET:
@@ -5168,6 +5246,9 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_REG_CPLD3_PN1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD4_PN_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD4_PN1_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_CPLD5_PN_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_CPLD5_PN1_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_RESET_GP4_OFFSET:
case MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET:
case MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET:
@@ -5191,6 +5272,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_SAFE_BIOS_WP_OFFSET:
case MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET:
case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_FU_CAP_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG2_OFFSET:
case MLXPLAT_CPLD_LPC_REG_DBG3_OFFSET:
@@ -5270,6 +5352,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
case MLXPLAT_CPLD_LPC_REG_CPLD2_MVER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD3_MVER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_CPLD4_MVER_OFFSET:
+ case MLXPLAT_CPLD_LPC_REG_CPLD5_MVER_OFFSET:
case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
case MLXPLAT_CPLD_LPC_REG_PWM2_OFFSET:
case MLXPLAT_CPLD_LPC_REG_PWM3_OFFSET:
@@ -5469,13 +5552,50 @@ static struct mlxreg_core_platform_data *mlxplat_fan;
static struct mlxreg_core_platform_data
*mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS];
static const struct regmap_config *mlxplat_regmap_config;
+static struct pci_dev *lpc_bridge;
+static struct pci_dev *i2c_bridge;
+static struct pci_dev *jtag_bridge;
+
+/* Platform default reset function */
+static int mlxplat_reboot_notifier(struct notifier_block *nb, unsigned long action, void *unused)
+{
+ struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev);
+ u32 regval;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET, &regval);
+
+ if (action == SYS_RESTART && !ret && regval & MLXPLAT_CPLD_SYS_RESET_MASK)
+ regmap_write(priv->regmap, MLXPLAT_CPLD_LPC_REG_RESET_GP1_OFFSET,
+ MLXPLAT_CPLD_RESET_MASK);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block mlxplat_reboot_default_nb = {
+ .notifier_call = mlxplat_reboot_notifier,
+};
/* Platform default poweroff function */
static void mlxplat_poweroff(void)
{
struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev);
+ if (mlxplat_reboot_nb)
+ unregister_reboot_notifier(mlxplat_reboot_nb);
regmap_write(priv->regmap, MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, MLXPLAT_CPLD_HALT_MASK);
+ kernel_halt();
+}
+
+static int __init mlxplat_register_platform_device(void)
+{
+ mlxplat_dev = platform_device_register_simple(MLX_PLAT_DEVICE_NAME, -1,
+ mlxplat_lpc_resources,
+ ARRAY_SIZE(mlxplat_lpc_resources));
+ if (IS_ERR(mlxplat_dev))
+ return PTR_ERR(mlxplat_dev);
+ else
+ return 1;
}
static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
@@ -5498,7 +5618,7 @@ static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_default_wc_matched(const struct dmi_system_id *dmi)
@@ -5521,7 +5641,7 @@ static int __init mlxplat_dmi_default_wc_matched(const struct dmi_system_id *dmi
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_default_eth_wc_blade_matched(const struct dmi_system_id *dmi)
@@ -5546,7 +5666,7 @@ static int __init mlxplat_dmi_default_eth_wc_blade_matched(const struct dmi_syst
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi)
@@ -5569,7 +5689,7 @@ static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi)
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi)
@@ -5592,7 +5712,7 @@ static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi)
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi)
@@ -5615,7 +5735,7 @@ static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi)
mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi)
@@ -5641,7 +5761,7 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi)
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_comex_matched(const struct dmi_system_id *dmi)
@@ -5666,7 +5786,7 @@ static int __init mlxplat_dmi_comex_matched(const struct dmi_system_id *dmi)
mlxplat_i2c = &mlxplat_mlxcpld_i2c_default_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_comex;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi)
@@ -5692,7 +5812,7 @@ static int __init mlxplat_dmi_ng400_matched(const struct dmi_system_id *dmi)
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi)
@@ -5712,7 +5832,7 @@ static int __init mlxplat_dmi_modular_matched(const struct dmi_system_id *dmi)
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_eth_modular;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_chassis_blade_matched(const struct dmi_system_id *dmi)
@@ -5734,7 +5854,7 @@ static int __init mlxplat_dmi_chassis_blade_matched(const struct dmi_system_id *
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_rack_switch_matched(const struct dmi_system_id *dmi)
@@ -5755,7 +5875,7 @@ static int __init mlxplat_dmi_rack_switch_matched(const struct dmi_system_id *dm
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_rack_switch;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi)
@@ -5776,7 +5896,7 @@ static int __init mlxplat_dmi_ng800_matched(const struct dmi_system_id *dmi)
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng400;
- return 1;
+ return mlxplat_register_platform_device();
}
static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi)
@@ -5797,8 +5917,9 @@ static int __init mlxplat_dmi_l1_switch_matched(const struct dmi_system_id *dmi)
mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data;
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_rack_switch;
pm_power_off = mlxplat_poweroff;
+ mlxplat_reboot_nb = &mlxplat_reboot_default_nb;
- return 1;
+ return mlxplat_register_platform_device();
}
static const struct dmi_system_id mlxplat_dmi_table[] __initconst = {
@@ -6042,12 +6163,6 @@ static int mlxplat_lpc_cpld_device_init(struct resource **hotplug_resources,
{
int err;
- mlxplat_dev = platform_device_register_simple(MLX_PLAT_DEVICE_NAME, PLATFORM_DEVID_NONE,
- mlxplat_lpc_resources,
- ARRAY_SIZE(mlxplat_lpc_resources));
- if (IS_ERR(mlxplat_dev))
- return PTR_ERR(mlxplat_dev);
-
mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev,
mlxplat_lpc_resources[1].start, 1);
if (!mlxplat_mlxcpld_regmap_ctx.base) {
@@ -6061,24 +6176,138 @@ static int mlxplat_lpc_cpld_device_init(struct resource **hotplug_resources,
return 0;
fail_devm_ioport_map:
- platform_device_unregister(mlxplat_dev);
return err;
}
static void mlxplat_lpc_cpld_device_exit(void)
{
- platform_device_unregister(mlxplat_dev);
+}
+
+static int
+mlxplat_pci_fpga_device_init(unsigned int device, const char *res_name, struct pci_dev **pci_bridge,
+ void __iomem **pci_bridge_addr)
+{
+ void __iomem *pci_mem_addr;
+ struct pci_dev *pci_dev;
+ int err;
+
+ pci_dev = pci_get_device(PCI_VENDOR_ID_LATTICE, device, NULL);
+ if (!pci_dev)
+ return -ENODEV;
+
+ err = pci_enable_device(pci_dev);
+ if (err) {
+ dev_err(&pci_dev->dev, "pci_enable_device failed with error %d\n", err);
+ goto fail_pci_enable_device;
+ }
+
+ err = pci_request_region(pci_dev, 0, res_name);
+ if (err) {
+ dev_err(&pci_dev->dev, "pci_request_regions failed with error %d\n", err);
+ goto fail_pci_request_regions;
+ }
+
+ err = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(64));
+ if (err) {
+ err = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32));
+ if (err) {
+ dev_err(&pci_dev->dev, "dma_set_mask failed with error %d\n", err);
+ goto fail_pci_set_dma_mask;
+ }
+ }
+
+ pci_set_master(pci_dev);
+
+ pci_mem_addr = devm_ioremap(&pci_dev->dev, pci_resource_start(pci_dev, 0),
+ pci_resource_len(pci_dev, 0));
+ if (!pci_mem_addr) {
+ dev_err(&mlxplat_dev->dev, "ioremap failed\n");
+ err = -EIO;
+ goto fail_ioremap;
+ }
+
+ *pci_bridge = pci_dev;
+ *pci_bridge_addr = pci_mem_addr;
+
+ return 0;
+
+fail_ioremap:
+fail_pci_set_dma_mask:
+ pci_release_regions(pci_dev);
+fail_pci_request_regions:
+ pci_disable_device(pci_dev);
+fail_pci_enable_device:
+ return err;
+}
+
+static void
+mlxplat_pci_fpga_device_exit(struct pci_dev *pci_bridge,
+ void __iomem *pci_bridge_addr)
+{
+ iounmap(pci_bridge_addr);
+ pci_release_regions(pci_bridge);
+ pci_disable_device(pci_bridge);
+}
+
+static int
+mlxplat_pci_fpga_devices_init(struct resource **hotplug_resources,
+ unsigned int *hotplug_resources_size)
+{
+ int err;
+
+ err = mlxplat_pci_fpga_device_init(PCI_DEVICE_ID_LATTICE_LPC_BRIDGE,
+ "mlxplat_lpc_bridge", &lpc_bridge,
+ &mlxplat_mlxcpld_regmap_ctx.base);
+ if (err)
+ goto mlxplat_pci_fpga_device_init_lpc_fail;
+
+ err = mlxplat_pci_fpga_device_init(PCI_DEVICE_ID_LATTICE_I2C_BRIDGE,
+ "mlxplat_i2c_bridge", &i2c_bridge,
+ &i2c_bridge_addr);
+ if (err)
+ goto mlxplat_pci_fpga_device_init_i2c_fail;
+
+ err = mlxplat_pci_fpga_device_init(PCI_DEVICE_ID_LATTICE_JTAG_BRIDGE,
+ "mlxplat_jtag_bridge", &jtag_bridge,
+ &jtag_bridge_addr);
+ if (err)
+ goto mlxplat_pci_fpga_device_init_jtag_fail;
+
+ return 0;
+
+mlxplat_pci_fpga_device_init_jtag_fail:
+ mlxplat_pci_fpga_device_exit(i2c_bridge, i2c_bridge_addr);
+mlxplat_pci_fpga_device_init_i2c_fail:
+ mlxplat_pci_fpga_device_exit(lpc_bridge, mlxplat_mlxcpld_regmap_ctx.base);
+mlxplat_pci_fpga_device_init_lpc_fail:
+ return err;
+}
+
+static void mlxplat_pci_fpga_devices_exit(void)
+{
+ mlxplat_pci_fpga_device_exit(jtag_bridge, jtag_bridge_addr);
+ mlxplat_pci_fpga_device_exit(i2c_bridge, i2c_bridge_addr);
+ mlxplat_pci_fpga_device_exit(lpc_bridge, mlxplat_mlxcpld_regmap_ctx.base);
}
static int
mlxplat_pre_init(struct resource **hotplug_resources, unsigned int *hotplug_resources_size)
{
- return mlxplat_lpc_cpld_device_init(hotplug_resources, hotplug_resources_size);
+ int err;
+
+ err = mlxplat_pci_fpga_devices_init(hotplug_resources, hotplug_resources_size);
+ if (err == -ENODEV)
+ return mlxplat_lpc_cpld_device_init(hotplug_resources, hotplug_resources_size);
+
+ return err;
}
static void mlxplat_post_exit(void)
{
- mlxplat_lpc_cpld_device_exit();
+ if (lpc_bridge)
+ mlxplat_pci_fpga_devices_exit();
+ else
+ mlxplat_lpc_cpld_device_exit();
}
static int mlxplat_post_init(struct mlxplat_priv *priv)
@@ -6088,6 +6317,8 @@ static int mlxplat_post_init(struct mlxplat_priv *priv)
/* Add hotplug driver */
if (mlxplat_hotplug) {
mlxplat_hotplug->regmap = priv->regmap;
+ if (priv->irq_fpga)
+ mlxplat_hotplug->irq = priv->irq_fpga;
priv->pdev_hotplug =
platform_device_register_resndata(&mlxplat_dev->dev,
"mlxreg-hotplug", PLATFORM_DEVID_NONE,
@@ -6201,7 +6432,7 @@ mlxplat_i2c_mux_complition_notify(void *handle, struct i2c_adapter *parent,
return mlxplat_post_init(priv);
}
-static int mlxplat_i2c_mux_topolgy_init(struct mlxplat_priv *priv)
+static int mlxplat_i2c_mux_topology_init(struct mlxplat_priv *priv)
{
int i, err;
@@ -6230,7 +6461,7 @@ fail_platform_mux_register:
return err;
}
-static void mlxplat_i2c_mux_topolgy_exit(struct mlxplat_priv *priv)
+static void mlxplat_i2c_mux_topology_exit(struct mlxplat_priv *priv)
{
int i;
@@ -6244,7 +6475,7 @@ static int mlxplat_i2c_main_complition_notify(void *handle, int id)
{
struct mlxplat_priv *priv = handle;
- return mlxplat_i2c_mux_topolgy_init(priv);
+ return mlxplat_i2c_mux_topology_init(priv);
}
static int mlxplat_i2c_main_init(struct mlxplat_priv *priv)
@@ -6262,6 +6493,9 @@ static int mlxplat_i2c_main_init(struct mlxplat_priv *priv)
mlxplat_i2c->regmap = priv->regmap;
mlxplat_i2c->handle = priv;
+ /* Set mapped base address of I2C-LPC bridge over PCIe */
+ if (lpc_bridge)
+ mlxplat_i2c->addr = i2c_bridge_addr;
priv->pdev_i2c = platform_device_register_resndata(&mlxplat_dev->dev, "i2c_mlxcpld",
nr, priv->hotplug_resources,
priv->hotplug_resources_size,
@@ -6272,14 +6506,14 @@ static int mlxplat_i2c_main_init(struct mlxplat_priv *priv)
}
if (priv->i2c_main_init_status == MLXPLAT_I2C_MAIN_BUS_NOTIFIED) {
- err = mlxplat_i2c_mux_topolgy_init(priv);
+ err = mlxplat_i2c_mux_topology_init(priv);
if (err)
- goto fail_mlxplat_i2c_mux_topolgy_init;
+ goto fail_mlxplat_i2c_mux_topology_init;
}
return 0;
-fail_mlxplat_i2c_mux_topolgy_init:
+fail_mlxplat_i2c_mux_topology_init:
fail_platform_i2c_register:
fail_mlxplat_mlxcpld_verify_bus_topology:
return err;
@@ -6287,20 +6521,26 @@ fail_mlxplat_mlxcpld_verify_bus_topology:
static void mlxplat_i2c_main_exit(struct mlxplat_priv *priv)
{
- mlxplat_i2c_mux_topolgy_exit(priv);
+ mlxplat_i2c_mux_topology_exit(priv);
if (priv->pdev_i2c)
platform_device_unregister(priv->pdev_i2c);
}
-static int __init mlxplat_init(void)
+static int mlxplat_probe(struct platform_device *pdev)
{
- unsigned int hotplug_resources_size;
- struct resource *hotplug_resources;
+ unsigned int hotplug_resources_size = 0;
+ struct resource *hotplug_resources = NULL;
+ struct acpi_device *acpi_dev;
struct mlxplat_priv *priv;
- int i, err;
-
- if (!dmi_check_system(mlxplat_dmi_table))
- return -ENODEV;
+ int irq_fpga = 0, i, err;
+
+ acpi_dev = ACPI_COMPANION(&pdev->dev);
+ if (acpi_dev) {
+ irq_fpga = acpi_dev_gpio_irq_get(acpi_dev, 0);
+ if (irq_fpga < 0)
+ return -ENODEV;
+ mlxplat_dev = pdev;
+ }
err = mlxplat_pre_init(&hotplug_resources, &hotplug_resources_size);
if (err)
@@ -6315,6 +6555,7 @@ static int __init mlxplat_init(void)
platform_set_drvdata(mlxplat_dev, priv);
priv->hotplug_resources = hotplug_resources;
priv->hotplug_resources_size = hotplug_resources_size;
+ priv->irq_fpga = irq_fpga;
if (!mlxplat_regmap_config)
mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config;
@@ -6346,8 +6587,15 @@ static int __init mlxplat_init(void)
if (err)
goto fail_regcache_sync;
+ if (mlxplat_reboot_nb) {
+ err = register_reboot_notifier(mlxplat_reboot_nb);
+ if (err)
+ goto fail_register_reboot_notifier;
+ }
+
return 0;
+fail_register_reboot_notifier:
fail_regcache_sync:
mlxplat_pre_exit(priv);
fail_mlxplat_i2c_main_init:
@@ -6357,17 +6605,57 @@ fail_alloc:
return err;
}
-module_init(mlxplat_init);
-static void __exit mlxplat_exit(void)
+static int mlxplat_remove(struct platform_device *pdev)
{
struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev);
if (pm_power_off)
pm_power_off = NULL;
+ if (mlxplat_reboot_nb)
+ unregister_reboot_notifier(mlxplat_reboot_nb);
mlxplat_pre_exit(priv);
mlxplat_i2c_main_exit(priv);
mlxplat_post_exit();
+ return 0;
+}
+
+static const struct acpi_device_id mlxplat_acpi_table[] = {
+ { "MLNXBF49", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, mlxplat_acpi_table);
+
+static struct platform_driver mlxplat_driver = {
+ .driver = {
+ .name = "mlxplat",
+ .acpi_match_table = mlxplat_acpi_table,
+ .probe_type = PROBE_FORCE_SYNCHRONOUS,
+ },
+ .probe = mlxplat_probe,
+ .remove = mlxplat_remove,
+};
+
+static int __init mlxplat_init(void)
+{
+ int err;
+
+ if (!dmi_check_system(mlxplat_dmi_table))
+ return -ENODEV;
+
+ err = platform_driver_register(&mlxplat_driver);
+ if (err)
+ return err;
+ return 0;
+}
+module_init(mlxplat_init);
+
+static void __exit mlxplat_exit(void)
+{
+ if (mlxplat_dev)
+ platform_device_unregister(mlxplat_dev);
+
+ platform_driver_unregister(&mlxplat_driver);
}
module_exit(mlxplat_exit);
diff --git a/drivers/platform/x86/sel3350-platform.c b/drivers/platform/x86/sel3350-platform.c
new file mode 100644
index 0000000000000..fa267d0d37781
--- /dev/null
+++ b/drivers/platform/x86/sel3350-platform.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
+/*
+ * Copyright 2023 Schweitzer Engineering Laboratories, Inc.
+ * 2350 NE Hopkins Court, Pullman, WA 99163 USA
+ *
+ * Platform support for the b2093 mainboard used in SEL-3350 computers.
+ * Consumes GPIO from the SoC to provide standard LED and power supply
+ * devices.
+ */
+
+#include <linux/acpi.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+/* Broxton communities */
+#define BXT_NW "INT3452:01"
+#define BXT_W "INT3452:02"
+#define BXT_SW "INT3452:03"
+
+#define B2093_GPIO_ACPI_ID "SEL0003"
+
+#define SEL_PS_A "sel_ps_a"
+#define SEL_PS_A_DETECT "sel_ps_a_detect"
+#define SEL_PS_A_GOOD "sel_ps_a_good"
+#define SEL_PS_B "sel_ps_b"
+#define SEL_PS_B_DETECT "sel_ps_b_detect"
+#define SEL_PS_B_GOOD "sel_ps_b_good"
+
+/* LEDs */
+static const struct gpio_led sel3350_leds[] = {
+ { .name = "sel:green:aux1" },
+ { .name = "sel:green:aux2" },
+ { .name = "sel:green:aux3" },
+ { .name = "sel:green:aux4" },
+ { .name = "sel:red:alarm" },
+ { .name = "sel:green:enabled",
+ .default_state = LEDS_GPIO_DEFSTATE_ON },
+ { .name = "sel:red:aux1" },
+ { .name = "sel:red:aux2" },
+ { .name = "sel:red:aux3" },
+ { .name = "sel:red:aux4" },
+};
+
+static const struct gpio_led_platform_data sel3350_leds_pdata = {
+ .num_leds = ARRAY_SIZE(sel3350_leds),
+ .leds = sel3350_leds,
+};
+
+/* Map GPIOs to LEDs */
+static struct gpiod_lookup_table sel3350_leds_table = {
+ .dev_id = "leds-gpio",
+ .table = {
+ GPIO_LOOKUP_IDX(BXT_NW, 49, NULL, 0, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_NW, 50, NULL, 1, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_NW, 51, NULL, 2, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_NW, 52, NULL, 3, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_W, 20, NULL, 4, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_W, 21, NULL, 5, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_SW, 37, NULL, 6, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_SW, 38, NULL, 7, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_SW, 39, NULL, 8, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX(BXT_SW, 40, NULL, 9, GPIO_ACTIVE_HIGH),
+ {},
+ }
+};
+
+/* Map GPIOs to power supplies */
+static struct gpiod_lookup_table sel3350_gpios_table = {
+ .dev_id = B2093_GPIO_ACPI_ID ":00",
+ .table = {
+ GPIO_LOOKUP(BXT_NW, 44, SEL_PS_A_DETECT, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP(BXT_NW, 45, SEL_PS_A_GOOD, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP(BXT_NW, 46, SEL_PS_B_DETECT, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP(BXT_NW, 47, SEL_PS_B_GOOD, GPIO_ACTIVE_LOW),
+ {},
+ }
+};
+
+/* Power Supplies */
+
+struct sel3350_power_cfg_data {
+ struct gpio_desc *ps_detect;
+ struct gpio_desc *ps_good;
+};
+
+static int sel3350_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sel3350_power_cfg_data *data = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (gpiod_get_value(data->ps_detect)) {
+ if (gpiod_get_value(data->ps_good))
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ } else {
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = gpiod_get_value(data->ps_detect);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = gpiod_get_value(data->ps_good);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const enum power_supply_property sel3350_power_properties[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc sel3350_ps_a_desc = {
+ .name = SEL_PS_A,
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = sel3350_power_properties,
+ .num_properties = ARRAY_SIZE(sel3350_power_properties),
+ .get_property = sel3350_power_get_property,
+};
+
+static const struct power_supply_desc sel3350_ps_b_desc = {
+ .name = SEL_PS_B,
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = sel3350_power_properties,
+ .num_properties = ARRAY_SIZE(sel3350_power_properties),
+ .get_property = sel3350_power_get_property,
+};
+
+struct sel3350_data {
+ struct platform_device *leds_pdev;
+ struct power_supply *ps_a;
+ struct power_supply *ps_b;
+ struct sel3350_power_cfg_data ps_a_cfg_data;
+ struct sel3350_power_cfg_data ps_b_cfg_data;
+};
+
+static int sel3350_probe(struct platform_device *pdev)
+{
+ int rs;
+ struct sel3350_data *sel3350;
+ struct power_supply_config ps_cfg = {};
+
+ sel3350 = devm_kzalloc(&pdev->dev, sizeof(struct sel3350_data), GFP_KERNEL);
+ if (!sel3350)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, sel3350);
+
+ gpiod_add_lookup_table(&sel3350_leds_table);
+ gpiod_add_lookup_table(&sel3350_gpios_table);
+
+ sel3350->leds_pdev = platform_device_register_data(
+ NULL,
+ "leds-gpio",
+ PLATFORM_DEVID_NONE,
+ &sel3350_leds_pdata,
+ sizeof(sel3350_leds_pdata));
+ if (IS_ERR(sel3350->leds_pdev)) {
+ rs = PTR_ERR(sel3350->leds_pdev);
+ dev_err(&pdev->dev, "Failed registering platform device: %d\n", rs);
+ goto err_platform;
+ }
+
+ /* Power Supply A */
+ sel3350->ps_a_cfg_data.ps_detect = devm_gpiod_get(&pdev->dev,
+ SEL_PS_A_DETECT,
+ GPIOD_IN);
+ sel3350->ps_a_cfg_data.ps_good = devm_gpiod_get(&pdev->dev,
+ SEL_PS_A_GOOD,
+ GPIOD_IN);
+ ps_cfg.drv_data = &sel3350->ps_a_cfg_data;
+ sel3350->ps_a = devm_power_supply_register(&pdev->dev,
+ &sel3350_ps_a_desc,
+ &ps_cfg);
+ if (IS_ERR(sel3350->ps_a)) {
+ rs = PTR_ERR(sel3350->ps_a);
+ dev_err(&pdev->dev, "Failed registering power supply A: %d\n", rs);
+ goto err_ps;
+ }
+
+ /* Power Supply B */
+ sel3350->ps_b_cfg_data.ps_detect = devm_gpiod_get(&pdev->dev,
+ SEL_PS_B_DETECT,
+ GPIOD_IN);
+ sel3350->ps_b_cfg_data.ps_good = devm_gpiod_get(&pdev->dev,
+ SEL_PS_B_GOOD,
+ GPIOD_IN);
+ ps_cfg.drv_data = &sel3350->ps_b_cfg_data;
+ sel3350->ps_b = devm_power_supply_register(&pdev->dev,
+ &sel3350_ps_b_desc,
+ &ps_cfg);
+ if (IS_ERR(sel3350->ps_b)) {
+ rs = PTR_ERR(sel3350->ps_b);
+ dev_err(&pdev->dev, "Failed registering power supply B: %d\n", rs);
+ goto err_ps;
+ }
+
+ return 0;
+
+err_ps:
+ platform_device_unregister(sel3350->leds_pdev);
+err_platform:
+ gpiod_remove_lookup_table(&sel3350_gpios_table);
+ gpiod_remove_lookup_table(&sel3350_leds_table);
+
+ return rs;
+}
+
+static int sel3350_remove(struct platform_device *pdev)
+{
+ struct sel3350_data *sel3350 = platform_get_drvdata(pdev);
+
+ platform_device_unregister(sel3350->leds_pdev);
+ gpiod_remove_lookup_table(&sel3350_gpios_table);
+ gpiod_remove_lookup_table(&sel3350_leds_table);
+
+ return 0;
+}
+
+static const struct acpi_device_id sel3350_device_ids[] = {
+ { B2093_GPIO_ACPI_ID, 0 },
+ { "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, sel3350_device_ids);
+
+static struct platform_driver sel3350_platform_driver = {
+ .probe = sel3350_probe,
+ .remove = sel3350_remove,
+ .driver = {
+ .name = "sel3350-platform",
+ .acpi_match_table = sel3350_device_ids,
+ },
+};
+module_platform_driver(sel3350_platform_driver);
+
+MODULE_AUTHOR("Schweitzer Engineering Laboratories");
+MODULE_DESCRIPTION("SEL-3350 platform driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_SOFTDEP("pre: pinctrl_broxton leds-gpio");
diff --git a/drivers/platform/x86/siemens/Kconfig b/drivers/platform/x86/siemens/Kconfig
new file mode 100644
index 0000000000000..33d028c26bf8b
--- /dev/null
+++ b/drivers/platform/x86/siemens/Kconfig
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Siemens X86 Platform Specific Drivers
+#
+
+config SIEMENS_SIMATIC_IPC
+ tristate "Siemens Simatic IPC Class driver"
+ help
+ This Simatic IPC class driver is the central of several drivers. It
+ is mainly used for system identification, after which drivers in other
+ classes will take care of driving specifics of those machines.
+ i.e. LEDs and watchdog.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc.
+
+config SIEMENS_SIMATIC_IPC_BATT
+ tristate "CMOS battery driver for Siemens Simatic IPCs"
+ default SIEMENS_SIMATIC_IPC
+ depends on HWMON
+ depends on SIEMENS_SIMATIC_IPC
+ help
+ This option enables support for monitoring the voltage of the CMOS
+ batteries of several Industrial PCs from Siemens.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc-batt.
+
+config SIEMENS_SIMATIC_IPC_BATT_APOLLOLAKE
+ tristate "CMOS Battery monitoring for Simatic IPCs based on Apollo Lake GPIO"
+ default SIEMENS_SIMATIC_IPC_BATT
+ depends on PINCTRL_BROXTON
+ depends on SIEMENS_SIMATIC_IPC_BATT
+ help
+ This option enables CMOS battery monitoring for Simatic Industrial PCs
+ from Siemens based on Apollo Lake GPIO.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc-batt-apollolake.
+
+config SIEMENS_SIMATIC_IPC_BATT_ELKHARTLAKE
+ tristate "CMOS Battery monitoring for Simatic IPCs based on Elkhart Lake GPIO"
+ default SIEMENS_SIMATIC_IPC_BATT
+ depends on PINCTRL_ELKHARTLAKE
+ depends on SIEMENS_SIMATIC_IPC_BATT
+ help
+ This option enables CMOS battery monitoring for Simatic Industrial PCs
+ from Siemens based on Elkhart Lake GPIO.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc-batt-elkhartlake.
+
+config SIEMENS_SIMATIC_IPC_BATT_F7188X
+ tristate "CMOS Battery monitoring for Simatic IPCs based on Nuvoton GPIO"
+ default SIEMENS_SIMATIC_IPC_BATT
+ depends on GPIO_F7188X
+ depends on PINCTRL_ALDERLAKE
+ depends on SIEMENS_SIMATIC_IPC_BATT
+ help
+ This option enables CMOS battery monitoring for Simatic Industrial PCs
+ from Siemens based on Nuvoton GPIO.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc-batt-f7188x.
diff --git a/drivers/platform/x86/siemens/Makefile b/drivers/platform/x86/siemens/Makefile
new file mode 100644
index 0000000000000..2b384b4cb8baa
--- /dev/null
+++ b/drivers/platform/x86/siemens/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/x86/siemens
+# Siemens x86 Platform-Specific Drivers
+#
+
+obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += simatic-ipc.o
+obj-$(CONFIG_SIEMENS_SIMATIC_IPC_BATT) += simatic-ipc-batt.o
+obj-$(CONFIG_SIEMENS_SIMATIC_IPC_BATT_APOLLOLAKE) += simatic-ipc-batt-apollolake.o
+obj-$(CONFIG_SIEMENS_SIMATIC_IPC_BATT_ELKHARTLAKE) += simatic-ipc-batt-elkhartlake.o
+obj-$(CONFIG_SIEMENS_SIMATIC_IPC_BATT_F7188X) += simatic-ipc-batt-f7188x.o
diff --git a/drivers/platform/x86/siemens/simatic-ipc-batt-apollolake.c b/drivers/platform/x86/siemens/simatic-ipc-batt-apollolake.c
new file mode 100644
index 0000000000000..8a67979d8f963
--- /dev/null
+++ b/drivers/platform/x86/siemens/simatic-ipc-batt-apollolake.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for CMOS battery monitoring
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Authors:
+ * Henning Schild <henning.schild@siemens.com>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "simatic-ipc-batt.h"
+
+static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_127e = {
+ .table = {
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 55, NULL, 0, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 61, NULL, 1, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.1", 41, NULL, 2, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ },
+};
+
+static int simatic_ipc_batt_apollolake_remove(struct platform_device *pdev)
+{
+ return simatic_ipc_batt_remove(pdev, &simatic_ipc_batt_gpio_table_127e);
+}
+
+static int simatic_ipc_batt_apollolake_probe(struct platform_device *pdev)
+{
+ return simatic_ipc_batt_probe(pdev, &simatic_ipc_batt_gpio_table_127e);
+}
+
+static struct platform_driver simatic_ipc_batt_driver = {
+ .probe = simatic_ipc_batt_apollolake_probe,
+ .remove = simatic_ipc_batt_apollolake_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+module_platform_driver(simatic_ipc_batt_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_SOFTDEP("pre: simatic-ipc-batt platform:apollolake-pinctrl");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/platform/x86/siemens/simatic-ipc-batt-elkhartlake.c b/drivers/platform/x86/siemens/simatic-ipc-batt-elkhartlake.c
new file mode 100644
index 0000000000000..607d033911a2a
--- /dev/null
+++ b/drivers/platform/x86/siemens/simatic-ipc-batt-elkhartlake.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for CMOS battery monitoring
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Authors:
+ * Henning Schild <henning.schild@siemens.com>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "simatic-ipc-batt.h"
+
+static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_bx_21a = {
+ .table = {
+ GPIO_LOOKUP_IDX("INTC1020:04", 18, NULL, 0, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1020:04", 19, NULL, 1, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1020:01", 66, NULL, 2, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ },
+};
+
+static int simatic_ipc_batt_elkhartlake_remove(struct platform_device *pdev)
+{
+ return simatic_ipc_batt_remove(pdev, &simatic_ipc_batt_gpio_table_bx_21a);
+}
+
+static int simatic_ipc_batt_elkhartlake_probe(struct platform_device *pdev)
+{
+ return simatic_ipc_batt_probe(pdev, &simatic_ipc_batt_gpio_table_bx_21a);
+}
+
+static struct platform_driver simatic_ipc_batt_driver = {
+ .probe = simatic_ipc_batt_elkhartlake_probe,
+ .remove = simatic_ipc_batt_elkhartlake_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+module_platform_driver(simatic_ipc_batt_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_SOFTDEP("pre: simatic-ipc-batt platform:elkhartlake-pinctrl");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/platform/x86/siemens/simatic-ipc-batt-f7188x.c b/drivers/platform/x86/siemens/simatic-ipc-batt-f7188x.c
new file mode 100644
index 0000000000000..a66107e0fe1eb
--- /dev/null
+++ b/drivers/platform/x86/siemens/simatic-ipc-batt-f7188x.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for CMOS battery monitoring
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Authors:
+ * Henning Schild <henning.schild@siemens.com>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/x86/simatic-ipc-base.h>
+
+#include "simatic-ipc-batt.h"
+
+static struct gpiod_lookup_table *batt_lookup_table;
+
+static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_227g = {
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-7", 6, NULL, 0, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("gpio-f7188x-7", 5, NULL, 1, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1020:01", 66, NULL, 2, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ },
+};
+
+static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_bx_39a = {
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-6", 4, NULL, 0, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("gpio-f7188x-6", 3, NULL, 1, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ },
+};
+
+static struct gpiod_lookup_table simatic_ipc_batt_gpio_table_bx_59a = {
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-7", 6, NULL, 0, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("gpio-f7188x-7", 5, NULL, 1, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1056:00", 438, NULL, 2, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ }
+};
+
+static int simatic_ipc_batt_f7188x_remove(struct platform_device *pdev)
+{
+ return simatic_ipc_batt_remove(pdev, batt_lookup_table);
+}
+
+static int simatic_ipc_batt_f7188x_probe(struct platform_device *pdev)
+{
+ const struct simatic_ipc_platform *plat = pdev->dev.platform_data;
+
+ switch (plat->devmode) {
+ case SIMATIC_IPC_DEVICE_227G:
+ batt_lookup_table = &simatic_ipc_batt_gpio_table_227g;
+ break;
+ case SIMATIC_IPC_DEVICE_BX_39A:
+ batt_lookup_table = &simatic_ipc_batt_gpio_table_bx_39a;
+ break;
+ case SIMATIC_IPC_DEVICE_BX_59A:
+ batt_lookup_table = &simatic_ipc_batt_gpio_table_bx_59a;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return simatic_ipc_batt_probe(pdev, batt_lookup_table);
+}
+
+static struct platform_driver simatic_ipc_batt_driver = {
+ .probe = simatic_ipc_batt_f7188x_probe,
+ .remove = simatic_ipc_batt_f7188x_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+module_platform_driver(simatic_ipc_batt_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_SOFTDEP("pre: simatic-ipc-batt gpio_f7188x platform:elkhartlake-pinctrl platform:alderlake-pinctrl");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/platform/x86/siemens/simatic-ipc-batt.c b/drivers/platform/x86/siemens/simatic-ipc-batt.c
new file mode 100644
index 0000000000000..ef28c806b3833
--- /dev/null
+++ b/drivers/platform/x86/siemens/simatic-ipc-batt.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for CMOS battery monitoring
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Authors:
+ * Gerd Haeussler <gerd.haeussler.ext@siemens.com>
+ * Henning Schild <henning.schild@siemens.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/x86/simatic-ipc-base.h>
+#include <linux/sizes.h>
+
+#include "simatic-ipc-batt.h"
+
+#define BATT_DELAY_MS (1000 * 60 * 60 * 24) /* 24 h delay */
+
+#define SIMATIC_IPC_BATT_LEVEL_FULL 3000
+#define SIMATIC_IPC_BATT_LEVEL_CRIT 2750
+#define SIMATIC_IPC_BATT_LEVEL_EMPTY 0
+
+static struct simatic_ipc_batt {
+ u8 devmode;
+ long current_state;
+ struct gpio_desc *gpios[3];
+ unsigned long last_updated_jiffies;
+} priv;
+
+static long simatic_ipc_batt_read_gpio(void)
+{
+ long r = SIMATIC_IPC_BATT_LEVEL_FULL;
+
+ if (priv.gpios[2]) {
+ gpiod_set_value(priv.gpios[2], 1);
+ msleep(150);
+ }
+
+ if (gpiod_get_value_cansleep(priv.gpios[0]))
+ r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
+ else if (gpiod_get_value_cansleep(priv.gpios[1]))
+ r = SIMATIC_IPC_BATT_LEVEL_CRIT;
+
+ if (priv.gpios[2])
+ gpiod_set_value(priv.gpios[2], 0);
+
+ return r;
+}
+
+#define SIMATIC_IPC_BATT_PORT_BASE 0x404D
+static struct resource simatic_ipc_batt_io_res =
+ DEFINE_RES_IO_NAMED(SIMATIC_IPC_BATT_PORT_BASE, SZ_1, KBUILD_MODNAME);
+
+static long simatic_ipc_batt_read_io(struct device *dev)
+{
+ long r = SIMATIC_IPC_BATT_LEVEL_FULL;
+ struct resource *res = &simatic_ipc_batt_io_res;
+ u8 val;
+
+ if (!request_muxed_region(res->start, resource_size(res), res->name)) {
+ dev_err(dev, "Unable to register IO resource at %pR\n", res);
+ return -EBUSY;
+ }
+
+ val = inb(SIMATIC_IPC_BATT_PORT_BASE);
+ release_region(simatic_ipc_batt_io_res.start, resource_size(&simatic_ipc_batt_io_res));
+
+ if (val & (1 << 7))
+ r = SIMATIC_IPC_BATT_LEVEL_EMPTY;
+ else if (val & (1 << 6))
+ r = SIMATIC_IPC_BATT_LEVEL_CRIT;
+
+ return r;
+}
+
+static long simatic_ipc_batt_read_value(struct device *dev)
+{
+ unsigned long next_update;
+
+ next_update = priv.last_updated_jiffies + msecs_to_jiffies(BATT_DELAY_MS);
+ if (time_after(jiffies, next_update) || !priv.last_updated_jiffies) {
+ if (priv.devmode == SIMATIC_IPC_DEVICE_227E)
+ priv.current_state = simatic_ipc_batt_read_io(dev);
+ else
+ priv.current_state = simatic_ipc_batt_read_gpio();
+
+ priv.last_updated_jiffies = jiffies;
+ if (priv.current_state < SIMATIC_IPC_BATT_LEVEL_FULL)
+ dev_warn(dev, "CMOS battery needs to be replaced.\n");
+ }
+
+ return priv.current_state;
+}
+
+static int simatic_ipc_batt_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ switch (attr) {
+ case hwmon_in_input:
+ *val = simatic_ipc_batt_read_value(dev);
+ break;
+ case hwmon_in_lcrit:
+ *val = SIMATIC_IPC_BATT_LEVEL_CRIT;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static umode_t simatic_ipc_batt_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ if (attr == hwmon_in_input || attr == hwmon_in_lcrit)
+ return 0444;
+
+ return 0;
+}
+
+static const struct hwmon_ops simatic_ipc_batt_ops = {
+ .is_visible = simatic_ipc_batt_is_visible,
+ .read = simatic_ipc_batt_read,
+};
+
+static const struct hwmon_channel_info *simatic_ipc_batt_info[] = {
+ HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LCRIT),
+ NULL
+};
+
+static const struct hwmon_chip_info simatic_ipc_batt_chip_info = {
+ .ops = &simatic_ipc_batt_ops,
+ .info = simatic_ipc_batt_info,
+};
+
+int simatic_ipc_batt_remove(struct platform_device *pdev, struct gpiod_lookup_table *table)
+{
+ gpiod_remove_lookup_table(table);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simatic_ipc_batt_remove);
+
+int simatic_ipc_batt_probe(struct platform_device *pdev, struct gpiod_lookup_table *table)
+{
+ struct simatic_ipc_platform *plat;
+ struct device *dev = &pdev->dev;
+ struct device *hwmon_dev;
+ unsigned long flags;
+ int err;
+
+ plat = pdev->dev.platform_data;
+ priv.devmode = plat->devmode;
+
+ switch (priv.devmode) {
+ case SIMATIC_IPC_DEVICE_127E:
+ case SIMATIC_IPC_DEVICE_227G:
+ case SIMATIC_IPC_DEVICE_BX_39A:
+ case SIMATIC_IPC_DEVICE_BX_21A:
+ case SIMATIC_IPC_DEVICE_BX_59A:
+ table->dev_id = dev_name(dev);
+ gpiod_add_lookup_table(table);
+ break;
+ case SIMATIC_IPC_DEVICE_227E:
+ goto nogpio;
+ default:
+ return -ENODEV;
+ }
+
+ priv.gpios[0] = devm_gpiod_get_index(dev, "CMOSBattery empty", 0, GPIOD_IN);
+ if (IS_ERR(priv.gpios[0])) {
+ err = PTR_ERR(priv.gpios[0]);
+ priv.gpios[0] = NULL;
+ goto out;
+ }
+ priv.gpios[1] = devm_gpiod_get_index(dev, "CMOSBattery low", 1, GPIOD_IN);
+ if (IS_ERR(priv.gpios[1])) {
+ err = PTR_ERR(priv.gpios[1]);
+ priv.gpios[1] = NULL;
+ goto out;
+ }
+
+ if (table->table[2].key) {
+ flags = GPIOD_OUT_HIGH;
+ if (priv.devmode == SIMATIC_IPC_DEVICE_BX_21A ||
+ priv.devmode == SIMATIC_IPC_DEVICE_BX_59A)
+ flags = GPIOD_OUT_LOW;
+ priv.gpios[2] = devm_gpiod_get_index(dev, "CMOSBattery meter", 2, flags);
+ if (IS_ERR(priv.gpios[2])) {
+ err = PTR_ERR(priv.gpios[2]);
+ priv.gpios[2] = NULL;
+ goto out;
+ }
+ } else {
+ priv.gpios[2] = NULL;
+ }
+
+nogpio:
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME,
+ &priv,
+ &simatic_ipc_batt_chip_info,
+ NULL);
+ if (IS_ERR(hwmon_dev)) {
+ err = PTR_ERR(hwmon_dev);
+ goto out;
+ }
+
+ /* warn about aging battery even if userspace never reads hwmon */
+ simatic_ipc_batt_read_value(dev);
+
+ return 0;
+out:
+ simatic_ipc_batt_remove(pdev, table);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(simatic_ipc_batt_probe);
+
+static int simatic_ipc_batt_io_remove(struct platform_device *pdev)
+{
+ return simatic_ipc_batt_remove(pdev, NULL);
+}
+
+static int simatic_ipc_batt_io_probe(struct platform_device *pdev)
+{
+ return simatic_ipc_batt_probe(pdev, NULL);
+}
+
+static struct platform_driver simatic_ipc_batt_driver = {
+ .probe = simatic_ipc_batt_io_probe,
+ .remove = simatic_ipc_batt_io_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+module_platform_driver(simatic_ipc_batt_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/platform/x86/siemens/simatic-ipc-batt.h b/drivers/platform/x86/siemens/simatic-ipc-batt.h
new file mode 100644
index 0000000000000..4545cd3e30269
--- /dev/null
+++ b/drivers/platform/x86/siemens/simatic-ipc-batt.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Siemens SIMATIC IPC driver for CMOS battery monitoring
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Author:
+ * Henning Schild <henning.schild@siemens.com>
+ */
+
+#ifndef _SIMATIC_IPC_BATT_H
+#define _SIMATIC_IPC_BATT_H
+
+int simatic_ipc_batt_probe(struct platform_device *pdev,
+ struct gpiod_lookup_table *table);
+
+int simatic_ipc_batt_remove(struct platform_device *pdev,
+ struct gpiod_lookup_table *table);
+
+#endif /* _SIMATIC_IPC_BATT_H */
diff --git a/drivers/platform/x86/siemens/simatic-ipc.c b/drivers/platform/x86/siemens/simatic-ipc.c
new file mode 100644
index 0000000000000..8ca6e277fa038
--- /dev/null
+++ b/drivers/platform/x86/siemens/simatic-ipc.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC platform driver
+ *
+ * Copyright (c) Siemens AG, 2018-2023
+ *
+ * Authors:
+ * Henning Schild <henning.schild@siemens.com>
+ * Jan Kiszka <jan.kiszka@siemens.com>
+ * Gerd Haeussler <gerd.haeussler.ext@siemens.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/dmi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_data/x86/simatic-ipc.h>
+#include <linux/platform_device.h>
+
+static struct platform_device *ipc_led_platform_device;
+static struct platform_device *ipc_wdt_platform_device;
+static struct platform_device *ipc_batt_platform_device;
+
+static const struct dmi_system_id simatic_ipc_whitelist[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
+ },
+ },
+ {}
+};
+
+static struct simatic_ipc_platform platform_data;
+
+#define SIMATIC_IPC_MAX_EXTRA_MODULES 2
+
+static struct {
+ u32 station_id;
+ u8 led_mode;
+ u8 wdt_mode;
+ u8 batt_mode;
+ char *extra_modules[SIMATIC_IPC_MAX_EXTRA_MODULES];
+} device_modes[] = {
+ {SIMATIC_IPC_IPC127E,
+ SIMATIC_IPC_DEVICE_127E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_127E,
+ { "emc1403", NULL }},
+ {SIMATIC_IPC_IPC227D,
+ SIMATIC_IPC_DEVICE_227D, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE,
+ { "emc1403", NULL }},
+ {SIMATIC_IPC_IPC227E,
+ SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_227E, SIMATIC_IPC_DEVICE_227E,
+ { "emc1403", NULL }},
+ {SIMATIC_IPC_IPC227G,
+ SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227G,
+ { "nct6775", "w83627hf_wdt" }},
+ {SIMATIC_IPC_IPC277G,
+ SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227G,
+ { "nct6775", "w83627hf_wdt" }},
+ {SIMATIC_IPC_IPC277E,
+ SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227E, SIMATIC_IPC_DEVICE_227E,
+ { "emc1403", NULL }},
+ {SIMATIC_IPC_IPC427D,
+ SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE,
+ { "emc1403", NULL }},
+ {SIMATIC_IPC_IPC427E,
+ SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE,
+ { "emc1403", NULL }},
+ {SIMATIC_IPC_IPC477E,
+ SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE,
+ { "emc1403", NULL }},
+ {SIMATIC_IPC_IPCBX_39A,
+ SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_39A,
+ { "nct6775", "w83627hf_wdt" }},
+ {SIMATIC_IPC_IPCPX_39A,
+ SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_39A,
+ { "nct6775", "w83627hf_wdt" }},
+ {SIMATIC_IPC_IPCBX_21A,
+ SIMATIC_IPC_DEVICE_BX_21A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_21A,
+ { "emc1403", NULL }},
+ {SIMATIC_IPC_IPCBX_56A,
+ SIMATIC_IPC_DEVICE_BX_59A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_59A,
+ { "emc1403", "w83627hf_wdt" }},
+ {SIMATIC_IPC_IPCBX_59A,
+ SIMATIC_IPC_DEVICE_BX_59A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_59A,
+ { "emc1403", "w83627hf_wdt" }},
+};
+
+static int register_platform_devices(u32 station_id)
+{
+ u8 ledmode = SIMATIC_IPC_DEVICE_NONE;
+ u8 wdtmode = SIMATIC_IPC_DEVICE_NONE;
+ u8 battmode = SIMATIC_IPC_DEVICE_NONE;
+ char *pdevname;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
+ if (device_modes[i].station_id == station_id) {
+ ledmode = device_modes[i].led_mode;
+ wdtmode = device_modes[i].wdt_mode;
+ battmode = device_modes[i].batt_mode;
+ break;
+ }
+ }
+
+ if (battmode != SIMATIC_IPC_DEVICE_NONE) {
+ pdevname = KBUILD_MODNAME "_batt";
+ if (battmode == SIMATIC_IPC_DEVICE_127E)
+ pdevname = KBUILD_MODNAME "_batt_apollolake";
+ if (battmode == SIMATIC_IPC_DEVICE_BX_21A)
+ pdevname = KBUILD_MODNAME "_batt_elkhartlake";
+ if (battmode == SIMATIC_IPC_DEVICE_227G ||
+ battmode == SIMATIC_IPC_DEVICE_BX_39A ||
+ battmode == SIMATIC_IPC_DEVICE_BX_59A)
+ pdevname = KBUILD_MODNAME "_batt_f7188x";
+ platform_data.devmode = battmode;
+ ipc_batt_platform_device =
+ platform_device_register_data(NULL, pdevname,
+ PLATFORM_DEVID_NONE, &platform_data,
+ sizeof(struct simatic_ipc_platform));
+ if (IS_ERR(ipc_batt_platform_device))
+ return PTR_ERR(ipc_batt_platform_device);
+
+ pr_debug("device=%s created\n",
+ ipc_batt_platform_device->name);
+ }
+
+ if (ledmode != SIMATIC_IPC_DEVICE_NONE) {
+ pdevname = KBUILD_MODNAME "_leds";
+ if (ledmode == SIMATIC_IPC_DEVICE_127E)
+ pdevname = KBUILD_MODNAME "_leds_gpio_apollolake";
+ if (ledmode == SIMATIC_IPC_DEVICE_227G || ledmode == SIMATIC_IPC_DEVICE_BX_59A)
+ pdevname = KBUILD_MODNAME "_leds_gpio_f7188x";
+ if (ledmode == SIMATIC_IPC_DEVICE_BX_21A)
+ pdevname = KBUILD_MODNAME "_leds_gpio_elkhartlake";
+ platform_data.devmode = ledmode;
+ ipc_led_platform_device =
+ platform_device_register_data(NULL,
+ 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);
+
+ pr_debug("device=%s created\n",
+ ipc_led_platform_device->name);
+ }
+
+ if (wdtmode != SIMATIC_IPC_DEVICE_NONE) {
+ platform_data.devmode = wdtmode;
+ ipc_wdt_platform_device =
+ platform_device_register_data(NULL,
+ 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);
+
+ pr_debug("device=%s created\n",
+ ipc_wdt_platform_device->name);
+ }
+
+ if (ledmode == SIMATIC_IPC_DEVICE_NONE &&
+ wdtmode == SIMATIC_IPC_DEVICE_NONE &&
+ battmode == SIMATIC_IPC_DEVICE_NONE) {
+ pr_warn("unsupported IPC detected, station id=%08x\n",
+ station_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void request_additional_modules(u32 station_id)
+{
+ char **extra_modules = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
+ if (device_modes[i].station_id == station_id) {
+ extra_modules = device_modes[i].extra_modules;
+ break;
+ }
+ }
+
+ if (!extra_modules)
+ return;
+
+ for (i = 0; i < SIMATIC_IPC_MAX_EXTRA_MODULES; i++) {
+ if (extra_modules[i])
+ request_module(extra_modules[i]);
+ else
+ break;
+ }
+}
+
+static int __init simatic_ipc_init_module(void)
+{
+ const struct dmi_system_id *match;
+ u32 station_id;
+ int err;
+
+ match = dmi_first_match(simatic_ipc_whitelist);
+ if (!match)
+ return 0;
+
+ err = dmi_walk(simatic_ipc_find_dmi_entry_helper, &station_id);
+
+ if (err || station_id == SIMATIC_IPC_INVALID_STATION_ID) {
+ pr_warn("DMI entry %d not found\n", SIMATIC_IPC_DMI_ENTRY_OEM);
+ return 0;
+ }
+
+ request_additional_modules(station_id);
+
+ return register_platform_devices(station_id);
+}
+
+static void __exit simatic_ipc_exit_module(void)
+{
+ platform_device_unregister(ipc_led_platform_device);
+ ipc_led_platform_device = NULL;
+
+ platform_device_unregister(ipc_wdt_platform_device);
+ ipc_wdt_platform_device = NULL;
+
+ platform_device_unregister(ipc_batt_platform_device);
+ ipc_batt_platform_device = NULL;
+}
+
+module_init(simatic_ipc_init_module);
+module_exit(simatic_ipc_exit_module);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>");
+MODULE_ALIAS("dmi:*:svnSIEMENSAG:*");
diff --git a/drivers/platform/x86/simatic-ipc.c b/drivers/platform/x86/simatic-ipc.c
deleted file mode 100644
index c773995b230d4..0000000000000
--- a/drivers/platform/x86/simatic-ipc.c
+++ /dev/null
@@ -1,151 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Siemens SIMATIC IPC platform driver
- *
- * Copyright (c) Siemens AG, 2018-2021
- *
- * Authors:
- * Henning Schild <henning.schild@siemens.com>
- * Jan Kiszka <jan.kiszka@siemens.com>
- * Gerd Haeussler <gerd.haeussler.ext@siemens.com>
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/dmi.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/pci.h>
-#include <linux/platform_data/x86/simatic-ipc.h>
-#include <linux/platform_device.h>
-
-static struct platform_device *ipc_led_platform_device;
-static struct platform_device *ipc_wdt_platform_device;
-
-static const struct dmi_system_id simatic_ipc_whitelist[] = {
- {
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"),
- },
- },
- {}
-};
-
-static struct simatic_ipc_platform platform_data;
-
-static struct {
- u32 station_id;
- u8 led_mode;
- u8 wdt_mode;
-} device_modes[] = {
- {SIMATIC_IPC_IPC127E, SIMATIC_IPC_DEVICE_127E, SIMATIC_IPC_DEVICE_NONE},
- {SIMATIC_IPC_IPC227D, SIMATIC_IPC_DEVICE_227D, SIMATIC_IPC_DEVICE_NONE},
- {SIMATIC_IPC_IPC227E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_227E},
- {SIMATIC_IPC_IPC227G, SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_227G},
- {SIMATIC_IPC_IPC277E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227E},
- {SIMATIC_IPC_IPC427D, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE},
- {SIMATIC_IPC_IPC427E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_427E},
- {SIMATIC_IPC_IPC477E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_427E},
- {SIMATIC_IPC_IPCBX_39A, SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_227G},
- {SIMATIC_IPC_IPCPX_39A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227G},
-};
-
-static int register_platform_devices(u32 station_id)
-{
- u8 ledmode = SIMATIC_IPC_DEVICE_NONE;
- u8 wdtmode = SIMATIC_IPC_DEVICE_NONE;
- char *pdevname = KBUILD_MODNAME "_leds";
- int i;
-
- platform_data.devmode = SIMATIC_IPC_DEVICE_NONE;
-
- for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
- if (device_modes[i].station_id == station_id) {
- ledmode = device_modes[i].led_mode;
- wdtmode = device_modes[i].wdt_mode;
- break;
- }
- }
-
- if (ledmode != SIMATIC_IPC_DEVICE_NONE) {
- if (ledmode == SIMATIC_IPC_DEVICE_127E)
- pdevname = KBUILD_MODNAME "_leds_gpio_apollolake";
- if (ledmode == SIMATIC_IPC_DEVICE_227G)
- pdevname = KBUILD_MODNAME "_leds_gpio_f7188x";
- platform_data.devmode = ledmode;
- ipc_led_platform_device =
- platform_device_register_data(NULL,
- 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);
-
- pr_debug("device=%s created\n",
- ipc_led_platform_device->name);
- }
-
- if (wdtmode == SIMATIC_IPC_DEVICE_227G) {
- request_module("w83627hf_wdt");
- return 0;
- }
-
- if (wdtmode != SIMATIC_IPC_DEVICE_NONE) {
- platform_data.devmode = wdtmode;
- ipc_wdt_platform_device =
- platform_device_register_data(NULL,
- 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);
-
- pr_debug("device=%s created\n",
- ipc_wdt_platform_device->name);
- }
-
- if (ledmode == SIMATIC_IPC_DEVICE_NONE &&
- wdtmode == SIMATIC_IPC_DEVICE_NONE) {
- pr_warn("unsupported IPC detected, station id=%08x\n",
- station_id);
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int __init simatic_ipc_init_module(void)
-{
- const struct dmi_system_id *match;
- u32 station_id;
- int err;
-
- match = dmi_first_match(simatic_ipc_whitelist);
- if (!match)
- return 0;
-
- err = dmi_walk(simatic_ipc_find_dmi_entry_helper, &station_id);
-
- if (err || station_id == SIMATIC_IPC_INVALID_STATION_ID) {
- pr_warn("DMI entry %d not found\n", SIMATIC_IPC_DMI_ENTRY_OEM);
- return 0;
- }
-
- return register_platform_devices(station_id);
-}
-
-static void __exit simatic_ipc_exit_module(void)
-{
- platform_device_unregister(ipc_led_platform_device);
- ipc_led_platform_device = NULL;
-
- platform_device_unregister(ipc_wdt_platform_device);
- ipc_wdt_platform_device = NULL;
-}
-
-module_init(simatic_ipc_init_module);
-module_exit(simatic_ipc_exit_module);
-
-MODULE_LICENSE("GPL v2");
-MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>");
-MODULE_ALIAS("dmi:*:svnSIEMENSAG:*");
diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c
index fc4708fa6ebea..3da753b3d00db 100644
--- a/drivers/platform/x86/system76_acpi.c
+++ b/drivers/platform/x86/system76_acpi.c
@@ -2,7 +2,7 @@
/*
* System76 ACPI Driver
*
- * Copyright (C) 2019 System76
+ * Copyright (C) 2023 System76
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -24,6 +24,12 @@
#include <acpi/battery.h>
+enum kbled_type {
+ KBLED_NONE,
+ KBLED_WHITE,
+ KBLED_RGB,
+};
+
struct system76_data {
struct acpi_device *acpi_dev;
struct led_classdev ap_led;
@@ -36,6 +42,7 @@ struct system76_data {
union acpi_object *ntmp;
struct input_dev *input;
bool has_open_ec;
+ enum kbled_type kbled_type;
};
static const struct acpi_device_id device_ids[] = {
@@ -327,7 +334,11 @@ static int kb_led_set(struct led_classdev *led, enum led_brightness value)
data = container_of(led, struct system76_data, kb_led);
data->kb_brightness = value;
- return system76_set(data, "SKBL", (int)data->kb_brightness);
+ if (acpi_has_method(acpi_device_handle(data->acpi_dev), "GKBK")) {
+ return system76_set(data, "SKBB", (int)data->kb_brightness);
+ } else {
+ return system76_set(data, "SKBL", (int)data->kb_brightness);
+ }
}
// Get the last set keyboard LED color
@@ -399,7 +410,12 @@ static void kb_led_hotkey_hardware(struct system76_data *data)
{
int value;
- value = system76_get(data, "GKBL");
+ if (acpi_has_method(acpi_device_handle(data->acpi_dev), "GKBK")) {
+ value = system76_get(data, "GKBB");
+ } else {
+ value = system76_get(data, "GKBL");
+ }
+
if (value < 0)
return;
data->kb_brightness = value;
@@ -459,8 +475,9 @@ static void kb_led_hotkey_color(struct system76_data *data)
{
int i;
- if (data->kb_color < 0)
+ if (data->kbled_type != KBLED_RGB)
return;
+
if (data->kb_brightness > 0) {
for (i = 0; i < ARRAY_SIZE(kb_colors); i++) {
if (kb_colors[i] == data->kb_color)
@@ -687,19 +704,46 @@ static int system76_add(struct acpi_device *acpi_dev)
data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME;
data->kb_led.brightness_get = kb_led_get;
data->kb_led.brightness_set_blocking = kb_led_set;
- if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) {
- data->kb_led.max_brightness = 255;
- data->kb_led.groups = system76_kb_led_color_groups;
- data->kb_toggle_brightness = 72;
- data->kb_color = 0xffffff;
- system76_set(data, "SKBC", data->kb_color);
+ if (acpi_has_method(acpi_device_handle(data->acpi_dev), "GKBK")) {
+ // Use the new ACPI methods
+ data->kbled_type = system76_get(data, "GKBK");
+
+ switch (data->kbled_type) {
+ case KBLED_NONE:
+ // Nothing to do: Device will not be registered.
+ break;
+ case KBLED_WHITE:
+ data->kb_led.max_brightness = 255;
+ data->kb_toggle_brightness = 72;
+ break;
+ case KBLED_RGB:
+ data->kb_led.max_brightness = 255;
+ data->kb_led.groups = system76_kb_led_color_groups;
+ data->kb_toggle_brightness = 72;
+ data->kb_color = 0xffffff;
+ system76_set(data, "SKBC", data->kb_color);
+ break;
+ }
} else {
- data->kb_led.max_brightness = 5;
- data->kb_color = -1;
+ // Use the old ACPI methods
+ if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) {
+ data->kbled_type = KBLED_RGB;
+ data->kb_led.max_brightness = 255;
+ data->kb_led.groups = system76_kb_led_color_groups;
+ data->kb_toggle_brightness = 72;
+ data->kb_color = 0xffffff;
+ system76_set(data, "SKBC", data->kb_color);
+ } else {
+ data->kbled_type = KBLED_WHITE;
+ data->kb_led.max_brightness = 5;
+ }
+ }
+
+ if (data->kbled_type != KBLED_NONE) {
+ err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led);
+ if (err)
+ return err;
}
- err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led);
- if (err)
- return err;
data->input = devm_input_allocate_device(&acpi_dev->dev);
if (!data->input)
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index ad460417f901a..d70c89d325344 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -50,6 +50,7 @@
#include <linux/kthread.h>
#include <linux/leds.h>
#include <linux/list.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/nvram.h>
@@ -907,16 +908,9 @@ static ssize_t dispatch_proc_write(struct file *file,
if (count > PAGE_SIZE - 1)
return -EINVAL;
- kernbuf = kmalloc(count + 1, GFP_KERNEL);
- if (!kernbuf)
- return -ENOMEM;
-
- if (copy_from_user(kernbuf, userbuf, count)) {
- kfree(kernbuf);
- return -EFAULT;
- }
-
- kernbuf[count] = 0;
+ kernbuf = memdup_user_nul(userbuf, count);
+ if (IS_ERR(kernbuf))
+ return PTR_ERR(kernbuf);
ret = ibm->write(kernbuf);
if (ret == 0)
ret = count;
@@ -2066,11 +2060,11 @@ static int hotkey_get_tablet_mode(int *status)
* hotkey_acpi_mask accordingly. Also resets any bits
* from hotkey_user_mask that are unavailable to be
* delivered (shadow requirement of the userspace ABI).
- *
- * Call with hotkey_mutex held
*/
static int hotkey_mask_get(void)
{
+ lockdep_assert_held(&hotkey_mutex);
+
if (tp_features.hotkey_mask) {
u32 m = 0;
@@ -2106,8 +2100,6 @@ static void hotkey_mask_warn_incomplete_mask(void)
* Also calls hotkey_mask_get to update hotkey_acpi_mask.
*
* NOTE: does not set bits in hotkey_user_mask, but may reset them.
- *
- * Call with hotkey_mutex held
*/
static int hotkey_mask_set(u32 mask)
{
@@ -2116,6 +2108,8 @@ static int hotkey_mask_set(u32 mask)
const u32 fwmask = mask & ~hotkey_source_mask;
+ lockdep_assert_held(&hotkey_mutex);
+
if (tp_features.hotkey_mask) {
for (i = 0; i < 32; i++) {
if (!acpi_evalf(hkey_handle,
@@ -2147,13 +2141,13 @@ static int hotkey_mask_set(u32 mask)
/*
* Sets hotkey_user_mask and tries to set the firmware mask
- *
- * Call with hotkey_mutex held
*/
static int hotkey_user_mask_set(const u32 mask)
{
int rc;
+ lockdep_assert_held(&hotkey_mutex);
+
/* Give people a chance to notice they are doing something that
* is bound to go boom on their users sooner or later */
if (!tp_warned.hotkey_mask_ff &&
@@ -2514,21 +2508,23 @@ exit:
return 0;
}
-/* call with hotkey_mutex held */
static void hotkey_poll_stop_sync(void)
{
+ lockdep_assert_held(&hotkey_mutex);
+
if (tpacpi_hotkey_task) {
kthread_stop(tpacpi_hotkey_task);
tpacpi_hotkey_task = NULL;
}
}
-/* call with hotkey_mutex held */
static void hotkey_poll_setup(const bool may_warn)
{
const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask;
const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask;
+ lockdep_assert_held(&hotkey_mutex);
+
if (hotkey_poll_freq > 0 &&
(poll_driver_mask ||
(poll_user_mask && tpacpi_inputdev->users > 0))) {
@@ -2557,9 +2553,10 @@ static void hotkey_poll_setup_safe(const bool may_warn)
mutex_unlock(&hotkey_mutex);
}
-/* call with hotkey_mutex held */
static void hotkey_poll_set_freq(unsigned int freq)
{
+ lockdep_assert_held(&hotkey_mutex);
+
if (!freq)
hotkey_poll_stop_sync();
@@ -3473,7 +3470,9 @@ 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;
@@ -3572,9 +3571,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);
if (res < 0 && res != -ENXIO) {
hotkey_exit();
return res;
@@ -6528,12 +6529,13 @@ static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */
static struct mutex brightness_mutex;
-/* NVRAM brightness access,
- * call with brightness_mutex held! */
+/* NVRAM brightness access */
static unsigned int tpacpi_brightness_nvram_get(void)
{
u8 lnvram;
+ lockdep_assert_held(&brightness_mutex);
+
lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)
& TP_NVRAM_MASK_LEVEL_BRIGHTNESS)
>> TP_NVRAM_POS_LEVEL_BRIGHTNESS;
@@ -6581,11 +6583,12 @@ unlock:
}
-/* call with brightness_mutex held! */
static int tpacpi_brightness_get_raw(int *status)
{
u8 lec = 0;
+ lockdep_assert_held(&brightness_mutex);
+
switch (brightness_mode) {
case TPACPI_BRGHT_MODE_UCMS_STEP:
*status = tpacpi_brightness_nvram_get();
@@ -6601,12 +6604,13 @@ static int tpacpi_brightness_get_raw(int *status)
}
}
-/* call with brightness_mutex held! */
/* do NOT call with illegal backlight level value */
static int tpacpi_brightness_set_ec(unsigned int value)
{
u8 lec = 0;
+ lockdep_assert_held(&brightness_mutex);
+
if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))
return -EIO;
@@ -6618,12 +6622,13 @@ static int tpacpi_brightness_set_ec(unsigned int value)
return 0;
}
-/* call with brightness_mutex held! */
static int tpacpi_brightness_set_ucmsstep(unsigned int value)
{
int cmos_cmd, inc;
unsigned int current_value, i;
+ lockdep_assert_held(&brightness_mutex);
+
current_value = tpacpi_brightness_nvram_get();
if (value == current_value)
@@ -8072,11 +8077,10 @@ static bool fan_select_fan2(void)
return true;
}
-/*
- * Call with fan_mutex held
- */
static void fan_update_desired_level(u8 status)
{
+ lockdep_assert_held(&fan_mutex);
+
if ((status &
(TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {
if (status > 7)
diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c
index 80137afb97535..644d2fd889c08 100644
--- a/drivers/platform/x86/wmi-bmof.c
+++ b/drivers/platform/x86/wmi-bmof.c
@@ -25,25 +25,13 @@ struct bmof_priv {
struct bin_attribute bmof_bin_attr;
};
-static ssize_t
-read_bmof(struct file *filp, struct kobject *kobj,
- struct bin_attribute *attr,
- char *buf, loff_t off, size_t count)
+static ssize_t read_bmof(struct file *filp, struct kobject *kobj, struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
{
- struct bmof_priv *priv =
- container_of(attr, struct bmof_priv, bmof_bin_attr);
+ struct bmof_priv *priv = container_of(attr, struct bmof_priv, bmof_bin_attr);
- if (off < 0)
- return -EINVAL;
-
- if (off >= priv->bmofdata->buffer.length)
- return 0;
-
- if (count > priv->bmofdata->buffer.length - off)
- count = priv->bmofdata->buffer.length - off;
-
- memcpy(buf, priv->bmofdata->buffer.pointer + off, count);
- return count;
+ return memory_read_from_buffer(buf, count, &off, priv->bmofdata->buffer.pointer,
+ priv->bmofdata->buffer.length);
}
static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
@@ -75,7 +63,7 @@ static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
priv->bmof_bin_attr.read = read_bmof;
priv->bmof_bin_attr.size = priv->bmofdata->buffer.length;
- ret = sysfs_create_bin_file(&wdev->dev.kobj, &priv->bmof_bin_attr);
+ ret = device_create_bin_file(&wdev->dev, &priv->bmof_bin_attr);
if (ret)
goto err_free;
@@ -90,7 +78,7 @@ static void wmi_bmof_remove(struct wmi_device *wdev)
{
struct bmof_priv *priv = dev_get_drvdata(&wdev->dev);
- sysfs_remove_bin_file(&wdev->dev.kobj, &priv->bmof_bin_attr);
+ device_remove_bin_file(&wdev->dev, &priv->bmof_bin_attr);
kfree(priv->bmofdata);
}
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index ee97d89dfc11a..0cbfb496b9c8e 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1680,9 +1680,10 @@ config NIC7018_WDT
config SIEMENS_SIMATIC_IPC_WDT
tristate "Siemens Simatic IPC Watchdog"
- depends on SIEMENS_SIMATIC_IPC
+ depends on SIEMENS_SIMATIC_IPC && PCI
+ default y
select WATCHDOG_CORE
- select P2SB
+ select P2SB if X86
help
This driver adds support for several watchdogs found in Industrial
PCs from Siemens.
diff --git a/drivers/watchdog/simatic-ipc-wdt.c b/drivers/watchdog/simatic-ipc-wdt.c
index 6599695dc6720..cdc1a2e151805 100644
--- a/drivers/watchdog/simatic-ipc-wdt.c
+++ b/drivers/watchdog/simatic-ipc-wdt.c
@@ -155,9 +155,8 @@ static int simatic_ipc_wdt_probe(struct platform_device *pdev)
switch (plat->devmode) {
case SIMATIC_IPC_DEVICE_227E:
- if (!devm_request_region(dev, gp_status_reg_227e_res.start,
- resource_size(&gp_status_reg_227e_res),
- KBUILD_MODNAME)) {
+ res = &gp_status_reg_227e_res;
+ if (!request_muxed_region(res->start, resource_size(res), res->name)) {
dev_err(dev,
"Unable to register IO resource at %pR\n",
&gp_status_reg_227e_res);
@@ -210,6 +209,10 @@ static int simatic_ipc_wdt_probe(struct platform_device *pdev)
if (wdd_data.bootstatus)
dev_warn(dev, "last reboot caused by watchdog reset\n");
+ if (plat->devmode == SIMATIC_IPC_DEVICE_227E)
+ release_region(gp_status_reg_227e_res.start,
+ resource_size(&gp_status_reg_227e_res));
+
watchdog_set_nowayout(&wdd_data, nowayout);
watchdog_stop_on_reboot(&wdd_data);
return devm_watchdog_register_device(dev, &wdd_data);
diff --git a/include/linux/intel_tpmi.h b/include/linux/intel_tpmi.h
index f505788c05dac..04d937ad4dc4f 100644
--- a/include/linux/intel_tpmi.h
+++ b/include/linux/intel_tpmi.h
@@ -27,4 +27,6 @@ struct intel_tpmi_plat_info *tpmi_get_platform_data(struct auxiliary_device *aux
struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int index);
int tpmi_get_resource_count(struct auxiliary_device *auxdev);
+int tpmi_get_feature_status(struct auxiliary_device *auxdev, int feature_id, int *locked,
+ int *disabled);
#endif
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index 28234dc9fa6ad..16e99a1c37fc4 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -66,6 +66,7 @@
#define ASUS_WMI_DEVID_CAMERA 0x00060013
#define ASUS_WMI_DEVID_LID_FLIP 0x00060062
#define ASUS_WMI_DEVID_LID_FLIP_ROG 0x00060077
+#define ASUS_WMI_DEVID_MINI_LED_MODE 0x0005001E
/* Storage */
#define ASUS_WMI_DEVID_CARDREADER 0x00080013
@@ -80,8 +81,19 @@
#define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 /* deprecated */
#define ASUS_WMI_DEVID_CPU_FAN_CTRL 0x00110013
#define ASUS_WMI_DEVID_GPU_FAN_CTRL 0x00110014
+#define ASUS_WMI_DEVID_MID_FAN_CTRL 0x00110031
#define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024
#define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025
+#define ASUS_WMI_DEVID_MID_FAN_CURVE 0x00110032
+
+/* Tunables for AUS ROG laptops */
+#define ASUS_WMI_DEVID_PPT_PL2_SPPT 0x001200A0
+#define ASUS_WMI_DEVID_PPT_PL1_SPL 0x001200A3
+#define ASUS_WMI_DEVID_PPT_APU_SPPT 0x001200B0
+#define ASUS_WMI_DEVID_PPT_PLAT_SPPT 0x001200B1
+#define ASUS_WMI_DEVID_PPT_FPPT 0x001200C1
+#define ASUS_WMI_DEVID_NV_DYN_BOOST 0x001200C0
+#define ASUS_WMI_DEVID_NV_THERM_TARGET 0x001200C2
/* Power */
#define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012
@@ -95,7 +107,12 @@
/* Keyboard dock */
#define ASUS_WMI_DEVID_KBD_DOCK 0x00120063
-/* dgpu on/off */
+/* Charging mode - 1=Barrel, 2=USB */
+#define ASUS_WMI_DEVID_CHARGE_MODE 0x0012006C
+
+/* epu is connected? 1 == true */
+#define ASUS_WMI_DEVID_EGPU_CONNECTED 0x00090018
+/* egpu on/off */
#define ASUS_WMI_DEVID_EGPU 0x00090019
/* dgpu on/off */
diff --git a/include/linux/platform_data/x86/simatic-ipc-base.h b/include/linux/platform_data/x86/simatic-ipc-base.h
index 57d6a10dfc9e0..2d7f7120ba6b5 100644
--- a/include/linux/platform_data/x86/simatic-ipc-base.h
+++ b/include/linux/platform_data/x86/simatic-ipc-base.h
@@ -2,7 +2,7 @@
/*
* Siemens SIMATIC IPC drivers
*
- * Copyright (c) Siemens AG, 2018-2021
+ * Copyright (c) Siemens AG, 2018-2023
*
* Authors:
* Henning Schild <henning.schild@siemens.com>
@@ -20,6 +20,9 @@
#define SIMATIC_IPC_DEVICE_127E 3
#define SIMATIC_IPC_DEVICE_227E 4
#define SIMATIC_IPC_DEVICE_227G 5
+#define SIMATIC_IPC_DEVICE_BX_21A 6
+#define SIMATIC_IPC_DEVICE_BX_39A 7
+#define SIMATIC_IPC_DEVICE_BX_59A 8
struct simatic_ipc_platform {
u8 devmode;
diff --git a/include/linux/platform_data/x86/simatic-ipc.h b/include/linux/platform_data/x86/simatic-ipc.h
index a48bb52409777..8d8b3b9196742 100644
--- a/include/linux/platform_data/x86/simatic-ipc.h
+++ b/include/linux/platform_data/x86/simatic-ipc.h
@@ -2,7 +2,7 @@
/*
* Siemens SIMATIC IPC drivers
*
- * Copyright (c) Siemens AG, 2018-2021
+ * Copyright (c) Siemens AG, 2018-2023
*
* Authors:
* Henning Schild <henning.schild@siemens.com>
@@ -32,8 +32,12 @@ enum simatic_ipc_station_ids {
SIMATIC_IPC_IPC477E = 0x00000A02,
SIMATIC_IPC_IPC127E = 0x00000D01,
SIMATIC_IPC_IPC227G = 0x00000F01,
+ SIMATIC_IPC_IPC277G = 0x00000F02,
SIMATIC_IPC_IPCBX_39A = 0x00001001,
SIMATIC_IPC_IPCPX_39A = 0x00001002,
+ SIMATIC_IPC_IPCBX_21A = 0x00001101,
+ SIMATIC_IPC_IPCBX_56A = 0x00001201,
+ SIMATIC_IPC_IPCBX_59A = 0x00001202,
};
static inline u32 simatic_ipc_get_station_id(u8 *data, int max_len)
diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c
index a73346e854b85..5fcc2a92957e7 100644
--- a/tools/power/x86/intel-speed-select/isst-config.c
+++ b/tools/power/x86/intel-speed-select/isst-config.c
@@ -5,6 +5,7 @@
*/
#include <linux/isst_if.h>
+#include <sys/utsname.h>
#include "isst.h"
@@ -15,7 +16,7 @@ struct process_cmd_struct {
int arg;
};
-static const char *version_str = "v1.16";
+static const char *version_str = "v1.17";
static const int supported_api_ver = 2;
static struct isst_if_platform_info isst_platform_info;
@@ -473,11 +474,44 @@ static unsigned int is_cpu_online(int cpu)
return online;
}
+static int get_kernel_version(int *major, int *minor)
+{
+ struct utsname buf;
+ int ret;
+
+ ret = uname(&buf);
+ if (ret)
+ return ret;
+
+ ret = sscanf(buf.release, "%d.%d", major, minor);
+ if (ret != 2)
+ return ret;
+
+ return 0;
+}
+
+#define CPU0_HOTPLUG_DEPRECATE_MAJOR_VER 6
+#define CPU0_HOTPLUG_DEPRECATE_MINOR_VER 5
+
void set_cpu_online_offline(int cpu, int state)
{
char buffer[128];
int fd, ret;
+ if (!cpu) {
+ int major, minor;
+
+ ret = get_kernel_version(&major, &minor);
+ if (!ret) {
+ if (major > CPU0_HOTPLUG_DEPRECATE_MAJOR_VER || (major == CPU0_HOTPLUG_DEPRECATE_MAJOR_VER &&
+ minor >= CPU0_HOTPLUG_DEPRECATE_MINOR_VER)) {
+ debug_printf("Ignore CPU 0 offline/online for kernel version >= %d.%d\n", major, minor);
+ debug_printf("Use cgroups to isolate CPU 0\n");
+ return;
+ }
+ }
+ }
+
snprintf(buffer, sizeof(buffer),
"/sys/devices/system/cpu/cpu%d/online", cpu);
@@ -778,6 +812,7 @@ static void create_cpu_map(void)
map.cpu_map[0].logical_cpu);
} else {
update_punit_cpu_info(map.cpu_map[0].physical_cpu, &cpu_map[i]);
+ punit_id = cpu_map[i].punit_id;
}
}
cpu_map[i].initialized = 1;
@@ -2621,10 +2656,11 @@ static struct process_cmd_struct isst_cmds[] = {
*/
void parse_cpu_command(char *optarg)
{
- unsigned int start, end;
+ unsigned int start, end, invalid_count;
char *next;
next = optarg;
+ invalid_count = 0;
while (next && *next) {
if (*next == '-') /* no negative cpu numbers */
@@ -2634,6 +2670,8 @@ void parse_cpu_command(char *optarg)
if (max_target_cpus < MAX_CPUS_IN_ONE_REQ)
target_cpus[max_target_cpus++] = start;
+ else
+ invalid_count = 1;
if (*next == '\0')
break;
@@ -2660,6 +2698,8 @@ void parse_cpu_command(char *optarg)
while (++start <= end) {
if (max_target_cpus < MAX_CPUS_IN_ONE_REQ)
target_cpus[max_target_cpus++] = start;
+ else
+ invalid_count = 1;
}
if (*next == ',')
@@ -2668,6 +2708,13 @@ void parse_cpu_command(char *optarg)
goto error;
}
+ if (invalid_count) {
+ isst_ctdp_display_information_start(outf);
+ isst_display_error_info_message(1, "Too many CPUs in one request: max is", 1, MAX_CPUS_IN_ONE_REQ - 1);
+ isst_ctdp_display_information_end(outf);
+ exit(-1);
+ }
+
#ifdef DEBUG
{
int i;
diff --git a/tools/power/x86/intel-speed-select/isst-display.c b/tools/power/x86/intel-speed-select/isst-display.c
index 0403d42ab1bae..14c9b037859ac 100644
--- a/tools/power/x86/intel-speed-select/isst-display.c
+++ b/tools/power/x86/intel-speed-select/isst-display.c
@@ -442,7 +442,7 @@ void isst_ctdp_display_information(struct isst_id *id, FILE *outf, int tdp_level
}
if (ctdp_level->mem_freq) {
- snprintf(header, sizeof(header), "mem-frequency(MHz)");
+ snprintf(header, sizeof(header), "max-mem-frequency(MHz)");
snprintf(value, sizeof(value), "%d",
ctdp_level->mem_freq);
format_and_print(outf, level + 2, header, value);
diff --git a/tools/power/x86/intel-speed-select/isst.h b/tools/power/x86/intel-speed-select/isst.h
index 54fc21575d56d..8def22dec4a27 100644
--- a/tools/power/x86/intel-speed-select/isst.h
+++ b/tools/power/x86/intel-speed-select/isst.h
@@ -79,7 +79,7 @@
#define DISP_FREQ_MULTIPLIER 100
-#define MAX_PACKAGE_COUNT 8
+#define MAX_PACKAGE_COUNT 32
#define MAX_DIE_PER_PACKAGE 2
#define MAX_PUNIT_PER_DIE 8