summaryrefslogtreecommitdiff
path: root/drivers/platform/x86
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86')
-rw-r--r--drivers/platform/x86/Kconfig341
-rw-r--r--drivers/platform/x86/Makefile38
-rw-r--r--drivers/platform/x86/acer-wireless.c52
-rw-r--r--drivers/platform/x86/acer-wmi.c374
-rw-r--r--drivers/platform/x86/acerhdf.c4
-rw-r--r--drivers/platform/x86/adv_swbutton.c6
-rw-r--r--drivers/platform/x86/amd/Kconfig12
-rw-r--r--drivers/platform/x86/amd/Makefile2
-rw-r--r--drivers/platform/x86/amd/amd_isp4.c419
-rw-r--r--drivers/platform/x86/amd/hfi/Kconfig18
-rw-r--r--drivers/platform/x86/amd/hfi/Makefile7
-rw-r--r--drivers/platform/x86/amd/hfi/hfi.c546
-rw-r--r--drivers/platform/x86/amd/hsmp/Kconfig2
-rw-r--r--drivers/platform/x86/amd/hsmp/Makefile1
-rw-r--r--drivers/platform/x86/amd/hsmp/acpi.c294
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.c48
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.h11
-rw-r--r--drivers/platform/x86/amd/hsmp/hwmon.c121
-rw-r--r--drivers/platform/x86/amd/hsmp/plat.c44
-rw-r--r--drivers/platform/x86/amd/pmc/mp1_stb.c6
-rw-r--r--drivers/platform/x86/amd/pmc/pmc-quirks.c150
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.c27
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.h1
-rw-r--r--drivers/platform/x86/amd/pmf/acpi.c127
-rw-r--r--drivers/platform/x86/amd/pmf/auto-mode.c18
-rw-r--r--drivers/platform/x86/amd/pmf/cnqf.c18
-rw-r--r--drivers/platform/x86/amd/pmf/core.c204
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h134
-rw-r--r--drivers/platform/x86/amd/pmf/spc.c91
-rw-r--r--drivers/platform/x86/amd/pmf/sps.c52
-rw-r--r--drivers/platform/x86/amd/pmf/tee-if.c229
-rw-r--r--drivers/platform/x86/amd/wbrf.c22
-rw-r--r--drivers/platform/x86/apple-gmux.c2
-rw-r--r--drivers/platform/x86/asus-armoury.c1167
-rw-r--r--drivers/platform/x86/asus-armoury.h2280
-rw-r--r--drivers/platform/x86/asus-laptop.c59
-rw-r--r--drivers/platform/x86/asus-nb-wmi.c47
-rw-r--r--drivers/platform/x86/asus-wireless.c55
-rw-r--r--drivers/platform/x86/asus-wmi.c651
-rw-r--r--drivers/platform/x86/asus-wmi.h3
-rw-r--r--drivers/platform/x86/ayaneo-ec.c593
-rw-r--r--drivers/platform/x86/barco-p50-gpio.c125
-rw-r--r--drivers/platform/x86/bitland-mifs-wmi.c837
-rw-r--r--drivers/platform/x86/classmate-laptop.c38
-rw-r--r--drivers/platform/x86/dasharo-acpi.c360
-rw-r--r--drivers/platform/x86/dell/Kconfig3
-rw-r--r--drivers/platform/x86/dell/alienware-wmi-wmax.c1240
-rw-r--r--drivers/platform/x86/dell/dcdbas.c2
-rw-r--r--drivers/platform/x86/dell/dell-lis3lv02d.c4
-rw-r--r--drivers/platform/x86/dell/dell-pc.c76
-rw-r--r--drivers/platform/x86/dell/dell-rbtn.c146
-rw-r--r--drivers/platform/x86/dell/dell-smbios-base.c23
-rw-r--r--drivers/platform/x86/dell/dell-smbios-smm.c3
-rw-r--r--drivers/platform/x86/dell/dell-smbios-wmi.c4
-rw-r--r--drivers/platform/x86/dell/dell-smbios.h2
-rw-r--r--drivers/platform/x86/dell/dell-uart-backlight.c2
-rw-r--r--drivers/platform/x86/dell/dell-wmi-base.c32
-rw-r--r--drivers/platform/x86/dell/dell-wmi-ddv.c250
-rw-r--r--drivers/platform/x86/dell/dell-wmi-privacy.c4
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h9
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c43
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c9
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c10
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c1
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c9
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/sysman.c82
-rw-r--r--drivers/platform/x86/dell/dell_rbu.c34
-rw-r--r--drivers/platform/x86/eeepc-laptop.c55
-rw-r--r--drivers/platform/x86/fujitsu-laptop.c538
-rw-r--r--drivers/platform/x86/fujitsu-tablet.c30
-rw-r--r--drivers/platform/x86/gigabyte-wmi.c4
-rw-r--r--drivers/platform/x86/gpd-pocket-fan.c4
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.c23
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.h12
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c13
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/int-attributes.c6
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c10
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c9
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/string-attributes.c6
-rw-r--r--drivers/platform/x86/hp/hp-wmi.c745
-rw-r--r--drivers/platform/x86/hp/hp_accel.c3
-rw-r--r--drivers/platform/x86/huawei-wmi.c6
-rw-r--r--drivers/platform/x86/ibm_rtl.c2
-rw-r--r--drivers/platform/x86/intel/Kconfig13
-rw-r--r--drivers/platform/x86/intel/Makefile1
-rw-r--r--drivers/platform/x86/intel/chtwc_int33fe.c29
-rw-r--r--drivers/platform/x86/intel/ehl_pse_io.c86
-rw-r--r--drivers/platform/x86/intel/hid.c111
-rw-r--r--drivers/platform/x86/intel/ifs/core.c7
-rw-r--r--drivers/platform/x86/intel/ifs/load.c21
-rw-r--r--drivers/platform/x86/intel/ifs/runtest.c17
-rw-r--r--drivers/platform/x86/intel/int0002_vgpio.c11
-rw-r--r--drivers/platform/x86/intel/int1092/intel_sar.c13
-rw-r--r--drivers/platform/x86/intel/int3472/Makefile3
-rw-r--r--drivers/platform/x86/intel/int3472/clk_and_regulator.c170
-rw-r--r--drivers/platform/x86/intel/int3472/common.c9
-rw-r--r--drivers/platform/x86/intel/int3472/common.h131
-rw-r--r--drivers/platform/x86/intel/int3472/discrete.c121
-rw-r--r--drivers/platform/x86/intel/int3472/discrete_quirks.c21
-rw-r--r--drivers/platform/x86/intel/int3472/led.c56
-rw-r--r--drivers/platform/x86/intel/int3472/tps68470.c6
-rw-r--r--drivers/platform/x86/intel/int3472/tps68470.h1
-rw-r--r--drivers/platform/x86/intel/int3472/tps68470_board_data.c219
-rw-r--r--drivers/platform/x86/intel/plr_tpmi.c50
-rw-r--r--drivers/platform/x86/intel/pmc/Kconfig4
-rw-r--r--drivers/platform/x86/intel/pmc/Makefile8
-rw-r--r--drivers/platform/x86/intel/pmc/arl.c32
-rw-r--r--drivers/platform/x86/intel/pmc/cnp.c7
-rw-r--r--drivers/platform/x86/intel/pmc/core.c560
-rw-r--r--drivers/platform/x86/intel/pmc/core.h87
-rw-r--r--drivers/platform/x86/intel/pmc/core_ssram.c332
-rw-r--r--drivers/platform/x86/intel/pmc/lnl.c18
-rw-r--r--drivers/platform/x86/intel/pmc/mtl.c21
-rw-r--r--drivers/platform/x86/intel/pmc/pltdrv.c2
-rw-r--r--drivers/platform/x86/intel/pmc/ptl.c36
-rw-r--r--drivers/platform/x86/intel/pmc/ssram_telemetry.c208
-rw-r--r--drivers/platform/x86/intel/pmc/ssram_telemetry.h24
-rw-r--r--drivers/platform/x86/intel/pmc/tgl.c4
-rw-r--r--drivers/platform/x86/intel/pmc/wcl.c504
-rw-r--r--drivers/platform/x86/intel/pmt/Kconfig28
-rw-r--r--drivers/platform/x86/intel/pmt/Makefile4
-rw-r--r--drivers/platform/x86/intel/pmt/class.c64
-rw-r--r--drivers/platform/x86/intel/pmt/class.h17
-rw-r--r--drivers/platform/x86/intel/pmt/crashlog.c459
-rw-r--r--drivers/platform/x86/intel/pmt/discovery-kunit.c116
-rw-r--r--drivers/platform/x86/intel/pmt/discovery.c637
-rw-r--r--drivers/platform/x86/intel/pmt/features.c205
-rw-r--r--drivers/platform/x86/intel/pmt/telemetry.c107
-rw-r--r--drivers/platform/x86/intel/pmt/telemetry.h12
-rw-r--r--drivers/platform/x86/intel/punit_ipc.c2
-rw-r--r--drivers/platform/x86/intel/rst.c27
-rw-r--r--drivers/platform/x86/intel/sdsi.c7
-rw-r--r--drivers/platform/x86/intel/smartconnect.c27
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_common.c52
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c15
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c4
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c208
-rw-r--r--drivers/platform/x86/intel/telemetry/core.c177
-rw-r--r--drivers/platform/x86/intel/telemetry/debugfs.c4
-rw-r--r--drivers/platform/x86/intel/telemetry/pltdrv.c233
-rw-r--r--drivers/platform/x86/intel/tpmi_power_domains.c48
-rw-r--r--drivers/platform/x86/intel/tpmi_power_domains.h1
-rw-r--r--drivers/platform/x86/intel/turbo_max_3.c5
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c47
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h29
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c154
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c31
-rw-r--r--drivers/platform/x86/intel/vbtn.c36
-rw-r--r--drivers/platform/x86/intel/vsec.c482
-rw-r--r--drivers/platform/x86/intel/vsec_tpmi.c53
-rw-r--r--drivers/platform/x86/intel/wmi/sbl-fw-update.c42
-rw-r--r--drivers/platform/x86/intel/wmi/thunderbolt.c27
-rw-r--r--drivers/platform/x86/intel_ips.c40
-rw-r--r--drivers/platform/x86/intel_scu_ipc.c2
-rw-r--r--drivers/platform/x86/lenovo/Kconfig276
-rw-r--r--drivers/platform/x86/lenovo/Makefile28
-rw-r--r--drivers/platform/x86/lenovo/ideapad-laptop.c (renamed from drivers/platform/x86/ideapad-laptop.c)349
-rw-r--r--drivers/platform/x86/lenovo/ideapad-laptop.h (renamed from drivers/platform/x86/ideapad-laptop.h)0
-rw-r--r--drivers/platform/x86/lenovo/think-lmi.c (renamed from drivers/platform/x86/think-lmi.c)232
-rw-r--r--drivers/platform/x86/lenovo/think-lmi.h (renamed from drivers/platform/x86/think-lmi.h)15
-rw-r--r--drivers/platform/x86/lenovo/thinkpad_acpi.c (renamed from drivers/platform/x86/thinkpad_acpi.c)508
-rw-r--r--drivers/platform/x86/lenovo/wmi-camera.c (renamed from drivers/platform/x86/lenovo-wmi-camera.c)1
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata.c828
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata.h85
-rw-r--r--drivers/platform/x86/lenovo/wmi-events.c197
-rw-r--r--drivers/platform/x86/lenovo/wmi-events.h20
-rw-r--r--drivers/platform/x86/lenovo/wmi-gamezone.c409
-rw-r--r--drivers/platform/x86/lenovo/wmi-helpers.c190
-rw-r--r--drivers/platform/x86/lenovo/wmi-helpers.h41
-rw-r--r--drivers/platform/x86/lenovo/wmi-hotkey-utilities.c (renamed from drivers/platform/x86/lenovo-wmi-hotkey-utilities.c)30
-rw-r--r--drivers/platform/x86/lenovo/wmi-other.c1135
-rw-r--r--drivers/platform/x86/lenovo/ymc.c (renamed from drivers/platform/x86/lenovo-ymc.c)1
-rw-r--r--drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c (renamed from drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c)40
-rw-r--r--drivers/platform/x86/lenovo/yogabook.c (renamed from drivers/platform/x86/lenovo-yogabook.c)14
-rw-r--r--drivers/platform/x86/lg-laptop.c114
-rw-r--r--drivers/platform/x86/meraki-mx100.c404
-rw-r--r--drivers/platform/x86/msi-laptop.c3
-rw-r--r--drivers/platform/x86/msi-wmi-platform.c142
-rw-r--r--drivers/platform/x86/mxm-wmi.c12
-rw-r--r--drivers/platform/x86/oxpec.c1001
-rw-r--r--drivers/platform/x86/panasonic-laptop.c87
-rw-r--r--drivers/platform/x86/pcengines-apuv2.c193
-rw-r--r--drivers/platform/x86/pmc_atom.c2
-rw-r--r--drivers/platform/x86/portwell-ec.c460
-rw-r--r--drivers/platform/x86/quickstart.c10
-rw-r--r--drivers/platform/x86/redmi-wmi.c154
-rw-r--r--drivers/platform/x86/samsung-galaxybook.c79
-rw-r--r--drivers/platform/x86/samsung-laptop.c112
-rw-r--r--drivers/platform/x86/serial-multi-instantiate.c13
-rw-r--r--drivers/platform/x86/silicom-platform.c11
-rw-r--r--drivers/platform/x86/sony-laptop.c340
-rw-r--r--drivers/platform/x86/system76_acpi.c67
-rw-r--r--drivers/platform/x86/topstar-laptop.c49
-rw-r--r--drivers/platform/x86/toshiba_acpi.c188
-rw-r--r--drivers/platform/x86/toshiba_bluetooth.c80
-rw-r--r--drivers/platform/x86/toshiba_haps.c61
-rw-r--r--drivers/platform/x86/touchscreen_dmi.c18
-rw-r--r--drivers/platform/x86/tuxedo/Kconfig8
-rw-r--r--drivers/platform/x86/tuxedo/Makefile8
-rw-r--r--drivers/platform/x86/tuxedo/nb04/Kconfig17
-rw-r--r--drivers/platform/x86/tuxedo/nb04/Makefile10
-rw-r--r--drivers/platform/x86/tuxedo/nb04/wmi_ab.c923
-rw-r--r--drivers/platform/x86/tuxedo/nb04/wmi_util.c91
-rw-r--r--drivers/platform/x86/tuxedo/nb04/wmi_util.h109
-rw-r--r--drivers/platform/x86/uniwill/Kconfig38
-rw-r--r--drivers/platform/x86/uniwill/Makefile8
-rw-r--r--drivers/platform/x86/uniwill/uniwill-acpi.c2538
-rw-r--r--drivers/platform/x86/uniwill/uniwill-wmi.c93
-rw-r--r--drivers/platform/x86/uniwill/uniwill-wmi.h129
-rw-r--r--drivers/platform/x86/uv_sysfs.c20
-rw-r--r--drivers/platform/x86/wireless-hotkey.c56
-rw-r--r--drivers/platform/x86/wmi-bmof.c36
-rw-r--r--drivers/platform/x86/wmi.c1390
-rw-r--r--drivers/platform/x86/x86-android-tablets/Makefile2
-rw-r--r--drivers/platform/x86/x86-android-tablets/acer.c247
-rw-r--r--drivers/platform/x86/x86-android-tablets/asus.c127
-rw-r--r--drivers/platform/x86/x86-android-tablets/core.c131
-rw-r--r--drivers/platform/x86/x86-android-tablets/dmi.c26
-rw-r--r--drivers/platform/x86/x86-android-tablets/lenovo.c291
-rw-r--r--drivers/platform/x86/x86-android-tablets/other.c420
-rw-r--r--drivers/platform/x86/x86-android-tablets/shared-psy-info.c110
-rw-r--r--drivers/platform/x86/x86-android-tablets/shared-psy-info.h9
-rw-r--r--drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c4
-rw-r--r--drivers/platform/x86/x86-android-tablets/x86-android-tablets.h31
-rw-r--r--drivers/platform/x86/xiaomi-wmi.c16
-rw-r--r--drivers/platform/x86/xo15-ebook.c12
226 files changed, 27708 insertions, 6626 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 43407e76476b..7a4956088300 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -16,27 +16,6 @@ menuconfig X86_PLATFORM_DEVICES
if X86_PLATFORM_DEVICES
-config ACPI_WMI
- tristate "WMI"
- depends on ACPI
- help
- This driver adds support for the ACPI-WMI (Windows Management
- Instrumentation) mapper device (PNP0C14) found on some systems.
-
- ACPI-WMI is a proprietary extension to ACPI to expose parts of the
- ACPI firmware to userspace - this is done through various vendor
- defined methods and data blocks in a PNP0C14 device, which are then
- made available for userspace to call.
-
- The implementation of this in Linux currently only exposes this to
- other kernel space drivers.
-
- This driver is a required dependency to build the firmware specific
- drivers needed on many machines, including Acer and HP laptops.
-
- It is safe to enable this driver even if your DSDT doesn't define
- any ACPI-WMI devices.
-
config WMI_BMOF
tristate "WMI embedded Binary MOF driver"
depends on ACPI_WMI
@@ -65,6 +44,8 @@ config HUAWEI_WMI
To compile this driver as a module, choose M here: the module
will be called huawei-wmi.
+source "drivers/platform/x86/uniwill/Kconfig"
+
config UV_SYSFS
tristate "Sysfs structure for UV systems"
depends on X86_UV
@@ -109,6 +90,18 @@ config XIAOMI_WMI
To compile this driver as a module, choose M here: the module will
be called xiaomi-wmi.
+config REDMI_WMI
+ tristate "Redmibook WMI key driver"
+ depends on ACPI_WMI
+ depends on INPUT
+ select INPUT_SPARSEKMAP
+ help
+ Say Y here if you want support for WMI-based hotkey events on
+ Xiaomi Redmibook devices.
+
+ To compile this driver as a module, choose M here: the module will
+ be called redmi-wmi.
+
config GIGABYTE_WMI
tristate "Gigabyte WMI temperature driver"
depends on ACPI_WMI
@@ -120,31 +113,24 @@ config GIGABYTE_WMI
To compile this driver as a module, choose M here: the module will
be called gigabyte-wmi.
-config YOGABOOK
- tristate "Lenovo Yoga Book tablet key driver"
+config BITLAND_MIFS_WMI
+ tristate "Bitland MIFS (MiInterface) WMI driver"
depends on ACPI_WMI
+ depends on HWMON
depends on INPUT
- depends on I2C
- select LEDS_CLASS
- select NEW_LEDS
+ depends on LEDS_CLASS
+ depends on POWER_SUPPLY
+ select ACPI_PLATFORM_PROFILE
+ select INPUT_SPARSEKMAP
help
- Say Y here if you want to support the 'Pen' key and keyboard backlight
- control on the Lenovo Yoga Book tablets.
+ This is a driver for Bitland MiInterface based laptops.
- To compile this driver as a module, choose M here: the module will
- be called lenovo-yogabook.
-
-config YT2_1380
- tristate "Lenovo Yoga Tablet 2 1380 fast charge driver"
- depends on SERIAL_DEV_BUS
- depends on EXTCON
- depends on ACPI
- help
- Say Y here to enable support for the custom fast charging protocol
- found on the Lenovo Yoga Tablet 2 1380F / 1380L models.
+ It provides the access to the temperature, fan speed, gpu
+ control, keyboard backlight brightness and platform profile
+ via hwmon and sysfs.
To compile this driver as a module, choose M here: the module will
- be called lenovo-yogabook.
+ be called bitland-mifs-wmi.
config ACERHDF
tristate "Acer Aspire One temperature and fan driver"
@@ -267,6 +253,18 @@ config ASUS_WIRELESS
If you choose to compile this driver as a module the module will be
called asus-wireless.
+config ASUS_ARMOURY
+ tristate "ASUS Armoury driver"
+ depends on ASUS_WMI
+ select FW_ATTR_CLASS
+ help
+ Say Y here if you have a WMI aware Asus machine and would like to use the
+ firmware_attributes API to control various settings typically exposed in
+ the ASUS Armoury Crate application available on Windows.
+
+ To compile this driver as a module, choose M here: the module will
+ be called asus-armoury.
+
config ASUS_WMI
tristate "ASUS WMI Driver"
depends on ACPI_WMI
@@ -289,6 +287,17 @@ config ASUS_WMI
To compile this driver as a module, choose M here: the module will
be called asus-wmi.
+config ASUS_WMI_DEPRECATED_ATTRS
+ bool "BIOS option support in WMI platform (DEPRECATED)"
+ depends on ASUS_WMI
+ default y
+ help
+ Say Y to expose the configurable BIOS options through the asus-wmi
+ driver.
+
+ This can be used with or without the asus-armoury driver which
+ has the same attributes, but more, and better features.
+
config ASUS_NB_WMI
tristate "Asus Notebook WMI Driver"
depends on ASUS_WMI
@@ -321,6 +330,19 @@ config ASUS_TF103C_DOCK
If you have an Asus TF103C tablet say Y or M here, for a generic x86
distro config say M here.
+config AYANEO_EC
+ tristate "Ayaneo EC platform control"
+ depends on DMI
+ depends on ACPI_EC
+ depends on ACPI_BATTERY
+ depends on HWMON
+ help
+ Enables support for the platform EC of Ayaneo devices. This
+ includes fan control, fan speed, charge limit, magic
+ module detection, and controller power control.
+
+ If you have an Ayaneo device, say Y or M here.
+
config MERAKI_MX100
tristate "Cisco Meraki MX100 Platform Driver"
depends on GPIOLIB
@@ -437,7 +459,7 @@ config WIRELESS_HOTKEY
depends on INPUT
help
This driver provides supports for the wireless buttons found on some AMD,
- HP, & Xioami laptops.
+ HP, & Xiaomi laptops.
On such systems the driver should load automatically (via ACPI alias).
To compile this driver as a module, choose M here: the module will
@@ -459,43 +481,6 @@ config IBM_RTL
state = 0 (BIOS SMIs on)
state = 1 (BIOS SMIs off)
-config IDEAPAD_LAPTOP
- tristate "Lenovo IdeaPad Laptop Extras"
- depends on ACPI
- depends on RFKILL && INPUT
- depends on SERIO_I8042
- depends on BACKLIGHT_CLASS_DEVICE
- depends on ACPI_VIDEO || ACPI_VIDEO = n
- depends on ACPI_WMI || ACPI_WMI = n
- select ACPI_PLATFORM_PROFILE
- select INPUT_SPARSEKMAP
- select NEW_LEDS
- select LEDS_CLASS
- help
- This is a driver for Lenovo IdeaPad netbooks contains drivers for
- rfkill switch, hotkey, fan control and backlight control.
-
-config LENOVO_WMI_HOTKEY_UTILITIES
- tristate "Lenovo Hotkey Utility WMI extras driver"
- depends on ACPI_WMI
- select NEW_LEDS
- select LEDS_CLASS
- imply IDEAPAD_LAPTOP
- help
- This driver provides WMI support for Lenovo customized hotkeys function,
- such as LED control for audio/mic mute event for Ideapad, YOGA, XiaoXin,
- Gaming, ThinkBook and so on.
-
-config LENOVO_YMC
- tristate "Lenovo Yoga Tablet Mode Control"
- depends on ACPI_WMI
- depends on INPUT
- depends on IDEAPAD_LAPTOP
- select INPUT_SPARSEKMAP
- help
- This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input
- events for Lenovo Yoga notebooks.
-
config SENSORS_HDAPS
tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
depends on INPUT
@@ -514,160 +499,8 @@ config SENSORS_HDAPS
Say Y here if you have an applicable laptop and want to experience
the awesome power of hdaps.
-config THINKPAD_ACPI
- tristate "ThinkPad ACPI Laptop Extras"
- depends on ACPI_EC
- depends on ACPI_BATTERY
- depends on INPUT
- depends on RFKILL || RFKILL = n
- depends on ACPI_VIDEO || ACPI_VIDEO = n
- depends on BACKLIGHT_CLASS_DEVICE
- depends on I2C
- depends on DRM
- select ACPI_PLATFORM_PROFILE
- select DRM_PRIVACY_SCREEN
- select HWMON
- select NVRAM
- select NEW_LEDS
- select LEDS_CLASS
- select INPUT_SPARSEKMAP
- help
- This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
- support for Fn-Fx key combinations, Bluetooth control, video
- output switching, ThinkLight control, UltraBay eject and more.
- For more information about this driver see
- <file:Documentation/admin-guide/laptops/thinkpad-acpi.rst> and
- <http://ibm-acpi.sf.net/> .
-
- This driver was formerly known as ibm-acpi.
-
- Extra functionality will be available if the rfkill (CONFIG_RFKILL)
- and/or ALSA (CONFIG_SND) subsystems are available in the kernel.
- Note that if you want ThinkPad-ACPI to be built-in instead of
- modular, ALSA and rfkill will also have to be built-in.
-
- If you have an IBM or Lenovo ThinkPad laptop, say Y or M here.
-
-config THINKPAD_ACPI_ALSA_SUPPORT
- bool "Console audio control ALSA interface"
- depends on THINKPAD_ACPI
- depends on SND
- depends on SND = y || THINKPAD_ACPI = SND
- default y
- help
- Enables monitoring of the built-in console audio output control
- (headphone and speakers), which is operated by the mute and (in
- some ThinkPad models) volume hotkeys.
-
- If this option is enabled, ThinkPad-ACPI will export an ALSA card
- with a single read-only mixer control, which should be used for
- on-screen-display feedback purposes by the Desktop Environment.
-
- Optionally, the driver will also allow software control (the
- ALSA mixer will be made read-write). Please refer to the driver
- documentation for details.
-
- All IBM models have both volume and mute control. Newer Lenovo
- models only have mute control (the volume hotkeys are just normal
- keys and volume control is done through the main HDA mixer).
-
-config THINKPAD_ACPI_DEBUGFACILITIES
- bool "Maintainer debug facilities"
- depends on THINKPAD_ACPI
- help
- Enables extra stuff in the thinkpad-acpi which is completely useless
- for normal use. Read the driver source to find out what it does.
-
- Say N here, unless you were told by a kernel maintainer to do
- otherwise.
-
-config THINKPAD_ACPI_DEBUG
- bool "Verbose debug mode"
- depends on THINKPAD_ACPI
- help
- Enables extra debugging information, at the expense of a slightly
- increase in driver size.
-
- If you are not sure, say N here.
-
-config THINKPAD_ACPI_UNSAFE_LEDS
- bool "Allow control of important LEDs (unsafe)"
- depends on THINKPAD_ACPI
- help
- Overriding LED state on ThinkPads can mask important
- firmware alerts (like critical battery condition), or misled
- the user into damaging the hardware (undocking or ejecting
- the bay while buses are still active), etc.
-
- LED control on the ThinkPad is write-only (with very few
- exceptions on very ancient models), which makes it
- impossible to know beforehand if important information will
- be lost when one changes LED state.
-
- Users that know what they are doing can enable this option
- and the driver will allow control of every LED, including
- the ones on the dock stations.
-
- Never enable this option on a distribution kernel.
-
- Say N here, unless you are building a kernel for your own
- use, and need to control the important firmware LEDs.
-
-config THINKPAD_ACPI_VIDEO
- bool "Video output control support"
- depends on THINKPAD_ACPI
- default y
- help
- Allows the thinkpad_acpi driver to provide an interface to control
- the various video output ports.
-
- This feature often won't work well, depending on ThinkPad model,
- display state, video output devices in use, whether there is a X
- server running, phase of the moon, and the current mood of
- Schroedinger's cat. If you can use X.org's RandR to control
- your ThinkPad's video output ports instead of this feature,
- don't think twice: do it and say N here to save memory and avoid
- bad interactions with X.org.
-
- NOTE: access to this feature is limited to processes with the
- CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms
- where it interacts badly with X.org.
-
- If you are not sure, say Y here but do try to check if you could
- be using X.org RandR instead.
-
-config THINKPAD_ACPI_HOTKEY_POLL
- bool "Support NVRAM polling for hot keys"
- depends on THINKPAD_ACPI
- default y
- help
- Some thinkpad models benefit from NVRAM polling to detect a few of
- the hot key press events. If you know your ThinkPad model does not
- need to do NVRAM polling to support any of the hot keys you use,
- unselecting this option will save about 1kB of memory.
-
- ThinkPads T40 and newer, R52 and newer, and X31 and newer are
- unlikely to need NVRAM polling in their latest BIOS versions.
-
- NVRAM polling can detect at most the following keys: ThinkPad/Access
- IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute,
- Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12).
-
- If you are not sure, say Y here. The driver enables polling only if
- it is strictly necessary to do so.
-
-config THINKPAD_LMI
- tristate "Lenovo WMI-based systems management driver"
- depends on ACPI_WMI
- select FW_ATTR_CLASS
- help
- This driver allows changing BIOS settings on Lenovo machines whose
- BIOS support the WMI interface.
-
- To compile this driver as a module, choose M here: the module will
- be called think-lmi.
-
source "drivers/platform/x86/intel/Kconfig"
+source "drivers/platform/x86/lenovo/Kconfig"
config ACPI_QUICKSTART
tristate "ACPI Quickstart button driver"
@@ -739,6 +572,7 @@ config MSI_WMI
config MSI_WMI_PLATFORM
tristate "MSI WMI Platform features"
depends on ACPI_WMI
+ depends on DMI
depends on HWMON
help
Say Y here if you want to have support for WMI-based platform features
@@ -779,6 +613,21 @@ config PCENGINES_APU2
To compile this driver as a module, choose M here: the module
will be called pcengines-apuv2.
+config PORTWELL_EC
+ tristate "Portwell Embedded Controller driver"
+ depends on X86 && HAS_IOPORT && WATCHDOG && GPIOLIB
+ select WATCHDOG_CORE
+ help
+ This driver provides support for the GPIO pins and watchdog timer
+ embedded in Portwell's EC.
+
+ Theoretically, this driver should work on multiple Portwell platforms,
+ but it has only been tested on the Portwell NANO-6064 board.
+ If you encounter any issues on other boards, please report them.
+
+ To compile this driver as a module, choose M here: the module
+ will be called portwell-ec.
+
config BARCO_P50_GPIO
tristate "Barco P50 GPIO driver for identify LED/button"
depends on GPIOLIB
@@ -810,6 +659,7 @@ config SAMSUNG_LAPTOP
tristate "Samsung Laptop driver"
depends on RFKILL || RFKILL = n
depends on ACPI_VIDEO || ACPI_VIDEO = n
+ depends on ACPI_BATTERY
depends on BACKLIGHT_CLASS_DEVICE
select LEDS_CLASS
select NEW_LEDS
@@ -1063,17 +913,15 @@ config INSPUR_PLATFORM_PROFILE
To compile this driver as a module, choose M here: the module
will be called inspur-platform-profile.
-config LENOVO_WMI_CAMERA
- tristate "Lenovo WMI Camera Button driver"
- depends on ACPI_WMI
- depends on INPUT
+config DASHARO_ACPI
+ tristate "Dasharo ACPI Platform Driver"
+ depends on ACPI
+ depends on HWMON
help
- This driver provides support for Lenovo camera button. The Camera
- button is a GPIO device. This driver receives ACPI notifications when
- the camera button is switched on/off.
+ This driver provides HWMON support for devices running Dasharo
+ firmware.
- To compile this driver as a module, choose M here: the module
- will be called lenovo-wmi-camera.
+ If you have a device with Dasharo firmware, choose Y or M here.
source "drivers/platform/x86/x86-android-tablets/Kconfig"
@@ -1201,6 +1049,19 @@ config SEL3350_PLATFORM
To compile this driver as a module, choose M here: the module
will be called sel3350-platform.
+config OXP_EC
+ tristate "OneXPlayer EC platform control"
+ depends on ACPI_EC
+ depends on ACPI_BATTERY
+ depends on HWMON
+ depends on X86
+ help
+ Enables support for the platform EC of OneXPlayer and AOKZOE
+ handheld devices. This includes fan speed, fan controls, and
+ disabling the default TDP behavior of the device.
+
+source "drivers/platform/x86/tuxedo/Kconfig"
+
endif # X86_PLATFORM_DEVICES
config P2SB
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 650dfbebb6c8..872ac3842391 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -5,7 +5,6 @@
#
# Windows Management Interface
-obj-$(CONFIG_ACPI_WMI) += wmi.o
obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o
# WMI drivers
@@ -13,7 +12,9 @@ obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o
obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
obj-$(CONFIG_NVIDIA_WMI_EC_BACKLIGHT) += nvidia-wmi-ec-backlight.o
obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o
+obj-$(CONFIG_REDMI_WMI) += redmi-wmi.o
obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o
+obj-$(CONFIG_BITLAND_MIFS_WMI) += bitland-mifs-wmi.o
# Acer
obj-$(CONFIG_ACERHDF) += acerhdf.o
@@ -32,12 +33,16 @@ obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
# ASUS
obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
obj-$(CONFIG_ASUS_WIRELESS) += asus-wireless.o
+obj-$(CONFIG_ASUS_ARMOURY) += asus-armoury.o
obj-$(CONFIG_ASUS_WMI) += asus-wmi.o
obj-$(CONFIG_ASUS_NB_WMI) += asus-nb-wmi.o
obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o
obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
+# Ayaneo
+obj-$(CONFIG_AYANEO_EC) += ayaneo-ec.o
+
# Cisco/Meraki
obj-$(CONFIG_MERAKI_MX100) += meraki-mx100.o
@@ -58,17 +63,14 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/
# Hewlett Packard Enterprise
obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o
-# IBM Thinkpad and Lenovo
+obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o
+
+# IBM Thinkpad (before 2005)
obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
-obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
-obj-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += lenovo-wmi-hotkey-utilities.o
-obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
-obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
-obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
-obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
-obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
-obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
+
+# Lenovo
+obj-y += lenovo/
# Intel
obj-y += intel/
@@ -92,6 +94,9 @@ obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o
# PC Engines
obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o
+# Portwell
+obj-$(CONFIG_PORTWELL_EC) += portwell-ec.o
+
# Barco
obj-$(CONFIG_BARCO_P50_GPIO) += barco-p50-gpio.o
@@ -109,9 +114,15 @@ obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o
# before toshiba_acpi initializes
obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
+# Uniwill
+obj-y += uniwill/
+
# Inspur
obj-$(CONFIG_INSPUR_PLATFORM_PROFILE) += inspur_platform_profile.o
+# Dasharo
+obj-$(CONFIG_DASHARO_ACPI) += dasharo-acpi.o
+
# Laptop drivers
obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o
obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
@@ -122,7 +133,6 @@ obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o
obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
# Platform drivers
-obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o
obj-$(CONFIG_SERIAL_MULTI_INSTANTIATE) += serial-multi-instantiate.o
obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o
obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o
@@ -149,8 +159,14 @@ obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += siemens/
# Silicom
obj-$(CONFIG_SILICOM_PLATFORM) += silicom-platform.o
+# TUXEDO
+obj-y += tuxedo/
+
# Winmate
obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o
# SEL
obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o
+
+# OneXPlayer
+obj-$(CONFIG_OXP_EC) += oxpec.o
diff --git a/drivers/platform/x86/acer-wireless.c b/drivers/platform/x86/acer-wireless.c
index 1b5d935d085a..fae8e5ad0f97 100644
--- a/drivers/platform/x86/acer-wireless.c
+++ b/drivers/platform/x86/acer-wireless.c
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci_ids.h>
+#include <linux/platform_device.h>
#include <linux/types.h>
static const struct acpi_device_id acer_wireless_acpi_ids[] = {
@@ -18,13 +19,14 @@ static const struct acpi_device_id acer_wireless_acpi_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, acer_wireless_acpi_ids);
-static void acer_wireless_notify(struct acpi_device *adev, u32 event)
+static void acer_wireless_notify(acpi_handle handle, u32 event, void *data)
{
- struct input_dev *idev = acpi_driver_data(adev);
+ struct device *dev = data;
+ struct input_dev *idev = dev_get_drvdata(dev);
- dev_dbg(&adev->dev, "event=%#x\n", event);
+ dev_dbg(dev, "event=%#x\n", event);
if (event != 0x80) {
- dev_notice(&adev->dev, "Unknown SMKB event: %#x\n", event);
+ dev_notice(dev, "Unknown SMKB event: %#x\n", event);
return;
}
input_report_key(idev, KEY_RFKILL, 1);
@@ -33,15 +35,21 @@ static void acer_wireless_notify(struct acpi_device *adev, u32 event)
input_sync(idev);
}
-static int acer_wireless_add(struct acpi_device *adev)
+static int acer_wireless_probe(struct platform_device *pdev)
{
+ struct acpi_device *adev;
struct input_dev *idev;
+ int ret;
- idev = devm_input_allocate_device(&adev->dev);
+ adev = ACPI_COMPANION(&pdev->dev);
+ if (!adev)
+ return -ENODEV;
+
+ idev = devm_input_allocate_device(&pdev->dev);
if (!idev)
return -ENOMEM;
- adev->driver_data = idev;
+ platform_set_drvdata(pdev, idev);
idev->name = "Acer Wireless Radio Control";
idev->phys = "acer-wireless/input0";
idev->id.bustype = BUS_HOST;
@@ -50,19 +58,31 @@ static int acer_wireless_add(struct acpi_device *adev)
set_bit(EV_KEY, idev->evbit);
set_bit(KEY_RFKILL, idev->keybit);
- return input_register_device(idev);
+ ret = input_register_device(idev);
+ if (ret)
+ return ret;
+
+ return acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
+ acer_wireless_notify,
+ &pdev->dev);
+}
+
+static void acer_wireless_remove(struct platform_device *pdev)
+{
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
+ ACPI_DEVICE_NOTIFY,
+ acer_wireless_notify);
}
-static struct acpi_driver acer_wireless_driver = {
- .name = "Acer Wireless Radio Control Driver",
- .class = "hotkey",
- .ids = acer_wireless_acpi_ids,
- .ops = {
- .add = acer_wireless_add,
- .notify = acer_wireless_notify,
+static struct platform_driver acer_wireless_driver = {
+ .probe = acer_wireless_probe,
+ .remove = acer_wireless_remove,
+ .driver = {
+ .name = "Acer Wireless Radio Control Driver",
+ .acpi_match_table = acer_wireless_acpi_ids,
},
};
-module_acpi_driver(acer_wireless_driver);
+module_platform_driver(acer_wireless_driver);
MODULE_DESCRIPTION("Acer Wireless Radio Control Driver");
MODULE_AUTHOR("Chris Chiu <chiu@gmail.com>");
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index 69336bd778ee..e0eaaefb13d0 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -12,10 +12,12 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/dmi.h>
+#include <linux/fixp-arith.h>
#include <linux/backlight.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
@@ -68,10 +70,27 @@ MODULE_LICENSE("GPL");
#define ACER_WMID_SET_GAMING_LED_METHODID 2
#define ACER_WMID_GET_GAMING_LED_METHODID 4
#define ACER_WMID_GET_GAMING_SYS_INFO_METHODID 5
-#define ACER_WMID_SET_GAMING_FAN_BEHAVIOR 14
+#define ACER_WMID_SET_GAMING_FAN_BEHAVIOR_METHODID 14
+#define ACER_WMID_GET_GAMING_FAN_BEHAVIOR_METHODID 15
+#define ACER_WMID_SET_GAMING_FAN_SPEED_METHODID 16
+#define ACER_WMID_GET_GAMING_FAN_SPEED_METHODID 17
#define ACER_WMID_SET_GAMING_MISC_SETTING_METHODID 22
#define ACER_WMID_GET_GAMING_MISC_SETTING_METHODID 23
+#define ACER_GAMING_FAN_BEHAVIOR_CPU BIT(0)
+#define ACER_GAMING_FAN_BEHAVIOR_GPU BIT(3)
+
+#define ACER_GAMING_FAN_BEHAVIOR_STATUS_MASK GENMASK_ULL(7, 0)
+#define ACER_GAMING_FAN_BEHAVIOR_ID_MASK GENMASK_ULL(15, 0)
+#define ACER_GAMING_FAN_BEHAVIOR_SET_CPU_MODE_MASK GENMASK(17, 16)
+#define ACER_GAMING_FAN_BEHAVIOR_SET_GPU_MODE_MASK GENMASK(23, 22)
+#define ACER_GAMING_FAN_BEHAVIOR_GET_CPU_MODE_MASK GENMASK(9, 8)
+#define ACER_GAMING_FAN_BEHAVIOR_GET_GPU_MODE_MASK GENMASK(15, 14)
+
+#define ACER_GAMING_FAN_SPEED_STATUS_MASK GENMASK_ULL(7, 0)
+#define ACER_GAMING_FAN_SPEED_ID_MASK GENMASK_ULL(7, 0)
+#define ACER_GAMING_FAN_SPEED_VALUE_MASK GENMASK_ULL(15, 8)
+
#define ACER_GAMING_MISC_SETTING_STATUS_MASK GENMASK_ULL(7, 0)
#define ACER_GAMING_MISC_SETTING_INDEX_MASK GENMASK_ULL(7, 0)
#define ACER_GAMING_MISC_SETTING_VALUE_MASK GENMASK_ULL(15, 8)
@@ -102,6 +121,7 @@ MODULE_ALIAS("wmi:676AA15E-6A47-4D9F-A2CC-1E6D18D14026");
enum acer_wmi_event_ids {
WMID_HOTKEY_EVENT = 0x1,
+ WMID_BACKLIGHT_EVENT = 0x4,
WMID_ACCEL_OR_KBD_DOCK_EVENT = 0x5,
WMID_GAMING_TURBO_KEY_EVENT = 0x7,
WMID_AC_EVENT = 0x8,
@@ -121,6 +141,17 @@ enum acer_wmi_predator_v4_sensor_id {
ACER_WMID_SENSOR_GPU_TEMPERATURE = 0x0A,
};
+enum acer_wmi_gaming_fan_id {
+ ACER_WMID_CPU_FAN = 0x01,
+ ACER_WMID_GPU_FAN = 0x04,
+};
+
+enum acer_wmi_gaming_fan_mode {
+ ACER_WMID_FAN_MODE_AUTO = 0x01,
+ ACER_WMID_FAN_MODE_TURBO = 0x02,
+ ACER_WMID_FAN_MODE_CUSTOM = 0x03,
+};
+
enum acer_wmi_predator_v4_oc {
ACER_WMID_OC_NORMAL = 0x0000,
ACER_WMID_OC_TURBO = 0x0002,
@@ -129,6 +160,7 @@ enum acer_wmi_predator_v4_oc {
enum acer_wmi_gaming_misc_setting {
ACER_WMID_MISC_SETTING_OC_1 = 0x0005,
ACER_WMID_MISC_SETTING_OC_2 = 0x0007,
+ /* Unreliable on some models */
ACER_WMID_MISC_SETTING_SUPPORTED_PROFILES = 0x000A,
ACER_WMID_MISC_SETTING_PLATFORM_PROFILE = 0x000B,
};
@@ -277,6 +309,7 @@ struct hotkey_function_type_aa {
#define ACER_CAP_TURBO_FAN BIT(9)
#define ACER_CAP_PLATFORM_PROFILE BIT(10)
#define ACER_CAP_HWMON BIT(11)
+#define ACER_CAP_PWM BIT(12)
/*
* Interface type flags
@@ -371,6 +404,7 @@ struct quirk_entry {
u8 cpu_fans;
u8 gpu_fans;
u8 predator_v4;
+ u8 pwm;
};
static struct quirk_entry *quirks;
@@ -390,6 +424,9 @@ static void __init set_quirks(void)
if (quirks->predator_v4)
interface->capability |= ACER_CAP_PLATFORM_PROFILE |
ACER_CAP_HWMON;
+
+ if (quirks->pwm)
+ interface->capability |= ACER_CAP_PWM;
}
static int __init dmi_matched(const struct dmi_system_id *dmi)
@@ -418,6 +455,11 @@ static struct quirk_entry quirk_acer_travelmate_2490 = {
.mailled = 1,
};
+static struct quirk_entry quirk_acer_nitro_an515_58 = {
+ .predator_v4 = 1,
+ .pwm = 1,
+};
+
static struct quirk_entry quirk_acer_predator_ph315_53 = {
.turbo = 1,
.cpu_fans = 1,
@@ -429,6 +471,7 @@ static struct quirk_entry quirk_acer_predator_ph16_72 = {
.cpu_fans = 1,
.gpu_fans = 1,
.predator_v4 = 1,
+ .pwm = 1,
};
static struct quirk_entry quirk_acer_predator_pt14_51 = {
@@ -436,6 +479,7 @@ static struct quirk_entry quirk_acer_predator_pt14_51 = {
.cpu_fans = 1,
.gpu_fans = 1,
.predator_v4 = 1,
+ .pwm = 1,
};
static struct quirk_entry quirk_acer_predator_v4 = {
@@ -616,7 +660,7 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-58"),
},
- .driver_data = &quirk_acer_predator_v4,
+ .driver_data = &quirk_acer_nitro_an515_58,
},
{
.callback = dmi_matched,
@@ -656,6 +700,15 @@ static const struct dmi_system_id acer_quirks[] __initconst = {
},
{
.callback = dmi_matched,
+ .ident = "Acer Predator Helios Neo 16",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Predator PHN16-72"),
+ },
+ .driver_data = &quirk_acer_predator_ph16_72,
+ },
+ {
+ .callback = dmi_matched,
.ident = "Acer Predator PH18-71",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
@@ -794,9 +847,6 @@ static bool platform_profile_support;
*/
static int last_non_turbo_profile = INT_MIN;
-/* The most performant supported profile */
-static int acer_predator_v4_max_perf;
-
enum acer_predator_v4_thermal_profile {
ACER_PREDATOR_V4_THERMAL_PROFILE_QUIET = 0x00,
ACER_PREDATOR_V4_THERMAL_PROFILE_BALANCED = 0x01,
@@ -1565,9 +1615,6 @@ static acpi_status WMID_gaming_set_u64(u64 value, u32 cap)
case ACER_CAP_TURBO_LED:
method_id = ACER_WMID_SET_GAMING_LED_METHODID;
break;
- case ACER_CAP_TURBO_FAN:
- method_id = ACER_WMID_SET_GAMING_FAN_BEHAVIOR;
- break;
default:
return AE_BAD_PARAMETER;
}
@@ -1618,25 +1665,125 @@ static int WMID_gaming_get_sys_info(u32 command, u64 *out)
return 0;
}
-static void WMID_gaming_set_fan_mode(u8 fan_mode)
+static int WMID_gaming_set_fan_behavior(u16 fan_bitmap, enum acer_wmi_gaming_fan_mode mode)
{
- /* fan_mode = 1 is used for auto, fan_mode = 2 used for turbo*/
- u64 gpu_fan_config1 = 0, gpu_fan_config2 = 0;
- int i;
+ acpi_status status;
+ u64 input = 0;
+ u64 result;
+
+ input |= FIELD_PREP(ACER_GAMING_FAN_BEHAVIOR_ID_MASK, fan_bitmap);
+
+ if (fan_bitmap & ACER_GAMING_FAN_BEHAVIOR_CPU)
+ input |= FIELD_PREP(ACER_GAMING_FAN_BEHAVIOR_SET_CPU_MODE_MASK, mode);
+
+ if (fan_bitmap & ACER_GAMING_FAN_BEHAVIOR_GPU)
+ input |= FIELD_PREP(ACER_GAMING_FAN_BEHAVIOR_SET_GPU_MODE_MASK, mode);
+
+ status = WMI_gaming_execute_u64(ACER_WMID_SET_GAMING_FAN_BEHAVIOR_METHODID, input,
+ &result);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ /* The return status must be zero for the operation to have succeeded */
+ if (FIELD_GET(ACER_GAMING_FAN_BEHAVIOR_STATUS_MASK, result))
+ return -EIO;
+
+ return 0;
+}
+
+static int WMID_gaming_get_fan_behavior(u16 fan_bitmap, enum acer_wmi_gaming_fan_mode *mode)
+{
+ acpi_status status;
+ u32 input = 0;
+ u64 result;
+ int value;
+
+ input |= FIELD_PREP(ACER_GAMING_FAN_BEHAVIOR_ID_MASK, fan_bitmap);
+ status = WMI_gaming_execute_u32_u64(ACER_WMID_GET_GAMING_FAN_BEHAVIOR_METHODID, input,
+ &result);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ /* The return status must be zero for the operation to have succeeded */
+ if (FIELD_GET(ACER_GAMING_FAN_BEHAVIOR_STATUS_MASK, result))
+ return -EIO;
+
+ /* Theoretically multiple fans can be specified, but this is currently unused */
+ if (fan_bitmap & ACER_GAMING_FAN_BEHAVIOR_CPU)
+ value = FIELD_GET(ACER_GAMING_FAN_BEHAVIOR_GET_CPU_MODE_MASK, result);
+ else if (fan_bitmap & ACER_GAMING_FAN_BEHAVIOR_GPU)
+ value = FIELD_GET(ACER_GAMING_FAN_BEHAVIOR_GET_GPU_MODE_MASK, result);
+ else
+ return -EINVAL;
+
+ if (value < ACER_WMID_FAN_MODE_AUTO || value > ACER_WMID_FAN_MODE_CUSTOM)
+ return -ENXIO;
+
+ *mode = value;
+
+ return 0;
+}
+
+static void WMID_gaming_set_fan_mode(enum acer_wmi_gaming_fan_mode mode)
+{
+ u16 fan_bitmap = 0;
if (quirks->cpu_fans > 0)
- gpu_fan_config2 |= 1;
- for (i = 0; i < (quirks->cpu_fans + quirks->gpu_fans); ++i)
- gpu_fan_config2 |= 1 << (i + 1);
- for (i = 0; i < quirks->gpu_fans; ++i)
- gpu_fan_config2 |= 1 << (i + 3);
- if (quirks->cpu_fans > 0)
- gpu_fan_config1 |= fan_mode;
- for (i = 0; i < (quirks->cpu_fans + quirks->gpu_fans); ++i)
- gpu_fan_config1 |= fan_mode << (2 * i + 2);
- for (i = 0; i < quirks->gpu_fans; ++i)
- gpu_fan_config1 |= fan_mode << (2 * i + 6);
- WMID_gaming_set_u64(gpu_fan_config2 | gpu_fan_config1 << 16, ACER_CAP_TURBO_FAN);
+ fan_bitmap |= ACER_GAMING_FAN_BEHAVIOR_CPU;
+
+ if (quirks->gpu_fans > 0)
+ fan_bitmap |= ACER_GAMING_FAN_BEHAVIOR_GPU;
+
+ WMID_gaming_set_fan_behavior(fan_bitmap, mode);
+}
+
+static int WMID_gaming_set_gaming_fan_speed(u8 fan, u8 speed)
+{
+ acpi_status status;
+ u64 input = 0;
+ u64 result;
+
+ if (speed > 100)
+ return -EINVAL;
+
+ input |= FIELD_PREP(ACER_GAMING_FAN_SPEED_ID_MASK, fan);
+ input |= FIELD_PREP(ACER_GAMING_FAN_SPEED_VALUE_MASK, speed);
+
+ status = WMI_gaming_execute_u64(ACER_WMID_SET_GAMING_FAN_SPEED_METHODID, input, &result);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ switch (FIELD_GET(ACER_GAMING_FAN_SPEED_STATUS_MASK, result)) {
+ case 0x00:
+ return 0;
+ case 0x01:
+ return -ENODEV;
+ case 0x02:
+ return -EINVAL;
+ default:
+ return -ENXIO;
+ }
+}
+
+static int WMID_gaming_get_gaming_fan_speed(u8 fan, u8 *speed)
+{
+ acpi_status status;
+ u32 input = 0;
+ u64 result;
+
+ input |= FIELD_PREP(ACER_GAMING_FAN_SPEED_ID_MASK, fan);
+
+ status = WMI_gaming_execute_u32_u64(ACER_WMID_GET_GAMING_FAN_SPEED_METHODID, input,
+ &result);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ if (FIELD_GET(ACER_GAMING_FAN_SPEED_STATUS_MASK, result))
+ return -ENODEV;
+
+ *speed = FIELD_GET(ACER_GAMING_FAN_SPEED_VALUE_MASK, result);
+
+ return 0;
}
static int WMID_gaming_set_misc_setting(enum acer_wmi_gaming_misc_setting setting, u8 value)
@@ -1923,7 +2070,8 @@ static int acer_toggle_turbo(void)
WMID_gaming_set_u64(0x1, ACER_CAP_TURBO_LED);
/* Set FAN mode to auto */
- WMID_gaming_set_fan_mode(0x1);
+ if (has_cap(ACER_CAP_TURBO_FAN))
+ WMID_gaming_set_fan_mode(ACER_WMID_FAN_MODE_AUTO);
/* Set OC to normal */
if (has_cap(ACER_CAP_TURBO_OC)) {
@@ -1937,7 +2085,8 @@ static int acer_toggle_turbo(void)
WMID_gaming_set_u64(0x10001, ACER_CAP_TURBO_LED);
/* Set FAN mode to turbo */
- WMID_gaming_set_fan_mode(0x2);
+ if (has_cap(ACER_CAP_TURBO_FAN))
+ WMID_gaming_set_fan_mode(ACER_WMID_FAN_MODE_TURBO);
/* Set OC to turbo mode */
if (has_cap(ACER_CAP_TURBO_OC)) {
@@ -2014,7 +2163,7 @@ acer_predator_v4_platform_profile_set(struct device *dev,
if (err)
return err;
- if (tp != acer_predator_v4_max_perf)
+ if (tp != ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO)
last_non_turbo_profile = tp;
return 0;
@@ -2023,55 +2172,14 @@ acer_predator_v4_platform_profile_set(struct device *dev,
static int
acer_predator_v4_platform_profile_probe(void *drvdata, unsigned long *choices)
{
- unsigned long supported_profiles;
- int err;
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_QUIET, choices);
+ set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
- err = WMID_gaming_get_misc_setting(ACER_WMID_MISC_SETTING_SUPPORTED_PROFILES,
- (u8 *)&supported_profiles);
- if (err)
- return err;
-
- /* Iterate through supported profiles in order of increasing performance */
- if (test_bit(ACER_PREDATOR_V4_THERMAL_PROFILE_ECO, &supported_profiles)) {
- set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
- acer_predator_v4_max_perf = ACER_PREDATOR_V4_THERMAL_PROFILE_ECO;
- last_non_turbo_profile = ACER_PREDATOR_V4_THERMAL_PROFILE_ECO;
- }
-
- if (test_bit(ACER_PREDATOR_V4_THERMAL_PROFILE_QUIET, &supported_profiles)) {
- set_bit(PLATFORM_PROFILE_QUIET, choices);
- acer_predator_v4_max_perf = ACER_PREDATOR_V4_THERMAL_PROFILE_QUIET;
- last_non_turbo_profile = ACER_PREDATOR_V4_THERMAL_PROFILE_QUIET;
- }
-
- if (test_bit(ACER_PREDATOR_V4_THERMAL_PROFILE_BALANCED, &supported_profiles)) {
- set_bit(PLATFORM_PROFILE_BALANCED, choices);
- acer_predator_v4_max_perf = ACER_PREDATOR_V4_THERMAL_PROFILE_BALANCED;
- last_non_turbo_profile = ACER_PREDATOR_V4_THERMAL_PROFILE_BALANCED;
- }
-
- if (test_bit(ACER_PREDATOR_V4_THERMAL_PROFILE_PERFORMANCE, &supported_profiles)) {
- set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
- acer_predator_v4_max_perf = ACER_PREDATOR_V4_THERMAL_PROFILE_PERFORMANCE;
-
- /* We only use this profile as a fallback option in case no prior
- * profile is supported.
- */
- if (last_non_turbo_profile < 0)
- last_non_turbo_profile = ACER_PREDATOR_V4_THERMAL_PROFILE_PERFORMANCE;
- }
-
- if (test_bit(ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO, &supported_profiles)) {
- set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
- acer_predator_v4_max_perf = ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO;
-
- /* We need to handle the hypothetical case where only the turbo profile
- * is supported. In this case the turbo toggle will essentially be a
- * no-op.
- */
- if (last_non_turbo_profile < 0)
- last_non_turbo_profile = ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO;
- }
+ /* Set default non-turbo profile */
+ last_non_turbo_profile = ACER_PREDATOR_V4_THERMAL_PROFILE_BALANCED;
return 0;
}
@@ -2108,19 +2216,15 @@ static int acer_thermal_profile_change(void)
if (cycle_gaming_thermal_profile) {
platform_profile_cycle();
} else {
- /* Do nothing if no suitable platform profiles where found */
- if (last_non_turbo_profile < 0)
- return 0;
-
err = WMID_gaming_get_misc_setting(
ACER_WMID_MISC_SETTING_PLATFORM_PROFILE, &current_tp);
if (err)
return err;
- if (current_tp == acer_predator_v4_max_perf)
+ if (current_tp == ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO)
tp = last_non_turbo_profile;
else
- tp = acer_predator_v4_max_perf;
+ tp = ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO;
err = WMID_gaming_set_misc_setting(
ACER_WMID_MISC_SETTING_PLATFORM_PROFILE, tp);
@@ -2128,7 +2232,7 @@ static int acer_thermal_profile_change(void)
return err;
/* Store last profile for toggle */
- if (current_tp != acer_predator_v4_max_perf)
+ if (current_tp != ACER_PREDATOR_V4_THERMAL_PROFILE_TURBO)
last_non_turbo_profile = current_tp;
platform_profile_notify(platform_profile_device);
@@ -2416,6 +2520,9 @@ static void acer_wmi_notify(union acpi_object *obj, void *context)
sparse_keymap_report_event(acer_wmi_input_dev, scancode, 1, true);
}
break;
+ case WMID_BACKLIGHT_EVENT:
+ /* Already handled by acpi-video */
+ break;
case WMID_ACCEL_OR_KBD_DOCK_EVENT:
acer_gsensor_event();
acer_kbd_dock_event(&return_value);
@@ -2810,6 +2917,16 @@ static const enum acer_wmi_predator_v4_sensor_id acer_wmi_fan_channel_to_sensor_
[1] = ACER_WMID_SENSOR_GPU_FAN_SPEED,
};
+static const enum acer_wmi_gaming_fan_id acer_wmi_fan_channel_to_fan_id[] = {
+ [0] = ACER_WMID_CPU_FAN,
+ [1] = ACER_WMID_GPU_FAN,
+};
+
+static const u16 acer_wmi_fan_channel_to_fan_bitmap[] = {
+ [0] = ACER_GAMING_FAN_BEHAVIOR_CPU,
+ [1] = ACER_GAMING_FAN_BEHAVIOR_GPU,
+};
+
static umode_t acer_wmi_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type, u32 attr,
int channel)
@@ -2821,6 +2938,11 @@ static umode_t acer_wmi_hwmon_is_visible(const void *data,
case hwmon_temp:
sensor_id = acer_wmi_temp_channel_to_sensor_id[channel];
break;
+ case hwmon_pwm:
+ if (!has_cap(ACER_CAP_PWM))
+ return 0;
+
+ fallthrough;
case hwmon_fan:
sensor_id = acer_wmi_fan_channel_to_sensor_id[channel];
break;
@@ -2828,8 +2950,12 @@ static umode_t acer_wmi_hwmon_is_visible(const void *data,
return 0;
}
- if (*supported_sensors & BIT(sensor_id - 1))
+ if (*supported_sensors & BIT(sensor_id - 1)) {
+ if (type == hwmon_pwm)
+ return 0644;
+
return 0444;
+ }
return 0;
}
@@ -2838,6 +2964,9 @@ static int acer_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
u64 command = ACER_WMID_CMD_GET_PREDATOR_V4_SENSOR_READING;
+ enum acer_wmi_gaming_fan_mode mode;
+ u16 fan_bitmap;
+ u8 fan, speed;
u64 result;
int ret;
@@ -2863,6 +2992,80 @@ static int acer_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
*val = FIELD_GET(ACER_PREDATOR_V4_SENSOR_READING_BIT_MASK, result);
return 0;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ fan = acer_wmi_fan_channel_to_fan_id[channel];
+ ret = WMID_gaming_get_gaming_fan_speed(fan, &speed);
+ if (ret < 0)
+ return ret;
+
+ *val = fixp_linear_interpolate(0, 0, 100, U8_MAX, speed);
+ return 0;
+ case hwmon_pwm_enable:
+ fan_bitmap = acer_wmi_fan_channel_to_fan_bitmap[channel];
+ ret = WMID_gaming_get_fan_behavior(fan_bitmap, &mode);
+ if (ret < 0)
+ return ret;
+
+ switch (mode) {
+ case ACER_WMID_FAN_MODE_AUTO:
+ *val = 2;
+ return 0;
+ case ACER_WMID_FAN_MODE_TURBO:
+ *val = 0;
+ return 0;
+ case ACER_WMID_FAN_MODE_CUSTOM:
+ *val = 1;
+ return 0;
+ default:
+ return -ENXIO;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int acer_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ enum acer_wmi_gaming_fan_mode mode;
+ u16 fan_bitmap;
+ u8 fan, speed;
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ fan = acer_wmi_fan_channel_to_fan_id[channel];
+ speed = fixp_linear_interpolate(0, 0, U8_MAX, 100,
+ clamp_val(val, 0, U8_MAX));
+
+ return WMID_gaming_set_gaming_fan_speed(fan, speed);
+ case hwmon_pwm_enable:
+ fan_bitmap = acer_wmi_fan_channel_to_fan_bitmap[channel];
+
+ switch (val) {
+ case 0:
+ mode = ACER_WMID_FAN_MODE_TURBO;
+ break;
+ case 1:
+ mode = ACER_WMID_FAN_MODE_CUSTOM;
+ break;
+ case 2:
+ mode = ACER_WMID_FAN_MODE_AUTO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return WMID_gaming_set_fan_behavior(fan_bitmap, mode);
+ default:
+ return -EOPNOTSUPP;
+ }
default:
return -EOPNOTSUPP;
}
@@ -2878,11 +3081,16 @@ static const struct hwmon_channel_info *const acer_wmi_hwmon_info[] = {
HWMON_F_INPUT,
HWMON_F_INPUT
),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE
+ ),
NULL
};
static const struct hwmon_ops acer_wmi_hwmon_ops = {
.read = acer_wmi_hwmon_read,
+ .write = acer_wmi_hwmon_write,
.is_visible = acer_wmi_hwmon_is_visible,
};
diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c
index 4c3bb68e8fe4..5ce5ad3efe69 100644
--- a/drivers/platform/x86/acerhdf.c
+++ b/drivers/platform/x86/acerhdf.c
@@ -271,7 +271,7 @@ static const struct bios_settings bios_tbl[] __initconst = {
* this struct is used to instruct thermal layer to use bang_bang instead of
* default governor for acerhdf
*/
-static struct thermal_zone_params acerhdf_zone_params = {
+static const struct thermal_zone_params acerhdf_zone_params = {
.governor_name = "bang_bang",
};
@@ -426,7 +426,7 @@ static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal,
}
/* bind callback functions to thermalzone */
-static struct thermal_zone_device_ops acerhdf_dev_ops = {
+static const struct thermal_zone_device_ops acerhdf_dev_ops = {
.should_bind = acerhdf_should_bind,
.get_temp = acerhdf_get_ec_temp,
.change_mode = acerhdf_change_mode,
diff --git a/drivers/platform/x86/adv_swbutton.c b/drivers/platform/x86/adv_swbutton.c
index 6fa60f3fc53c..8f7a26e6de81 100644
--- a/drivers/platform/x86/adv_swbutton.c
+++ b/drivers/platform/x86/adv_swbutton.c
@@ -48,10 +48,14 @@ static int adv_swbutton_probe(struct platform_device *device)
{
struct adv_swbutton *button;
struct input_dev *input;
- acpi_handle handle = ACPI_HANDLE(&device->dev);
+ acpi_handle handle;
acpi_status status;
int error;
+ handle = ACPI_HANDLE(&device->dev);
+ if (!handle)
+ return -ENODEV;
+
button = devm_kzalloc(&device->dev, sizeof(*button), GFP_KERNEL);
if (!button)
return -ENOMEM;
diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig
index c3e086ea64fc..b813f9265368 100644
--- a/drivers/platform/x86/amd/Kconfig
+++ b/drivers/platform/x86/amd/Kconfig
@@ -6,6 +6,7 @@
source "drivers/platform/x86/amd/hsmp/Kconfig"
source "drivers/platform/x86/amd/pmf/Kconfig"
source "drivers/platform/x86/amd/pmc/Kconfig"
+source "drivers/platform/x86/amd/hfi/Kconfig"
config AMD_3D_VCACHE
tristate "AMD 3D V-Cache Performance Optimizer Driver"
@@ -32,3 +33,14 @@ config AMD_WBRF
This mechanism will only be activated on platforms that advertise a
need for it.
+
+config AMD_ISP_PLATFORM
+ tristate "AMD ISP4 platform driver"
+ depends on I2C && X86_64 && ACPI
+ help
+ Platform driver for AMD platforms containing image signal processor
+ gen 4. Provides camera sensor module board information to allow
+ sensor and V4L drivers to work properly.
+
+ This driver can also be built as a module. If so, the module
+ will be called amd_isp4.
diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile
index c6c40bdcbded..f6ff0c837f34 100644
--- a/drivers/platform/x86/amd/Makefile
+++ b/drivers/platform/x86/amd/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_AMD_PMC) += pmc/
obj-$(CONFIG_AMD_HSMP) += hsmp/
obj-$(CONFIG_AMD_PMF) += pmf/
obj-$(CONFIG_AMD_WBRF) += wbrf.o
+obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o
+obj-$(CONFIG_AMD_HFI) += hfi/
diff --git a/drivers/platform/x86/amd/amd_isp4.c b/drivers/platform/x86/amd/amd_isp4.c
new file mode 100644
index 000000000000..0d494899502c
--- /dev/null
+++ b/drivers/platform/x86/amd/amd_isp4.c
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * AMD ISP platform driver for sensor i2-client instantiation
+ *
+ * Copyright 2025 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/soc/amd/isp4_misc.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#define AMDISP_OV05C10_I2C_ADDR 0x10
+#define AMDISP_OV05C10_HID "OMNI5C10"
+#define AMDISP_OV05C10_REMOTE_EP_NAME "ov05c10_isp_4_1_1"
+#define AMD_ISP_PLAT_DRV_NAME "amd-isp4"
+
+static const struct software_node isp4_mipi1_endpoint_node;
+static const struct software_node ov05c10_endpoint_node;
+
+/*
+ * AMD ISP platform info definition to initialize sensor
+ * specific platform configuration to prepare the amdisp
+ * platform.
+ */
+struct amdisp_platform_info {
+ struct i2c_board_info board_info;
+ const struct software_node **swnodes;
+};
+
+/*
+ * AMD ISP platform definition to configure the device properties
+ * missing in the ACPI table.
+ */
+struct amdisp_platform {
+ const struct amdisp_platform_info *pinfo;
+ struct i2c_board_info board_info;
+ struct notifier_block i2c_nb;
+ struct i2c_client *i2c_dev;
+ struct mutex lock; /* protects i2c client creation */
+};
+
+/* Root AMD CAMERA SWNODE */
+
+/* Root amd camera node definition */
+static const struct software_node amd_camera_node = {
+ .name = "amd_camera",
+};
+
+/* ISP4 SWNODE */
+
+/* ISP4 OV05C10 camera node definition */
+static const struct software_node isp4_node = {
+ .name = "isp4",
+ .parent = &amd_camera_node,
+};
+
+/*
+ * ISP4 Ports node definition. No properties defined for
+ * ports node.
+ */
+static const struct software_node isp4_ports = {
+ .name = "ports",
+ .parent = &isp4_node,
+};
+
+/*
+ * ISP4 Port node definition. No properties defined for
+ * port node.
+ */
+static const struct software_node isp4_port_node = {
+ .name = "port@0",
+ .parent = &isp4_ports,
+};
+
+/*
+ * ISP4 MIPI1 remote endpoint points to OV05C10 endpoint
+ * node.
+ */
+static const struct software_node_ref_args isp4_refs[] = {
+ SOFTWARE_NODE_REFERENCE(&ov05c10_endpoint_node),
+};
+
+/* ISP4 MIPI1 endpoint node properties table */
+static const struct property_entry isp4_mipi1_endpoint_props[] = {
+ PROPERTY_ENTRY_REF_ARRAY("remote-endpoint", isp4_refs),
+ { }
+};
+
+/* ISP4 MIPI1 endpoint node definition */
+static const struct software_node isp4_mipi1_endpoint_node = {
+ .name = "endpoint",
+ .parent = &isp4_port_node,
+ .properties = isp4_mipi1_endpoint_props,
+};
+
+/* I2C1 SWNODE */
+
+/* I2C1 camera node property table */
+static const struct property_entry i2c1_camera_props[] = {
+ PROPERTY_ENTRY_U32("clock-frequency", 1 * HZ_PER_MHZ),
+ { }
+};
+
+/* I2C1 camera node definition */
+static const struct software_node i2c1_node = {
+ .name = "i2c1",
+ .parent = &amd_camera_node,
+ .properties = i2c1_camera_props,
+};
+
+/* I2C1 camera node property table */
+static const struct property_entry ov05c10_camera_props[] = {
+ PROPERTY_ENTRY_U32("clock-frequency", 24 * HZ_PER_MHZ),
+ { }
+};
+
+/* OV05C10 camera node definition */
+static const struct software_node ov05c10_camera_node = {
+ .name = AMDISP_OV05C10_HID,
+ .parent = &i2c1_node,
+ .properties = ov05c10_camera_props,
+};
+
+/*
+ * OV05C10 Ports node definition. No properties defined for
+ * ports node for OV05C10.
+ */
+static const struct software_node ov05c10_ports = {
+ .name = "ports",
+ .parent = &ov05c10_camera_node,
+};
+
+/*
+ * OV05C10 Port node definition.
+ */
+static const struct software_node ov05c10_port_node = {
+ .name = "port@0",
+ .parent = &ov05c10_ports,
+};
+
+/*
+ * OV05C10 remote endpoint points to ISP4 MIPI1 endpoint
+ * node.
+ */
+static const struct software_node_ref_args ov05c10_refs[] = {
+ SOFTWARE_NODE_REFERENCE(&isp4_mipi1_endpoint_node),
+};
+
+/* OV05C10 supports one single link frequency */
+static const u64 ov05c10_link_freqs[] = {
+ 900 * HZ_PER_MHZ,
+};
+
+/* OV05C10 supports only 2-lane configuration */
+static const u32 ov05c10_data_lanes[] = {
+ 1,
+ 2,
+};
+
+/* OV05C10 endpoint node properties table */
+static const struct property_entry ov05c10_endpoint_props[] = {
+ PROPERTY_ENTRY_U32("bus-type", 4),
+ PROPERTY_ENTRY_U32_ARRAY_LEN("data-lanes", ov05c10_data_lanes,
+ ARRAY_SIZE(ov05c10_data_lanes)),
+ PROPERTY_ENTRY_U64_ARRAY_LEN("link-frequencies", ov05c10_link_freqs,
+ ARRAY_SIZE(ov05c10_link_freqs)),
+ PROPERTY_ENTRY_REF_ARRAY("remote-endpoint", ov05c10_refs),
+ { }
+};
+
+/* OV05C10 endpoint node definition */
+static const struct software_node ov05c10_endpoint_node = {
+ .name = "endpoint",
+ .parent = &ov05c10_port_node,
+ .properties = ov05c10_endpoint_props,
+};
+
+/*
+ * AMD Camera swnode graph uses 10 nodes and also its relationship is
+ * fixed to align with the structure that v4l2 and i2c frameworks expects
+ * for successful parsing of fwnodes and its properties with standard names.
+ *
+ * It is only the node property_entries that will vary for each platform
+ * supporting different sensor modules.
+ *
+ * AMD ISP4 SWNODE GRAPH Structure
+ *
+ * amd_camera {
+ * isp4 {
+ * ports {
+ * port@0 {
+ * isp4_mipi1_ep: endpoint {
+ * remote-endpoint = &OMNI5C10_ep;
+ * };
+ * };
+ * };
+ * };
+ *
+ * i2c1 {
+ * clock-frequency = 1 MHz;
+ * OMNI5C10 {
+ * clock-frequency = 24MHz;
+ * ports {
+ * port@0 {
+ * OMNI5C10_ep: endpoint {
+ * bus-type = 4;
+ * data-lanes = <1 2>;
+ * link-frequencies = 900MHz;
+ * remote-endpoint = &isp4_mipi1;
+ * };
+ * };
+ * };
+ * };
+ * };
+ * };
+ *
+ */
+static const struct software_node *amd_isp4_nodes[] = {
+ &amd_camera_node,
+ &isp4_node,
+ &isp4_ports,
+ &isp4_port_node,
+ &isp4_mipi1_endpoint_node,
+ &i2c1_node,
+ &ov05c10_camera_node,
+ &ov05c10_ports,
+ &ov05c10_port_node,
+ &ov05c10_endpoint_node,
+ NULL
+};
+
+/* OV05C10 specific AMD ISP platform configuration */
+static const struct amdisp_platform_info ov05c10_platform_config = {
+ .board_info = {
+ .dev_name = "ov05c10",
+ I2C_BOARD_INFO("ov05c10", AMDISP_OV05C10_I2C_ADDR),
+ },
+ .swnodes = amd_isp4_nodes,
+};
+
+static const struct acpi_device_id amdisp_sensor_ids[] = {
+ { AMDISP_OV05C10_HID, (kernel_ulong_t)&ov05c10_platform_config },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, amdisp_sensor_ids);
+
+static inline bool is_isp_i2c_adapter(struct i2c_adapter *adap)
+{
+ return !strcmp(adap->name, AMDISP_I2C_ADAP_NAME);
+}
+
+static void instantiate_isp_i2c_client(struct amdisp_platform *isp4_platform,
+ struct i2c_adapter *adap)
+{
+ struct i2c_board_info *info = &isp4_platform->board_info;
+ struct i2c_client *i2c_dev;
+
+ guard(mutex)(&isp4_platform->lock);
+
+ if (isp4_platform->i2c_dev)
+ return;
+
+ i2c_dev = i2c_new_client_device(adap, info);
+ if (IS_ERR(i2c_dev)) {
+ dev_err(&adap->dev, "error %pe registering isp i2c_client\n", i2c_dev);
+ return;
+ }
+ isp4_platform->i2c_dev = i2c_dev;
+}
+
+static int isp_i2c_bus_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct amdisp_platform *isp4_platform =
+ container_of(nb, struct amdisp_platform, i2c_nb);
+ struct device *dev = data;
+ struct i2c_client *client;
+ struct i2c_adapter *adap;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ adap = i2c_verify_adapter(dev);
+ if (!adap)
+ break;
+ if (is_isp_i2c_adapter(adap))
+ instantiate_isp_i2c_client(isp4_platform, adap);
+ break;
+ case BUS_NOTIFY_REMOVED_DEVICE:
+ client = i2c_verify_client(dev);
+ if (!client)
+ break;
+
+ scoped_guard(mutex, &isp4_platform->lock) {
+ if (isp4_platform->i2c_dev == client) {
+ dev_dbg(&client->adapter->dev, "amdisp i2c_client removed\n");
+ isp4_platform->i2c_dev = NULL;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct amdisp_platform *prepare_amdisp_platform(struct device *dev,
+ const struct amdisp_platform_info *src)
+{
+ struct amdisp_platform *isp4_platform;
+ int ret;
+
+ isp4_platform = devm_kzalloc(dev, sizeof(*isp4_platform), GFP_KERNEL);
+ if (!isp4_platform)
+ return ERR_PTR(-ENOMEM);
+
+ ret = devm_mutex_init(dev, &isp4_platform->lock);
+ if (ret)
+ return ERR_PTR(ret);
+
+ isp4_platform->board_info.dev_name = src->board_info.dev_name;
+ strscpy(isp4_platform->board_info.type, src->board_info.type);
+ isp4_platform->board_info.addr = src->board_info.addr;
+ isp4_platform->pinfo = src;
+
+ ret = software_node_register_node_group(src->swnodes);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /* initialize ov05c10_camera_node */
+ isp4_platform->board_info.swnode = src->swnodes[6];
+
+ return isp4_platform;
+}
+
+static int try_to_instantiate_i2c_client(struct device *dev, void *data)
+{
+ struct i2c_adapter *adap = i2c_verify_adapter(dev);
+ struct amdisp_platform *isp4_platform = data;
+
+ if (!isp4_platform || !adap)
+ return 0;
+ if (!adap->owner)
+ return 0;
+
+ if (is_isp_i2c_adapter(adap))
+ instantiate_isp_i2c_client(isp4_platform, adap);
+
+ return 0;
+}
+
+static int amd_isp_probe(struct platform_device *pdev)
+{
+ const struct amdisp_platform_info *pinfo;
+ struct amdisp_platform *isp4_platform;
+ struct acpi_device *adev;
+ int ret;
+
+ pinfo = device_get_match_data(&pdev->dev);
+ if (!pinfo)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "failed to get valid ACPI data\n");
+
+ isp4_platform = prepare_amdisp_platform(&pdev->dev, pinfo);
+ if (IS_ERR(isp4_platform))
+ return dev_err_probe(&pdev->dev, PTR_ERR(isp4_platform),
+ "failed to prepare AMD ISP platform fwnode\n");
+
+ isp4_platform->i2c_nb.notifier_call = isp_i2c_bus_notify;
+ ret = bus_register_notifier(&i2c_bus_type, &isp4_platform->i2c_nb);
+ if (ret)
+ goto error_unregister_sw_node;
+
+ adev = ACPI_COMPANION(&pdev->dev);
+ /* initialize root amd_camera_node */
+ adev->driver_data = (void *)pinfo->swnodes[0];
+
+ /* check if adapter is already registered and create i2c client instance */
+ i2c_for_each_dev(isp4_platform, try_to_instantiate_i2c_client);
+
+ platform_set_drvdata(pdev, isp4_platform);
+ return 0;
+
+error_unregister_sw_node:
+ software_node_unregister_node_group(isp4_platform->pinfo->swnodes);
+ return ret;
+}
+
+static void amd_isp_remove(struct platform_device *pdev)
+{
+ struct amdisp_platform *isp4_platform = platform_get_drvdata(pdev);
+
+ bus_unregister_notifier(&i2c_bus_type, &isp4_platform->i2c_nb);
+ i2c_unregister_device(isp4_platform->i2c_dev);
+ software_node_unregister_node_group(isp4_platform->pinfo->swnodes);
+}
+
+static struct platform_driver amd_isp_platform_driver = {
+ .driver = {
+ .name = AMD_ISP_PLAT_DRV_NAME,
+ .acpi_match_table = amdisp_sensor_ids,
+ },
+ .probe = amd_isp_probe,
+ .remove = amd_isp_remove,
+};
+
+module_platform_driver(amd_isp_platform_driver);
+
+MODULE_AUTHOR("Benjamin Chan <benjamin.chan@amd.com>");
+MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
+MODULE_DESCRIPTION("AMD ISP4 Platform Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/amd/hfi/Kconfig b/drivers/platform/x86/amd/hfi/Kconfig
new file mode 100644
index 000000000000..fecef6848023
--- /dev/null
+++ b/drivers/platform/x86/amd/hfi/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# AMD Hardware Feedback Interface Driver
+#
+
+config AMD_HFI
+ bool "AMD Hetero Core Hardware Feedback Driver"
+ depends on ACPI
+ depends on CPU_SUP_AMD
+ depends on SCHED_MC_PRIO
+ help
+ Select this option to enable the AMD Heterogeneous Core Hardware
+ Feedback Interface. If selected, hardware provides runtime thread
+ classification guidance to the operating system on the performance and
+ energy efficiency capabilities of each heterogeneous CPU core. These
+ capabilities may vary due to the inherent differences in the core types
+ and can also change as a result of variations in the operating
+ conditions of the system such as power and thermal limits.
diff --git a/drivers/platform/x86/amd/hfi/Makefile b/drivers/platform/x86/amd/hfi/Makefile
new file mode 100644
index 000000000000..672c6ac106e9
--- /dev/null
+++ b/drivers/platform/x86/amd/hfi/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# AMD Hardware Feedback Interface Driver
+#
+
+obj-$(CONFIG_AMD_HFI) += amd_hfi.o
+amd_hfi-objs := hfi.o
diff --git a/drivers/platform/x86/amd/hfi/hfi.c b/drivers/platform/x86/amd/hfi/hfi.c
new file mode 100644
index 000000000000..83863a5e0fbc
--- /dev/null
+++ b/drivers/platform/x86/amd/hfi/hfi.c
@@ -0,0 +1,546 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Hardware Feedback Interface Driver
+ *
+ * Copyright (C) 2025 Advanced Micro Devices, Inc. All Rights Reserved.
+ *
+ * Authors: Perry Yuan <Perry.Yuan@amd.com>
+ * Mario Limonciello <mario.limonciello@amd.com>
+ */
+
+#define pr_fmt(fmt) "amd-hfi: " fmt
+
+#include <linux/acpi.h>
+#include <linux/cpu.h>
+#include <linux/debugfs.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/percpu-defs.h>
+#include <linux/platform_device.h>
+#include <linux/smp.h>
+#include <linux/topology.h>
+#include <linux/workqueue.h>
+
+#include <asm/cpu_device_id.h>
+
+#include <acpi/pcc.h>
+#include <acpi/cppc_acpi.h>
+
+#define AMD_HFI_DRIVER "amd_hfi"
+#define AMD_HFI_MAILBOX_COUNT 1
+#define AMD_HETERO_RANKING_TABLE_VER 2
+
+#define AMD_HETERO_CPUID_27 0x80000027
+
+static struct platform_device *device;
+
+/**
+ * struct amd_shmem_info - Shared memory table for AMD HFI
+ *
+ * @header: The PCCT table header including signature, length flags and command.
+ * @version_number: Version number of the table
+ * @n_logical_processors: Number of logical processors
+ * @n_capabilities: Number of ranking dimensions (performance, efficiency, etc)
+ * @table_update_context: Command being sent over the subspace
+ * @n_bitmaps: Number of 32-bit bitmaps to enumerate all the APIC IDs
+ * This is based on the maximum APIC ID enumerated in the system
+ * @reserved: 24 bit spare
+ * @table_data: Bit Map(s) of enabled logical processors
+ * Followed by the ranking data for each logical processor
+ */
+struct amd_shmem_info {
+ struct acpi_pcct_ext_pcc_shared_memory header;
+ u32 version_number :8,
+ n_logical_processors :8,
+ n_capabilities :8,
+ table_update_context :8;
+ u32 n_bitmaps :8,
+ reserved :24;
+ u32 table_data[];
+};
+
+struct amd_hfi_data {
+ const char *name;
+ struct device *dev;
+
+ /* PCCT table related */
+ struct pcc_mbox_chan *pcc_chan;
+ void __iomem *pcc_comm_addr;
+ struct acpi_subtable_header *pcct_entry;
+ struct amd_shmem_info *shmem;
+
+ struct dentry *dbgfs_dir;
+};
+
+/**
+ * struct amd_hfi_classes - HFI class capabilities per CPU
+ * @perf: Performance capability
+ * @eff: Power efficiency capability
+ *
+ * Capabilities of a logical processor in the ranking table. These capabilities
+ * are unitless and specific to each HFI class.
+ */
+struct amd_hfi_classes {
+ u32 perf;
+ u32 eff;
+};
+
+/**
+ * struct amd_hfi_cpuinfo - HFI workload class info per CPU
+ * @cpu: CPU index
+ * @apic_id: APIC id of the current CPU
+ * @class_index: workload class ID index
+ * @nr_class: max number of workload class supported
+ * @ipcc_scores: ipcc scores for each class
+ * @amd_hfi_classes: current CPU workload class ranking data
+ *
+ * Parameters of a logical processor linked with hardware feedback class.
+ */
+struct amd_hfi_cpuinfo {
+ int cpu;
+ u32 apic_id;
+ s16 class_index;
+ u8 nr_class;
+ int *ipcc_scores;
+ struct amd_hfi_classes *amd_hfi_classes;
+};
+
+static DEFINE_PER_CPU(struct amd_hfi_cpuinfo, amd_hfi_cpuinfo) = {.class_index = -1};
+
+static DEFINE_MUTEX(hfi_cpuinfo_lock);
+
+static void amd_hfi_sched_itmt_work(struct work_struct *work)
+{
+ sched_set_itmt_support();
+}
+static DECLARE_WORK(sched_amd_hfi_itmt_work, amd_hfi_sched_itmt_work);
+
+static int find_cpu_index_by_apicid(unsigned int target_apicid)
+{
+ int cpu_index;
+
+ for_each_possible_cpu(cpu_index) {
+ struct cpuinfo_x86 *info = &cpu_data(cpu_index);
+
+ if (info->topo.apicid == target_apicid) {
+ pr_debug("match APIC id %u for CPU index: %d\n",
+ info->topo.apicid, cpu_index);
+ return cpu_index;
+ }
+ }
+
+ return -ENODEV;
+}
+
+static int amd_hfi_fill_metadata(struct amd_hfi_data *amd_hfi_data)
+{
+ struct acpi_pcct_ext_pcc_slave *pcct_ext =
+ (struct acpi_pcct_ext_pcc_slave *)amd_hfi_data->pcct_entry;
+ void __iomem *pcc_comm_addr;
+ u32 apic_start = 0;
+
+ pcc_comm_addr = acpi_os_ioremap(amd_hfi_data->pcc_chan->shmem_base_addr,
+ amd_hfi_data->pcc_chan->shmem_size);
+ if (!pcc_comm_addr) {
+ dev_err(amd_hfi_data->dev, "failed to ioremap PCC common region mem\n");
+ return -ENOMEM;
+ }
+
+ memcpy_fromio(amd_hfi_data->shmem, pcc_comm_addr, pcct_ext->length);
+ iounmap(pcc_comm_addr);
+
+ if (amd_hfi_data->shmem->header.signature != PCC_SIGNATURE) {
+ dev_err(amd_hfi_data->dev, "invalid signature in shared memory\n");
+ return -EINVAL;
+ }
+ if (amd_hfi_data->shmem->version_number != AMD_HETERO_RANKING_TABLE_VER) {
+ dev_err(amd_hfi_data->dev, "invalid version %d\n",
+ amd_hfi_data->shmem->version_number);
+ return -EINVAL;
+ }
+
+ for (unsigned int i = 0; i < amd_hfi_data->shmem->n_bitmaps; i++) {
+ u32 bitmap = amd_hfi_data->shmem->table_data[i];
+
+ for (unsigned int j = 0; j < BITS_PER_TYPE(u32); j++) {
+ u32 apic_id = i * BITS_PER_TYPE(u32) + j;
+ struct amd_hfi_cpuinfo *info;
+ int cpu_index, apic_index;
+
+ if (!(bitmap & BIT(j)))
+ continue;
+
+ cpu_index = find_cpu_index_by_apicid(apic_id);
+ if (cpu_index < 0) {
+ dev_warn(amd_hfi_data->dev, "APIC ID %u not found\n", apic_id);
+ continue;
+ }
+
+ info = per_cpu_ptr(&amd_hfi_cpuinfo, cpu_index);
+ info->apic_id = apic_id;
+
+ /* Fill the ranking data for each logical processor */
+ info = per_cpu_ptr(&amd_hfi_cpuinfo, cpu_index);
+ apic_index = apic_start * info->nr_class * 2;
+ for (unsigned int k = 0; k < info->nr_class; k++) {
+ u32 *table = amd_hfi_data->shmem->table_data +
+ amd_hfi_data->shmem->n_bitmaps +
+ i * info->nr_class;
+
+ info->amd_hfi_classes[k].eff = table[apic_index + 2 * k];
+ info->amd_hfi_classes[k].perf = table[apic_index + 2 * k + 1];
+ }
+ apic_start++;
+ }
+ }
+
+ return 0;
+}
+
+static int amd_hfi_alloc_class_data(struct platform_device *pdev)
+{
+ struct amd_hfi_cpuinfo *hfi_cpuinfo;
+ struct device *dev = &pdev->dev;
+ u32 nr_class_id;
+ int idx;
+
+ nr_class_id = cpuid_eax(AMD_HETERO_CPUID_27);
+ if (nr_class_id > 255) {
+ dev_err(dev, "number of supported classes too large: %d\n",
+ nr_class_id);
+ return -EINVAL;
+ }
+
+ for_each_possible_cpu(idx) {
+ struct amd_hfi_classes *classes;
+ int *ipcc_scores;
+
+ classes = devm_kcalloc(dev,
+ nr_class_id,
+ sizeof(struct amd_hfi_classes),
+ GFP_KERNEL);
+ if (!classes)
+ return -ENOMEM;
+ ipcc_scores = devm_kcalloc(dev, nr_class_id, sizeof(int), GFP_KERNEL);
+ if (!ipcc_scores)
+ return -ENOMEM;
+ hfi_cpuinfo = per_cpu_ptr(&amd_hfi_cpuinfo, idx);
+ hfi_cpuinfo->amd_hfi_classes = classes;
+ hfi_cpuinfo->ipcc_scores = ipcc_scores;
+ hfi_cpuinfo->nr_class = nr_class_id;
+ }
+
+ return 0;
+}
+
+static void amd_hfi_remove(struct platform_device *pdev)
+{
+ struct amd_hfi_data *dev = platform_get_drvdata(pdev);
+
+ debugfs_remove_recursive(dev->dbgfs_dir);
+}
+
+static int amd_set_hfi_ipcc_score(struct amd_hfi_cpuinfo *hfi_cpuinfo, int cpu)
+{
+ for (int i = 0; i < hfi_cpuinfo->nr_class; i++)
+ WRITE_ONCE(hfi_cpuinfo->ipcc_scores[i],
+ hfi_cpuinfo->amd_hfi_classes[i].perf);
+
+ sched_set_itmt_core_prio(hfi_cpuinfo->ipcc_scores[0], cpu);
+
+ return 0;
+}
+
+static int amd_hfi_set_state(unsigned int cpu, bool state)
+{
+ int ret;
+
+ ret = wrmsrq_on_cpu(cpu, MSR_AMD_WORKLOAD_CLASS_CONFIG, state ? 1 : 0);
+ if (ret)
+ return ret;
+
+ return wrmsrq_on_cpu(cpu, MSR_AMD_WORKLOAD_HRST, 0x1);
+}
+
+/**
+ * amd_hfi_online() - Enable workload classification on @cpu
+ * @cpu: CPU in which the workload classification will be enabled
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int amd_hfi_online(unsigned int cpu)
+{
+ struct amd_hfi_cpuinfo *hfi_info = per_cpu_ptr(&amd_hfi_cpuinfo, cpu);
+ struct amd_hfi_classes *hfi_classes;
+ int ret;
+
+ if (WARN_ON_ONCE(!hfi_info))
+ return -EINVAL;
+
+ /*
+ * Check if @cpu as an associated, initialized and ranking data must
+ * be filled.
+ */
+ hfi_classes = hfi_info->amd_hfi_classes;
+ if (!hfi_classes)
+ return -EINVAL;
+
+ guard(mutex)(&hfi_cpuinfo_lock);
+
+ ret = amd_hfi_set_state(cpu, true);
+ if (ret)
+ pr_err("WCT enable failed for CPU %u\n", cpu);
+
+ return ret;
+}
+
+/**
+ * amd_hfi_offline() - Disable workload classification on @cpu
+ * @cpu: CPU in which the workload classification will be disabled
+ *
+ * Remove @cpu from those covered by its HFI instance.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int amd_hfi_offline(unsigned int cpu)
+{
+ struct amd_hfi_cpuinfo *hfi_info = &per_cpu(amd_hfi_cpuinfo, cpu);
+ int ret;
+
+ if (WARN_ON_ONCE(!hfi_info))
+ return -EINVAL;
+
+ guard(mutex)(&hfi_cpuinfo_lock);
+
+ ret = amd_hfi_set_state(cpu, false);
+ if (ret)
+ pr_err("WCT disable failed for CPU %u\n", cpu);
+
+ return ret;
+}
+
+static int update_hfi_ipcc_scores(void)
+{
+ int cpu;
+ int ret;
+
+ for_each_possible_cpu(cpu) {
+ struct amd_hfi_cpuinfo *hfi_cpuinfo = per_cpu_ptr(&amd_hfi_cpuinfo, cpu);
+
+ ret = amd_set_hfi_ipcc_score(hfi_cpuinfo, cpu);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int amd_hfi_metadata_parser(struct platform_device *pdev,
+ struct amd_hfi_data *amd_hfi_data)
+{
+ struct acpi_pcct_ext_pcc_slave *pcct_ext;
+ struct acpi_subtable_header *pcct_entry;
+ struct mbox_chan *pcc_mbox_channels;
+ struct acpi_table_header *pcct_tbl;
+ struct pcc_mbox_chan *pcc_chan;
+ acpi_status status;
+ int ret;
+
+ pcc_mbox_channels = devm_kcalloc(&pdev->dev, AMD_HFI_MAILBOX_COUNT,
+ sizeof(*pcc_mbox_channels), GFP_KERNEL);
+ if (!pcc_mbox_channels)
+ return -ENOMEM;
+
+ pcc_chan = devm_kcalloc(&pdev->dev, AMD_HFI_MAILBOX_COUNT,
+ sizeof(*pcc_chan), GFP_KERNEL);
+ if (!pcc_chan)
+ return -ENOMEM;
+
+ status = acpi_get_table(ACPI_SIG_PCCT, 0, &pcct_tbl);
+ if (ACPI_FAILURE(status) || !pcct_tbl)
+ return -ENODEV;
+
+ /* get pointer to the first PCC subspace entry */
+ pcct_entry = (struct acpi_subtable_header *) (
+ (unsigned long)pcct_tbl + sizeof(struct acpi_table_pcct));
+
+ pcc_chan->mchan = &pcc_mbox_channels[0];
+
+ amd_hfi_data->pcc_chan = pcc_chan;
+ amd_hfi_data->pcct_entry = pcct_entry;
+ pcct_ext = (struct acpi_pcct_ext_pcc_slave *)pcct_entry;
+
+ if (pcct_ext->length <= 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ amd_hfi_data->shmem = devm_kzalloc(amd_hfi_data->dev, pcct_ext->length, GFP_KERNEL);
+ if (!amd_hfi_data->shmem) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ pcc_chan->shmem_base_addr = pcct_ext->base_address;
+ pcc_chan->shmem_size = pcct_ext->length;
+
+ /* parse the shared memory info from the PCCT table */
+ ret = amd_hfi_fill_metadata(amd_hfi_data);
+
+out:
+ /* Don't leak any ACPI memory */
+ acpi_put_table(pcct_tbl);
+
+ return ret;
+}
+
+static int class_capabilities_show(struct seq_file *s, void *unused)
+{
+ u32 cpu, idx;
+
+ seq_puts(s, "CPU #\tWLC\tPerf\tEff\n");
+ for_each_possible_cpu(cpu) {
+ struct amd_hfi_cpuinfo *hfi_cpuinfo = per_cpu_ptr(&amd_hfi_cpuinfo, cpu);
+
+ seq_printf(s, "%d", cpu);
+ for (idx = 0; idx < hfi_cpuinfo->nr_class; idx++) {
+ seq_printf(s, "\t%u\t%u\t%u\n", idx,
+ hfi_cpuinfo->amd_hfi_classes[idx].perf,
+ hfi_cpuinfo->amd_hfi_classes[idx].eff);
+ }
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(class_capabilities);
+
+static int amd_hfi_pm_resume(struct device *dev)
+{
+ int ret, cpu;
+
+ for_each_online_cpu(cpu) {
+ ret = amd_hfi_set_state(cpu, true);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable workload class config: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int amd_hfi_pm_suspend(struct device *dev)
+{
+ int ret, cpu;
+
+ for_each_online_cpu(cpu) {
+ ret = amd_hfi_set_state(cpu, false);
+ if (ret < 0) {
+ dev_err(dev, "failed to disable workload class config: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(amd_hfi_pm_ops, amd_hfi_pm_suspend, amd_hfi_pm_resume);
+
+static const struct acpi_device_id amd_hfi_platform_match[] = {
+ {"AMDI0104", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, amd_hfi_platform_match);
+
+static int amd_hfi_probe(struct platform_device *pdev)
+{
+ struct amd_hfi_data *amd_hfi_data;
+ int ret;
+
+ if (!acpi_match_device(amd_hfi_platform_match, &pdev->dev))
+ return -ENODEV;
+
+ amd_hfi_data = devm_kzalloc(&pdev->dev, sizeof(*amd_hfi_data), GFP_KERNEL);
+ if (!amd_hfi_data)
+ return -ENOMEM;
+
+ amd_hfi_data->dev = &pdev->dev;
+ platform_set_drvdata(pdev, amd_hfi_data);
+
+ ret = amd_hfi_alloc_class_data(pdev);
+ if (ret)
+ return ret;
+
+ ret = amd_hfi_metadata_parser(pdev, amd_hfi_data);
+ if (ret)
+ return ret;
+
+ ret = update_hfi_ipcc_scores();
+ if (ret)
+ return ret;
+
+ /*
+ * Tasks will already be running at the time this happens. This is
+ * OK because rankings will be adjusted by the callbacks.
+ */
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/amd_hfi:online",
+ amd_hfi_online, amd_hfi_offline);
+ if (ret < 0)
+ return ret;
+
+ schedule_work(&sched_amd_hfi_itmt_work);
+
+ amd_hfi_data->dbgfs_dir = debugfs_create_dir("amd_hfi", arch_debugfs_dir);
+ debugfs_create_file("class_capabilities", 0644, amd_hfi_data->dbgfs_dir, pdev,
+ &class_capabilities_fops);
+
+ return 0;
+}
+
+static struct platform_driver amd_hfi_driver = {
+ .driver = {
+ .name = AMD_HFI_DRIVER,
+ .pm = &amd_hfi_pm_ops,
+ .acpi_match_table = ACPI_PTR(amd_hfi_platform_match),
+ },
+ .probe = amd_hfi_probe,
+ .remove = amd_hfi_remove,
+};
+
+static int __init amd_hfi_init(void)
+{
+ int ret;
+
+ if (acpi_disabled ||
+ !cpu_feature_enabled(X86_FEATURE_AMD_HTR_CORES) ||
+ !cpu_feature_enabled(X86_FEATURE_AMD_WORKLOAD_CLASS))
+ return -ENODEV;
+
+ device = platform_device_register_simple(AMD_HFI_DRIVER, -1, NULL, 0);
+ if (IS_ERR(device)) {
+ pr_err("unable to register HFI platform device\n");
+ return PTR_ERR(device);
+ }
+
+ ret = platform_driver_register(&amd_hfi_driver);
+ if (ret)
+ pr_err("failed to register HFI driver\n");
+
+ return ret;
+}
+
+static __exit void amd_hfi_exit(void)
+{
+ platform_driver_unregister(&amd_hfi_driver);
+ platform_device_unregister(device);
+}
+module_init(amd_hfi_init);
+module_exit(amd_hfi_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AMD Hardware Feedback Interface Driver");
diff --git a/drivers/platform/x86/amd/hsmp/Kconfig b/drivers/platform/x86/amd/hsmp/Kconfig
index d6f7a62d55b5..2911120792e8 100644
--- a/drivers/platform/x86/amd/hsmp/Kconfig
+++ b/drivers/platform/x86/amd/hsmp/Kconfig
@@ -12,6 +12,7 @@ menu "AMD HSMP Driver"
config AMD_HSMP_ACPI
tristate "AMD HSMP ACPI device driver"
depends on ACPI
+ depends on HWMON || !HWMON
select AMD_HSMP
help
Host System Management Port (HSMP) interface is a mailbox interface
@@ -29,6 +30,7 @@ config AMD_HSMP_ACPI
config AMD_HSMP_PLAT
tristate "AMD HSMP platform device driver"
+ depends on HWMON || !HWMON
select AMD_HSMP
help
Host System Management Port (HSMP) interface is a mailbox interface
diff --git a/drivers/platform/x86/amd/hsmp/Makefile b/drivers/platform/x86/amd/hsmp/Makefile
index 0759bbcd13f6..ce8342e71f50 100644
--- a/drivers/platform/x86/amd/hsmp/Makefile
+++ b/drivers/platform/x86/amd/hsmp/Makefile
@@ -6,6 +6,7 @@
obj-$(CONFIG_AMD_HSMP) += hsmp_common.o
hsmp_common-y := hsmp.o
+hsmp_common-$(CONFIG_HWMON) += hwmon.o
obj-$(CONFIG_AMD_HSMP_PLAT) += amd_hsmp.o
amd_hsmp-y := plat.o
obj-$(CONFIG_AMD_HSMP_ACPI) += hsmp_acpi.o
diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c
index c1eccb3c80c5..97ed71593bdf 100644
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -9,9 +9,12 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <asm/amd_hsmp.h>
+#include <asm/amd/hsmp.h>
#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/ioport.h>
@@ -19,17 +22,14 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
+#include <linux/topology.h>
#include <linux/uuid.h>
#include <uapi/asm-generic/errno-base.h>
-#include <asm/amd_node.h>
-
#include "hsmp.h"
-#define DRIVER_NAME "amd_hsmp"
-#define DRIVER_VERSION "2.3"
-#define ACPI_HSMP_DEVICE_HID "AMDI0097"
+#define DRIVER_NAME "hsmp_acpi"
/* These are the strings specified in ACPI table */
#define MSG_IDOFF_STR "MsgIdOffset"
@@ -38,6 +38,11 @@
static struct hsmp_plat_device *hsmp_pdev;
+struct hsmp_sys_attr {
+ struct device_attribute dattr;
+ u32 msg_id;
+};
+
static int amd_hsmp_acpi_rdwr(struct hsmp_socket *sock, u32 offset,
u32 *value, bool write)
{
@@ -245,6 +250,215 @@ static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
return 0;
}
+static umode_t hsmp_is_sock_dev_attr_visible(struct kobject *kobj,
+ struct attribute *attr, int id)
+{
+ return attr->mode;
+}
+
+#define to_hsmp_sys_attr(_attr) container_of(_attr, struct hsmp_sys_attr, dattr)
+
+static ssize_t hsmp_msg_resp32_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", data);
+}
+
+#define DDR_MAX_BW_MASK GENMASK(31, 20)
+#define DDR_UTIL_BW_MASK GENMASK(19, 8)
+#define DDR_UTIL_BW_PERC_MASK GENMASK(7, 0)
+#define FW_VER_MAJOR_MASK GENMASK(23, 16)
+#define FW_VER_MINOR_MASK GENMASK(15, 8)
+#define FW_VER_DEBUG_MASK GENMASK(7, 0)
+#define FMAX_MASK GENMASK(31, 16)
+#define FMIN_MASK GENMASK(15, 0)
+#define FREQ_LIMIT_MASK GENMASK(31, 16)
+#define FREQ_SRC_IND_MASK GENMASK(15, 0)
+
+static ssize_t hsmp_ddr_max_bw_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu\n", FIELD_GET(DDR_MAX_BW_MASK, data));
+}
+
+static ssize_t hsmp_ddr_util_bw_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu\n", FIELD_GET(DDR_UTIL_BW_MASK, data));
+}
+
+static ssize_t hsmp_ddr_util_bw_perc_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu\n", FIELD_GET(DDR_UTIL_BW_PERC_MASK, data));
+}
+
+static ssize_t hsmp_msg_fw_ver_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu.%lu.%lu\n",
+ FIELD_GET(FW_VER_MAJOR_MASK, data),
+ FIELD_GET(FW_VER_MINOR_MASK, data),
+ FIELD_GET(FW_VER_DEBUG_MASK, data));
+}
+
+static ssize_t hsmp_fclk_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data[2];
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, data, 2);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", data[0]);
+}
+
+static ssize_t hsmp_mclk_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data[2];
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, data, 2);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", data[1]);
+}
+
+static ssize_t hsmp_clk_fmax_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu\n", FIELD_GET(FMAX_MASK, data));
+}
+
+static ssize_t hsmp_clk_fmin_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu\n", FIELD_GET(FMIN_MASK, data));
+}
+
+static ssize_t hsmp_freq_limit_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%lu\n", FIELD_GET(FREQ_LIMIT_MASK, data));
+}
+
+static const char * const freqlimit_srcnames[] = {
+ "cHTC-Active",
+ "PROCHOT",
+ "TDC limit",
+ "PPT Limit",
+ "OPN Max",
+ "Reliability Limit",
+ "APML Agent",
+ "HSMP Agent",
+};
+
+static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+ unsigned int index;
+ int len = 0;
+ u16 src_ind;
+ u32 data;
+ int ret;
+
+ ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1);
+ if (ret)
+ return ret;
+
+ src_ind = FIELD_GET(FREQ_SRC_IND_MASK, data);
+ for (index = 0; index < ARRAY_SIZE(freqlimit_srcnames); index++) {
+ if (!src_ind)
+ break;
+ if (src_ind & 1)
+ len += sysfs_emit_at(buf, len, "%s\n", freqlimit_srcnames[index]);
+ src_ind >>= 1;
+ }
+ return len;
+}
+
static int init_acpi(struct device *dev)
{
u16 sock_ind;
@@ -280,15 +494,21 @@ static int init_acpi(struct device *dev)
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
ret = hsmp_get_tbl_dram_base(sock_ind);
if (ret)
- dev_err(dev, "Failed to init metric table\n");
+ dev_info(dev, "Failed to init metric table\n");
}
- return ret;
+ ret = hsmp_create_sensor(dev, sock_ind);
+ if (ret)
+ dev_info(dev, "Failed to register HSMP sensors with hwmon\n");
+
+ dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]);
+
+ return 0;
}
static const struct bin_attribute hsmp_metric_tbl_attr = {
.attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444},
- .read_new = hsmp_metric_tbl_acpi_read,
+ .read = hsmp_metric_tbl_acpi_read,
.size = sizeof(struct hsmp_metric_table),
};
@@ -297,9 +517,52 @@ static const struct bin_attribute *hsmp_attr_list[] = {
NULL
};
+#define HSMP_DEV_ATTR(_name, _msg_id, _show, _mode) \
+static struct hsmp_sys_attr hattr_##_name = { \
+ .dattr = __ATTR(_name, _mode, _show, NULL), \
+ .msg_id = _msg_id, \
+}
+
+HSMP_DEV_ATTR(c0_residency_input, HSMP_GET_C0_PERCENT, hsmp_msg_resp32_show, 0444);
+HSMP_DEV_ATTR(prochot_status, HSMP_GET_PROC_HOT, hsmp_msg_resp32_show, 0444);
+HSMP_DEV_ATTR(smu_fw_version, HSMP_GET_SMU_VER, hsmp_msg_fw_ver_show, 0444);
+HSMP_DEV_ATTR(protocol_version, HSMP_GET_PROTO_VER, hsmp_msg_resp32_show, 0444);
+HSMP_DEV_ATTR(cclk_freq_limit_input, HSMP_GET_CCLK_THROTTLE_LIMIT, hsmp_msg_resp32_show, 0444);
+HSMP_DEV_ATTR(ddr_max_bw, HSMP_GET_DDR_BANDWIDTH, hsmp_ddr_max_bw_show, 0444);
+HSMP_DEV_ATTR(ddr_utilised_bw_input, HSMP_GET_DDR_BANDWIDTH, hsmp_ddr_util_bw_show, 0444);
+HSMP_DEV_ATTR(ddr_utilised_bw_perc_input, HSMP_GET_DDR_BANDWIDTH, hsmp_ddr_util_bw_perc_show, 0444);
+HSMP_DEV_ATTR(fclk_input, HSMP_GET_FCLK_MCLK, hsmp_fclk_show, 0444);
+HSMP_DEV_ATTR(mclk_input, HSMP_GET_FCLK_MCLK, hsmp_mclk_show, 0444);
+HSMP_DEV_ATTR(clk_fmax, HSMP_GET_SOCKET_FMAX_FMIN, hsmp_clk_fmax_show, 0444);
+HSMP_DEV_ATTR(clk_fmin, HSMP_GET_SOCKET_FMAX_FMIN, hsmp_clk_fmin_show, 0444);
+HSMP_DEV_ATTR(pwr_current_active_freq_limit, HSMP_GET_SOCKET_FREQ_LIMIT,
+ hsmp_freq_limit_show, 0444);
+HSMP_DEV_ATTR(pwr_current_active_freq_limit_source, HSMP_GET_SOCKET_FREQ_LIMIT,
+ hsmp_freq_limit_source_show, 0444);
+
+static struct attribute *hsmp_dev_attr_list[] = {
+ &hattr_c0_residency_input.dattr.attr,
+ &hattr_prochot_status.dattr.attr,
+ &hattr_smu_fw_version.dattr.attr,
+ &hattr_protocol_version.dattr.attr,
+ &hattr_cclk_freq_limit_input.dattr.attr,
+ &hattr_ddr_max_bw.dattr.attr,
+ &hattr_ddr_utilised_bw_input.dattr.attr,
+ &hattr_ddr_utilised_bw_perc_input.dattr.attr,
+ &hattr_fclk_input.dattr.attr,
+ &hattr_mclk_input.dattr.attr,
+ &hattr_clk_fmax.dattr.attr,
+ &hattr_clk_fmin.dattr.attr,
+ &hattr_pwr_current_active_freq_limit.dattr.attr,
+ &hattr_pwr_current_active_freq_limit_source.dattr.attr,
+ NULL
+};
+
static const struct attribute_group hsmp_attr_grp = {
- .bin_attrs_new = hsmp_attr_list,
+ .bin_attrs = hsmp_attr_list,
+ .attrs = hsmp_dev_attr_list,
.is_bin_visible = hsmp_is_sock_attr_visible,
+ .is_visible = hsmp_is_sock_dev_attr_visible,
};
static const struct attribute_group *hsmp_groups[] = {
@@ -322,9 +585,11 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
return -ENOMEM;
if (!hsmp_pdev->is_probed) {
- hsmp_pdev->num_sockets = amd_num_nodes();
- if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES)
+ hsmp_pdev->num_sockets = topology_max_packages();
+ if (!hsmp_pdev->num_sockets) {
+ dev_err(&pdev->dev, "No CPU sockets detected\n");
return -ENODEV;
+ }
hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
sizeof(*hsmp_pdev->sock),
@@ -341,9 +606,12 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
if (!hsmp_pdev->is_probed) {
ret = hsmp_misc_register(&pdev->dev);
- if (ret)
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register misc device\n");
return ret;
+ }
hsmp_pdev->is_probed = true;
+ dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n");
}
return 0;
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c
index a3ac09a90de4..631ffc0978d1 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -7,7 +7,7 @@
* This file provides a device implementation for HSMP interface
*/
-#include <asm/amd_hsmp.h>
+#include <asm/amd/hsmp.h>
#include <linux/acpi.h>
#include <linux/delay.h>
@@ -32,8 +32,6 @@
#define HSMP_WR true
#define HSMP_RD false
-#define DRIVER_VERSION "2.4"
-
/*
* When same message numbers are used for both GET and SET operation,
* bit:31 indicates whether its SET or GET operation.
@@ -99,7 +97,7 @@ static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *ms
short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP);
timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT);
- while (time_before(jiffies, timeout)) {
+ while (true) {
ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_RD);
if (ret) {
dev_err(sock->dev, "Error %d reading mailbox status\n", ret);
@@ -108,6 +106,10 @@ static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *ms
if (mbox_status != HSMP_STATUS_NOT_READY)
break;
+
+ if (!time_before(jiffies, timeout))
+ break;
+
if (time_before(jiffies, short_sleep))
usleep_range(50, 100);
else
@@ -115,7 +117,7 @@ static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *ms
}
if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) {
- dev_err(sock->dev, "Message ID 0x%X failure : SMU tmeout (status = 0x%X)\n",
+ dev_err(sock->dev, "Message ID 0x%X failure : SMU timeout (status = 0x%X)\n",
msg->msg_id, mbox_status);
return -ETIMEDOUT;
} else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) {
@@ -212,13 +214,7 @@ int hsmp_send_message(struct hsmp_message *msg)
return -ENODEV;
sock = &hsmp_pdev.sock[msg->sock_ind];
- /*
- * The time taken by smu operation to complete is between
- * 10us to 1ms. Sometime it may take more time.
- * In SMP system timeout of 100 millisecs should
- * be enough for the previous thread to finish the operation
- */
- ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));
+ ret = down_interruptible(&sock->hsmp_sem);
if (ret < 0)
return ret;
@@ -230,6 +226,29 @@ int hsmp_send_message(struct hsmp_message *msg)
}
EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP");
+int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args)
+{
+ struct hsmp_message msg = {};
+ unsigned int i;
+ int ret;
+
+ if (!data)
+ return -EINVAL;
+ msg.msg_id = msg_id;
+ msg.sock_ind = sock_ind;
+ msg.response_sz = num_args;
+
+ ret = hsmp_send_message(&msg);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < num_args; i++)
+ data[i] = msg.args[i];
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_msg_get_nargs, "AMD_HSMP");
+
int hsmp_test(u16 sock_ind, u32 value)
{
struct hsmp_message msg = { 0 };
@@ -337,6 +356,11 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
if (!sock || !buf)
return -EINVAL;
+ if (!sock->metric_tbl_addr) {
+ dev_err(sock->dev, "Metrics table address not available\n");
+ return -ENOMEM;
+ }
+
/* Do not support lseek(), also don't allow more than the size of metric table */
if (size != sizeof(struct hsmp_metric_table)) {
dev_err(sock->dev, "Wrong buffer size\n");
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h
index af8b21f821d6..0509a442eaae 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.h
+++ b/drivers/platform/x86/amd/hsmp/hsmp.h
@@ -12,6 +12,8 @@
#include <linux/compiler_types.h>
#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/kconfig.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
#include <linux/semaphore.h>
@@ -23,6 +25,9 @@
#define HSMP_CDEV_NAME "hsmp_cdev"
#define HSMP_DEVNODE_NAME "hsmp"
+#define ACPI_HSMP_DEVICE_HID "AMDI0097"
+
+#define DRIVER_VERSION "2.5"
struct hsmp_mbaddr_info {
u32 base_addr;
@@ -60,4 +65,10 @@ int hsmp_misc_register(struct device *dev);
int hsmp_get_tbl_dram_base(u16 sock_ind);
ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size);
struct hsmp_plat_device *get_hsmp_pdev(void);
+#if IS_ENABLED(CONFIG_HWMON)
+int hsmp_create_sensor(struct device *dev, u16 sock_ind);
+#else
+static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; }
+#endif
+int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args);
#endif /* HSMP_H */
diff --git a/drivers/platform/x86/amd/hsmp/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c
new file mode 100644
index 000000000000..0cc9a742497f
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/hwmon.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD HSMP hwmon support
+ * Copyright (c) 2025, AMD.
+ * All Rights Reserved.
+ *
+ * This file provides hwmon implementation for HSMP interface.
+ */
+
+#include <asm/amd/hsmp.h>
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include "hsmp.h"
+
+#define HSMP_HWMON_NAME "amd_hsmp_hwmon"
+
+static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ u16 sock_ind = (uintptr_t)dev_get_drvdata(dev);
+ struct hsmp_message msg = {};
+
+ if (type != hwmon_power)
+ return -EOPNOTSUPP;
+
+ if (attr != hwmon_power_cap)
+ return -EOPNOTSUPP;
+
+ msg.num_args = 1;
+ msg.args[0] = val / MICROWATT_PER_MILLIWATT;
+ msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT;
+ msg.sock_ind = sock_ind;
+ return hsmp_send_message(&msg);
+}
+
+static int hsmp_hwmon_read(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ u16 sock_ind = (uintptr_t)dev_get_drvdata(dev);
+ struct hsmp_message msg = {};
+ int ret;
+
+ if (type != hwmon_power)
+ return -EOPNOTSUPP;
+
+ msg.sock_ind = sock_ind;
+ msg.response_sz = 1;
+
+ switch (attr) {
+ case hwmon_power_input:
+ msg.msg_id = HSMP_GET_SOCKET_POWER;
+ break;
+ case hwmon_power_cap:
+ msg.msg_id = HSMP_GET_SOCKET_POWER_LIMIT;
+ break;
+ case hwmon_power_cap_max:
+ msg.msg_id = HSMP_GET_SOCKET_POWER_LIMIT_MAX;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = hsmp_send_message(&msg);
+ if (!ret)
+ *val = msg.args[0] * MICROWATT_PER_MILLIWATT;
+
+ return ret;
+}
+
+static umode_t hsmp_hwmon_is_visble(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ if (type != hwmon_power)
+ return 0;
+
+ switch (attr) {
+ case hwmon_power_input:
+ return 0444;
+ case hwmon_power_cap:
+ return 0644;
+ case hwmon_power_cap_max:
+ return 0444;
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_ops hsmp_hwmon_ops = {
+ .read = hsmp_hwmon_read,
+ .is_visible = hsmp_hwmon_is_visble,
+ .write = hsmp_hwmon_write,
+};
+
+static const struct hwmon_channel_info * const hsmp_info[] = {
+ HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX),
+ NULL
+};
+
+static const struct hwmon_chip_info hsmp_chip_info = {
+ .ops = &hsmp_hwmon_ops,
+ .info = hsmp_info,
+};
+
+int hsmp_create_sensor(struct device *dev, u16 sock_ind)
+{
+ struct device *hwmon_dev;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, HSMP_HWMON_NAME,
+ (void *)(uintptr_t)sock_ind,
+ &hsmp_chip_info,
+ NULL);
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+EXPORT_SYMBOL_NS(hsmp_create_sensor, "AMD_HSMP");
diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c
index b9782a078dbd..e07f68575055 100644
--- a/drivers/platform/x86/amd/hsmp/plat.c
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -9,21 +9,23 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <asm/amd_hsmp.h>
+#include <asm/amd/hsmp.h>
+#include <linux/acpi.h>
#include <linux/build_bug.h>
#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/kconfig.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
-#include <asm/amd_node.h>
+#include <asm/amd/node.h>
#include "hsmp.h"
#define DRIVER_NAME "amd_hsmp"
-#define DRIVER_VERSION "2.3"
/*
* To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
@@ -91,7 +93,7 @@ static_assert(MAX_AMD_NUM_NODES == 8);
static const struct bin_attribute attr##index = { \
.attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444}, \
.private = (void *)index, \
- .read_new = hsmp_metric_tbl_plat_read, \
+ .read = hsmp_metric_tbl_plat_read, \
.size = sizeof(struct hsmp_metric_table), \
}; \
static const struct bin_attribute _list[] = { \
@@ -110,7 +112,7 @@ HSMP_BIN_ATTR(7, *sock7_attr_list);
#define HSMP_BIN_ATTR_GRP(index, _list, _name) \
static const struct attribute_group sock##index##_attr_grp = { \
- .bin_attrs_new = _list, \
+ .bin_attrs = _list, \
.is_bin_visible = hsmp_is_sock_attr_visible, \
.name = #_name, \
}
@@ -187,8 +189,13 @@ static int init_platform_device(struct device *dev)
if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
ret = hsmp_get_tbl_dram_base(i);
if (ret)
- dev_err(dev, "Failed to init metric table\n");
+ dev_info(dev, "Failed to init metric table\n");
}
+
+ /* Register with hwmon interface for reporting power */
+ ret = hsmp_create_sensor(dev, i);
+ if (ret)
+ dev_info(dev, "Failed to register HSMP sensors with hwmon\n");
}
return 0;
@@ -210,7 +217,14 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev)
return ret;
}
- return hsmp_misc_register(&pdev->dev);
+ ret = hsmp_misc_register(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register misc device\n");
+ return ret;
+ }
+
+ dev_dbg(&pdev->dev, "AMD HSMP is probed successfully\n");
+ return 0;
}
static void hsmp_pltdrv_remove(struct platform_device *pdev)
@@ -266,7 +280,7 @@ static bool legacy_hsmp_support(void)
}
case 0x1A:
switch (boot_cpu_data.x86_model) {
- case 0x00 ... 0x1F:
+ case 0x00 ... 0x0F:
return true;
default:
return false;
@@ -282,8 +296,16 @@ static int __init hsmp_plt_init(void)
{
int ret = -ENODEV;
+ if (acpi_dev_present(ACPI_HSMP_DEVICE_HID, NULL, -1)) {
+ if (IS_ENABLED(CONFIG_AMD_HSMP_ACPI))
+ pr_debug("HSMP is supported through ACPI on this platform, please use hsmp_acpi.ko\n");
+ else
+ pr_info("HSMP is supported through ACPI on this platform, please enable AMD_HSMP_ACPI config\n");
+ return -ENODEV;
+ }
+
if (!legacy_hsmp_support()) {
- pr_info("HSMP is not supported on Family:%x model:%x\n",
+ pr_info("HSMP interface is either disabled or not supported on family:%x model:%x\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
return ret;
}
@@ -297,8 +319,10 @@ static int __init hsmp_plt_init(void)
* if we have N SMN/DF interfaces that ideally means N sockets
*/
hsmp_pdev->num_sockets = amd_num_nodes();
- if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES)
+ if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES) {
+ pr_err("Wrong number of sockets\n");
return ret;
+ }
ret = platform_driver_register(&amd_hsmp_driver);
if (ret)
diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c
index c005f00988f7..753d630f3283 100644
--- a/drivers/platform/x86/amd/pmc/mp1_stb.c
+++ b/drivers/platform/x86/amd/pmc/mp1_stb.c
@@ -11,7 +11,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <asm/amd_nb.h>
+#include <asm/amd/nb.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
@@ -141,7 +141,7 @@ static int amd_stb_handle_efr(struct file *filp)
u32 fsize;
fsize = dev->dram_size - S2D_RSVD_RAM_SPACE;
- stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
+ stb_data_arr = kmalloc_flex(*stb_data_arr, data, fsize);
if (!stb_data_arr)
return -ENOMEM;
@@ -189,7 +189,7 @@ static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
}
fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX);
- stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
+ stb_data_arr = kmalloc_flex(*stb_data_arr, data, fsize);
if (!stb_data_arr)
return -ENOMEM;
diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c
index b4f49720c87f..24506e342943 100644
--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c
+++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
@@ -11,6 +11,7 @@
#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/ioport.h>
+#include <linux/platform_data/x86/amd-fch.h>
#include "pmc.h"
@@ -20,17 +21,22 @@ struct quirk_entry {
};
static struct quirk_entry quirk_s2idle_bug = {
- .s2idle_bug_mmio = 0xfed80380,
+ .s2idle_bug_mmio = FCH_PM_BASE + FCH_PM_SCRATCH,
};
static struct quirk_entry quirk_spurious_8042 = {
.spurious_8042 = true,
};
+static struct quirk_entry quirk_s2idle_spurious_8042 = {
+ .s2idle_bug_mmio = FCH_PM_BASE + FCH_PM_SCRATCH,
+ .spurious_8042 = true,
+};
+
static const struct dmi_system_id fwbug_list[] = {
{
.ident = "L14 Gen2 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20X5"),
@@ -38,7 +44,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "T14s Gen2 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20XF"),
@@ -46,7 +52,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "X13 Gen2 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20XH"),
@@ -54,7 +60,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "T14 Gen2 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20XK"),
@@ -62,7 +68,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "T14 Gen1 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20UD"),
@@ -70,7 +76,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "T14 Gen1 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20UE"),
@@ -78,7 +84,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "T14s Gen1 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20UH"),
@@ -86,7 +92,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "T14s Gen1 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20UJ"),
@@ -94,7 +100,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "P14s Gen1 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20Y1"),
@@ -102,7 +108,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "P14s Gen2 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21A0"),
@@ -110,12 +116,20 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "P14s Gen2 AMD",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "21A1"),
}
},
+ {
+ .ident = "ROG Xbox Ally RC73YA",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "RC73YA"),
+ }
+ },
/* https://bugzilla.kernel.org/show_bug.cgi?id=218024 */
{
.ident = "V14 G4 AMN",
@@ -151,7 +165,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "IdeaPad 1 14AMN7",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "82VF"),
@@ -159,7 +173,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "IdeaPad 1 15AMN7",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "82VG"),
@@ -167,7 +181,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "IdeaPad 1 15AMN7",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "82X5"),
@@ -175,7 +189,7 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "IdeaPad Slim 3 14AMN8",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "82XN"),
@@ -183,16 +197,51 @@ static const struct dmi_system_id fwbug_list[] = {
},
{
.ident = "IdeaPad Slim 3 15AMN8",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "82XQ"),
}
},
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=221273 */
+ {
+ .ident = "Thinkpad L14 Gen3",
+ .driver_data = &quirk_s2idle_bug,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "21C6"),
+ }
+ },
+ /* https://gitlab.freedesktop.org/drm/amd/-/issues/4434 */
+ {
+ .ident = "Lenovo Yoga 6 13ALC6",
+ .driver_data = &quirk_s2idle_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "82ND"),
+ }
+ },
+ /* https://gitlab.freedesktop.org/drm/amd/-/issues/4618 */
+ {
+ .ident = "Lenovo Legion Go 2",
+ .driver_data = &quirk_s2idle_bug,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83N0"),
+ }
+ },
+ {
+ .ident = "Lenovo Legion Go 2",
+ .driver_data = &quirk_s2idle_bug,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83N1"),
+ }
+ },
/* https://gitlab.freedesktop.org/drm/amd/-/issues/2684 */
{
.ident = "HP Laptop 15s-eq2xxx",
- .driver_data = &quirk_s2idle_bug,
+ .driver_data = &quirk_s2idle_spurious_8042,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP Laptop 15s-eq2xxx"),
@@ -217,6 +266,58 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_BIOS_VERSION, "03.05"),
}
},
+ {
+ .ident = "MECHREVO Wujie 14X (GX4HRXL)",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "WUJIE14-GX4HRXL"),
+ }
+ },
+ {
+ .ident = "MECHREVO Yilong15Pro Series GM5HG7A",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MECHREVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Yilong15Pro Series GM5HG7A"),
+ }
+ },
+ /* https://bugzilla.kernel.org/show_bug.cgi?id=220116 */
+ {
+ .ident = "PCSpecialist Lafite Pro V 14M",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "PCSpecialist"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Lafite Pro V 14M"),
+ }
+ },
+ {
+ .ident = "TUXEDO Stellaris Slim 15 AMD Gen6",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GMxHGxx"),
+ }
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14/15 AMD Gen10",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "XxHP4NAx"),
+ }
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14/15 AMD Gen10",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"),
+ }
+ },
+ {
+ .ident = "MECHREVO Wujie 15X Pro",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "WUJIE Series-X5SP4NAG"),
+ }
+ },
{}
};
@@ -259,6 +360,16 @@ void amd_pmc_quirks_init(struct amd_pmc_dev *dev)
{
const struct dmi_system_id *dmi_id;
+ /*
+ * IRQ1 may cause an interrupt during resume even without a keyboard
+ * press.
+ *
+ * Affects Renoir, Cezanne and Barcelo SoCs
+ *
+ * A solution is available in PMFW 64.66.0, but it must be activated by
+ * SBIOS. If SBIOS is known to have the fix a quirk can be added for
+ * a given system to avoid workaround.
+ */
if (dev->cpu_id == AMD_CPU_ID_CZN)
dev->disable_8042_wakeup = true;
@@ -269,6 +380,5 @@ void amd_pmc_quirks_init(struct amd_pmc_dev *dev)
if (dev->quirks->s2idle_bug_mmio)
pr_info("Using s2idle quirk to avoid %s platform firmware bug\n",
dmi_id->ident);
- if (dev->quirks->spurious_8042)
- dev->disable_8042_wakeup = true;
+ dev->disable_8042_wakeup = dev->quirks->spurious_8042;
}
diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index d789d6cab794..cae3fcafd4d7 100644
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -28,7 +28,7 @@
#include <linux/seq_file.h>
#include <linux/uaccess.h>
-#include <asm/amd_node.h>
+#include <asm/amd/node.h>
#include "pmc.h"
@@ -106,6 +106,7 @@ static void amd_pmc_get_ip_info(struct amd_pmc_dev *dev)
switch (dev->cpu_id) {
case AMD_CPU_ID_PCO:
case AMD_CPU_ID_RN:
+ case AMD_CPU_ID_VG:
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
dev->num_ips = 12;
@@ -157,6 +158,8 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev)
return -ENOMEM;
}
+ memset_io(dev->smu_virt_addr, 0, sizeof(struct smu_metrics));
+
/* Start the logging */
amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_RESET, false);
amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_START, false);
@@ -515,6 +518,7 @@ static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev)
case AMD_CPU_ID_PCO:
return MSG_OS_HINT_PCO;
case AMD_CPU_ID_RN:
+ case AMD_CPU_ID_VG:
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
case AMD_CPU_ID_PS:
@@ -528,19 +532,6 @@ static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev)
static int amd_pmc_wa_irq1(struct amd_pmc_dev *pdev)
{
struct device *d;
- int rc;
-
- /* cezanne platform firmware has a fix in 64.66.0 */
- if (pdev->cpu_id == AMD_CPU_ID_CZN) {
- if (!pdev->major) {
- rc = amd_pmc_get_smu_version(pdev);
- if (rc)
- return rc;
- }
-
- if (pdev->major > 64 || (pdev->major == 64 && pdev->minor > 65))
- return 0;
- }
d = bus_find_device_by_name(&serio_bus, NULL, "serio0");
if (!d)
@@ -644,10 +635,9 @@ static void amd_pmc_s2idle_check(void)
struct smu_metrics table;
int rc;
- /* CZN: Ensure that future s0i3 entry attempts at least 10ms passed */
- if (pdev->cpu_id == AMD_CPU_ID_CZN && !get_metrics_table(pdev, &table) &&
- table.s0i3_last_entry_status)
- usleep_range(10000, 20000);
+ /* Avoid triggering OVP */
+ if (!get_metrics_table(pdev, &table) && table.s0i3_last_entry_status)
+ msleep(2500);
/* Dump the IdleMask before we add to the STB */
amd_pmc_idlemask_read(pdev, pdev->dev, NULL);
@@ -729,6 +719,7 @@ static const struct pci_device_id pmc_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RV) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_SP) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_SHP) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_VG) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) },
{ }
diff --git a/drivers/platform/x86/amd/pmc/pmc.h b/drivers/platform/x86/amd/pmc/pmc.h
index 62f3e51020fd..fe3f53eb5955 100644
--- a/drivers/platform/x86/amd/pmc/pmc.h
+++ b/drivers/platform/x86/amd/pmc/pmc.h
@@ -156,6 +156,7 @@ void amd_mp2_stb_deinit(struct amd_pmc_dev *dev);
#define AMD_CPU_ID_RN 0x1630
#define AMD_CPU_ID_PCO AMD_CPU_ID_RV
#define AMD_CPU_ID_CZN AMD_CPU_ID_RN
+#define AMD_CPU_ID_VG 0x1645
#define AMD_CPU_ID_YC 0x14B5
#define AMD_CPU_ID_CB 0x14D8
#define AMD_CPU_ID_PS 0x14E8
diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
index f75f7ecd8cd9..3d94b03cf794 100644
--- a/drivers/platform/x86/amd/pmf/acpi.c
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -9,6 +9,9 @@
*/
#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/dev_printk.h>
#include "pmf.h"
#define APMF_CQL_NOTIFICATION 2
@@ -161,6 +164,11 @@ int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index)
return !!(pdev->supported_func & BIT(index - 1));
}
+int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev)
+{
+ return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS);
+}
+
int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
struct amd_pmf_apts_granular_output *data, u32 apts_idx)
{
@@ -315,12 +323,59 @@ int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
}
+int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req)
+{
+ return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
+}
+
int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS,
req, sizeof(*req));
}
+/* Store custom BIOS inputs data in ring buffer */
+static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev)
+{
+ struct pmf_cbi_ring_buffer *rb = &pmf_dev->cbi_buf;
+ int i;
+
+ guard(mutex)(&pmf_dev->cbi_mutex);
+
+ switch (pmf_dev->cpu_id) {
+ case AMD_CPU_ID_PS:
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs_v1); i++)
+ rb->data[rb->head].val[i] = pmf_dev->req1.custom_policy[i];
+ rb->data[rb->head].preq = pmf_dev->req1.pending_req;
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
+ rb->data[rb->head].val[i] = pmf_dev->req.custom_policy[i];
+ rb->data[rb->head].preq = pmf_dev->req.pending_req;
+ break;
+ default:
+ return;
+ }
+
+ if (CIRC_SPACE(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0) {
+ /* Rare case: ensures the newest BIOS input value is kept */
+ dev_warn(pmf_dev->dev, "Overwriting BIOS input value, data may be lost\n");
+ rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
+ }
+
+ rb->head = (rb->head + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
+}
+
+static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev)
+{
+ if (!pdev->cb_flag)
+ return;
+
+ amd_pmf_invoke_cmd_enact(pdev);
+ pdev->cb_flag = false;
+}
+
static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
{
struct amd_pmf_dev *pmf_dev = data;
@@ -329,8 +384,36 @@ static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
guard(mutex)(&pmf_dev->cb_mutex);
ret = apmf_get_sbios_requests_v2(pmf_dev, &pmf_dev->req);
- if (ret)
+ if (ret) {
dev_err(pmf_dev->dev, "Failed to get v2 SBIOS requests: %d\n", ret);
+ return;
+ }
+
+ dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req);
+
+ amd_pmf_handle_early_preq(pmf_dev);
+
+ amd_pmf_custom_bios_inputs_rb(pmf_dev);
+}
+
+static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data)
+{
+ struct amd_pmf_dev *pmf_dev = data;
+ int ret;
+
+ guard(mutex)(&pmf_dev->cb_mutex);
+
+ ret = apmf_get_sbios_requests_v1(pmf_dev, &pmf_dev->req1);
+ if (ret) {
+ dev_err(pmf_dev->dev, "Failed to get v1 SBIOS requests: %d\n", ret);
+ return;
+ }
+
+ dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req);
+
+ amd_pmf_handle_early_preq(pmf_dev);
+
+ amd_pmf_custom_bios_inputs_rb(pmf_dev);
}
static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
@@ -385,6 +468,7 @@ static int apmf_if_verify_interface(struct amd_pmf_dev *pdev)
pdev->pmf_if_version = output.version;
+ pdev->notifications = output.notification_mask;
return 0;
}
@@ -421,6 +505,11 @@ int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_
return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data));
}
+static apmf_event_handler_t apmf_event_handlers[] = {
+ [PMF_IF_V1] = apmf_event_handler_v1,
+ [PMF_IF_V2] = apmf_event_handler_v2,
+};
+
int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
{
acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
@@ -440,13 +529,26 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
apmf_event_handler(ahandle, 0, pmf_dev);
}
- if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2) {
+ if (!pmf_dev->smart_pc_enabled)
+ return -EINVAL;
+
+ switch (pmf_dev->pmf_if_version) {
+ case PMF_IF_V1:
+ if (!is_apmf_bios_input_notifications_supported(pmf_dev))
+ break;
+ fallthrough;
+ case PMF_IF_V2:
status = acpi_install_notify_handler(ahandle, ACPI_ALL_NOTIFY,
- apmf_event_handler_v2, pmf_dev);
+ apmf_event_handlers[pmf_dev->pmf_if_version], pmf_dev);
if (ACPI_FAILURE(status)) {
- dev_err(pmf_dev->dev, "failed to install notify handler for custom BIOS inputs\n");
+ dev_err(pmf_dev->dev,
+ "failed to install notify handler v%d for custom BIOS inputs\n",
+ pmf_dev->pmf_if_version);
return -ENODEV;
}
+ break;
+ default:
+ break;
}
return 0;
@@ -500,8 +602,21 @@ void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev)
is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS))
acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler);
- if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2)
- acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler_v2);
+ if (!pmf_dev->smart_pc_enabled)
+ return;
+
+ switch (pmf_dev->pmf_if_version) {
+ case PMF_IF_V1:
+ if (!is_apmf_bios_input_notifications_supported(pmf_dev))
+ break;
+ fallthrough;
+ case PMF_IF_V2:
+ acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY,
+ apmf_event_handlers[pmf_dev->pmf_if_version]);
+ break;
+ default:
+ break;
+ }
}
int apmf_acpi_init(struct amd_pmf_dev *pmf_dev)
diff --git a/drivers/platform/x86/amd/pmf/auto-mode.c b/drivers/platform/x86/amd/pmf/auto-mode.c
index 02ff68be10d0..faf15a8f74bb 100644
--- a/drivers/platform/x86/amd/pmf/auto-mode.c
+++ b/drivers/platform/x86/amd/pmf/auto-mode.c
@@ -114,15 +114,15 @@ static void amd_pmf_set_automode(struct amd_pmf_dev *dev, int idx,
{
struct power_table_control *pwr_ctrl = &config_store.mode_set[idx].power_control;
- amd_pmf_send_cmd(dev, SET_SPL, false, pwr_ctrl->spl, NULL);
- amd_pmf_send_cmd(dev, SET_FPPT, false, pwr_ctrl->fppt, NULL);
- amd_pmf_send_cmd(dev, SET_SPPT, false, pwr_ctrl->sppt, NULL);
- amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pwr_ctrl->sppt_apu_only, NULL);
- amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pwr_ctrl->stt_min, NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
- pwr_ctrl->stt_skin_temp[STT_TEMP_APU], NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
- pwr_ctrl->stt_skin_temp[STT_TEMP_HS2], NULL);
+ amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, pwr_ctrl->spl, NULL);
+ amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, pwr_ctrl->fppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, pwr_ctrl->sppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD, pwr_ctrl->sppt_apu_only, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD, pwr_ctrl->stt_min, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
+ fixp_q88_fromint(pwr_ctrl->stt_skin_temp[STT_TEMP_APU]), NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
+ fixp_q88_fromint(pwr_ctrl->stt_skin_temp[STT_TEMP_HS2]), NULL);
if (is_apmf_func_supported(dev, APMF_FUNC_SET_FAN_IDX))
apmf_update_fan_idx(dev, config_store.mode_set[idx].fan_control.manual,
diff --git a/drivers/platform/x86/amd/pmf/cnqf.c b/drivers/platform/x86/amd/pmf/cnqf.c
index bc8899e15c91..5469fefb6001 100644
--- a/drivers/platform/x86/amd/pmf/cnqf.c
+++ b/drivers/platform/x86/amd/pmf/cnqf.c
@@ -76,15 +76,15 @@ static int amd_pmf_set_cnqf(struct amd_pmf_dev *dev, int src, int idx,
pc = &config_store.mode_set[src][idx].power_control;
- amd_pmf_send_cmd(dev, SET_SPL, false, pc->spl, NULL);
- amd_pmf_send_cmd(dev, SET_FPPT, false, pc->fppt, NULL);
- amd_pmf_send_cmd(dev, SET_SPPT, false, pc->sppt, NULL);
- amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pc->sppt_apu_only, NULL);
- amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pc->stt_min, NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false, pc->stt_skin_temp[STT_TEMP_APU],
- NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false, pc->stt_skin_temp[STT_TEMP_HS2],
- NULL);
+ amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, pc->spl, NULL);
+ amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, pc->fppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, pc->sppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD, pc->sppt_apu_only, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD, pc->stt_min, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
+ fixp_q88_fromint(pc->stt_skin_temp[STT_TEMP_APU]), NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
+ fixp_q88_fromint(pc->stt_skin_temp[STT_TEMP_HS2]), NULL);
if (is_apmf_func_supported(dev, APMF_FUNC_SET_FAN_IDX))
apmf_update_fan_idx(dev,
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index a2cb2d5544f5..b9e5a2cf3aae 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -8,13 +8,17 @@
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/iopoll.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
-#include <asm/amd_node.h>
+#include <linux/string.h>
+#include <asm/amd/node.h>
#include "pmf.h"
/* PMF-SMU communication registers */
@@ -53,6 +57,12 @@ static bool force_load;
module_param(force_load, bool, 0444);
MODULE_PARM_DESC(force_load, "Force load this driver on supported older platforms (experimental)");
+static bool smart_pc_support = true;
+module_param(smart_pc_support, bool, 0444);
+MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
+
+static struct device *pmf_device;
+
static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
{
struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier);
@@ -131,7 +141,7 @@ static void amd_pmf_get_metrics(struct work_struct *work)
/* Transfer table contents */
memset(dev->buf, 0, sizeof(dev->m_table));
- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, 0, 7, NULL);
+ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
@@ -176,6 +186,20 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
}
+/**
+ * fixp_q88_fromint: Convert integer to Q8.8
+ * @val: input value
+ *
+ * Converts an integer into binary fixed point format where 8 bits
+ * are used for integer and 8 bits are used for the decimal.
+ *
+ * Return: unsigned integer converted to Q8.8 format
+ */
+u32 fixp_q88_fromint(u32 val)
+{
+ return val << 8;
+}
+
int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data)
{
int rc;
@@ -266,7 +290,7 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id);
}
- dev->buf = kzalloc(dev->mtable_size, GFP_KERNEL);
+ dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
if (!dev->buf)
return -ENOMEM;
}
@@ -275,8 +299,8 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
hi = phys_addr >> 32;
low = phys_addr & GENMASK(31, 0);
- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, 0, hi, NULL);
- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, 0, low, NULL);
+ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
+ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
return 0;
}
@@ -300,6 +324,126 @@ int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
return 0;
}
+static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
+{
+ switch (pdev->cpu_id) {
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
+{
+ int ret, i;
+
+ guard(mutex)(&dev->metrics_mutex);
+
+ ret = is_npu_metrics_supported(dev);
+ if (ret)
+ return ret;
+
+ ret = amd_pmf_set_dram_addr(dev, true);
+ if (ret)
+ return ret;
+
+ memset(dev->buf, 0, dev->mtable_size);
+
+ /* Send SMU command to get NPU metrics */
+ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
+ if (ret) {
+ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
+ return ret;
+ }
+
+ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
+
+ data->npuclk_freq = dev->m_table_v2.npuclk_freq;
+ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
+ data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
+ data->npu_power = dev->m_table_v2.npu_power;
+ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
+ data->npu_reads = dev->m_table_v2.npu_reads;
+ data->npu_writes = dev->m_table_v2.npu_writes;
+
+ return 0;
+}
+
+int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
+{
+ struct amd_pmf_dev *pdev;
+
+ if (!info)
+ return -EINVAL;
+
+ if (!pmf_device)
+ return -ENODEV;
+
+ pdev = dev_get_drvdata(pmf_device);
+ if (!pdev)
+ return -ENODEV;
+
+ return amd_pmf_get_smu_metrics(pdev, info);
+}
+EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
+
+static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
+{
+ bool status;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
+ ret = amd_pmf_tee_init(pdev, &amd_pmf_ta_uuid[i]);
+ if (ret) {
+ dev_err(pdev->dev, "TEE init failed for UUID[%d] ret: %d\n", i, ret);
+ return ret;
+ }
+
+ ret = amd_pmf_start_policy_engine(pdev);
+ dev_dbg(pdev->dev, "start policy engine ret: %d (UUID idx: %d)\n", ret, i);
+ status = ret == TA_PMF_TYPE_SUCCESS;
+ if (status)
+ break;
+ amd_pmf_tee_deinit(pdev);
+ }
+
+ return 0;
+}
+
+static int amd_pmf_restore_handler(struct device *dev)
+{
+ struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
+ int ret;
+
+ if (pdev->buf) {
+ ret = amd_pmf_set_dram_addr(pdev, false);
+ if (ret)
+ return ret;
+ }
+
+ if (pdev->smart_pc_enabled)
+ amd_pmf_reinit_ta(pdev);
+
+ return 0;
+}
+
+static int amd_pmf_freeze_handler(struct device *dev)
+{
+ struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
+
+ if (!pdev->smart_pc_enabled)
+ return 0;
+
+ cancel_delayed_work_sync(&pdev->pb_work);
+ /* Clear all TEE resources */
+ amd_pmf_tee_deinit(pdev);
+ pdev->session_id = 0;
+
+ return 0;
+}
+
static int amd_pmf_suspend_handler(struct device *dev)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
@@ -333,7 +477,12 @@ static int amd_pmf_resume_handler(struct device *dev)
return 0;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmf_pm, amd_pmf_suspend_handler, amd_pmf_resume_handler);
+static const struct dev_pm_ops amd_pmf_pm = {
+ .suspend = amd_pmf_suspend_handler,
+ .resume = amd_pmf_resume_handler,
+ .freeze = amd_pmf_freeze_handler,
+ .restore = amd_pmf_restore_handler,
+};
static void amd_pmf_init_features(struct amd_pmf_dev *dev)
{
@@ -348,11 +497,15 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
}
- amd_pmf_init_smart_pc(dev);
- if (dev->smart_pc_enabled) {
- dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
- /* If Smart PC is enabled, no need to check for other features */
- return;
+ if (smart_pc_support) {
+ amd_pmf_init_smart_pc(dev);
+ if (dev->smart_pc_enabled) {
+ dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
+ /* If Smart PC is enabled, no need to check for other features */
+ return;
+ }
+ } else {
+ dev->smart_pc_enabled = false;
}
if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) {
@@ -389,6 +542,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = {
{"AMDI0103", 0},
{"AMDI0105", 0},
{"AMDI0107", 0},
+ {"AMDI0108", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
@@ -450,9 +604,25 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (!dev->regbase)
return -ENOMEM;
- mutex_init(&dev->lock);
- mutex_init(&dev->update_mutex);
- mutex_init(&dev->cb_mutex);
+ err = devm_mutex_init(dev->dev, &dev->lock);
+ if (err)
+ return err;
+
+ err = devm_mutex_init(dev->dev, &dev->update_mutex);
+ if (err)
+ return err;
+
+ err = devm_mutex_init(dev->dev, &dev->cb_mutex);
+ if (err)
+ return err;
+
+ err = devm_mutex_init(dev->dev, &dev->cbi_mutex);
+ if (err)
+ return err;
+
+ err = devm_mutex_init(dev->dev, &dev->metrics_mutex);
+ if (err)
+ return err;
apmf_acpi_init(dev);
platform_set_drvdata(pdev, dev);
@@ -462,6 +632,8 @@ static int amd_pmf_probe(struct platform_device *pdev)
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
+ pmf_device = dev->dev;
+
dev_info(dev->dev, "registered PMF device successfully\n");
return 0;
@@ -476,10 +648,6 @@ static void amd_pmf_remove(struct platform_device *pdev)
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD);
apmf_acpi_deinit(dev);
amd_pmf_dbgfs_unregister(dev);
- mutex_destroy(&dev->lock);
- mutex_destroy(&dev->update_mutex);
- mutex_destroy(&dev->cb_mutex);
- kfree(dev->buf);
}
static const struct attribute_group *amd_pmf_driver_groups[] = {
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index e6bdee68ccf3..69fef7448744 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -12,7 +12,10 @@
#define PMF_H
#include <linux/acpi.h>
+#include <linux/amd-pmf-io.h>
+#include <linux/circ_buf.h>
#include <linux/input.h>
+#include <linux/mutex_types.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
@@ -93,6 +96,8 @@ struct cookie_header {
#define PMF_POLICY_BIOS_OUTPUT_1 10
#define PMF_POLICY_BIOS_OUTPUT_2 11
#define PMF_POLICY_P3T 38
+#define PMF_POLICY_PMF_PPT 54
+#define PMF_POLICY_PMF_PPT_APU_ONLY 55
#define PMF_POLICY_BIOS_OUTPUT_3 57
#define PMF_POLICY_BIOS_OUTPUT_4 58
#define PMF_POLICY_BIOS_OUTPUT_5 59
@@ -116,6 +121,23 @@ struct cookie_header {
#define PMF_IF_V2 2
#define APTS_MAX_STATES 16
+#define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7)
+#define BIOS_INPUTS_MAX 10
+#define CUSTOM_BIOS_INPUT_RING_ENTRIES 64 /* Must be power of two for CIRC_* macros */
+
+/* amd_pmf_send_cmd() set/get */
+#define SET_CMD false
+#define GET_CMD true
+
+#define METRICS_TABLE_ID 7
+
+typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
+
+static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8,
+ 0x8a, 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
+ UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d,
+ 0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43),
+ };
/* APTS PMF BIOS Interface */
struct amd_pmf_apts_output {
@@ -184,6 +206,24 @@ struct apmf_sbios_req {
u8 skin_temp_hs2;
} __packed;
+/* As per APMF spec 1.3 */
+struct apmf_sbios_req_v1 {
+ u16 size;
+ u32 pending_req;
+ u8 rsvd;
+ u8 cql_event;
+ u8 amt_event;
+ u32 fppt;
+ u32 sppt;
+ u32 sppt_apu_only;
+ u32 spl;
+ u32 stt_min_limit;
+ u8 skin_temp_apu;
+ u8 skin_temp_hs2;
+ u8 enable_cnqf;
+ u32 custom_policy[BIOS_INPUTS_MAX];
+} __packed;
+
struct apmf_sbios_req_v2 {
u16 size;
u32 pending_req;
@@ -193,7 +233,7 @@ struct apmf_sbios_req_v2 {
u32 stt_min_limit;
u8 skin_temp_apu;
u8 skin_temp_hs2;
- u32 custom_policy[10];
+ u32 custom_policy[BIOS_INPUTS_MAX];
} __packed;
struct apmf_fan_idx {
@@ -220,12 +260,12 @@ struct smu_pmf_metrics_v2 {
u16 vclk_freq; /* MHz */
u16 vcn_activity; /* VCN busy % [0-100] */
u16 vpeclk_freq; /* MHz */
- u16 ipuclk_freq; /* MHz */
- u16 ipu_busy[8]; /* NPU busy % [0-100] */
+ u16 npuclk_freq; /* MHz */
+ u16 npu_busy[8]; /* NPU busy % [0-100] */
u16 dram_reads; /* MB/sec */
u16 dram_writes; /* MB/sec */
u16 core_c0residency[16]; /* C0 residency % [0-100] */
- u16 ipu_power; /* mW */
+ u16 npu_power; /* mW */
u32 apu_power; /* mW */
u32 gfx_power; /* mW */
u32 dgpu_power; /* mW */
@@ -234,9 +274,9 @@ struct smu_pmf_metrics_v2 {
u32 filter_alpha_value; /* time constant [us] */
u32 metrics_counter;
u16 memclk_freq; /* MHz */
- u16 mpipuclk_freq; /* MHz */
- u16 ipu_reads; /* MB/sec */
- u16 ipu_writes; /* MB/sec */
+ u16 mpnpuclk_freq; /* MHz */
+ u16 npu_reads; /* MB/sec */
+ u16 npu_writes; /* MB/sec */
u32 throttle_residency_prochot;
u32 throttle_residency_spl;
u32 throttle_residency_fppt;
@@ -331,6 +371,26 @@ enum power_modes_v2 {
POWER_MODE_V2_MAX,
};
+struct pmf_bios_inputs_prev {
+ u32 custom_bios_inputs[BIOS_INPUTS_MAX];
+};
+
+/**
+ * struct pmf_bios_input_entry - Snapshot of custom BIOS input event
+ * @val: Array of custom BIOS input values
+ * @preq: Pending request value associated with this event
+ */
+struct pmf_bios_input_entry {
+ u32 val[BIOS_INPUTS_MAX];
+ u32 preq;
+};
+
+struct pmf_cbi_ring_buffer {
+ struct pmf_bios_input_entry data[CUSTOM_BIOS_INPUT_RING_ENTRIES];
+ int head;
+ int tail;
+};
+
struct amd_pmf_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
@@ -375,6 +435,13 @@ struct amd_pmf_dev {
struct resource *res;
struct apmf_sbios_req_v2 req; /* To get custom bios pending request */
struct mutex cb_mutex;
+ u32 notifications;
+ struct apmf_sbios_req_v1 req1;
+ struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */
+ bool cb_flag; /* To handle first custom BIOS input */
+ struct pmf_cbi_ring_buffer cbi_buf;
+ struct mutex cbi_mutex; /* Protects ring buffer access */
+ struct mutex metrics_mutex;
};
struct apmf_sps_prop_granular_v2 {
@@ -420,7 +487,7 @@ struct os_power_slider {
struct amd_pmf_notify_smart_pc_update {
u16 size;
u32 pending_req;
- u32 custom_bios[10];
+ u32 custom_bios[BIOS_INPUTS_MAX];
} __packed;
struct fan_table_control {
@@ -621,14 +688,35 @@ enum ta_slider {
TA_MAX,
};
-enum apmf_smartpc_custom_bios_inputs {
- APMF_SMARTPC_CUSTOM_BIOS_INPUT1,
- APMF_SMARTPC_CUSTOM_BIOS_INPUT2,
+struct amd_pmf_pb_bitmap {
+ const char *name;
+ u32 bit_mask;
};
-enum apmf_preq_smartpc {
- NOTIFY_CUSTOM_BIOS_INPUT1 = 5,
- NOTIFY_CUSTOM_BIOS_INPUT2,
+static const struct amd_pmf_pb_bitmap custom_bios_inputs[] __used = {
+ {"NOTIFY_CUSTOM_BIOS_INPUT1", BIT(5)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT2", BIT(6)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT3", BIT(7)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT4", BIT(8)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT5", BIT(9)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT6", BIT(10)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT7", BIT(11)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT8", BIT(12)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT9", BIT(13)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(14)},
+};
+
+static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = {
+ {"NOTIFY_CUSTOM_BIOS_INPUT1", BIT(7)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT2", BIT(8)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT3", BIT(9)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT4", BIT(10)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT5", BIT(11)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT6", BIT(12)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT7", BIT(13)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT8", BIT(14)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT9", BIT(15)},
+ {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)},
};
enum platform_type {
@@ -677,6 +765,8 @@ struct pmf_action_table {
u32 stt_skintemp_apu; /* in C */
u32 stt_skintemp_hs2; /* in C */
u32 p3t_limit; /* in mW */
+ u32 pmf_ppt; /* in mW */
+ u32 pmf_ppt_apu_only; /* in mW */
};
/* Input conditions */
@@ -686,8 +776,7 @@ struct ta_pmf_condition_info {
u32 power_slider;
u32 lid_state;
bool user_present;
- u32 bios_input1;
- u32 bios_input2;
+ u32 bios_input_1[2];
u32 monitor_count;
u32 rsvd2[2];
u32 bat_design;
@@ -711,7 +800,9 @@ struct ta_pmf_condition_info {
u32 workload_type;
u32 display_type;
u32 display_state;
- u32 rsvd5[150];
+ u32 rsvd5_1[17];
+ u32 bios_input_2[8];
+ u32 rsvd5[125];
};
struct ta_pmf_load_policy_table {
@@ -737,6 +828,7 @@ struct ta_pmf_enact_table {
struct ta_pmf_action {
u32 action_index;
u32 value;
+ u32 spl_arg;
};
/* Output actions from TA */
@@ -777,6 +869,8 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev);
int apmf_os_power_slider_update(struct amd_pmf_dev *dev, u8 flag);
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer);
int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag);
+u32 fixp_q88_fromint(u32 val);
+int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev);
/* SPS Layer */
int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
@@ -804,6 +898,7 @@ void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev);
void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev);
void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms);
int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req);
+int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req);
int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req);
void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event);
@@ -827,5 +922,10 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq
/* Smart PC - TA interfaces */
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
+int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
+
+int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
+void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
+int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
#endif /* PMF_H */
diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c
index 1d90f9382024..f48678a23cc7 100644
--- a/drivers/platform/x86/amd/pmf/spc.c
+++ b/drivers/platform/x86/amd/pmf/spc.c
@@ -11,6 +11,7 @@
#include <acpi/button.h>
#include <linux/amd-pmf-io.h>
+#include <linux/cleanup.h>
#include <linux/power_supply.h>
#include <linux/units.h>
#include "pmf.h"
@@ -70,8 +71,22 @@ static const char *ta_slider_as_str(unsigned int state)
}
}
+static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
+{
+ switch (index) {
+ case 0 ... 1:
+ return in->ev_info.bios_input_1[index];
+ case 2 ... 9:
+ return in->ev_info.bios_input_2[index - 2];
+ default:
+ return 0;
+ }
+}
+
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
+ int i;
+
dev_dbg(dev->dev, "==== TA inputs START ====\n");
dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider));
dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source));
@@ -90,33 +105,85 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *
dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type));
dev_dbg(dev->dev, "Laptop placement: %s\n",
laptop_placement_as_str(in->ev_info.device_state));
- dev_dbg(dev->dev, "Custom BIOS input1: %u\n", in->ev_info.bios_input1);
- dev_dbg(dev->dev, "Custom BIOS input2: %u\n", in->ev_info.bios_input2);
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
+ dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1,
+ amd_pmf_get_ta_custom_bios_inputs(in, i));
dev_dbg(dev->dev, "==== TA inputs END ====\n");
}
#else
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
#endif
+/*
+ * This helper function sets the appropriate BIOS input value in the TA enact
+ * table based on the provided index. We need this approach because the custom
+ * BIOS input array is not continuous, due to the existing TA structure layout.
+ */
+static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int index, u32 value)
+{
+ switch (index) {
+ case 0 ... 1:
+ in->ev_info.bios_input_1[index] = value;
+ break;
+ case 2 ... 9:
+ in->ev_info.bios_input_2[index - 2] = value;
+ break;
+ default:
+ return;
+ }
+}
+
+static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, struct pmf_bios_input_entry *data,
+ const struct amd_pmf_pb_bitmap *inputs,
+ struct ta_pmf_enact_table *in)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) {
+ if (!(data->preq & inputs[i].bit_mask))
+ continue;
+ amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]);
+ pdev->cb_prev.custom_bios_inputs[i] = data->val[i];
+ dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]);
+ }
+}
+
static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
struct ta_pmf_enact_table *in)
{
- if (!pdev->req.pending_req)
+ struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf;
+ unsigned int i;
+
+ guard(mutex)(&pdev->cbi_mutex);
+
+ for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
+ amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]);
+
+ if (CIRC_CNT(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0)
return;
- switch (pdev->req.pending_req) {
- case BIT(NOTIFY_CUSTOM_BIOS_INPUT1):
- in->ev_info.bios_input1 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT1];
+ /* If no active custom BIOS input pending request, do not consume further work */
+ if (!rb->data[rb->tail].preq)
+ goto out_rbadvance;
+
+ if (!pdev->smart_pc_enabled)
+ return;
+
+ switch (pdev->pmf_if_version) {
+ case PMF_IF_V1:
+ if (!is_apmf_bios_input_notifications_supported(pdev))
+ return;
+ amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in);
break;
- case BIT(NOTIFY_CUSTOM_BIOS_INPUT2):
- in->ev_info.bios_input2 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT2];
+ case PMF_IF_V2:
+ amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in);
break;
default:
- dev_dbg(pdev->dev, "Invalid preq for BIOS input: 0x%x\n", pdev->req.pending_req);
+ break;
}
- /* Clear pending requests after handling */
- memset(&pdev->req, 0, sizeof(pdev->req));
+out_rbadvance:
+ rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
}
static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
@@ -140,7 +207,7 @@ static void amd_pmf_get_smu_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_ta
{
/* Get the updated metrics table data */
memset(dev->buf, 0, dev->mtable_size);
- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, 0, 7, NULL);
+ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
switch (dev->cpu_id) {
case AMD_CPU_ID_PS:
diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c
index d3083383f11f..0b70a5153f46 100644
--- a/drivers/platform/x86/amd/pmf/sps.c
+++ b/drivers/platform/x86/amd/pmf/sps.c
@@ -192,15 +192,17 @@ static void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev)
static void amd_pmf_update_slider_v2(struct amd_pmf_dev *dev, int idx)
{
- amd_pmf_send_cmd(dev, SET_PMF_PPT, false, apts_config_store.val[idx].pmf_ppt, NULL);
- amd_pmf_send_cmd(dev, SET_PMF_PPT_APU_ONLY, false,
+ amd_pmf_send_cmd(dev, SET_PMF_PPT, SET_CMD, apts_config_store.val[idx].pmf_ppt, NULL);
+ amd_pmf_send_cmd(dev, SET_PMF_PPT_APU_ONLY, SET_CMD,
apts_config_store.val[idx].ppt_pmf_apu_only, NULL);
- amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false,
+ amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD,
apts_config_store.val[idx].stt_min_limit, NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
- apts_config_store.val[idx].stt_skin_temp_limit_apu, NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
- apts_config_store.val[idx].stt_skin_temp_limit_hs2, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
+ fixp_q88_fromint(apts_config_store.val[idx].stt_skin_temp_limit_apu),
+ NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
+ fixp_q88_fromint(apts_config_store.val[idx].stt_skin_temp_limit_hs2),
+ NULL);
}
void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
@@ -209,28 +211,30 @@ void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
int src = amd_pmf_get_power_source();
if (op == SLIDER_OP_SET) {
- amd_pmf_send_cmd(dev, SET_SPL, false, config_store.prop[src][idx].spl, NULL);
- amd_pmf_send_cmd(dev, SET_FPPT, false, config_store.prop[src][idx].fppt, NULL);
- amd_pmf_send_cmd(dev, SET_SPPT, false, config_store.prop[src][idx].sppt, NULL);
- amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false,
+ amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, config_store.prop[src][idx].spl, NULL);
+ amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, config_store.prop[src][idx].fppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, config_store.prop[src][idx].sppt, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD,
config_store.prop[src][idx].sppt_apu_only, NULL);
- amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false,
+ amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD,
config_store.prop[src][idx].stt_min, NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
- config_store.prop[src][idx].stt_skin_temp[STT_TEMP_APU], NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
- config_store.prop[src][idx].stt_skin_temp[STT_TEMP_HS2], NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
+ fixp_q88_fromint(config_store.prop[src][idx].stt_skin_temp[STT_TEMP_APU]),
+ NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
+ fixp_q88_fromint(config_store.prop[src][idx].stt_skin_temp[STT_TEMP_HS2]),
+ NULL);
} else if (op == SLIDER_OP_GET) {
- amd_pmf_send_cmd(dev, GET_SPL, true, ARG_NONE, &table->prop[src][idx].spl);
- amd_pmf_send_cmd(dev, GET_FPPT, true, ARG_NONE, &table->prop[src][idx].fppt);
- amd_pmf_send_cmd(dev, GET_SPPT, true, ARG_NONE, &table->prop[src][idx].sppt);
- amd_pmf_send_cmd(dev, GET_SPPT_APU_ONLY, true, ARG_NONE,
+ amd_pmf_send_cmd(dev, GET_SPL, GET_CMD, ARG_NONE, &table->prop[src][idx].spl);
+ amd_pmf_send_cmd(dev, GET_FPPT, GET_CMD, ARG_NONE, &table->prop[src][idx].fppt);
+ amd_pmf_send_cmd(dev, GET_SPPT, GET_CMD, ARG_NONE, &table->prop[src][idx].sppt);
+ amd_pmf_send_cmd(dev, GET_SPPT_APU_ONLY, GET_CMD, ARG_NONE,
&table->prop[src][idx].sppt_apu_only);
- amd_pmf_send_cmd(dev, GET_STT_MIN_LIMIT, true, ARG_NONE,
+ amd_pmf_send_cmd(dev, GET_STT_MIN_LIMIT, GET_CMD, ARG_NONE,
&table->prop[src][idx].stt_min);
- amd_pmf_send_cmd(dev, GET_STT_LIMIT_APU, true, ARG_NONE,
+ amd_pmf_send_cmd(dev, GET_STT_LIMIT_APU, GET_CMD, ARG_NONE,
(u32 *)&table->prop[src][idx].stt_skin_temp[STT_TEMP_APU]);
- amd_pmf_send_cmd(dev, GET_STT_LIMIT_HS2, true, ARG_NONE,
+ amd_pmf_send_cmd(dev, GET_STT_LIMIT_HS2, GET_CMD, ARG_NONE,
(u32 *)&table->prop[src][idx].stt_skin_temp[STT_TEMP_HS2]);
}
}
@@ -279,7 +283,7 @@ int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf)
bool is_pprof_balanced(struct amd_pmf_dev *pmf)
{
- return (pmf->current_profile == PLATFORM_PROFILE_BALANCED) ? true : false;
+ return pmf->current_profile == PLATFORM_PROFILE_BALANCED;
}
static int amd_pmf_profile_get(struct device *dev,
diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c
index a1e43873a07b..7ccd93f506b2 100644
--- a/drivers/platform/x86/amd/pmf/tee-if.c
+++ b/drivers/platform/x86/amd/pmf/tee-if.c
@@ -27,12 +27,6 @@ module_param(pb_side_load, bool, 0444);
MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures");
#endif
-static const uuid_t amd_pmf_ta_uuid[] = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, 0x8a,
- 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
- UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, 0xc5,
- 0x29, 0xb1, 0x3d, 0x85, 0x43),
- };
-
static const char *amd_pmf_uevent_as_str(unsigned int state)
{
switch (state) {
@@ -73,17 +67,56 @@ static void amd_pmf_update_uevents(struct amd_pmf_dev *dev, u16 event)
input_sync(dev->pmf_idev);
}
+static int amd_pmf_get_bios_output_idx(u32 action_idx)
+{
+ switch (action_idx) {
+ case PMF_POLICY_BIOS_OUTPUT_1:
+ return 0;
+ case PMF_POLICY_BIOS_OUTPUT_2:
+ return 1;
+ case PMF_POLICY_BIOS_OUTPUT_3:
+ return 2;
+ case PMF_POLICY_BIOS_OUTPUT_4:
+ return 3;
+ case PMF_POLICY_BIOS_OUTPUT_5:
+ return 4;
+ case PMF_POLICY_BIOS_OUTPUT_6:
+ return 5;
+ case PMF_POLICY_BIOS_OUTPUT_7:
+ return 6;
+ case PMF_POLICY_BIOS_OUTPUT_8:
+ return 7;
+ case PMF_POLICY_BIOS_OUTPUT_9:
+ return 8;
+ case PMF_POLICY_BIOS_OUTPUT_10:
+ return 9;
+ default:
+ return -EINVAL;
+ }
+}
+
+static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action)
+{
+ u32 bios_idx;
+
+ bios_idx = amd_pmf_get_bios_output_idx(action->action_index);
+
+ amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx);
+}
+
static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out)
{
+ struct ta_pmf_action *action;
u32 val;
int idx;
for (idx = 0; idx < out->actions_count; idx++) {
- val = out->actions_list[idx].value;
- switch (out->actions_list[idx].action_index) {
+ action = &out->actions_list[idx];
+ val = action->value;
+ switch (action->action_index) {
case PMF_POLICY_SPL:
if (dev->prev_data->spl != val) {
- amd_pmf_send_cmd(dev, SET_SPL, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update SPL: %u\n", val);
dev->prev_data->spl = val;
}
@@ -91,7 +124,7 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_SPPT:
if (dev->prev_data->sppt != val) {
- amd_pmf_send_cmd(dev, SET_SPPT, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update SPPT: %u\n", val);
dev->prev_data->sppt = val;
}
@@ -99,7 +132,7 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_FPPT:
if (dev->prev_data->fppt != val) {
- amd_pmf_send_cmd(dev, SET_FPPT, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update FPPT: %u\n", val);
dev->prev_data->fppt = val;
}
@@ -107,7 +140,7 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_SPPT_APU_ONLY:
if (dev->prev_data->sppt_apuonly != val) {
- amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update SPPT_APU_ONLY: %u\n", val);
dev->prev_data->sppt_apuonly = val;
}
@@ -115,7 +148,7 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_STT_MIN:
if (dev->prev_data->stt_minlimit != val) {
- amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update STT_MIN: %u\n", val);
dev->prev_data->stt_minlimit = val;
}
@@ -123,7 +156,8 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_STT_SKINTEMP_APU:
if (dev->prev_data->stt_skintemp_apu != val) {
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
+ fixp_q88_fromint(val), NULL);
dev_dbg(dev->dev, "update STT_SKINTEMP_APU: %u\n", val);
dev->prev_data->stt_skintemp_apu = val;
}
@@ -131,7 +165,8 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_STT_SKINTEMP_HS2:
if (dev->prev_data->stt_skintemp_hs2 != val) {
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
+ fixp_q88_fromint(val), NULL);
dev_dbg(dev->dev, "update STT_SKINTEMP_HS2: %u\n", val);
dev->prev_data->stt_skintemp_hs2 = val;
}
@@ -139,12 +174,28 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_P3T:
if (dev->prev_data->p3t_limit != val) {
- amd_pmf_send_cmd(dev, SET_P3T, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_P3T, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update P3T: %u\n", val);
dev->prev_data->p3t_limit = val;
}
break;
+ case PMF_POLICY_PMF_PPT:
+ if (dev->prev_data->pmf_ppt != val) {
+ amd_pmf_send_cmd(dev, SET_PMF_PPT, SET_CMD, val, NULL);
+ dev_dbg(dev->dev, "update PMF PPT: %u\n", val);
+ dev->prev_data->pmf_ppt = val;
+ }
+ break;
+
+ case PMF_POLICY_PMF_PPT_APU_ONLY:
+ if (dev->prev_data->pmf_ppt_apu_only != val) {
+ amd_pmf_send_cmd(dev, SET_PMF_PPT_APU_ONLY, SET_CMD, val, NULL);
+ dev_dbg(dev->dev, "update PMF PPT APU ONLY: %u\n", val);
+ dev->prev_data->pmf_ppt_apu_only = val;
+ }
+ break;
+
case PMF_POLICY_SYSTEM_STATE:
switch (val) {
case 0:
@@ -165,49 +216,22 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
break;
case PMF_POLICY_BIOS_OUTPUT_1:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(0), 0);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_2:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(1), 1);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_3:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(2), 2);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_4:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(3), 3);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_5:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(4), 4);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_6:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(5), 5);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_7:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(6), 6);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_8:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(7), 7);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_9:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(8), 8);
- break;
-
case PMF_POLICY_BIOS_OUTPUT_10:
- amd_pmf_smartpc_apply_bios_output(dev, val, BIT(9), 9);
+ amd_pmf_update_bios_output(dev, action);
break;
}
}
}
-static int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev)
+int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev)
{
struct ta_pmf_shared_memory *ta_sm = NULL;
struct ta_pmf_enact_result *out = NULL;
@@ -294,7 +318,7 @@ static void amd_pmf_invoke_cmd(struct work_struct *work)
schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms));
}
-static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
+int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
{
struct cookie_header *header;
int res;
@@ -332,6 +356,11 @@ static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
return 0;
}
+static inline bool amd_pmf_pb_valid(struct amd_pmf_dev *dev)
+{
+ return memchr_inv(dev->policy_buf, 0xff, dev->policy_sz);
+}
+
#ifdef CONFIG_AMD_PMF_DEBUG
static void amd_pmf_hex_dump_pb(struct amd_pmf_dev *dev)
{
@@ -351,14 +380,22 @@ static ssize_t amd_pmf_get_pb_data(struct file *filp, const char __user *buf,
return -EINVAL;
/* re-alloc to the new buffer length of the policy binary */
- new_policy_buf = memdup_user(buf, length);
- if (IS_ERR(new_policy_buf))
- return PTR_ERR(new_policy_buf);
+ new_policy_buf = devm_kzalloc(dev->dev, length, GFP_KERNEL);
+ if (!new_policy_buf)
+ return -ENOMEM;
- kfree(dev->policy_buf);
+ if (copy_from_user(new_policy_buf, buf, length)) {
+ devm_kfree(dev->dev, new_policy_buf);
+ return -EFAULT;
+ }
+
+ devm_kfree(dev->dev, dev->policy_buf);
dev->policy_buf = new_policy_buf;
dev->policy_sz = length;
+ if (!amd_pmf_pb_valid(dev))
+ return -EINVAL;
+
amd_pmf_hex_dump_pb(dev);
ret = amd_pmf_start_policy_engine(dev);
if (ret < 0)
@@ -405,12 +442,12 @@ static int amd_pmf_ta_open_session(struct tee_context *ctx, u32 *id, const uuid_
rc = tee_client_open_session(ctx, &sess_arg, NULL);
if (rc < 0 || sess_arg.ret != 0) {
pr_err("Failed to open TEE session err:%#x, rc:%d\n", sess_arg.ret, rc);
- return rc;
+ return rc ?: -EINVAL;
}
*id = sess_arg.session;
- return rc;
+ return 0;
}
static int amd_pmf_register_input_device(struct amd_pmf_dev *dev)
@@ -437,7 +474,7 @@ static int amd_pmf_register_input_device(struct amd_pmf_dev *dev)
return 0;
}
-static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
+int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
{
u32 size;
int ret;
@@ -445,7 +482,9 @@ static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
dev->tee_ctx = tee_client_open_context(NULL, amd_pmf_amdtee_ta_match, NULL, NULL);
if (IS_ERR(dev->tee_ctx)) {
dev_err(dev->dev, "Failed to open TEE context\n");
- return PTR_ERR(dev->tee_ctx);
+ ret = PTR_ERR(dev->tee_ctx);
+ dev->tee_ctx = NULL;
+ return ret;
}
ret = amd_pmf_ta_open_session(dev->tee_ctx, &dev->session_id, uuid);
@@ -483,11 +522,14 @@ out_ctx:
return ret;
}
-static void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
+void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
{
+ if (!dev->tee_ctx)
+ return;
tee_shm_free(dev->fw_shm_pool);
tee_client_close_session(dev->tee_ctx, dev->session_id);
tee_client_close_context(dev->tee_ctx);
+ dev->tee_ctx = NULL;
}
int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
@@ -510,58 +552,49 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
ret = amd_pmf_set_dram_addr(dev, true);
if (ret)
- goto err_cancel_work;
+ return ret;
dev->policy_base = devm_ioremap_resource(dev->dev, dev->res);
- if (IS_ERR(dev->policy_base)) {
- ret = PTR_ERR(dev->policy_base);
- goto err_free_dram_buf;
- }
+ if (IS_ERR(dev->policy_base))
+ return PTR_ERR(dev->policy_base);
- dev->policy_buf = kzalloc(dev->policy_sz, GFP_KERNEL);
- if (!dev->policy_buf) {
- ret = -ENOMEM;
- goto err_free_dram_buf;
- }
+ dev->policy_buf = devm_kzalloc(dev->dev, dev->policy_sz, GFP_KERNEL);
+ if (!dev->policy_buf)
+ return -ENOMEM;
memcpy_fromio(dev->policy_buf, dev->policy_base, dev->policy_sz);
+ if (!amd_pmf_pb_valid(dev)) {
+ dev_info(dev->dev, "No Smart PC policy present\n");
+ return -EINVAL;
+ }
+
amd_pmf_hex_dump_pb(dev);
- dev->prev_data = kzalloc(sizeof(*dev->prev_data), GFP_KERNEL);
- if (!dev->prev_data) {
- ret = -ENOMEM;
- goto err_free_policy;
- }
+ dev->prev_data = devm_kzalloc(dev->dev, sizeof(*dev->prev_data), GFP_KERNEL);
+ if (!dev->prev_data)
+ return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
ret = amd_pmf_tee_init(dev, &amd_pmf_ta_uuid[i]);
if (ret)
- goto err_free_prev_data;
+ return ret;
ret = amd_pmf_start_policy_engine(dev);
- switch (ret) {
- case TA_PMF_TYPE_SUCCESS:
- status = true;
+ dev_dbg(dev->dev, "start policy engine ret: %d\n", ret);
+ status = ret == TA_PMF_TYPE_SUCCESS;
+ if (status) {
+ dev->cb_flag = true;
+ dev->cbi_buf.head = 0;
+ dev->cbi_buf.tail = 0;
break;
- case TA_ERROR_CRYPTO_INVALID_PARAM:
- case TA_ERROR_CRYPTO_BIN_TOO_LARGE:
- amd_pmf_tee_deinit(dev);
- status = false;
- break;
- default:
- ret = -EINVAL;
- amd_pmf_tee_deinit(dev);
- goto err_free_prev_data;
}
-
- if (status)
- break;
+ amd_pmf_tee_deinit(dev);
}
if (!status && !pb_side_load) {
ret = -EINVAL;
- goto err_free_prev_data;
+ goto err;
}
if (pb_side_load)
@@ -569,22 +602,12 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
ret = amd_pmf_register_input_device(dev);
if (ret)
- goto err_pmf_remove_pb;
+ goto err;
return 0;
-err_pmf_remove_pb:
- if (pb_side_load && dev->esbin)
- amd_pmf_remove_pb(dev);
- amd_pmf_tee_deinit(dev);
-err_free_prev_data:
- kfree(dev->prev_data);
-err_free_policy:
- kfree(dev->policy_buf);
-err_free_dram_buf:
- kfree(dev->buf);
-err_cancel_work:
- cancel_delayed_work_sync(&dev->pb_work);
+err:
+ amd_pmf_deinit_smart_pc(dev);
return ret;
}
@@ -598,11 +621,5 @@ void amd_pmf_deinit_smart_pc(struct amd_pmf_dev *dev)
amd_pmf_remove_pb(dev);
cancel_delayed_work_sync(&dev->pb_work);
- kfree(dev->prev_data);
- dev->prev_data = NULL;
- kfree(dev->policy_buf);
- dev->policy_buf = NULL;
- kfree(dev->buf);
- dev->buf = NULL;
amd_pmf_tee_deinit(dev);
}
diff --git a/drivers/platform/x86/amd/wbrf.c b/drivers/platform/x86/amd/wbrf.c
index dd197b3aebe0..3281357185d1 100644
--- a/drivers/platform/x86/amd/wbrf.c
+++ b/drivers/platform/x86/amd/wbrf.c
@@ -42,8 +42,6 @@ static BLOCKING_NOTIFIER_HEAD(wbrf_chain_head);
static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ranges_in_out *in)
{
union acpi_object argv4;
- union acpi_object *tmp;
- union acpi_object *obj;
u32 num_of_ranges = 0;
u32 num_of_elements;
u32 arg_idx = 0;
@@ -74,7 +72,8 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran
*/
num_of_elements = 2 * num_of_ranges + 2;
- tmp = kcalloc(num_of_elements, sizeof(*tmp), GFP_KERNEL);
+ union acpi_object *tmp __free(kfree) = kzalloc_objs(*tmp,
+ num_of_elements);
if (!tmp)
return -ENOMEM;
@@ -101,24 +100,19 @@ static int wbrf_record(struct acpi_device *adev, uint8_t action, struct wbrf_ran
tmp[arg_idx++].integer.value = in->band_list[i].end;
}
- obj = acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
- WBRF_REVISION, WBRF_RECORD, &argv4);
+ union acpi_object *obj __free(kfree) =
+ acpi_evaluate_dsm(adev->handle, &wifi_acpi_dsm_guid,
+ WBRF_REVISION, WBRF_RECORD, &argv4);
if (!obj)
return -EINVAL;
- if (obj->type != ACPI_TYPE_INTEGER) {
- ret = -EINVAL;
- goto out;
- }
+ if (obj->type != ACPI_TYPE_INTEGER)
+ return -EINVAL;
ret = obj->integer.value;
if (ret)
- ret = -EINVAL;
-
-out:
- ACPI_FREE(obj);
- kfree(tmp);
+ return -EINVAL;
return ret;
}
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c
index 1417e230edbd..fbc30f1f8abd 100644
--- a/drivers/platform/x86/apple-gmux.c
+++ b/drivers/platform/x86/apple-gmux.c
@@ -799,7 +799,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
return -ENODEV;
}
- gmux_data = kzalloc(sizeof(*gmux_data), GFP_KERNEL);
+ gmux_data = kzalloc_obj(*gmux_data);
if (!gmux_data)
return -ENOMEM;
pnp_set_drvdata(pnp, gmux_data);
diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c
new file mode 100644
index 000000000000..495dc1e31d40
--- /dev/null
+++ b/drivers/platform/x86/asus-armoury.c
@@ -0,0 +1,1167 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Asus Armoury (WMI) attributes driver.
+ *
+ * This driver uses the fw_attributes class to expose various WMI functions
+ * that are present in many gaming and some non-gaming ASUS laptops.
+ *
+ * These typically don't fit anywhere else in the sysfs such as under LED class,
+ * hwmon or others, and are set in Windows using the ASUS Armoury Crate tool.
+ *
+ * Copyright(C) 2024 Luke Jones <luke@ljones.dev>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/kmod.h>
+#include <linux/kobject.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/platform_data/x86/asus-wmi.h>
+#include <linux/printk.h>
+#include <linux/power_supply.h>
+#include <linux/sysfs.h>
+
+#include "asus-armoury.h"
+#include "firmware_attributes_class.h"
+
+#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C"
+
+#define ASUS_MINI_LED_MODE_MASK GENMASK(1, 0)
+/* Standard modes for devices with only on/off */
+#define ASUS_MINI_LED_OFF 0x00
+#define ASUS_MINI_LED_ON 0x01
+/* Like "on" but the effect is more vibrant or brighter */
+#define ASUS_MINI_LED_STRONG_MODE 0x02
+/* New modes for devices with 3 mini-led mode types */
+#define ASUS_MINI_LED_2024_WEAK 0x00
+#define ASUS_MINI_LED_2024_STRONG 0x01
+#define ASUS_MINI_LED_2024_OFF 0x02
+
+/* Power tunable attribute name defines */
+#define ATTR_PPT_PL1_SPL "ppt_pl1_spl"
+#define ATTR_PPT_PL2_SPPT "ppt_pl2_sppt"
+#define ATTR_PPT_PL3_FPPT "ppt_pl3_fppt"
+#define ATTR_PPT_APU_SPPT "ppt_apu_sppt"
+#define ATTR_PPT_PLATFORM_SPPT "ppt_platform_sppt"
+#define ATTR_NV_DYNAMIC_BOOST "nv_dynamic_boost"
+#define ATTR_NV_TEMP_TARGET "nv_temp_target"
+#define ATTR_NV_BASE_TGP "nv_base_tgp"
+#define ATTR_NV_TGP "nv_tgp"
+
+#define ASUS_ROG_TUNABLE_DC 0
+#define ASUS_ROG_TUNABLE_AC 1
+
+struct rog_tunables {
+ const struct power_limits *power_limits;
+ u32 ppt_pl1_spl; // cpu
+ u32 ppt_pl2_sppt; // cpu
+ u32 ppt_pl3_fppt; // cpu
+ u32 ppt_apu_sppt; // plat
+ u32 ppt_platform_sppt; // plat
+
+ u32 nv_dynamic_boost;
+ u32 nv_temp_target;
+ u32 nv_tgp;
+};
+
+struct asus_armoury_priv {
+ struct device *fw_attr_dev;
+ struct kset *fw_attr_kset;
+
+ /*
+ * Mutex to protect eGPU activation/deactivation
+ * sequences and dGPU connection status:
+ * do not allow concurrent changes or changes
+ * before a reboot if dGPU got disabled.
+ */
+ struct mutex egpu_mutex;
+
+ /* Index 0 for DC, 1 for AC */
+ struct rog_tunables *rog_tunables[2];
+
+ u32 mini_led_dev_id;
+ u32 gpu_mux_dev_id;
+};
+
+static struct asus_armoury_priv asus_armoury = {
+ .egpu_mutex = __MUTEX_INITIALIZER(asus_armoury.egpu_mutex),
+};
+
+struct fw_attrs_group {
+ bool pending_reboot;
+};
+
+static struct fw_attrs_group fw_attrs = {
+ .pending_reboot = false,
+};
+
+struct asus_attr_group {
+ const struct attribute_group *attr_group;
+ u32 wmi_devid;
+};
+
+static void asus_set_reboot_and_signal_event(void)
+{
+ fw_attrs.pending_reboot = true;
+ kobject_uevent(&asus_armoury.fw_attr_dev->kobj, KOBJ_CHANGE);
+}
+
+static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%d\n", fw_attrs.pending_reboot);
+}
+
+static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
+
+static bool asus_bios_requires_reboot(struct kobj_attribute *attr)
+{
+ return !strcmp(attr->attr.name, "gpu_mux_mode") ||
+ !strcmp(attr->attr.name, "panel_hd_mode");
+}
+
+/**
+ * armoury_has_devstate() - Check presence of the WMI function state.
+ *
+ * @dev_id: The WMI method ID to check for presence.
+ *
+ * Returns: true iif method is supported.
+ */
+static bool armoury_has_devstate(u32 dev_id)
+{
+ u32 retval;
+ int status;
+
+ status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, &retval);
+ pr_debug("%s called (0x%08x), retval: 0x%08x\n", __func__, dev_id, retval);
+
+ return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT);
+}
+
+/**
+ * armoury_get_devstate() - Get the WMI function state.
+ * @attr: NULL or the kobj_attribute associated to called WMI function.
+ * @dev_id: The WMI method ID to call.
+ * @retval:
+ * * non-NULL pointer to where to store the value returned from WMI
+ * * with the function presence bit cleared.
+ *
+ * Intended usage is from sysfs attribute checking associated WMI function.
+ *
+ * Returns:
+ * * %-ENODEV - method ID is unsupported.
+ * * %0 - successful and retval is filled.
+ * * %other - error from WMI call.
+ */
+static int armoury_get_devstate(struct kobj_attribute *attr, u32 *retval, u32 dev_id)
+{
+ int err;
+
+ err = asus_wmi_get_devstate_dsts(dev_id, retval);
+ if (err) {
+ if (attr)
+ pr_err("Failed to get %s: %d\n", attr->attr.name, err);
+ else
+ pr_err("Failed to get devstate for 0x%x: %d\n", dev_id, err);
+
+ return err;
+ }
+
+ /*
+ * asus_wmi_get_devstate_dsts will populate retval with WMI return, but
+ * the true value is expressed when ASUS_WMI_DSTS_PRESENCE_BIT is clear.
+ */
+ *retval &= ~ASUS_WMI_DSTS_PRESENCE_BIT;
+
+ return 0;
+}
+
+/**
+ * armoury_set_devstate() - Set the WMI function state.
+ * @attr: The kobj_attribute associated to called WMI function.
+ * @dev_id: The WMI method ID to call.
+ * @value: The new value to be set.
+ * @retval: Where to store the value returned from WMI or NULL.
+ *
+ * Intended usage is from sysfs attribute setting associated WMI function.
+ * Before calling the presence of the function should be checked.
+ *
+ * Every WMI write MUST go through this function to enforce safety checks.
+ *
+ * Results !1 is usually considered a fail by ASUS, but some WMI methods
+ * (like eGPU or CPU cores) do use > 1 to return a status code or similar:
+ * in these cases caller is interested in the actual return value
+ * and should perform relevant checks.
+ *
+ * Returns:
+ * * %-EINVAL - attempt to set a dangerous or unsupported value.
+ * * %-EIO - WMI function returned an error.
+ * * %0 - successful and retval is filled.
+ * * %other - error from WMI call.
+ */
+static int armoury_set_devstate(struct kobj_attribute *attr,
+ u32 value, u32 *retval, u32 dev_id)
+{
+ u32 result;
+ int err;
+
+ /*
+ * Prevent developers from bricking devices or issuing dangerous
+ * commands that can be difficult or impossible to recover from.
+ */
+ switch (dev_id) {
+ case ASUS_WMI_DEVID_APU_MEM:
+ /*
+ * A hard reset might suffice to save the device,
+ * but there is no value in sending these commands.
+ */
+ if (value == 0x100 || value == 0x101) {
+ pr_err("Refusing to set APU memory to unsafe value: 0x%x\n", value);
+ return -EINVAL;
+ }
+ break;
+ default:
+ /* No problems are known for this dev_id */
+ break;
+ }
+
+ err = asus_wmi_set_devstate(dev_id, value, retval ? retval : &result);
+ if (err) {
+ if (attr)
+ pr_err("Failed to set %s: %d\n", attr->attr.name, err);
+ else
+ pr_err("Failed to set devstate for 0x%x: %d\n", dev_id, err);
+
+ return err;
+ }
+
+ /*
+ * If retval == NULL caller is uninterested in return value:
+ * perform the most common result check here.
+ */
+ if ((retval == NULL) && (result == 0)) {
+ pr_err("Failed to set %s: (result): 0x%x\n", attr->attr.name, result);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int armoury_attr_enum_list(char *buf, size_t enum_values)
+{
+ size_t i;
+ int len = 0;
+
+ for (i = 0; i < enum_values; i++) {
+ if (i == 0)
+ len += sysfs_emit_at(buf, len, "%zu", i);
+ else
+ len += sysfs_emit_at(buf, len, ";%zu", i);
+ }
+ len += sysfs_emit_at(buf, len, "\n");
+
+ return len;
+}
+
+ssize_t armoury_attr_uint_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count, u32 min, u32 max,
+ u32 *store_value, u32 wmi_dev)
+{
+ u32 value;
+ int err;
+
+ err = kstrtou32(buf, 10, &value);
+ if (err)
+ return err;
+
+ if (value < min || value > max)
+ return -EINVAL;
+
+ err = armoury_set_devstate(attr, value, NULL, wmi_dev);
+ if (err)
+ return err;
+
+ if (store_value != NULL)
+ *store_value = value;
+ sysfs_notify(kobj, NULL, attr->attr.name);
+
+ if (asus_bios_requires_reboot(attr))
+ asus_set_reboot_and_signal_event();
+
+ return count;
+}
+
+ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf, u32 wmi_dev)
+{
+ u32 result;
+ int err;
+
+ err = armoury_get_devstate(attr, &result, wmi_dev);
+ if (err)
+ return err;
+
+ return sysfs_emit(buf, "%u\n", result);
+}
+
+static ssize_t enum_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "enumeration\n");
+}
+
+static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "integer\n");
+}
+
+/* Mini-LED mode **************************************************************/
+
+/* Values map for mini-led modes on 2023 and earlier models. */
+static u32 mini_led_mode1_map[] = {
+ [0] = ASUS_MINI_LED_OFF,
+ [1] = ASUS_MINI_LED_ON,
+};
+
+/* Values map for mini-led modes on 2024 and later models. */
+static u32 mini_led_mode2_map[] = {
+ [0] = ASUS_MINI_LED_2024_OFF,
+ [1] = ASUS_MINI_LED_2024_WEAK,
+ [2] = ASUS_MINI_LED_2024_STRONG,
+};
+
+static ssize_t mini_led_mode_current_value_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ u32 *mini_led_mode_map;
+ size_t mini_led_mode_map_size;
+ u32 i, mode;
+ int err;
+
+ switch (asus_armoury.mini_led_dev_id) {
+ case ASUS_WMI_DEVID_MINI_LED_MODE:
+ mini_led_mode_map = mini_led_mode1_map;
+ mini_led_mode_map_size = ARRAY_SIZE(mini_led_mode1_map);
+ break;
+
+ case ASUS_WMI_DEVID_MINI_LED_MODE2:
+ mini_led_mode_map = mini_led_mode2_map;
+ mini_led_mode_map_size = ARRAY_SIZE(mini_led_mode2_map);
+ break;
+
+ default:
+ pr_err("Unrecognized mini-LED device: %u\n", asus_armoury.mini_led_dev_id);
+ return -ENODEV;
+ }
+
+ err = armoury_get_devstate(attr, &mode, asus_armoury.mini_led_dev_id);
+ if (err)
+ return err;
+
+ mode = FIELD_GET(ASUS_MINI_LED_MODE_MASK, mode);
+
+ for (i = 0; i < mini_led_mode_map_size; i++)
+ if (mode == mini_led_mode_map[i])
+ return sysfs_emit(buf, "%u\n", i);
+
+ pr_warn("Unrecognized mini-LED mode: %u", mode);
+ return -EINVAL;
+}
+
+static ssize_t mini_led_mode_current_value_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ u32 *mini_led_mode_map;
+ size_t mini_led_mode_map_size;
+ char mapped_value[12];
+ u32 mode;
+ int err;
+
+ err = kstrtou32(buf, 10, &mode);
+ if (err)
+ return err;
+
+ switch (asus_armoury.mini_led_dev_id) {
+ case ASUS_WMI_DEVID_MINI_LED_MODE:
+ mini_led_mode_map = mini_led_mode1_map;
+ mini_led_mode_map_size = ARRAY_SIZE(mini_led_mode1_map);
+ break;
+
+ case ASUS_WMI_DEVID_MINI_LED_MODE2:
+ mini_led_mode_map = mini_led_mode2_map;
+ mini_led_mode_map_size = ARRAY_SIZE(mini_led_mode2_map);
+ break;
+
+ default:
+ pr_err("Unrecognized mini-LED devid: %u\n", asus_armoury.mini_led_dev_id);
+ return -EINVAL;
+ }
+
+ if (mode >= mini_led_mode_map_size) {
+ pr_warn("mini-LED mode unrecognized device: %u\n", mode);
+ return -ENODEV;
+ }
+
+ /*
+ * armoury_attr_uint_store() parses and sends the value from the
+ * passed buffer; hand it the mapped firmware value so the device
+ * receives the translated mode instead of the raw index.
+ */
+ snprintf(mapped_value, sizeof(mapped_value), "%u", mini_led_mode_map[mode]);
+
+ return armoury_attr_uint_store(kobj, attr, mapped_value, count, 0,
+ mini_led_mode_map[mode], NULL,
+ asus_armoury.mini_led_dev_id);
+}
+
+static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ switch (asus_armoury.mini_led_dev_id) {
+ case ASUS_WMI_DEVID_MINI_LED_MODE:
+ return armoury_attr_enum_list(buf, ARRAY_SIZE(mini_led_mode1_map));
+ case ASUS_WMI_DEVID_MINI_LED_MODE2:
+ return armoury_attr_enum_list(buf, ARRAY_SIZE(mini_led_mode2_map));
+ default:
+ return -ENODEV;
+ }
+}
+ASUS_ATTR_GROUP_ENUM(mini_led_mode, "mini_led_mode", "Set the mini-LED backlight mode");
+
+static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ bool optimus;
+
+ err = kstrtobool(buf, &optimus);
+ if (err)
+ return err;
+
+ if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU)) {
+ err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_DGPU);
+ if (err)
+ return err;
+ if (result && !optimus) {
+ pr_warn("Cannot switch MUX to dGPU mode when dGPU is disabled: %02X\n",
+ result);
+ return -ENODEV;
+ }
+ }
+
+ if (armoury_has_devstate(ASUS_WMI_DEVID_EGPU)) {
+ err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_EGPU);
+ if (err)
+ return err;
+ if (result && !optimus) {
+ pr_warn("Cannot switch MUX to dGPU mode when eGPU is enabled\n");
+ return -EBUSY;
+ }
+ }
+
+ err = armoury_set_devstate(attr, optimus ? 1 : 0, NULL, asus_armoury.gpu_mux_dev_id);
+ if (err)
+ return err;
+
+ sysfs_notify(kobj, NULL, attr->attr.name);
+ asus_set_reboot_and_signal_event();
+
+ return count;
+}
+ASUS_WMI_SHOW_INT(gpu_mux_mode_current_value, asus_armoury.gpu_mux_dev_id);
+ASUS_ATTR_GROUP_BOOL(gpu_mux_mode, "gpu_mux_mode", "Set the GPU display MUX mode");
+
+static ssize_t dgpu_disable_current_value_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf,
+ size_t count)
+{
+ int result, err;
+ bool disable;
+
+ err = kstrtobool(buf, &disable);
+ if (err)
+ return err;
+
+ if (asus_armoury.gpu_mux_dev_id) {
+ err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id);
+ if (err)
+ return err;
+ if (!result && disable) {
+ pr_warn("Cannot disable dGPU when the MUX is in dGPU mode\n");
+ return -EBUSY;
+ }
+ }
+
+ scoped_guard(mutex, &asus_armoury.egpu_mutex) {
+ err = armoury_set_devstate(attr, disable ? 1 : 0, NULL, ASUS_WMI_DEVID_DGPU);
+ if (err)
+ return err;
+ }
+
+ sysfs_notify(kobj, NULL, attr->attr.name);
+
+ return count;
+}
+ASUS_WMI_SHOW_INT(dgpu_disable_current_value, ASUS_WMI_DEVID_DGPU);
+ASUS_ATTR_GROUP_BOOL(dgpu_disable, "dgpu_disable", "Disable the dGPU");
+
+/* Values map for eGPU activation requests. */
+static u32 egpu_status_map[] = {
+ [0] = 0x00000000U,
+ [1] = 0x00000001U,
+ [2] = 0x00000101U,
+ [3] = 0x00000201U,
+};
+
+/*
+ * armoury_pci_rescan() - Performs a PCI rescan
+ *
+ * Bring up any GPU that has been hotplugged in the system.
+ */
+static void armoury_pci_rescan(void)
+{
+ struct pci_bus *b = NULL;
+
+ pci_lock_rescan_remove();
+ while ((b = pci_find_next_bus(b)) != NULL)
+ pci_rescan_bus(b);
+ pci_unlock_rescan_remove();
+}
+
+/*
+ * The ACPI call to enable the eGPU might also disable the internal dGPU,
+ * but this is not always the case and on certain models enabling the eGPU
+ * when the dGPU is either still active or has been disabled without rebooting
+ * will make both GPUs malfunction and the kernel will detect many
+ * PCI AER unrecoverable errors.
+ */
+static ssize_t egpu_enable_current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int err;
+ u32 requested, enable, result;
+
+ err = kstrtou32(buf, 10, &requested);
+ if (err)
+ return err;
+
+ if (requested >= ARRAY_SIZE(egpu_status_map))
+ return -EINVAL;
+ enable = egpu_status_map[requested];
+
+ scoped_guard(mutex, &asus_armoury.egpu_mutex) {
+ /* Ensure the eGPU is connected before attempting to activate it. */
+ if (enable) {
+ err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_EGPU_CONNECTED);
+ if (err) {
+ pr_warn("Failed to get eGPU connection status: %d\n", err);
+ return err;
+ }
+ if (!result) {
+ pr_warn("Cannot activate eGPU while undetected\n");
+ return -ENOENT;
+ }
+ }
+
+ if (asus_armoury.gpu_mux_dev_id) {
+ err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id);
+ if (err)
+ return err;
+
+ if (!result && enable) {
+ pr_warn("Cannot enable eGPU when the MUX is in dGPU mode\n");
+ return -ENODEV;
+ }
+ }
+
+ err = armoury_set_devstate(attr, enable, &result, ASUS_WMI_DEVID_EGPU);
+ if (err) {
+ pr_err("Failed to set %s: %d\n", attr->attr.name, err);
+ return err;
+ }
+
+ /*
+ * ACPI returns value 0x01 on success and 0x02 on a partial activation:
+ * performing a pci rescan will bring up the device in pci-e 3.0 speed,
+ * after a reboot the device will work at full speed.
+ */
+ switch (result) {
+ case 0x01:
+ /*
+ * When a GPU is in use it does not get disconnected even if
+ * the ACPI call returns a success.
+ */
+ if (!enable) {
+ err = armoury_get_devstate(attr, &result, ASUS_WMI_DEVID_EGPU);
+ if (err) {
+ pr_warn("Failed to ensure eGPU is deactivated: %d\n", err);
+ return err;
+ }
+
+ if (result != 0)
+ return -EBUSY;
+ }
+
+ pr_debug("Success changing the eGPU status\n");
+ break;
+ case 0x02:
+ pr_info("Success changing the eGPU status, a reboot is strongly advised\n");
+ asus_set_reboot_and_signal_event();
+ break;
+ default:
+ pr_err("Failed to change the eGPU status: wmi result is 0x%x\n", result);
+ return -EIO;
+ }
+ }
+
+ /*
+ * Perform a PCI rescan: on every tested model this is necessary
+ * to make the eGPU visible on the bus without rebooting.
+ */
+ armoury_pci_rescan();
+
+ sysfs_notify(kobj, NULL, attr->attr.name);
+
+ return count;
+}
+
+static ssize_t egpu_enable_current_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int i, err;
+ u32 status;
+
+ scoped_guard(mutex, &asus_armoury.egpu_mutex) {
+ err = armoury_get_devstate(attr, &status, ASUS_WMI_DEVID_EGPU);
+ if (err)
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(egpu_status_map); i++) {
+ if (egpu_status_map[i] == status)
+ return sysfs_emit(buf, "%u\n", i);
+ }
+
+ return -EIO;
+}
+
+static ssize_t egpu_enable_possible_values_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return armoury_attr_enum_list(buf, ARRAY_SIZE(egpu_status_map));
+}
+ASUS_ATTR_GROUP_ENUM(egpu_enable, "egpu_enable", "Enable the eGPU (also disables dGPU)");
+
+/* Device memory available to APU */
+
+/*
+ * Values map for APU reserved memory (index + 1 number of GB).
+ * Some looks out of order, but are actually correct.
+ */
+static u32 apu_mem_map[] = {
+ [0] = 0x000, /* called "AUTO" on the BIOS, is the minimum available */
+ [1] = 0x102,
+ [2] = 0x103,
+ [3] = 0x104,
+ [4] = 0x105,
+ [5] = 0x107,
+ [6] = 0x108,
+ [7] = 0x109,
+ [8] = 0x106,
+};
+
+static ssize_t apu_mem_current_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ int err;
+ u32 mem;
+
+ err = armoury_get_devstate(attr, &mem, ASUS_WMI_DEVID_APU_MEM);
+ if (err)
+ return err;
+
+ /* After 0x000 is set, a read will return 0x100 */
+ if (mem == 0x100)
+ return sysfs_emit(buf, "0\n");
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(apu_mem_map); i++) {
+ if (apu_mem_map[i] == mem)
+ return sysfs_emit(buf, "%u\n", i);
+ }
+
+ pr_warn("Unrecognised value for APU mem 0x%08x\n", mem);
+ return -EIO;
+}
+
+static ssize_t apu_mem_current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int result, err;
+ u32 requested, mem;
+
+ result = kstrtou32(buf, 10, &requested);
+ if (result)
+ return result;
+
+ if (requested >= ARRAY_SIZE(apu_mem_map))
+ return -EINVAL;
+ mem = apu_mem_map[requested];
+
+ err = armoury_set_devstate(attr, mem, NULL, ASUS_WMI_DEVID_APU_MEM);
+ if (err) {
+ pr_warn("Failed to set apu_mem 0x%x: %d\n", mem, err);
+ return err;
+ }
+
+ pr_info("APU memory changed to %uGB, reboot required\n", requested + 1);
+ sysfs_notify(kobj, NULL, attr->attr.name);
+
+ asus_set_reboot_and_signal_event();
+
+ return count;
+}
+
+static ssize_t apu_mem_possible_values_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return armoury_attr_enum_list(buf, ARRAY_SIZE(apu_mem_map));
+}
+ASUS_ATTR_GROUP_ENUM(apu_mem, "apu_mem", "Set available system RAM (in GB) for the APU to use");
+
+/* Define helper to access the current power mode tunable values */
+static inline struct rog_tunables *get_current_tunables(void)
+{
+ if (power_supply_is_system_supplied())
+ return asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC];
+
+ return asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC];
+}
+
+/* Simple attribute creation */
+ASUS_ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2\n",
+ "Show the current mode of charging");
+ASUS_ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND,
+ "Set the boot POST sound");
+ASUS_ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE,
+ "Set MCU powersaving mode");
+ASUS_ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD,
+ "Set the panel refresh overdrive");
+ASUS_ATTR_GROUP_BOOL_RW(panel_hd_mode, "panel_hd_mode", ASUS_WMI_DEVID_PANEL_HD,
+ "Set the panel HD mode to UHD<0> or FHD<1>");
+ASUS_ATTR_GROUP_BOOL_RW(screen_auto_brightness, "screen_auto_brightness",
+ ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS,
+ "Set the panel brightness to Off<0> or On<1>");
+ASUS_ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED,
+ "Show the eGPU connection status");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, ATTR_PPT_PL1_SPL, ASUS_WMI_DEVID_PPT_PL1_SPL,
+ "Set the CPU slow package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, ATTR_PPT_PL2_SPPT, ASUS_WMI_DEVID_PPT_PL2_SPPT,
+ "Set the CPU fast package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_pl3_fppt, ATTR_PPT_PL3_FPPT, ASUS_WMI_DEVID_PPT_PL3_FPPT,
+ "Set the CPU fastest package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, ATTR_PPT_APU_SPPT, ASUS_WMI_DEVID_PPT_APU_SPPT,
+ "Set the APU package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, ATTR_PPT_PLATFORM_SPPT, ASUS_WMI_DEVID_PPT_PLAT_SPPT,
+ "Set the platform package limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, ATTR_NV_DYNAMIC_BOOST, ASUS_WMI_DEVID_NV_DYN_BOOST,
+ "Set the Nvidia dynamic boost limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(nv_temp_target, ATTR_NV_TEMP_TARGET, ASUS_WMI_DEVID_NV_THERM_TARGET,
+ "Set the Nvidia max thermal limit");
+ASUS_ATTR_GROUP_ROG_TUNABLE(nv_tgp, "nv_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP,
+ "Set the additional TGP on top of the base TGP");
+ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(nv_base_tgp, ATTR_NV_BASE_TGP, ASUS_WMI_DEVID_DGPU_BASE_TGP,
+ "Read the base TGP value");
+
+/* If an attribute does not require any special case handling add it here */
+static const struct asus_attr_group armoury_attr_groups[] = {
+ { &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED },
+ { &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU },
+ { &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU },
+ { &apu_mem_attr_group, ASUS_WMI_DEVID_APU_MEM },
+
+ { &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL },
+ { &ppt_pl2_sppt_attr_group, ASUS_WMI_DEVID_PPT_PL2_SPPT },
+ { &ppt_pl3_fppt_attr_group, ASUS_WMI_DEVID_PPT_PL3_FPPT },
+ { &ppt_apu_sppt_attr_group, ASUS_WMI_DEVID_PPT_APU_SPPT },
+ { &ppt_platform_sppt_attr_group, ASUS_WMI_DEVID_PPT_PLAT_SPPT },
+ { &nv_dynamic_boost_attr_group, ASUS_WMI_DEVID_NV_DYN_BOOST },
+ { &nv_temp_target_attr_group, ASUS_WMI_DEVID_NV_THERM_TARGET },
+ { &nv_base_tgp_attr_group, ASUS_WMI_DEVID_DGPU_BASE_TGP },
+ { &nv_tgp_attr_group, ASUS_WMI_DEVID_DGPU_SET_TGP },
+
+ { &charge_mode_attr_group, ASUS_WMI_DEVID_CHARGE_MODE },
+ { &boot_sound_attr_group, ASUS_WMI_DEVID_BOOT_SOUND },
+ { &mcu_powersave_attr_group, ASUS_WMI_DEVID_MCU_POWERSAVE },
+ { &panel_od_attr_group, ASUS_WMI_DEVID_PANEL_OD },
+ { &panel_hd_mode_attr_group, ASUS_WMI_DEVID_PANEL_HD },
+ { &screen_auto_brightness_attr_group, ASUS_WMI_DEVID_SCREEN_AUTO_BRIGHTNESS },
+};
+
+/**
+ * is_power_tunable_attr - Determines if an attribute is a power-related tunable
+ * @name: The name of the attribute to check
+ *
+ * This function checks if the given attribute name is related to power tuning.
+ *
+ * Return: true if the attribute is a power-related tunable, false otherwise
+ */
+static bool is_power_tunable_attr(const char *name)
+{
+ static const char * const power_tunable_attrs[] = {
+ ATTR_PPT_PL1_SPL, ATTR_PPT_PL2_SPPT,
+ ATTR_PPT_PL3_FPPT, ATTR_PPT_APU_SPPT,
+ ATTR_PPT_PLATFORM_SPPT, ATTR_NV_DYNAMIC_BOOST,
+ ATTR_NV_TEMP_TARGET, ATTR_NV_BASE_TGP,
+ ATTR_NV_TGP
+ };
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(power_tunable_attrs); i++) {
+ if (!strcmp(name, power_tunable_attrs[i]))
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * has_valid_limit - Checks if a power-related attribute has a valid limit value
+ * @name: The name of the attribute to check
+ * @limits: Pointer to the power_limits structure containing limit values
+ *
+ * This function checks if a power-related attribute has a valid limit value.
+ * It returns false if limits is NULL or if the corresponding limit value is zero.
+ *
+ * Return: true if the attribute has a valid limit value, false otherwise
+ */
+static bool has_valid_limit(const char *name, const struct power_limits *limits)
+{
+ u32 limit_value = 0;
+
+ if (!limits)
+ return false;
+
+ if (!strcmp(name, ATTR_PPT_PL1_SPL))
+ limit_value = limits->ppt_pl1_spl_max;
+ else if (!strcmp(name, ATTR_PPT_PL2_SPPT))
+ limit_value = limits->ppt_pl2_sppt_max;
+ else if (!strcmp(name, ATTR_PPT_PL3_FPPT))
+ limit_value = limits->ppt_pl3_fppt_max;
+ else if (!strcmp(name, ATTR_PPT_APU_SPPT))
+ limit_value = limits->ppt_apu_sppt_max;
+ else if (!strcmp(name, ATTR_PPT_PLATFORM_SPPT))
+ limit_value = limits->ppt_platform_sppt_max;
+ else if (!strcmp(name, ATTR_NV_DYNAMIC_BOOST))
+ limit_value = limits->nv_dynamic_boost_max;
+ else if (!strcmp(name, ATTR_NV_TEMP_TARGET))
+ limit_value = limits->nv_temp_target_max;
+ else if (!strcmp(name, ATTR_NV_BASE_TGP) ||
+ !strcmp(name, ATTR_NV_TGP))
+ limit_value = limits->nv_tgp_max;
+
+ return limit_value > 0;
+}
+
+static int asus_fw_attr_add(void)
+{
+ const struct rog_tunables *const ac_rog_tunables =
+ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC];
+ const struct power_limits *limits;
+ bool should_create;
+ const char *name;
+ int err, i;
+
+ asus_armoury.fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
+ NULL, "%s", DRIVER_NAME);
+ if (IS_ERR(asus_armoury.fw_attr_dev)) {
+ err = PTR_ERR(asus_armoury.fw_attr_dev);
+ goto fail_class_get;
+ }
+
+ asus_armoury.fw_attr_kset = kset_create_and_add("attributes", NULL,
+ &asus_armoury.fw_attr_dev->kobj);
+ if (!asus_armoury.fw_attr_kset) {
+ err = -ENOMEM;
+ goto err_destroy_classdev;
+ }
+
+ err = sysfs_create_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
+ if (err) {
+ pr_err("Failed to create sysfs level attributes\n");
+ goto err_destroy_kset;
+ }
+
+ asus_armoury.mini_led_dev_id = 0;
+ if (armoury_has_devstate(ASUS_WMI_DEVID_MINI_LED_MODE))
+ asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE;
+ else if (armoury_has_devstate(ASUS_WMI_DEVID_MINI_LED_MODE2))
+ asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2;
+
+ if (asus_armoury.mini_led_dev_id) {
+ err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
+ &mini_led_mode_attr_group);
+ if (err) {
+ pr_err("Failed to create sysfs-group for mini_led\n");
+ goto err_remove_file;
+ }
+ }
+
+ asus_armoury.gpu_mux_dev_id = 0;
+ if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX))
+ asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX;
+ else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX_VIVO))
+ asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO;
+
+ if (asus_armoury.gpu_mux_dev_id) {
+ err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
+ &gpu_mux_mode_attr_group);
+ if (err) {
+ pr_err("Failed to create sysfs-group for gpu_mux\n");
+ goto err_remove_mini_led_group;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) {
+ if (!armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
+ continue;
+
+ /* Always create by default, unless PPT is not present */
+ should_create = true;
+ name = armoury_attr_groups[i].attr_group->name;
+
+ /* Check if this is a power-related tunable requiring limits */
+ if (ac_rog_tunables && ac_rog_tunables->power_limits &&
+ is_power_tunable_attr(name)) {
+ limits = ac_rog_tunables->power_limits;
+ /* Check only AC: if not present then DC won't be either */
+ should_create = has_valid_limit(name, limits);
+ if (!should_create)
+ pr_debug("Missing max value for tunable %s\n", name);
+ }
+
+ if (should_create) {
+ err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
+ armoury_attr_groups[i].attr_group);
+ if (err) {
+ pr_err("Failed to create sysfs-group for %s\n",
+ armoury_attr_groups[i].attr_group->name);
+ goto err_remove_groups;
+ }
+ }
+ }
+
+ return 0;
+
+err_remove_groups:
+ while (i--) {
+ if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
+ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj,
+ armoury_attr_groups[i].attr_group);
+ }
+ if (asus_armoury.gpu_mux_dev_id)
+ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group);
+err_remove_mini_led_group:
+ if (asus_armoury.mini_led_dev_id)
+ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group);
+err_remove_file:
+ sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
+err_destroy_kset:
+ kset_unregister(asus_armoury.fw_attr_kset);
+err_destroy_classdev:
+fail_class_get:
+ device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ return err;
+}
+
+/* Init / exit ****************************************************************/
+
+/* Set up the min/max and defaults for ROG tunables */
+static void init_rog_tunables(void)
+{
+ const struct power_limits *ac_limits, *dc_limits;
+ struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL;
+ const struct power_data *power_data;
+ const struct dmi_system_id *dmi_id;
+
+ /* Match the system against the power_limits table */
+ dmi_id = dmi_first_match(power_limits);
+ if (!dmi_id) {
+ pr_warn("No matching power limits found for this system\n");
+ return;
+ }
+
+ /* Get the power data for this system */
+ power_data = dmi_id->driver_data;
+ if (!power_data) {
+ pr_info("No power data available for this system\n");
+ return;
+ }
+
+ /* Initialize AC power tunables */
+ ac_limits = power_data->ac_data;
+ if (ac_limits) {
+ ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]);
+ if (!ac_rog_tunables)
+ goto err_nomem;
+
+ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables;
+ ac_rog_tunables->power_limits = ac_limits;
+
+ /* Set initial AC values */
+ ac_rog_tunables->ppt_pl1_spl =
+ ac_limits->ppt_pl1_spl_def ?
+ ac_limits->ppt_pl1_spl_def :
+ ac_limits->ppt_pl1_spl_max;
+
+ ac_rog_tunables->ppt_pl2_sppt =
+ ac_limits->ppt_pl2_sppt_def ?
+ ac_limits->ppt_pl2_sppt_def :
+ ac_limits->ppt_pl2_sppt_max;
+
+ ac_rog_tunables->ppt_pl3_fppt =
+ ac_limits->ppt_pl3_fppt_def ?
+ ac_limits->ppt_pl3_fppt_def :
+ ac_limits->ppt_pl3_fppt_max;
+
+ ac_rog_tunables->ppt_apu_sppt =
+ ac_limits->ppt_apu_sppt_def ?
+ ac_limits->ppt_apu_sppt_def :
+ ac_limits->ppt_apu_sppt_max;
+
+ ac_rog_tunables->ppt_platform_sppt =
+ ac_limits->ppt_platform_sppt_def ?
+ ac_limits->ppt_platform_sppt_def :
+ ac_limits->ppt_platform_sppt_max;
+
+ ac_rog_tunables->nv_dynamic_boost =
+ ac_limits->nv_dynamic_boost_max;
+ ac_rog_tunables->nv_temp_target =
+ ac_limits->nv_temp_target_max;
+ ac_rog_tunables->nv_tgp = ac_limits->nv_tgp_max;
+
+ pr_debug("AC power limits initialized for %s\n", dmi_id->matches[0].substr);
+ } else {
+ pr_debug("No AC PPT limits defined\n");
+ }
+
+ /* Initialize DC power tunables */
+ dc_limits = power_data->dc_data;
+ if (dc_limits) {
+ dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]);
+ if (!dc_rog_tunables) {
+ kfree(ac_rog_tunables);
+ goto err_nomem;
+ }
+
+ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables;
+ dc_rog_tunables->power_limits = dc_limits;
+
+ /* Set initial DC values */
+ dc_rog_tunables->ppt_pl1_spl =
+ dc_limits->ppt_pl1_spl_def ?
+ dc_limits->ppt_pl1_spl_def :
+ dc_limits->ppt_pl1_spl_max;
+
+ dc_rog_tunables->ppt_pl2_sppt =
+ dc_limits->ppt_pl2_sppt_def ?
+ dc_limits->ppt_pl2_sppt_def :
+ dc_limits->ppt_pl2_sppt_max;
+
+ dc_rog_tunables->ppt_pl3_fppt =
+ dc_limits->ppt_pl3_fppt_def ?
+ dc_limits->ppt_pl3_fppt_def :
+ dc_limits->ppt_pl3_fppt_max;
+
+ dc_rog_tunables->ppt_apu_sppt =
+ dc_limits->ppt_apu_sppt_def ?
+ dc_limits->ppt_apu_sppt_def :
+ dc_limits->ppt_apu_sppt_max;
+
+ dc_rog_tunables->ppt_platform_sppt =
+ dc_limits->ppt_platform_sppt_def ?
+ dc_limits->ppt_platform_sppt_def :
+ dc_limits->ppt_platform_sppt_max;
+
+ dc_rog_tunables->nv_dynamic_boost =
+ dc_limits->nv_dynamic_boost_max;
+ dc_rog_tunables->nv_temp_target =
+ dc_limits->nv_temp_target_max;
+ dc_rog_tunables->nv_tgp = dc_limits->nv_tgp_max;
+
+ pr_debug("DC power limits initialized for %s\n", dmi_id->matches[0].substr);
+ } else {
+ pr_debug("No DC PPT limits defined\n");
+ }
+
+ return;
+
+err_nomem:
+ pr_err("Failed to allocate memory for tunables\n");
+}
+
+static int __init asus_fw_init(void)
+{
+ char *wmi_uid;
+
+ wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID);
+ if (!wmi_uid)
+ return -ENODEV;
+
+ /*
+ * if equal to "ASUSWMI" then it's DCTS that can't be used for this
+ * driver, DSTS is required.
+ */
+ if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI))
+ return -ENODEV;
+
+ init_rog_tunables();
+
+ /* Must always be last step to ensure data is available */
+ return asus_fw_attr_add();
+}
+
+static void __exit asus_fw_exit(void)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(armoury_attr_groups) - 1; i >= 0; i--) {
+ if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid))
+ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj,
+ armoury_attr_groups[i].attr_group);
+ }
+
+ if (asus_armoury.gpu_mux_dev_id)
+ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group);
+
+ if (asus_armoury.mini_led_dev_id)
+ sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group);
+
+ sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
+ kset_unregister(asus_armoury.fw_attr_kset);
+ device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+
+ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]);
+ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]);
+}
+
+module_init(asus_fw_init);
+module_exit(asus_fw_exit);
+
+MODULE_IMPORT_NS("ASUS_WMI");
+MODULE_AUTHOR("Luke Jones <luke@ljones.dev>");
+MODULE_DESCRIPTION("ASUS BIOS Configuration Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("wmi:" ASUS_NB_WMI_EVENT_GUID);
diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h
new file mode 100644
index 000000000000..692978b61959
--- /dev/null
+++ b/drivers/platform/x86/asus-armoury.h
@@ -0,0 +1,2280 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Definitions for kernel modules using asus-armoury driver
+ *
+ * Copyright (c) 2024 Luke Jones <luke@ljones.dev>
+ */
+
+#ifndef _ASUS_ARMOURY_H_
+#define _ASUS_ARMOURY_H_
+
+#include <linux/dmi.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#define DRIVER_NAME "asus-armoury"
+
+/**
+ * armoury_attr_uint_store() - Send an uint to WMI method if within min/max.
+ * @kobj: Pointer to the driver object.
+ * @attr: Pointer to the attribute calling this function.
+ * @buf: The buffer to read from, this is parsed to `uint` type.
+ * @count: Required by sysfs attribute macros, pass in from the callee attr.
+ * @min: Minimum accepted value. Below this returns -EINVAL.
+ * @max: Maximum accepted value. Above this returns -EINVAL.
+ * @store_value: Pointer to where the parsed value should be stored.
+ * @wmi_dev: The WMI function ID to use.
+ *
+ * This function is intended to be generic so it can be called from any "_store"
+ * attribute which works only with integers.
+ *
+ * Integers to be sent to the WMI method is inclusive range checked and
+ * an error returned if out of range.
+ *
+ * If the value is valid and WMI is success then the sysfs attribute is notified
+ * and if asus_bios_requires_reboot() is true then reboot attribute
+ * is also notified.
+ *
+ * Returns: Either count, or an error.
+ */
+ssize_t armoury_attr_uint_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count, u32 min, u32 max,
+ u32 *store_value, u32 wmi_dev);
+
+/**
+ * armoury_attr_uint_show() - Receive an uint from a WMI method.
+ * @kobj: Pointer to the driver object.
+ * @attr: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to, as an `uint` type.
+ * @wmi_dev: The WMI function ID to use.
+ *
+ * This function is intended to be generic so it can be called from any "_show"
+ * attribute which works only with integers.
+ *
+ * Returns: Either count, or an error.
+ */
+ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf, u32 wmi_dev);
+
+#define __ASUS_ATTR_RO(_func, _name) \
+ { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = _func##_##_name##_show, \
+ }
+
+#define __ASUS_ATTR_RO_AS(_name, _show) \
+ { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = _show, \
+ }
+
+#define __ASUS_ATTR_RW(_func, _name) \
+ __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
+
+#define __WMI_STORE_INT(_attr, _min, _max, _wmi) \
+ static ssize_t _attr##_store(struct kobject *kobj, \
+ struct kobj_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ return armoury_attr_uint_store(kobj, attr, buf, count, _min, \
+ _max, NULL, _wmi); \
+ }
+
+#define ASUS_WMI_SHOW_INT(_attr, _wmi) \
+ static ssize_t _attr##_show(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+ { \
+ return armoury_attr_uint_show(kobj, attr, buf, _wmi); \
+ }
+
+/* Create functions and attributes for use in other macros or on their own */
+
+/* Shows a formatted static variable */
+#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \
+ static ssize_t _attrname##_##_prop##_show( \
+ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ return sysfs_emit(buf, _fmt, _val); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_##_prop = \
+ __ASUS_ATTR_RO(_attrname, _prop)
+
+#define __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname)\
+ ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \
+ static struct kobj_attribute attr_##_attrname##_current_value = \
+ __ASUS_ATTR_RO(_attrname, current_value); \
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __ASUS_ATTR_RO_AS(type, enum_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_possible_values.attr, \
+ &attr_##_attrname##_type.attr, \
+ NULL \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, .attrs = _attrname##_attrs \
+ }
+
+#define __ATTR_RW_INT_GROUP_ENUM(_attrname, _minv, _maxv, _wmi, _fsname,\
+ _possible, _dispname) \
+ __WMI_STORE_INT(_attrname##_current_value, _minv, _maxv, _wmi); \
+ ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \
+ static struct kobj_attribute attr_##_attrname##_current_value = \
+ __ASUS_ATTR_RW(_attrname, current_value); \
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __ASUS_ATTR_RO_AS(type, enum_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_possible_values.attr, \
+ &attr_##_attrname##_type.attr, \
+ NULL \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, .attrs = _attrname##_attrs \
+ }
+
+/* Boolean style enumeration, base macro. Requires adding show/store */
+#define __ATTR_GROUP_ENUM(_attrname, _fsname, _possible, _dispname) \
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ __ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __ASUS_ATTR_RO_AS(type, enum_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_possible_values.attr, \
+ &attr_##_attrname##_type.attr, \
+ NULL \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, .attrs = _attrname##_attrs \
+ }
+
+#define ASUS_ATTR_GROUP_BOOL_RO(_attrname, _fsname, _wmi, _dispname) \
+ __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, "0;1", _dispname)
+
+
+#define ASUS_ATTR_GROUP_BOOL_RW(_attrname, _fsname, _wmi, _dispname) \
+ __ATTR_RW_INT_GROUP_ENUM(_attrname, 0, 1, _wmi, _fsname, "0;1", _dispname)
+
+#define ASUS_ATTR_GROUP_ENUM_INT_RO(_attrname, _fsname, _wmi, _possible, _dispname) \
+ __ATTR_RO_INT_GROUP_ENUM(_attrname, _wmi, _fsname, _possible, _dispname)
+
+/*
+ * Requires <name>_current_value_show(), <name>_current_value_show()
+ */
+#define ASUS_ATTR_GROUP_BOOL(_attrname, _fsname, _dispname) \
+ static struct kobj_attribute attr_##_attrname##_current_value = \
+ __ASUS_ATTR_RW(_attrname, current_value); \
+ __ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname)
+
+/*
+ * Requires <name>_current_value_show(), <name>_current_value_show()
+ * and <name>_possible_values_show()
+ */
+#define ASUS_ATTR_GROUP_ENUM(_attrname, _fsname, _dispname) \
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ static struct kobj_attribute attr_##_attrname##_current_value = \
+ __ASUS_ATTR_RW(_attrname, current_value); \
+ static struct kobj_attribute attr_##_attrname##_possible_values = \
+ __ASUS_ATTR_RO(_attrname, possible_values); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __ASUS_ATTR_RO_AS(type, enum_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_possible_values.attr, \
+ &attr_##_attrname##_type.attr, \
+ NULL \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, .attrs = _attrname##_attrs \
+ }
+
+#define ASUS_ATTR_GROUP_INT_VALUE_ONLY_RO(_attrname, _fsname, _wmi, _dispname) \
+ ASUS_WMI_SHOW_INT(_attrname##_current_value, _wmi); \
+ static struct kobj_attribute attr_##_attrname##_current_value = \
+ __ASUS_ATTR_RO(_attrname, current_value); \
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __ASUS_ATTR_RO_AS(type, int_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_type.attr, NULL \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, .attrs = _attrname##_attrs \
+ }
+
+/*
+ * ROG PPT attributes need a little different in setup as they
+ * require rog_tunables members.
+ */
+
+#define __ROG_TUNABLE_SHOW(_prop, _attrname, _val) \
+ static ssize_t _attrname##_##_prop##_show( \
+ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ struct rog_tunables *tunables = get_current_tunables(); \
+ \
+ if (!tunables || !tunables->power_limits) \
+ return -ENODEV; \
+ \
+ return sysfs_emit(buf, "%d\n", tunables->power_limits->_val); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_##_prop = \
+ __ASUS_ATTR_RO(_attrname, _prop)
+
+#define __ROG_TUNABLE_SHOW_DEFAULT(_attrname) \
+ static ssize_t _attrname##_default_value_show( \
+ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ struct rog_tunables *tunables = get_current_tunables(); \
+ \
+ if (!tunables || !tunables->power_limits) \
+ return -ENODEV; \
+ \
+ return sysfs_emit( \
+ buf, "%d\n", \
+ tunables->power_limits->_attrname##_def ? \
+ tunables->power_limits->_attrname##_def : \
+ tunables->power_limits->_attrname##_max); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_default_value = \
+ __ASUS_ATTR_RO(_attrname, default_value)
+
+#define __ROG_TUNABLE_RW(_attr, _wmi) \
+ static ssize_t _attr##_current_value_store( \
+ struct kobject *kobj, struct kobj_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ struct rog_tunables *tunables = get_current_tunables(); \
+ \
+ if (!tunables || !tunables->power_limits) \
+ return -ENODEV; \
+ \
+ if (tunables->power_limits->_attr##_min == \
+ tunables->power_limits->_attr##_max) \
+ return -EINVAL; \
+ \
+ return armoury_attr_uint_store(kobj, attr, buf, count, \
+ tunables->power_limits->_attr##_min, \
+ tunables->power_limits->_attr##_max, \
+ &tunables->_attr, _wmi); \
+ } \
+ static ssize_t _attr##_current_value_show( \
+ struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+ { \
+ struct rog_tunables *tunables = get_current_tunables(); \
+ \
+ if (!tunables) \
+ return -ENODEV; \
+ \
+ return sysfs_emit(buf, "%u\n", tunables->_attr); \
+ } \
+ static struct kobj_attribute attr_##_attr##_current_value = \
+ __ASUS_ATTR_RW(_attr, current_value)
+
+#define ASUS_ATTR_GROUP_ROG_TUNABLE(_attrname, _fsname, _wmi, _dispname) \
+ __ROG_TUNABLE_RW(_attrname, _wmi); \
+ __ROG_TUNABLE_SHOW_DEFAULT(_attrname); \
+ __ROG_TUNABLE_SHOW(min_value, _attrname, _attrname##_min); \
+ __ROG_TUNABLE_SHOW(max_value, _attrname, _attrname##_max); \
+ __ATTR_SHOW_FMT(scalar_increment, _attrname, "%d\n", 1); \
+ __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __ASUS_ATTR_RO_AS(type, int_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_default_value.attr, \
+ &attr_##_attrname##_min_value.attr, \
+ &attr_##_attrname##_max_value.attr, \
+ &attr_##_attrname##_scalar_increment.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_type.attr, \
+ NULL \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, .attrs = _attrname##_attrs \
+ }
+
+/* Default is always the maximum value unless *_def is specified */
+struct power_limits {
+ u8 ppt_pl1_spl_min;
+ u8 ppt_pl1_spl_def;
+ u8 ppt_pl1_spl_max;
+ u8 ppt_pl2_sppt_min;
+ u8 ppt_pl2_sppt_def;
+ u8 ppt_pl2_sppt_max;
+ u8 ppt_pl3_fppt_min;
+ u8 ppt_pl3_fppt_def;
+ u8 ppt_pl3_fppt_max;
+ u8 ppt_apu_sppt_min;
+ u8 ppt_apu_sppt_def;
+ u8 ppt_apu_sppt_max;
+ u8 ppt_platform_sppt_min;
+ u8 ppt_platform_sppt_def;
+ u8 ppt_platform_sppt_max;
+ /* Nvidia GPU specific, default is always max */
+ u8 nv_dynamic_boost_def; // unused. exists for macro
+ u8 nv_dynamic_boost_min;
+ u8 nv_dynamic_boost_max;
+ u8 nv_temp_target_def; // unused. exists for macro
+ u8 nv_temp_target_min;
+ u8 nv_temp_target_max;
+ u8 nv_tgp_def; // unused. exists for macro
+ u8 nv_tgp_min;
+ u8 nv_tgp_max;
+};
+
+struct power_data {
+ const struct power_limits *ac_data;
+ const struct power_limits *dc_data;
+ bool requires_fan_curve;
+};
+
+/*
+ * For each available attribute there must be a min and a max.
+ * _def is not required and will be assumed to be default == max if missing.
+ */
+static const struct dmi_system_id power_limits[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA401EA"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 95,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 100,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 71,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 71,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 71,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA401UM"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 15,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 31,
+ .ppt_pl2_sppt_max = 44,
+ .ppt_pl3_fppt_min = 45,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA401UV"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 75,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 31,
+ .ppt_pl2_sppt_max = 44,
+ .ppt_pl3_fppt_min = 45,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA401W"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 75,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 30,
+ .ppt_pl2_sppt_min = 31,
+ .ppt_pl2_sppt_max = 44,
+ .ppt_pl3_fppt_min = 45,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA507N"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA507UV"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA507R"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA507X"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 85,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA507Z"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 105,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 15,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 85,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 60,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA607NU"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 25,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA607P"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_def = 100,
+ .ppt_pl1_spl_max = 135,
+ .ppt_pl2_sppt_min = 30,
+ .ppt_pl2_sppt_def = 115,
+ .ppt_pl2_sppt_max = 135,
+ .ppt_pl3_fppt_min = 30,
+ .ppt_pl3_fppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_def = 60,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 25,
+ .ppt_pl3_fppt_max = 80,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA608UM"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 90,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_def = 65,
+ .ppt_pl3_fppt_max = 90,
+ .nv_dynamic_boost_min = 10,
+ .nv_dynamic_boost_max = 15,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 100,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA608WI"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 90,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 90,
+ .ppt_pl2_sppt_max = 90,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_def = 90,
+ .ppt_pl3_fppt_max = 90,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_def = 65,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA617NS"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 15,
+ .ppt_apu_sppt_max = 80,
+ .ppt_platform_sppt_min = 30,
+ .ppt_platform_sppt_max = 120,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 25,
+ .ppt_apu_sppt_max = 35,
+ .ppt_platform_sppt_min = 45,
+ .ppt_platform_sppt_max = 100,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA617NT"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 15,
+ .ppt_apu_sppt_max = 80,
+ .ppt_platform_sppt_min = 30,
+ .ppt_platform_sppt_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 15,
+ .ppt_apu_sppt_max = 45,
+ .ppt_platform_sppt_min = 30,
+ .ppt_platform_sppt_max = 50,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA617XS"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 15,
+ .ppt_apu_sppt_max = 80,
+ .ppt_platform_sppt_min = 30,
+ .ppt_platform_sppt_max = 120,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 25,
+ .ppt_apu_sppt_max = 35,
+ .ppt_platform_sppt_min = 45,
+ .ppt_platform_sppt_max = 100,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FA617XT"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 15,
+ .ppt_apu_sppt_max = 80,
+ .ppt_platform_sppt_min = 30,
+ .ppt_platform_sppt_max = 145,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 25,
+ .ppt_apu_sppt_max = 35,
+ .ppt_platform_sppt_min = 45,
+ .ppt_platform_sppt_max = 100,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FX507VI"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 135,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 60,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FX507VV"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 115,
+ .ppt_pl1_spl_max = 135,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 60,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FX507Z"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 15,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 60,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "FX607VU"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 115,
+ .ppt_pl1_spl_max = 135,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 60,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA401Q"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 15,
+ .ppt_pl2_sppt_max = 80,
+ },
+ .dc_data = NULL,
+ },
+ },
+ {
+ .matches = {
+ // This model is full AMD. No Nvidia dGPU.
+ DMI_MATCH(DMI_BOARD_NAME, "GA402R"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 15,
+ .ppt_apu_sppt_max = 80,
+ .ppt_platform_sppt_min = 30,
+ .ppt_platform_sppt_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_apu_sppt_min = 25,
+ .ppt_apu_sppt_def = 30,
+ .ppt_apu_sppt_max = 45,
+ .ppt_platform_sppt_min = 40,
+ .ppt_platform_sppt_max = 60,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA402X"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 35,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_def = 65,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA403UI"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 65,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA403UV"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 65,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA403WM"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 0,
+ .nv_dynamic_boost_max = 15,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 85,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA403WR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 0,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 80,
+ .nv_tgp_max = 95,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA403WW"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 0,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 80,
+ .nv_tgp_max = 95,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA503QM"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 35,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_max = 80,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA503QR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 35,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_max = 80,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA503R"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 35,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 65,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 25,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 60,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GA605W"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 85,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 31,
+ .ppt_pl2_sppt_max = 44,
+ .ppt_pl3_fppt_min = 45,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU603Z"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 60,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 40,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 40,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ }
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU604V"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 65,
+ .ppt_pl1_spl_max = 120,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_max = 150,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 40,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 40,
+ .ppt_pl2_sppt_max = 60,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU605CP"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 45,
+ .ppt_pl1_spl_max = 75,
+ .ppt_pl2_sppt_min = 56,
+ .ppt_pl2_sppt_max = 95,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 15,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_def = 75,
+ .nv_tgp_max = 95,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 75,
+ .ppt_pl2_sppt_min = 32,
+ .ppt_pl2_sppt_max = 95,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU605CR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 38,
+ .ppt_pl2_sppt_max = 110,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 80,
+ .nv_tgp_def = 90,
+ .nv_tgp_max = 105,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 38,
+ .ppt_pl2_sppt_max = 110,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU605CW"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 45,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 56,
+ .ppt_pl2_sppt_max = 110,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 80,
+ .nv_tgp_def = 90,
+ .nv_tgp_max = 110,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 32,
+ .ppt_pl2_sppt_max = 110,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU605CX"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 45,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 56,
+ .ppt_pl2_sppt_max = 110,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 7,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 95,
+ .nv_tgp_def = 100,
+ .nv_tgp_max = 110,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 85,
+ .ppt_pl2_sppt_min = 32,
+ .ppt_pl2_sppt_max = 110,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU605MU"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 85,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 38,
+ .ppt_pl2_sppt_max = 53,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GU605M"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 38,
+ .ppt_pl2_sppt_max = 53,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GV301Q"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_max = 80,
+ },
+ .dc_data = NULL,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GV301R"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 45,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 54,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GV302XU"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 60,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GV302XV"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 60,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 35,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GV601R"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 35,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 100,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_def = 80,
+ .ppt_pl3_fppt_max = 125,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 28,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 54,
+ .ppt_pl2_sppt_max = 60,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_def = 80,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GV601V"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 100,
+ .ppt_pl1_spl_max = 110,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 40,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 40,
+ .ppt_pl2_sppt_max = 60,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GX650P"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 110,
+ .ppt_pl1_spl_max = 130,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 125,
+ .ppt_pl2_sppt_max = 130,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_def = 125,
+ .ppt_pl3_fppt_max = 135,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_def = 25,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_def = 35,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_def = 42,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GX650RX"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 70,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_def = 70,
+ .ppt_pl2_sppt_max = 100,
+ .ppt_pl3_fppt_min = 28,
+ .ppt_pl3_fppt_def = 110,
+ .ppt_pl3_fppt_max = 125,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 76,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 50,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 50,
+ .ppt_pl3_fppt_min = 28,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 76,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "GZ302EA"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 60,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 32,
+ .ppt_pl2_sppt_def = 75,
+ .ppt_pl2_sppt_max = 92,
+ .ppt_pl3_fppt_min = 45,
+ .ppt_pl3_fppt_def = 86,
+ .ppt_pl3_fppt_max = 93,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 45,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 32,
+ .ppt_pl2_sppt_def = 52,
+ .ppt_pl2_sppt_max = 92,
+ .ppt_pl3_fppt_min = 45,
+ .ppt_pl3_fppt_def = 71,
+ .ppt_pl3_fppt_max = 93,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G513I"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ /* Yes this laptop is very limited */
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 15,
+ .ppt_pl2_sppt_max = 80,
+ },
+ .dc_data = NULL,
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G513QM"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ /* Yes this laptop is very limited */
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 100,
+ .ppt_pl2_sppt_min = 15,
+ .ppt_pl2_sppt_max = 190,
+ },
+ .dc_data = NULL,
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G513QY"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ /* Advantage Edition Laptop, no PL1 or PL2 limits */
+ .ppt_apu_sppt_min = 15,
+ .ppt_apu_sppt_max = 100,
+ .ppt_platform_sppt_min = 70,
+ .ppt_platform_sppt_max = 190,
+ },
+ .dc_data = NULL,
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G513R"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 35,
+ .ppt_pl1_spl_max = 90,
+ .ppt_pl2_sppt_min = 54,
+ .ppt_pl2_sppt_max = 100,
+ .ppt_pl3_fppt_min = 54,
+ .ppt_pl3_fppt_max = 125,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 50,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 50,
+ .ppt_pl3_fppt_min = 28,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G614FP"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_max = 120,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_def = 140,
+ .ppt_pl2_sppt_max = 165,
+ .ppt_pl3_fppt_min = 65,
+ .ppt_pl3_fppt_def = 140,
+ .ppt_pl3_fppt_max = 165,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 15,
+ .nv_tgp_min = 50,
+ .nv_tgp_max = 100,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 75,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G614FR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_max = 120,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_def = 140,
+ .ppt_pl2_sppt_max = 162,
+ .ppt_pl3_fppt_min = 65,
+ .ppt_pl3_fppt_def = 140,
+ .ppt_pl3_fppt_max = 162,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_tgp_min = 65,
+ .nv_tgp_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 75,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G614J"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 140,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 175,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 70,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G615LR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 140,
+ .ppt_pl1_spl_max = 175,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 175,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_tgp_min = 65,
+ .nv_tgp_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 70,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G634J"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 140,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 175,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 70,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G713PV"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_def = 120,
+ .ppt_pl1_spl_max = 130,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_def = 125,
+ .ppt_pl2_sppt_max = 130,
+ .ppt_pl3_fppt_min = 65,
+ .ppt_pl3_fppt_def = 125,
+ .ppt_pl3_fppt_max = 130,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 75,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G733C"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 170,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 175,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 35,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G733P"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 30,
+ .ppt_pl1_spl_def = 100,
+ .ppt_pl1_spl_max = 130,
+ .ppt_pl2_sppt_min = 65,
+ .ppt_pl2_sppt_def = 125,
+ .ppt_pl2_sppt_max = 130,
+ .ppt_pl3_fppt_min = 65,
+ .ppt_pl3_fppt_def = 125,
+ .ppt_pl3_fppt_max = 130,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 65,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 65,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 75,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G733QS"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 15,
+ .ppt_pl2_sppt_max = 80,
+ },
+ .requires_fan_curve = false,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G814J"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 140,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 140,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 70,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G834J"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_max = 140,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 175,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 70,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G835LR"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 140,
+ .ppt_pl1_spl_max = 175,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 175,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 65,
+ .nv_tgp_max = 115,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 70,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "G835LW"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 28,
+ .ppt_pl1_spl_def = 140,
+ .ppt_pl1_spl_max = 175,
+ .ppt_pl2_sppt_min = 28,
+ .ppt_pl2_sppt_max = 175,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 25,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 80,
+ .nv_tgp_max = 150,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 55,
+ .ppt_pl2_sppt_min = 25,
+ .ppt_pl2_sppt_max = 70,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ .requires_fan_curve = true,
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "H7606W"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 15,
+ .ppt_pl1_spl_max = 80,
+ .ppt_pl2_sppt_min = 35,
+ .ppt_pl2_sppt_max = 80,
+ .ppt_pl3_fppt_min = 35,
+ .ppt_pl3_fppt_max = 80,
+ .nv_dynamic_boost_min = 5,
+ .nv_dynamic_boost_max = 20,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ .nv_tgp_min = 55,
+ .nv_tgp_max = 85,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 25,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 31,
+ .ppt_pl2_sppt_max = 44,
+ .ppt_pl3_fppt_min = 45,
+ .ppt_pl3_fppt_max = 65,
+ .nv_temp_target_min = 75,
+ .nv_temp_target_max = 87,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "RC71"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 7,
+ .ppt_pl1_spl_max = 30,
+ .ppt_pl2_sppt_min = 15,
+ .ppt_pl2_sppt_max = 43,
+ .ppt_pl3_fppt_min = 15,
+ .ppt_pl3_fppt_max = 53,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 7,
+ .ppt_pl1_spl_def = 15,
+ .ppt_pl1_spl_max = 25,
+ .ppt_pl2_sppt_min = 15,
+ .ppt_pl2_sppt_def = 20,
+ .ppt_pl2_sppt_max = 30,
+ .ppt_pl3_fppt_min = 15,
+ .ppt_pl3_fppt_def = 25,
+ .ppt_pl3_fppt_max = 35,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "RC72"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 7,
+ .ppt_pl1_spl_max = 30,
+ .ppt_pl2_sppt_min = 15,
+ .ppt_pl2_sppt_max = 43,
+ .ppt_pl3_fppt_min = 15,
+ .ppt_pl3_fppt_max = 53,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 7,
+ .ppt_pl1_spl_def = 17,
+ .ppt_pl1_spl_max = 25,
+ .ppt_pl2_sppt_min = 15,
+ .ppt_pl2_sppt_def = 24,
+ .ppt_pl2_sppt_max = 30,
+ .ppt_pl3_fppt_min = 15,
+ .ppt_pl3_fppt_def = 30,
+ .ppt_pl3_fppt_max = 35,
+ },
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "RC73XA"),
+ },
+ .driver_data = &(struct power_data) {
+ .ac_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 7,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 14,
+ .ppt_pl2_sppt_max = 45,
+ .ppt_pl3_fppt_min = 19,
+ .ppt_pl3_fppt_max = 55,
+ },
+ .dc_data = &(struct power_limits) {
+ .ppt_pl1_spl_min = 7,
+ .ppt_pl1_spl_def = 17,
+ .ppt_pl1_spl_max = 35,
+ .ppt_pl2_sppt_min = 13,
+ .ppt_pl2_sppt_def = 21,
+ .ppt_pl2_sppt_max = 45,
+ .ppt_pl3_fppt_min = 19,
+ .ppt_pl3_fppt_def = 26,
+ .ppt_pl3_fppt_max = 55,
+ },
+ },
+ },
+ {}
+};
+
+#endif /* _ASUS_ARMOURY_H_ */
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
index d460dd194f19..140ac8a10537 100644
--- a/drivers/platform/x86/asus-laptop.c
+++ b/drivers/platform/x86/asus-laptop.c
@@ -426,11 +426,14 @@ static int asus_pega_lucid_set(struct asus_laptop *asus, int unit, bool enable)
static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method)
{
+ unsigned long long val = (unsigned long long)curr;
+ acpi_status status;
int i, delta;
- unsigned long long val;
- for (i = 0; i < PEGA_ACC_RETRIES; i++) {
- acpi_evaluate_integer(asus->handle, method, NULL, &val);
+ for (i = 0; i < PEGA_ACC_RETRIES; i++) {
+ status = acpi_evaluate_integer(asus->handle, method, NULL, &val);
+ if (ACPI_FAILURE(status))
+ continue;
/* The output is noisy. From reading the ASL
* dissassembly, timeout errors are returned with 1's
* in the high word, and the lack of locking around
@@ -1514,9 +1517,9 @@ static void asus_input_exit(struct asus_laptop *asus)
/*
* ACPI driver
*/
-static void asus_acpi_notify(struct acpi_device *device, u32 event)
+static void asus_acpi_notify(acpi_handle handle, u32 event, void *data)
{
- struct asus_laptop *asus = acpi_driver_data(device);
+ struct asus_laptop *asus = data;
u16 count;
/* TODO Find a better way to handle events count. */
@@ -1821,20 +1824,24 @@ static void asus_dmi_check(void)
static bool asus_device_present;
-static int asus_acpi_add(struct acpi_device *device)
+static int asus_acpi_probe(struct platform_device *pdev)
{
+ struct acpi_device *device;
struct asus_laptop *asus;
int result;
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
pr_notice("Asus Laptop Support version %s\n",
ASUS_LAPTOP_VERSION);
- asus = kzalloc(sizeof(struct asus_laptop), GFP_KERNEL);
+ asus = kzalloc_obj(struct asus_laptop);
if (!asus)
return -ENOMEM;
asus->handle = device->handle;
strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME);
strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS);
- device->driver_data = asus;
asus->device = device;
asus_dmi_check();
@@ -1843,6 +1850,8 @@ static int asus_acpi_add(struct acpi_device *device)
if (result)
goto fail_platform;
+ platform_set_drvdata(pdev, asus);
+
/*
* Need platform type detection first, then the platform
* device. It is used as a parent for the sub-devices below.
@@ -1878,6 +1887,11 @@ static int asus_acpi_add(struct acpi_device *device)
if (result && result != -ENODEV)
goto fail_pega_rfkill;
+ result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ asus_acpi_notify, asus);
+ if (result)
+ goto fail_pega_rfkill;
+
asus_device_present = true;
return 0;
@@ -1899,10 +1913,12 @@ fail_platform:
return result;
}
-static void asus_acpi_remove(struct acpi_device *device)
+static void asus_acpi_remove(struct platform_device *pdev)
{
- struct asus_laptop *asus = acpi_driver_data(device);
+ struct asus_laptop *asus = platform_get_drvdata(pdev);
+ acpi_dev_remove_notify_handler(asus->device, ACPI_DEVICE_NOTIFY,
+ asus_acpi_notify);
asus_backlight_exit(asus);
asus_rfkill_exit(asus);
asus_led_exit(asus);
@@ -1921,16 +1937,13 @@ static const struct acpi_device_id asus_device_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, asus_device_ids);
-static struct acpi_driver asus_acpi_driver = {
- .name = ASUS_LAPTOP_NAME,
- .class = ASUS_LAPTOP_CLASS,
- .ids = asus_device_ids,
- .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
- .ops = {
- .add = asus_acpi_add,
- .remove = asus_acpi_remove,
- .notify = asus_acpi_notify,
- },
+static struct platform_driver asus_acpi_driver = {
+ .probe = asus_acpi_probe,
+ .remove = asus_acpi_remove,
+ .driver = {
+ .name = ASUS_LAPTOP_NAME,
+ .acpi_match_table = asus_device_ids,
+ },
};
static int __init asus_laptop_init(void)
@@ -1941,7 +1954,7 @@ static int __init asus_laptop_init(void)
if (result < 0)
return result;
- result = acpi_bus_register_driver(&asus_acpi_driver);
+ result = platform_driver_register(&asus_acpi_driver);
if (result < 0)
goto fail_acpi_driver;
if (!asus_device_present) {
@@ -1951,7 +1964,7 @@ static int __init asus_laptop_init(void)
return 0;
fail_no_device:
- acpi_bus_unregister_driver(&asus_acpi_driver);
+ platform_driver_unregister(&asus_acpi_driver);
fail_acpi_driver:
platform_driver_unregister(&platform_driver);
return result;
@@ -1959,7 +1972,7 @@ fail_acpi_driver:
static void __exit asus_laptop_exit(void)
{
- acpi_bus_unregister_driver(&asus_acpi_driver);
+ platform_driver_unregister(&asus_acpi_driver);
platform_driver_unregister(&platform_driver);
}
diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
index 3f8b2a324efd..8005c088e9ee 100644
--- a/drivers/platform/x86/asus-nb-wmi.c
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -147,7 +147,12 @@ static struct quirk_entry quirk_asus_ignore_fan = {
};
static struct quirk_entry quirk_asus_zenbook_duo_kbd = {
- .ignore_key_wlan = true,
+ .key_wlan_event = ASUS_WMI_KEY_IGNORE,
+};
+
+static struct quirk_entry quirk_asus_z13 = {
+ .key_wlan_event = ASUS_WMI_KEY_ARMOURY,
+ .tablet_switch_mode = asus_wmi_kbd_dock_devid,
};
static int dmi_matched(const struct dmi_system_id *dmi)
@@ -530,6 +535,33 @@ static const struct dmi_system_id asus_quirks[] = {
},
.driver_data = &quirk_asus_zenbook_duo_kbd,
},
+ {
+ .callback = dmi_matched,
+ .ident = "ASUS Zenbook Duo UX8406CA",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "UX8406CA"),
+ },
+ .driver_data = &quirk_asus_zenbook_duo_kbd,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUS Zenbook Duo UX8407AA",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook Duo UX8407AA"),
+ },
+ .driver_data = &quirk_asus_zenbook_duo_kbd,
+ },
+ {
+ .callback = dmi_matched,
+ .ident = "ASUS ROG Z13",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ROG Flow Z13"),
+ },
+ .driver_data = &quirk_asus_z13,
+ },
{},
};
@@ -557,6 +589,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
{ KE_KEY, 0x2a, { KEY_SELECTIVE_SCREENSHOT } },
{ KE_IGNORE, 0x2b, }, /* PrintScreen (also send via PS/2) on newer models */
{ KE_IGNORE, 0x2c, }, /* CapsLock (also send via PS/2) on newer models */
+ { KE_KEY, 0x2d, { KEY_DISPLAYTOGGLE } },
{ KE_KEY, 0x30, { KEY_VOLUMEUP } },
{ KE_KEY, 0x31, { KEY_VOLUMEDOWN } },
{ KE_KEY, 0x32, { KEY_MUTE } },
@@ -609,6 +642,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
{ KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */
{ KE_KEY, 0x95, { KEY_MEDIA } },
{ KE_KEY, 0x99, { KEY_PHONE } }, /* Conflicts with fan mode switch */
+ { KE_KEY, 0X9D, { KEY_FN_F } },
{ KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */
{ KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */
{ KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */
@@ -623,10 +657,13 @@ static const struct key_entry asus_nb_wmi_keymap[] = {
{ KE_IGNORE, 0xC0, }, /* External display connect/disconnect notification */
{ KE_KEY, 0xC4, { KEY_KBDILLUMUP } },
{ KE_KEY, 0xC5, { KEY_KBDILLUMDOWN } },
+ { KE_KEY, 0xCA, { KEY_F13 } }, /* Noise cancelling on Expertbook B9 */
+ { KE_KEY, 0xCB, { KEY_F14 } }, /* Fn+noise-cancel */
{ KE_IGNORE, 0xC6, }, /* Ambient Light Sensor notification */
{ KE_IGNORE, 0xCF, }, /* AC mode */
{ KE_KEY, 0xFA, { KEY_PROG2 } }, /* Lid flip action */
{ KE_KEY, 0xBD, { KEY_PROG2 } }, /* Lid flip action on ROG xflow laptops */
+ { KE_KEY, ASUS_WMI_KEY_ARMOURY, { KEY_PROG3 } },
{ KE_END, 0},
};
@@ -647,10 +684,10 @@ static void asus_nb_wmi_key_filter(struct asus_wmi_driver *asus_wmi, int *code,
*code = ASUS_WMI_KEY_IGNORE;
break;
case 0x5D: /* Wireless console Toggle */
- case 0x5E: /* Wireless console Enable */
- case 0x5F: /* Wireless console Disable */
- if (quirks->ignore_key_wlan)
- *code = ASUS_WMI_KEY_IGNORE;
+ case 0x5E: /* Wireless console Enable / Keyboard Attach, Detach */
+ case 0x5F: /* Wireless console Disable / Special Key */
+ if (quirks->key_wlan_event)
+ *code = quirks->key_wlan_event;
break;
}
}
diff --git a/drivers/platform/x86/asus-wireless.c b/drivers/platform/x86/asus-wireless.c
index 41227bf95878..2b494bf3cba8 100644
--- a/drivers/platform/x86/asus-wireless.c
+++ b/drivers/platform/x86/asus-wireless.c
@@ -12,6 +12,7 @@
#include <linux/acpi.h>
#include <linux/input.h>
#include <linux/pci_ids.h>
+#include <linux/platform_device.h>
#include <linux/leds.h>
struct hswc_params {
@@ -108,9 +109,10 @@ static void led_state_set(struct led_classdev *led, enum led_brightness value)
queue_work(data->wq, &data->led_work);
}
-static void asus_wireless_notify(struct acpi_device *adev, u32 event)
+static void asus_wireless_notify(acpi_handle handle, u32 event, void *context)
{
- struct asus_wireless_data *data = acpi_driver_data(adev);
+ struct asus_wireless_data *data = context;
+ struct acpi_device *adev = data->adev;
dev_dbg(&adev->dev, "event=%#x\n", event);
if (event != 0x88) {
@@ -123,19 +125,22 @@ static void asus_wireless_notify(struct acpi_device *adev, u32 event)
input_sync(data->idev);
}
-static int asus_wireless_add(struct acpi_device *adev)
+static int asus_wireless_probe(struct platform_device *pdev)
{
+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
struct asus_wireless_data *data;
const struct acpi_device_id *id;
int err;
- data = devm_kzalloc(&adev->dev, sizeof(*data), GFP_KERNEL);
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- adev->driver_data = data;
+
+ platform_set_drvdata(pdev, data);
+
data->adev = adev;
- data->idev = devm_input_allocate_device(&adev->dev);
+ data->idev = devm_input_allocate_device(&pdev->dev);
if (!data->idev)
return -ENOMEM;
data->idev->name = "Asus Wireless Radio Control";
@@ -164,34 +169,44 @@ static int asus_wireless_add(struct acpi_device *adev)
data->led.flags = LED_CORE_SUSPENDRESUME;
data->led.max_brightness = 1;
data->led.default_trigger = "rfkill-none";
- err = devm_led_classdev_register(&adev->dev, &data->led);
+ err = devm_led_classdev_register(&pdev->dev, &data->led);
if (err)
- destroy_workqueue(data->wq);
+ goto err;
+
+ err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
+ asus_wireless_notify, data);
+ if (err) {
+ devm_led_classdev_unregister(&pdev->dev, &data->led);
+ goto err;
+ }
+ return 0;
+err:
+ destroy_workqueue(data->wq);
return err;
}
-static void asus_wireless_remove(struct acpi_device *adev)
+static void asus_wireless_remove(struct platform_device *pdev)
{
- struct asus_wireless_data *data = acpi_driver_data(adev);
+ struct asus_wireless_data *data = platform_get_drvdata(pdev);
+ acpi_dev_remove_notify_handler(data->adev, ACPI_DEVICE_NOTIFY,
+ asus_wireless_notify);
if (data->wq) {
- devm_led_classdev_unregister(&adev->dev, &data->led);
+ devm_led_classdev_unregister(&pdev->dev, &data->led);
destroy_workqueue(data->wq);
}
}
-static struct acpi_driver asus_wireless_driver = {
- .name = "Asus Wireless Radio Control Driver",
- .class = "hotkey",
- .ids = device_ids,
- .ops = {
- .add = asus_wireless_add,
- .remove = asus_wireless_remove,
- .notify = asus_wireless_notify,
+static struct platform_driver asus_wireless_driver = {
+ .probe = asus_wireless_probe,
+ .remove = asus_wireless_remove,
+ .driver = {
+ .name = "Asus Wireless Radio Control Driver",
+ .acpi_match_table = device_ids,
},
};
-module_acpi_driver(asus_wireless_driver);
+module_platform_driver(asus_wireless_driver);
MODULE_DESCRIPTION("Asus Wireless Radio Control Driver");
MODULE_AUTHOR("João Paulo Rechi Vita <jprvita@gmail.com>");
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 38ef778e8c19..80144c412b90 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -15,6 +15,7 @@
#include <linux/acpi.h>
#include <linux/backlight.h>
+#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -36,6 +37,7 @@
#include <linux/rfkill.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/units.h>
@@ -55,8 +57,6 @@ module_param(fnlock_default, bool, 0444);
#define to_asus_wmi_driver(pdrv) \
(container_of((pdrv), struct asus_wmi_driver, platform_driver))
-#define ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66"
-
#define NOTIFY_BRNUP_MIN 0x11
#define NOTIFY_BRNUP_MAX 0x1f
#define NOTIFY_BRNDOWN_MIN 0x20
@@ -105,8 +105,6 @@ module_param(fnlock_default, bool, 0444);
#define USB_INTEL_XUSB2PR 0xD0
#define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31
-#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI"
-
#define WMI_EVENT_MASK 0xFFFF
#define FAN_CURVE_POINTS 8
@@ -127,7 +125,6 @@ module_param(fnlock_default, bool, 0444);
#define NVIDIA_TEMP_MIN 75
#define NVIDIA_TEMP_MAX 87
-#define ASUS_SCREENPAD_BRIGHT_MIN 20
#define ASUS_SCREENPAD_BRIGHT_MAX 255
#define ASUS_SCREENPAD_BRIGHT_DEFAULT 60
@@ -142,16 +139,20 @@ module_param(fnlock_default, bool, 0444);
#define ASUS_MINI_LED_2024_STRONG 0x01
#define ASUS_MINI_LED_2024_OFF 0x02
-/* Controls the power state of the USB0 hub on ROG Ally which input is on */
#define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE"
-/* 300ms so far seems to produce a reliable result on AC and battery */
-#define ASUS_USB0_PWR_EC0_CSEE_WAIT 1500
+/*
+ * The period required to wait after screen off/on/s2idle.check in MS.
+ * Time here greatly impacts the wake behaviour. Used in suspend/wake.
+ */
+#define ASUS_USB0_PWR_EC0_CSEE_WAIT 600
+#define ASUS_USB0_PWR_EC0_CSEE_OFF 0xB7
+#define ASUS_USB0_PWR_EC0_CSEE_ON 0xB8
static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
static int throttle_thermal_policy_write(struct asus_wmi *);
-static const struct dmi_system_id asus_ally_mcu_quirk[] = {
+static const struct dmi_system_id asus_rog_ally_device[] = {
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
@@ -254,6 +255,9 @@ struct asus_wmi {
int tpd_led_wk;
struct led_classdev kbd_led;
int kbd_led_wk;
+ bool kbd_led_notify;
+ bool kbd_led_avail;
+ bool kbd_led_registered;
struct led_classdev lightbar_led;
int lightbar_led_wk;
struct led_classdev micmute_led;
@@ -262,6 +266,7 @@ struct asus_wmi {
struct work_struct tpd_led_work;
struct work_struct wlan_led_work;
struct work_struct lightbar_led_work;
+ struct work_struct kbd_led_work;
struct asus_rfkill wlan;
struct asus_rfkill bluetooth;
@@ -274,9 +279,6 @@ struct asus_wmi {
u32 tablet_switch_dev_id;
bool tablet_switch_inverted;
- /* The ROG Ally device requires the MCU USB device be disconnected before suspend */
- bool ally_mcu_usb_switch;
-
enum fan_type fan_type;
enum fan_type gpu_fan_type;
enum fan_type mid_fan_type;
@@ -304,6 +306,7 @@ struct asus_wmi {
u32 kbd_rgb_dev;
bool kbd_rgb_state_available;
+ bool oobe_state_available;
u8 throttle_thermal_policy_mode;
u32 throttle_thermal_policy_dev;
@@ -335,6 +338,16 @@ struct asus_wmi {
struct asus_wmi_driver *driver;
};
+/* Global to allow setting externally without requiring driver data */
+static enum asus_ally_mcu_hack use_ally_mcu_hack = ASUS_WMI_ALLY_MCU_HACK_INIT;
+
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
+static void asus_wmi_show_deprecated(void)
+{
+ pr_notice_once("Accessing attributes through /sys/bus/platform/asus_wmi is deprecated and will be removed in a future release. Please switch over to /sys/class/firmware_attributes.\n");
+}
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
+
/* WMI ************************************************************************/
static int asus_wmi_evaluate_method3(u32 method_id,
@@ -385,7 +398,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
{
return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval);
}
-EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);
+EXPORT_SYMBOL_NS_GPL(asus_wmi_evaluate_method, "ASUS_WMI");
static int asus_wmi_evaluate_method5(u32 method_id,
u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval)
@@ -549,12 +562,52 @@ static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval)
return 0;
}
-static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param,
- u32 *retval)
+/**
+ * asus_wmi_get_devstate_dsts() - Get the WMI function state.
+ * @dev_id: The WMI method ID to call.
+ * @retval: A pointer to where to store the value returned from WMI.
+ *
+ * Returns:
+ * * %-ENODEV - method ID is unsupported.
+ * * %0 - successful and retval is filled.
+ * * %other - error from WMI call.
+ */
+int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval)
+{
+ int err;
+
+ err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, retval);
+ if (err)
+ return err;
+
+ if ((*retval & ASUS_WMI_DSTS_PRESENCE_BIT) == 0x00)
+ return -ENODEV;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(asus_wmi_get_devstate_dsts, "ASUS_WMI");
+
+/**
+ * asus_wmi_set_devstate() - Set the WMI function state.
+ *
+ * Note: an asus_wmi_set_devstate() call must be paired with a
+ * asus_wmi_get_devstate_dsts() to check if the WMI function is supported.
+ *
+ * @dev_id: The WMI function to call.
+ * @ctrl_param: The argument to be used for this WMI function.
+ * @retval: A pointer to where to store the value returned from WMI.
+ *
+ * Returns:
+ * * %-ENODEV - method ID is unsupported.
+ * * %0 - successful and retval is filled.
+ * * %other - error from WMI call.
+ */
+int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval)
{
return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id,
ctrl_param, retval);
}
+EXPORT_SYMBOL_NS_GPL(asus_wmi_set_devstate, "ASUS_WMI");
/* Helper for special devices with magic return codes */
static int asus_wmi_get_devstate_bits(struct asus_wmi *asus,
@@ -687,6 +740,7 @@ static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus)
}
/* Charging mode, 1=Barrel, 2=USB ******************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t charge_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -697,12 +751,16 @@ static ssize_t charge_mode_show(struct device *dev,
if (result < 0)
return result;
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", value & 0xff);
}
static DEVICE_ATTR_RO(charge_mode);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* dGPU ********************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t dgpu_disable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -713,6 +771,8 @@ static ssize_t dgpu_disable_show(struct device *dev,
if (result < 0)
return result;
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", result);
}
@@ -766,8 +826,10 @@ static ssize_t dgpu_disable_store(struct device *dev,
return count;
}
static DEVICE_ATTR_RW(dgpu_disable);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* eGPU ********************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t egpu_enable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -778,6 +840,8 @@ static ssize_t egpu_enable_show(struct device *dev,
if (result < 0)
return result;
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", result);
}
@@ -834,8 +898,10 @@ static ssize_t egpu_enable_store(struct device *dev,
return count;
}
static DEVICE_ATTR_RW(egpu_enable);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* Is eGPU connected? *********************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t egpu_connected_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -846,12 +912,16 @@ static ssize_t egpu_connected_show(struct device *dev,
if (result < 0)
return result;
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", result);
}
static DEVICE_ATTR_RO(egpu_connected);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* gpu mux switch *************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t gpu_mux_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -862,6 +932,8 @@ static ssize_t gpu_mux_mode_show(struct device *dev,
if (result < 0)
return result;
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", result);
}
@@ -920,6 +992,7 @@ static ssize_t gpu_mux_mode_store(struct device *dev,
return count;
}
static DEVICE_ATTR_RW(gpu_mux_mode);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* TUF Laptop Keyboard RGB Modes **********************************************/
static ssize_t kbd_rgb_mode_store(struct device *dev,
@@ -1043,6 +1116,7 @@ static const struct attribute_group *kbd_rgb_mode_groups[] = {
};
/* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t ppt_pl2_sppt_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
@@ -1081,6 +1155,8 @@ static ssize_t ppt_pl2_sppt_show(struct device *dev,
{
struct asus_wmi *asus = dev_get_drvdata(dev);
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%u\n", asus->ppt_pl2_sppt);
}
static DEVICE_ATTR_RW(ppt_pl2_sppt);
@@ -1123,6 +1199,8 @@ static ssize_t ppt_pl1_spl_show(struct device *dev,
{
struct asus_wmi *asus = dev_get_drvdata(dev);
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%u\n", asus->ppt_pl1_spl);
}
static DEVICE_ATTR_RW(ppt_pl1_spl);
@@ -1143,7 +1221,7 @@ static ssize_t ppt_fppt_store(struct device *dev,
if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
return -EINVAL;
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_FPPT, value, &result);
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL3_FPPT, value, &result);
if (err) {
pr_warn("Failed to set ppt_fppt: %d\n", err);
return err;
@@ -1166,6 +1244,8 @@ static ssize_t ppt_fppt_show(struct device *dev,
{
struct asus_wmi *asus = dev_get_drvdata(dev);
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%u\n", asus->ppt_fppt);
}
static DEVICE_ATTR_RW(ppt_fppt);
@@ -1209,6 +1289,8 @@ static ssize_t ppt_apu_sppt_show(struct device *dev,
{
struct asus_wmi *asus = dev_get_drvdata(dev);
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%u\n", asus->ppt_apu_sppt);
}
static DEVICE_ATTR_RW(ppt_apu_sppt);
@@ -1252,6 +1334,8 @@ static ssize_t ppt_platform_sppt_show(struct device *dev,
{
struct asus_wmi *asus = dev_get_drvdata(dev);
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%u\n", asus->ppt_platform_sppt);
}
static DEVICE_ATTR_RW(ppt_platform_sppt);
@@ -1295,6 +1379,8 @@ static ssize_t nv_dynamic_boost_show(struct device *dev,
{
struct asus_wmi *asus = dev_get_drvdata(dev);
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%u\n", asus->nv_dynamic_boost);
}
static DEVICE_ATTR_RW(nv_dynamic_boost);
@@ -1338,11 +1424,53 @@ static ssize_t nv_temp_target_show(struct device *dev,
{
struct asus_wmi *asus = dev_get_drvdata(dev);
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%u\n", asus->nv_temp_target);
}
static DEVICE_ATTR_RW(nv_temp_target);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* Ally MCU Powersave ********************************************************/
+
+/*
+ * The HID driver needs to check MCU version and set this to false if the MCU FW
+ * version is >= the minimum requirements. New FW do not need the hacks.
+ */
+void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
+{
+ use_ally_mcu_hack = status;
+ pr_debug("%s Ally MCU suspend quirk\n",
+ status == ASUS_WMI_ALLY_MCU_HACK_ENABLED ? "Enabled" : "Disabled");
+}
+EXPORT_SYMBOL_NS_GPL(set_ally_mcu_hack, "ASUS_WMI");
+
+/*
+ * mcu_powersave should be enabled always, as it is fixed in MCU FW versions:
+ * - v313 for Ally X
+ * - v319 for Ally 1
+ * The HID driver checks MCU versions and so should set this if requirements match
+ */
+void set_ally_mcu_powersave(bool enabled)
+{
+ int result, err;
+
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MCU_POWERSAVE, enabled, &result);
+ if (err) {
+ pr_warn("Failed to set MCU powersave: %d\n", err);
+ return;
+ }
+ if (result > 1) {
+ pr_warn("Failed to set MCU powersave (result): 0x%x\n", result);
+ return;
+ }
+
+ pr_debug("%s MCU Powersave\n",
+ enabled ? "Enabled" : "Disabled");
+}
+EXPORT_SYMBOL_NS_GPL(set_ally_mcu_powersave, "ASUS_WMI");
+
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t mcu_powersave_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -1353,6 +1481,8 @@ static ssize_t mcu_powersave_show(struct device *dev,
if (result < 0)
return result;
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", result);
}
@@ -1388,6 +1518,7 @@ static ssize_t mcu_powersave_store(struct device *dev,
return count;
}
static DEVICE_ATTR_RW(mcu_powersave);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* Battery ********************************************************************/
@@ -1425,7 +1556,10 @@ static ssize_t charge_control_end_threshold_show(struct device *device,
struct device_attribute *attr,
char *buf)
{
- return sysfs_emit(buf, "%d\n", charge_end_threshold);
+ if ((charge_end_threshold >= 0) && (charge_end_threshold <= 100))
+ return sysfs_emit(buf, "%d\n", charge_end_threshold);
+
+ return -ENODATA;
}
static DEVICE_ATTR_RW(charge_control_end_threshold);
@@ -1448,11 +1582,11 @@ static int asus_wmi_battery_add(struct power_supply *battery, struct acpi_batter
return -ENODEV;
/* The charge threshold is only reset when the system is power cycled,
- * and we can't get the current threshold so let set it to 100% when
- * a battery is added.
+ * and we can't read the current threshold, however the majority of
+ * platforms retains it, therefore signal the threshold as unknown
+ * until user explicitly sets it to a new value.
*/
- asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, 100, NULL);
- charge_end_threshold = 100;
+ charge_end_threshold = -1;
return 0;
}
@@ -1487,6 +1621,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
/* LEDs ***********************************************************************/
+struct asus_hid_ref {
+ struct list_head listeners;
+ struct asus_wmi *asus;
+ /* Protects concurrent access from hid-asus and asus-wmi to leds */
+ spinlock_t lock;
+};
+
+static struct asus_hid_ref asus_ref = {
+ .listeners = LIST_HEAD_INIT(asus_ref.listeners),
+ .asus = NULL,
+ /*
+ * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
+ * asus variables are read-only after .asus is set.
+ *
+ * The led cdev device is not protected because it calls backlight_get
+ * during initialization, which would result in a nested lock attempt.
+ *
+ * The led cdev is safe to access without a lock because if
+ * kbd_led_avail is true it is initialized before .asus is set and never
+ * changed until .asus is dropped. If kbd_led_avail is false, the led
+ * cdev is registered by the workqueue, which is single-threaded and
+ * cancelled before asus-wmi would access the led cdev to unregister it.
+ *
+ * A spinlock is used, because the protected variables can be accessed
+ * from an IRQ context from asus-hid.
+ */
+ .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
+};
+
+/*
+ * Allows registering hid-asus listeners that want to be notified of
+ * keyboard backlight changes.
+ */
+int asus_hid_register_listener(struct asus_hid_listener *bdev)
+{
+ struct asus_wmi *asus;
+
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ list_add_tail(&bdev->list, &asus_ref.listeners);
+ asus = asus_ref.asus;
+ if (asus)
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_hid_register_listener);
+
+/*
+ * Allows unregistering hid-asus listeners that were added with
+ * asus_hid_register_listener().
+ */
+void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
+{
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ list_del(&bdev->list);
+}
+EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
+
+static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
+
+static void kbd_led_update_all(struct work_struct *work)
+{
+ struct asus_wmi *asus;
+ bool registered, notify;
+ int ret, value;
+
+ asus = container_of(work, struct asus_wmi, kbd_led_work);
+
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ registered = asus->kbd_led_registered;
+ value = asus->kbd_led_wk;
+ notify = asus->kbd_led_notify;
+ }
+
+ if (!registered) {
+ /*
+ * This workqueue runs under asus-wmi, which means probe has
+ * completed and asus-wmi will keep running until it finishes.
+ * Therefore, we can safely register the LED without holding
+ * a spinlock.
+ */
+ ret = devm_led_classdev_register(&asus->platform_device->dev,
+ &asus->kbd_led);
+ if (!ret) {
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_registered = true;
+ } else {
+ pr_warn("Failed to register keyboard backlight LED: %d\n", ret);
+ return;
+ }
+ }
+
+ if (value >= 0)
+ do_kbd_led_set(&asus->kbd_led, value);
+ if (notify) {
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_notify = false;
+ led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value);
+ }
+}
+
+/*
+ * This function is called from hid-asus to inform asus-wmi of brightness
+ * changes initiated by the keyboard backlight keys.
+ */
+int asus_hid_event(enum asus_hid_event event)
+{
+ struct asus_wmi *asus;
+ int brightness;
+
+ guard(spinlock_irqsave)(&asus_ref.lock);
+ asus = asus_ref.asus;
+ if (!asus || !asus->kbd_led_registered)
+ return -EBUSY;
+
+ brightness = asus->kbd_led_wk;
+
+ switch (event) {
+ case ASUS_EV_BRTUP:
+ brightness += 1;
+ break;
+ case ASUS_EV_BRTDOWN:
+ brightness -= 1;
+ break;
+ case ASUS_EV_BRTTOGGLE:
+ if (brightness >= ASUS_EV_MAX_BRIGHTNESS)
+ brightness = 0;
+ else
+ brightness += 1;
+ break;
+ }
+
+ asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS);
+ asus->kbd_led_notify = true;
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_hid_event);
+
/*
* These functions actually update the LED's, and are called from a
* workqueue. By doing this as separate work rather than when the LED
@@ -1533,7 +1805,8 @@ static void kbd_led_update(struct asus_wmi *asus)
{
int ctrl_param = 0;
- ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
}
@@ -1566,32 +1839,40 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
{
+ struct asus_hid_listener *listener;
struct asus_wmi *asus;
- int max_level;
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
- max_level = asus->kbd_led.max_brightness;
- asus->kbd_led_wk = clamp_val(value, 0, max_level);
- kbd_led_update(asus);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS);
+
+ if (asus->kbd_led_avail)
+ kbd_led_update(asus);
+
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ list_for_each_entry(listener, &asus_ref.listeners, list)
+ listener->brightness_set(listener, asus->kbd_led_wk);
+ }
}
-static void kbd_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
+static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
{
/* Prevent disabling keyboard backlight on module unregister */
if (led_cdev->flags & LED_UNREGISTERING)
- return;
+ return 0;
do_kbd_led_set(led_cdev, value);
+ return 0;
}
static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
{
- struct led_classdev *led_cdev = &asus->kbd_led;
-
- do_kbd_led_set(led_cdev, value);
- led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ asus->kbd_led_wk = value;
+ asus->kbd_led_notify = true;
+ }
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
@@ -1601,10 +1882,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ if (!asus->kbd_led_avail)
+ return asus->kbd_led_wk;
+ }
+
retval = kbd_led_read(asus, &value, NULL);
if (retval < 0)
return retval;
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus->kbd_led_wk = value;
+
return value;
}
@@ -1716,7 +2005,9 @@ static int camera_led_set(struct led_classdev *led_cdev,
static void asus_wmi_led_exit(struct asus_wmi *asus)
{
- led_classdev_unregister(&asus->kbd_led);
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ asus_ref.asus = NULL;
+
led_classdev_unregister(&asus->tpd_led);
led_classdev_unregister(&asus->wlan_led);
led_classdev_unregister(&asus->lightbar_led);
@@ -1754,22 +2045,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
goto error;
}
- if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
- pr_info("using asus-wmi for asus::kbd_backlight\n");
- asus->kbd_led_wk = led_val;
- asus->kbd_led.name = "asus::kbd_backlight";
- asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
- asus->kbd_led.brightness_set = kbd_led_set;
- asus->kbd_led.brightness_get = kbd_led_get;
- asus->kbd_led.max_brightness = 3;
+ asus->kbd_led.name = "asus::kbd_backlight";
+ asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
+ asus->kbd_led.brightness_set_blocking = kbd_led_set;
+ asus->kbd_led.brightness_get = kbd_led_get;
+ asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS;
+ asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
+ INIT_WORK(&asus->kbd_led_work, kbd_led_update_all);
+ if (asus->kbd_led_avail) {
+ asus->kbd_led_wk = led_val;
if (num_rgb_groups != 0)
asus->kbd_led.groups = kbd_rgb_mode_groups;
+ } else {
+ asus->kbd_led_wk = -1;
+ }
- rv = led_classdev_register(&asus->platform_device->dev,
- &asus->kbd_led);
- if (rv)
- goto error;
+ scoped_guard(spinlock_irqsave, &asus_ref.lock) {
+ asus_ref.asus = asus;
+ if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners))
+ queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
@@ -1826,7 +2121,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
goto error;
}
- if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_OOBE)) {
+ if (asus->oobe_state_available) {
/*
* Disable OOBE state, so that e.g. the keyboard backlight
* works.
@@ -2261,6 +2556,7 @@ exit:
}
/* Panel Overdrive ************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t panel_od_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -2271,6 +2567,8 @@ static ssize_t panel_od_show(struct device *dev,
if (result < 0)
return result;
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", result);
}
@@ -2307,9 +2605,10 @@ static ssize_t panel_od_store(struct device *dev,
return count;
}
static DEVICE_ATTR_RW(panel_od);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* Bootup sound ***************************************************************/
-
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t boot_sound_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -2320,6 +2619,8 @@ static ssize_t boot_sound_show(struct device *dev,
if (result < 0)
return result;
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", result);
}
@@ -2355,8 +2656,10 @@ static ssize_t boot_sound_store(struct device *dev,
return count;
}
static DEVICE_ATTR_RW(boot_sound);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* Mini-LED mode **************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t mini_led_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -2387,6 +2690,8 @@ static ssize_t mini_led_mode_show(struct device *dev,
}
}
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "%d\n", value);
}
@@ -2457,10 +2762,13 @@ static ssize_t available_mini_led_mode_show(struct device *dev,
return sysfs_emit(buf, "0 1 2\n");
}
+ asus_wmi_show_deprecated();
+
return sysfs_emit(buf, "0\n");
}
static DEVICE_ATTR_RO(available_mini_led_mode);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* Quirks *********************************************************************/
@@ -3748,6 +4056,7 @@ static int throttle_thermal_policy_set_default(struct asus_wmi *asus)
return throttle_thermal_policy_write(asus);
}
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
static ssize_t throttle_thermal_policy_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -3791,6 +4100,7 @@ static ssize_t throttle_thermal_policy_store(struct device *dev,
* Throttle thermal policy: 0 - default, 1 - overboost, 2 - silent
*/
static DEVICE_ATTR_RW(throttle_thermal_policy);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
/* Platform profile ***********************************************************/
static int asus_wmi_platform_profile_get(struct device *dev,
@@ -4100,43 +4410,35 @@ static int read_screenpad_brightness(struct backlight_device *bd)
return err;
/* The device brightness can only be read if powered, so return stored */
if (err == BACKLIGHT_POWER_OFF)
- return asus->driver->screenpad_brightness - ASUS_SCREENPAD_BRIGHT_MIN;
+ return bd->props.brightness;
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &retval);
if (err < 0)
return err;
- return (retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK) - ASUS_SCREENPAD_BRIGHT_MIN;
+ return retval & ASUS_WMI_DSTS_BRIGHTNESS_MASK;
}
static int update_screenpad_bl_status(struct backlight_device *bd)
{
- struct asus_wmi *asus = bl_get_data(bd);
- int power, err = 0;
- u32 ctrl_param;
+ u32 ctrl_param = bd->props.brightness;
+ int err = 0;
- power = read_screenpad_backlight_power(asus);
- if (power < 0)
- return power;
+ if (bd->props.power) {
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 1, NULL);
+ if (err < 0)
+ return err;
- if (bd->props.power != power) {
- if (power != BACKLIGHT_POWER_ON) {
- /* Only brightness > 0 can power it back on */
- ctrl_param = asus->driver->screenpad_brightness - ASUS_SCREENPAD_BRIGHT_MIN;
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT,
- ctrl_param, NULL);
- } else {
- err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL);
- }
- } else if (power == BACKLIGHT_POWER_ON) {
- /* Only set brightness if powered on or we get invalid/unsync state */
- ctrl_param = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN;
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_LIGHT, ctrl_param, NULL);
+ if (err < 0)
+ return err;
}
- /* Ensure brightness is stored to turn back on with */
- if (err == 0)
- asus->driver->screenpad_brightness = bd->props.brightness + ASUS_SCREENPAD_BRIGHT_MIN;
+ if (!bd->props.power) {
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_SCREENPAD_POWER, 0, NULL);
+ if (err < 0)
+ return err;
+ }
return err;
}
@@ -4154,22 +4456,19 @@ static int asus_screenpad_init(struct asus_wmi *asus)
int err, power;
int brightness = 0;
- power = read_screenpad_backlight_power(asus);
+ power = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER);
if (power < 0)
return power;
- if (power != BACKLIGHT_POWER_OFF) {
+ if (power) {
err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness);
if (err < 0)
return err;
}
- /* default to an acceptable min brightness on boot if too low */
- if (brightness < ASUS_SCREENPAD_BRIGHT_MIN)
- brightness = ASUS_SCREENPAD_BRIGHT_DEFAULT;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW; /* ensure this bd is last to be picked */
- props.max_brightness = ASUS_SCREENPAD_BRIGHT_MAX - ASUS_SCREENPAD_BRIGHT_MIN;
+ props.max_brightness = ASUS_SCREENPAD_BRIGHT_MAX;
bd = backlight_device_register("asus_screenpad",
&asus->platform_device->dev, asus,
&asus_screenpad_bl_ops, &props);
@@ -4180,7 +4479,7 @@ static int asus_screenpad_init(struct asus_wmi *asus)
asus->screenpad_backlight_device = bd;
asus->driver->screenpad_brightness = brightness;
- bd->props.brightness = brightness - ASUS_SCREENPAD_BRIGHT_MIN;
+ bd->props.brightness = brightness;
bd->props.power = power;
backlight_update_status(bd);
@@ -4229,6 +4528,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj)
static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
{
+ enum led_brightness led_value;
unsigned int key_value = 1;
bool autorelease = 1;
@@ -4245,19 +4545,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
return;
}
+ scoped_guard(spinlock_irqsave, &asus_ref.lock)
+ led_value = asus->kbd_led_wk;
+
if (code == NOTIFY_KBD_BRTUP) {
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
+ kbd_led_set_by_kbd(asus, led_value + 1);
return;
}
if (code == NOTIFY_KBD_BRTDWN) {
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
+ kbd_led_set_by_kbd(asus, led_value - 1);
return;
}
if (code == NOTIFY_KBD_BRTTOGGLE) {
- if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
+ if (led_value >= ASUS_EV_MAX_BRIGHTNESS)
kbd_led_set_by_kbd(asus, 0);
else
- kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
+ kbd_led_set_by_kbd(asus, led_value + 1);
return;
}
@@ -4392,27 +4695,29 @@ 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_mcu_powersave.attr,
- &dev_attr_boot_sound.attr,
- &dev_attr_panel_od.attr,
- &dev_attr_mini_led_mode.attr,
- &dev_attr_available_mini_led_mode.attr,
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
+ &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_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_mcu_powersave.attr,
+ &dev_attr_boot_sound.attr,
+ &dev_attr_panel_od.attr,
+ &dev_attr_mini_led_mode.attr,
+ &dev_attr_available_mini_led_mode.attr,
+ &dev_attr_throttle_thermal_policy.attr,
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
NULL
};
@@ -4434,7 +4739,11 @@ 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)
+ else if (attr == &dev_attr_fan_boost_mode.attr)
+ ok = asus->fan_boost_mode_available;
+
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
+ if (attr == &dev_attr_charge_mode.attr)
devid = ASUS_WMI_DEVID_CHARGE_MODE;
else if (attr == &dev_attr_egpu_enable.attr)
ok = asus->egpu_enable_available;
@@ -4453,7 +4762,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
else if (attr == &dev_attr_ppt_pl1_spl.attr)
devid = ASUS_WMI_DEVID_PPT_PL1_SPL;
else if (attr == &dev_attr_ppt_fppt.attr)
- devid = ASUS_WMI_DEVID_PPT_FPPT;
+ devid = ASUS_WMI_DEVID_PPT_PL3_FPPT;
else if (attr == &dev_attr_ppt_apu_sppt.attr)
devid = ASUS_WMI_DEVID_PPT_APU_SPPT;
else if (attr == &dev_attr_ppt_platform_sppt.attr)
@@ -4472,6 +4781,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
ok = asus->mini_led_dev_id != 0;
else if (attr == &dev_attr_available_mini_led_mode.attr)
ok = asus->mini_led_dev_id != 0;
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
if (devid != -1) {
ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
@@ -4695,7 +5005,7 @@ static int asus_wmi_add(struct platform_device *pdev)
int err;
u32 result;
- asus = kzalloc(sizeof(struct asus_wmi), GFP_KERNEL);
+ asus = kzalloc_obj(struct asus_wmi);
if (!asus)
return -ENOMEM;
@@ -4711,7 +5021,23 @@ static int asus_wmi_add(struct platform_device *pdev)
if (err)
goto fail_platform;
+ if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_INIT) {
+ if (acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE)
+ && dmi_check_system(asus_rog_ally_device))
+ use_ally_mcu_hack = ASUS_WMI_ALLY_MCU_HACK_ENABLED;
+ if (dmi_match(DMI_BOARD_NAME, "RC71")) {
+ /*
+ * These steps ensure the device is in a valid good state, this is
+ * especially important for the Ally 1 after a reboot.
+ */
+ acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE,
+ ASUS_USB0_PWR_EC0_CSEE_ON);
+ msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
+ }
+ }
+
/* ensure defaults for tunables */
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
asus->ppt_pl2_sppt = 5;
asus->ppt_pl1_spl = 5;
asus->ppt_apu_sppt = 5;
@@ -4723,8 +5049,6 @@ static int asus_wmi_add(struct platform_device *pdev)
asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
- asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE)
- && dmi_check_system(asus_ally_mcu_quirk);
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE))
asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE;
@@ -4735,17 +5059,20 @@ static int asus_wmi_add(struct platform_device *pdev)
asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX;
else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX_VIVO))
asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX_VIVO;
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
- if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE))
- asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE;
- else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2))
- asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2;
+ asus->oobe_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_OOBE);
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY))
asus->throttle_thermal_policy_dev = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY;
else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO))
asus->throttle_thermal_policy_dev = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO;
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE))
+ asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE;
+ else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2))
+ asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2;
+
err = fan_boost_mode_check_present(asus);
if (err)
goto fail_fan_boost_mode;
@@ -4777,7 +5104,8 @@ static int asus_wmi_add(struct platform_device *pdev)
goto fail_leds;
asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WLAN, &result);
- if (result & (ASUS_WMI_DSTS_PRESENCE_BIT | ASUS_WMI_DSTS_USER_BIT))
+ if ((result & (ASUS_WMI_DSTS_PRESENCE_BIT | ASUS_WMI_DSTS_USER_BIT)) ==
+ (ASUS_WMI_DSTS_PRESENCE_BIT | ASUS_WMI_DSTS_USER_BIT))
asus->driver->wlan_ctrl_by_user = 1;
if (!(asus->driver->wlan_ctrl_by_user && ashs_present())) {
@@ -4910,34 +5238,6 @@ static int asus_hotk_resume(struct device *device)
return 0;
}
-static int asus_hotk_resume_early(struct device *device)
-{
- struct asus_wmi *asus = dev_get_drvdata(device);
-
- if (asus->ally_mcu_usb_switch) {
- /* sleep required to prevent USB0 being yanked then reappearing rapidly */
- if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8)))
- dev_err(device, "ROG Ally MCU failed to connect USB dev\n");
- else
- msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
- }
- return 0;
-}
-
-static int asus_hotk_prepare(struct device *device)
-{
- struct asus_wmi *asus = dev_get_drvdata(device);
-
- if (asus->ally_mcu_usb_switch) {
- /* sleep required to ensure USB0 is disabled before sleep continues */
- if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB7)))
- dev_err(device, "ROG Ally MCU failed to disconnect USB dev\n");
- else
- msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
- }
- return 0;
-}
-
static int asus_hotk_restore(struct device *device)
{
struct asus_wmi *asus = dev_get_drvdata(device);
@@ -4970,6 +5270,13 @@ static int asus_hotk_restore(struct device *device)
}
if (!IS_ERR_OR_NULL(asus->kbd_led.dev))
kbd_led_update(asus);
+ if (asus->oobe_state_available) {
+ /*
+ * Disable OOBE state, so that e.g. the keyboard backlight
+ * works.
+ */
+ asus_wmi_set_devstate(ASUS_WMI_DEVID_OOBE, 1, NULL);
+ }
if (asus_wmi_has_fnlock_key(asus))
asus_wmi_fnlock_update(asus);
@@ -4978,11 +5285,50 @@ static int asus_hotk_restore(struct device *device)
return 0;
}
+static int asus_hotk_prepare(struct device *device)
+{
+ if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_ENABLED) {
+ acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE,
+ ASUS_USB0_PWR_EC0_CSEE_OFF);
+ msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
+ }
+ return 0;
+}
+
+#if defined(CONFIG_SUSPEND)
+static void asus_ally_s2idle_restore(void)
+{
+ if (use_ally_mcu_hack == ASUS_WMI_ALLY_MCU_HACK_ENABLED) {
+ acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE,
+ ASUS_USB0_PWR_EC0_CSEE_ON);
+ msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
+ }
+}
+
+/* Use only for Ally devices due to the wake_on_ac */
+static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = {
+ .restore = asus_ally_s2idle_restore,
+};
+
+static void asus_s2idle_check_register(void)
+{
+ if (acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops))
+ pr_warn("failed to register LPS0 sleep handler in asus-wmi\n");
+}
+
+static void asus_s2idle_check_unregister(void)
+{
+ acpi_unregister_lps0_dev(&asus_ally_s2idle_dev_ops);
+}
+#else
+static void asus_s2idle_check_register(void) {}
+static void asus_s2idle_check_unregister(void) {}
+#endif /* CONFIG_SUSPEND */
+
static const struct dev_pm_ops asus_pm_ops = {
.thaw = asus_hotk_thaw,
.restore = asus_hotk_restore,
.resume = asus_hotk_resume,
- .resume_early = asus_hotk_resume_early,
.prepare = asus_hotk_prepare,
};
@@ -5010,16 +5356,24 @@ static int asus_wmi_probe(struct platform_device *pdev)
return ret;
}
- return asus_wmi_add(pdev);
+ asus_s2idle_check_register();
+
+ ret = asus_wmi_add(pdev);
+ if (ret)
+ asus_s2idle_check_unregister();
+
+ return ret;
}
static bool used;
+static DEFINE_MUTEX(register_mutex);
int __init_or_module asus_wmi_register_driver(struct asus_wmi_driver *driver)
{
struct platform_driver *platform_driver;
struct platform_device *platform_device;
+ guard(mutex)(&register_mutex);
if (used)
return -EBUSY;
@@ -5042,22 +5396,11 @@ EXPORT_SYMBOL_GPL(asus_wmi_register_driver);
void asus_wmi_unregister_driver(struct asus_wmi_driver *driver)
{
+ guard(mutex)(&register_mutex);
+ asus_s2idle_check_unregister();
+
platform_device_unregister(driver->platform_device);
platform_driver_unregister(&driver->platform_driver);
used = false;
}
EXPORT_SYMBOL_GPL(asus_wmi_unregister_driver);
-
-static int __init asus_wmi_init(void)
-{
- pr_info("ASUS WMI generic driver loaded\n");
- return 0;
-}
-
-static void __exit asus_wmi_exit(void)
-{
- pr_info("ASUS WMI generic driver unloaded\n");
-}
-
-module_init(asus_wmi_init);
-module_exit(asus_wmi_exit);
diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h
index 018dfde4025e..5cd4392b964e 100644
--- a/drivers/platform/x86/asus-wmi.h
+++ b/drivers/platform/x86/asus-wmi.h
@@ -18,6 +18,7 @@
#include <linux/i8042.h>
#define ASUS_WMI_KEY_IGNORE (-1)
+#define ASUS_WMI_KEY_ARMOURY 0xffff01
#define ASUS_WMI_BRN_DOWN 0x2e
#define ASUS_WMI_BRN_UP 0x2f
@@ -40,7 +41,7 @@ struct quirk_entry {
bool wmi_force_als_set;
bool wmi_ignore_fan;
bool filter_i8042_e1_extended_codes;
- bool ignore_key_wlan;
+ int key_wlan_event;
enum asus_wmi_tablet_switch_mode tablet_switch_mode;
int wapf;
/*
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
new file mode 100644
index 000000000000..41a24e091248
--- /dev/null
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Platform driver for the Embedded Controller (EC) of Ayaneo devices. Handles
+ * hwmon (fan speed, fan control), battery charge limits, and magic module
+ * control (connected modules, controller disconnection).
+ *
+ * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/power_supply.h>
+#include <linux/sysfs.h>
+#include <acpi/battery.h>
+
+#define AYANEO_PWM_ENABLE_REG 0x4A
+#define AYANEO_PWM_REG 0x4B
+#define AYANEO_PWM_MODE_AUTO 0x00
+#define AYANEO_PWM_MODE_MANUAL 0x01
+
+#define AYANEO_FAN_REG 0x76
+
+#define EC_CHARGE_CONTROL_BEHAVIOURS \
+ (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))
+#define AYANEO_CHARGE_REG 0x1e
+#define AYANEO_CHARGE_VAL_AUTO 0xaa
+#define AYANEO_CHARGE_VAL_INHIBIT 0x55
+
+#define AYANEO_POWER_REG 0x2d
+#define AYANEO_POWER_OFF 0xfe
+#define AYANEO_POWER_ON 0xff
+#define AYANEO_MODULE_REG 0x2f
+#define AYANEO_MODULE_LEFT BIT(0)
+#define AYANEO_MODULE_RIGHT BIT(1)
+#define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
+
+struct ayaneo_ec_quirk {
+ bool has_fan_control;
+ bool has_charge_control;
+ bool has_magic_modules;
+};
+
+struct ayaneo_ec_platform_data {
+ struct platform_device *pdev;
+ struct ayaneo_ec_quirk *quirks;
+ struct acpi_battery_hook battery_hook;
+
+ // Protects access to restore_pwm
+ struct mutex hwmon_lock;
+ bool restore_charge_limit;
+ bool restore_pwm;
+};
+
+static const struct ayaneo_ec_quirk quirk_fan = {
+ .has_fan_control = true,
+};
+
+static const struct ayaneo_ec_quirk quirk_charge_limit = {
+ .has_fan_control = true,
+ .has_charge_control = true,
+};
+
+static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
+ .has_fan_control = true,
+ .has_charge_control = true,
+ .has_magic_modules = true,
+};
+
+static const struct dmi_system_id dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
+ },
+ .driver_data = (void *)&quirk_fan,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "FLIP"),
+ },
+ .driver_data = (void *)&quirk_fan,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "GEEK"),
+ },
+ .driver_data = (void *)&quirk_fan,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 3"),
+ },
+ .driver_data = (void *)&quirk_ayaneo3,
+ },
+ {},
+};
+
+/* Callbacks for hwmon interface */
+static umode_t ayaneo_ec_hwmon_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ return 0444;
+ case hwmon_pwm:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ u8 tmp;
+ int ret;
+
+ switch (type) {
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ ret = ec_read(AYANEO_FAN_REG, &tmp);
+ if (ret)
+ return ret;
+ *val = tmp << 8;
+ ret = ec_read(AYANEO_FAN_REG + 1, &tmp);
+ if (ret)
+ return ret;
+ *val |= tmp;
+ return 0;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ ret = ec_read(AYANEO_PWM_REG, &tmp);
+ if (ret)
+ return ret;
+ if (tmp > 100)
+ return -EIO;
+ *val = (255 * tmp) / 100;
+ return 0;
+ case hwmon_pwm_enable:
+ ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
+ if (ret)
+ return ret;
+ if (tmp == AYANEO_PWM_MODE_MANUAL)
+ *val = 1;
+ else if (tmp == AYANEO_PWM_MODE_AUTO)
+ *val = 2;
+ else
+ return -EIO;
+ return 0;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct ayaneo_ec_platform_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ guard(mutex)(&data->hwmon_lock);
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ data->restore_pwm = false;
+ switch (val) {
+ case 1:
+ return ec_write(AYANEO_PWM_ENABLE_REG,
+ AYANEO_PWM_MODE_MANUAL);
+ case 2:
+ return ec_write(AYANEO_PWM_ENABLE_REG,
+ AYANEO_PWM_MODE_AUTO);
+ default:
+ return -EINVAL;
+ }
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+ if (data->restore_pwm) {
+ /*
+ * Defer restoring PWM control to after
+ * userspace resumes successfully
+ */
+ ret = ec_write(AYANEO_PWM_ENABLE_REG,
+ AYANEO_PWM_MODE_MANUAL);
+ if (ret)
+ return ret;
+ data->restore_pwm = false;
+ }
+ return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops ayaneo_ec_hwmon_ops = {
+ .is_visible = ayaneo_ec_hwmon_is_visible,
+ .read = ayaneo_ec_read,
+ .write = ayaneo_ec_write,
+};
+
+static const struct hwmon_channel_info *const ayaneo_ec_sensors[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+ NULL,
+};
+
+static const struct hwmon_chip_info ayaneo_ec_chip_info = {
+ .ops = &ayaneo_ec_hwmon_ops,
+ .info = ayaneo_ec_sensors,
+};
+
+static int ayaneo_psy_ext_get_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret;
+ u8 tmp;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ ret = ec_read(AYANEO_CHARGE_REG, &tmp);
+ if (ret)
+ return ret;
+
+ if (tmp == AYANEO_CHARGE_VAL_INHIBIT)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ayaneo_psy_ext_set_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ u8 raw_val;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ raw_val = AYANEO_CHARGE_VAL_AUTO;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ raw_val = AYANEO_CHARGE_VAL_INHIBIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ec_write(AYANEO_CHARGE_REG, raw_val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ayaneo_psy_prop_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp)
+{
+ return true;
+}
+
+static const enum power_supply_property ayaneo_psy_ext_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+static const struct power_supply_ext ayaneo_psy_ext = {
+ .name = "ayaneo-charge-control",
+ .properties = ayaneo_psy_ext_props,
+ .num_properties = ARRAY_SIZE(ayaneo_psy_ext_props),
+ .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS,
+ .get_property = ayaneo_psy_ext_get_prop,
+ .set_property = ayaneo_psy_ext_set_prop,
+ .property_is_writeable = ayaneo_psy_prop_is_writeable,
+};
+
+static int ayaneo_add_battery(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ struct ayaneo_ec_platform_data *data =
+ container_of(hook, struct ayaneo_ec_platform_data, battery_hook);
+
+ return power_supply_register_extension(battery, &ayaneo_psy_ext,
+ &data->pdev->dev, NULL);
+}
+
+static int ayaneo_remove_battery(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &ayaneo_psy_ext);
+ return 0;
+}
+
+static ssize_t controller_power_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ bool value;
+ int ret;
+
+ ret = kstrtobool(buf, &value);
+ if (ret)
+ return ret;
+
+ ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t controller_power_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 val;
+
+ ret = ec_read(AYANEO_POWER_REG, &val);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
+}
+
+static DEVICE_ATTR_RW(controller_power);
+
+static ssize_t controller_modules_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 unconnected_modules;
+ char *out;
+ int ret;
+
+ ret = ec_read(AYANEO_MODULE_REG, &unconnected_modules);
+ if (ret)
+ return ret;
+
+ switch (~unconnected_modules & AYANEO_MODULE_MASK) {
+ case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
+ out = "both";
+ break;
+ case AYANEO_MODULE_LEFT:
+ out = "left";
+ break;
+ case AYANEO_MODULE_RIGHT:
+ out = "right";
+ break;
+ default:
+ out = "none";
+ break;
+ }
+
+ return sysfs_emit(buf, "%s\n", out);
+}
+
+static DEVICE_ATTR_RO(controller_modules);
+
+static struct attribute *aya_mm_attrs[] = {
+ &dev_attr_controller_power.attr,
+ &dev_attr_controller_modules.attr,
+ NULL
+};
+
+static umode_t aya_mm_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
+
+ if (data->quirks->has_magic_modules)
+ return attr->mode;
+ return 0;
+}
+
+static const struct attribute_group aya_mm_attribute_group = {
+ .is_visible = aya_mm_is_visible,
+ .attrs = aya_mm_attrs,
+};
+
+static const struct attribute_group *ayaneo_ec_groups[] = {
+ &aya_mm_attribute_group,
+ NULL
+};
+
+static int ayaneo_ec_probe(struct platform_device *pdev)
+{
+ const struct dmi_system_id *dmi_entry;
+ struct ayaneo_ec_platform_data *data;
+ struct device *hwdev;
+ int ret;
+
+ dmi_entry = dmi_first_match(dmi_table);
+ if (!dmi_entry)
+ return -ENODEV;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->pdev = pdev;
+ data->quirks = dmi_entry->driver_data;
+ ret = devm_mutex_init(&pdev->dev, &data->hwmon_lock);
+ if (ret)
+ return ret;
+ platform_set_drvdata(pdev, data);
+
+ if (data->quirks->has_fan_control) {
+ hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "ayaneo_ec", data, &ayaneo_ec_chip_info, NULL);
+ if (IS_ERR(hwdev))
+ return PTR_ERR(hwdev);
+ }
+
+ if (data->quirks->has_charge_control) {
+ data->battery_hook.add_battery = ayaneo_add_battery;
+ data->battery_hook.remove_battery = ayaneo_remove_battery;
+ data->battery_hook.name = "Ayaneo Battery";
+ ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ayaneo_freeze(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
+ int ret;
+ u8 tmp;
+
+ if (data->quirks->has_charge_control) {
+ ret = ec_read(AYANEO_CHARGE_REG, &tmp);
+ if (ret)
+ return ret;
+
+ data->restore_charge_limit = tmp == AYANEO_CHARGE_VAL_INHIBIT;
+ }
+
+ if (data->quirks->has_fan_control) {
+ ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
+ if (ret)
+ return ret;
+
+ data->restore_pwm = tmp == AYANEO_PWM_MODE_MANUAL;
+
+ /*
+ * Release the fan when entering hibernation to avoid
+ * overheating if hibernation fails and hangs.
+ */
+ if (data->restore_pwm) {
+ ret = ec_write(AYANEO_PWM_ENABLE_REG, AYANEO_PWM_MODE_AUTO);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int ayaneo_restore(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
+ int ret;
+
+ if (data->quirks->has_charge_control && data->restore_charge_limit) {
+ ret = ec_write(AYANEO_CHARGE_REG, AYANEO_CHARGE_VAL_INHIBIT);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops ayaneo_pm_ops = {
+ .freeze = ayaneo_freeze,
+ .restore = ayaneo_restore,
+};
+
+static struct platform_driver ayaneo_platform_driver = {
+ .driver = {
+ .name = "ayaneo-ec",
+ .dev_groups = ayaneo_ec_groups,
+ .pm = pm_sleep_ptr(&ayaneo_pm_ops),
+ },
+ .probe = ayaneo_ec_probe,
+};
+
+static struct platform_device *ayaneo_platform_device;
+
+static int __init ayaneo_ec_init(void)
+{
+ ayaneo_platform_device =
+ platform_create_bundle(&ayaneo_platform_driver,
+ ayaneo_ec_probe, NULL, 0, NULL, 0);
+
+ return PTR_ERR_OR_ZERO(ayaneo_platform_device);
+}
+
+static void __exit ayaneo_ec_exit(void)
+{
+ platform_device_unregister(ayaneo_platform_device);
+ platform_driver_unregister(&ayaneo_platform_driver);
+}
+
+MODULE_DEVICE_TABLE(dmi, dmi_table);
+
+module_init(ayaneo_ec_init);
+module_exit(ayaneo_ec_exit);
+
+MODULE_AUTHOR("Antheas Kapenekakis <lkml@antheas.dev>");
+MODULE_DESCRIPTION("Ayaneo Embedded Controller (EC) platform features");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/barco-p50-gpio.c b/drivers/platform/x86/barco-p50-gpio.c
index 143d14548565..2a6d8607c402 100644
--- a/drivers/platform/x86/barco-p50-gpio.c
+++ b/drivers/platform/x86/barco-p50-gpio.c
@@ -11,6 +11,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/delay.h>
+#include <linux/dev_printk.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/io.h>
@@ -18,10 +19,11 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
-#include <linux/gpio_keys.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
+#include <linux/property.h>
#define DRIVER_NAME "barco-p50-gpio"
@@ -78,44 +80,57 @@ static const char * const gpio_names[] = {
[P50_GPIO_LINE_BTN] = "identify-button",
};
-
-static struct gpiod_lookup_table p50_gpio_led_table = {
- .dev_id = "leds-gpio",
- .table = {
- GPIO_LOOKUP_IDX(DRIVER_NAME, P50_GPIO_LINE_LED, NULL, 0, GPIO_ACTIVE_HIGH),
- {}
- }
+static const struct software_node gpiochip_node = {
+ .name = DRIVER_NAME,
};
/* GPIO LEDs */
-static struct gpio_led leds[] = {
- { .name = "identify" }
+static const struct software_node gpio_leds_node = {
+ .name = "gpio-leds-identify",
+};
+
+static const struct property_entry identify_led_props[] = {
+ PROPERTY_ENTRY_GPIO("gpios", &gpiochip_node, P50_GPIO_LINE_LED, GPIO_ACTIVE_HIGH),
+ { }
};
-static struct gpio_led_platform_data leds_pdata = {
- .num_leds = ARRAY_SIZE(leds),
- .leds = leds,
+static const struct software_node identify_led_node = {
+ .parent = &gpio_leds_node,
+ .name = "identify",
+ .properties = identify_led_props,
};
/* GPIO keyboard */
-static struct gpio_keys_button buttons[] = {
- {
- .code = KEY_VENDOR,
- .gpio = P50_GPIO_LINE_BTN,
- .active_low = 1,
- .type = EV_KEY,
- .value = 1,
- },
+static const struct property_entry gpio_keys_props[] = {
+ PROPERTY_ENTRY_STRING("label", "identify"),
+ PROPERTY_ENTRY_U32("poll-interval", 100),
+ { }
};
-static struct gpio_keys_platform_data keys_pdata = {
- .buttons = buttons,
- .nbuttons = ARRAY_SIZE(buttons),
- .poll_interval = 100,
- .rep = 0,
- .name = "identify",
+static const struct software_node gpio_keys_node = {
+ .name = "gpio-keys-identify",
+ .properties = gpio_keys_props,
+};
+
+static struct property_entry vendor_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_VENDOR),
+ PROPERTY_ENTRY_GPIO("gpios", &gpiochip_node, P50_GPIO_LINE_BTN, GPIO_ACTIVE_LOW),
+ { }
};
+static const struct software_node vendor_key_node = {
+ .parent = &gpio_keys_node,
+ .properties = vendor_key_props,
+};
+
+static const struct software_node *p50_swnodes[] = {
+ &gpiochip_node,
+ &gpio_leds_node,
+ &identify_led_node,
+ &gpio_keys_node,
+ &vendor_key_node,
+ NULL
+};
/* low level access routines */
@@ -257,30 +272,41 @@ static int p50_gpio_get(struct gpio_chip *gc, unsigned int offset)
struct p50_gpio *p50 = gpiochip_get_data(gc);
int ret;
- mutex_lock(&p50->lock);
+ guard(mutex)(&p50->lock);
ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_READ_GPIO, gpio_params[offset], 0);
- if (ret == 0)
- ret = p50_read_mbox_reg(p50, P50_MBOX_REG_DATA);
+ if (ret < 0)
+ return ret;
- mutex_unlock(&p50->lock);
+ ret = p50_read_mbox_reg(p50, P50_MBOX_REG_DATA);
+ if (ret < 0)
+ return ret;
- return ret;
+ return !!ret;
}
-static void p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+static int p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
{
struct p50_gpio *p50 = gpiochip_get_data(gc);
- mutex_lock(&p50->lock);
+ guard(mutex)(&p50->lock);
- p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO, gpio_params[offset], value);
-
- mutex_unlock(&p50->lock);
+ return p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO,
+ gpio_params[offset], value);
}
static int p50_gpio_probe(struct platform_device *pdev)
{
+ struct platform_device_info key_info = {
+ .name = "gpio-keys-polled",
+ .id = PLATFORM_DEVID_NONE,
+ .parent = &pdev->dev,
+ };
+ struct platform_device_info led_info = {
+ .name = "leds-gpio",
+ .id = PLATFORM_DEVID_NONE,
+ .parent = &pdev->dev,
+ };
struct p50_gpio *p50;
struct resource *res;
int ret;
@@ -335,25 +361,20 @@ static int p50_gpio_probe(struct platform_device *pdev)
return ret;
}
- gpiod_add_lookup_table(&p50_gpio_led_table);
-
- p50->leds_pdev = platform_device_register_data(&pdev->dev,
- "leds-gpio", PLATFORM_DEVID_NONE, &leds_pdata, sizeof(leds_pdata));
+ ret = software_node_register_node_group(p50_swnodes);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to register software nodes");
+ led_info.fwnode = software_node_fwnode(&gpio_leds_node);
+ p50->leds_pdev = platform_device_register_full(&led_info);
if (IS_ERR(p50->leds_pdev)) {
ret = PTR_ERR(p50->leds_pdev);
dev_err(&pdev->dev, "Could not register leds-gpio: %d\n", ret);
goto err_leds;
}
- /* gpio-keys-polled uses old-style gpio interface, pass the right identifier */
- buttons[0].gpio += p50->gc.base;
-
- p50->keys_pdev =
- platform_device_register_data(&pdev->dev, "gpio-keys-polled",
- PLATFORM_DEVID_NONE,
- &keys_pdata, sizeof(keys_pdata));
-
+ key_info.fwnode = software_node_fwnode(&gpio_keys_node);
+ p50->keys_pdev = platform_device_register_full(&key_info);
if (IS_ERR(p50->keys_pdev)) {
ret = PTR_ERR(p50->keys_pdev);
dev_err(&pdev->dev, "Could not register gpio-keys-polled: %d\n", ret);
@@ -365,7 +386,7 @@ static int p50_gpio_probe(struct platform_device *pdev)
err_keys:
platform_device_unregister(p50->leds_pdev);
err_leds:
- gpiod_remove_lookup_table(&p50_gpio_led_table);
+ software_node_unregister_node_group(p50_swnodes);
return ret;
}
@@ -377,7 +398,7 @@ static void p50_gpio_remove(struct platform_device *pdev)
platform_device_unregister(p50->keys_pdev);
platform_device_unregister(p50->leds_pdev);
- gpiod_remove_lookup_table(&p50_gpio_led_table);
+ software_node_unregister_node_group(p50_swnodes);
}
static struct platform_driver p50_gpio_driver = {
diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86/bitland-mifs-wmi.c
new file mode 100644
index 000000000000..b0d06a80e89e
--- /dev/null
+++ b/drivers/platform/x86/bitland-mifs-wmi.c
@@ -0,0 +1,837 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Linux driver for Bitland notebooks.
+ *
+ * Copyright (C) 2026 2 Mingyou Chen <qby140326@gmail.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/init.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/pm.h>
+#include <linux/power_supply.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+#include <linux/wmi.h>
+
+#define DRV_NAME "bitland-mifs-wmi"
+#define BITLAND_MIFS_GUID "B60BFB48-3E5B-49E4-A0E9-8CFFE1B3434B"
+#define BITLAND_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF"
+
+enum bitland_mifs_operation {
+ WMI_METHOD_GET = 250,
+ WMI_METHOD_SET = 251,
+};
+
+enum bitland_mifs_function {
+ WMI_FN_SYSTEM_PER_MODE = 8,
+ WMI_FN_GPU_MODE = 9,
+ WMI_FN_KBD_TYPE = 10,
+ WMI_FN_FN_LOCK = 11,
+ WMI_FN_TP_LOCK = 12,
+ WMI_FN_FAN_SPEEDS = 13,
+ WMI_FN_RGB_KB_MODE = 16,
+ WMI_FN_RGB_KB_COLOR = 17,
+ WMI_FN_RGB_KB_BRIGHTNESS = 18,
+ WMI_FN_SYSTEM_AC_TYPE = 19,
+ WMI_FN_MAX_FAN_SWITCH = 20,
+ WMI_FN_MAX_FAN_SPEED = 21,
+ WMI_FN_CPU_THERMOMETER = 22,
+ WMI_FN_CPU_POWER = 23,
+};
+
+enum bitland_system_ac_mode {
+ WMI_SYSTEM_AC_TYPEC = 1,
+ /* Unknown type, this is unused in the original driver */
+ WMI_SYSTEM_AC_CIRCULARHOLE = 2,
+};
+
+enum bitland_mifs_power_profile {
+ WMI_PP_BALANCED = 0,
+ WMI_PP_PERFORMANCE = 1,
+ WMI_PP_QUIET = 2,
+ WMI_PP_FULL_SPEED = 3,
+};
+
+enum bitland_mifs_event_id {
+ WMI_EVENT_RESERVED_1 = 1,
+ WMI_EVENT_RESERVED_2 = 2,
+ WMI_EVENT_RESERVED_3 = 3,
+ WMI_EVENT_AIRPLANE_MODE = 4,
+ WMI_EVENT_KBD_BRIGHTNESS = 5,
+ WMI_EVENT_TOUCHPAD_STATE = 6,
+ WMI_EVENT_FNLOCK_STATE = 7,
+ WMI_EVENT_KBD_MODE = 8,
+ WMI_EVENT_CAPSLOCK_STATE = 9,
+ WMI_EVENT_CALCULATOR_START = 11,
+ WMI_EVENT_BROWSER_START = 12,
+ WMI_EVENT_NUMLOCK_STATE = 13,
+ WMI_EVENT_SCROLLLOCK_STATE = 14,
+ WMI_EVENT_PERFORMANCE_PLAN = 15,
+ WMI_EVENT_FN_J = 16,
+ WMI_EVENT_FN_F = 17,
+ WMI_EVENT_FN_0 = 18,
+ WMI_EVENT_FN_1 = 19,
+ WMI_EVENT_FN_2 = 20,
+ WMI_EVENT_FN_3 = 21,
+ WMI_EVENT_FN_4 = 22,
+ WMI_EVENT_FN_5 = 24,
+ WMI_EVENT_REFRESH_RATE = 25,
+ WMI_EVENT_CPU_FAN_SPEED = 26,
+ WMI_EVENT_GPU_FAN_SPEED = 32,
+ WMI_EVENT_WIN_KEY_LOCK = 33,
+ WMI_EVENT_RESERVED_23 = 34,
+ WMI_EVENT_OPEN_APP = 35,
+};
+
+enum bitland_mifs_event_type {
+ WMI_EVENT_TYPE_HOTKEY = 1,
+};
+
+enum bitland_wmi_device_type {
+ BITLAND_WMI_CONTROL = 0,
+ BITLAND_WMI_EVENT = 1,
+};
+
+struct bitland_mifs_input {
+ u8 reserved1;
+ u8 operation;
+ u8 reserved2;
+ u8 function;
+ u8 payload[28];
+} __packed;
+
+struct bitland_mifs_output {
+ u8 reserved1;
+ u8 operation;
+ u8 reserved2;
+ u8 function;
+ u8 data[28];
+} __packed;
+
+struct bitland_mifs_event {
+ u8 event_type;
+ u8 event_id;
+ u8 value_low; /* For most events, this is the value */
+ u8 value_high; /* For fan speed events, combined with value_low */
+ u8 reserved[4];
+} __packed;
+
+static BLOCKING_NOTIFIER_HEAD(bitland_notifier_list);
+
+enum bitland_notifier_actions {
+ BITLAND_NOTIFY_KBD_BRIGHTNESS,
+ BITLAND_NOTIFY_PLATFORM_PROFILE,
+ BITLAND_NOTIFY_HWMON,
+};
+
+struct bitland_fan_notify_data {
+ int channel; /* 0 = CPU, 1 = GPU */
+ u16 speed;
+};
+
+struct bitland_mifs_wmi_data {
+ struct wmi_device *wdev;
+ struct mutex lock; /* Protects WMI calls */
+ struct led_classdev kbd_led;
+ struct notifier_block notifier;
+ struct input_dev *input_dev;
+ struct device *hwmon_dev;
+ struct device *pp_dev;
+ enum platform_profile_option saved_profile;
+};
+
+static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data,
+ const struct bitland_mifs_input *input,
+ struct bitland_mifs_output *output)
+{
+ struct wmi_buffer in_buf = { .length = sizeof(*input), .data = (void *)input };
+ struct wmi_buffer out_buf = { 0 };
+ int ret;
+
+ guard(mutex)(&data->lock);
+
+ if (!output)
+ return wmidev_invoke_procedure(data->wdev, 0, 1, &in_buf);
+
+ ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, sizeof(*output));
+ if (ret)
+ return ret;
+
+ memcpy(output, out_buf.data, sizeof(*output));
+ kfree(out_buf.data);
+
+ return 0;
+}
+
+static int laptop_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_GET,
+ .reserved2 = 0,
+ .function = WMI_FN_SYSTEM_PER_MODE,
+ };
+ struct bitland_mifs_output result;
+ int ret;
+
+ ret = bitland_mifs_wmi_call(data, &input, &result);
+ if (ret)
+ return ret;
+
+ switch (result.data[0]) {
+ case WMI_PP_BALANCED:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ break;
+ case WMI_PP_PERFORMANCE:
+ *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+ break;
+ case WMI_PP_QUIET:
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ break;
+ case WMI_PP_FULL_SPEED:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data)
+{
+ struct bitland_mifs_input input = {
+ .operation = WMI_METHOD_GET,
+ .function = WMI_FN_SYSTEM_AC_TYPE,
+ };
+ struct bitland_mifs_output output;
+ int ret;
+
+ /* Full-speed/performance mode requires DC power (not USB-C) */
+ if (!power_supply_is_system_supplied())
+ return -EOPNOTSUPP;
+
+ ret = bitland_mifs_wmi_call(data, &input, &output);
+ if (ret)
+ return ret;
+
+ if (output.data[0] != WMI_SYSTEM_AC_CIRCULARHOLE)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int laptop_profile_set(struct device *dev,
+ enum platform_profile_option profile)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_SET,
+ .reserved2 = 0,
+ .function = WMI_FN_SYSTEM_PER_MODE,
+ };
+ int ret;
+ u8 val;
+
+ switch (profile) {
+ case PLATFORM_PROFILE_LOW_POWER:
+ val = WMI_PP_QUIET;
+ break;
+ case PLATFORM_PROFILE_BALANCED:
+ val = WMI_PP_BALANCED;
+ break;
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+ ret = bitland_check_performance_capability(data);
+ if (ret)
+ return ret;
+ val = WMI_PP_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_PERFORMANCE:
+ ret = bitland_check_performance_capability(data);
+ if (ret)
+ return ret;
+ val = WMI_PP_FULL_SPEED;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ input.payload[0] = val;
+
+ return bitland_mifs_wmi_call(data, &input, NULL);
+}
+
+static int platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+ set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+
+ return 0;
+}
+
+static int bitland_mifs_wmi_suspend(struct device *dev)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ enum platform_profile_option profile;
+ int ret;
+
+ ret = laptop_profile_get(data->pp_dev, &profile);
+ if (ret == 0)
+ data->saved_profile = profile;
+
+ return ret;
+}
+
+static int bitland_mifs_wmi_resume(struct device *dev)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "Resuming, restoring profile %d\n", data->saved_profile);
+ return laptop_profile_set(dev, data->saved_profile);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(bitland_mifs_wmi_pm_ops,
+ bitland_mifs_wmi_suspend,
+ bitland_mifs_wmi_resume);
+
+static const struct platform_profile_ops laptop_profile_ops = {
+ .probe = platform_profile_probe,
+ .profile_get = laptop_profile_get,
+ .profile_set = laptop_profile_set,
+};
+
+static const char *const fan_labels[] = {
+ "CPU", /* 0 */
+ "GPU", /* 1 */
+ "SYS", /* 2 */
+};
+
+static int laptop_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_GET,
+ .reserved2 = 0,
+ };
+ struct bitland_mifs_output res;
+ int ret;
+
+ switch (type) {
+ case hwmon_temp:
+ input.function = WMI_FN_CPU_THERMOMETER;
+ ret = bitland_mifs_wmi_call(data, &input, &res);
+ if (!ret)
+ *val = res.data[0] * MILLIDEGREE_PER_DEGREE;
+ return ret;
+ case hwmon_fan:
+ input.function = WMI_FN_FAN_SPEEDS;
+ ret = bitland_mifs_wmi_call(data, &input, &res);
+ if (ret)
+ return ret;
+
+ switch (channel) {
+ case 0: /* CPU */
+ *val = get_unaligned_le16(&res.data[0]);
+ return 0;
+ case 1: /* GPU */
+ *val = get_unaligned_le16(&res.data[2]);
+ return 0;
+ case 2: /* SYS */
+ *val = get_unaligned_le16(&res.data[6]);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int laptop_hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ if (type == hwmon_fan && attr == hwmon_fan_label) {
+ if (channel >= 0 && channel < ARRAY_SIZE(fan_labels)) {
+ *str = fan_labels[channel];
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static const struct hwmon_channel_info *laptop_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops laptop_hwmon_ops = {
+ .visible = 0444,
+ .read = laptop_hwmon_read,
+ .read_string = laptop_hwmon_read_string,
+};
+
+static const struct hwmon_chip_info laptop_chip_info = {
+ .ops = &laptop_hwmon_ops,
+ .info = laptop_hwmon_info,
+};
+
+static int laptop_kbd_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct bitland_mifs_wmi_data *data =
+ container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_SET,
+ .reserved2 = 0,
+ .function = WMI_FN_RGB_KB_BRIGHTNESS,
+ };
+
+ input.payload[0] = (u8)value;
+
+ return bitland_mifs_wmi_call(data, &input, NULL);
+}
+
+static enum led_brightness laptop_kbd_led_get(struct led_classdev *led_cdev)
+{
+ struct bitland_mifs_wmi_data *data =
+ container_of(led_cdev, struct bitland_mifs_wmi_data, kbd_led);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_GET,
+ .reserved2 = 0,
+ .function = WMI_FN_RGB_KB_BRIGHTNESS,
+ };
+ struct bitland_mifs_output res;
+ int ret;
+
+ ret = bitland_mifs_wmi_call(data, &input, &res);
+ if (ret)
+ return ret;
+
+ return res.data[0];
+}
+
+static const char *const gpu_mode_strings[] = {
+ "hybrid",
+ "discrete",
+ "uma",
+};
+
+/* GPU Mode: 0:Hybrid, 1:Discrete, 2:UMA */
+static ssize_t gpu_mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_GET,
+ .reserved2 = 0,
+ .function = WMI_FN_GPU_MODE,
+ };
+ struct bitland_mifs_output res;
+ u8 mode_val;
+ int ret;
+
+ ret = bitland_mifs_wmi_call(data, &input, &res);
+ if (ret)
+ return ret;
+
+ mode_val = res.data[0];
+ if (mode_val >= ARRAY_SIZE(gpu_mode_strings))
+ return -EPROTO;
+
+ return sysfs_emit(buf, "%s\n", gpu_mode_strings[mode_val]);
+}
+
+static ssize_t gpu_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_SET,
+ .reserved2 = 0,
+ .function = WMI_FN_GPU_MODE,
+ };
+ int val;
+ int ret;
+
+ val = sysfs_match_string(gpu_mode_strings, buf);
+ if (val < 0)
+ return -EINVAL;
+
+ input.payload[0] = (u8)val;
+
+ ret = bitland_mifs_wmi_call(data, &input, NULL);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static const char *const kb_mode_strings[] = {
+ "off", /* 0 */
+ "cyclic", /* 1 */
+ "fixed", /* 2 */
+ "custom", /* 3 */
+};
+
+static ssize_t kb_mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_GET,
+ .reserved2 = 0,
+ .function = WMI_FN_RGB_KB_MODE,
+ };
+ struct bitland_mifs_output res;
+ u8 mode_val;
+ int ret;
+
+ ret = bitland_mifs_wmi_call(data, &input, &res);
+ if (ret)
+ return ret;
+
+ mode_val = res.data[0];
+ if (mode_val >= ARRAY_SIZE(kb_mode_strings))
+ return -EPROTO;
+
+ return sysfs_emit(buf, "%s\n", kb_mode_strings[mode_val]);
+}
+
+static ssize_t kb_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_SET,
+ .reserved2 = 0,
+ .function = WMI_FN_RGB_KB_MODE,
+ };
+ // the wmi value (0, 1, 2 or 3)
+ int val;
+ int ret;
+
+ val = sysfs_match_string(kb_mode_strings, buf);
+ if (val < 0)
+ return -EINVAL;
+
+ input.payload[0] = (u8)val;
+
+ ret = bitland_mifs_wmi_call(data, &input, NULL);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+/* Fan Boost: 0:Normal, 1:Max Speed */
+static ssize_t fan_boost_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+ struct bitland_mifs_input input = {
+ .reserved1 = 0,
+ .operation = WMI_METHOD_SET,
+ .reserved2 = 0,
+ .function = WMI_FN_MAX_FAN_SWITCH,
+ };
+ bool val;
+ int ret;
+
+ if (kstrtobool(buf, &val))
+ return -EINVAL;
+
+ input.payload[0] = 0; /* CPU/GPU Fan */
+ input.payload[1] = val;
+
+ ret = bitland_mifs_wmi_call(data, &input, NULL);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static const DEVICE_ATTR_RW(gpu_mode);
+static const DEVICE_ATTR_RW(kb_mode);
+static const DEVICE_ATTR_WO(fan_boost);
+
+static const struct attribute *const laptop_attrs[] = {
+ &dev_attr_gpu_mode.attr,
+ &dev_attr_kb_mode.attr,
+ &dev_attr_fan_boost.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(laptop);
+
+static const struct key_entry bitland_mifs_wmi_keymap[] = {
+ { KE_KEY, WMI_EVENT_OPEN_APP, { KEY_PROG1 } },
+ { KE_KEY, WMI_EVENT_CALCULATOR_START, { KEY_CALC } },
+ { KE_KEY, WMI_EVENT_BROWSER_START, { KEY_WWW } },
+ { KE_IGNORE, WMI_EVENT_FN_J, { KEY_RESERVED } },
+ { KE_IGNORE, WMI_EVENT_FN_F, { KEY_RESERVED } },
+ { KE_IGNORE, WMI_EVENT_FN_0, { KEY_RESERVED } },
+ { KE_IGNORE, WMI_EVENT_FN_1, { KEY_RESERVED } },
+ { KE_IGNORE, WMI_EVENT_FN_2, { KEY_RESERVED } },
+ { KE_IGNORE, WMI_EVENT_FN_3, { KEY_RESERVED } },
+ { KE_IGNORE, WMI_EVENT_FN_4, { KEY_RESERVED } },
+ { KE_IGNORE, WMI_EVENT_FN_5, { KEY_RESERVED } },
+ { KE_END, 0 }
+};
+
+static void bitland_notifier_unregister(void *data)
+{
+ struct notifier_block *nb = data;
+
+ blocking_notifier_chain_unregister(&bitland_notifier_list, nb);
+}
+
+static int bitland_notifier_callback(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct bitland_mifs_wmi_data *data_ctx =
+ container_of(nb, struct bitland_mifs_wmi_data, notifier);
+ struct bitland_fan_notify_data *fan_info;
+ u8 *brightness;
+
+ switch (action) {
+ case BITLAND_NOTIFY_KBD_BRIGHTNESS:
+ brightness = data;
+ led_classdev_notify_brightness_hw_changed(&data_ctx->kbd_led,
+ *brightness);
+ break;
+ case BITLAND_NOTIFY_PLATFORM_PROFILE:
+ platform_profile_notify(data_ctx->pp_dev);
+ break;
+ case BITLAND_NOTIFY_HWMON:
+ fan_info = data;
+
+ hwmon_notify_event(data_ctx->hwmon_dev, hwmon_fan,
+ hwmon_fan_input, fan_info->channel);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+ struct bitland_mifs_wmi_data *drv_data;
+ enum bitland_wmi_device_type dev_type =
+ (enum bitland_wmi_device_type)(unsigned long)context;
+ struct led_init_data init_data = {
+ .devicename = DRV_NAME,
+ .default_label = ":" LED_FUNCTION_KBD_BACKLIGHT,
+ .devname_mandatory = true,
+ };
+ int ret;
+
+ drv_data = devm_kzalloc(&wdev->dev, sizeof(*drv_data), GFP_KERNEL);
+ if (!drv_data)
+ return -ENOMEM;
+
+ drv_data->wdev = wdev;
+
+ ret = devm_mutex_init(&wdev->dev, &drv_data->lock);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(&wdev->dev, drv_data);
+
+ if (dev_type == BITLAND_WMI_EVENT) {
+ /* Register input device for hotkeys */
+ drv_data->input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!drv_data->input_dev)
+ return -ENOMEM;
+
+ drv_data->input_dev->name = "Bitland MIFS WMI hotkeys";
+ drv_data->input_dev->phys = "wmi/input0";
+ drv_data->input_dev->id.bustype = BUS_HOST;
+ drv_data->input_dev->dev.parent = &wdev->dev;
+
+ ret = sparse_keymap_setup(drv_data->input_dev,
+ bitland_mifs_wmi_keymap, NULL);
+ if (ret)
+ return ret;
+
+ return input_register_device(drv_data->input_dev);
+ }
+
+ /* Register platform profile */
+ drv_data->pp_dev = devm_platform_profile_register(&wdev->dev, DRV_NAME, drv_data,
+ &laptop_profile_ops);
+ if (IS_ERR(drv_data->pp_dev))
+ return PTR_ERR(drv_data->pp_dev);
+
+ /* Register hwmon */
+ drv_data->hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev,
+ "bitland_mifs",
+ drv_data,
+ &laptop_chip_info,
+ NULL);
+ if (IS_ERR(drv_data->hwmon_dev))
+ return PTR_ERR(drv_data->hwmon_dev);
+
+ /* Register keyboard LED */
+ drv_data->kbd_led.max_brightness = 3;
+ drv_data->kbd_led.brightness_set_blocking = laptop_kbd_led_set;
+ drv_data->kbd_led.brightness_get = laptop_kbd_led_get;
+ drv_data->kbd_led.brightness = laptop_kbd_led_get(&drv_data->kbd_led);
+ drv_data->kbd_led.flags = LED_CORE_SUSPENDRESUME |
+ LED_BRIGHT_HW_CHANGED |
+ LED_REJECT_NAME_CONFLICT;
+ ret = devm_led_classdev_register_ext(&wdev->dev, &drv_data->kbd_led, &init_data);
+ if (ret)
+ return ret;
+
+ drv_data->notifier.notifier_call = bitland_notifier_callback;
+ ret = blocking_notifier_chain_register(&bitland_notifier_list, &drv_data->notifier);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(&wdev->dev,
+ bitland_notifier_unregister,
+ &drv_data->notifier);
+}
+
+static void bitland_mifs_wmi_notify(struct wmi_device *wdev,
+ const struct wmi_buffer *buffer)
+{
+ struct bitland_mifs_wmi_data *data = dev_get_drvdata(&wdev->dev);
+ const struct bitland_mifs_event *event = buffer->data;
+ struct bitland_fan_notify_data fan_data;
+ u8 brightness;
+
+ /* Validate event type */
+ if (event->event_type != WMI_EVENT_TYPE_HOTKEY)
+ return;
+
+ dev_dbg(&wdev->dev,
+ "WMI event: id=0x%02x value_low=0x%02x value_high=0x%02x\n",
+ event->event_id, event->value_low, event->value_high);
+
+ switch (event->event_id) {
+ case WMI_EVENT_KBD_BRIGHTNESS:
+ brightness = event->value_low;
+ blocking_notifier_call_chain(&bitland_notifier_list,
+ BITLAND_NOTIFY_KBD_BRIGHTNESS,
+ &brightness);
+ break;
+
+ case WMI_EVENT_PERFORMANCE_PLAN:
+ blocking_notifier_call_chain(&bitland_notifier_list,
+ BITLAND_NOTIFY_PLATFORM_PROFILE,
+ NULL);
+ break;
+
+ case WMI_EVENT_OPEN_APP:
+ case WMI_EVENT_CALCULATOR_START:
+ case WMI_EVENT_BROWSER_START: {
+ guard(mutex)(&data->lock);
+ if (!sparse_keymap_report_event(data->input_dev,
+ event->event_id, 1, true))
+ dev_warn(&wdev->dev, "Unknown key pressed: 0x%02x\n",
+ event->event_id);
+ break;
+ }
+
+ /*
+ * The device has 3 fans (CPU, GPU, SYS),
+ * but there are only the CPU and GPU fan has events
+ */
+ case WMI_EVENT_CPU_FAN_SPEED:
+ case WMI_EVENT_GPU_FAN_SPEED:
+ if (event->event_id == WMI_EVENT_CPU_FAN_SPEED)
+ fan_data.channel = 0;
+ else
+ fan_data.channel = 1;
+
+ /* Fan speed is 16-bit value (value_low is LSB, value_high is MSB) */
+ fan_data.speed = (event->value_high << 8) | event->value_low;
+ blocking_notifier_call_chain(&bitland_notifier_list,
+ BITLAND_NOTIFY_HWMON,
+ &fan_data);
+ break;
+
+ case WMI_EVENT_AIRPLANE_MODE:
+ case WMI_EVENT_TOUCHPAD_STATE:
+ case WMI_EVENT_FNLOCK_STATE:
+ case WMI_EVENT_KBD_MODE:
+ case WMI_EVENT_CAPSLOCK_STATE:
+ case WMI_EVENT_NUMLOCK_STATE:
+ case WMI_EVENT_SCROLLLOCK_STATE:
+ case WMI_EVENT_REFRESH_RATE:
+ case WMI_EVENT_WIN_KEY_LOCK:
+ /* These events are informational or handled by firmware */
+ dev_dbg(&wdev->dev, "State change event: id=%d value=%d\n",
+ event->event_id, event->value_low);
+ break;
+
+ default:
+ dev_dbg(&wdev->dev, "Unknown event: id=0x%02x value=0x%02x\n",
+ event->event_id, event->value_low);
+ break;
+ }
+}
+
+static const struct wmi_device_id bitland_mifs_wmi_id_table[] = {
+ { BITLAND_MIFS_GUID, (void *)BITLAND_WMI_CONTROL },
+ { BITLAND_EVENT_GUID, (void *)BITLAND_WMI_EVENT },
+ {}
+};
+MODULE_DEVICE_TABLE(wmi, bitland_mifs_wmi_id_table);
+
+static struct wmi_driver bitland_mifs_wmi_driver = {
+ .no_singleton = true,
+ .driver = {
+ .name = DRV_NAME,
+ .dev_groups = laptop_groups,
+ .pm = pm_sleep_ptr(&bitland_mifs_wmi_pm_ops),
+ },
+ .id_table = bitland_mifs_wmi_id_table,
+ .min_event_size = sizeof(struct bitland_mifs_event),
+ .probe = bitland_mifs_wmi_probe,
+ .notify_new = bitland_mifs_wmi_notify,
+};
+
+module_wmi_driver(bitland_mifs_wmi_driver);
+
+MODULE_AUTHOR("Mingyou Chen <qby140326@gmail.com>");
+MODULE_DESCRIPTION("Bitland MIFS (MiInterface) WMI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c
index 6b1b8e444e24..e6eed3d65580 100644
--- a/drivers/platform/x86/classmate-laptop.c
+++ b/drivers/platform/x86/classmate-laptop.c
@@ -207,7 +207,12 @@ static ssize_t cmpc_accel_sensitivity_show_v4(struct device *dev,
acpi = to_acpi_device(dev);
inputdev = dev_get_drvdata(&acpi->dev);
+ if (!inputdev)
+ return -ENXIO;
+
accel = dev_get_drvdata(&inputdev->dev);
+ if (!accel)
+ return -ENXIO;
return sysfs_emit(buf, "%d\n", accel->sensitivity);
}
@@ -224,7 +229,12 @@ static ssize_t cmpc_accel_sensitivity_store_v4(struct device *dev,
acpi = to_acpi_device(dev);
inputdev = dev_get_drvdata(&acpi->dev);
+ if (!inputdev)
+ return -ENXIO;
+
accel = dev_get_drvdata(&inputdev->dev);
+ if (!accel)
+ return -ENXIO;
r = kstrtoul(buf, 0, &sensitivity);
if (r)
@@ -256,7 +266,12 @@ static ssize_t cmpc_accel_g_select_show_v4(struct device *dev,
acpi = to_acpi_device(dev);
inputdev = dev_get_drvdata(&acpi->dev);
+ if (!inputdev)
+ return -ENXIO;
+
accel = dev_get_drvdata(&inputdev->dev);
+ if (!accel)
+ return -ENXIO;
return sysfs_emit(buf, "%d\n", accel->g_select);
}
@@ -273,7 +288,12 @@ static ssize_t cmpc_accel_g_select_store_v4(struct device *dev,
acpi = to_acpi_device(dev);
inputdev = dev_get_drvdata(&acpi->dev);
+ if (!inputdev)
+ return -ENXIO;
+
accel = dev_get_drvdata(&inputdev->dev);
+ if (!accel)
+ return -ENXIO;
r = kstrtoul(buf, 0, &g_select);
if (r)
@@ -302,6 +322,8 @@ static int cmpc_accel_open_v4(struct input_dev *input)
acpi = to_acpi_device(input->dev.parent);
accel = dev_get_drvdata(&input->dev);
+ if (!accel)
+ return -ENXIO;
cmpc_accel_set_sensitivity_v4(acpi->handle, accel->sensitivity);
cmpc_accel_set_g_select_v4(acpi->handle, accel->g_select);
@@ -378,7 +400,7 @@ static int cmpc_accel_add_v4(struct acpi_device *acpi)
struct input_dev *inputdev;
struct cmpc_accel *accel;
- accel = kmalloc(sizeof(*accel), GFP_KERNEL);
+ accel = kmalloc_obj(*accel);
if (!accel)
return -ENOMEM;
@@ -549,7 +571,12 @@ static ssize_t cmpc_accel_sensitivity_show(struct device *dev,
acpi = to_acpi_device(dev);
inputdev = dev_get_drvdata(&acpi->dev);
+ if (!inputdev)
+ return -ENXIO;
+
accel = dev_get_drvdata(&inputdev->dev);
+ if (!accel)
+ return -ENXIO;
return sysfs_emit(buf, "%d\n", accel->sensitivity);
}
@@ -566,7 +593,12 @@ static ssize_t cmpc_accel_sensitivity_store(struct device *dev,
acpi = to_acpi_device(dev);
inputdev = dev_get_drvdata(&acpi->dev);
+ if (!inputdev)
+ return -ENXIO;
+
accel = dev_get_drvdata(&inputdev->dev);
+ if (!accel)
+ return -ENXIO;
r = kstrtoul(buf, 0, &sensitivity);
if (r)
@@ -618,7 +650,7 @@ static int cmpc_accel_add(struct acpi_device *acpi)
struct input_dev *inputdev;
struct cmpc_accel *accel;
- accel = kmalloc(sizeof(*accel), GFP_KERNEL);
+ accel = kmalloc_obj(*accel);
if (!accel)
return -ENOMEM;
@@ -932,7 +964,7 @@ static int cmpc_ipml_add(struct acpi_device *acpi)
struct ipml200_dev *ipml;
struct backlight_properties props;
- ipml = kmalloc(sizeof(*ipml), GFP_KERNEL);
+ ipml = kmalloc_obj(*ipml);
if (ipml == NULL)
return -ENOMEM;
diff --git a/drivers/platform/x86/dasharo-acpi.c b/drivers/platform/x86/dasharo-acpi.c
new file mode 100644
index 000000000000..f0c5136af29d
--- /dev/null
+++ b/drivers/platform/x86/dasharo-acpi.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Dasharo ACPI Driver
+ */
+
+#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+enum dasharo_feature {
+ DASHARO_FEATURE_TEMPERATURE = 0,
+ DASHARO_FEATURE_FAN_PWM,
+ DASHARO_FEATURE_FAN_TACH,
+ DASHARO_FEATURE_MAX
+};
+
+enum dasharo_temperature {
+ DASHARO_TEMPERATURE_CPU_PACKAGE = 0,
+ DASHARO_TEMPERATURE_CPU_CORE,
+ DASHARO_TEMPERATURE_GPU,
+ DASHARO_TEMPERATURE_BOARD,
+ DASHARO_TEMPERATURE_CHASSIS,
+ DASHARO_TEMPERATURE_MAX
+};
+
+enum dasharo_fan {
+ DASHARO_FAN_CPU = 0,
+ DASHARO_FAN_GPU,
+ DASHARO_FAN_CHASSIS,
+ DASHARO_FAN_MAX
+};
+
+#define MAX_GROUPS_PER_FEAT 8
+
+static const char * const dasharo_group_names[DASHARO_FEATURE_MAX][MAX_GROUPS_PER_FEAT] = {
+ [DASHARO_FEATURE_TEMPERATURE] = {
+ [DASHARO_TEMPERATURE_CPU_PACKAGE] = "CPU Package",
+ [DASHARO_TEMPERATURE_CPU_CORE] = "CPU Core",
+ [DASHARO_TEMPERATURE_GPU] = "GPU",
+ [DASHARO_TEMPERATURE_BOARD] = "Board",
+ [DASHARO_TEMPERATURE_CHASSIS] = "Chassis",
+ },
+ [DASHARO_FEATURE_FAN_PWM] = {
+ [DASHARO_FAN_CPU] = "CPU",
+ [DASHARO_FAN_GPU] = "GPU",
+ [DASHARO_FAN_CHASSIS] = "Chassis",
+ },
+ [DASHARO_FEATURE_FAN_TACH] = {
+ [DASHARO_FAN_CPU] = "CPU",
+ [DASHARO_FAN_GPU] = "GPU",
+ [DASHARO_FAN_CHASSIS] = "Chassis",
+ },
+};
+
+struct dasharo_capability {
+ unsigned int group;
+ unsigned int index;
+ char name[16];
+};
+
+#define MAX_CAPS_PER_FEAT 24
+
+struct dasharo_data {
+ struct platform_device *pdev;
+ int caps_found[DASHARO_FEATURE_MAX];
+ struct dasharo_capability capabilities[DASHARO_FEATURE_MAX][MAX_CAPS_PER_FEAT];
+};
+
+static int dasharo_get_feature_cap_count(struct dasharo_data *data, enum dasharo_feature feat, int cap)
+{
+ struct acpi_object_list obj_list;
+ union acpi_object obj[2];
+ acpi_handle handle;
+ acpi_status status;
+ u64 count;
+
+ obj[0].type = ACPI_TYPE_INTEGER;
+ obj[0].integer.value = feat;
+ obj[1].type = ACPI_TYPE_INTEGER;
+ obj[1].integer.value = cap;
+ obj_list.count = 2;
+ obj_list.pointer = &obj[0];
+
+ handle = ACPI_HANDLE(&data->pdev->dev);
+ status = acpi_evaluate_integer(handle, "GFCP", &obj_list, &count);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ return count;
+}
+
+static int dasharo_read_channel(struct dasharo_data *data, char *method, enum dasharo_feature feat, int channel, long *value)
+{
+ struct acpi_object_list obj_list;
+ union acpi_object obj[2];
+ acpi_handle handle;
+ acpi_status status;
+ u64 val;
+
+ if (feat >= ARRAY_SIZE(data->capabilities))
+ return -EINVAL;
+
+ if (channel >= data->caps_found[feat])
+ return -EINVAL;
+
+ obj[0].type = ACPI_TYPE_INTEGER;
+ obj[0].integer.value = data->capabilities[feat][channel].group;
+ obj[1].type = ACPI_TYPE_INTEGER;
+ obj[1].integer.value = data->capabilities[feat][channel].index;
+ obj_list.count = 2;
+ obj_list.pointer = &obj[0];
+
+ handle = ACPI_HANDLE(&data->pdev->dev);
+ status = acpi_evaluate_integer(handle, method, &obj_list, &val);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ *value = val;
+ return 0;
+}
+
+static int dasharo_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct dasharo_data *data = dev_get_drvdata(dev);
+ long value;
+ int ret;
+
+ switch (type) {
+ case hwmon_temp:
+ ret = dasharo_read_channel(data, "GTMP", DASHARO_FEATURE_TEMPERATURE, channel, &value);
+ if (!ret)
+ *val = value * MILLIDEGREE_PER_DEGREE;
+ break;
+ case hwmon_fan:
+ ret = dasharo_read_channel(data, "GFTH", DASHARO_FEATURE_FAN_TACH, channel, &value);
+ if (!ret)
+ *val = value;
+ break;
+ case hwmon_pwm:
+ ret = dasharo_read_channel(data, "GFDC", DASHARO_FEATURE_FAN_PWM, channel, &value);
+ if (!ret)
+ *val = value;
+ break;
+ default:
+ return -ENODEV;
+ break;
+ }
+
+ return ret;
+}
+
+static int dasharo_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ struct dasharo_data *data = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ if (channel >= data->caps_found[DASHARO_FEATURE_TEMPERATURE])
+ return -EINVAL;
+
+ *str = data->capabilities[DASHARO_FEATURE_TEMPERATURE][channel].name;
+ break;
+ case hwmon_fan:
+ if (channel >= data->caps_found[DASHARO_FEATURE_FAN_TACH])
+ return -EINVAL;
+
+ *str = data->capabilities[DASHARO_FEATURE_FAN_TACH][channel].name;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static umode_t dasharo_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct dasharo_data *data = drvdata;
+
+ switch (type) {
+ case hwmon_temp:
+ if (channel < data->caps_found[DASHARO_FEATURE_TEMPERATURE])
+ return 0444;
+ break;
+ case hwmon_pwm:
+ if (channel < data->caps_found[DASHARO_FEATURE_FAN_PWM])
+ return 0444;
+ break;
+ case hwmon_fan:
+ if (channel < data->caps_found[DASHARO_FEATURE_FAN_TACH])
+ return 0444;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops dasharo_hwmon_ops = {
+ .is_visible = dasharo_hwmon_is_visible,
+ .read_string = dasharo_hwmon_read_string,
+ .read = dasharo_hwmon_read,
+};
+
+// Max 24 capabilities per feature
+static const struct hwmon_channel_info * const dasharo_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ NULL
+};
+
+static const struct hwmon_chip_info dasharo_hwmon_chip_info = {
+ .ops = &dasharo_hwmon_ops,
+ .info = dasharo_hwmon_info,
+};
+
+static void dasharo_fill_feature_caps(struct dasharo_data *data, enum dasharo_feature feat)
+{
+ struct dasharo_capability *cap;
+ int cap_count = 0;
+ int count;
+
+ for (int group = 0; group < MAX_GROUPS_PER_FEAT; ++group) {
+ count = dasharo_get_feature_cap_count(data, feat, group);
+ if (count <= 0)
+ continue;
+
+ for (unsigned int i = 0; i < count; ++i) {
+ if (cap_count >= ARRAY_SIZE(data->capabilities[feat]))
+ break;
+
+ cap = &data->capabilities[feat][cap_count];
+ cap->group = group;
+ cap->index = i;
+ scnprintf(cap->name, sizeof(cap->name), "%s %d",
+ dasharo_group_names[feat][group], i);
+ cap_count++;
+ }
+ }
+ data->caps_found[feat] = cap_count;
+}
+
+static int dasharo_probe(struct platform_device *pdev)
+{
+ struct dasharo_data *data;
+ struct device *hwmon;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ data->pdev = pdev;
+
+ for (unsigned int i = 0; i < DASHARO_FEATURE_MAX; ++i)
+ dasharo_fill_feature_caps(data, i);
+
+ hwmon = devm_hwmon_device_register_with_info(&pdev->dev, "dasharo_acpi", data,
+ &dasharo_hwmon_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(hwmon);
+}
+
+static const struct acpi_device_id dasharo_device_ids[] = {
+ {"DSHR0001", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, dasharo_device_ids);
+
+static struct platform_driver dasharo_driver = {
+ .driver = {
+ .name = "dasharo-acpi",
+ .acpi_match_table = dasharo_device_ids,
+ },
+ .probe = dasharo_probe,
+};
+module_platform_driver(dasharo_driver);
+
+MODULE_DESCRIPTION("Dasharo ACPI Driver");
+MODULE_AUTHOR("Michał Kopeć <michal.kopec@3mdeb.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig
index f8a0dffcaab7..738c108c2163 100644
--- a/drivers/platform/x86/dell/Kconfig
+++ b/drivers/platform/x86/dell/Kconfig
@@ -22,6 +22,7 @@ config ALIENWARE_WMI
depends on DMI
depends on LEDS_CLASS
depends on NEW_LEDS
+ depends on HWMON
help
This is a driver for controlling Alienware WMI driven features.
@@ -171,7 +172,7 @@ config DELL_SMBIOS_SMM
config DELL_SMO8800
tristate "Dell Latitude freefall driver (ACPI SMO88XX)"
- default m
+ default m if ACPI
depends on I2C
depends on ACPI || COMPILE_TEST
help
diff --git a/drivers/platform/x86/dell/alienware-wmi-wmax.c b/drivers/platform/x86/dell/alienware-wmi-wmax.c
index 3d3014b5adf0..d1b4df91401b 100644
--- a/drivers/platform/x86/dell/alienware-wmi-wmax.c
+++ b/drivers/platform/x86/dell/alienware-wmi-wmax.c
@@ -8,11 +8,21 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/array_size.h>
#include <linux/bitfield.h>
+#include <linux/bitmap.h>
#include <linux/bits.h>
+#include <linux/debugfs.h>
#include <linux/dmi.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/kstrtox.h>
+#include <linux/minmax.h>
#include <linux/moduleparam.h>
#include <linux/platform_profile.h>
+#include <linux/pm.h>
+#include <linux/seq_file.h>
+#include <linux/units.h>
#include <linux/wmi.h>
#include "alienware-wmi.h"
@@ -24,16 +34,31 @@
#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
#define WMAX_METHOD_BRIGHTNESS 0x3
#define WMAX_METHOD_ZONE_CONTROL 0x4
-#define WMAX_METHOD_THERMAL_INFORMATION 0x14
-#define WMAX_METHOD_THERMAL_CONTROL 0x15
-#define WMAX_METHOD_GAME_SHIFT_STATUS 0x25
-#define WMAX_THERMAL_MODE_GMODE 0xAB
+#define AWCC_METHOD_GET_FAN_SENSORS 0x13
+#define AWCC_METHOD_THERMAL_INFORMATION 0x14
+#define AWCC_METHOD_THERMAL_CONTROL 0x15
+#define AWCC_METHOD_FWUP_GPIO_CONTROL 0x20
+#define AWCC_METHOD_READ_TOTAL_GPIOS 0x21
+#define AWCC_METHOD_READ_GPIO_STATUS 0x22
+#define AWCC_METHOD_GAME_SHIFT_STATUS 0x25
-#define WMAX_FAILURE_CODE 0xFFFFFFFF
-#define WMAX_THERMAL_TABLE_MASK GENMASK(7, 4)
-#define WMAX_THERMAL_MODE_MASK GENMASK(3, 0)
-#define WMAX_SENSOR_ID_MASK BIT(8)
+#define AWCC_FAILURE_CODE 0xFFFFFFFF
+#define AWCC_FAILURE_CODE_2 0xFFFFFFFE
+
+#define AWCC_SENSOR_ID_FLAG BIT(8)
+#define AWCC_THERMAL_MODE_MASK GENMASK(3, 0)
+#define AWCC_THERMAL_TABLE_MASK GENMASK(7, 4)
+#define AWCC_RESOURCE_ID_MASK GENMASK(7, 0)
+
+/* Arbitrary limit based on supported models */
+#define AWCC_MAX_RES_COUNT 16
+#define AWCC_ID_BITMAP_SIZE (U8_MAX + 1)
+#define AWCC_ID_BITMAP_LONGS BITS_TO_LONGS(AWCC_ID_BITMAP_SIZE)
+
+static bool force_hwmon;
+module_param_unsafe(force_hwmon, bool, 0);
+MODULE_PARM_DESC(force_hwmon, "Force probing for HWMON support without checking if the WMI backend is available");
static bool force_platform_profile;
module_param_unsafe(force_platform_profile, bool, 0);
@@ -44,16 +69,19 @@ module_param_unsafe(force_gmode, bool, 0);
MODULE_PARM_DESC(force_gmode, "Forces G-Mode when performance profile is selected");
struct awcc_quirks {
+ bool hwmon;
bool pprof;
bool gmode;
};
static struct awcc_quirks g_series_quirks = {
+ .hwmon = true,
.pprof = true,
.gmode = true,
};
static struct awcc_quirks generic_quirks = {
+ .hwmon = true,
.pprof = true,
.gmode = false,
};
@@ -62,127 +90,221 @@ static struct awcc_quirks empty_quirks;
static const struct dmi_system_id awcc_dmi_table[] __initconst = {
{
- .ident = "Alienware m16 R1 AMD",
+ .ident = "Alienware 16 Area-51",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
- DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware 16 Area-51"),
+ },
+ .driver_data = &g_series_quirks,
+ },
+ {
+ .ident = "Alienware 16X Aurora",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware 16X Aurora"),
+ },
+ .driver_data = &g_series_quirks,
+ },
+ {
+ .ident = "Alienware 18 Area-51",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware 18 Area-51"),
+ },
+ .driver_data = &g_series_quirks,
+ },
+ {
+ .ident = "Alienware 16 Aurora",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware 16 Aurora"),
+ },
+ .driver_data = &g_series_quirks,
+ },
+ {
+ .ident = "Alienware Area-51m",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware Area-51m"),
},
.driver_data = &generic_quirks,
},
{
- .ident = "Alienware m17 R5",
+ .ident = "Alienware m15",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
- DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m15"),
},
.driver_data = &generic_quirks,
},
{
- .ident = "Alienware m18 R2",
+ .ident = "Alienware m16 R1 AMD",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
- DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R2"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"),
},
.driver_data = &generic_quirks,
},
{
- .ident = "Alienware x15 R1",
+ .ident = "Alienware m16 R1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1"),
+ },
+ .driver_data = &g_series_quirks,
+ },
+ {
+ .ident = "Alienware m16 R2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
- DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15 R1"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R2"),
},
.driver_data = &generic_quirks,
},
{
- .ident = "Alienware x17 R2",
+ .ident = "Alienware m17",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
- DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17 R2"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17"),
},
.driver_data = &generic_quirks,
},
{
- .ident = "Dell Inc. G15 5510",
+ .ident = "Alienware m18",
.matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5510"),
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18"),
},
.driver_data = &g_series_quirks,
},
{
- .ident = "Dell Inc. G15 5511",
+ .ident = "Alienware x15",
.matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x15"),
+ },
+ .driver_data = &generic_quirks,
+ },
+ {
+ .ident = "Alienware x16",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x16"),
},
.driver_data = &g_series_quirks,
},
{
- .ident = "Dell Inc. G15 5515",
+ .ident = "Alienware x17",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware x17"),
+ },
+ .driver_data = &generic_quirks,
+ },
+ {
+ .ident = "Dell Inc. G15",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15 5515"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Dell G15"),
},
.driver_data = &g_series_quirks,
},
{
- .ident = "Dell Inc. G3 3500",
+ .ident = "Dell Inc. G16",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME, "G3 3500"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Dell G16"),
},
.driver_data = &g_series_quirks,
},
{
- .ident = "Dell Inc. G3 3590",
+ .ident = "Dell Inc. G3",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME, "G3 3590"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G3"),
},
.driver_data = &g_series_quirks,
},
{
- .ident = "Dell Inc. G5 5500",
+ .ident = "Dell Inc. G5",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
- DMI_MATCH(DMI_PRODUCT_NAME, "G5 5500"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G5"),
},
.driver_data = &g_series_quirks,
},
+ {}
+};
+
+enum AWCC_GET_FAN_SENSORS_OPERATIONS {
+ AWCC_OP_GET_TOTAL_FAN_TEMPS = 0x01,
+ AWCC_OP_GET_FAN_TEMP_ID = 0x02,
+};
+
+enum AWCC_THERMAL_INFORMATION_OPERATIONS {
+ AWCC_OP_GET_SYSTEM_DESCRIPTION = 0x02,
+ AWCC_OP_GET_RESOURCE_ID = 0x03,
+ AWCC_OP_GET_TEMPERATURE = 0x04,
+ AWCC_OP_GET_FAN_RPM = 0x05,
+ AWCC_OP_GET_FAN_MIN_RPM = 0x08,
+ AWCC_OP_GET_FAN_MAX_RPM = 0x09,
+ AWCC_OP_GET_CURRENT_PROFILE = 0x0B,
+ AWCC_OP_GET_FAN_BOOST = 0x0C,
};
-enum WMAX_THERMAL_INFORMATION_OPERATIONS {
- WMAX_OPERATION_SYS_DESCRIPTION = 0x02,
- WMAX_OPERATION_LIST_IDS = 0x03,
- WMAX_OPERATION_CURRENT_PROFILE = 0x0B,
+enum AWCC_THERMAL_CONTROL_OPERATIONS {
+ AWCC_OP_ACTIVATE_PROFILE = 0x01,
+ AWCC_OP_SET_FAN_BOOST = 0x02,
};
-enum WMAX_THERMAL_CONTROL_OPERATIONS {
- WMAX_OPERATION_ACTIVATE_PROFILE = 0x01,
+enum AWCC_GAME_SHIFT_STATUS_OPERATIONS {
+ AWCC_OP_TOGGLE_GAME_SHIFT = 0x01,
+ AWCC_OP_GET_GAME_SHIFT_STATUS = 0x02,
};
-enum WMAX_GAME_SHIFT_STATUS_OPERATIONS {
- WMAX_OPERATION_TOGGLE_GAME_SHIFT = 0x01,
- WMAX_OPERATION_GET_GAME_SHIFT_STATUS = 0x02,
+enum AWCC_THERMAL_TABLES {
+ AWCC_THERMAL_TABLE_LEGACY = 0x9,
+ AWCC_THERMAL_TABLE_USTT = 0xA,
};
-enum WMAX_THERMAL_TABLES {
- WMAX_THERMAL_TABLE_BASIC = 0x90,
- WMAX_THERMAL_TABLE_USTT = 0xA0,
+enum AWCC_TEMP_SENSOR_TYPES {
+ AWCC_TEMP_SENSOR_CPU = 0x01,
+ AWCC_TEMP_SENSOR_FRONT = 0x03,
+ AWCC_TEMP_SENSOR_GPU = 0x06,
};
-enum wmax_thermal_mode {
- THERMAL_MODE_USTT_BALANCED,
- THERMAL_MODE_USTT_BALANCED_PERFORMANCE,
- THERMAL_MODE_USTT_COOL,
- THERMAL_MODE_USTT_QUIET,
- THERMAL_MODE_USTT_PERFORMANCE,
- THERMAL_MODE_USTT_LOW_POWER,
- THERMAL_MODE_BASIC_QUIET,
- THERMAL_MODE_BASIC_BALANCED,
- THERMAL_MODE_BASIC_BALANCED_PERFORMANCE,
- THERMAL_MODE_BASIC_PERFORMANCE,
- THERMAL_MODE_LAST,
+enum AWCC_FAN_TYPES {
+ AWCC_FAN_CPU_1 = 0x32,
+ AWCC_FAN_GPU_1 = 0x33,
+ AWCC_FAN_PCI = 0x34,
+ AWCC_FAN_MID = 0x35,
+ AWCC_FAN_TOP_1 = 0x36,
+ AWCC_FAN_SIDE = 0x37,
+ AWCC_FAN_U2_1 = 0x38,
+ AWCC_FAN_U2_2 = 0x39,
+ AWCC_FAN_FRONT_1 = 0x3A,
+ AWCC_FAN_CPU_2 = 0x3B,
+ AWCC_FAN_GPU_2 = 0x3C,
+ AWCC_FAN_TOP_2 = 0x3D,
+ AWCC_FAN_TOP_3 = 0x3E,
+ AWCC_FAN_FRONT_2 = 0x3F,
+ AWCC_FAN_BOTTOM_1 = 0x40,
+ AWCC_FAN_BOTTOM_2 = 0x41,
+};
+
+enum awcc_thermal_profile {
+ AWCC_PROFILE_SPECIAL_CUSTOM = 0x00,
+ AWCC_PROFILE_LEGACY_QUIET = 0x96,
+ AWCC_PROFILE_LEGACY_BALANCED = 0x97,
+ AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE = 0x98,
+ AWCC_PROFILE_LEGACY_PERFORMANCE = 0x99,
+ AWCC_PROFILE_USTT_BALANCED = 0xA0,
+ AWCC_PROFILE_USTT_BALANCED_PERFORMANCE = 0xA1,
+ AWCC_PROFILE_USTT_COOL = 0xA2,
+ AWCC_PROFILE_USTT_QUIET = 0xA3,
+ AWCC_PROFILE_USTT_PERFORMANCE = 0xA4,
+ AWCC_PROFILE_USTT_LOW_POWER = 0xA5,
+ AWCC_PROFILE_SPECIAL_GMODE = 0xAB,
};
struct wmax_led_args {
@@ -207,23 +329,35 @@ struct wmax_u32_args {
u8 arg3;
};
+struct awcc_fan_data {
+ unsigned long auto_channels_temp;
+ u32 min_rpm;
+ u32 max_rpm;
+ u8 suspend_cache;
+ u8 id;
+};
+
struct awcc_priv {
struct wmi_device *wdev;
+ union {
+ u32 system_description;
+ struct {
+ u8 fan_count;
+ u8 temp_count;
+ u8 unknown_count;
+ u8 profile_count;
+ };
+ u8 res_count[4];
+ };
+
struct device *ppdev;
- enum wmax_thermal_mode supported_thermal_profiles[PLATFORM_PROFILE_LAST];
-};
+ u8 supported_profiles[PLATFORM_PROFILE_LAST];
+
+ struct device *hwdev;
+ struct awcc_fan_data **fan_data;
+ unsigned long temp_sensors[AWCC_ID_BITMAP_LONGS];
-static const enum platform_profile_option wmax_mode_to_platform_profile[THERMAL_MODE_LAST] = {
- [THERMAL_MODE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
- [THERMAL_MODE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
- [THERMAL_MODE_USTT_COOL] = PLATFORM_PROFILE_COOL,
- [THERMAL_MODE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
- [THERMAL_MODE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
- [THERMAL_MODE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
- [THERMAL_MODE_BASIC_QUIET] = PLATFORM_PROFILE_QUIET,
- [THERMAL_MODE_BASIC_BALANCED] = PLATFORM_PROFILE_BALANCED,
- [THERMAL_MODE_BASIC_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
- [THERMAL_MODE_BASIC_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
+ u32 gpio_count;
};
static struct awcc_quirks *awcc;
@@ -441,123 +575,696 @@ const struct attribute_group wmax_deepsleep_attribute_group = {
};
/*
- * Thermal Profile control
- * - Provides thermal profile control through the Platform Profile API
+ * AWCC Helpers
*/
-static bool is_wmax_thermal_code(u32 code)
+static int awcc_profile_to_pprof(enum awcc_thermal_profile profile,
+ enum platform_profile_option *pprof)
{
- if (code & WMAX_SENSOR_ID_MASK)
- return false;
+ switch (profile) {
+ case AWCC_PROFILE_SPECIAL_CUSTOM:
+ *pprof = PLATFORM_PROFILE_CUSTOM;
+ break;
+ case AWCC_PROFILE_LEGACY_QUIET:
+ case AWCC_PROFILE_USTT_QUIET:
+ *pprof = PLATFORM_PROFILE_QUIET;
+ break;
+ case AWCC_PROFILE_LEGACY_BALANCED:
+ case AWCC_PROFILE_USTT_BALANCED:
+ *pprof = PLATFORM_PROFILE_BALANCED;
+ break;
+ case AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE:
+ case AWCC_PROFILE_USTT_BALANCED_PERFORMANCE:
+ *pprof = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+ break;
+ case AWCC_PROFILE_LEGACY_PERFORMANCE:
+ case AWCC_PROFILE_USTT_PERFORMANCE:
+ case AWCC_PROFILE_SPECIAL_GMODE:
+ *pprof = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case AWCC_PROFILE_USTT_COOL:
+ *pprof = PLATFORM_PROFILE_COOL;
+ break;
+ case AWCC_PROFILE_USTT_LOW_POWER:
+ *pprof = PLATFORM_PROFILE_LOW_POWER;
+ break;
+ default:
+ return -EINVAL;
+ }
- if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
- return false;
+ return 0;
+}
- if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
- (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
- return true;
+static int awcc_wmi_command(struct wmi_device *wdev, u32 method_id,
+ struct wmax_u32_args *args, u32 *out)
+{
+ int ret;
- if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
- (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
- return true;
+ ret = alienware_wmi_command(wdev, method_id, args, sizeof(*args), out);
+ if (ret)
+ return ret;
+
+ if (*out == AWCC_FAILURE_CODE || *out == AWCC_FAILURE_CODE_2)
+ return -EBADRQC;
+
+ return 0;
+}
- return false;
+static int awcc_get_fan_sensors(struct wmi_device *wdev, u8 operation,
+ u8 fan_id, u8 index, u32 *out)
+{
+ struct wmax_u32_args args = {
+ .operation = operation,
+ .arg1 = fan_id,
+ .arg2 = index,
+ .arg3 = 0,
+ };
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_GET_FAN_SENSORS, &args, out);
}
-static int wmax_thermal_information(struct wmi_device *wdev, u8 operation,
- u8 arg, u32 *out_data)
+static int awcc_thermal_information(struct wmi_device *wdev, u8 operation, u8 arg,
+ u32 *out)
{
- struct wmax_u32_args in_args = {
+ struct wmax_u32_args args = {
.operation = operation,
.arg1 = arg,
.arg2 = 0,
.arg3 = 0,
};
- int ret;
- ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_INFORMATION,
- &in_args, sizeof(in_args), out_data);
- if (ret < 0)
- return ret;
+ return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
+}
- if (*out_data == WMAX_FAILURE_CODE)
- return -EBADRQC;
+static int awcc_fwup_gpio_control(struct wmi_device *wdev, u8 pin, u8 status)
+{
+ struct wmax_u32_args args = {
+ .operation = pin,
+ .arg1 = status,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+ u32 out;
- return 0;
+ return awcc_wmi_command(wdev, AWCC_METHOD_FWUP_GPIO_CONTROL, &args, &out);
}
-static int wmax_thermal_control(struct wmi_device *wdev, u8 profile)
+static int awcc_read_total_gpios(struct wmi_device *wdev, u32 *count)
{
- struct wmax_u32_args in_args = {
- .operation = WMAX_OPERATION_ACTIVATE_PROFILE,
- .arg1 = profile,
+ struct wmax_u32_args args = {};
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_READ_TOTAL_GPIOS, &args, count);
+}
+
+static int awcc_read_gpio_status(struct wmi_device *wdev, u8 pin, u32 *status)
+{
+ struct wmax_u32_args args = {
+ .operation = pin,
+ .arg1 = 0,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_READ_GPIO_STATUS, &args, status);
+}
+
+static int awcc_game_shift_status(struct wmi_device *wdev, u8 operation,
+ u32 *out)
+{
+ struct wmax_u32_args args = {
+ .operation = operation,
+ .arg1 = 0,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_GAME_SHIFT_STATUS, &args, out);
+}
+
+/**
+ * awcc_op_get_resource_id - Get the resource ID at a given index
+ * @wdev: AWCC WMI device
+ * @index: Index
+ * @out: Value returned by the WMI call
+ *
+ * Get the resource ID at a given @index. Resource IDs are listed in the
+ * following order:
+ *
+ * - Fan IDs
+ * - Sensor IDs
+ * - Unknown IDs
+ * - Thermal Profile IDs
+ *
+ * The total number of IDs of a given type can be obtained with
+ * AWCC_OP_GET_SYSTEM_DESCRIPTION.
+ *
+ * Return: 0 on success, -errno on failure
+ */
+static int awcc_op_get_resource_id(struct wmi_device *wdev, u8 index, u8 *out)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_GET_RESOURCE_ID,
+ .arg1 = index,
.arg2 = 0,
.arg3 = 0,
};
u32 out_data;
int ret;
- ret = alienware_wmi_command(wdev, WMAX_METHOD_THERMAL_CONTROL,
- &in_args, sizeof(in_args), &out_data);
+ ret = awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, &out_data);
if (ret)
return ret;
- if (out_data == WMAX_FAILURE_CODE)
- return -EBADRQC;
+ *out = FIELD_GET(AWCC_RESOURCE_ID_MASK, out_data);
return 0;
}
-static int wmax_game_shift_status(struct wmi_device *wdev, u8 operation,
- u32 *out_data)
+static int awcc_op_get_fan_rpm(struct wmi_device *wdev, u8 fan_id, u32 *out)
{
- struct wmax_u32_args in_args = {
- .operation = operation,
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_GET_FAN_RPM,
+ .arg1 = fan_id,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
+}
+
+static int awcc_op_get_temperature(struct wmi_device *wdev, u8 temp_id, u32 *out)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_GET_TEMPERATURE,
+ .arg1 = temp_id,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
+}
+
+static int awcc_op_get_fan_boost(struct wmi_device *wdev, u8 fan_id, u32 *out)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_GET_FAN_BOOST,
+ .arg1 = fan_id,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
+}
+
+static int awcc_op_get_current_profile(struct wmi_device *wdev, u32 *out)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_GET_CURRENT_PROFILE,
.arg1 = 0,
.arg2 = 0,
.arg3 = 0,
};
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_INFORMATION, &args, out);
+}
+
+static int awcc_op_activate_profile(struct wmi_device *wdev, u8 profile)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_ACTIVATE_PROFILE,
+ .arg1 = profile,
+ .arg2 = 0,
+ .arg3 = 0,
+ };
+ u32 out;
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out);
+}
+
+static int awcc_op_set_fan_boost(struct wmi_device *wdev, u8 fan_id, u8 boost)
+{
+ struct wmax_u32_args args = {
+ .operation = AWCC_OP_SET_FAN_BOOST,
+ .arg1 = fan_id,
+ .arg2 = boost,
+ .arg3 = 0,
+ };
+ u32 out;
+
+ return awcc_wmi_command(wdev, AWCC_METHOD_THERMAL_CONTROL, &args, &out);
+}
+
+/*
+ * HWMON
+ * - Provides temperature and fan speed monitoring as well as manual fan
+ * control
+ */
+static umode_t awcc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct awcc_priv *priv = drvdata;
+ unsigned int temp_count;
+
+ switch (type) {
+ case hwmon_temp:
+ temp_count = bitmap_weight(priv->temp_sensors, AWCC_ID_BITMAP_SIZE);
+
+ return channel < temp_count ? 0444 : 0;
+ case hwmon_fan:
+ return channel < priv->fan_count ? 0444 : 0;
+ case hwmon_pwm:
+ return channel < priv->fan_count ? 0444 : 0;
+ default:
+ return 0;
+ }
+}
+
+static int awcc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ const struct awcc_fan_data *fan;
+ u32 state;
int ret;
+ u8 temp;
- ret = alienware_wmi_command(wdev, WMAX_METHOD_GAME_SHIFT_STATUS,
- &in_args, sizeof(in_args), out_data);
- if (ret < 0)
- return ret;
+ switch (type) {
+ case hwmon_temp:
+ temp = find_nth_bit(priv->temp_sensors, AWCC_ID_BITMAP_SIZE, channel);
+
+ switch (attr) {
+ case hwmon_temp_input:
+ ret = awcc_op_get_temperature(priv->wdev, temp, &state);
+ if (ret)
+ return ret;
+
+ *val = state * MILLIDEGREE_PER_DEGREE;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ break;
+ case hwmon_fan:
+ fan = priv->fan_data[channel];
+
+ switch (attr) {
+ case hwmon_fan_input:
+ ret = awcc_op_get_fan_rpm(priv->wdev, fan->id, &state);
+ if (ret)
+ return ret;
+
+ *val = state;
+ break;
+ case hwmon_fan_min:
+ *val = fan->min_rpm;
+ break;
+ case hwmon_fan_max:
+ *val = fan->max_rpm;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
- if (*out_data == WMAX_FAILURE_CODE)
+ break;
+ case hwmon_pwm:
+ fan = priv->fan_data[channel];
+
+ switch (attr) {
+ case hwmon_pwm_auto_channels_temp:
+ *val = fan->auto_channels_temp;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ break;
+ default:
return -EOPNOTSUPP;
+ }
return 0;
}
-static int thermal_profile_get(struct device *dev,
- enum platform_profile_option *profile)
+static int awcc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
{
struct awcc_priv *priv = dev_get_drvdata(dev);
- u32 out_data;
+ u8 temp;
+
+ switch (type) {
+ case hwmon_temp:
+ temp = find_nth_bit(priv->temp_sensors, AWCC_ID_BITMAP_SIZE, channel);
+
+ switch (temp) {
+ case AWCC_TEMP_SENSOR_CPU:
+ *str = "CPU";
+ break;
+ case AWCC_TEMP_SENSOR_FRONT:
+ *str = "Front";
+ break;
+ case AWCC_TEMP_SENSOR_GPU:
+ *str = "GPU";
+ break;
+ default:
+ *str = "Unknown";
+ break;
+ }
+
+ break;
+ case hwmon_fan:
+ switch (priv->fan_data[channel]->id) {
+ case AWCC_FAN_CPU_1:
+ case AWCC_FAN_CPU_2:
+ *str = "CPU Fan";
+ break;
+ case AWCC_FAN_GPU_1:
+ case AWCC_FAN_GPU_2:
+ *str = "GPU Fan";
+ break;
+ case AWCC_FAN_PCI:
+ *str = "PCI Fan";
+ break;
+ case AWCC_FAN_MID:
+ *str = "Mid Fan";
+ break;
+ case AWCC_FAN_TOP_1:
+ case AWCC_FAN_TOP_2:
+ case AWCC_FAN_TOP_3:
+ *str = "Top Fan";
+ break;
+ case AWCC_FAN_SIDE:
+ *str = "Side Fan";
+ break;
+ case AWCC_FAN_U2_1:
+ case AWCC_FAN_U2_2:
+ *str = "U.2 Fan";
+ break;
+ case AWCC_FAN_FRONT_1:
+ case AWCC_FAN_FRONT_2:
+ *str = "Front Fan";
+ break;
+ case AWCC_FAN_BOTTOM_1:
+ case AWCC_FAN_BOTTOM_2:
+ *str = "Bottom Fan";
+ break;
+ default:
+ *str = "Unknown Fan";
+ break;
+ }
+
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_ops awcc_hwmon_ops = {
+ .is_visible = awcc_hwmon_is_visible,
+ .read = awcc_hwmon_read,
+ .read_string = awcc_hwmon_read_string,
+};
+
+static const struct hwmon_channel_info * const awcc_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ HWMON_T_LABEL | HWMON_T_INPUT,
+ HWMON_T_LABEL | HWMON_T_INPUT
+ ),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_LABEL | HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX
+ ),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_AUTO_CHANNELS_TEMP,
+ HWMON_PWM_AUTO_CHANNELS_TEMP,
+ HWMON_PWM_AUTO_CHANNELS_TEMP,
+ HWMON_PWM_AUTO_CHANNELS_TEMP,
+ HWMON_PWM_AUTO_CHANNELS_TEMP,
+ HWMON_PWM_AUTO_CHANNELS_TEMP
+ ),
+ NULL
+};
+
+static const struct hwmon_chip_info awcc_hwmon_chip_info = {
+ .ops = &awcc_hwmon_ops,
+ .info = awcc_hwmon_info,
+};
+
+static ssize_t fan_boost_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ int index = to_sensor_dev_attr(attr)->index;
+ struct awcc_fan_data *fan = priv->fan_data[index];
+ u32 boost;
int ret;
- ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_CURRENT_PROFILE,
- 0, &out_data);
+ ret = awcc_op_get_fan_boost(priv->wdev, fan->id, &boost);
+ if (ret)
+ return ret;
- if (ret < 0)
+ return sysfs_emit(buf, "%u\n", boost);
+}
+
+static ssize_t fan_boost_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ int index = to_sensor_dev_attr(attr)->index;
+ struct awcc_fan_data *fan = priv->fan_data[index];
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
return ret;
- if (out_data == WMAX_THERMAL_MODE_GMODE) {
- *profile = PLATFORM_PROFILE_PERFORMANCE;
- return 0;
+ ret = awcc_op_set_fan_boost(priv->wdev, fan->id, clamp_val(val, 0, 255));
+
+ return ret ? ret : count;
+}
+
+static SENSOR_DEVICE_ATTR_RW(fan1_boost, fan_boost, 0);
+static SENSOR_DEVICE_ATTR_RW(fan2_boost, fan_boost, 1);
+static SENSOR_DEVICE_ATTR_RW(fan3_boost, fan_boost, 2);
+static SENSOR_DEVICE_ATTR_RW(fan4_boost, fan_boost, 3);
+static SENSOR_DEVICE_ATTR_RW(fan5_boost, fan_boost, 4);
+static SENSOR_DEVICE_ATTR_RW(fan6_boost, fan_boost, 5);
+
+static umode_t fan_boost_attr_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+ struct awcc_priv *priv = dev_get_drvdata(kobj_to_dev(kobj));
+
+ return n < priv->fan_count ? attr->mode : 0;
+}
+
+static bool fan_boost_group_visible(struct kobject *kobj)
+{
+ return true;
+}
+
+DEFINE_SYSFS_GROUP_VISIBLE(fan_boost);
+
+static struct attribute *fan_boost_attrs[] = {
+ &sensor_dev_attr_fan1_boost.dev_attr.attr,
+ &sensor_dev_attr_fan2_boost.dev_attr.attr,
+ &sensor_dev_attr_fan3_boost.dev_attr.attr,
+ &sensor_dev_attr_fan4_boost.dev_attr.attr,
+ &sensor_dev_attr_fan5_boost.dev_attr.attr,
+ &sensor_dev_attr_fan6_boost.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group fan_boost_group = {
+ .attrs = fan_boost_attrs,
+ .is_visible = SYSFS_GROUP_VISIBLE(fan_boost),
+};
+
+static const struct attribute_group *awcc_hwmon_groups[] = {
+ &fan_boost_group,
+ NULL
+};
+
+static int awcc_hwmon_temps_init(struct wmi_device *wdev)
+{
+ struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
+ unsigned int i;
+ int ret;
+ u8 id;
+
+ for (i = 0; i < priv->temp_count; i++) {
+ /*
+ * Temperature sensors IDs are listed after the fan IDs at
+ * offset `fan_count`
+ */
+ ret = awcc_op_get_resource_id(wdev, i + priv->fan_count, &id);
+ if (ret)
+ return ret;
+
+ __set_bit(id, priv->temp_sensors);
}
- if (!is_wmax_thermal_code(out_data))
- return -ENODATA;
+ return 0;
+}
+
+static int awcc_hwmon_fans_init(struct wmi_device *wdev)
+{
+ struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
+ unsigned long fan_temps[AWCC_ID_BITMAP_LONGS];
+ unsigned long gather[AWCC_ID_BITMAP_LONGS];
+ u32 min_rpm, max_rpm, temp_count, temp_id;
+ struct awcc_fan_data *fan_data;
+ unsigned int i, j;
+ int ret;
+ u8 id;
+
+ for (i = 0; i < priv->fan_count; i++) {
+ fan_data = devm_kzalloc(&wdev->dev, sizeof(*fan_data), GFP_KERNEL);
+ if (!fan_data)
+ return -ENOMEM;
+
+ /*
+ * Fan IDs are listed first at offset 0
+ */
+ ret = awcc_op_get_resource_id(wdev, i, &id);
+ if (ret)
+ return ret;
+
+ ret = awcc_thermal_information(wdev, AWCC_OP_GET_FAN_MIN_RPM, id,
+ &min_rpm);
+ if (ret)
+ return ret;
+
+ ret = awcc_thermal_information(wdev, AWCC_OP_GET_FAN_MAX_RPM, id,
+ &max_rpm);
+ if (ret)
+ return ret;
+
+ ret = awcc_get_fan_sensors(wdev, AWCC_OP_GET_TOTAL_FAN_TEMPS, id,
+ 0, &temp_count);
+ if (ret)
+ return ret;
+
+ bitmap_zero(fan_temps, AWCC_ID_BITMAP_SIZE);
+
+ for (j = 0; j < temp_count; j++) {
+ ret = awcc_get_fan_sensors(wdev, AWCC_OP_GET_FAN_TEMP_ID,
+ id, j, &temp_id);
+ if (ret)
+ break;
+
+ temp_id = FIELD_GET(AWCC_RESOURCE_ID_MASK, temp_id);
+ __set_bit(temp_id, fan_temps);
+ }
- out_data &= WMAX_THERMAL_MODE_MASK;
- *profile = wmax_mode_to_platform_profile[out_data];
+ fan_data->id = id;
+ fan_data->min_rpm = min_rpm;
+ fan_data->max_rpm = max_rpm;
+ bitmap_gather(gather, fan_temps, priv->temp_sensors, AWCC_ID_BITMAP_SIZE);
+ bitmap_copy(&fan_data->auto_channels_temp, gather, BITS_PER_LONG);
+ priv->fan_data[i] = fan_data;
+ }
return 0;
}
-static int thermal_profile_set(struct device *dev,
- enum platform_profile_option profile)
+static int awcc_hwmon_init(struct wmi_device *wdev)
+{
+ struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
+ int ret;
+
+ priv->fan_data = devm_kcalloc(&wdev->dev, priv->fan_count,
+ sizeof(*priv->fan_data), GFP_KERNEL);
+ if (!priv->fan_data)
+ return -ENOMEM;
+
+ ret = awcc_hwmon_temps_init(wdev);
+ if (ret)
+ return ret;
+
+ ret = awcc_hwmon_fans_init(wdev);
+ if (ret)
+ return ret;
+
+ priv->hwdev = devm_hwmon_device_register_with_info(&wdev->dev, "alienware_wmi",
+ priv, &awcc_hwmon_chip_info,
+ awcc_hwmon_groups);
+
+ return PTR_ERR_OR_ZERO(priv->hwdev);
+}
+
+static void awcc_hwmon_suspend(struct device *dev)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ struct awcc_fan_data *fan;
+ unsigned int i;
+ u32 boost;
+ int ret;
+
+ for (i = 0; i < priv->fan_count; i++) {
+ fan = priv->fan_data[i];
+
+ ret = awcc_thermal_information(priv->wdev, AWCC_OP_GET_FAN_BOOST,
+ fan->id, &boost);
+ if (ret)
+ dev_err(dev, "Failed to store Fan %u boost while suspending\n", i);
+
+ fan->suspend_cache = ret ? 0 : clamp_val(boost, 0, 255);
+
+ awcc_op_set_fan_boost(priv->wdev, fan->id, 0);
+ if (ret)
+ dev_err(dev, "Failed to set Fan %u boost to 0 while suspending\n", i);
+ }
+}
+
+static void awcc_hwmon_resume(struct device *dev)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ struct awcc_fan_data *fan;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < priv->fan_count; i++) {
+ fan = priv->fan_data[i];
+
+ if (!fan->suspend_cache)
+ continue;
+
+ ret = awcc_op_set_fan_boost(priv->wdev, fan->id, fan->suspend_cache);
+ if (ret)
+ dev_err(dev, "Failed to restore Fan %u boost while resuming\n", i);
+ }
+}
+
+/*
+ * Thermal Profile control
+ * - Provides thermal profile control through the Platform Profile API
+ */
+static int awcc_platform_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ u32 out_data;
+ int ret;
+
+ ret = awcc_op_get_current_profile(priv->wdev, &out_data);
+ if (ret)
+ return ret;
+
+ return awcc_profile_to_pprof(out_data, profile);
+}
+
+static int awcc_platform_profile_set(struct device *dev,
+ enum platform_profile_option profile)
{
struct awcc_priv *priv = dev_get_drvdata(dev);
@@ -565,8 +1272,8 @@ static int thermal_profile_set(struct device *dev,
u32 gmode_status;
int ret;
- ret = wmax_game_shift_status(priv->wdev,
- WMAX_OPERATION_GET_GAME_SHIFT_STATUS,
+ ret = awcc_game_shift_status(priv->wdev,
+ AWCC_OP_GET_GAME_SHIFT_STATUS,
&gmode_status);
if (ret < 0)
@@ -574,8 +1281,8 @@ static int thermal_profile_set(struct device *dev,
if ((profile == PLATFORM_PROFILE_PERFORMANCE && !gmode_status) ||
(profile != PLATFORM_PROFILE_PERFORMANCE && gmode_status)) {
- ret = wmax_game_shift_status(priv->wdev,
- WMAX_OPERATION_TOGGLE_GAME_SHIFT,
+ ret = awcc_game_shift_status(priv->wdev,
+ AWCC_OP_TOGGLE_GAME_SHIFT,
&gmode_status);
if (ret < 0)
@@ -583,64 +1290,74 @@ static int thermal_profile_set(struct device *dev,
}
}
- return wmax_thermal_control(priv->wdev,
- priv->supported_thermal_profiles[profile]);
+ return awcc_op_activate_profile(priv->wdev, priv->supported_profiles[profile]);
}
-static int thermal_profile_probe(void *drvdata, unsigned long *choices)
+static int awcc_platform_profile_probe(void *drvdata, unsigned long *choices)
{
enum platform_profile_option profile;
struct awcc_priv *priv = drvdata;
- enum wmax_thermal_mode mode;
- u8 sys_desc[4];
- u32 first_mode;
- u32 out_data;
+ u8 id, offset = 0;
int ret;
- ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_SYS_DESCRIPTION,
- 0, (u32 *) &sys_desc);
- if (ret < 0)
- return ret;
-
- first_mode = sys_desc[0] + sys_desc[1];
-
- for (u32 i = 0; i < sys_desc[3]; i++) {
- ret = wmax_thermal_information(priv->wdev, WMAX_OPERATION_LIST_IDS,
- i + first_mode, &out_data);
-
- if (ret == -EIO)
- return ret;
-
+ /*
+ * Thermal profile IDs are listed last at offset
+ * fan_count + temp_count + unknown_count
+ */
+ for (unsigned int i = 0; i < ARRAY_SIZE(priv->res_count) - 1; i++)
+ offset += priv->res_count[i];
+
+ for (unsigned int i = 0; i < priv->profile_count; i++) {
+ ret = awcc_op_get_resource_id(priv->wdev, i + offset, &id);
+ /*
+ * Some devices report an incorrect number of thermal profiles
+ * so the resource ID list may end prematurely
+ */
if (ret == -EBADRQC)
break;
+ if (ret)
+ return ret;
- if (!is_wmax_thermal_code(out_data))
+ /*
+ * G-Mode profile ID is not listed consistently across modeles
+ * that support it, therefore we handle it through quirks.
+ */
+ if (id == AWCC_PROFILE_SPECIAL_GMODE)
continue;
- mode = out_data & WMAX_THERMAL_MODE_MASK;
- profile = wmax_mode_to_platform_profile[mode];
- priv->supported_thermal_profiles[profile] = out_data;
+ ret = awcc_profile_to_pprof(id, &profile);
+ if (ret) {
+ dev_dbg(&priv->wdev->dev, "Unmapped thermal profile ID 0x%02x\n", id);
+ continue;
+ }
- set_bit(profile, choices);
+ priv->supported_profiles[profile] = id;
+ __set_bit(profile, choices);
}
if (bitmap_empty(choices, PLATFORM_PROFILE_LAST))
return -ENODEV;
if (awcc->gmode) {
- priv->supported_thermal_profiles[PLATFORM_PROFILE_PERFORMANCE] =
- WMAX_THERMAL_MODE_GMODE;
+ priv->supported_profiles[PLATFORM_PROFILE_PERFORMANCE] =
+ AWCC_PROFILE_SPECIAL_GMODE;
- set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+ __set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
}
+ /* Every model supports the "custom" profile */
+ priv->supported_profiles[PLATFORM_PROFILE_CUSTOM] =
+ AWCC_PROFILE_SPECIAL_CUSTOM;
+
+ __set_bit(PLATFORM_PROFILE_CUSTOM, choices);
+
return 0;
}
static const struct platform_profile_ops awcc_platform_profile_ops = {
- .probe = thermal_profile_probe,
- .profile_get = thermal_profile_get,
- .profile_set = thermal_profile_set,
+ .probe = awcc_platform_profile_probe,
+ .profile_get = awcc_platform_profile_get,
+ .profile_set = awcc_platform_profile_set,
};
static int awcc_platform_profile_init(struct wmi_device *wdev)
@@ -653,6 +1370,157 @@ static int awcc_platform_profile_init(struct wmi_device *wdev)
return PTR_ERR_OR_ZERO(priv->ppdev);
}
+/*
+ * DebugFS
+ */
+static int awcc_debugfs_system_description_read(struct seq_file *seq, void *data)
+{
+ struct device *dev = seq->private;
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+
+ seq_printf(seq, "0x%08x\n", priv->system_description);
+
+ return 0;
+}
+
+static int awcc_debugfs_hwmon_data_read(struct seq_file *seq, void *data)
+{
+ struct device *dev = seq->private;
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+ const struct awcc_fan_data *fan;
+ unsigned int bit;
+
+ seq_printf(seq, "Number of fans: %u\n", priv->fan_count);
+ seq_printf(seq, "Number of temperature sensors: %u\n\n", priv->temp_count);
+
+ for (u32 i = 0; i < priv->fan_count; i++) {
+ fan = priv->fan_data[i];
+
+ seq_printf(seq, "Fan %u:\n", i);
+ seq_printf(seq, " ID: 0x%02x\n", fan->id);
+ seq_printf(seq, " Related temperature sensors bitmap: %lu\n",
+ fan->auto_channels_temp);
+ }
+
+ seq_puts(seq, "\nTemperature sensor IDs:\n");
+ for_each_set_bit(bit, priv->temp_sensors, AWCC_ID_BITMAP_SIZE)
+ seq_printf(seq, " 0x%02x\n", bit);
+
+ return 0;
+}
+
+static int awcc_debugfs_pprof_data_read(struct seq_file *seq, void *data)
+{
+ struct device *dev = seq->private;
+ struct awcc_priv *priv = dev_get_drvdata(dev);
+
+ seq_printf(seq, "Number of thermal profiles: %u\n\n", priv->profile_count);
+
+ for (u32 i = 0; i < PLATFORM_PROFILE_LAST; i++) {
+ if (!priv->supported_profiles[i])
+ continue;
+
+ seq_printf(seq, "Platform profile %u:\n", i);
+ seq_printf(seq, " ID: 0x%02x\n", priv->supported_profiles[i]);
+ }
+
+ return 0;
+}
+
+static int awcc_gpio_pin_show(struct seq_file *seq, void *data)
+{
+ unsigned long pin = debugfs_get_aux_num(seq->file);
+ struct wmi_device *wdev = seq->private;
+ u32 status;
+ int ret;
+
+ ret = awcc_read_gpio_status(wdev, pin, &status);
+ if (ret)
+ return ret;
+
+ seq_printf(seq, "%u\n", status);
+
+ return 0;
+}
+
+static ssize_t awcc_gpio_pin_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long pin = debugfs_get_aux_num(file);
+ struct seq_file *seq = file->private_data;
+ struct wmi_device *wdev = seq->private;
+ bool status;
+ int ret;
+
+ if (!ppos || *ppos)
+ return -EINVAL;
+
+ ret = kstrtobool_from_user(buf, count, &status);
+ if (ret)
+ return ret;
+
+ ret = awcc_fwup_gpio_control(wdev, pin, status);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+DEFINE_SHOW_STORE_ATTRIBUTE(awcc_gpio_pin);
+
+static void awcc_debugfs_remove(void *data)
+{
+ struct dentry *root = data;
+
+ debugfs_remove(root);
+}
+
+static void awcc_debugfs_init(struct wmi_device *wdev)
+{
+ struct awcc_priv *priv = dev_get_drvdata(&wdev->dev);
+ struct dentry *root, *gpio_ctl;
+ u32 gpio_count;
+ char name[64];
+ int ret;
+
+ scnprintf(name, sizeof(name), "%s-%s", "alienware-wmi", dev_name(&wdev->dev));
+ root = debugfs_create_dir(name, NULL);
+
+ debugfs_create_devm_seqfile(&wdev->dev, "system_description", root,
+ awcc_debugfs_system_description_read);
+
+ if (awcc->hwmon)
+ debugfs_create_devm_seqfile(&wdev->dev, "hwmon_data", root,
+ awcc_debugfs_hwmon_data_read);
+
+ if (awcc->pprof)
+ debugfs_create_devm_seqfile(&wdev->dev, "pprof_data", root,
+ awcc_debugfs_pprof_data_read);
+
+ ret = awcc_read_total_gpios(wdev, &gpio_count);
+ if (ret) {
+ dev_dbg(&wdev->dev, "Failed to get total GPIO Pin count\n");
+ goto out_add_action;
+ } else if (gpio_count > AWCC_MAX_RES_COUNT) {
+ dev_dbg(&wdev->dev, "Reported GPIO Pin count may be incorrect: %u\n", gpio_count);
+ goto out_add_action;
+ }
+
+ gpio_ctl = debugfs_create_dir("gpio_ctl", root);
+
+ priv->gpio_count = gpio_count;
+ debugfs_create_u32("total_gpios", 0444, gpio_ctl, &priv->gpio_count);
+
+ for (unsigned int i = 0; i < gpio_count; i++) {
+ scnprintf(name, sizeof(name), "pin%u", i);
+ debugfs_create_file_aux_num(name, 0644, gpio_ctl, wdev, i,
+ &awcc_gpio_pin_fops);
+ }
+
+out_add_action:
+ devm_add_action_or_reset(&wdev->dev, awcc_debugfs_remove, root);
+}
+
static int alienware_awcc_setup(struct wmi_device *wdev)
{
struct awcc_priv *priv;
@@ -662,15 +1530,37 @@ static int alienware_awcc_setup(struct wmi_device *wdev)
if (!priv)
return -ENOMEM;
+ ret = awcc_thermal_information(wdev, AWCC_OP_GET_SYSTEM_DESCRIPTION,
+ 0, &priv->system_description);
+ if (ret < 0)
+ return ret;
+
+ /* Sanity check */
+ for (unsigned int i = 0; i < ARRAY_SIZE(priv->res_count); i++) {
+ if (priv->res_count[i] > AWCC_MAX_RES_COUNT) {
+ dev_err(&wdev->dev, "Malformed system description: 0x%08x\n",
+ priv->system_description);
+ return -ENXIO;
+ }
+ }
+
priv->wdev = wdev;
dev_set_drvdata(&wdev->dev, priv);
+ if (awcc->hwmon) {
+ ret = awcc_hwmon_init(wdev);
+ if (ret)
+ return ret;
+ }
+
if (awcc->pprof) {
ret = awcc_platform_profile_init(wdev);
if (ret)
return ret;
}
+ awcc_debugfs_init(wdev);
+
return 0;
}
@@ -721,6 +1611,24 @@ static int wmax_wmi_probe(struct wmi_device *wdev, const void *context)
return ret;
}
+static int wmax_wmi_suspend(struct device *dev)
+{
+ if (awcc && awcc->hwmon)
+ awcc_hwmon_suspend(dev);
+
+ return 0;
+}
+
+static int wmax_wmi_resume(struct device *dev)
+{
+ if (awcc && awcc->hwmon)
+ awcc_hwmon_resume(dev);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(wmax_wmi_pm_ops, wmax_wmi_suspend, wmax_wmi_resume);
+
static const struct wmi_device_id alienware_wmax_device_id_table[] = {
{ WMAX_CONTROL_GUID, NULL },
{ },
@@ -731,6 +1639,7 @@ static struct wmi_driver alienware_wmax_wmi_driver = {
.driver = {
.name = "alienware-wmi-wmax",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .pm = pm_sleep_ptr(&wmax_wmi_pm_ops),
},
.id_table = alienware_wmax_device_id_table,
.probe = wmax_wmi_probe,
@@ -745,6 +1654,13 @@ int __init alienware_wmax_wmi_init(void)
if (id)
awcc = id->driver_data;
+ if (force_hwmon) {
+ if (!awcc)
+ awcc = &empty_quirks;
+
+ awcc->hwmon = true;
+ }
+
if (force_platform_profile) {
if (!awcc)
awcc = &empty_quirks;
diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c
index 8149be25fa26..678f44252a45 100644
--- a/drivers/platform/x86/dell/dcdbas.c
+++ b/drivers/platform/x86/dell/dcdbas.c
@@ -662,7 +662,7 @@ static struct attribute *dcdbas_dev_attrs[] = {
static const struct attribute_group dcdbas_attr_group = {
.attrs = dcdbas_dev_attrs,
- .bin_attrs_new = dcdbas_bin_attrs,
+ .bin_attrs = dcdbas_bin_attrs,
};
static int dcdbas_probe(struct platform_device *dev)
diff --git a/drivers/platform/x86/dell/dell-lis3lv02d.c b/drivers/platform/x86/dell/dell-lis3lv02d.c
index efe26d667973..fe52bcd896f7 100644
--- a/drivers/platform/x86/dell/dell-lis3lv02d.c
+++ b/drivers/platform/x86/dell/dell-lis3lv02d.c
@@ -44,10 +44,14 @@ static const struct dmi_system_id lis3lv02d_devices[] __initconst = {
/*
* Additional individual entries were added after verification.
*/
+ DELL_LIS3LV02D_DMI_ENTRY("Latitude 5400", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude 5480", 0x29),
+ DELL_LIS3LV02D_DMI_ENTRY("Latitude 5500", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E6330", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Latitude E6430", 0x29),
+ DELL_LIS3LV02D_DMI_ENTRY("Latitude E6530", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Precision 3540", 0x29),
+ DELL_LIS3LV02D_DMI_ENTRY("Precision 3551", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Precision M6800", 0x29),
DELL_LIS3LV02D_DMI_ENTRY("Vostro V131", 0x1d),
DELL_LIS3LV02D_DMI_ENTRY("Vostro 5568", 0x29),
diff --git a/drivers/platform/x86/dell/dell-pc.c b/drivers/platform/x86/dell/dell-pc.c
index 483240bb36e7..becdd9aaef29 100644
--- a/drivers/platform/x86/dell/dell-pc.c
+++ b/drivers/platform/x86/dell/dell-pc.c
@@ -11,19 +11,20 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bitfield.h>
+#include <linux/bitops.h>
#include <linux/bits.h>
+#include <linux/device/faux.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_profile.h>
-#include <linux/platform_device.h>
#include <linux/slab.h>
#include "dell-smbios.h"
-static struct platform_device *platform_device;
+static struct faux_device *dell_pc_fdev;
static int supported_modes;
static const struct dmi_system_id dell_device_table[] __initconst = {
@@ -146,11 +147,6 @@ static int thermal_get_supported_modes(int *supported_bits)
dell_fill_request(&buffer, 0x0, 0, 0, 0);
ret = dell_send_request(&buffer, CLASS_INFO, SELECT_THERMAL_MANAGEMENT);
- /* Thermal function not supported */
- if (ret == -ENXIO) {
- *supported_bits = 0;
- return 0;
- }
if (ret)
return ret;
*supported_bits = FIELD_GET(DELL_THERMAL_SUPPORTED, buffer.output[1]);
@@ -232,14 +228,23 @@ static int thermal_platform_profile_get(struct device *dev,
static int thermal_platform_profile_probe(void *drvdata, unsigned long *choices)
{
+ int current_mode;
+
if (supported_modes & DELL_QUIET)
- set_bit(PLATFORM_PROFILE_QUIET, choices);
+ __set_bit(PLATFORM_PROFILE_QUIET, choices);
if (supported_modes & DELL_COOL_BOTTOM)
- set_bit(PLATFORM_PROFILE_COOL, choices);
+ __set_bit(PLATFORM_PROFILE_COOL, choices);
if (supported_modes & DELL_BALANCED)
- set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ __set_bit(PLATFORM_PROFILE_BALANCED, choices);
if (supported_modes & DELL_PERFORMANCE)
- set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+ __set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+
+ /* Make sure that ACPI is in sync with the profile set by USTT */
+ current_mode = thermal_get_mode();
+ if (current_mode < 0)
+ return current_mode;
+
+ thermal_set_mode(current_mode);
return 0;
}
@@ -250,68 +255,43 @@ static const struct platform_profile_ops dell_pc_platform_profile_ops = {
.profile_set = thermal_platform_profile_set,
};
-static int thermal_init(void)
+static int dell_pc_faux_probe(struct faux_device *fdev)
{
struct device *ppdev;
int ret;
- /* If thermal commands are not supported, exit without error */
if (!dell_smbios_class_is_supported(CLASS_INFO))
- return 0;
+ return -ENODEV;
- /* If thermal modes are not supported, exit without error */
ret = thermal_get_supported_modes(&supported_modes);
if (ret < 0)
return ret;
- if (!supported_modes)
- return 0;
-
- platform_device = platform_device_register_simple("dell-pc", PLATFORM_DEVID_NONE, NULL, 0);
- if (IS_ERR(platform_device))
- return PTR_ERR(platform_device);
-
- ppdev = devm_platform_profile_register(&platform_device->dev, "dell-pc",
- NULL, &dell_pc_platform_profile_ops);
- if (IS_ERR(ppdev)) {
- ret = PTR_ERR(ppdev);
- goto cleanup_platform_device;
- }
-
- return 0;
-cleanup_platform_device:
- platform_device_unregister(platform_device);
+ ppdev = devm_platform_profile_register(&fdev->dev, "dell-pc", NULL,
+ &dell_pc_platform_profile_ops);
- return ret;
+ return PTR_ERR_OR_ZERO(ppdev);
}
-static void thermal_cleanup(void)
-{
- platform_device_unregister(platform_device);
-}
+static const struct faux_device_ops dell_pc_faux_ops = {
+ .probe = dell_pc_faux_probe,
+};
static int __init dell_init(void)
{
- int ret;
-
if (!dmi_check_system(dell_device_table))
return -ENODEV;
- /* Do not fail module if thermal modes not supported, just skip */
- ret = thermal_init();
- if (ret)
- goto fail_thermal;
+ dell_pc_fdev = faux_device_create("dell-pc", NULL, &dell_pc_faux_ops);
+ if (!dell_pc_fdev)
+ return -ENODEV;
return 0;
-
-fail_thermal:
- thermal_cleanup();
- return ret;
}
static void __exit dell_exit(void)
{
- thermal_cleanup();
+ faux_device_destroy(dell_pc_fdev);
}
module_init(dell_init);
diff --git a/drivers/platform/x86/dell/dell-rbtn.c b/drivers/platform/x86/dell/dell-rbtn.c
index a415c432d4c3..180b8c6720e6 100644
--- a/drivers/platform/x86/dell/dell-rbtn.c
+++ b/drivers/platform/x86/dell/dell-rbtn.c
@@ -9,6 +9,7 @@
#include <linux/acpi.h>
#include <linux/rfkill.h>
#include <linux/input.h>
+#include <linux/platform_device.h>
#include "dell-rbtn.h"
@@ -109,9 +110,9 @@ static const struct rfkill_ops rbtn_ops = {
.set_block = rbtn_rfkill_set_block,
};
-static int rbtn_rfkill_init(struct acpi_device *device)
+static int rbtn_rfkill_init(struct device *dev)
{
- struct rbtn_data *rbtn_data = device->driver_data;
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
int ret;
if (rbtn_data->rfkill)
@@ -122,8 +123,8 @@ static int rbtn_rfkill_init(struct acpi_device *device)
* but rfkill interface does not support "ANY" type
* so "WLAN" type is used
*/
- rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev,
- RFKILL_TYPE_WLAN, &rbtn_ops, device);
+ rbtn_data->rfkill = rfkill_alloc("dell-rbtn", dev, RFKILL_TYPE_WLAN,
+ &rbtn_ops, ACPI_COMPANION(dev));
if (!rbtn_data->rfkill)
return -ENOMEM;
@@ -137,9 +138,9 @@ static int rbtn_rfkill_init(struct acpi_device *device)
return 0;
}
-static void rbtn_rfkill_exit(struct acpi_device *device)
+static void rbtn_rfkill_exit(struct device *dev)
{
- struct rbtn_data *rbtn_data = device->driver_data;
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
if (!rbtn_data->rfkill)
return;
@@ -149,12 +150,12 @@ static void rbtn_rfkill_exit(struct acpi_device *device)
rbtn_data->rfkill = NULL;
}
-static void rbtn_rfkill_event(struct acpi_device *device)
+static void rbtn_rfkill_event(struct device *dev)
{
- struct rbtn_data *rbtn_data = device->driver_data;
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
if (rbtn_data->rfkill)
- rbtn_rfkill_query(rbtn_data->rfkill, device);
+ rbtn_rfkill_query(rbtn_data->rfkill, ACPI_COMPANION(dev));
}
@@ -205,9 +206,9 @@ static void rbtn_input_event(struct rbtn_data *rbtn_data)
* acpi driver
*/
-static int rbtn_add(struct acpi_device *device);
-static void rbtn_remove(struct acpi_device *device);
-static void rbtn_notify(struct acpi_device *device, u32 event);
+static int rbtn_probe(struct platform_device *pdev);
+static void rbtn_remove(struct platform_device *pdev);
+static void rbtn_notify(acpi_handle handle, u32 event, void *data);
static const struct acpi_device_id rbtn_ids[] = {
{ "DELRBTN", 0 },
@@ -251,8 +252,7 @@ static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context)
static int rbtn_suspend(struct device *dev)
{
- struct acpi_device *device = to_acpi_device(dev);
- struct rbtn_data *rbtn_data = acpi_driver_data(device);
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
rbtn_data->suspended = true;
@@ -261,8 +261,7 @@ static int rbtn_suspend(struct device *dev)
static int rbtn_resume(struct device *dev)
{
- struct acpi_device *device = to_acpi_device(dev);
- struct rbtn_data *rbtn_data = acpi_driver_data(device);
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
acpi_status status;
/*
@@ -286,14 +285,13 @@ static int rbtn_resume(struct device *dev)
static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume);
-static struct acpi_driver rbtn_driver = {
- .name = "dell-rbtn",
- .ids = rbtn_ids,
- .drv.pm = &rbtn_pm_ops,
- .ops = {
- .add = rbtn_add,
- .remove = rbtn_remove,
- .notify = rbtn_notify,
+static struct platform_driver rbtn_driver = {
+ .probe = rbtn_probe,
+ .remove = rbtn_remove,
+ .driver = {
+ .name = "dell-rbtn",
+ .acpi_match_table = rbtn_ids,
+ .pm = &rbtn_pm_ops,
},
};
@@ -308,8 +306,7 @@ static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head);
static int rbtn_inc_count(struct device *dev, void *data)
{
- struct acpi_device *device = to_acpi_device(dev);
- struct rbtn_data *rbtn_data = device->driver_data;
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
int *count = data;
if (rbtn_data->type == RBTN_SLIDER)
@@ -320,17 +317,16 @@ static int rbtn_inc_count(struct device *dev, void *data)
static int rbtn_switch_dev(struct device *dev, void *data)
{
- struct acpi_device *device = to_acpi_device(dev);
- struct rbtn_data *rbtn_data = device->driver_data;
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
bool enable = data;
if (rbtn_data->type != RBTN_SLIDER)
return 0;
if (enable)
- rbtn_rfkill_init(device);
+ rbtn_rfkill_init(dev);
else
- rbtn_rfkill_exit(device);
+ rbtn_rfkill_exit(dev);
return 0;
}
@@ -342,7 +338,7 @@ int dell_rbtn_notifier_register(struct notifier_block *nb)
int ret;
count = 0;
- ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count,
+ ret = driver_for_each_device(&rbtn_driver.driver, NULL, &count,
rbtn_inc_count);
if (ret || count == 0)
return -ENODEV;
@@ -354,7 +350,7 @@ int dell_rbtn_notifier_register(struct notifier_block *nb)
return ret;
if (auto_remove_rfkill && first)
- ret = driver_for_each_device(&rbtn_driver.drv, NULL,
+ ret = driver_for_each_device(&rbtn_driver.driver, NULL,
(void *)false, rbtn_switch_dev);
return ret;
@@ -370,7 +366,7 @@ int dell_rbtn_notifier_unregister(struct notifier_block *nb)
return ret;
if (auto_remove_rfkill && !rbtn_chain_head.head)
- ret = driver_for_each_device(&rbtn_driver.drv, NULL,
+ ret = driver_for_each_device(&rbtn_driver.driver, NULL,
(void *)true, rbtn_switch_dev);
return ret;
@@ -382,30 +378,52 @@ EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister);
* acpi driver functions
*/
-static int rbtn_add(struct acpi_device *device)
+static void rbtn_cleanup(struct device *dev)
+{
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
+
+ switch (rbtn_data->type) {
+ case RBTN_TOGGLE:
+ rbtn_input_exit(rbtn_data);
+ break;
+ case RBTN_SLIDER:
+ rbtn_rfkill_exit(dev);
+ break;
+ default:
+ break;
+ }
+}
+
+static int rbtn_probe(struct platform_device *pdev)
{
struct rbtn_data *rbtn_data;
+ struct acpi_device *device;
enum rbtn_type type;
int ret = 0;
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
type = rbtn_check(device);
if (type == RBTN_UNKNOWN) {
- dev_info(&device->dev, "Unknown device type\n");
+ dev_info(&pdev->dev, "Unknown device type\n");
return -EINVAL;
}
- rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
+ rbtn_data = devm_kzalloc(&pdev->dev, sizeof(*rbtn_data), GFP_KERNEL);
if (!rbtn_data)
return -ENOMEM;
ret = rbtn_acquire(device, true);
if (ret < 0) {
- dev_err(&device->dev, "Cannot enable device\n");
+ dev_err(&pdev->dev, "Cannot enable device\n");
return ret;
}
+ platform_set_drvdata(pdev, rbtn_data);
+
rbtn_data->type = type;
- device->driver_data = rbtn_data;
switch (rbtn_data->type) {
case RBTN_TOGGLE:
@@ -415,51 +433,54 @@ static int rbtn_add(struct acpi_device *device)
if (auto_remove_rfkill && rbtn_chain_head.head)
ret = 0;
else
- ret = rbtn_rfkill_init(device);
+ ret = rbtn_rfkill_init(&pdev->dev);
break;
default:
ret = -EINVAL;
break;
}
if (ret)
- rbtn_acquire(device, false);
+ goto err;
+
+ ret = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ rbtn_notify, &pdev->dev);
+ if (ret)
+ goto err_cleanup;
+ return 0;
+
+err_cleanup:
+ rbtn_cleanup(&pdev->dev);
+err:
+ rbtn_acquire(device, false);
return ret;
}
-static void rbtn_remove(struct acpi_device *device)
+static void rbtn_remove(struct platform_device *pdev)
{
- struct rbtn_data *rbtn_data = device->driver_data;
-
- switch (rbtn_data->type) {
- case RBTN_TOGGLE:
- rbtn_input_exit(rbtn_data);
- break;
- case RBTN_SLIDER:
- rbtn_rfkill_exit(device);
- break;
- default:
- break;
- }
+ struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
+ acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, rbtn_notify);
+ rbtn_cleanup(&pdev->dev);
rbtn_acquire(device, false);
}
-static void rbtn_notify(struct acpi_device *device, u32 event)
+static void rbtn_notify(acpi_handle handle, u32 event, void *data)
{
- struct rbtn_data *rbtn_data = device->driver_data;
+ struct device *dev = data;
+ struct rbtn_data *rbtn_data = dev_get_drvdata(dev);
/*
* Some BIOSes send a notification at resume.
* Ignore it to prevent unwanted input events.
*/
if (rbtn_data->suspended) {
- dev_dbg(&device->dev, "ACPI notification ignored\n");
+ dev_dbg(dev, "ACPI notification ignored\n");
return;
}
if (event != 0x80) {
- dev_info(&device->dev, "Received unknown event (0x%x)\n",
+ dev_info(dev, "Received unknown event (0x%x)\n",
event);
return;
}
@@ -469,20 +490,15 @@ static void rbtn_notify(struct acpi_device *device, u32 event)
rbtn_input_event(rbtn_data);
break;
case RBTN_SLIDER:
- rbtn_rfkill_event(device);
- atomic_notifier_call_chain(&rbtn_chain_head, event, device);
+ rbtn_rfkill_event(dev);
+ atomic_notifier_call_chain(&rbtn_chain_head, event, NULL);
break;
default:
break;
}
}
-
-/*
- * module functions
- */
-
-module_acpi_driver(rbtn_driver);
+module_platform_driver(rbtn_driver);
module_param(auto_remove_rfkill, bool, 0444);
diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c
index 01c72b91a50d..4b7c54b76228 100644
--- a/drivers/platform/x86/dell/dell-smbios-base.c
+++ b/drivers/platform/x86/dell/dell-smbios-base.c
@@ -39,6 +39,7 @@ struct token_sysfs_data {
struct smbios_device {
struct list_head list;
struct device *device;
+ int priority;
int (*call_fn)(struct calling_interface_buffer *arg);
};
@@ -145,7 +146,7 @@ int dell_smbios_error(int value)
}
EXPORT_SYMBOL_GPL(dell_smbios_error);
-int dell_smbios_register_device(struct device *d, void *call_fn)
+int dell_smbios_register_device(struct device *d, int priority, void *call_fn)
{
struct smbios_device *priv;
@@ -154,6 +155,7 @@ int dell_smbios_register_device(struct device *d, void *call_fn)
return -ENOMEM;
get_device(d);
priv->device = d;
+ priv->priority = priority;
priv->call_fn = call_fn;
mutex_lock(&smbios_mutex);
list_add_tail(&priv->list, &smbios_device_list);
@@ -292,28 +294,25 @@ EXPORT_SYMBOL_GPL(dell_smbios_call_filter);
int dell_smbios_call(struct calling_interface_buffer *buffer)
{
- int (*call_fn)(struct calling_interface_buffer *) = NULL;
- struct device *selected_dev = NULL;
+ struct smbios_device *selected = NULL;
struct smbios_device *priv;
int ret;
mutex_lock(&smbios_mutex);
list_for_each_entry(priv, &smbios_device_list, list) {
- if (!selected_dev || priv->device->id >= selected_dev->id) {
- dev_dbg(priv->device, "Trying device ID: %d\n",
- priv->device->id);
- call_fn = priv->call_fn;
- selected_dev = priv->device;
+ if (!selected || priv->priority >= selected->priority) {
+ dev_dbg(priv->device, "Trying device ID: %d\n", priv->priority);
+ selected = priv;
}
}
- if (!selected_dev) {
+ if (!selected) {
ret = -ENODEV;
pr_err("No dell-smbios drivers are loaded\n");
goto out_smbios_call;
}
- ret = call_fn(buffer);
+ ret = selected->call_fn(buffer);
out_smbios_call:
mutex_unlock(&smbios_mutex);
@@ -496,12 +495,12 @@ static int build_tokens_sysfs(struct platform_device *dev)
int ret;
int i, j;
- token_entries = kcalloc(da_num_tokens, sizeof(*token_entries), GFP_KERNEL);
+ token_entries = kzalloc_objs(*token_entries, da_num_tokens);
if (!token_entries)
return -ENOMEM;
/* need to store both location and value + terminator*/
- token_attrs = kcalloc((2 * da_num_tokens) + 1, sizeof(*token_attrs), GFP_KERNEL);
+ token_attrs = kzalloc_objs(*token_attrs, (2 * da_num_tokens) + 1);
if (!token_attrs)
goto out_allocate_attrs;
diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c
index 4d375985c85f..7055e2c40f34 100644
--- a/drivers/platform/x86/dell/dell-smbios-smm.c
+++ b/drivers/platform/x86/dell/dell-smbios-smm.c
@@ -125,8 +125,7 @@ int init_dell_smbios_smm(void)
if (ret)
goto fail_platform_device_add;
- ret = dell_smbios_register_device(&platform_device->dev,
- &dell_smbios_smm_call);
+ ret = dell_smbios_register_device(&platform_device->dev, 0, &dell_smbios_smm_call);
if (ret)
goto fail_register;
diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c
index ae9012549560..a7dca8c59d60 100644
--- a/drivers/platform/x86/dell/dell-smbios-wmi.c
+++ b/drivers/platform/x86/dell/dell-smbios-wmi.c
@@ -264,9 +264,7 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context)
if (ret)
return ret;
- /* ID is used by dell-smbios to set priority of drivers */
- wdev->dev.id = 1;
- ret = dell_smbios_register_device(&wdev->dev, &dell_smbios_wmi_call);
+ ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call);
if (ret)
return ret;
diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h
index 77baa15eb523..f421b8533a9e 100644
--- a/drivers/platform/x86/dell/dell-smbios.h
+++ b/drivers/platform/x86/dell/dell-smbios.h
@@ -64,7 +64,7 @@ struct calling_interface_structure {
struct calling_interface_token tokens[];
} __packed;
-int dell_smbios_register_device(struct device *d, void *call_fn);
+int dell_smbios_register_device(struct device *d, int priority, void *call_fn);
void dell_smbios_unregister_device(struct device *d);
int dell_smbios_error(int value);
diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
index 8f868f845350..f323a667dc2d 100644
--- a/drivers/platform/x86/dell/dell-uart-backlight.c
+++ b/drivers/platform/x86/dell/dell-uart-backlight.c
@@ -305,7 +305,7 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
/* Initialize bl_power to a known value */
- ret = dell_uart_set_bl_power(dell_bl, FB_BLANK_UNBLANK);
+ ret = dell_uart_set_bl_power(dell_bl, BACKLIGHT_POWER_ON);
if (ret)
return ret;
diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c
index 841a5414d28a..2a5804efd3ea 100644
--- a/drivers/platform/x86/dell/dell-wmi-base.c
+++ b/drivers/platform/x86/dell/dell-wmi-base.c
@@ -80,6 +80,12 @@ static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = {
static const struct key_entry dell_wmi_keymap_type_0000[] = {
{ KE_IGNORE, 0x003a, { KEY_CAPSLOCK } },
+ /* Audio mute toggle */
+ { KE_KEY, 0x0109, { KEY_MUTE } },
+
+ /* Mic mute toggle */
+ { KE_KEY, 0x0150, { KEY_MICMUTE } },
+
/* Meta key lock */
{ KE_IGNORE, 0xe000, { KEY_RIGHTMETA } },
@@ -365,6 +371,13 @@ static const struct key_entry dell_wmi_keymap_type_0012[] = {
/* Backlight brightness change event */
{ KE_IGNORE, 0x0003, { KEY_RESERVED } },
+ /*
+ * Electronic privacy screen toggled, extended data gives state,
+ * separate entries for on/off see handling in dell_wmi_process_key().
+ */
+ { KE_KEY, 0x000c, { KEY_EPRIVACY_SCREEN_OFF } },
+ { KE_KEY, 0x000c, { KEY_EPRIVACY_SCREEN_ON } },
+
/* Ultra-performance mode switch request */
{ KE_IGNORE, 0x000d, { KEY_RESERVED } },
@@ -435,6 +448,11 @@ static int dell_wmi_process_key(struct wmi_device *wdev, int type, int code, u16
"Dell tablet mode switch",
SW_TABLET_MODE, !buffer[0]);
return 1;
+ } else if (type == 0x0012 && code == 0x000c && remaining > 0) {
+ /* Eprivacy toggle, switch to "on" key entry for on events */
+ if (buffer[0] == 2)
+ key++;
+ used = 1;
} else if (type == 0x0012 && code == 0x000d && remaining > 0) {
value = (buffer[2] == 2);
used = 1;
@@ -574,7 +592,7 @@ static void handle_dmi_entry(const struct dmi_header *dm, void *opaque)
return;
}
- keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL);
+ keymap = kzalloc_objs(struct key_entry, hotkey_num);
if (!keymap) {
results->err = -ENOMEM;
return;
@@ -644,13 +662,8 @@ static int dell_wmi_input_setup(struct wmi_device *wdev)
goto err_free_dev;
}
- keymap = kcalloc(dmi_results.keymap_size +
- ARRAY_SIZE(dell_wmi_keymap_type_0000) +
- ARRAY_SIZE(dell_wmi_keymap_type_0010) +
- ARRAY_SIZE(dell_wmi_keymap_type_0011) +
- ARRAY_SIZE(dell_wmi_keymap_type_0012) +
- 1,
- sizeof(struct key_entry), GFP_KERNEL);
+ keymap = kzalloc_objs(struct key_entry,
+ dmi_results.keymap_size + ARRAY_SIZE(dell_wmi_keymap_type_0000) + ARRAY_SIZE(dell_wmi_keymap_type_0010) + ARRAY_SIZE(dell_wmi_keymap_type_0011) + ARRAY_SIZE(dell_wmi_keymap_type_0012) + 1);
if (!keymap) {
kfree(dmi_results.keymap);
err = -ENOMEM;
@@ -761,7 +774,7 @@ static int dell_wmi_events_set_enabled(bool enable)
struct calling_interface_buffer *buffer;
int ret;
- buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL);
+ buffer = kzalloc_obj(struct calling_interface_buffer);
if (!buffer)
return -ENOMEM;
buffer->cmd_class = CLASS_INFO;
@@ -812,6 +825,7 @@ static struct wmi_driver dell_wmi_driver = {
.name = "dell-wmi",
},
.id_table = dell_wmi_id_table,
+ .min_event_size = sizeof(u16),
.probe = dell_wmi_probe,
.remove = dell_wmi_remove,
.notify = dell_wmi_notify,
diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c
index f27739da380f..62e3d060f038 100644
--- a/drivers/platform/x86/dell/dell-wmi-ddv.c
+++ b/drivers/platform/x86/dell/dell-wmi-ddv.c
@@ -8,6 +8,7 @@
#define pr_format(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/device/driver.h>
@@ -39,6 +40,33 @@
#define DELL_DDV_SUPPORTED_VERSION_MAX 3
#define DELL_DDV_GUID "8A42EA14-4F2A-FD45-6422-0087F7A7E608"
+/* Battery indices 1, 2 and 3 */
+#define DELL_DDV_NUM_BATTERIES 3
+
+#define SBS_MANUFACTURE_YEAR_MASK GENMASK(15, 9)
+#define SBS_MANUFACTURE_MONTH_MASK GENMASK(8, 5)
+#define SBS_MANUFACTURE_DAY_MASK GENMASK(4, 0)
+
+#define MA_FAILURE_MODE_MASK GENMASK(11, 8)
+#define MA_FAILURE_MODE_PERMANENT 0x9
+#define MA_FAILURE_MODE_OVERHEAT 0xA
+#define MA_FAILURE_MODE_OVERCURRENT 0xB
+
+#define MA_PERMANENT_FAILURE_CODE_MASK GENMASK(13, 12)
+#define MA_PERMANENT_FAILURE_FUSE_BLOWN 0x0
+#define MA_PERMANENT_FAILURE_CELL_IMBALANCE 0x1
+#define MA_PERMANENT_FAILURE_OVERVOLTAGE 0x2
+#define MA_PERMANENT_FAILURE_FET_FAILURE 0x3
+
+#define MA_OVERHEAT_FAILURE_CODE_MASK GENMASK(15, 12)
+#define MA_OVERHEAT_FAILURE_START 0x5
+#define MA_OVERHEAT_FAILURE_CHARGING 0x7
+#define MA_OVERHEAT_FAILURE_DISCHARGING 0x8
+
+#define MA_OVERCURRENT_FAILURE_CODE_MASK GENMASK(15, 12)
+#define MA_OVERCURRENT_FAILURE_CHARGING 0x6
+#define MA_OVERCURRENT_FAILURE_DISCHARGING 0xB
+
#define DELL_EPPID_LENGTH 20
#define DELL_EPPID_EXT_LENGTH 23
@@ -105,6 +133,8 @@ struct dell_wmi_ddv_sensors {
struct dell_wmi_ddv_data {
struct acpi_battery_hook hook;
struct device_attribute eppid_attr;
+ struct mutex translation_cache_lock; /* Protects the translation cache */
+ struct power_supply *translation_cache[DELL_DDV_NUM_BATTERIES];
struct dell_wmi_ddv_sensors fans;
struct dell_wmi_ddv_sensors temps;
struct wmi_device *wdev;
@@ -639,15 +669,82 @@ err_release:
return ret;
}
-static int dell_wmi_ddv_battery_index(struct acpi_device *acpi_dev, u32 *index)
+static int dell_wmi_ddv_battery_translate(struct dell_wmi_ddv_data *data,
+ struct power_supply *battery, u32 *index)
{
- const char *uid_str;
+ u32 serial_dec, serial_hex, serial;
+ union power_supply_propval val;
+ int ret;
+
+ guard(mutex)(&data->translation_cache_lock);
+
+ for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) {
+ if (data->translation_cache[i] == battery) {
+ dev_dbg(&data->wdev->dev, "Translation cache hit for battery index %u\n",
+ i + 1);
+ *index = i + 1;
+ return 0;
+ }
+ }
+
+ dev_dbg(&data->wdev->dev, "Translation cache miss\n");
+
+ /*
+ * Perform a translation between a ACPI battery and a battery index.
+ * We have to use power_supply_get_property_direct() here because this
+ * function will also get called from the callbacks of the power supply
+ * extension.
+ */
+ ret = power_supply_get_property_direct(battery, POWER_SUPPLY_PROP_SERIAL_NUMBER, &val);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Some devices display the serial number of the ACPI battery (string!) as a decimal
+ * number while other devices display it as a hexadecimal number. Because of this we
+ * have to check both cases.
+ */
+ ret = kstrtou32(val.strval, 16, &serial_hex);
+ if (ret < 0)
+ return ret; /* Should never fail */
+
+ ret = kstrtou32(val.strval, 10, &serial_dec);
+ if (ret < 0)
+ serial_dec = 0; /* Can fail, thus we only mark serial_dec as invalid */
+
+ for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) {
+ ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_SERIAL_NUMBER, i + 1,
+ &serial);
+ if (ret < 0)
+ return ret;
+
+ /* A serial number of 0 signals that this index is not associated with a battery */
+ if (!serial)
+ continue;
- uid_str = acpi_device_uid(acpi_dev);
- if (!uid_str)
- return -ENODEV;
+ if (serial == serial_dec || serial == serial_hex) {
+ dev_dbg(&data->wdev->dev, "Translation cache update for battery index %u\n",
+ i + 1);
+ data->translation_cache[i] = battery;
+ *index = i + 1;
+ return 0;
+ }
+ }
- return kstrtou32(uid_str, 10, index);
+ return -ENODEV;
+}
+
+static void dell_wmi_battery_invalidate(struct dell_wmi_ddv_data *data,
+ struct power_supply *battery)
+{
+ guard(mutex)(&data->translation_cache_lock);
+
+ for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) {
+ if (data->translation_cache[i] == battery) {
+ data->translation_cache[i] = NULL;
+ return;
+ }
+ }
}
static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, char *buf)
@@ -657,7 +754,7 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha
u32 index;
int ret;
- ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), &index);
+ ret = dell_wmi_ddv_battery_translate(data, to_power_supply(dev), &index);
if (ret < 0)
return ret;
@@ -676,6 +773,116 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha
return ret;
}
+static int dell_wmi_ddv_get_health(struct dell_wmi_ddv_data *data, u32 index,
+ union power_supply_propval *val)
+{
+ u32 value, code;
+ int ret;
+
+ ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_MANUFACTURER_ACCESS, index,
+ &value);
+ if (ret < 0)
+ return ret;
+
+ switch (FIELD_GET(MA_FAILURE_MODE_MASK, value)) {
+ case MA_FAILURE_MODE_PERMANENT:
+ code = FIELD_GET(MA_PERMANENT_FAILURE_CODE_MASK, value);
+ switch (code) {
+ case MA_PERMANENT_FAILURE_FUSE_BLOWN:
+ val->intval = POWER_SUPPLY_HEALTH_BLOWN_FUSE;
+ return 0;
+ case MA_PERMANENT_FAILURE_CELL_IMBALANCE:
+ val->intval = POWER_SUPPLY_HEALTH_CELL_IMBALANCE;
+ return 0;
+ case MA_PERMANENT_FAILURE_OVERVOLTAGE:
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ return 0;
+ case MA_PERMANENT_FAILURE_FET_FAILURE:
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ return 0;
+ default:
+ dev_notice_once(&data->wdev->dev, "Unknown permanent failure code %u\n",
+ code);
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+ case MA_FAILURE_MODE_OVERHEAT:
+ code = FIELD_GET(MA_OVERHEAT_FAILURE_CODE_MASK, value);
+ switch (code) {
+ case MA_OVERHEAT_FAILURE_START:
+ case MA_OVERHEAT_FAILURE_CHARGING:
+ case MA_OVERHEAT_FAILURE_DISCHARGING:
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ return 0;
+ default:
+ dev_notice_once(&data->wdev->dev, "Unknown overheat failure code %u\n",
+ code);
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+ case MA_FAILURE_MODE_OVERCURRENT:
+ code = FIELD_GET(MA_OVERCURRENT_FAILURE_CODE_MASK, value);
+ switch (code) {
+ case MA_OVERCURRENT_FAILURE_CHARGING:
+ case MA_OVERCURRENT_FAILURE_DISCHARGING:
+ val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT;
+ return 0;
+ default:
+ dev_notice_once(&data->wdev->dev, "Unknown overcurrent failure code %u\n",
+ code);
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+ default:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ return 0;
+ }
+}
+
+static int dell_wmi_ddv_get_manufacture_date(struct dell_wmi_ddv_data *data, u32 index,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ u16 year, month, day;
+ u32 value;
+ int ret;
+
+ ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_MANUFACTURE_DATE,
+ index, &value);
+ if (ret < 0)
+ return ret;
+ if (value > U16_MAX)
+ return -ENXIO;
+
+ /*
+ * Some devices report a invalid manufacture date value
+ * like 0.0.1980. Because of this we have to check the
+ * whole value before exposing parts of it to user space.
+ */
+ year = FIELD_GET(SBS_MANUFACTURE_YEAR_MASK, value) + 1980;
+ month = FIELD_GET(SBS_MANUFACTURE_MONTH_MASK, value);
+ if (month < 1 || month > 12)
+ return -ENODATA;
+
+ day = FIELD_GET(SBS_MANUFACTURE_DAY_MASK, value);
+ if (day < 1 || day > 31)
+ return -ENODATA;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
+ val->intval = year;
+ return 0;
+ case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
+ val->intval = month;
+ return 0;
+ case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
+ val->intval = day;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct power_supply_ext *ext,
void *drvdata, enum power_supply_property psp,
union power_supply_propval *val)
@@ -684,11 +891,13 @@ static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct powe
u32 index, value;
int ret;
- ret = dell_wmi_ddv_battery_index(to_acpi_device(psy->dev.parent), &index);
+ ret = dell_wmi_ddv_battery_translate(data, psy, &index);
if (ret < 0)
return ret;
switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ return dell_wmi_ddv_get_health(data, index, val);
case POWER_SUPPLY_PROP_TEMP:
ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_TEMPERATURE, index,
&value);
@@ -700,13 +909,21 @@ static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct powe
*/
val->intval = value - 2732;
return 0;
+ case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
+ case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
+ case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
+ return dell_wmi_ddv_get_manufacture_date(data, index, psp, val);
default:
return -EINVAL;
}
}
static const enum power_supply_property dell_wmi_ddv_properties[] = {
+ POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
+ POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
+ POWER_SUPPLY_PROP_MANUFACTURE_DAY,
};
static const struct power_supply_ext dell_wmi_ddv_extension = {
@@ -719,13 +936,12 @@ static const struct power_supply_ext dell_wmi_ddv_extension = {
static int dell_wmi_ddv_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
{
struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook);
- u32 index;
int ret;
- /* Return 0 instead of error to avoid being unloaded */
- ret = dell_wmi_ddv_battery_index(to_acpi_device(battery->dev.parent), &index);
- if (ret < 0)
- return 0;
+ /*
+ * We cannot do the battery matching here since the battery might be absent, preventing
+ * us from reading the serial number.
+ */
ret = device_create_file(&battery->dev, &data->eppid_attr);
if (ret < 0)
@@ -749,11 +965,19 @@ static int dell_wmi_ddv_remove_battery(struct power_supply *battery, struct acpi
device_remove_file(&battery->dev, &data->eppid_attr);
power_supply_unregister_extension(battery, &dell_wmi_ddv_extension);
+ dell_wmi_battery_invalidate(data, battery);
+
return 0;
}
static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data)
{
+ int ret;
+
+ ret = devm_mutex_init(&data->wdev->dev, &data->translation_cache_lock);
+ if (ret < 0)
+ return ret;
+
data->hook.name = "Dell DDV Battery Extension";
data->hook.add_battery = dell_wmi_ddv_add_battery;
data->hook.remove_battery = dell_wmi_ddv_remove_battery;
diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c
index 4b65e1655d42..ed099a431ea4 100644
--- a/drivers/platform/x86/dell/dell-wmi-privacy.c
+++ b/drivers/platform/x86/dell/dell-wmi-privacy.c
@@ -314,8 +314,8 @@ static int dell_privacy_wmi_probe(struct wmi_device *wdev, const void *context)
return -ENOMEM;
/* remap the wmi keymap event to new keymap */
- keymap = kcalloc(ARRAY_SIZE(dell_wmi_keymap_type_0012),
- sizeof(struct key_entry), GFP_KERNEL);
+ keymap = kzalloc_objs(struct key_entry,
+ ARRAY_SIZE(dell_wmi_keymap_type_0012));
if (!keymap)
return -ENOMEM;
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h
index 3ad33a094588..5278a93fdaf7 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h
@@ -89,6 +89,11 @@ extern struct wmi_sysman_priv wmi_priv;
enum { ENUM, INT, STR, PO };
+#define ENUM_MIN_ELEMENTS 8
+#define INT_MIN_ELEMENTS 9
+#define STR_MIN_ELEMENTS 8
+#define PO_MIN_ELEMENTS 4
+
enum {
ATTR_NAME,
DISPL_NAME_LANG_CODE,
@@ -184,8 +189,8 @@ void exit_bios_attr_set_interface(void);
int init_bios_attr_set_interface(void);
int map_wmi_error(int error_code);
size_t calculate_string_buffer(const char *str);
-size_t calculate_security_buffer(char *authentication);
-void populate_security_buffer(char *buffer, char *authentication);
+size_t calculate_security_buffer(const char *authentication);
+void populate_security_buffer(char *buffer, const char *authentication);
ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str);
int set_new_password(const char *password_type, const char *new);
int init_bios_attr_pass_interface(void);
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c
index 8cc212c85266..a85639d8a076 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c
@@ -6,10 +6,32 @@
* Copyright (c) 2020 Dell Inc.
*/
+#include <linux/bug.h>
+
#include "dell-wmi-sysman.h"
get_instance_id(enumeration);
+static int append_enum_string(char *dest, const char *src)
+{
+ size_t dest_len = strlen(dest);
+ ssize_t copied;
+
+ if (WARN_ON_ONCE(dest_len >= MAX_BUFF))
+ return -EINVAL;
+
+ copied = strscpy(dest + dest_len, src, MAX_BUFF - dest_len);
+ if (copied < 0)
+ return -EINVAL;
+
+ dest_len += copied;
+ copied = strscpy(dest + dest_len, ";", MAX_BUFF - dest_len);
+ if (copied < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
int instance_id = get_enumeration_instance_id(kobj);
@@ -23,9 +45,10 @@ static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *a
obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);
if (!obj)
return -EIO;
- if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) {
+ if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count < ENUM_MIN_ELEMENTS ||
+ obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) {
kfree(obj);
- return -EINVAL;
+ return -EIO;
}
ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer);
kfree(obj);
@@ -118,8 +141,8 @@ int alloc_enum_data(void)
wmi_priv.enumeration_instances_count =
get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID);
- wmi_priv.enumeration_data = kcalloc(wmi_priv.enumeration_instances_count,
- sizeof(struct enumeration_data), GFP_KERNEL);
+ wmi_priv.enumeration_data = kzalloc_objs(struct enumeration_data,
+ wmi_priv.enumeration_instances_count);
if (!wmi_priv.enumeration_data) {
wmi_priv.enumeration_instances_count = 0;
ret = -ENOMEM;
@@ -175,9 +198,9 @@ int populate_enum_data(union acpi_object *enumeration_obj, int instance_id,
return -EINVAL;
if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING))
return -EINVAL;
- strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier,
- enumeration_obj[next_obj++].string.pointer);
- strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";");
+ if (append_enum_string(wmi_priv.enumeration_data[instance_id].dell_value_modifier,
+ enumeration_obj[next_obj++].string.pointer))
+ return -EINVAL;
}
if (next_obj >= enum_property_count)
@@ -192,9 +215,9 @@ int populate_enum_data(union acpi_object *enumeration_obj, int instance_id,
return -EINVAL;
if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING))
return -EINVAL;
- strcat(wmi_priv.enumeration_data[instance_id].possible_values,
- enumeration_obj[next_obj++].string.pointer);
- strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";");
+ if (append_enum_string(wmi_priv.enumeration_data[instance_id].possible_values,
+ enumeration_obj[next_obj++].string.pointer))
+ return -EINVAL;
}
return sysfs_create_group(attr_name_kobj, &enumeration_attr_group);
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c
index 951e75b538fa..ab5c24722475 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c
@@ -25,9 +25,10 @@ static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *a
obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);
if (!obj)
return -EIO;
- if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) {
+ if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count < INT_MIN_ELEMENTS ||
+ obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) {
kfree(obj);
- return -EINVAL;
+ return -EIO;
}
ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value);
kfree(obj);
@@ -122,8 +123,8 @@ int alloc_int_data(void)
int ret = 0;
wmi_priv.integer_instances_count = get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID);
- wmi_priv.integer_data = kcalloc(wmi_priv.integer_instances_count,
- sizeof(struct integer_data), GFP_KERNEL);
+ wmi_priv.integer_data = kzalloc_objs(struct integer_data,
+ wmi_priv.integer_instances_count);
if (!wmi_priv.integer_data) {
wmi_priv.integer_instances_count = 0;
ret = -ENOMEM;
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c
index 230e6ee96636..be939b731063 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c
@@ -26,9 +26,10 @@ static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr
obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
if (!obj)
return -EIO;
- if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) {
+ if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count < PO_MIN_ELEMENTS ||
+ obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) {
kfree(obj);
- return -EINVAL;
+ return -EIO;
}
ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value);
kfree(obj);
@@ -45,7 +46,7 @@ static ssize_t current_password_store(struct kobject *kobj,
int length;
length = strlen(buf);
- if (buf[length-1] == '\n')
+ if (length && buf[length - 1] == '\n')
length--;
/* firmware does verifiation of min/max password length,
@@ -142,7 +143,8 @@ int alloc_po_data(void)
int ret = 0;
wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID);
- wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL);
+ wmi_priv.po_data = kzalloc_objs(struct po_data,
+ wmi_priv.po_instances_count);
if (!wmi_priv.po_data) {
wmi_priv.po_instances_count = 0;
ret = -ENOMEM;
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c
index 86ec962aace9..e586f7957946 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c
@@ -93,7 +93,6 @@ int set_new_password(const char *password_type, const char *new)
if (ret < 0)
goto out;
- print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size);
/* on success copy the new password to current password */
if (!ret)
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c
index c392f0ecf8b5..41e19f0391b2 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c
@@ -25,9 +25,10 @@ static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *a
obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);
if (!obj)
return -EIO;
- if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) {
+ if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count < STR_MIN_ELEMENTS ||
+ obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) {
kfree(obj);
- return -EINVAL;
+ return -EIO;
}
ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer);
kfree(obj);
@@ -107,8 +108,8 @@ int alloc_str_data(void)
int ret = 0;
wmi_priv.str_instances_count = get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID);
- wmi_priv.str_data = kcalloc(wmi_priv.str_instances_count,
- sizeof(struct str_data), GFP_KERNEL);
+ wmi_priv.str_data = kzalloc_objs(struct str_data,
+ wmi_priv.str_instances_count);
if (!wmi_priv.str_data) {
wmi_priv.str_instances_count = 0;
ret = -ENOMEM;
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
index d00389b860e4..51d25fdc1389 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
@@ -7,10 +7,13 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/align.h>
#include <linux/fs.h>
#include <linux/dmi.h>
#include <linux/module.h>
#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
#include <linux/wmi.h>
#include "dell-wmi-sysman.h"
#include "../../firmware_attributes_class.h"
@@ -72,13 +75,9 @@ size_t calculate_string_buffer(const char *str)
*
* Currently only supported type is Admin password
*/
-size_t calculate_security_buffer(char *authentication)
+size_t calculate_security_buffer(const char *authentication)
{
- if (strlen(authentication) > 0) {
- return (sizeof(u32) * 2) + strlen(authentication) +
- strlen(authentication) % 2;
- }
- return sizeof(u32) * 2;
+ return sizeof(u32) * 2 + ALIGN(strlen(authentication), 2);
}
/**
@@ -88,18 +87,18 @@ size_t calculate_security_buffer(char *authentication)
*
* Currently only supported type is PLAIN TEXT
*/
-void populate_security_buffer(char *buffer, char *authentication)
+void populate_security_buffer(char *buffer, const char *authentication)
{
+ size_t seclen = strlen(authentication);
char *auth = buffer + sizeof(u32) * 2;
u32 *sectype = (u32 *) buffer;
- u32 *seclen = sectype + 1;
+ u32 *seclenp = sectype + 1;
- *sectype = strlen(authentication) > 0 ? 1 : 0;
- *seclen = strlen(authentication);
+ *sectype = !!seclen;
+ *seclenp = seclen;
/* plain text */
- if (strlen(authentication) > 0)
- memcpy(auth, authentication, *seclen);
+ memcpy(auth, authentication, seclen);
}
/**
@@ -143,17 +142,17 @@ int map_wmi_error(int error_code)
*/
static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
- char *start = buf;
+ ssize_t len = 0;
int i;
for (i = 0; i < MAX_TYPES; i++) {
if (i == reset_option)
- buf += sprintf(buf, "[%s] ", reset_types[i]);
+ len += sysfs_emit_at(buf, len, "[%s] ", reset_types[i]);
else
- buf += sprintf(buf, "%s ", reset_types[i]);
+ len += sysfs_emit_at(buf, len, "%s ", reset_types[i]);
}
- buf += sprintf(buf, "\n");
- return buf-start;
+ len += sysfs_emit_at(buf, len, "\n");
+ return len;
}
/**
@@ -194,7 +193,7 @@ static ssize_t reset_bios_store(struct kobject *kobj,
static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
- return sprintf(buf, "%d\n", wmi_priv.pending_changes);
+ return sysfs_emit(buf, "%d\n", wmi_priv.pending_changes);
}
static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios);
@@ -220,35 +219,6 @@ static int create_attributes_level_sysfs_files(void)
return 0;
}
-static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr,
- char *buf)
-{
- struct kobj_attribute *kattr;
- ssize_t ret = -EIO;
-
- kattr = container_of(attr, struct kobj_attribute, attr);
- if (kattr->show)
- ret = kattr->show(kobj, kattr, buf);
- return ret;
-}
-
-static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr,
- const char *buf, size_t count)
-{
- struct kobj_attribute *kattr;
- ssize_t ret = -EIO;
-
- kattr = container_of(attr, struct kobj_attribute, attr);
- if (kattr->store)
- ret = kattr->store(kobj, kattr, buf, count);
- return ret;
-}
-
-static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = {
- .show = wmi_sysman_attr_show,
- .store = wmi_sysman_attr_store,
-};
-
static void attr_name_release(struct kobject *kobj)
{
kfree(kobj);
@@ -256,7 +226,7 @@ static void attr_name_release(struct kobject *kobj)
static const struct kobj_type attr_name_ktype = {
.release = attr_name_release,
- .sysfs_ops = &wmi_sysman_kobj_sysfs_ops,
+ .sysfs_ops = &kobj_sysfs_ops,
};
/**
@@ -343,7 +313,7 @@ static int alloc_attributes_data(int attr_type)
* destroy_attribute_objs() - Free a kset of kobjects
* @kset: The kset to destroy
*
- * Fress kobjects created for each attribute_name under attribute type kset
+ * Frees kobjects created for each attribute_name under attribute type kset.
*/
static void destroy_attribute_objs(struct kset *kset)
{
@@ -407,10 +377,10 @@ static int init_bios_attributes(int attr_type, const char *guid)
return retval;
switch (attr_type) {
- case ENUM: min_elements = 8; break;
- case INT: min_elements = 9; break;
- case STR: min_elements = 8; break;
- case PO: min_elements = 4; break;
+ case ENUM: min_elements = ENUM_MIN_ELEMENTS; break;
+ case INT: min_elements = INT_MIN_ELEMENTS; break;
+ case STR: min_elements = STR_MIN_ELEMENTS; break;
+ case PO: min_elements = PO_MIN_ELEMENTS; break;
default:
pr_err("Error: Unknown attr_type: %d\n", attr_type);
return -EINVAL;
@@ -460,7 +430,7 @@ static int init_bios_attributes(int attr_type, const char *guid)
}
/* build attribute */
- attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ attr_name_kobj = kzalloc_obj(*attr_name_kobj);
if (!attr_name_kobj) {
retval = -ENOMEM;
goto err_attr_init;
@@ -597,7 +567,7 @@ err_release_attributes_data:
release_attributes_data();
err_destroy_classdev:
- device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ device_unregister(wmi_priv.class_dev);
err_exit_bios_attr_pass_interface:
exit_bios_attr_pass_interface();
@@ -611,7 +581,7 @@ err_exit_bios_attr_set_interface:
static void __exit sysman_exit(void)
{
release_attributes_data();
- device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ device_unregister(wmi_priv.class_dev);
exit_bios_attr_set_interface();
exit_bios_attr_pass_interface();
}
diff --git a/drivers/platform/x86/dell/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c
index e30ca325938c..3fa9de9aa47b 100644
--- a/drivers/platform/x86/dell/dell_rbu.c
+++ b/drivers/platform/x86/dell/dell_rbu.c
@@ -30,6 +30,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
+#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
@@ -45,7 +46,7 @@
MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
MODULE_LICENSE("GPL");
-MODULE_VERSION("3.2");
+MODULE_VERSION("3.3");
#define BIOS_SCAN_LIMIT 0xffffffff
#define MAX_IMAGE_LENGTH 16
@@ -77,21 +78,21 @@ struct packet_data {
int ordernum;
};
-static struct packet_data packet_data_head;
+static struct list_head packet_data_list;
static struct platform_device *rbu_device;
static int context;
static void init_packet_head(void)
{
- INIT_LIST_HEAD(&packet_data_head.list);
+ INIT_LIST_HEAD(&packet_data_list);
rbu_data.packet_read_count = 0;
rbu_data.num_packets = 0;
rbu_data.packetsize = 0;
rbu_data.imagesize = 0;
}
-static int create_packet(void *data, size_t length)
+static int create_packet(void *data, size_t length) __must_hold(&rbu_data.lock)
{
struct packet_data *newpacket;
int ordernum = 0;
@@ -111,7 +112,7 @@ static int create_packet(void *data, size_t length)
spin_unlock(&rbu_data.lock);
- newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL);
+ newpacket = kzalloc_obj(struct packet_data);
if (!newpacket) {
pr_warn("failed to allocate new packet\n");
@@ -183,7 +184,7 @@ static int create_packet(void *data, size_t length)
/* initialize the newly created packet headers */
INIT_LIST_HEAD(&newpacket->list);
- list_add_tail(&newpacket->list, &packet_data_head.list);
+ list_add_tail(&newpacket->list, &packet_data_list);
memcpy(newpacket->data, data, length);
@@ -232,7 +233,8 @@ static int packetize_data(const u8 *data, size_t length)
done = 1;
}
- if ((rc = create_packet(temp, packet_length)))
+ rc = create_packet(temp, packet_length);
+ if (rc)
return rc;
pr_debug("%p:%td\n", temp, (end - temp));
@@ -276,7 +278,7 @@ static int do_packet_read(char *data, struct packet_data *newpacket,
return bytes_copied;
}
-static int packet_read_list(char *data, size_t * pread_length)
+static int packet_read_list(char *data, size_t *pread_length)
{
struct packet_data *newpacket;
int temp_count = 0;
@@ -292,7 +294,7 @@ static int packet_read_list(char *data, size_t * pread_length)
remaining_bytes = *pread_length;
bytes_read = rbu_data.packet_read_count;
- list_for_each_entry(newpacket, (&packet_data_head.list)->next, list) {
+ list_for_each_entry(newpacket, &packet_data_list, list) {
bytes_copied = do_packet_read(pdest, newpacket,
remaining_bytes, bytes_read, &temp_count);
remaining_bytes -= bytes_copied;
@@ -315,14 +317,14 @@ static void packet_empty_list(void)
{
struct packet_data *newpacket, *tmp;
- list_for_each_entry_safe(newpacket, tmp, (&packet_data_head.list)->next, list) {
+ list_for_each_entry_safe(newpacket, tmp, &packet_data_list, list) {
list_del(&newpacket->list);
/*
* zero out the RBU packet memory before freeing
* to make sure there are no stale RBU packets left in memory
*/
- memset(newpacket->data, 0, rbu_data.packetsize);
+ memset(newpacket->data, 0, newpacket->length);
set_memory_wb((unsigned long)newpacket->data,
1 << newpacket->ordernum);
free_pages((unsigned long) newpacket->data,
@@ -445,7 +447,8 @@ static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count)
bytes_left = rbu_data.imagesize - pos;
data_length = min(bytes_left, count);
- if ((retval = packet_read_list(ptempBuf, &data_length)) < 0)
+ retval = packet_read_list(ptempBuf, &data_length);
+ if (retval < 0)
goto read_rbu_data_exit;
if ((pos + count) > rbu_data.imagesize) {
@@ -617,9 +620,12 @@ static ssize_t packet_size_write(struct file *filp, struct kobject *kobj,
char *buffer, loff_t pos, size_t count)
{
unsigned long temp;
+
+ if (kstrtoul(buffer, 10, &temp))
+ return -EINVAL;
+
spin_lock(&rbu_data.lock);
packet_empty_list();
- sscanf(buffer, "%lu", &temp);
if (temp < 0xffffffff)
rbu_data.packetsize = temp;
@@ -636,7 +642,7 @@ static const struct bin_attribute *const rbu_bin_attrs[] = {
};
static const struct attribute_group rbu_group = {
- .bin_attrs_new = rbu_bin_attrs,
+ .bin_attrs = rbu_bin_attrs,
};
static int __init dcdrbu_init(void)
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
index f52fbc4924d4..d18a80907611 100644
--- a/drivers/platform/x86/eeepc-laptop.c
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -1204,9 +1204,10 @@ static void eeepc_input_notify(struct eeepc_laptop *eeepc, int event)
pr_info("Unknown key %x pressed\n", event);
}
-static void eeepc_acpi_notify(struct acpi_device *device, u32 event)
+static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data)
{
- struct eeepc_laptop *eeepc = acpi_driver_data(device);
+ struct eeepc_laptop *eeepc = data;
+ struct acpi_device *device = eeepc->device;
int old_brightness, new_brightness;
u16 count;
@@ -1360,21 +1361,27 @@ static void eeepc_enable_camera(struct eeepc_laptop *eeepc)
static bool eeepc_device_present;
-static int eeepc_acpi_add(struct acpi_device *device)
+static int eeepc_acpi_probe(struct platform_device *pdev)
{
+ struct acpi_device *device;
struct eeepc_laptop *eeepc;
int result;
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
pr_notice(EEEPC_LAPTOP_NAME "\n");
- eeepc = kzalloc(sizeof(struct eeepc_laptop), GFP_KERNEL);
+ eeepc = kzalloc_obj(struct eeepc_laptop);
if (!eeepc)
return -ENOMEM;
eeepc->handle = device->handle;
- strcpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
- strcpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
- device->driver_data = eeepc;
+ strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
+ strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
eeepc->device = device;
+ platform_set_drvdata(pdev, eeepc);
+
eeepc->hotplug_disabled = hotplug_disabled;
eeepc_dmi_check(eeepc);
@@ -1422,9 +1429,16 @@ static int eeepc_acpi_add(struct acpi_device *device)
if (result)
goto fail_rfkill;
+ result = acpi_dev_install_notify_handler(device, ACPI_ALL_NOTIFY,
+ eeepc_acpi_notify, eeepc);
+ if (result)
+ goto fail_acpi_notifier;
+
eeepc_device_present = true;
return 0;
+fail_acpi_notifier:
+ eeepc_rfkill_exit(eeepc);
fail_rfkill:
eeepc_led_exit(eeepc);
fail_led:
@@ -1440,10 +1454,12 @@ fail_platform:
return result;
}
-static void eeepc_acpi_remove(struct acpi_device *device)
+static void eeepc_acpi_remove(struct platform_device *pdev)
{
- struct eeepc_laptop *eeepc = acpi_driver_data(device);
+ struct eeepc_laptop *eeepc = platform_get_drvdata(pdev);
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
+ ACPI_ALL_NOTIFY, eeepc_acpi_notify);
eeepc_backlight_exit(eeepc);
eeepc_rfkill_exit(eeepc);
eeepc_input_exit(eeepc);
@@ -1460,15 +1476,12 @@ static const struct acpi_device_id eeepc_device_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, eeepc_device_ids);
-static struct acpi_driver eeepc_acpi_driver = {
- .name = EEEPC_LAPTOP_NAME,
- .class = EEEPC_ACPI_CLASS,
- .ids = eeepc_device_ids,
- .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
- .ops = {
- .add = eeepc_acpi_add,
- .remove = eeepc_acpi_remove,
- .notify = eeepc_acpi_notify,
+static struct platform_driver eeepc_acpi_driver = {
+ .probe = eeepc_acpi_probe,
+ .remove = eeepc_acpi_remove,
+ .driver = {
+ .name = EEEPC_LAPTOP_NAME,
+ .acpi_match_table = eeepc_device_ids,
},
};
@@ -1481,7 +1494,7 @@ static int __init eeepc_laptop_init(void)
if (result < 0)
return result;
- result = acpi_bus_register_driver(&eeepc_acpi_driver);
+ result = platform_driver_register(&eeepc_acpi_driver);
if (result < 0)
goto fail_acpi_driver;
@@ -1493,7 +1506,7 @@ static int __init eeepc_laptop_init(void)
return 0;
fail_no_device:
- acpi_bus_unregister_driver(&eeepc_acpi_driver);
+ platform_driver_unregister(&eeepc_acpi_driver);
fail_acpi_driver:
platform_driver_unregister(&platform_driver);
return result;
@@ -1501,7 +1514,7 @@ fail_acpi_driver:
static void __exit eeepc_laptop_exit(void)
{
- acpi_bus_unregister_driver(&eeepc_acpi_driver);
+ platform_driver_unregister(&eeepc_acpi_driver);
platform_driver_unregister(&platform_driver);
}
diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c
index a0eae24ca9e6..54d0b9cec4d3 100644
--- a/drivers/platform/x86/fujitsu-laptop.c
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@ -17,13 +17,13 @@
/*
* fujitsu-laptop.c - Fujitsu laptop support, providing access to additional
* features made available on a range of Fujitsu laptops including the
- * P2xxx/P5xxx/S6xxx/S7xxx series.
+ * P2xxx/P5xxx/S2xxx/S6xxx/S7xxx series.
*
* This driver implements a vendor-specific backlight control interface for
* Fujitsu laptops and provides support for hotkeys present on certain Fujitsu
* laptops.
*
- * This driver has been tested on a Fujitsu Lifebook S6410, S7020 and
+ * This driver has been tested on a Fujitsu Lifebook S2110, S6410, S7020 and
* P8010. It should work on most P-series and S-series Lifebooks, but
* YMMV.
*
@@ -107,7 +107,11 @@
#define KEY2_CODE 0x411
#define KEY3_CODE 0x412
#define KEY4_CODE 0x413
-#define KEY5_CODE 0x420
+#define KEY5_CODE 0x414
+#define KEY6_CODE 0x415
+#define KEY7_CODE 0x416
+#define KEY8_CODE 0x417
+#define KEY9_CODE 0x420
/* Hotkey ringbuffer limits */
#define MAX_HOTKEY_RINGBUFFER_SIZE 100
@@ -140,11 +144,11 @@ struct fujitsu_laptop {
bool charge_control_supported;
};
-static struct acpi_device *fext;
+static struct device *fext;
/* Fujitsu ACPI interface function */
-static int call_fext_func(struct acpi_device *device,
+static int call_fext_func(struct device *dev,
int func, int op, int feature, int state)
{
union acpi_object params[4] = {
@@ -154,18 +158,17 @@ static int call_fext_func(struct acpi_device *device,
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = state }
};
struct acpi_object_list arg_list = { 4, params };
+ acpi_handle handle = ACPI_HANDLE(dev);
unsigned long long value;
acpi_status status;
- status = acpi_evaluate_integer(device->handle, "FUNC", &arg_list,
- &value);
+ status = acpi_evaluate_integer(handle, "FUNC", &arg_list, &value);
if (ACPI_FAILURE(status)) {
- acpi_handle_err(device->handle, "Failed to evaluate FUNC\n");
+ acpi_handle_err(handle, "Failed to evaluate FUNC\n");
return -ENODEV;
}
- acpi_handle_debug(device->handle,
- "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
+ acpi_handle_debug(handle, "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
func, op, feature, state, (int)value);
return value;
}
@@ -176,15 +179,19 @@ static ssize_t charge_control_end_threshold_store(struct device *dev,
const char *buf, size_t count)
{
int cc_end_value, s006_cc_return;
- int value, ret;
+ unsigned int value;
+ int ret;
ret = kstrtouint(buf, 10, &value);
if (ret)
return ret;
- if (value < 50 || value > 100)
+ if (value > 100)
return -EINVAL;
+ if (value < 50)
+ value = 50;
+
cc_end_value = value * 0x100 + 0x20;
s006_cc_return = call_fext_func(fext, FUNC_S006_METHOD,
CHARGE_CONTROL_RW, cc_end_value, 0x0);
@@ -243,9 +250,9 @@ static struct acpi_battery_hook battery_hook = {
* These functions are intended to be called from acpi_fujitsu_laptop_add and
* acpi_fujitsu_laptop_remove.
*/
-static int fujitsu_battery_charge_control_add(struct acpi_device *device)
+static int fujitsu_battery_charge_control_add(struct device *dev)
{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
int s006_cc_return;
priv->charge_control_supported = false;
@@ -266,9 +273,9 @@ static int fujitsu_battery_charge_control_add(struct acpi_device *device)
return 0;
}
-static void fujitsu_battery_charge_control_remove(struct acpi_device *device)
+static void fujitsu_battery_charge_control_remove(struct device *dev)
{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
if (priv->charge_control_supported)
battery_hook_unregister(&battery_hook);
@@ -276,15 +283,16 @@ static void fujitsu_battery_charge_control_remove(struct acpi_device *device)
/* Hardware access for LCD brightness control */
-static int set_lcd_level(struct acpi_device *device, int level)
+static int set_lcd_level(struct device *dev, int level)
{
- struct fujitsu_bl *priv = acpi_driver_data(device);
+ struct fujitsu_bl *priv = dev_get_drvdata(dev);
+ acpi_handle handle = ACPI_HANDLE(dev);
acpi_status status;
char *method;
switch (use_alt_lcd_levels) {
case -1:
- if (acpi_has_method(device->handle, "SBL2"))
+ if (acpi_has_method(handle, "SBL2"))
method = "SBL2";
else
method = "SBLL";
@@ -297,16 +305,14 @@ static int set_lcd_level(struct acpi_device *device, int level)
break;
}
- acpi_handle_debug(device->handle, "set lcd level via %s [%d]\n", method,
- level);
+ acpi_handle_debug(handle, "set lcd level via %s [%d]\n", method, level);
if (level < 0 || level >= priv->max_brightness)
return -EINVAL;
- status = acpi_execute_simple_method(device->handle, method, level);
+ status = acpi_execute_simple_method(handle, method, level);
if (ACPI_FAILURE(status)) {
- acpi_handle_err(device->handle, "Failed to evaluate %s\n",
- method);
+ acpi_handle_err(handle, "Failed to evaluate %s\n", method);
return -ENODEV;
}
@@ -315,15 +321,16 @@ static int set_lcd_level(struct acpi_device *device, int level)
return 0;
}
-static int get_lcd_level(struct acpi_device *device)
+static int get_lcd_level(struct device *dev)
{
- struct fujitsu_bl *priv = acpi_driver_data(device);
+ struct fujitsu_bl *priv = dev_get_drvdata(dev);
+ acpi_handle handle = ACPI_HANDLE(dev);
unsigned long long state = 0;
acpi_status status = AE_OK;
- acpi_handle_debug(device->handle, "get lcd level via GBLL\n");
+ acpi_handle_debug(handle, "get lcd level via GBLL\n");
- status = acpi_evaluate_integer(device->handle, "GBLL", NULL, &state);
+ status = acpi_evaluate_integer(handle, "GBLL", NULL, &state);
if (ACPI_FAILURE(status))
return 0;
@@ -332,15 +339,16 @@ static int get_lcd_level(struct acpi_device *device)
return priv->brightness_level;
}
-static int get_max_brightness(struct acpi_device *device)
+static int get_max_brightness(struct device *dev)
{
- struct fujitsu_bl *priv = acpi_driver_data(device);
+ struct fujitsu_bl *priv = dev_get_drvdata(dev);
+ acpi_handle handle = ACPI_HANDLE(dev);
unsigned long long state = 0;
acpi_status status = AE_OK;
- acpi_handle_debug(device->handle, "get max lcd level via RBLL\n");
+ acpi_handle_debug(handle, "get max lcd level via RBLL\n");
- status = acpi_evaluate_integer(device->handle, "RBLL", NULL, &state);
+ status = acpi_evaluate_integer(handle, "RBLL", NULL, &state);
if (ACPI_FAILURE(status))
return -1;
@@ -353,15 +361,13 @@ static int get_max_brightness(struct acpi_device *device)
static int bl_get_brightness(struct backlight_device *b)
{
- struct acpi_device *device = bl_get_data(b);
+ struct device *dev = bl_get_data(b);
- return b->props.power == BACKLIGHT_POWER_OFF ? 0 : get_lcd_level(device);
+ return b->props.power == BACKLIGHT_POWER_OFF ? 0 : get_lcd_level(dev);
}
static int bl_update_status(struct backlight_device *b)
{
- struct acpi_device *device = bl_get_data(b);
-
if (fext) {
if (b->props.power == BACKLIGHT_POWER_OFF)
call_fext_func(fext, FUNC_BACKLIGHT, 0x1,
@@ -371,7 +377,7 @@ static int bl_update_status(struct backlight_device *b)
BACKLIGHT_PARAM_POWER, BACKLIGHT_ON);
}
- return set_lcd_level(device, b->props.brightness);
+ return set_lcd_level(bl_get_data(b), b->props.brightness);
}
static const struct backlight_ops fujitsu_bl_ops = {
@@ -447,12 +453,13 @@ static const struct key_entry keymap_backlight[] = {
{ KE_END, 0 }
};
-static int acpi_fujitsu_bl_input_setup(struct acpi_device *device)
+static int acpi_fujitsu_bl_input_setup(struct device *dev)
{
- struct fujitsu_bl *priv = acpi_driver_data(device);
+ struct fujitsu_bl *priv = dev_get_drvdata(dev);
+ struct acpi_device *device = ACPI_COMPANION(dev);
int ret;
- priv->input = devm_input_allocate_device(&device->dev);
+ priv->input = devm_input_allocate_device(dev);
if (!priv->input)
return -ENOMEM;
@@ -471,9 +478,9 @@ static int acpi_fujitsu_bl_input_setup(struct acpi_device *device)
return input_register_device(priv->input);
}
-static int fujitsu_backlight_register(struct acpi_device *device)
+static int fujitsu_backlight_register(struct device *dev)
{
- struct fujitsu_bl *priv = acpi_driver_data(device);
+ struct fujitsu_bl *priv = dev_get_drvdata(dev);
const struct backlight_properties props = {
.brightness = priv->brightness_level,
.max_brightness = priv->max_brightness - 1,
@@ -481,9 +488,8 @@ static int fujitsu_backlight_register(struct acpi_device *device)
};
struct backlight_device *bd;
- bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop",
- &device->dev, device,
- &fujitsu_bl_ops, &props);
+ bd = devm_backlight_device_register(dev, "fujitsu-laptop",
+ dev, dev, &fujitsu_bl_ops, &props);
if (IS_ERR(bd))
return PTR_ERR(bd);
@@ -492,65 +498,82 @@ static int fujitsu_backlight_register(struct acpi_device *device)
return 0;
}
-static int acpi_fujitsu_bl_add(struct acpi_device *device)
+/* Brightness notify */
+
+static void acpi_fujitsu_bl_notify(acpi_handle handle, u32 event, void *data)
{
+ struct device *dev = data;
+ struct fujitsu_bl *priv = dev_get_drvdata(dev);
+ int oldb, newb;
+
+ if (event != ACPI_FUJITSU_NOTIFY_CODE) {
+ acpi_handle_info(handle, "unsupported event [0x%x]\n", event);
+ sparse_keymap_report_event(priv->input, -1, 1, true);
+ return;
+ }
+
+ oldb = priv->brightness_level;
+ get_lcd_level(dev);
+ newb = priv->brightness_level;
+
+ acpi_handle_debug(handle, "brightness button event [%i -> %i]\n",
+ oldb, newb);
+
+ if (oldb == newb)
+ return;
+
+ if (!disable_brightness_adjust)
+ set_lcd_level(dev, newb);
+
+ sparse_keymap_report_event(priv->input, oldb < newb, 1, true);
+}
+
+static int acpi_fujitsu_bl_probe(struct platform_device *pdev)
+{
+ struct acpi_device *device;
struct fujitsu_bl *priv;
int ret;
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
return -ENODEV;
- priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
fujitsu_bl = priv;
strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME);
strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
- device->driver_data = priv;
+
+ platform_set_drvdata(pdev, priv);
pr_info("ACPI: %s [%s]\n",
acpi_device_name(device), acpi_device_bid(device));
- if (get_max_brightness(device) <= 0)
+ if (get_max_brightness(&pdev->dev) <= 0)
priv->max_brightness = FUJITSU_LCD_N_LEVELS;
- get_lcd_level(device);
+ get_lcd_level(&pdev->dev);
- ret = acpi_fujitsu_bl_input_setup(device);
+ ret = acpi_fujitsu_bl_input_setup(&pdev->dev);
if (ret)
return ret;
- return fujitsu_backlight_register(device);
-}
+ ret = fujitsu_backlight_register(&pdev->dev);
+ if (ret)
+ return ret;
-/* Brightness notify */
+ return acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ acpi_fujitsu_bl_notify, &pdev->dev);
+}
-static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event)
+static void acpi_fujitsu_bl_remove(struct platform_device *pdev)
{
- struct fujitsu_bl *priv = acpi_driver_data(device);
- int oldb, newb;
-
- if (event != ACPI_FUJITSU_NOTIFY_CODE) {
- acpi_handle_info(device->handle, "unsupported event [0x%x]\n",
- event);
- sparse_keymap_report_event(priv->input, -1, 1, true);
- return;
- }
-
- oldb = priv->brightness_level;
- get_lcd_level(device);
- newb = priv->brightness_level;
-
- acpi_handle_debug(device->handle,
- "brightness button event [%i -> %i]\n", oldb, newb);
-
- if (oldb == newb)
- return;
-
- if (!disable_brightness_adjust)
- set_lcd_level(device, newb);
-
- sparse_keymap_report_event(priv->input, oldb < newb, 1, true);
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
+ ACPI_DEVICE_NOTIFY, acpi_fujitsu_bl_notify);
}
/* ACPI device for hotkey handling */
@@ -560,7 +583,7 @@ static const struct key_entry keymap_default[] = {
{ KE_KEY, KEY2_CODE, { KEY_PROG2 } },
{ KE_KEY, KEY3_CODE, { KEY_PROG3 } },
{ KE_KEY, KEY4_CODE, { KEY_PROG4 } },
- { KE_KEY, KEY5_CODE, { KEY_RFKILL } },
+ { KE_KEY, KEY9_CODE, { KEY_RFKILL } },
/* Soft keys read from status flags */
{ KE_KEY, FLAG_RFKILL, { KEY_RFKILL } },
{ KE_KEY, FLAG_TOUCHPAD_TOGGLE, { KEY_TOUCHPAD_TOGGLE } },
@@ -584,6 +607,18 @@ static const struct key_entry keymap_p8010[] = {
{ KE_END, 0 }
};
+static const struct key_entry keymap_s2110[] = {
+ { KE_KEY, KEY1_CODE, { KEY_PROG1 } }, /* "A" */
+ { KE_KEY, KEY2_CODE, { KEY_PROG2 } }, /* "B" */
+ { KE_KEY, KEY3_CODE, { KEY_WWW } }, /* "Internet" */
+ { KE_KEY, KEY4_CODE, { KEY_EMAIL } }, /* "E-mail" */
+ { KE_KEY, KEY5_CODE, { KEY_STOPCD } },
+ { KE_KEY, KEY6_CODE, { KEY_PLAYPAUSE } },
+ { KE_KEY, KEY7_CODE, { KEY_PREVIOUSSONG } },
+ { KE_KEY, KEY8_CODE, { KEY_NEXTSONG } },
+ { KE_END, 0 }
+};
+
static const struct key_entry *keymap = keymap_default;
static int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id)
@@ -621,15 +656,25 @@ static const struct dmi_system_id fujitsu_laptop_dmi_table[] = {
},
.driver_data = (void *)keymap_p8010
},
+ {
+ .callback = fujitsu_laptop_dmi_keymap_override,
+ .ident = "Fujitsu LifeBook S2110",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S2110"),
+ },
+ .driver_data = (void *)keymap_s2110
+ },
{}
};
-static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device)
+static int acpi_fujitsu_laptop_input_setup(struct device *dev)
{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
+ struct acpi_device *device = ACPI_COMPANION(dev);
int ret;
- priv->input = devm_input_allocate_device(&device->dev);
+ priv->input = devm_input_allocate_device(dev);
if (!priv->input)
return -ENOMEM;
@@ -648,9 +693,9 @@ static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device)
return input_register_device(priv->input);
}
-static int fujitsu_laptop_platform_add(struct acpi_device *device)
+static int fujitsu_laptop_platform_add(struct device *dev)
{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
int ret;
priv->pf_device = platform_device_alloc("fujitsu-laptop", PLATFORM_DEVID_NONE);
@@ -678,9 +723,9 @@ err_put_platform_device:
return ret;
}
-static void fujitsu_laptop_platform_remove(struct acpi_device *device)
+static void fujitsu_laptop_platform_remove(struct device *dev)
{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
sysfs_remove_group(&priv->pf_device->dev.kobj,
&fujitsu_pf_attribute_group);
@@ -690,7 +735,7 @@ static void fujitsu_laptop_platform_remove(struct acpi_device *device)
static int logolamp_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
- struct acpi_device *device = to_acpi_device(cdev->dev->parent);
+ struct device *parent = cdev->dev->parent;
int poweron = FUNC_LED_ON, always = FUNC_LED_ON;
int ret;
@@ -700,23 +745,23 @@ static int logolamp_set(struct led_classdev *cdev,
if (brightness < LED_FULL)
always = FUNC_LED_OFF;
- ret = call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron);
+ ret = call_fext_func(parent, FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron);
if (ret < 0)
return ret;
- return call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always);
+ return call_fext_func(parent, FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always);
}
static enum led_brightness logolamp_get(struct led_classdev *cdev)
{
- struct acpi_device *device = to_acpi_device(cdev->dev->parent);
+ struct device *parent = cdev->dev->parent;
int ret;
- ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
+ ret = call_fext_func(parent, FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
if (ret == FUNC_LED_ON)
return LED_FULL;
- ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
+ ret = call_fext_func(parent, FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
if (ret == FUNC_LED_ON)
return LED_HALF;
@@ -726,22 +771,21 @@ static enum led_brightness logolamp_get(struct led_classdev *cdev)
static int kblamps_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
- struct acpi_device *device = to_acpi_device(cdev->dev->parent);
+ struct device *parent = cdev->dev->parent;
if (brightness >= LED_FULL)
- return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
+ return call_fext_func(parent, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
FUNC_LED_ON);
else
- return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
+ return call_fext_func(parent, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
FUNC_LED_OFF);
}
static enum led_brightness kblamps_get(struct led_classdev *cdev)
{
- struct acpi_device *device = to_acpi_device(cdev->dev->parent);
enum led_brightness brightness = LED_OFF;
- if (call_fext_func(device,
+ if (call_fext_func(cdev->dev->parent,
FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
brightness = LED_FULL;
@@ -751,22 +795,22 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev)
static int radio_led_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
- struct acpi_device *device = to_acpi_device(cdev->dev->parent);
+ struct device *parent = cdev->dev->parent;
if (brightness >= LED_FULL)
- return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON,
+ return call_fext_func(parent, FUNC_FLAGS, 0x5, RADIO_LED_ON,
RADIO_LED_ON);
else
- return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON,
+ return call_fext_func(parent, FUNC_FLAGS, 0x5, RADIO_LED_ON,
0x0);
}
static enum led_brightness radio_led_get(struct led_classdev *cdev)
{
- struct acpi_device *device = to_acpi_device(cdev->dev->parent);
+ struct device *parent = cdev->dev->parent;
enum led_brightness brightness = LED_OFF;
- if (call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON)
+ if (call_fext_func(parent, FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON)
brightness = LED_FULL;
return brightness;
@@ -775,60 +819,58 @@ static enum led_brightness radio_led_get(struct led_classdev *cdev)
static int eco_led_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
- struct acpi_device *device = to_acpi_device(cdev->dev->parent);
+ struct device *parent = cdev->dev->parent;
int curr;
- curr = call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0);
+ curr = call_fext_func(parent, FUNC_LEDS, 0x2, ECO_LED, 0x0);
if (brightness >= LED_FULL)
- return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED,
+ return call_fext_func(parent, FUNC_LEDS, 0x1, ECO_LED,
curr | ECO_LED_ON);
else
- return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED,
+ return call_fext_func(parent, FUNC_LEDS, 0x1, ECO_LED,
curr & ~ECO_LED_ON);
}
static enum led_brightness eco_led_get(struct led_classdev *cdev)
{
- struct acpi_device *device = to_acpi_device(cdev->dev->parent);
+ struct device *parent = cdev->dev->parent;
enum led_brightness brightness = LED_OFF;
- if (call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON)
+ if (call_fext_func(parent, FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON)
brightness = LED_FULL;
return brightness;
}
-static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
+static int acpi_fujitsu_laptop_leds_register(struct device *dev)
{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
struct led_classdev *led;
int ret;
- if (call_fext_func(device,
- FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
- led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
+ if (call_fext_func(dev, FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
led->name = "fujitsu::logolamp";
led->brightness_set_blocking = logolamp_set;
led->brightness_get = logolamp_get;
- ret = devm_led_classdev_register(&device->dev, led);
+ ret = devm_led_classdev_register(dev, led);
if (ret)
return ret;
}
- if ((call_fext_func(device,
- FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
- (call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
- led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
+ if ((call_fext_func(dev, FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
+ (call_fext_func(dev, FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
led->name = "fujitsu::kblamps";
led->brightness_set_blocking = kblamps_set;
led->brightness_get = kblamps_get;
- ret = devm_led_classdev_register(&device->dev, led);
+ ret = devm_led_classdev_register(dev, led);
if (ret)
return ret;
}
@@ -843,7 +885,7 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
* whether given model has a radio toggle button.
*/
if (priv->flags_supported & BIT(17)) {
- led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
@@ -851,7 +893,7 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
led->brightness_set_blocking = radio_led_set;
led->brightness_get = radio_led_get;
led->default_trigger = "rfkill-any";
- ret = devm_led_classdev_register(&device->dev, led);
+ ret = devm_led_classdev_register(dev, led);
if (ret)
return ret;
}
@@ -861,17 +903,16 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
* bit 14 seems to indicate presence of said led as well.
* Confirm by testing the status.
*/
- if ((call_fext_func(device, FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) &&
- (call_fext_func(device,
- FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) {
- led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
+ if ((call_fext_func(dev, FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) &&
+ (call_fext_func(dev, FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) {
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
led->name = "fujitsu::eco_led";
led->brightness_set_blocking = eco_led_set;
led->brightness_get = eco_led_get;
- ret = devm_led_classdev_register(&device->dev, led);
+ ret = devm_led_classdev_register(dev, led);
if (ret)
return ret;
}
@@ -879,21 +920,102 @@ static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
return 0;
}
-static int acpi_fujitsu_laptop_add(struct acpi_device *device)
+static void acpi_fujitsu_laptop_press(struct device *dev, int scancode)
+{
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = kfifo_in_locked(&priv->fifo, (unsigned char *)&scancode,
+ sizeof(scancode), &priv->fifo_lock);
+ if (ret != sizeof(scancode)) {
+ dev_info(&priv->input->dev, "Could not push scancode [0x%x]\n",
+ scancode);
+ return;
+ }
+ sparse_keymap_report_event(priv->input, scancode, 1, false);
+ dev_dbg(&priv->input->dev, "Push scancode into ringbuffer [0x%x]\n",
+ scancode);
+}
+
+static void acpi_fujitsu_laptop_release(struct device *dev)
+{
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
+ int scancode, ret;
+
+ while (true) {
+ ret = kfifo_out_locked(&priv->fifo, (unsigned char *)&scancode,
+ sizeof(scancode), &priv->fifo_lock);
+ if (ret != sizeof(scancode))
+ return;
+ sparse_keymap_report_event(priv->input, scancode, 0, false);
+ dev_dbg(&priv->input->dev,
+ "Pop scancode from ringbuffer [0x%x]\n", scancode);
+ }
+}
+
+static void acpi_fujitsu_laptop_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct device *dev = data;
+ struct fujitsu_laptop *priv = dev_get_drvdata(dev);
+ unsigned long flags;
+ int scancode, i = 0;
+ unsigned int irb;
+
+ if (event != ACPI_FUJITSU_NOTIFY_CODE) {
+ acpi_handle_info(handle, "Unsupported event [0x%x]\n", event);
+ sparse_keymap_report_event(priv->input, -1, 1, true);
+ return;
+ }
+
+ if (priv->flags_supported)
+ priv->flags_state = call_fext_func(dev, FUNC_FLAGS, 0x4, 0x0, 0x0);
+
+ while ((irb = call_fext_func(dev, FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 &&
+ i++ < MAX_HOTKEY_RINGBUFFER_SIZE) {
+ scancode = irb & 0x4ff;
+ if (sparse_keymap_entry_from_scancode(priv->input, scancode))
+ acpi_fujitsu_laptop_press(dev, scancode);
+ else if (scancode == 0)
+ acpi_fujitsu_laptop_release(dev);
+ else
+ acpi_handle_info(handle, "Unknown GIRB result [%x]\n", irb);
+ }
+
+ /*
+ * First seen on the Skylake-based Lifebook E736/E746/E756), the
+ * touchpad toggle hotkey (Fn+F4) is handled in software. Other models
+ * have since added additional "soft keys". These are reported in the
+ * status flags queried using FUNC_FLAGS.
+ */
+ if (priv->flags_supported & (FLAG_SOFTKEYS)) {
+ flags = call_fext_func(dev, FUNC_FLAGS, 0x1, 0x0, 0x0);
+ flags &= (FLAG_SOFTKEYS);
+ for_each_set_bit(i, &flags, BITS_PER_LONG)
+ sparse_keymap_report_event(priv->input, BIT(i), 1, true);
+ }
+}
+
+static int acpi_fujitsu_laptop_probe(struct platform_device *pdev)
{
struct fujitsu_laptop *priv;
+ struct acpi_device *device;
int ret, i = 0;
- priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended.");
- fext = device;
+ fext = &pdev->dev;
strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME);
strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
- device->driver_data = priv;
+
+ platform_set_drvdata(pdev, priv);
/* kfifo */
spin_lock_init(&priv->fifo_lock);
@@ -905,14 +1027,13 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device)
pr_info("ACPI: %s [%s]\n",
acpi_device_name(device), acpi_device_bid(device));
- while (call_fext_func(device, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 &&
+ while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 &&
i++ < MAX_HOTKEY_RINGBUFFER_SIZE)
; /* No action, result is discarded */
acpi_handle_debug(device->handle, "Discarded %i ringbuffer entries\n",
i);
- priv->flags_supported = call_fext_func(device, FUNC_FLAGS, 0x0, 0x0,
- 0x0);
+ priv->flags_supported = call_fext_func(fext, FUNC_FLAGS, 0x0, 0x0, 0x0);
/* Make sure our bitmask of supported functions is cleared if the
RFKILL function block is not implemented, like on the S7020. */
@@ -920,12 +1041,12 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device)
priv->flags_supported = 0;
if (priv->flags_supported)
- priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0,
+ priv->flags_state = call_fext_func(fext, FUNC_FLAGS, 0x4, 0x0,
0x0);
/* Suspect this is a keymap of the application panel, print it */
acpi_handle_info(device->handle, "BTNI: [0x%x]\n",
- call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0));
+ call_fext_func(fext, FUNC_BUTTONS, 0x0, 0x0, 0x0));
/* Sync backlight power status */
if (fujitsu_bl && fujitsu_bl->bl_device &&
@@ -937,117 +1058,49 @@ static int acpi_fujitsu_laptop_add(struct acpi_device *device)
fujitsu_bl->bl_device->props.power = BACKLIGHT_POWER_ON;
}
- ret = acpi_fujitsu_laptop_input_setup(device);
+ ret = acpi_fujitsu_laptop_input_setup(fext);
if (ret)
goto err_free_fifo;
- ret = acpi_fujitsu_laptop_leds_register(device);
+ ret = acpi_fujitsu_laptop_leds_register(fext);
if (ret)
goto err_free_fifo;
- ret = fujitsu_laptop_platform_add(device);
+ ret = fujitsu_laptop_platform_add(fext);
if (ret)
goto err_free_fifo;
- ret = fujitsu_battery_charge_control_add(device);
+ ret = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ acpi_fujitsu_laptop_notify, fext);
+ if (ret)
+ goto err_platform_remove;
+
+ ret = fujitsu_battery_charge_control_add(fext);
if (ret < 0)
pr_warn("Unable to register battery charge control: %d\n", ret);
return 0;
+err_platform_remove:
+ fujitsu_laptop_platform_remove(fext);
err_free_fifo:
kfifo_free(&priv->fifo);
return ret;
}
-static void acpi_fujitsu_laptop_remove(struct acpi_device *device)
+static void acpi_fujitsu_laptop_remove(struct platform_device *pdev)
{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
+ struct fujitsu_laptop *priv = platform_get_drvdata(pdev);
- fujitsu_battery_charge_control_remove(device);
+ fujitsu_battery_charge_control_remove(&pdev->dev);
- fujitsu_laptop_platform_remove(device);
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), ACPI_DEVICE_NOTIFY,
+ acpi_fujitsu_laptop_notify);
- kfifo_free(&priv->fifo);
-}
-
-static void acpi_fujitsu_laptop_press(struct acpi_device *device, int scancode)
-{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
- int ret;
+ fujitsu_laptop_platform_remove(&pdev->dev);
- ret = kfifo_in_locked(&priv->fifo, (unsigned char *)&scancode,
- sizeof(scancode), &priv->fifo_lock);
- if (ret != sizeof(scancode)) {
- dev_info(&priv->input->dev, "Could not push scancode [0x%x]\n",
- scancode);
- return;
- }
- sparse_keymap_report_event(priv->input, scancode, 1, false);
- dev_dbg(&priv->input->dev, "Push scancode into ringbuffer [0x%x]\n",
- scancode);
-}
-
-static void acpi_fujitsu_laptop_release(struct acpi_device *device)
-{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
- int scancode, ret;
-
- while (true) {
- ret = kfifo_out_locked(&priv->fifo, (unsigned char *)&scancode,
- sizeof(scancode), &priv->fifo_lock);
- if (ret != sizeof(scancode))
- return;
- sparse_keymap_report_event(priv->input, scancode, 0, false);
- dev_dbg(&priv->input->dev,
- "Pop scancode from ringbuffer [0x%x]\n", scancode);
- }
-}
-
-static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event)
-{
- struct fujitsu_laptop *priv = acpi_driver_data(device);
- unsigned long flags;
- int scancode, i = 0;
- unsigned int irb;
-
- if (event != ACPI_FUJITSU_NOTIFY_CODE) {
- acpi_handle_info(device->handle, "Unsupported event [0x%x]\n",
- event);
- sparse_keymap_report_event(priv->input, -1, 1, true);
- return;
- }
-
- if (priv->flags_supported)
- priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0,
- 0x0);
-
- while ((irb = call_fext_func(device,
- FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 &&
- i++ < MAX_HOTKEY_RINGBUFFER_SIZE) {
- scancode = irb & 0x4ff;
- if (sparse_keymap_entry_from_scancode(priv->input, scancode))
- acpi_fujitsu_laptop_press(device, scancode);
- else if (scancode == 0)
- acpi_fujitsu_laptop_release(device);
- else
- acpi_handle_info(device->handle,
- "Unknown GIRB result [%x]\n", irb);
- }
-
- /*
- * First seen on the Skylake-based Lifebook E736/E746/E756), the
- * touchpad toggle hotkey (Fn+F4) is handled in software. Other models
- * have since added additional "soft keys". These are reported in the
- * status flags queried using FUNC_FLAGS.
- */
- if (priv->flags_supported & (FLAG_SOFTKEYS)) {
- flags = call_fext_func(device, FUNC_FLAGS, 0x1, 0x0, 0x0);
- flags &= (FLAG_SOFTKEYS);
- for_each_set_bit(i, &flags, BITS_PER_LONG)
- sparse_keymap_report_event(priv->input, BIT(i), 1, true);
- }
+ kfifo_free(&priv->fifo);
}
/* Initialization */
@@ -1057,14 +1110,13 @@ static const struct acpi_device_id fujitsu_bl_device_ids[] = {
{"", 0},
};
-static struct acpi_driver acpi_fujitsu_bl_driver = {
- .name = ACPI_FUJITSU_BL_DRIVER_NAME,
- .class = ACPI_FUJITSU_CLASS,
- .ids = fujitsu_bl_device_ids,
- .ops = {
- .add = acpi_fujitsu_bl_add,
- .notify = acpi_fujitsu_bl_notify,
- },
+static struct platform_driver acpi_fujitsu_bl_driver = {
+ .probe = acpi_fujitsu_bl_probe,
+ .remove = acpi_fujitsu_bl_remove,
+ .driver = {
+ .name = ACPI_FUJITSU_BL_DRIVER_NAME,
+ .acpi_match_table = fujitsu_bl_device_ids,
+ },
};
static const struct acpi_device_id fujitsu_laptop_device_ids[] = {
@@ -1072,15 +1124,13 @@ static const struct acpi_device_id fujitsu_laptop_device_ids[] = {
{"", 0},
};
-static struct acpi_driver acpi_fujitsu_laptop_driver = {
- .name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME,
- .class = ACPI_FUJITSU_CLASS,
- .ids = fujitsu_laptop_device_ids,
- .ops = {
- .add = acpi_fujitsu_laptop_add,
- .remove = acpi_fujitsu_laptop_remove,
- .notify = acpi_fujitsu_laptop_notify,
- },
+static struct platform_driver acpi_fujitsu_laptop_driver = {
+ .probe = acpi_fujitsu_laptop_probe,
+ .remove = acpi_fujitsu_laptop_remove,
+ .driver = {
+ .name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME,
+ .acpi_match_table = fujitsu_laptop_device_ids,
+ },
};
static const struct acpi_device_id fujitsu_ids[] __used = {
@@ -1094,7 +1144,7 @@ static int __init fujitsu_init(void)
{
int ret;
- ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver);
+ ret = platform_driver_register(&acpi_fujitsu_bl_driver);
if (ret)
return ret;
@@ -1106,7 +1156,7 @@ static int __init fujitsu_init(void)
/* Register laptop driver */
- ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver);
+ ret = platform_driver_register(&acpi_fujitsu_laptop_driver);
if (ret)
goto err_unregister_platform_driver;
@@ -1117,18 +1167,18 @@ static int __init fujitsu_init(void)
err_unregister_platform_driver:
platform_driver_unregister(&fujitsu_pf_driver);
err_unregister_acpi:
- acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
+ platform_driver_unregister(&acpi_fujitsu_bl_driver);
return ret;
}
static void __exit fujitsu_cleanup(void)
{
- acpi_bus_unregister_driver(&acpi_fujitsu_laptop_driver);
+ platform_driver_unregister(&acpi_fujitsu_laptop_driver);
platform_driver_unregister(&fujitsu_pf_driver);
- acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
+ platform_driver_unregister(&acpi_fujitsu_bl_driver);
pr_info("driver unloaded\n");
}
diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c
index 17f08ce7552d..2f8c1b89cbca 100644
--- a/drivers/platform/x86/fujitsu-tablet.c
+++ b/drivers/platform/x86/fujitsu-tablet.c
@@ -18,6 +18,7 @@
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/dmi.h>
+#include <linux/platform_device.h>
#define MODULENAME "fujitsu-tablet"
@@ -442,13 +443,15 @@ static acpi_status fujitsu_walk_resources(struct acpi_resource *res, void *data)
}
}
-static int acpi_fujitsu_add(struct acpi_device *adev)
+static int acpi_fujitsu_probe(struct platform_device *pdev)
{
+ struct acpi_device *adev;
acpi_status status;
int error;
+ adev = ACPI_COMPANION(&pdev->dev);
if (!adev)
- return -EINVAL;
+ return -ENODEV;
status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
fujitsu_walk_resources, NULL);
@@ -461,7 +464,7 @@ static int acpi_fujitsu_add(struct acpi_device *adev)
snprintf(fujitsu.phys, sizeof(fujitsu.phys),
"%s/input0", acpi_device_hid(adev));
- error = input_fujitsu_setup(&adev->dev,
+ error = input_fujitsu_setup(&pdev->dev,
acpi_device_name(adev), fujitsu.phys);
if (error)
return error;
@@ -484,7 +487,7 @@ static int acpi_fujitsu_add(struct acpi_device *adev)
return 0;
}
-static void acpi_fujitsu_remove(struct acpi_device *adev)
+static void acpi_fujitsu_remove(struct platform_device *pdev)
{
free_irq(fujitsu.irq, fujitsu_interrupt);
release_region(fujitsu.io_base, fujitsu.io_length);
@@ -501,15 +504,14 @@ static int acpi_fujitsu_resume(struct device *dev)
static SIMPLE_DEV_PM_OPS(acpi_fujitsu_pm, NULL, acpi_fujitsu_resume);
-static struct acpi_driver acpi_fujitsu_driver = {
- .name = MODULENAME,
- .class = "hotkey",
- .ids = fujitsu_ids,
- .ops = {
- .add = acpi_fujitsu_add,
- .remove = acpi_fujitsu_remove,
+static struct platform_driver acpi_fujitsu_driver = {
+ .probe = acpi_fujitsu_probe,
+ .remove = acpi_fujitsu_remove,
+ .driver = {
+ .name = MODULENAME,
+ .acpi_match_table = fujitsu_ids,
+ .pm = &acpi_fujitsu_pm,
},
- .drv.pm = &acpi_fujitsu_pm,
};
static int __init fujitsu_module_init(void)
@@ -518,7 +520,7 @@ static int __init fujitsu_module_init(void)
dmi_check_system(dmi_ids);
- error = acpi_bus_register_driver(&acpi_fujitsu_driver);
+ error = platform_driver_register(&acpi_fujitsu_driver);
if (error)
return error;
@@ -527,7 +529,7 @@ static int __init fujitsu_module_init(void)
static void __exit fujitsu_module_exit(void)
{
- acpi_bus_unregister_driver(&acpi_fujitsu_driver);
+ platform_driver_unregister(&acpi_fujitsu_driver);
}
module_init(fujitsu_module_init);
diff --git a/drivers/platform/x86/gigabyte-wmi.c b/drivers/platform/x86/gigabyte-wmi.c
index f6ba88baee4d..f42c85607a6b 100644
--- a/drivers/platform/x86/gigabyte-wmi.c
+++ b/drivers/platform/x86/gigabyte-wmi.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * Copyright (C) 2021 Thomas Weißschuh <thomas@weissschuh.net>
+ * Copyright (C) 2021 Thomas Weißschuh <linux@weissschuh.net>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -159,6 +159,6 @@ static struct wmi_driver gigabyte_wmi_driver = {
module_wmi_driver(gigabyte_wmi_driver);
MODULE_DEVICE_TABLE(wmi, gigabyte_wmi_id_table);
-MODULE_AUTHOR("Thomas Weißschuh <thomas@weissschuh.net>");
+MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net>");
MODULE_DESCRIPTION("Gigabyte WMI temperature driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/gpd-pocket-fan.c b/drivers/platform/x86/gpd-pocket-fan.c
index 7a20f68ae206..c9236738f896 100644
--- a/drivers/platform/x86/gpd-pocket-fan.c
+++ b/drivers/platform/x86/gpd-pocket-fan.c
@@ -112,14 +112,14 @@ set_speed:
gpd_pocket_fan_set_speed(fan, speed);
/* When mostly idle (low temp/speed), slow down the poll interval. */
- queue_delayed_work(system_wq, &fan->work,
+ queue_delayed_work(system_percpu_wq, &fan->work,
msecs_to_jiffies(4000 / (speed + 1)));
}
static void gpd_pocket_fan_force_update(struct gpd_pocket_fan_data *fan)
{
fan->last_speed = -1;
- mod_delayed_work(system_wq, &fan->work, 0);
+ mod_delayed_work(system_percpu_wq, &fan->work, 0);
}
static int gpd_pocket_fan_probe(struct platform_device *pdev)
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
index 13237890fc92..27fd6cd21529 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
@@ -10,6 +10,8 @@
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/string.h>
#include <linux/wmi.h>
#include "bioscfg.h"
#include "../../firmware_attributes_class.h"
@@ -586,7 +588,7 @@ static int hp_add_other_attributes(int attr_type)
int ret;
char *attr_name;
- attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ attr_name_kobj = kzalloc_obj(*attr_name_kobj);
if (!attr_name_kobj)
return -ENOMEM;
@@ -694,6 +696,11 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type,
return ret;
}
+ if (!str_value || !str_value[0]) {
+ pr_debug("Ignoring attribute with empty name\n");
+ goto pack_attr_exit;
+ }
+
/* All duplicate attributes found are ignored */
duplicate = kset_find_obj(temp_kset, str_value);
if (duplicate) {
@@ -704,7 +711,7 @@ static int hp_init_bios_package_attribute(enum hp_wmi_data_type attr_type,
}
/* build attribute */
- attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ attr_name_kobj = kzalloc_obj(*attr_name_kobj);
if (!attr_name_kobj) {
ret = -ENOMEM;
goto pack_attr_exit;
@@ -781,6 +788,12 @@ static int hp_init_bios_buffer_attribute(enum hp_wmi_data_type attr_type,
if (ret < 0)
goto buff_attr_exit;
+ if (strlen(str) == 0) {
+ pr_debug("Ignoring attribute with empty name\n");
+ 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;
@@ -797,7 +810,7 @@ static int hp_init_bios_buffer_attribute(enum hp_wmi_data_type attr_type,
}
/* build attribute */
- attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL);
+ attr_name_kobj = kzalloc_obj(*attr_name_kobj);
if (!attr_name_kobj) {
ret = -ENOMEM;
goto buff_attr_exit;
@@ -1034,7 +1047,7 @@ err_release_attributes_data:
release_attributes_data();
err_destroy_classdev:
- device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ device_unregister(bioscfg_drv.class_dev);
err_unregister_class:
hp_exit_attr_set_interface();
@@ -1045,7 +1058,7 @@ err_unregister_class:
static void __exit hp_exit(void)
{
release_attributes_data();
- device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ device_unregister(bioscfg_drv.class_dev);
hp_exit_attr_set_interface();
}
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
index 3166ef328eba..f1eec0e4ba07 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.h
@@ -10,6 +10,7 @@
#include <linux/wmi.h>
#include <linux/types.h>
+#include <linux/string.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
@@ -56,14 +57,14 @@ enum mechanism_values {
#define PASSWD_MECHANISM_TYPES "password"
-#define HP_WMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4"
+#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_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"
+#define HP_WMI_SET_BIOS_SETTING_GUID "1F4C91EB-DC5C-460B-951D-C7CB9B4B8D5E"
enum hp_wmi_spm_commandtype {
HPWMI_SECUREPLATFORM_GET_STATE = 0x10,
@@ -285,8 +286,9 @@ enum hp_wmi_data_elements {
{ \
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)) \
+ for (i = 0; i < bioscfg_drv.type##_instances_count; i++) { \
+ if (bioscfg_drv.type##_data[i].attr_name_kobj && \
+ !strcmp(kobj->name, bioscfg_drv.type##_data[i].attr_name_kobj->name)) \
return i; \
} \
return -EIO; \
diff --git a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
index c50ad5880503..af4d1920d488 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/enum-attributes.c
@@ -94,8 +94,11 @@ 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_instances_count)
+ return -EINVAL;
+ bioscfg_drv.enumeration_data = kvcalloc(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;
@@ -207,7 +210,7 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
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) {
+ if (elem + reqs >= enum_obj_count) {
pr_err("Error enum-objects package is too small\n");
return -EINVAL;
}
@@ -255,7 +258,7 @@ static int hp_populate_enumeration_elements_from_package(union acpi_object *enum
for (pos_values = 0; pos_values < size && pos_values < MAX_VALUES_SIZE;
pos_values++) {
- if (elem >= enum_obj_count) {
+ if (elem + pos_values >= enum_obj_count) {
pr_err("Error enum-objects package is too small\n");
return -EINVAL;
}
@@ -444,6 +447,6 @@ void hp_exit_enumeration_attributes(void)
}
bioscfg_drv.enumeration_instances_count = 0;
- kfree(bioscfg_drv.enumeration_data);
+ kvfree(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
index 6c7f4d5fa9cb..d96e160953e3 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/int-attributes.c
@@ -109,8 +109,8 @@ static const struct attribute_group integer_attr_group = {
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);
+ bioscfg_drv.integer_data = kzalloc_objs(*bioscfg_drv.integer_data,
+ bioscfg_drv.integer_instances_count);
if (!bioscfg_drv.integer_data) {
bioscfg_drv.integer_instances_count = 0;
@@ -227,7 +227,7 @@ static int hp_populate_integer_elements_from_package(union acpi_object *integer_
size = min_t(u32, integer_data->common.prerequisites_size, MAX_PREREQUISITES_SIZE);
for (reqs = 0; reqs < size; reqs++) {
- if (elem >= integer_obj_count) {
+ if (elem + reqs >= integer_obj_count) {
pr_err("Error elem-objects package is too small\n");
return -EINVAL;
}
diff --git a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
index c6e57bb9d8b7..f09489a085c8 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/order-list-attributes.c
@@ -98,9 +98,8 @@ 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);
+ bioscfg_drv.ordered_list_data = kzalloc_objs(*bioscfg_drv.ordered_list_data,
+ bioscfg_drv.ordered_list_instances_count);
if (!bioscfg_drv.ordered_list_data) {
bioscfg_drv.ordered_list_instances_count = 0;
return -ENOMEM;
@@ -216,6 +215,11 @@ static int hp_populate_ordered_list_elements_from_package(union acpi_object *ord
size = min_t(u32, ordered_list_data->common.prerequisites_size,
MAX_PREREQUISITES_SIZE);
for (reqs = 0; reqs < size; reqs++) {
+ if (elem + reqs >= order_obj_count) {
+ pr_err("Error elem-objects package is too small\n");
+ return -EINVAL;
+ }
+
ret = hp_convert_hexstr_to_str(order_obj[elem + reqs].string.pointer,
order_obj[elem + reqs].string.length,
&str_value, &value_len);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
index 187b372123ed..4d79eb8056a5 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/passwdobj-attributes.c
@@ -185,8 +185,8 @@ static const struct attribute_group password_attr_group = {
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);
+ bioscfg_drv.password_data = kzalloc_objs(*bioscfg_drv.password_data,
+ bioscfg_drv.password_instances_count);
if (!bioscfg_drv.password_data) {
bioscfg_drv.password_instances_count = 0;
return -ENOMEM;
@@ -303,6 +303,11 @@ static int hp_populate_password_elements_from_package(union acpi_object *passwor
MAX_PREREQUISITES_SIZE);
for (reqs = 0; reqs < size; reqs++) {
+ if (elem + reqs >= password_obj_count) {
+ pr_err("Error elem-objects package is too small\n");
+ return -EINVAL;
+ }
+
ret = hp_convert_hexstr_to_str(password_obj[elem + reqs].string.pointer,
password_obj[elem + reqs].string.length,
&str_value, &value_len);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
index 27758b779b2d..fe5a9a3a4ef1 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/string-attributes.c
@@ -101,8 +101,8 @@ static const struct attribute_group string_attr_group = {
int hp_alloc_string_data(void)
{
bioscfg_drv.string_instances_count = hp_get_instance_count(HP_WMI_BIOS_STRING_GUID);
- bioscfg_drv.string_data = kcalloc(bioscfg_drv.string_instances_count,
- sizeof(*bioscfg_drv.string_data), GFP_KERNEL);
+ bioscfg_drv.string_data = kzalloc_objs(*bioscfg_drv.string_data,
+ bioscfg_drv.string_instances_count);
if (!bioscfg_drv.string_data) {
bioscfg_drv.string_instances_count = 0;
return -ENOMEM;
@@ -217,7 +217,7 @@ static int hp_populate_string_elements_from_package(union acpi_object *string_ob
MAX_PREREQUISITES_SIZE);
for (reqs = 0; reqs < size; reqs++) {
- if (elem >= string_obj_count) {
+ if (elem + reqs >= string_obj_count) {
pr_err("Error elem-objects package is too small\n");
return -EINVAL;
}
diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c
index db5fdee2109c..f63bc00d9a9b 100644
--- a/drivers/platform/x86/hp/hp-wmi.c
+++ b/drivers/platform/x86/hp/hp-wmi.c
@@ -13,23 +13,28 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/kernel.h>
-#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/compiler_attributes.h>
+#include <linux/dmi.h>
+#include <linux/fixp-arith.h>
+#include <linux/hwmon.h>
#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/types.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
-#include <linux/hwmon.h>
-#include <linux/acpi.h>
-#include <linux/mutex.h>
-#include <linux/cleanup.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
+#include <linux/slab.h>
#include <linux/string.h>
-#include <linux/dmi.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
MODULE_AUTHOR("Matthew Garrett <mjg59@srcf.ucam.org>");
MODULE_DESCRIPTION("HP laptop WMI driver");
@@ -41,18 +46,99 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
#define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
#define HPWMI_BIOS_GUID "5FB7F034-2C63-45E9-BE91-3D44E2C707E4"
-#define HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET 0x62
-#define HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET 0x63
-#define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95
+enum hp_ec_offsets {
+ HP_EC_OFFSET_UNKNOWN = 0x00,
+ HP_NO_THERMAL_PROFILE_OFFSET = 0x01,
+ HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET = 0x59,
+ HP_OMEN_EC_THERMAL_PROFILE_FLAGS_OFFSET = 0x62,
+ HP_OMEN_EC_THERMAL_PROFILE_TIMER_OFFSET = 0x63,
+ HP_OMEN_EC_THERMAL_PROFILE_OFFSET = 0x95,
+};
#define HP_FAN_SPEED_AUTOMATIC 0x00
#define HP_POWER_LIMIT_DEFAULT 0x00
#define HP_POWER_LIMIT_NO_CHANGE 0xFF
-#define ACPI_AC_CLASS "ac_adapter"
-
#define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required
+enum hp_thermal_profile_omen_v0 {
+ HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
+ HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
+};
+
+enum hp_thermal_profile_omen_v1 {
+ HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
+ HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
+ HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
+};
+
+enum hp_thermal_profile_omen_flags {
+ HP_OMEN_EC_FLAGS_TURBO = 0x04,
+ HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
+ HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
+};
+
+enum hp_thermal_profile_victus {
+ HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
+ HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
+};
+
+enum hp_thermal_profile_victus_s {
+ HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
+ HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
+};
+
+enum hp_thermal_profile {
+ HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
+ HP_THERMAL_PROFILE_DEFAULT = 0x01,
+ HP_THERMAL_PROFILE_COOL = 0x02,
+ HP_THERMAL_PROFILE_QUIET = 0x03,
+};
+
+
+struct thermal_profile_params {
+ u8 performance;
+ u8 balanced;
+ u8 low_power;
+ u8 ec_tp_offset;
+};
+
+static const struct thermal_profile_params victus_s_thermal_params = {
+ .performance = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE,
+ .balanced = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
+ .low_power = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT,
+ .ec_tp_offset = HP_EC_OFFSET_UNKNOWN,
+};
+
+static const struct thermal_profile_params omen_v1_thermal_params = {
+ .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE,
+ .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .ec_tp_offset = HP_VICTUS_S_EC_THERMAL_PROFILE_OFFSET,
+};
+
+static const struct thermal_profile_params omen_v1_legacy_thermal_params = {
+ .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE,
+ .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .ec_tp_offset = HP_OMEN_EC_THERMAL_PROFILE_OFFSET,
+};
+
+static const struct thermal_profile_params omen_v1_no_ec_thermal_params = {
+ .performance = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE,
+ .balanced = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .low_power = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT,
+ .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET,
+};
+
+/*
+ * A generic pointer for the currently-active board's thermal profile
+ * parameters.
+ */
+static struct thermal_profile_params *active_thermal_profile_params;
+
/* DMI board names of devices that should use the omen specific path for
* thermal profiles.
* This was obtained by taking a look in the windows omen command center
@@ -63,12 +149,18 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45E9-BE91-3D44E2C707E4");
* contains "PerformanceControl".
*/
static const char * const omen_thermal_profile_boards[] = {
- "84DA", "84DB", "84DC", "8574", "8575", "860A", "87B5", "8572", "8573",
- "8600", "8601", "8602", "8605", "8606", "8607", "8746", "8747", "8749",
- "874A", "8603", "8604", "8748", "886B", "886C", "878A", "878B", "878C",
- "88C8", "88CB", "8786", "8787", "8788", "88D1", "88D2", "88F4", "88FD",
- "88F5", "88F6", "88F7", "88FE", "88FF", "8900", "8901", "8902", "8912",
- "8917", "8918", "8949", "894A", "89EB", "8BAD", "8A42", "8A15"
+ "84DA", "84DB", "84DC",
+ "8572", "8573", "8574", "8575",
+ "8600", "8601", "8602", "8603", "8604", "8605", "8606", "8607", "860A",
+ "8746", "8747", "8748", "8749", "874A", "8786", "8787", "8788", "878A",
+ "878B", "878C", "87B5",
+ "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6",
+ "88F7", "88FD", "88FE", "88FF",
+ "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB",
+ "8A15", "8A42",
+ "8BAD",
+ "8C58",
+ "8E41",
};
/* DMI Board names of Omen laptops that are specifically set to be thermal
@@ -76,7 +168,8 @@ static const char * const omen_thermal_profile_boards[] = {
* the get system design information WMI call returns
*/
static const char * const omen_thermal_profile_force_v0_boards[] = {
- "8607", "8746", "8747", "8749", "874A", "8748"
+ "8607",
+ "8746", "8747", "8748", "8749", "874A",
};
/* DMI board names of Omen laptops that have a thermal profile timer which will
@@ -84,19 +177,91 @@ static const char * const omen_thermal_profile_force_v0_boards[] = {
* "balanced" when reaching zero.
*/
static const char * const omen_timed_thermal_profile_boards[] = {
- "8BAD", "8A42", "8A15"
+ "8A15", "8A42",
+ "8BAD",
};
-/* DMI Board names of Victus 16-d1xxx laptops */
+/* DMI Board names of Victus 16-d laptops */
static const char * const victus_thermal_profile_boards[] = {
- "8A25"
+ "88F8",
+ "8A25",
};
-/* DMI Board names of Victus 16-s1000 laptops */
-static const char * const victus_s_thermal_profile_boards[] = {
- "8C9C"
+/* DMI Board names of Victus 16-r and Victus 16-s laptops */
+static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = {
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") },
+ .driver_data = (void *)&omen_v1_legacy_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") },
+ .driver_data = (void *)&omen_v1_legacy_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") },
+ .driver_data = (void *)&omen_v1_legacy_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BC2") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCA") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BCD") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD4") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BD5") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C76") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C77") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C78") },
+ .driver_data = (void *)&omen_v1_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C99") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8C9C") },
+ .driver_data = (void *)&victus_s_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D41") },
+ .driver_data = (void *)&omen_v1_no_ec_thermal_params,
+ },
+ {
+ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") },
+ .driver_data = (void *)&omen_v1_no_ec_thermal_params,
+ },
+ {},
};
+static bool is_victus_s_board;
+
enum hp_wmi_radio {
HPWMI_WIFI = 0x0,
HPWMI_BLUETOOTH = 0x1,
@@ -122,6 +287,7 @@ enum hp_wmi_event_ids {
HPWMI_BATTERY_CHARGE_PERIOD = 0x10,
HPWMI_SANITIZATION_MODE = 0x17,
HPWMI_CAMERA_TOGGLE = 0x1A,
+ HPWMI_FN_P_HOTKEY = 0x1B,
HPWMI_OMEN_KEY = 0x1D,
HPWMI_SMART_EXPERIENCE_APP = 0x21,
};
@@ -181,7 +347,8 @@ enum hp_wmi_gm_commandtype {
HPWMI_SET_GPU_THERMAL_MODES_QUERY = 0x22,
HPWMI_SET_POWER_LIMITS_QUERY = 0x29,
HPWMI_VICTUS_S_FAN_SPEED_GET_QUERY = 0x2D,
- HPWMI_FAN_SPEED_SET_QUERY = 0x2E,
+ HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY = 0x2E,
+ HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY = 0x2F,
};
enum hp_wmi_command {
@@ -216,42 +383,6 @@ enum hp_wireless2_bits {
HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD,
};
-enum hp_thermal_profile_omen_v0 {
- HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01,
- HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02,
-};
-
-enum hp_thermal_profile_omen_v1 {
- HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30,
- HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31,
- HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
-};
-
-enum hp_thermal_profile_omen_flags {
- HP_OMEN_EC_FLAGS_TURBO = 0x04,
- HP_OMEN_EC_FLAGS_NOTIMER = 0x02,
- HP_OMEN_EC_FLAGS_JUSTSET = 0x01,
-};
-
-enum hp_thermal_profile_victus {
- HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
- HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
-};
-
-enum hp_thermal_profile_victus_s {
- HP_VICTUS_S_THERMAL_PROFILE_DEFAULT = 0x00,
- HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE = 0x01,
-};
-
-enum hp_thermal_profile {
- HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
- HP_THERMAL_PROFILE_DEFAULT = 0x01,
- HP_THERMAL_PROFILE_COOL = 0x02,
- HP_THERMAL_PROFILE_QUIET = 0x03,
-};
-
#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)
#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
@@ -295,6 +426,11 @@ static const struct key_entry hp_wmi_keymap[] = {
{ KE_KEY, 0x21a9, { KEY_TOUCHPAD_OFF } },
{ KE_KEY, 0x121a9, { KEY_TOUCHPAD_ON } },
{ KE_KEY, 0x231b, { KEY_HELP } },
+ { KE_IGNORE, 0x21ab, }, /* FnLock on */
+ { KE_IGNORE, 0x121ab, }, /* FnLock off */
+ { KE_IGNORE, 0x30021aa, }, /* kbd backlight: level 2 -> off */
+ { KE_IGNORE, 0x33221aa, }, /* kbd backlight: off -> level 1 */
+ { KE_IGNORE, 0x36421aa, }, /* kbd backlight: level 1 -> level 2*/
{ KE_END, 0 }
};
@@ -339,6 +475,59 @@ static const char * const tablet_chassis_types[] = {
#define DEVICE_MODE_TABLET 0x06
+#define CPU_FAN 0
+#define GPU_FAN 1
+
+enum pwm_modes {
+ PWM_MODE_MAX = 0,
+ PWM_MODE_MANUAL = 1,
+ PWM_MODE_AUTO = 2,
+};
+
+struct hp_wmi_hwmon_priv {
+ struct mutex lock; /* protects mode, pwm */
+ u8 min_rpm;
+ u8 max_rpm;
+ int gpu_delta;
+ u8 mode;
+ u8 pwm;
+ struct delayed_work keep_alive_dwork;
+};
+
+struct victus_s_fan_table_header {
+ u8 num_fans;
+ u8 unknown;
+} __packed;
+
+struct victus_s_fan_table_entry {
+ u8 cpu_rpm;
+ u8 gpu_rpm;
+ u8 noise_db;
+} __packed;
+
+struct victus_s_fan_table {
+ struct victus_s_fan_table_header header;
+ struct victus_s_fan_table_entry entries[];
+} __packed;
+
+/*
+ * 90s delay to prevent the firmware from resetting fan mode after fixed
+ * 120s timeout
+ */
+#define KEEP_ALIVE_DELAY_SECS 90
+
+static inline u8 rpm_to_pwm(u8 rpm, struct hp_wmi_hwmon_priv *priv)
+{
+ return fixp_linear_interpolate(0, 0, priv->max_rpm, U8_MAX,
+ clamp_val(rpm, 0, priv->max_rpm));
+}
+
+static inline u8 pwm_to_rpm(u8 pwm, struct hp_wmi_hwmon_priv *priv)
+{
+ return fixp_linear_interpolate(0, 0, U8_MAX, priv->max_rpm,
+ clamp_val(pwm, 0, U8_MAX));
+}
+
/* map output size to the corresponding WMI method id */
static inline int encode_outsize_for_pvsz(int outsize)
{
@@ -628,41 +817,53 @@ static int hp_wmi_fan_speed_max_set(int enabled)
return enabled;
}
-static int hp_wmi_fan_speed_reset(void)
+static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed)
{
- u8 fan_speed[2] = { HP_FAN_SPEED_AUTOMATIC, HP_FAN_SPEED_AUTOMATIC };
- int ret;
-
- ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_SET_QUERY, HPWMI_GM,
- &fan_speed, sizeof(fan_speed), 0);
+ u8 fan_speed[2];
+ int gpu_speed, ret;
- return ret;
-}
+ fan_speed[CPU_FAN] = speed;
+ fan_speed[GPU_FAN] = speed;
-static int hp_wmi_fan_speed_max_reset(void)
-{
- int ret;
+ /*
+ * GPU fan speed is always a little higher than CPU fan speed, we fetch
+ * this delta value from the fan table during hwmon init.
+ * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to
+ * automatic mode.
+ */
+ if (speed != HP_FAN_SPEED_AUTOMATIC) {
+ gpu_speed = speed + priv->gpu_delta;
+ fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX);
+ }
+ ret = hp_wmi_get_fan_count_userdefine_trigger();
+ if (ret < 0)
+ return ret;
+ /* Max fans need to be explicitly disabled */
ret = hp_wmi_fan_speed_max_set(0);
- if (ret)
+ if (ret < 0)
return ret;
+ ret = hp_wmi_perform_query(HPWMI_VICTUS_S_FAN_SPEED_SET_QUERY, HPWMI_GM,
+ &fan_speed, sizeof(fan_speed), 0);
- /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
- ret = hp_wmi_fan_speed_reset();
return ret;
}
-static int hp_wmi_fan_speed_max_get(void)
+static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv)
{
- int val = 0, ret;
+ return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC);
+}
- ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM,
- &val, zero_if_sup(val), sizeof(val));
+static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv)
+{
+ int ret;
+ ret = hp_wmi_fan_speed_max_set(0);
if (ret)
- return ret < 0 ? ret : -EINVAL;
+ return ret;
- return val;
+ /* Disabling max fan speed on Victus s1xxx laptops needs a 2nd step: */
+ return hp_wmi_fan_speed_reset(priv);
}
static int __init hp_wmi_bios_2008_later(void)
@@ -981,6 +1182,9 @@ static void hp_wmi_notify(union acpi_object *obj, void *context)
key_code, 1, true))
pr_info("Unknown key code - 0x%x\n", key_code);
break;
+ case HPWMI_FN_P_HOTKEY:
+ platform_profile_cycle();
+ break;
case HPWMI_OMEN_KEY:
if (event_data) /* Only should be true for HP Omen */
key_code = event_data;
@@ -1569,15 +1773,8 @@ static int platform_profile_victus_set_ec(enum platform_profile_option profile)
static bool is_victus_s_thermal_profile(void)
{
- const char *board_name;
-
- board_name = dmi_get_system_info(DMI_BOARD_NAME);
- if (!board_name)
- return false;
-
- return match_string(victus_s_thermal_profile_boards,
- ARRAY_SIZE(victus_s_thermal_profile_boards),
- board_name) >= 0;
+ /* Initialised in driver init, hence safe to use here */
+ return is_victus_s_board;
}
static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable,
@@ -1658,27 +1855,87 @@ static int victus_s_set_cpu_pl1_pl2(u8 pl1, u8 pl2)
return ret;
}
+static int platform_profile_victus_s_get_ec(enum platform_profile_option *profile)
+{
+ int ret = 0;
+ bool current_ctgp_state, current_ppab_state;
+ u8 current_dstate, current_gpu_slowdown_temp, tp;
+ const struct thermal_profile_params *params;
+
+ params = active_thermal_profile_params;
+ if (params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN ||
+ params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) {
+ *profile = active_platform_profile;
+ return 0;
+ }
+
+ ret = ec_read(params->ec_tp_offset, &tp);
+ if (ret)
+ return ret;
+
+ /*
+ * We cannot use active_thermal_profile_params here, because boards
+ * like 8C78 have tp == 0x0 || tp == 0x1 after cold boot, but logically
+ * it should have tp == 0x30 || tp == 0x31, as corrected by the Omen
+ * Gaming Hub on windows. Hence accept both of these values.
+ */
+ if (tp == victus_s_thermal_params.performance ||
+ tp == omen_v1_thermal_params.performance) {
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ } else if (tp == victus_s_thermal_params.balanced ||
+ tp == omen_v1_thermal_params.balanced) {
+ /*
+ * Since both PLATFORM_PROFILE_LOW_POWER and
+ * PLATFORM_PROFILE_BALANCED share the same thermal profile
+ * parameter value, hence to differentiate between them, we
+ * query the GPU CTGP and PPAB states and compare based off of
+ * that.
+ */
+ ret = victus_s_gpu_thermal_profile_get(&current_ctgp_state,
+ &current_ppab_state,
+ &current_dstate,
+ &current_gpu_slowdown_temp);
+ if (ret < 0)
+ return ret;
+ if (current_ctgp_state == 0 && current_ppab_state == 0)
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ else if (current_ctgp_state == 0 && current_ppab_state == 1)
+ *profile = PLATFORM_PROFILE_BALANCED;
+ else
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int platform_profile_victus_s_set_ec(enum platform_profile_option profile)
{
+ struct thermal_profile_params *params;
bool gpu_ctgp_enable, gpu_ppab_enable;
u8 gpu_dstate; /* Test shows 1 = 100%, 2 = 50%, 3 = 25%, 4 = 12.5% */
int err, tp;
+ params = active_thermal_profile_params;
+ if (!params)
+ return -ENODEV;
+
switch (profile) {
case PLATFORM_PROFILE_PERFORMANCE:
- tp = HP_VICTUS_S_THERMAL_PROFILE_PERFORMANCE;
+ tp = params->performance;
gpu_ctgp_enable = true;
gpu_ppab_enable = true;
gpu_dstate = 1;
break;
case PLATFORM_PROFILE_BALANCED:
- tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
+ tp = params->balanced;
gpu_ctgp_enable = false;
gpu_ppab_enable = true;
gpu_dstate = 1;
break;
case PLATFORM_PROFILE_LOW_POWER:
- tp = HP_VICTUS_S_THERMAL_PROFILE_DEFAULT;
+ tp = params->low_power;
gpu_ctgp_enable = false;
gpu_ppab_enable = false;
gpu_dstate = 1;
@@ -1820,6 +2077,7 @@ static int victus_s_powersource_event(struct notifier_block *nb,
void *data)
{
struct acpi_bus_event *event_entry = data;
+ enum platform_profile_option actual_profile;
int err;
if (strcmp(event_entry->device_class, ACPI_AC_CLASS) != 0)
@@ -1827,6 +2085,17 @@ static int victus_s_powersource_event(struct notifier_block *nb,
pr_debug("Received power source device event\n");
+ guard(mutex)(&active_platform_profile_lock);
+ err = platform_profile_victus_s_get_ec(&actual_profile);
+ if (err < 0) {
+ /*
+ * Although we failed to get the current platform profile, we
+ * still want the other event consumers to process it.
+ */
+ pr_warn("Failed to read current platform profile (%d)\n", err);
+ return NOTIFY_DONE;
+ }
+
/*
* Switching to battery power source while Performance mode is active
* needs manual triggering of CPU power limits. Same goes when switching
@@ -1835,7 +2104,7 @@ static int victus_s_powersource_event(struct notifier_block *nb,
* Seen on HP 16-s1034nf (board 8C9C) with F.11 and F.13 BIOS versions.
*/
- if (active_platform_profile == PLATFORM_PROFILE_PERFORMANCE) {
+ if (actual_profile == PLATFORM_PROFILE_PERFORMANCE) {
pr_debug("Triggering CPU PL1/PL2 actualization\n");
err = victus_s_set_cpu_pl1_pl2(HP_POWER_LIMIT_DEFAULT,
HP_POWER_LIMIT_DEFAULT);
@@ -1946,11 +2215,23 @@ static int thermal_profile_setup(struct platform_device *device)
ops = &platform_profile_victus_ops;
} else if (is_victus_s_thermal_profile()) {
/*
- * Being unable to retrieve laptop's current thermal profile,
- * during this setup, we set it to Balanced by default.
+ * For an unknown EC layout board, platform_profile_victus_s_get_ec(),
+ * behaves like a wrapper around active_platform_profile, to avoid using
+ * uninitialized data, we default to PLATFORM_PROFILE_BALANCED.
*/
- active_platform_profile = PLATFORM_PROFILE_BALANCED;
+ if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN ||
+ active_thermal_profile_params->ec_tp_offset == HP_NO_THERMAL_PROFILE_OFFSET) {
+ active_platform_profile = PLATFORM_PROFILE_BALANCED;
+ } else {
+ err = platform_profile_victus_s_get_ec(&active_platform_profile);
+ if (err < 0)
+ return err;
+ }
+ /*
+ * call thermal profile write command to ensure that the
+ * firmware correctly sets the OEM variables
+ */
err = platform_profile_victus_s_set_ec(active_platform_profile);
if (err < 0)
return err;
@@ -2019,6 +2300,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
static void __exit hp_wmi_bios_remove(struct platform_device *device)
{
int i;
+ struct hp_wmi_hwmon_priv *priv;
for (i = 0; i < rfkill2_count; i++) {
rfkill_unregister(rfkill2[i].rfkill);
@@ -2037,6 +2319,10 @@ static void __exit hp_wmi_bios_remove(struct platform_device *device)
rfkill_unregister(wwan_rfkill);
rfkill_destroy(wwan_rfkill);
}
+
+ priv = platform_get_drvdata(device);
+ if (priv)
+ cancel_delayed_work_sync(&priv->keep_alive_dwork);
}
static int hp_wmi_resume_handler(struct device *device)
@@ -2096,12 +2382,59 @@ static struct platform_driver hp_wmi_driver __refdata = {
.remove = __exit_p(hp_wmi_bios_remove),
};
+static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv)
+{
+ int ret;
+
+ switch (priv->mode) {
+ case PWM_MODE_MAX:
+ if (is_victus_s_thermal_profile()) {
+ ret = hp_wmi_get_fan_count_userdefine_trigger();
+ if (ret < 0)
+ return ret;
+ }
+ ret = hp_wmi_fan_speed_max_set(1);
+ if (ret < 0)
+ return ret;
+ mod_delayed_work(system_wq, &priv->keep_alive_dwork,
+ secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
+ return 0;
+ case PWM_MODE_MANUAL:
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv));
+ if (ret < 0)
+ return ret;
+ mod_delayed_work(system_wq, &priv->keep_alive_dwork,
+ secs_to_jiffies(KEEP_ALIVE_DELAY_SECS));
+ return 0;
+ case PWM_MODE_AUTO:
+ if (is_victus_s_thermal_profile()) {
+ ret = hp_wmi_get_fan_count_userdefine_trigger();
+ if (ret < 0)
+ return ret;
+ ret = hp_wmi_fan_speed_max_reset(priv);
+ } else {
+ ret = hp_wmi_fan_speed_max_set(0);
+ }
+ if (ret < 0)
+ return ret;
+ cancel_delayed_work(&priv->keep_alive_dwork);
+ return 0;
+ default:
+ /* shouldn't happen */
+ return -EINVAL;
+ }
+}
+
static umode_t hp_wmi_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_pwm:
+ if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile())
+ return 0;
return 0644;
case hwmon_fan:
if (is_victus_s_thermal_profile()) {
@@ -2122,8 +2455,11 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data,
static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
- int ret;
+ struct hp_wmi_hwmon_priv *priv;
+ int rpm, ret;
+ u8 mode;
+ priv = dev_get_drvdata(dev);
switch (type) {
case hwmon_fan:
if (is_victus_s_thermal_profile())
@@ -2135,16 +2471,23 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
*val = ret;
return 0;
case hwmon_pwm:
- switch (hp_wmi_fan_speed_max_get()) {
- case 0:
- /* 0 is automatic fan, which is 2 for hwmon */
- *val = 2;
+ if (attr == hwmon_pwm_input) {
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+
+ rpm = hp_wmi_get_fan_speed_victus_s(channel);
+ if (rpm < 0)
+ return rpm;
+ *val = rpm_to_pwm(rpm / 100, priv);
return 0;
- case 1:
- /* 1 is max fan, which is 0
- * (no fan speed control) for hwmon
- */
- *val = 0;
+ }
+ scoped_guard(mutex, &priv->lock)
+ mode = priv->mode;
+ switch (mode) {
+ case PWM_MODE_MAX:
+ case PWM_MODE_MANUAL:
+ case PWM_MODE_AUTO:
+ *val = mode;
return 0;
default:
/* shouldn't happen */
@@ -2158,23 +2501,47 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
+ struct hp_wmi_hwmon_priv *priv;
+ int rpm;
+
+ priv = dev_get_drvdata(dev);
+ guard(mutex)(&priv->lock);
switch (type) {
case hwmon_pwm:
+ if (attr == hwmon_pwm_input) {
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ /* PWM input is invalid when not in manual mode */
+ if (priv->mode != PWM_MODE_MANUAL)
+ return -EINVAL;
+
+ /* ensure PWM input is within valid fan speeds */
+ rpm = pwm_to_rpm(val, priv);
+ rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm);
+ priv->pwm = rpm_to_pwm(rpm, priv);
+ return hp_wmi_apply_fan_settings(priv);
+ }
switch (val) {
- case 0:
- if (is_victus_s_thermal_profile())
- hp_wmi_get_fan_count_userdefine_trigger();
- /* 0 is no fan speed control (max), which is 1 for us */
- return hp_wmi_fan_speed_max_set(1);
- case 2:
- /* 2 is automatic speed control, which is 0 for us */
- if (is_victus_s_thermal_profile()) {
- hp_wmi_get_fan_count_userdefine_trigger();
- return hp_wmi_fan_speed_max_reset();
- } else
- return hp_wmi_fan_speed_max_set(0);
+ case PWM_MODE_MAX:
+ priv->mode = PWM_MODE_MAX;
+ return hp_wmi_apply_fan_settings(priv);
+ case PWM_MODE_MANUAL:
+ if (!is_victus_s_thermal_profile())
+ return -EOPNOTSUPP;
+ /*
+ * When switching to manual mode, set fan speed to
+ * current RPM values to ensure a smooth transition.
+ */
+ rpm = hp_wmi_get_fan_speed_victus_s(channel);
+ if (rpm < 0)
+ return rpm;
+ priv->pwm = rpm_to_pwm(rpm / 100, priv);
+ priv->mode = PWM_MODE_MANUAL;
+ return hp_wmi_apply_fan_settings(priv);
+ case PWM_MODE_AUTO:
+ priv->mode = PWM_MODE_AUTO;
+ return hp_wmi_apply_fan_settings(priv);
default:
- /* we don't support manual fan speed control */
return -EINVAL;
}
default:
@@ -2184,7 +2551,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
static const struct hwmon_channel_info * const info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT),
NULL
};
@@ -2199,12 +2566,105 @@ static const struct hwmon_chip_info chip_info = {
.info = info,
};
+static void hp_wmi_hwmon_keep_alive_handler(struct work_struct *work)
+{
+ struct delayed_work *dwork;
+ struct hp_wmi_hwmon_priv *priv;
+ int ret;
+
+ dwork = to_delayed_work(work);
+ priv = container_of(dwork, struct hp_wmi_hwmon_priv, keep_alive_dwork);
+
+ guard(mutex)(&priv->lock);
+ /*
+ * Re-apply the current hwmon context settings.
+ * NOTE: hp_wmi_apply_fan_settings will handle the re-scheduling.
+ */
+ ret = hp_wmi_apply_fan_settings(priv);
+ if (ret)
+ pr_warn_ratelimited("keep-alive failed to refresh fan settings: %d\n",
+ ret);
+}
+
+static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv)
+{
+ u8 fan_data[128] = { 0 };
+ struct victus_s_fan_table *fan_table;
+ u8 min_rpm, max_rpm;
+ u8 cpu_rpm, gpu_rpm, noise_db;
+ int gpu_delta, i, num_entries, ret;
+ size_t header_size, entry_size;
+
+ /* Default behaviour on hwmon init is automatic mode */
+ priv->mode = PWM_MODE_AUTO;
+
+ /* Bypass all non-Victus S devices */
+ if (!is_victus_s_thermal_profile())
+ return 0;
+
+ ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY,
+ HPWMI_GM, &fan_data, 4, sizeof(fan_data));
+ if (ret)
+ return ret;
+
+ fan_table = (struct victus_s_fan_table *)fan_data;
+ if (fan_table->header.num_fans == 0)
+ return -EINVAL;
+
+ header_size = sizeof(struct victus_s_fan_table_header);
+ entry_size = sizeof(struct victus_s_fan_table_entry);
+ num_entries = (sizeof(fan_data) - header_size) / entry_size;
+ min_rpm = U8_MAX;
+ max_rpm = 0;
+
+ for (i = 0 ; i < num_entries ; i++) {
+ cpu_rpm = fan_table->entries[i].cpu_rpm;
+ gpu_rpm = fan_table->entries[i].gpu_rpm;
+ noise_db = fan_table->entries[i].noise_db;
+
+ /*
+ * On some devices, the fan table is truncated with an all-zero row,
+ * hence we stop parsing here.
+ */
+ if (cpu_rpm == 0 && gpu_rpm == 0 && noise_db == 0)
+ break;
+
+ if (cpu_rpm < min_rpm)
+ min_rpm = cpu_rpm;
+ if (cpu_rpm > max_rpm)
+ max_rpm = cpu_rpm;
+ }
+
+ if (min_rpm == U8_MAX || max_rpm == 0)
+ return -EINVAL;
+
+ gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm;
+ priv->min_rpm = min_rpm;
+ priv->max_rpm = max_rpm;
+ priv->gpu_delta = gpu_delta;
+
+ return 0;
+}
+
static int hp_wmi_hwmon_init(void)
{
struct device *dev = &hp_wmi_platform_dev->dev;
+ struct hp_wmi_hwmon_priv *priv;
struct device *hwmon;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(dev, &priv->lock);
+ if (ret)
+ return ret;
- hwmon = devm_hwmon_device_register_with_info(dev, "hp", &hp_wmi_driver,
+ ret = hp_wmi_setup_fan_settings(priv);
+ if (ret)
+ return ret;
+ hwmon = devm_hwmon_device_register_with_info(dev, "hp", priv,
&chip_info, NULL);
if (IS_ERR(hwmon)) {
@@ -2212,9 +2672,39 @@ static int hp_wmi_hwmon_init(void)
return PTR_ERR(hwmon);
}
+ INIT_DELAYED_WORK(&priv->keep_alive_dwork, hp_wmi_hwmon_keep_alive_handler);
+ platform_set_drvdata(hp_wmi_platform_dev, priv);
+ ret = hp_wmi_apply_fan_settings(priv);
+ if (ret)
+ dev_warn(dev, "Failed to apply initial fan settings: %d\n", ret);
+
return 0;
}
+static void __init setup_active_thermal_profile_params(void)
+{
+ const struct dmi_system_id *id;
+
+ /*
+ * Currently only victus_s devices use the
+ * active_thermal_profile_params
+ */
+ id = dmi_first_match(victus_s_thermal_profile_boards);
+ if (id) {
+ /*
+ * Marking this boolean is required to ensure that
+ * is_victus_s_thermal_profile() behaves like a valid
+ * wrapper.
+ */
+ is_victus_s_board = true;
+ active_thermal_profile_params = id->driver_data;
+ if (active_thermal_profile_params->ec_tp_offset == HP_EC_OFFSET_UNKNOWN) {
+ pr_warn("Unknown EC layout for board %s. Thermal profile readback will be disabled. Please report this to platform-driver-x86@vger.kernel.org\n",
+ dmi_get_system_info(DMI_BOARD_NAME));
+ }
+ }
+}
+
static int __init hp_wmi_init(void)
{
int event_capable = wmi_has_guid(HPWMI_EVENT_GUID);
@@ -2242,6 +2732,11 @@ static int __init hp_wmi_init(void)
goto err_destroy_input;
}
+ /*
+ * Setup active board's thermal profile parameters before
+ * starting platform driver probe.
+ */
+ setup_active_thermal_profile_params();
err = platform_driver_probe(&hp_wmi_driver, hp_wmi_bios_setup);
if (err)
goto err_unregister_device;
diff --git a/drivers/platform/x86/hp/hp_accel.c b/drivers/platform/x86/hp/hp_accel.c
index 10d5af18d639..39b73dc473f1 100644
--- a/drivers/platform/x86/hp/hp_accel.c
+++ b/drivers/platform/x86/hp/hp_accel.c
@@ -300,6 +300,9 @@ static int lis3lv02d_probe(struct platform_device *device)
int ret;
lis3_dev.bus_priv = ACPI_COMPANION(&device->dev);
+ if (!lis3_dev.bus_priv)
+ return -ENODEV;
+
lis3_dev.init = lis3lv02d_acpi_init;
lis3_dev.read = lis3lv02d_acpi_read;
lis3_dev.write = lis3lv02d_acpi_write;
diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
index c3772df34679..93cca17fdf58 100644
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -81,6 +81,10 @@ static const struct key_entry huawei_wmi_keymap[] = {
{ KE_KEY, 0x289, { KEY_WLAN } },
// Huawei |M| key
{ KE_KEY, 0x28a, { KEY_CONFIG } },
+ // HONOR YOYO key
+ { KE_KEY, 0x28b, { KEY_NOTIFICATION_CENTER } },
+ // HONOR print screen
+ { KE_KEY, 0x28e, { KEY_PRINT } },
// Keyboard backlit
{ KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } },
{ KE_IGNORE, 0x294, { KEY_KBDILLUMUP } },
@@ -850,7 +854,7 @@ static __init int huawei_wmi_init(void)
struct platform_device *pdev;
int err;
- huawei_wmi = kzalloc(sizeof(struct huawei_wmi), GFP_KERNEL);
+ huawei_wmi = kzalloc_obj(struct huawei_wmi);
if (!huawei_wmi)
return -ENOMEM;
diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c
index 231b37909801..139956168cf9 100644
--- a/drivers/platform/x86/ibm_rtl.c
+++ b/drivers/platform/x86/ibm_rtl.c
@@ -273,7 +273,7 @@ static int __init ibm_rtl_init(void) {
/* search for the _RTL_ signature at the start of the table */
for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
struct ibm_rtl_table __iomem * tmp;
- tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i);
+ tmp = (struct ibm_rtl_table __iomem *) (ebda_map + i*sizeof(unsigned int));
if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
phys_addr_t addr;
unsigned int plen;
diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig
index 19a2246f2770..2900407d6095 100644
--- a/drivers/platform/x86/intel/Kconfig
+++ b/drivers/platform/x86/intel/Kconfig
@@ -41,6 +41,19 @@ config INTEL_VBTN
To compile this driver as a module, choose M here: the module will
be called intel_vbtn.
+config INTEL_EHL_PSE_IO
+ tristate "Intel Elkhart Lake PSE I/O driver"
+ depends on PCI
+ select AUXILIARY_BUS
+ help
+ Select this option to enable Intel Elkhart Lake PSE GPIO and Timed
+ I/O support. This driver enumerates the PCI parent device and
+ creates auxiliary child devices for these capabilities. The actual
+ functionalities are provided by their respective auxiliary drivers.
+
+ To compile this driver as a module, choose M here: the module will
+ be called intel_ehl_pse_io.
+
config INTEL_INT0002_VGPIO
tristate "Intel ACPI INT0002 Virtual GPIO driver"
depends on GPIOLIB && ACPI && PM_SLEEP
diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile
index 78acb414e154..138b13756158 100644
--- a/drivers/platform/x86/intel/Makefile
+++ b/drivers/platform/x86/intel/Makefile
@@ -21,6 +21,7 @@ intel-target-$(CONFIG_INTEL_HID_EVENT) += hid.o
intel-target-$(CONFIG_INTEL_VBTN) += vbtn.o
# Intel miscellaneous drivers
+intel-target-$(CONFIG_INTEL_EHL_PSE_IO) += ehl_pse_io.o
intel-target-$(CONFIG_INTEL_INT0002_VGPIO) += int0002_vgpio.o
intel-target-$(CONFIG_INTEL_ISHTP_ECLITE) += ishtp_eclite.o
intel-target-$(CONFIG_INTEL_OAKTRAIL) += oaktrail.o
diff --git a/drivers/platform/x86/intel/chtwc_int33fe.c b/drivers/platform/x86/intel/chtwc_int33fe.c
index 29e8b5432f4c..d183aa53c318 100644
--- a/drivers/platform/x86/intel/chtwc_int33fe.c
+++ b/drivers/platform/x86/intel/chtwc_int33fe.c
@@ -77,7 +77,7 @@ static const struct software_node max17047_node = {
* software node.
*/
static struct software_node_ref_args fusb302_mux_refs[] = {
- { .node = NULL },
+ SOFTWARE_NODE_REFERENCE(NULL),
};
static const struct property_entry fusb302_properties[] = {
@@ -190,11 +190,6 @@ static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data)
{
software_node_unregister_node_group(node_group);
- if (fusb302_mux_refs[0].node) {
- fwnode_handle_put(software_node_fwnode(fusb302_mux_refs[0].node));
- fusb302_mux_refs[0].node = NULL;
- }
-
if (data->dp) {
data->dp->secondary = NULL;
fwnode_handle_put(data->dp);
@@ -202,7 +197,15 @@ static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data)
}
}
-static int cht_int33fe_add_nodes(struct cht_int33fe_data *data)
+static void cht_int33fe_put_swnode(void *data)
+{
+ struct fwnode_handle *fwnode = data;
+
+ fwnode_handle_put(fwnode);
+ fusb302_mux_refs[0] = SOFTWARE_NODE_REFERENCE(NULL);
+}
+
+static int cht_int33fe_add_nodes(struct device *dev, struct cht_int33fe_data *data)
{
const struct software_node *mux_ref_node;
int ret;
@@ -212,17 +215,25 @@ static int cht_int33fe_add_nodes(struct cht_int33fe_data *data)
* until the mux driver has created software node for the mux device.
* It means we depend on the mux driver. This function will return
* -EPROBE_DEFER until the mux device is registered.
+ *
+ * FIXME: the relevant software node exists in intel-xhci-usb-role-switch
+ * and - if exported - could be used to set up a static reference.
*/
mux_ref_node = software_node_find_by_name(NULL, "intel-xhci-usb-sw");
if (!mux_ref_node)
return -EPROBE_DEFER;
+ ret = devm_add_action_or_reset(dev, cht_int33fe_put_swnode,
+ software_node_fwnode(mux_ref_node));
+ if (ret)
+ return ret;
+
/*
* Update node used in "usb-role-switch" property. Note that we
* rely on software_node_register_node_group() to use the original
* instance of properties instead of copying them.
*/
- fusb302_mux_refs[0].node = mux_ref_node;
+ fusb302_mux_refs[0] = SOFTWARE_NODE_REFERENCE(mux_ref_node);
ret = software_node_register_node_group(node_group);
if (ret)
@@ -345,7 +356,7 @@ static int cht_int33fe_typec_probe(struct platform_device *pdev)
return fusb302_irq;
}
- ret = cht_int33fe_add_nodes(data);
+ ret = cht_int33fe_add_nodes(dev, data);
if (ret)
return ret;
diff --git a/drivers/platform/x86/intel/ehl_pse_io.c b/drivers/platform/x86/intel/ehl_pse_io.c
new file mode 100644
index 000000000000..861e14808b35
--- /dev/null
+++ b/drivers/platform/x86/intel/ehl_pse_io.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Elkhart Lake Programmable Service Engine (PSE) I/O
+ *
+ * Copyright (c) 2025 Intel Corporation.
+ *
+ * Author: Raag Jadav <raag.jadav@intel.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device/devres.h>
+#include <linux/errno.h>
+#include <linux/gfp_types.h>
+#include <linux/ioport.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+#include <linux/ehl_pse_io_aux.h>
+
+#define EHL_PSE_IO_DEV_SIZE SZ_4K
+
+static int ehl_pse_io_dev_create(struct pci_dev *pci, const char *name, int idx)
+{
+ struct device *dev = &pci->dev;
+ struct auxiliary_device *adev;
+ struct ehl_pse_io_data *data;
+ resource_size_t start, offset;
+ u32 id;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ id = (pci_domain_nr(pci->bus) << 16) | pci_dev_id(pci);
+ start = pci_resource_start(pci, 0);
+ offset = EHL_PSE_IO_DEV_SIZE * idx;
+
+ data->mem = DEFINE_RES_MEM(start + offset, EHL_PSE_IO_DEV_SIZE);
+ data->irq = pci_irq_vector(pci, idx);
+
+ adev = __devm_auxiliary_device_create(dev, EHL_PSE_IO_NAME, name, data, id);
+
+ return adev ? 0 : -ENODEV;
+}
+
+static int ehl_pse_io_probe(struct pci_dev *pci, const struct pci_device_id *id)
+{
+ int ret;
+
+ ret = pcim_enable_device(pci);
+ if (ret)
+ return ret;
+
+ pci_set_master(pci);
+
+ ret = pci_alloc_irq_vectors(pci, 2, 2, PCI_IRQ_MSI);
+ if (ret < 0)
+ return ret;
+
+ ret = ehl_pse_io_dev_create(pci, EHL_PSE_GPIO_NAME, 0);
+ if (ret)
+ return ret;
+
+ return ehl_pse_io_dev_create(pci, EHL_PSE_TIO_NAME, 1);
+}
+
+static const struct pci_device_id ehl_pse_io_ids[] = {
+ { PCI_VDEVICE(INTEL, 0x4b88) },
+ { PCI_VDEVICE(INTEL, 0x4b89) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, ehl_pse_io_ids);
+
+static struct pci_driver ehl_pse_io_driver = {
+ .name = EHL_PSE_IO_NAME,
+ .id_table = ehl_pse_io_ids,
+ .probe = ehl_pse_io_probe,
+};
+module_pci_driver(ehl_pse_io_driver);
+
+MODULE_AUTHOR("Raag Jadav <raag.jadav@intel.com>");
+MODULE_DESCRIPTION("Intel Elkhart Lake PSE I/O driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c
index 88a1a9ff2f34..085093506dda 100644
--- a/drivers/platform/x86/intel/hid.c
+++ b/drivers/platform/x86/intel/hid.c
@@ -44,16 +44,19 @@ MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alex Hung");
static const struct acpi_device_id intel_hid_ids[] = {
- {"INT33D5", 0},
- {"INTC1051", 0},
- {"INTC1054", 0},
- {"INTC1070", 0},
- {"INTC1076", 0},
- {"INTC1077", 0},
- {"INTC1078", 0},
- {"INTC107B", 0},
- {"INTC10CB", 0},
- {"", 0},
+ { "INT33D5" },
+ { "INTC1051" },
+ { "INTC1054" },
+ { "INTC1070" },
+ { "INTC1076" },
+ { "INTC1077" },
+ { "INTC1078" },
+ { "INTC107B" },
+ { "INTC10CB" },
+ { "INTC10CC" },
+ { "INTC10F1" },
+ { "INTC10F2" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, intel_hid_ids);
@@ -133,6 +136,13 @@ static const struct dmi_system_id button_array_table[] = {
},
},
{
+ .ident = "Lenovo ThinkPad X1 Fold 16 Gen 1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Fold 16 Gen 1"),
+ },
+ },
+ {
.ident = "Microsoft Surface Go 3",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
@@ -174,6 +184,30 @@ static const struct dmi_system_id dmi_vgbs_allow_list[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "HP Elite Dragonfly G2 Notebook PC"),
},
},
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Dell Pro Rugged 10 Tablet RA00260"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Dell Pro Rugged 12 Tablet RA02260"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Dell 14 Plus 2-in-1 DB04250"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Dell 16 Plus 2-in-1 DB06250"),
+ },
+ },
{ }
};
@@ -404,6 +438,14 @@ static int intel_hid_pl_suspend_handler(struct device *device)
return 0;
}
+static int intel_hid_pl_freeze_handler(struct device *device)
+{
+ struct intel_hid_priv *priv = dev_get_drvdata(device);
+
+ priv->wakeup_mode = false;
+ return intel_hid_pl_suspend_handler(device);
+}
+
static int intel_hid_pl_resume_handler(struct device *device)
{
intel_hid_pm_complete(device);
@@ -418,7 +460,7 @@ static int intel_hid_pl_resume_handler(struct device *device)
static const struct dev_pm_ops intel_hid_pl_pm_ops = {
.prepare = intel_hid_pm_prepare,
.complete = intel_hid_pm_complete,
- .freeze = intel_hid_pl_suspend_handler,
+ .freeze = intel_hid_pl_freeze_handler,
.thaw = intel_hid_pl_resume_handler,
.restore = intel_hid_pl_resume_handler,
.suspend = intel_hid_pl_suspend_handler,
@@ -646,12 +688,16 @@ static bool button_array_present(struct platform_device *device)
static int intel_hid_probe(struct platform_device *device)
{
- acpi_handle handle = ACPI_HANDLE(&device->dev);
unsigned long long mode, dummy;
struct intel_hid_priv *priv;
+ acpi_handle handle;
acpi_status status;
int err;
+ handle = ACPI_HANDLE(&device->dev);
+ if (!handle)
+ return -ENODEV;
+
intel_hid_init_dsm(handle);
if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) {
@@ -764,43 +810,4 @@ static struct platform_driver intel_hid_pl_driver = {
.remove = intel_hid_remove,
};
-/*
- * Unfortunately, some laptops provide a _HID="INT33D5" device with
- * _CID="PNP0C02". This causes the pnpacpi scan driver to claim the
- * ACPI node, so no platform device will be created. The pnpacpi
- * driver rejects this device in subsequent processing, so no physical
- * node is created at all.
- *
- * As a workaround until the ACPI core figures out how to handle
- * this corner case, manually ask the ACPI platform device code to
- * claim the ACPI node.
- */
-static acpi_status __init
-check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
-{
- const struct acpi_device_id *ids = context;
- struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
-
- if (dev && acpi_match_device_ids(dev, ids) == 0)
- if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
- dev_info(&dev->dev,
- "intel-hid: created platform device\n");
-
- return AE_OK;
-}
-
-static int __init intel_hid_init(void)
-{
- acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
- ACPI_UINT32_MAX, check_acpi_dev, NULL,
- (void *)intel_hid_ids, NULL);
-
- return platform_driver_register(&intel_hid_pl_driver);
-}
-module_init(intel_hid_init);
-
-static void __exit intel_hid_exit(void)
-{
- platform_driver_unregister(&intel_hid_pl_driver);
-}
-module_exit(intel_hid_exit);
+module_platform_driver(intel_hid_pl_driver);
diff --git a/drivers/platform/x86/intel/ifs/core.c b/drivers/platform/x86/intel/ifs/core.c
index 1ae50702bdb7..3dafa28a1010 100644
--- a/drivers/platform/x86/intel/ifs/core.c
+++ b/drivers/platform/x86/intel/ifs/core.c
@@ -8,6 +8,7 @@
#include <linux/slab.h>
#include <asm/cpu_device_id.h>
+#include <asm/msr.h>
#include "ifs.h"
@@ -115,16 +116,16 @@ static int __init ifs_init(void)
if (!m)
return -ENODEV;
- if (rdmsrl_safe(MSR_IA32_CORE_CAPS, &msrval))
+ if (rdmsrq_safe(MSR_IA32_CORE_CAPS, &msrval))
return -ENODEV;
if (!(msrval & MSR_IA32_CORE_CAPS_INTEGRITY_CAPS))
return -ENODEV;
- if (rdmsrl_safe(MSR_INTEGRITY_CAPS, &msrval))
+ if (rdmsrq_safe(MSR_INTEGRITY_CAPS, &msrval))
return -ENODEV;
- ifs_pkg_auth = kmalloc_array(topology_max_packages(), sizeof(bool), GFP_KERNEL);
+ ifs_pkg_auth = kmalloc_objs(bool, topology_max_packages());
if (!ifs_pkg_auth)
return -ENOMEM;
diff --git a/drivers/platform/x86/intel/ifs/load.c b/drivers/platform/x86/intel/ifs/load.c
index de54bd1a5970..50f1fdf7dfed 100644
--- a/drivers/platform/x86/intel/ifs/load.c
+++ b/drivers/platform/x86/intel/ifs/load.c
@@ -5,6 +5,7 @@
#include <linux/sizes.h>
#include <asm/cpu.h>
#include <asm/microcode.h>
+#include <asm/msr.h>
#include "ifs.h"
@@ -127,8 +128,8 @@ static void copy_hashes_authenticate_chunks(struct work_struct *work)
ifsd = ifs_get_data(dev);
msrs = ifs_get_test_msrs(dev);
/* run scan hash copy */
- wrmsrl(msrs->copy_hashes, ifs_hash_ptr);
- rdmsrl(msrs->copy_hashes_status, hashes_status.data);
+ wrmsrq(msrs->copy_hashes, ifs_hash_ptr);
+ rdmsrq(msrs->copy_hashes_status, hashes_status.data);
/* enumerate the scan image information */
num_chunks = hashes_status.num_chunks;
@@ -149,8 +150,8 @@ static void copy_hashes_authenticate_chunks(struct work_struct *work)
linear_addr = base + i * chunk_size;
linear_addr |= i;
- wrmsrl(msrs->copy_chunks, linear_addr);
- rdmsrl(msrs->copy_chunks_status, chunk_status.data);
+ wrmsrq(msrs->copy_chunks, linear_addr);
+ rdmsrq(msrs->copy_chunks_status, chunk_status.data);
ifsd->valid_chunks = chunk_status.valid_chunks;
err_code = chunk_status.error_code;
@@ -195,8 +196,8 @@ static int copy_hashes_authenticate_chunks_gen2(struct device *dev)
msrs = ifs_get_test_msrs(dev);
if (need_copy_scan_hashes(ifsd)) {
- wrmsrl(msrs->copy_hashes, ifs_hash_ptr);
- rdmsrl(msrs->copy_hashes_status, hashes_status.data);
+ wrmsrq(msrs->copy_hashes, ifs_hash_ptr);
+ rdmsrq(msrs->copy_hashes_status, hashes_status.data);
/* enumerate the scan image information */
chunk_size = hashes_status.chunk_size * SZ_1K;
@@ -216,8 +217,8 @@ static int copy_hashes_authenticate_chunks_gen2(struct device *dev)
}
if (ifsd->generation >= IFS_GEN_STRIDE_AWARE) {
- wrmsrl(msrs->test_ctrl, INVALIDATE_STRIDE);
- rdmsrl(msrs->copy_chunks_status, chunk_status.data);
+ wrmsrq(msrs->test_ctrl, INVALIDATE_STRIDE);
+ rdmsrq(msrs->copy_chunks_status, chunk_status.data);
if (chunk_status.valid_chunks != 0) {
dev_err(dev, "Couldn't invalidate installed stride - %d\n",
chunk_status.valid_chunks);
@@ -238,9 +239,9 @@ static int copy_hashes_authenticate_chunks_gen2(struct device *dev)
chunk_table[1] = linear_addr;
do {
local_irq_disable();
- wrmsrl(msrs->copy_chunks, (u64)chunk_table);
+ wrmsrq(msrs->copy_chunks, (u64)chunk_table);
local_irq_enable();
- rdmsrl(msrs->copy_chunks_status, chunk_status.data);
+ rdmsrq(msrs->copy_chunks_status, chunk_status.data);
err_code = chunk_status.error_code;
} while (err_code == AUTH_INTERRUPTED_ERROR && --retry_count);
diff --git a/drivers/platform/x86/intel/ifs/runtest.c b/drivers/platform/x86/intel/ifs/runtest.c
index f978dd05d4d8..dfc119d7354d 100644
--- a/drivers/platform/x86/intel/ifs/runtest.c
+++ b/drivers/platform/x86/intel/ifs/runtest.c
@@ -7,6 +7,7 @@
#include <linux/nmi.h>
#include <linux/slab.h>
#include <linux/stop_machine.h>
+#include <asm/msr.h>
#include "ifs.h"
@@ -209,8 +210,8 @@ static int doscan(void *data)
* take up to 200 milliseconds (in the case where all chunks
* are processed in a single pass) before it retires.
*/
- wrmsrl(MSR_ACTIVATE_SCAN, params->activate->data);
- rdmsrl(MSR_SCAN_STATUS, status.data);
+ wrmsrq(MSR_ACTIVATE_SCAN, params->activate->data);
+ rdmsrq(MSR_SCAN_STATUS, status.data);
trace_ifs_status(ifsd->cur_batch, start, stop, status.data);
@@ -321,9 +322,9 @@ static int do_array_test(void *data)
first = cpumask_first(cpu_smt_mask(cpu));
if (cpu == first) {
- wrmsrl(MSR_ARRAY_BIST, command->data);
+ wrmsrq(MSR_ARRAY_BIST, command->data);
/* Pass back the result of the test */
- rdmsrl(MSR_ARRAY_BIST, command->data);
+ rdmsrq(MSR_ARRAY_BIST, command->data);
}
return 0;
@@ -374,8 +375,8 @@ static int do_array_test_gen1(void *status)
first = cpumask_first(cpu_smt_mask(cpu));
if (cpu == first) {
- wrmsrl(MSR_ARRAY_TRIGGER, ARRAY_GEN1_TEST_ALL_ARRAYS);
- rdmsrl(MSR_ARRAY_STATUS, *((u64 *)status));
+ wrmsrq(MSR_ARRAY_TRIGGER, ARRAY_GEN1_TEST_ALL_ARRAYS);
+ rdmsrq(MSR_ARRAY_STATUS, *((u64 *)status));
}
return 0;
@@ -526,8 +527,8 @@ static int dosbaf(void *data)
* starts scan of each requested bundle. The core test happens
* during the "execution" of the WRMSR.
*/
- wrmsrl(MSR_ACTIVATE_SBAF, run_params->activate->data);
- rdmsrl(MSR_SBAF_STATUS, status.data);
+ wrmsrq(MSR_ACTIVATE_SBAF, run_params->activate->data);
+ rdmsrq(MSR_SBAF_STATUS, status.data);
trace_ifs_sbaf(ifsd->cur_batch, *run_params->activate, status);
/* Pass back the result of the test */
diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c
index 3b48cd7a4075..562e88025643 100644
--- a/drivers/platform/x86/intel/int0002_vgpio.c
+++ b/drivers/platform/x86/intel/int0002_vgpio.c
@@ -23,7 +23,7 @@
* ACPI mechanisms, this is not a real GPIO at all.
*
* This driver will bind to the INT0002 device, and register as a GPIO
- * controller, letting gpiolib-acpi.c call the _L02 handler as it would
+ * controller, letting gpiolib-acpi call the _L02 handler as it would
* for a real GPIO controller.
*/
@@ -65,9 +65,10 @@ static int int0002_gpio_get(struct gpio_chip *chip, unsigned int offset)
return 0;
}
-static void int0002_gpio_set(struct gpio_chip *chip, unsigned int offset,
- int value)
+static int int0002_gpio_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
{
+ return 0;
}
static int int0002_gpio_direction_output(struct gpio_chip *chip,
@@ -205,8 +206,8 @@ static int int0002_probe(struct platform_device *pdev)
* FIXME: augment this if we managed to pull handling of shared
* IRQs into gpiolib.
*/
- ret = devm_request_irq(dev, irq, int0002_irq,
- IRQF_ONESHOT | IRQF_SHARED, "INT0002", chip);
+ ret = devm_request_irq(dev, irq, int0002_irq, IRQF_SHARED, "INT0002",
+ chip);
if (ret) {
dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret);
return ret;
diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c
index e526841aff60..849f7b415c1e 100644
--- a/drivers/platform/x86/intel/int1092/intel_sar.c
+++ b/drivers/platform/x86/intel/int1092/intel_sar.c
@@ -91,8 +91,8 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob
item->package.count <= data->total_dev_mode)
return AE_ERROR;
- data->device_mode_info = kmalloc_array(data->total_dev_mode,
- sizeof(struct wwan_device_mode_info), GFP_KERNEL);
+ data->device_mode_info = kmalloc_objs(struct wwan_device_mode_info,
+ data->total_dev_mode);
if (!data->device_mode_info)
return AE_ERROR;
@@ -245,15 +245,20 @@ static void sar_get_data(int reg, struct wwan_sar_context *context)
static int sar_probe(struct platform_device *device)
{
struct wwan_sar_context *context;
+ acpi_handle handle;
int reg;
int result;
- context = kzalloc(sizeof(*context), GFP_KERNEL);
+ handle = ACPI_HANDLE(&device->dev);
+ if (!handle)
+ return -ENODEV;
+
+ context = kzalloc_obj(*context);
if (!context)
return -ENOMEM;
context->sar_device = device;
- context->handle = ACPI_HANDLE(&device->dev);
+ context->handle = handle;
dev_set_drvdata(&device->dev, context);
result = guid_parse(SAR_DSM_UUID, &context->guid);
diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile
index a8aba07bf1dc..103661e6685d 100644
--- a/drivers/platform/x86/intel/int3472/Makefile
+++ b/drivers/platform/x86/intel/int3472/Makefile
@@ -1,7 +1,8 @@
obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \
intel_skl_int3472_tps68470.o \
intel_skl_int3472_common.o
-intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o led.o
+intel_skl_int3472_discrete-y := discrete.o discrete_quirks.o \
+ clk_and_regulator.o led.o
intel_skl_int3472_tps68470-y := tps68470.o tps68470_board_data.o
intel_skl_int3472_common-y += common.o
diff --git a/drivers/platform/x86/intel/int3472/clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c
index 16e36ac0a7b4..9e052b164a1a 100644
--- a/drivers/platform/x86/intel/int3472/clk_and_regulator.c
+++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c
@@ -5,13 +5,11 @@
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
-#include <linux/dmi.h>
#include <linux/gpio/consumer.h>
+#include <linux/platform_data/x86/int3472.h>
#include <linux/regulator/driver.h>
#include <linux/slab.h>
-#include "common.h"
-
/*
* 82c0d13a-78c5-4244-9bb1-eb8b539a8d11
* This _DSM GUID allows controlling the sensor clk when it is not controlled
@@ -118,7 +116,7 @@ static const struct clk_ops skl_int3472_clock_ops = {
.recalc_rate = skl_int3472_clk_recalc_rate,
};
-int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472)
+static int skl_int3472_register_clock(struct int3472_discrete_device *int3472)
{
struct acpi_device *adev = int3472->adev;
struct clk_init_data init = {
@@ -127,12 +125,6 @@ int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472)
};
int ret;
- if (int3472->clock.cl)
- return 0; /* A GPIO controlled clk has already been registered */
-
- if (!acpi_check_dsm(adev->handle, &img_clk_guid, 0, BIT(1)))
- return 0; /* DSM clock control is not available */
-
init.name = kasprintf(GFP_KERNEL, "%s-clk", acpi_dev_name(adev));
if (!init.name)
return -ENOMEM;
@@ -161,51 +153,26 @@ out_free_init_name:
return ret;
}
+int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472)
+{
+ if (int3472->clock.cl)
+ return 0; /* A GPIO controlled clk has already been registered */
+
+ if (!acpi_check_dsm(int3472->adev->handle, &img_clk_guid, 0, BIT(1)))
+ return 0; /* DSM clock control is not available */
+
+ return skl_int3472_register_clock(int3472);
+}
+
int skl_int3472_register_gpio_clock(struct int3472_discrete_device *int3472,
struct gpio_desc *gpio)
{
- struct clk_init_data init = {
- .ops = &skl_int3472_clock_ops,
- .flags = CLK_GET_RATE_NOCACHE,
- };
- int ret;
-
if (int3472->clock.cl)
return -EBUSY;
int3472->clock.ena_gpio = gpio;
- init.name = kasprintf(GFP_KERNEL, "%s-clk",
- acpi_dev_name(int3472->adev));
- if (!init.name)
- return -ENOMEM;
-
- int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472);
-
- int3472->clock.clk_hw.init = &init;
- int3472->clock.clk = clk_register(&int3472->adev->dev,
- &int3472->clock.clk_hw);
- if (IS_ERR(int3472->clock.clk)) {
- ret = PTR_ERR(int3472->clock.clk);
- goto out_free_init_name;
- }
-
- int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL,
- int3472->sensor_name);
- if (!int3472->clock.cl) {
- ret = -ENOMEM;
- goto err_unregister_clk;
- }
-
- kfree(init.name);
- return 0;
-
-err_unregister_clk:
- clk_unregister(int3472->clock.clk);
-out_free_init_name:
- kfree(init.name);
-
- return ret;
+ return skl_int3472_register_clock(int3472);
}
void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472)
@@ -215,100 +182,75 @@ void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472)
clkdev_drop(int3472->clock.cl);
clk_unregister(int3472->clock.clk);
+ gpiod_put(int3472->clock.ena_gpio);
}
-/*
- * The INT3472 device is going to be the only supplier of a regulator for
- * the sensor device. But unlike the clk framework the regulator framework
- * does not allow matching by consumer-device-name only.
- *
- * Ideally all sensor drivers would use "avdd" as supply-id. But for drivers
- * where this cannot be changed because another supply-id is already used in
- * e.g. DeviceTree files an alias for the other supply-id can be added here.
- *
- * Do not forget to update GPIO_REGULATOR_SUPPLY_MAP_COUNT when changing this.
- */
-static const char * const skl_int3472_regulator_map_supplies[] = {
- "avdd",
- "AVDD",
-};
-
-static_assert(ARRAY_SIZE(skl_int3472_regulator_map_supplies) ==
- GPIO_REGULATOR_SUPPLY_MAP_COUNT);
-
-/*
- * On some models there is a single GPIO regulator which is shared between
- * sensors and only listed in the ACPI resources of one sensor.
- * This DMI table contains the name of the second sensor. This is used to add
- * entries for the second sensor to the supply_map.
- */
-static const struct dmi_system_id skl_int3472_regulator_second_sensor[] = {
- {
- /* Lenovo Miix 510-12IKB */
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 510-12IKB"),
- },
- .driver_data = "i2c-OVTI2680:00",
- },
- { }
-};
-
int skl_int3472_register_regulator(struct int3472_discrete_device *int3472,
- struct gpio_desc *gpio)
+ struct gpio_desc *gpio,
+ unsigned int enable_time,
+ const char *supply_name,
+ const char *second_sensor)
{
struct regulator_init_data init_data = { };
+ struct int3472_gpio_regulator *regulator;
struct regulator_config cfg = { };
- const char *second_sensor = NULL;
- const struct dmi_system_id *id;
int i, j;
- id = dmi_first_match(skl_int3472_regulator_second_sensor);
- if (id)
- second_sensor = id->driver_data;
+ if (int3472->n_regulator_gpios >= INT3472_MAX_REGULATORS) {
+ dev_err(int3472->dev, "Too many regulators mapped\n");
+ return -EINVAL;
+ }
+
+ if (strlen(supply_name) >= GPIO_SUPPLY_NAME_LENGTH) {
+ dev_err(int3472->dev, "supply-name '%s' length too long\n", supply_name);
+ return -E2BIG;
+ }
+
+ regulator = &int3472->regulators[int3472->n_regulator_gpios];
+ string_upper(regulator->supply_name_upper, supply_name);
- for (i = 0, j = 0; i < ARRAY_SIZE(skl_int3472_regulator_map_supplies); i++) {
- int3472->regulator.supply_map[j].supply = skl_int3472_regulator_map_supplies[i];
- int3472->regulator.supply_map[j].dev_name = int3472->sensor_name;
+ /* The below code assume that map-count is 2 (upper- and lower-case) */
+ static_assert(GPIO_REGULATOR_SUPPLY_MAP_COUNT == 2);
+
+ for (i = 0, j = 0; i < GPIO_REGULATOR_SUPPLY_MAP_COUNT; i++) {
+ const char *supply = i ? regulator->supply_name_upper : supply_name;
+
+ regulator->supply_map[j].supply = supply;
+ regulator->supply_map[j].dev_name = int3472->sensor_name;
j++;
if (second_sensor) {
- int3472->regulator.supply_map[j].supply =
- skl_int3472_regulator_map_supplies[i];
- int3472->regulator.supply_map[j].dev_name = second_sensor;
+ regulator->supply_map[j].supply = supply;
+ regulator->supply_map[j].dev_name = second_sensor;
j++;
}
}
init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS;
- init_data.consumer_supplies = int3472->regulator.supply_map;
+ init_data.consumer_supplies = regulator->supply_map;
init_data.num_consumer_supplies = j;
- snprintf(int3472->regulator.regulator_name,
- sizeof(int3472->regulator.regulator_name), "%s-regulator",
- acpi_dev_name(int3472->adev));
- snprintf(int3472->regulator.supply_name,
- GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0");
+ snprintf(regulator->regulator_name, sizeof(regulator->regulator_name), "%s-%s",
+ acpi_dev_name(int3472->adev), supply_name);
- int3472->regulator.rdesc = INT3472_REGULATOR(
- int3472->regulator.regulator_name,
- int3472->regulator.supply_name,
- &int3472_gpio_regulator_ops);
-
- int3472->regulator.gpio = gpio;
+ regulator->rdesc = INT3472_REGULATOR(regulator->regulator_name,
+ &int3472_gpio_regulator_ops,
+ enable_time, GPIO_REGULATOR_OFF_ON_DELAY);
cfg.dev = &int3472->adev->dev;
cfg.init_data = &init_data;
- cfg.ena_gpiod = int3472->regulator.gpio;
+ cfg.ena_gpiod = gpio;
- int3472->regulator.rdev = regulator_register(int3472->dev,
- &int3472->regulator.rdesc,
- &cfg);
+ regulator->rdev = regulator_register(int3472->dev, &regulator->rdesc, &cfg);
+ if (IS_ERR(regulator->rdev))
+ return PTR_ERR(regulator->rdev);
- return PTR_ERR_OR_ZERO(int3472->regulator.rdev);
+ int3472->n_regulator_gpios++;
+ return 0;
}
void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472)
{
- regulator_unregister(int3472->regulator.rdev);
+ for (int i = 0; i < int3472->n_regulator_gpios; i++)
+ regulator_unregister(int3472->regulators[i].rdev);
}
diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c
index 1638be8fa71e..6dc38d5cbd0b 100644
--- a/drivers/platform/x86/intel/int3472/common.c
+++ b/drivers/platform/x86/intel/int3472/common.c
@@ -2,10 +2,9 @@
/* Author: Dan Scally <djrscally@gmail.com> */
#include <linux/acpi.h>
+#include <linux/platform_data/x86/int3472.h>
#include <linux/slab.h>
-#include "common.h"
-
union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
@@ -29,7 +28,7 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *i
return obj;
}
-EXPORT_SYMBOL_GPL(skl_int3472_get_acpi_buffer);
+EXPORT_SYMBOL_NS_GPL(skl_int3472_get_acpi_buffer, "INTEL_INT3472");
int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb)
{
@@ -53,7 +52,7 @@ out_free_obj:
kfree(obj);
return ret;
}
-EXPORT_SYMBOL_GPL(skl_int3472_fill_cldb);
+EXPORT_SYMBOL_NS_GPL(skl_int3472_fill_cldb, "INTEL_INT3472");
/* sensor_adev_ret may be NULL, name_ret must not be NULL */
int skl_int3472_get_sensor_adev_and_name(struct device *dev,
@@ -84,7 +83,7 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev,
return ret;
}
-EXPORT_SYMBOL_GPL(skl_int3472_get_sensor_adev_and_name);
+EXPORT_SYMBOL_NS_GPL(skl_int3472_get_sensor_adev_and_name, "INTEL_INT3472");
MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver library");
MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>");
diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h
deleted file mode 100644
index 145dec66df64..000000000000
--- a/drivers/platform/x86/intel/int3472/common.h
+++ /dev/null
@@ -1,131 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/* Author: Dan Scally <djrscally@gmail.com> */
-
-#ifndef _INTEL_SKL_INT3472_H
-#define _INTEL_SKL_INT3472_H
-
-#include <linux/clk-provider.h>
-#include <linux/gpio/machine.h>
-#include <linux/leds.h>
-#include <linux/regulator/driver.h>
-#include <linux/regulator/machine.h>
-#include <linux/types.h>
-
-/* FIXME drop this once the I2C_DEV_NAME_FORMAT macro has been added to include/linux/i2c.h */
-#ifndef I2C_DEV_NAME_FORMAT
-#define I2C_DEV_NAME_FORMAT "i2c-%s"
-#endif
-
-/* PMIC GPIO Types */
-#define INT3472_GPIO_TYPE_RESET 0x00
-#define INT3472_GPIO_TYPE_POWERDOWN 0x01
-#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b
-#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c
-#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d
-
-#define INT3472_PDEV_MAX_NAME_LEN 23
-#define INT3472_MAX_SENSOR_GPIOS 3
-
-#define GPIO_REGULATOR_NAME_LENGTH 21
-#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9
-#define GPIO_REGULATOR_SUPPLY_MAP_COUNT 2
-
-#define INT3472_LED_MAX_NAME_LEN 32
-
-#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86
-
-#define INT3472_REGULATOR(_name, _supply, _ops) \
- (const struct regulator_desc) { \
- .name = _name, \
- .supply_name = _supply, \
- .type = REGULATOR_VOLTAGE, \
- .ops = _ops, \
- .owner = THIS_MODULE, \
- }
-
-#define to_int3472_clk(hw) \
- container_of(hw, struct int3472_clock, clk_hw)
-
-#define to_int3472_device(clk) \
- container_of(clk, struct int3472_discrete_device, clock)
-
-struct acpi_device;
-struct i2c_client;
-struct platform_device;
-
-struct int3472_cldb {
- u8 version;
- /*
- * control logic type
- * 0: UNKNOWN
- * 1: DISCRETE(CRD-D)
- * 2: PMIC TPS68470
- * 3: PMIC uP6641
- */
- u8 control_logic_type;
- u8 control_logic_id;
- u8 sensor_card_sku;
- u8 reserved[10];
- u8 clock_source;
- u8 reserved2[17];
-};
-
-struct int3472_discrete_device {
- struct acpi_device *adev;
- struct device *dev;
- struct acpi_device *sensor;
- const char *sensor_name;
-
- const struct int3472_sensor_config *sensor_config;
-
- struct int3472_gpio_regulator {
- /* SUPPLY_MAP_COUNT * 2 to make room for second sensor mappings */
- struct regulator_consumer_supply supply_map[GPIO_REGULATOR_SUPPLY_MAP_COUNT * 2];
- char regulator_name[GPIO_REGULATOR_NAME_LENGTH];
- char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH];
- struct gpio_desc *gpio;
- struct regulator_dev *rdev;
- struct regulator_desc rdesc;
- } regulator;
-
- struct int3472_clock {
- struct clk *clk;
- struct clk_hw clk_hw;
- struct clk_lookup *cl;
- struct gpio_desc *ena_gpio;
- u32 frequency;
- u8 imgclk_index;
- } clock;
-
- struct int3472_pled {
- struct led_classdev classdev;
- struct led_lookup_data lookup;
- char name[INT3472_LED_MAX_NAME_LEN];
- struct gpio_desc *gpio;
- } pled;
-
- unsigned int ngpios; /* how many GPIOs have we seen */
- unsigned int n_sensor_gpios; /* how many have we mapped to sensor */
- struct gpiod_lookup_table gpios;
-};
-
-union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev,
- char *id);
-int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb);
-int skl_int3472_get_sensor_adev_and_name(struct device *dev,
- struct acpi_device **sensor_adev_ret,
- const char **name_ret);
-
-int skl_int3472_register_gpio_clock(struct int3472_discrete_device *int3472,
- struct gpio_desc *gpio);
-int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472);
-void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472);
-
-int skl_int3472_register_regulator(struct int3472_discrete_device *int3472,
- struct gpio_desc *gpio);
-void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472);
-
-int skl_int3472_register_pled(struct int3472_discrete_device *int3472, struct gpio_desc *gpio);
-void skl_int3472_unregister_pled(struct int3472_discrete_device *int3472);
-
-#endif
diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c
index 30ff8f3ea1f5..115bb37577a1 100644
--- a/drivers/platform/x86/intel/int3472/discrete.c
+++ b/drivers/platform/x86/intel/int3472/discrete.c
@@ -5,18 +5,18 @@
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/overflow.h>
+#include <linux/platform_data/x86/int3472.h>
#include <linux/platform_device.h>
#include <linux/string_choices.h>
#include <linux/uuid.h>
-#include "common.h"
-
/*
* 79234640-9e10-4fea-a5c1-b5aa8b19756f
* This _DSM GUID returns information about the GPIO lines mapped to a
@@ -107,7 +107,7 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472,
int ret;
struct gpiod_lookup_table *lookup __free(kfree) =
- kzalloc(struct_size(lookup, table, 2), GFP_KERNEL);
+ kzalloc_flex(*lookup, table, 2);
if (!lookup)
return ERR_PTR(-ENOMEM);
@@ -117,7 +117,7 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472,
return ERR_PTR(ret);
gpiod_add_lookup_table(lookup);
- desc = devm_gpiod_get(int3472->dev, con_id, GPIOD_OUT_LOW);
+ desc = gpiod_get(int3472->dev, con_id, GPIOD_OUT_LOW);
gpiod_remove_lookup_table(lookup);
return desc;
@@ -129,6 +129,7 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472,
* @hid: The ACPI HID of the device without the instance number e.g. INT347E
* @type_from: The GPIO type from ACPI ?SDT
* @type_to: The assigned GPIO type, typically same as @type_from
+ * @enable_time_us: Enable time in usec for GPIOs mapped to regulators
* @con_id: The name of the GPIO for the device
* @polarity_low: GPIO_ACTIVE_LOW true if the @polarity_low is true,
* GPIO_ACTIVE_HIGH otherwise
@@ -138,16 +139,38 @@ struct int3472_gpio_map {
u8 type_from;
u8 type_to;
bool polarity_low;
+ unsigned int enable_time_us;
const char *con_id;
};
static const struct int3472_gpio_map int3472_gpio_map[] = {
- { "INT347E", INT3472_GPIO_TYPE_RESET, INT3472_GPIO_TYPE_RESET, false, "enable" },
+ { /* mt9m114 designs declare a powerdown pin which controls the regulators */
+ .hid = "INT33F0",
+ .type_from = INT3472_GPIO_TYPE_POWERDOWN,
+ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE,
+ .con_id = "vdd",
+ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME,
+ },
+ { /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */
+ .hid = "INT347E",
+ .type_from = INT3472_GPIO_TYPE_RESET,
+ .type_to = INT3472_GPIO_TYPE_RESET,
+ .con_id = "enable",
+ },
+ { /* ov08x40's handshake pin needs a 45 ms delay on some HP laptops */
+ .hid = "OVTI08F4",
+ .type_from = INT3472_GPIO_TYPE_HANDSHAKE,
+ .type_to = INT3472_GPIO_TYPE_HANDSHAKE,
+ .con_id = "dvdd",
+ .enable_time_us = 45 * USEC_PER_MSEC,
+ },
};
-static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
- const char **con_id, unsigned long *gpio_flags)
+static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type,
+ const char **con_id, unsigned long *gpio_flags,
+ unsigned int *enable_time_us)
{
+ struct acpi_device *adev = int3472->sensor;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(int3472_gpio_map); i++) {
@@ -162,13 +185,19 @@ static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
if (!acpi_dev_hid_uid_match(adev, int3472_gpio_map[i].hid, NULL))
continue;
+ dev_dbg(int3472->dev, "mapping type 0x%02x pin to 0x%02x %s\n",
+ *type, int3472_gpio_map[i].type_to, int3472_gpio_map[i].con_id);
+
*type = int3472_gpio_map[i].type_to;
*gpio_flags = int3472_gpio_map[i].polarity_low ?
GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH;
*con_id = int3472_gpio_map[i].con_id;
+ *enable_time_us = int3472_gpio_map[i].enable_time_us;
return;
}
+ *enable_time_us = GPIO_REGULATOR_ENABLE_TIME;
+
switch (*type) {
case INT3472_GPIO_TYPE_RESET:
*con_id = "reset";
@@ -183,12 +212,30 @@ static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
*gpio_flags = GPIO_ACTIVE_HIGH;
break;
case INT3472_GPIO_TYPE_PRIVACY_LED:
- *con_id = "privacy-led";
+ *con_id = "privacy";
+ *gpio_flags = GPIO_ACTIVE_HIGH;
+ break;
+ case INT3472_GPIO_TYPE_STROBE:
+ *con_id = "ir_flood";
+ *gpio_flags = GPIO_ACTIVE_HIGH;
+ break;
+ case INT3472_GPIO_TYPE_HOTPLUG_DETECT:
+ *con_id = "hpd";
*gpio_flags = GPIO_ACTIVE_HIGH;
break;
case INT3472_GPIO_TYPE_POWER_ENABLE:
- *con_id = "power-enable";
+ *con_id = "avdd";
+ *gpio_flags = GPIO_ACTIVE_HIGH;
+ break;
+ case INT3472_GPIO_TYPE_DOVDD:
+ *con_id = "dovdd";
+ *gpio_flags = GPIO_ACTIVE_HIGH;
+ break;
+ case INT3472_GPIO_TYPE_HANDSHAKE:
+ *con_id = "dvdd";
*gpio_flags = GPIO_ACTIVE_HIGH;
+ /* Setups using a handshake pin need 25 ms enable delay */
+ *enable_time_us = 25 * USEC_PER_MSEC;
break;
default:
*con_id = "unknown";
@@ -209,9 +256,12 @@ static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
*
* 0x00 Reset
* 0x01 Power down
+ * 0x02 Strobe
* 0x0b Power enable
* 0x0c Clock enable
* 0x0d Privacy LED
+ * 0x10 DOVDD (digital I/O voltage)
+ * 0x13 Hotplug detect
*
* There are some known platform specific quirks where that does not quite
* hold up; for example where a pin with type 0x01 (Power down) is mapped to
@@ -233,13 +283,15 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
void *data)
{
struct int3472_discrete_device *int3472 = data;
+ const char *second_sensor = NULL;
struct acpi_resource_gpio *agpio;
+ unsigned int enable_time_us;
u8 active_value, pin, type;
+ unsigned long gpio_flags;
union acpi_object *obj;
struct gpio_desc *gpio;
const char *err_msg;
const char *con_id;
- unsigned long gpio_flags;
int ret;
if (!acpi_gpio_get_io_resource(ares, &agpio))
@@ -262,7 +314,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj->integer.value);
- int3472_get_con_id_and_polarity(int3472->sensor, &type, &con_id, &gpio_flags);
+ int3472_get_con_id_and_polarity(int3472, &type, &con_id, &gpio_flags, &enable_time_us);
pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value);
/* Pin field is not really used under Windows and wraps around at 8 bits */
@@ -281,6 +333,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
switch (type) {
case INT3472_GPIO_TYPE_RESET:
case INT3472_GPIO_TYPE_POWERDOWN:
+ case INT3472_GPIO_TYPE_HOTPLUG_DETECT:
ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, con_id, gpio_flags);
if (ret)
err_msg = "Failed to map GPIO pin to sensor\n";
@@ -288,7 +341,10 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
break;
case INT3472_GPIO_TYPE_CLK_ENABLE:
case INT3472_GPIO_TYPE_PRIVACY_LED:
+ case INT3472_GPIO_TYPE_STROBE:
case INT3472_GPIO_TYPE_POWER_ENABLE:
+ case INT3472_GPIO_TYPE_DOVDD:
+ case INT3472_GPIO_TYPE_HANDSHAKE:
gpio = skl_int3472_gpiod_get_from_temp_lookup(int3472, agpio, con_id, gpio_flags);
if (IS_ERR(gpio)) {
ret = PTR_ERR(gpio);
@@ -304,21 +360,31 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
break;
case INT3472_GPIO_TYPE_PRIVACY_LED:
- ret = skl_int3472_register_pled(int3472, gpio);
+ case INT3472_GPIO_TYPE_STROBE:
+ ret = skl_int3472_register_led(int3472, gpio, con_id);
if (ret)
err_msg = "Failed to register LED\n";
break;
case INT3472_GPIO_TYPE_POWER_ENABLE:
- ret = skl_int3472_register_regulator(int3472, gpio);
+ second_sensor = int3472->quirks.avdd_second_sensor;
+ fallthrough;
+ case INT3472_GPIO_TYPE_DOVDD:
+ case INT3472_GPIO_TYPE_HANDSHAKE:
+ ret = skl_int3472_register_regulator(int3472, gpio, enable_time_us,
+ con_id, second_sensor);
if (ret)
- err_msg = "Failed to map regulator to sensor\n";
+ err_msg = "Failed to register regulator\n";
break;
default: /* Never reached */
ret = -EINVAL;
break;
}
+
+ if (ret)
+ gpiod_put(gpio);
+
break;
default:
dev_warn(int3472->dev,
@@ -338,7 +404,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
return 1;
}
-static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472)
+int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472)
{
LIST_HEAD(resource_list);
int ret;
@@ -363,28 +429,39 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472)
return 0;
}
+EXPORT_SYMBOL_NS_GPL(int3472_discrete_parse_crs, "INTEL_INT3472_DISCRETE");
-static void skl_int3472_discrete_remove(struct platform_device *pdev)
+void int3472_discrete_cleanup(struct int3472_discrete_device *int3472)
{
- struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev);
-
gpiod_remove_lookup_table(&int3472->gpios);
skl_int3472_unregister_clock(int3472);
- skl_int3472_unregister_pled(int3472);
+ skl_int3472_unregister_leds(int3472);
skl_int3472_unregister_regulator(int3472);
}
+EXPORT_SYMBOL_NS_GPL(int3472_discrete_cleanup, "INTEL_INT3472_DISCRETE");
+
+static void skl_int3472_discrete_remove(struct platform_device *pdev)
+{
+ int3472_discrete_cleanup(platform_get_drvdata(pdev));
+}
static int skl_int3472_discrete_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+ const struct int3472_discrete_quirks *quirks = NULL;
struct int3472_discrete_device *int3472;
+ const struct dmi_system_id *id;
struct int3472_cldb cldb;
int ret;
if (!adev)
return -ENODEV;
+ id = dmi_first_match(skl_int3472_discrete_quirks);
+ if (id)
+ quirks = id->driver_data;
+
ret = skl_int3472_fill_cldb(adev, &cldb);
if (ret) {
dev_err(&pdev->dev, "Couldn't fill CLDB structure\n");
@@ -408,6 +485,9 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, int3472);
int3472->clock.imgclk_index = cldb.clock_source;
+ if (quirks)
+ int3472->quirks = *quirks;
+
ret = skl_int3472_get_sensor_adev_and_name(&pdev->dev, &int3472->sensor,
&int3472->sensor_name);
if (ret)
@@ -419,7 +499,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev)
*/
INIT_LIST_HEAD(&int3472->gpios.list);
- ret = skl_int3472_parse_crs(int3472);
+ ret = int3472_discrete_parse_crs(int3472);
if (ret) {
skl_int3472_discrete_remove(pdev);
return ret;
@@ -448,3 +528,4 @@ module_platform_driver(int3472_discrete);
MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Discrete Device Driver");
MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>");
MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("INTEL_INT3472");
diff --git a/drivers/platform/x86/intel/int3472/discrete_quirks.c b/drivers/platform/x86/intel/int3472/discrete_quirks.c
new file mode 100644
index 000000000000..552869ef91ab
--- /dev/null
+++ b/drivers/platform/x86/intel/int3472/discrete_quirks.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Author: Hans de Goede <hansg@kernel.org> */
+
+#include <linux/dmi.h>
+#include <linux/platform_data/x86/int3472.h>
+
+static const struct int3472_discrete_quirks lenovo_miix_510_quirks = {
+ .avdd_second_sensor = "i2c-OVTI2680:00",
+};
+
+const struct dmi_system_id skl_int3472_discrete_quirks[] = {
+ {
+ /* Lenovo Miix 510-12IKB */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 510-12IKB"),
+ },
+ .driver_data = (void *)&lenovo_miix_510_quirks,
+ },
+ { }
+};
diff --git a/drivers/platform/x86/intel/int3472/led.c b/drivers/platform/x86/intel/int3472/led.c
index 9cbed694e2ca..9b2573cc347b 100644
--- a/drivers/platform/x86/intel/int3472/led.c
+++ b/drivers/platform/x86/intel/int3472/led.c
@@ -4,56 +4,60 @@
#include <linux/acpi.h>
#include <linux/gpio/consumer.h>
#include <linux/leds.h>
-#include "common.h"
+#include <linux/platform_data/x86/int3472.h>
-static int int3472_pled_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+static int int3472_led_set(struct led_classdev *led_cdev, enum led_brightness brightness)
{
- struct int3472_discrete_device *int3472 =
- container_of(led_cdev, struct int3472_discrete_device, pled.classdev);
+ struct int3472_led *led = container_of(led_cdev, struct int3472_led, classdev);
- gpiod_set_value_cansleep(int3472->pled.gpio, brightness);
+ gpiod_set_value_cansleep(led->gpio, brightness);
return 0;
}
-int skl_int3472_register_pled(struct int3472_discrete_device *int3472, struct gpio_desc *gpio)
+int skl_int3472_register_led(struct int3472_discrete_device *int3472, struct gpio_desc *gpio,
+ const char *con_id)
{
+ struct int3472_led *led;
char *p;
int ret;
- if (int3472->pled.classdev.dev)
- return -EBUSY;
+ if (int3472->n_leds >= INT3472_MAX_LEDS)
+ return -ENOSPC;
- int3472->pled.gpio = gpio;
+ led = &int3472->leds[int3472->n_leds];
+ led->gpio = gpio;
/* Generate the name, replacing the ':' in the ACPI devname with '_' */
- snprintf(int3472->pled.name, sizeof(int3472->pled.name),
- "%s::privacy_led", acpi_dev_name(int3472->sensor));
- p = strchr(int3472->pled.name, ':');
+ snprintf(led->name, sizeof(led->name),
+ "%s::%s_led", acpi_dev_name(int3472->sensor), con_id);
+ p = strchr(led->name, ':');
if (p)
*p = '_';
- int3472->pled.classdev.name = int3472->pled.name;
- int3472->pled.classdev.max_brightness = 1;
- int3472->pled.classdev.brightness_set_blocking = int3472_pled_set;
+ led->classdev.name = led->name;
+ led->classdev.max_brightness = 1;
+ led->classdev.brightness_set_blocking = int3472_led_set;
- ret = led_classdev_register(int3472->dev, &int3472->pled.classdev);
+ ret = led_classdev_register(int3472->dev, &led->classdev);
if (ret)
return ret;
- int3472->pled.lookup.provider = int3472->pled.name;
- int3472->pled.lookup.dev_id = int3472->sensor_name;
- int3472->pled.lookup.con_id = "privacy-led";
- led_add_lookup(&int3472->pled.lookup);
+ led->lookup.provider = led->name;
+ led->lookup.dev_id = int3472->sensor_name;
+ led->lookup.con_id = con_id;
+ led_add_lookup(&led->lookup);
+ int3472->n_leds++;
return 0;
}
-void skl_int3472_unregister_pled(struct int3472_discrete_device *int3472)
+void skl_int3472_unregister_leds(struct int3472_discrete_device *int3472)
{
- if (IS_ERR_OR_NULL(int3472->pled.classdev.dev))
- return;
+ for (unsigned int i = 0; i < int3472->n_leds; i++) {
+ struct int3472_led *led = &int3472->leds[i];
- led_remove_lookup(&int3472->pled.lookup);
- led_classdev_unregister(&int3472->pled.classdev);
+ led_remove_lookup(&led->lookup);
+ led_classdev_unregister(&led->classdev);
+ gpiod_put(led->gpio);
+ }
}
diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c
index 81ac4c691963..a77ed32abe55 100644
--- a/drivers/platform/x86/intel/int3472/tps68470.c
+++ b/drivers/platform/x86/intel/int3472/tps68470.c
@@ -8,10 +8,10 @@
#include <linux/mfd/tps68470.h>
#include <linux/platform_device.h>
#include <linux/platform_data/tps68470.h>
+#include <linux/platform_data/x86/int3472.h>
#include <linux/regmap.h>
#include <linux/string.h>
-#include "common.h"
#include "tps68470.h"
#define DESIGNED_FOR_CHROMEOS 1
@@ -180,7 +180,7 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client)
if (!board_data)
return dev_err_probe(&client->dev, -ENODEV, "No board-data found for this model\n");
- cells = kcalloc(TPS68470_WIN_MFD_CELL_COUNT, sizeof(*cells), GFP_KERNEL);
+ cells = kzalloc_objs(*cells, TPS68470_WIN_MFD_CELL_COUNT);
if (!cells)
return -ENOMEM;
@@ -197,6 +197,7 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client)
cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata;
cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data);
cells[2].name = "tps68470-gpio";
+ cells[2].swnode = board_data->tps68470_gpio_swnode;
for (i = 0; i < board_data->n_gpiod_lookups; i++)
gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]);
@@ -261,4 +262,5 @@ module_i2c_driver(int3472_tps68470);
MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver");
MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>");
MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS("INTEL_INT3472");
MODULE_SOFTDEP("pre: clk-tps68470 tps68470-regulator");
diff --git a/drivers/platform/x86/intel/int3472/tps68470.h b/drivers/platform/x86/intel/int3472/tps68470.h
index 35915e701593..3bbaade96c57 100644
--- a/drivers/platform/x86/intel/int3472/tps68470.h
+++ b/drivers/platform/x86/intel/int3472/tps68470.h
@@ -17,6 +17,7 @@ struct tps68470_regulator_platform_data;
struct int3472_tps68470_board_data {
const char *dev_name;
const struct tps68470_regulator_platform_data *tps68470_regulator_pdata;
+ const struct software_node *tps68470_gpio_swnode;
unsigned int n_gpiod_lookups;
struct gpiod_lookup_table *tps68470_gpio_lookup_tables[];
};
diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c
index 322237e056f3..c1ddbf9a82c0 100644
--- a/drivers/platform/x86/intel/int3472/tps68470_board_data.c
+++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c
@@ -12,6 +12,7 @@
#include <linux/dmi.h>
#include <linux/gpio/machine.h>
#include <linux/platform_data/tps68470.h>
+#include <linux/property.h>
#include <linux/regulator/machine.h>
#include "tps68470.h"
@@ -129,6 +130,165 @@ static const struct tps68470_regulator_platform_data surface_go_tps68470_pdata =
},
};
+/* Settings for Dell 7212 Tablet */
+
+static struct regulator_consumer_supply int3479_vsio_consumer_supplies[] = {
+ REGULATOR_SUPPLY("avdd", "i2c-INT3479:00"),
+};
+
+static struct regulator_consumer_supply int3479_aux1_consumer_supplies[] = {
+ REGULATOR_SUPPLY("dvdd", "i2c-INT3479:00"),
+};
+
+static struct regulator_consumer_supply int3479_aux2_consumer_supplies[] = {
+ REGULATOR_SUPPLY("dovdd", "i2c-INT3479:00"),
+};
+
+static const struct regulator_init_data dell_7212_tps68470_core_reg_init_data = {
+ .constraints = {
+ .min_uV = 1200000,
+ .max_uV = 1200000,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+};
+
+static const struct regulator_init_data dell_7212_tps68470_ana_reg_init_data = {
+ .constraints = {
+ .min_uV = 2815200,
+ .max_uV = 2815200,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+};
+
+static const struct regulator_init_data dell_7212_tps68470_vcm_reg_init_data = {
+ .constraints = {
+ .min_uV = 2815200,
+ .max_uV = 2815200,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+};
+
+static const struct regulator_init_data dell_7212_tps68470_vio_reg_init_data = {
+ .constraints = {
+ .min_uV = 1800600,
+ .max_uV = 1800600,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+};
+
+static const struct regulator_init_data dell_7212_tps68470_vsio_reg_init_data = {
+ .constraints = {
+ .min_uV = 1800600,
+ .max_uV = 1800600,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = ARRAY_SIZE(int3479_vsio_consumer_supplies),
+ .consumer_supplies = int3479_vsio_consumer_supplies,
+};
+
+static const struct regulator_init_data dell_7212_tps68470_aux1_reg_init_data = {
+ .constraints = {
+ .min_uV = 1213200,
+ .max_uV = 1213200,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = ARRAY_SIZE(int3479_aux1_consumer_supplies),
+ .consumer_supplies = int3479_aux1_consumer_supplies,
+};
+
+static const struct regulator_init_data dell_7212_tps68470_aux2_reg_init_data = {
+ .constraints = {
+ .min_uV = 1800600,
+ .max_uV = 1800600,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = ARRAY_SIZE(int3479_aux2_consumer_supplies),
+ .consumer_supplies = int3479_aux2_consumer_supplies,
+};
+
+static const struct tps68470_regulator_platform_data dell_7212_tps68470_pdata = {
+ .reg_init_data = {
+ [TPS68470_CORE] = &dell_7212_tps68470_core_reg_init_data,
+ [TPS68470_ANA] = &dell_7212_tps68470_ana_reg_init_data,
+ [TPS68470_VCM] = &dell_7212_tps68470_vcm_reg_init_data,
+ [TPS68470_VIO] = &dell_7212_tps68470_vio_reg_init_data,
+ [TPS68470_VSIO] = &dell_7212_tps68470_vsio_reg_init_data,
+ [TPS68470_AUX1] = &dell_7212_tps68470_aux1_reg_init_data,
+ [TPS68470_AUX2] = &dell_7212_tps68470_aux2_reg_init_data,
+ },
+};
+
+/* Settings for MSI Prestige 14 AI+ Evo C2VMG laptop. */
+static struct regulator_consumer_supply ovti5675_avdd_consumer_supplies[] = {
+ REGULATOR_SUPPLY("avdd", "i2c-OVTI5675:00"),
+};
+
+static struct regulator_consumer_supply ovti5675_dovdd_consumer_supplies[] = {
+ REGULATOR_SUPPLY("dovdd", "i2c-OVTI5675:00"),
+};
+
+static struct regulator_consumer_supply ovti5675_dvdd_consumer_supplies[] = {
+ REGULATOR_SUPPLY("dvdd", "i2c-OVTI5675:00"),
+};
+
+static const struct regulator_init_data msi_p14_ai_evo_tps68470_core_reg_init_data = {
+ .constraints = {
+ .min_uV = 1200000,
+ .max_uV = 1200000,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = ARRAY_SIZE(ovti5675_dvdd_consumer_supplies),
+ .consumer_supplies = ovti5675_dvdd_consumer_supplies,
+};
+
+static const struct regulator_init_data msi_p14_ai_evo_tps68470_ana_reg_init_data = {
+ .constraints = {
+ .min_uV = 2815200,
+ .max_uV = 2815200,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = ARRAY_SIZE(ovti5675_avdd_consumer_supplies),
+ .consumer_supplies = ovti5675_avdd_consumer_supplies,
+};
+
+static const struct regulator_init_data msi_p14_ai_evo_tps68470_vio_reg_init_data = {
+ .constraints = {
+ .min_uV = 1800600,
+ .max_uV = 1800600,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+};
+
+static const struct regulator_init_data msi_p14_ai_evo_tps68470_vsio_reg_init_data = {
+ .constraints = {
+ .min_uV = 1800600,
+ .max_uV = 1800600,
+ .apply_uV = 1,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = ARRAY_SIZE(ovti5675_dovdd_consumer_supplies),
+ .consumer_supplies = ovti5675_dovdd_consumer_supplies,
+};
+
+static const struct tps68470_regulator_platform_data msi_p14_ai_evo_tps68470_pdata = {
+ .reg_init_data = {
+ [TPS68470_CORE] = &msi_p14_ai_evo_tps68470_core_reg_init_data,
+ [TPS68470_ANA] = &msi_p14_ai_evo_tps68470_ana_reg_init_data,
+ [TPS68470_VIO] = &msi_p14_ai_evo_tps68470_vio_reg_init_data,
+ [TPS68470_VSIO] = &msi_p14_ai_evo_tps68470_vsio_reg_init_data,
+ },
+};
+
static struct gpiod_lookup_table surface_go_int347a_gpios = {
.dev_id = "i2c-INT347A:00",
.table = {
@@ -146,6 +306,32 @@ static struct gpiod_lookup_table surface_go_int347e_gpios = {
}
};
+static struct gpiod_lookup_table dell_7212_int3479_gpios = {
+ .dev_id = "i2c-INT3479:00",
+ .table = {
+ GPIO_LOOKUP("tps68470-gpio", 3, "reset", GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP("tps68470-gpio", 4, "powerdown", GPIO_ACTIVE_LOW),
+ { }
+ }
+};
+
+static struct gpiod_lookup_table msi_p14_ai_evo_ovti5675_gpios = {
+ .dev_id = "i2c-OVTI5675:00",
+ .table = {
+ GPIO_LOOKUP("tps68470-gpio", 9, "reset", GPIO_ACTIVE_LOW),
+ { }
+ }
+};
+
+static const struct property_entry msi_p14_ai_evo_gpio_props[] = {
+ PROPERTY_ENTRY_BOOL("daisy-chain-enable"),
+ { }
+};
+
+static const struct software_node msi_p14_ai_evo_tps68470_gpio_swnode = {
+ .properties = msi_p14_ai_evo_gpio_props,
+};
+
static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = {
.dev_name = "i2c-INT3472:05",
.tps68470_regulator_pdata = &surface_go_tps68470_pdata,
@@ -166,6 +352,25 @@ static const struct int3472_tps68470_board_data surface_go3_tps68470_board_data
},
};
+static const struct int3472_tps68470_board_data dell_7212_tps68470_board_data = {
+ .dev_name = "i2c-INT3472:05",
+ .tps68470_regulator_pdata = &dell_7212_tps68470_pdata,
+ .n_gpiod_lookups = 1,
+ .tps68470_gpio_lookup_tables = {
+ &dell_7212_int3479_gpios,
+ },
+};
+
+static const struct int3472_tps68470_board_data msi_p14_ai_evo_tps68470_board_data = {
+ .dev_name = "i2c-INT3472:06",
+ .tps68470_regulator_pdata = &msi_p14_ai_evo_tps68470_pdata,
+ .tps68470_gpio_swnode = &msi_p14_ai_evo_tps68470_gpio_swnode,
+ .n_gpiod_lookups = 1,
+ .tps68470_gpio_lookup_tables = {
+ &msi_p14_ai_evo_ovti5675_gpios,
+ },
+};
+
static const struct dmi_system_id int3472_tps68470_board_data_table[] = {
{
.matches = {
@@ -188,6 +393,20 @@ static const struct dmi_system_id int3472_tps68470_board_data_table[] = {
},
.driver_data = (void *)&surface_go3_tps68470_board_data,
},
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 7212 Rugged Extreme Tablet"),
+ },
+ .driver_data = (void *)&dell_7212_tps68470_board_data,
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Prestige 14 AI+ Evo C2VMG"),
+ },
+ .driver_data = (void *)&msi_p14_ai_evo_tps68470_board_data,
+ },
{ }
};
diff --git a/drivers/platform/x86/intel/plr_tpmi.c b/drivers/platform/x86/intel/plr_tpmi.c
index 2b55347a5a93..8faecc311038 100644
--- a/drivers/platform/x86/intel/plr_tpmi.c
+++ b/drivers/platform/x86/intel/plr_tpmi.c
@@ -14,6 +14,7 @@
#include <linux/err.h>
#include <linux/gfp_types.h>
#include <linux/intel_tpmi.h>
+#include <linux/intel_vsec.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kstrtox.h>
@@ -21,6 +22,7 @@
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
+#include <linux/notifier.h>
#include <linux/seq_file.h>
#include <linux/sprintf.h>
#include <linux/types.h>
@@ -59,6 +61,8 @@ struct tpmi_plr {
struct tpmi_plr_die *die_info;
int num_dies;
struct auxiliary_device *auxdev;
+ struct notifier_block nb;
+ struct mutex lock; /* Protect access to dbgfs_dir */
};
static const char * const plr_coarse_reasons[] = {
@@ -254,9 +258,33 @@ static ssize_t plr_status_write(struct file *filp, const char __user *ubuf,
}
DEFINE_SHOW_STORE_ATTRIBUTE(plr_status);
+static int intel_plr_notify(struct notifier_block *self, unsigned long action, void *data)
+{
+ struct tpmi_plr *plr = container_of(self, struct tpmi_plr, nb);
+
+ if (action == TPMI_CORE_EXIT) {
+ guard(mutex)(&plr->lock);
+ plr->dbgfs_dir = NULL;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int intel_plr_register_notifier(struct notifier_block *nb)
+{
+ nb->notifier_call = intel_plr_notify;
+ nb->priority = 0;
+ return tpmi_register_notifier(nb);
+}
+
+static void intel_plr_unregister_notifier(struct notifier_block *nb)
+{
+ tpmi_unregister_notifier(nb);
+}
+
static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
{
- struct intel_tpmi_plat_info *plat_info;
+ struct oobmsm_plat_info *plat_info;
struct dentry *dentry;
int i, num_resources;
struct resource *res;
@@ -281,10 +309,18 @@ static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxilia
if (!plr)
return -ENOMEM;
+ err = devm_mutex_init(&auxdev->dev, &plr->lock);
+ if (err)
+ return err;
+
+ intel_plr_register_notifier(&plr->nb);
+
plr->die_info = devm_kcalloc(&auxdev->dev, num_resources, sizeof(*plr->die_info),
GFP_KERNEL);
- if (!plr->die_info)
- return -ENOMEM;
+ if (!plr->die_info) {
+ err = -ENOMEM;
+ goto err_notify;
+ }
plr->num_dies = num_resources;
plr->dbgfs_dir = debugfs_create_dir("plr", dentry);
@@ -315,7 +351,7 @@ static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxilia
snprintf(name, sizeof(name), "domain%d", i);
dentry = debugfs_create_dir(name, plr->dbgfs_dir);
- debugfs_create_file("status", 0444, dentry, &plr->die_info[i],
+ debugfs_create_file("status", 0644, dentry, &plr->die_info[i],
&plr_status_fops);
}
@@ -325,6 +361,9 @@ static int intel_plr_probe(struct auxiliary_device *auxdev, const struct auxilia
err:
debugfs_remove_recursive(plr->dbgfs_dir);
+err_notify:
+ intel_plr_unregister_notifier(&plr->nb);
+
return err;
}
@@ -332,6 +371,9 @@ static void intel_plr_remove(struct auxiliary_device *auxdev)
{
struct tpmi_plr *plr = auxiliary_get_drvdata(auxdev);
+ intel_plr_unregister_notifier(&plr->nb);
+
+ guard(mutex)(&plr->lock);
debugfs_remove_recursive(plr->dbgfs_dir);
}
diff --git a/drivers/platform/x86/intel/pmc/Kconfig b/drivers/platform/x86/intel/pmc/Kconfig
index d2f651fbec2c..c6ef0bcf76af 100644
--- a/drivers/platform/x86/intel/pmc/Kconfig
+++ b/drivers/platform/x86/intel/pmc/Kconfig
@@ -8,6 +8,7 @@ config INTEL_PMC_CORE
depends on PCI
depends on ACPI
depends on INTEL_PMT_TELEMETRY
+ select INTEL_PMC_SSRAM_TELEMETRY
help
The Intel Platform Controller Hub for Intel Core SoCs provides access
to Power Management Controller registers via various interfaces. This
@@ -24,3 +25,6 @@ config INTEL_PMC_CORE
- SLPS0 Debug registers (Cannonlake/Icelake PCH)
- Low Power Mode registers (Tigerlake and beyond)
- PMC quirks as needed to enable SLPS0/S0ix
+
+config INTEL_PMC_SSRAM_TELEMETRY
+ tristate
diff --git a/drivers/platform/x86/intel/pmc/Makefile b/drivers/platform/x86/intel/pmc/Makefile
index b148b40d09f5..bb960c8721d7 100644
--- a/drivers/platform/x86/intel/pmc/Makefile
+++ b/drivers/platform/x86/intel/pmc/Makefile
@@ -3,8 +3,12 @@
# Intel x86 Platform-Specific Drivers
#
-intel_pmc_core-y := core.o core_ssram.o spt.o cnp.o \
- icl.o tgl.o adl.o mtl.o arl.o lnl.o ptl.o
+intel_pmc_core-y := core.o spt.o cnp.o icl.o \
+ tgl.o adl.o mtl.o arl.o lnl.o ptl.o wcl.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o
intel_pmc_core_pltdrv-y := pltdrv.o
obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core_pltdrv.o
+
+# Intel PMC SSRAM driver
+intel_pmc_ssram_telemetry-y += ssram_telemetry.o
+obj-$(CONFIG_INTEL_PMC_SSRAM_TELEMETRY) += intel_pmc_ssram_telemetry.o
diff --git a/drivers/platform/x86/intel/pmc/arl.c b/drivers/platform/x86/intel/pmc/arl.c
index 320993bd6d31..eb23bc68340a 100644
--- a/drivers/platform/x86/intel/pmc/arl.c
+++ b/drivers/platform/x86/intel/pmc/arl.c
@@ -10,7 +10,6 @@
#include <linux/pci.h>
#include "core.h"
-#include "../pmt/telemetry.h"
/* PMC SSRAM PMT Telemetry GUID */
#define IOEP_LPM_REQ_GUID 0x5077612
@@ -282,6 +281,7 @@ static const struct pmc_reg_map arl_socs_reg_map = {
.etr3_offset = ETR3_OFFSET,
.pson_residency_offset = TGL_PSON_RESIDENCY_OFFSET,
.pson_residency_counter_step = TGL_PSON_RES_COUNTER_STEP,
+ .lpm_req_guid = SOCS_LPM_REQ_GUID,
};
static const struct pmc_bit_map arl_pchs_ltr_show_map[] = {
@@ -649,31 +649,24 @@ static const struct pmc_reg_map arl_pchs_reg_map = {
.lpm_num_maps = ADL_LPM_NUM_MAPS,
.lpm_reg_index = ARL_LPM_REG_INDEX,
.etr3_offset = ETR3_OFFSET,
+ .lpm_req_guid = PCHS_LPM_REQ_GUID,
};
-#define PMC_DEVID_SOCM 0x777f
-#define PMC_DEVID_SOCS 0xae7f
-#define PMC_DEVID_IOEP 0x7ecf
-#define PMC_DEVID_PCHS 0x7f27
static struct pmc_info arl_pmc_info_list[] = {
{
- .guid = IOEP_LPM_REQ_GUID,
- .devid = PMC_DEVID_IOEP,
+ .devid = PMC_DEVID_ARL_IOEP,
.map = &mtl_ioep_reg_map,
},
{
- .guid = SOCS_LPM_REQ_GUID,
- .devid = PMC_DEVID_SOCS,
+ .devid = PMC_DEVID_ARL_SOCS,
.map = &arl_socs_reg_map,
},
{
- .guid = PCHS_LPM_REQ_GUID,
- .devid = PMC_DEVID_PCHS,
+ .devid = PMC_DEVID_ARL_PCHS,
.map = &arl_pchs_reg_map,
},
{
- .guid = SOCM_LPM_REQ_GUID,
- .devid = PMC_DEVID_SOCM,
+ .devid = PMC_DEVID_ARL_SOCM,
.map = &mtl_socm_reg_map,
},
{}
@@ -681,6 +674,7 @@ static struct pmc_info arl_pmc_info_list[] = {
#define ARL_NPU_PCI_DEV 0xad1d
#define ARL_GNA_PCI_DEV 0xae4c
+#define ARL_H_NPU_PCI_DEV 0x7d1d
#define ARL_H_GNA_PCI_DEV 0x774c
/*
* Set power state of select devices that do not have drivers to D3
@@ -694,7 +688,7 @@ static void arl_d3_fixup(void)
static void arl_h_d3_fixup(void)
{
- pmc_core_set_device_d3(ARL_NPU_PCI_DEV);
+ pmc_core_set_device_d3(ARL_H_NPU_PCI_DEV);
pmc_core_set_device_d3(ARL_H_GNA_PCI_DEV);
}
@@ -724,22 +718,28 @@ static int arl_h_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_
return generic_core_init(pmcdev, pmc_dev_info);
}
+static u32 ARL_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, 0x0};
struct pmc_dev_info arl_pmc_dev = {
.pci_func = 0,
- .dmu_guid = ARL_PMT_DMU_GUID,
+ .dmu_guids = ARL_PMT_DMU_GUIDS,
.regmap_list = arl_pmc_info_list,
.map = &arl_socs_reg_map,
+ .sub_req_show = &pmc_core_substate_req_regs_fops,
.suspend = cnl_suspend,
.resume = arl_resume,
.init = arl_core_init,
+ .sub_req = pmc_core_pmt_get_lpm_req,
};
+static u32 ARL_H_PMT_DMU_GUIDS[] = {ARL_PMT_DMU_GUID, ARL_H_PMT_DMU_GUID, 0x0};
struct pmc_dev_info arl_h_pmc_dev = {
.pci_func = 2,
- .dmu_guid = ARL_PMT_DMU_GUID,
+ .dmu_guids = ARL_H_PMT_DMU_GUIDS,
.regmap_list = arl_pmc_info_list,
.map = &mtl_socm_reg_map,
+ .sub_req_show = &pmc_core_substate_req_regs_fops,
.suspend = cnl_suspend,
.resume = arl_h_resume,
.init = arl_h_core_init,
+ .sub_req = pmc_core_pmt_get_lpm_req,
};
diff --git a/drivers/platform/x86/intel/pmc/cnp.c b/drivers/platform/x86/intel/pmc/cnp.c
index 2c5af158bbe2..efea4e1ba52b 100644
--- a/drivers/platform/x86/intel/pmc/cnp.c
+++ b/drivers/platform/x86/intel/pmc/cnp.c
@@ -10,6 +10,7 @@
#include <linux/smp.h>
#include <linux/suspend.h>
+#include <asm/msr.h>
#include "core.h"
/* Cannon Lake: PGD PFET Enable Ack Status Register(s) bitmap */
@@ -227,10 +228,10 @@ static void disable_c1_auto_demote(void *unused)
int cpunum = smp_processor_id();
u64 val;
- rdmsrl(MSR_PKG_CST_CONFIG_CONTROL, val);
+ rdmsrq(MSR_PKG_CST_CONFIG_CONTROL, val);
per_cpu(pkg_cst_config, cpunum) = val;
val &= ~NHM_C1_AUTO_DEMOTE;
- wrmsrl(MSR_PKG_CST_CONFIG_CONTROL, val);
+ wrmsrq(MSR_PKG_CST_CONFIG_CONTROL, val);
pr_debug("%s: cpu:%d cst %llx\n", __func__, cpunum, val);
}
@@ -239,7 +240,7 @@ static void restore_c1_auto_demote(void *unused)
{
int cpunum = smp_processor_id();
- wrmsrl(MSR_PKG_CST_CONFIG_CONTROL, per_cpu(pkg_cst_config, cpunum));
+ wrmsrq(MSR_PKG_CST_CONFIG_CONTROL, per_cpu(pkg_cst_config, cpunum));
pr_debug("%s: cpu:%d cst %llx\n", __func__, cpunum,
per_cpu(pkg_cst_config, cpunum));
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 7a1d11f2914f..d91e1ab842d6 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -11,10 +11,16 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+enum header_type {
+ HEADER_STATUS,
+ HEADER_VALUE,
+};
+
#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/dmi.h>
+#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pci.h>
@@ -22,13 +28,14 @@
#include <linux/suspend.h>
#include <linux/units.h>
-#include <asm/cpuid.h>
+#include <asm/cpuid/api.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include <asm/msr.h>
#include <asm/tsc.h>
#include "core.h"
+#include "ssram_telemetry.h"
#include "../pmt/telemetry.h"
/* Maximum number of modes supported by platfoms that has low power mode capability */
@@ -305,20 +312,20 @@ static inline u8 pmc_core_reg_read_byte(struct pmc *pmc, int offset)
}
static void pmc_core_display_map(struct seq_file *s, int index, int idx, int ip,
- int pmc_index, u8 pf_reg, const struct pmc_bit_map **pf_map)
+ int pmc_idx, u8 pf_reg, const struct pmc_bit_map **pf_map)
{
seq_printf(s, "PMC%d:PCH IP: %-2d - %-32s\tState: %s\n",
- pmc_index, ip, pf_map[idx][index].name,
+ pmc_idx, ip, pf_map[idx][index].name,
pf_map[idx][index].bit_mask & pf_reg ? "Off" : "On");
}
static int pmc_core_ppfear_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- unsigned int i;
+ unsigned int pmc_idx;
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
- struct pmc *pmc = pmcdev->pmcs[i];
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ struct pmc *pmc = pmcdev->pmcs[pmc_idx];
const struct pmc_bit_map **maps;
u8 pf_regs[PPFEAR_MAX_NUM_ENTRIES];
unsigned int index, iter, idx, ip = 0;
@@ -336,7 +343,7 @@ static int pmc_core_ppfear_show(struct seq_file *s, void *unused)
for (idx = 0; maps[idx]; idx++) {
for (index = 0; maps[idx][index].name &&
index < pmc->map->ppfear_buckets * 8; ip++, index++)
- pmc_core_display_map(s, index, idx, ip, i,
+ pmc_core_display_map(s, index, idx, ip, pmc_idx,
pf_regs[index / 8], maps);
}
}
@@ -465,7 +472,7 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore)
struct pmc *pmc;
const struct pmc_reg_map *map;
u32 reg;
- unsigned int pmc_index;
+ unsigned int pmc_idx;
int ltr_index;
ltr_index = value;
@@ -473,8 +480,8 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore)
* is based on the contiguous indexes from ltr_show output.
* pmc index and ltr index needs to be calculated from it.
*/
- for (pmc_index = 0; pmc_index < ARRAY_SIZE(pmcdev->pmcs) && ltr_index >= 0; pmc_index++) {
- pmc = pmcdev->pmcs[pmc_index];
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs) && ltr_index >= 0; pmc_idx++) {
+ pmc = pmcdev->pmcs[pmc_idx];
if (!pmc)
continue;
@@ -491,10 +498,10 @@ int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore)
ltr_index = ltr_index - (map->ltr_ignore_max + 2) - 1;
}
- if (pmc_index >= ARRAY_SIZE(pmcdev->pmcs) || ltr_index < 0)
+ if (pmc_idx >= ARRAY_SIZE(pmcdev->pmcs) || ltr_index < 0)
return -EINVAL;
- pr_debug("ltr_ignore for pmc%d: ltr_index:%d\n", pmc_index, ltr_index);
+ pr_debug("ltr_ignore for pmc%d: ltr_index:%d\n", pmc_idx, ltr_index);
guard(mutex)(&pmcdev->lock);
@@ -629,14 +636,14 @@ static int pmc_core_ltr_show(struct seq_file *s, void *unused)
u64 decoded_snoop_ltr, decoded_non_snoop_ltr, val;
u32 ltr_raw_data, scale;
u16 snoop_ltr, nonsnoop_ltr;
- unsigned int i, index, ltr_index = 0;
+ unsigned int pmc_idx, index, ltr_index = 0;
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
struct pmc *pmc;
const struct pmc_bit_map *map;
u32 ltr_ign_reg;
- pmc = pmcdev->pmcs[i];
+ pmc = pmcdev->pmcs[pmc_idx];
if (!pmc)
continue;
@@ -670,7 +677,7 @@ static int pmc_core_ltr_show(struct seq_file *s, void *unused)
}
seq_printf(s, "%d\tPMC%d:%-32s\tLTR: RAW: 0x%-16x\tNon-Snoop(ns): %-16llu\tSnoop(ns): %-16llu\tLTR_IGNORE: %d\n",
- ltr_index, i, map[index].name, ltr_raw_data,
+ ltr_index, pmc_idx, map[index].name, ltr_raw_data,
decoded_non_snoop_ltr,
decoded_snoop_ltr, ltr_ign_data);
ltr_index++;
@@ -683,15 +690,15 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_ltr);
static int pmc_core_s0ix_blocker_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- unsigned int pmcidx;
+ unsigned int pmc_idx;
- for (pmcidx = 0; pmcidx < ARRAY_SIZE(pmcdev->pmcs); pmcidx++) {
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
const struct pmc_bit_map **maps;
unsigned int arr_size, r_idx;
u32 offset, counter;
struct pmc *pmc;
- pmc = pmcdev->pmcs[pmcidx];
+ pmc = pmcdev->pmcs[pmc_idx];
if (!pmc)
continue;
maps = pmc->map->s0ix_blocker_maps;
@@ -705,7 +712,7 @@ static int pmc_core_s0ix_blocker_show(struct seq_file *s, void *unused)
if (!map->blk)
continue;
counter = pmc_core_reg_read(pmc, offset);
- seq_printf(s, "PMC%d:%-30s %-30d\n", pmcidx,
+ seq_printf(s, "PMC%d:%-30s %-30d\n", pmc_idx,
map->name, counter);
offset += map->blk * S0IX_BLK_SIZE;
}
@@ -717,13 +724,13 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_s0ix_blocker);
static void pmc_core_ltr_ignore_all(struct pmc_dev *pmcdev)
{
- unsigned int i;
+ unsigned int pmc_idx;
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); i++) {
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
struct pmc *pmc;
u32 ltr_ign;
- pmc = pmcdev->pmcs[i];
+ pmc = pmcdev->pmcs[pmc_idx];
if (!pmc)
continue;
@@ -744,12 +751,12 @@ static void pmc_core_ltr_ignore_all(struct pmc_dev *pmcdev)
static void pmc_core_ltr_restore_all(struct pmc_dev *pmcdev)
{
- unsigned int i;
+ unsigned int pmc_idx;
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); i++) {
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
struct pmc *pmc;
- pmc = pmcdev->pmcs[i];
+ pmc = pmcdev->pmcs[pmc_idx];
if (!pmc)
continue;
@@ -769,16 +776,26 @@ static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset,
static int pmc_core_substate_res_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
- const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
- u32 offset = pmc->map->lpm_residency_offset;
- int mode;
+ unsigned int pmc_idx;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ int lpm_adj_x2;
+ struct pmc *pmc;
+ u32 offset;
+ u8 mode;
- seq_printf(s, "%-10s %-15s\n", "Substate", "Residency");
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc)
+ continue;
- pmc_for_each_mode(mode, pmcdev) {
- seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode],
- adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
+ lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
+ offset = pmc->map->lpm_residency_offset;
+
+ seq_printf(s, "pmc%u %10s %15s\n", pmc_idx, "Substate", "Residency");
+ pmc_for_each_mode(mode, pmc) {
+ seq_printf(s, "%15s %15llu\n", pmc_lpm_modes[mode],
+ adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
+ }
}
return 0;
@@ -788,10 +805,10 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_res);
static int pmc_core_substate_sts_regs_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- unsigned int i;
+ unsigned int pmc_idx;
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
- struct pmc *pmc = pmcdev->pmcs[i];
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ struct pmc *pmc = pmcdev->pmcs[pmc_idx];
const struct pmc_bit_map **maps;
u32 offset;
@@ -799,7 +816,7 @@ static int pmc_core_substate_sts_regs_show(struct seq_file *s, void *unused)
continue;
maps = pmc->map->lpm_sts;
offset = pmc->map->lpm_status_offset;
- pmc_core_lpm_display(pmc, NULL, s, offset, i, "STATUS", maps);
+ pmc_core_lpm_display(pmc, NULL, s, offset, pmc_idx, "STATUS", maps);
}
return 0;
@@ -809,10 +826,10 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_sts_regs);
static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- unsigned int i;
+ unsigned int pmc_idx;
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
- struct pmc *pmc = pmcdev->pmcs[i];
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ struct pmc *pmc = pmcdev->pmcs[pmc_idx];
const struct pmc_bit_map **maps;
u32 offset;
@@ -820,37 +837,105 @@ static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused)
continue;
maps = pmc->map->lpm_sts;
offset = pmc->map->lpm_live_status_offset;
- pmc_core_lpm_display(pmc, NULL, s, offset, i, "LIVE_STATUS", maps);
+ pmc_core_lpm_display(pmc, NULL, s, offset, pmc_idx, "LIVE_STATUS", maps);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs);
-static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index)
+static void pmc_core_substate_req_header_show(struct seq_file *s, int pmc_index,
+ enum header_type type)
{
struct pmc_dev *pmcdev = s->private;
- int mode;
+ struct pmc *pmc = pmcdev->pmcs[pmc_index];
+ u8 mode;
- seq_printf(s, "%30s |", "Element");
- pmc_for_each_mode(mode, pmcdev)
+ seq_printf(s, "%40s |", "Element");
+ pmc_for_each_mode(mode, pmc)
seq_printf(s, " %9s |", pmc_lpm_modes[mode]);
- seq_printf(s, " %9s |", "Status");
- seq_printf(s, " %11s |\n", "Live Status");
+ if (type == HEADER_STATUS) {
+ seq_printf(s, " %9s |", "Status");
+ seq_printf(s, " %11s |\n", "Live Status");
+ } else {
+ seq_printf(s, " %9s |\n", "Value");
+ }
+}
+
+static int pmc_core_substate_blk_req_show(struct seq_file *s, void *unused)
+{
+ struct pmc_dev *pmcdev = s->private;
+ unsigned int pmc_idx;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
+ const struct pmc_bit_map **maps;
+ unsigned int arr_size, r_idx;
+ u32 offset, counter;
+ u32 *lpm_req_regs;
+ struct pmc *pmc;
+
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc || !pmc->lpm_req_regs)
+ continue;
+
+ lpm_req_regs = pmc->lpm_req_regs;
+ maps = pmc->map->s0ix_blocker_maps;
+ offset = pmc->map->s0ix_blocker_offset;
+ arr_size = pmc_core_lpm_get_arr_size(maps);
+
+ /* Display the header */
+ pmc_core_substate_req_header_show(s, pmc_idx, HEADER_VALUE);
+
+ for (r_idx = 0; r_idx < arr_size; r_idx++) {
+ const struct pmc_bit_map *map;
+
+ for (map = maps[r_idx]; map->name; map++) {
+ u8 mode;
+
+ if (!map->blk)
+ continue;
+
+ counter = pmc_core_reg_read(pmc, offset);
+ seq_printf(s, "pmc%u: %34s |", pmc_idx, map->name);
+ pmc_for_each_mode(mode, pmc) {
+ bool required = *lpm_req_regs & BIT(mode);
+
+ seq_printf(s, " %9s |", required ? "Required" : " ");
+ }
+ seq_printf(s, " %9u |\n", counter);
+ offset += map->blk * S0IX_BLK_SIZE;
+ lpm_req_regs++;
+ }
+ }
+ }
+ return 0;
+}
+
+static int pmc_core_substate_blk_req_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pmc_core_substate_blk_req_show, inode->i_private);
}
+const struct file_operations pmc_core_substate_blk_req_fops = {
+ .owner = THIS_MODULE,
+ .open = pmc_core_substate_blk_req_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
u32 sts_offset;
u32 sts_offset_live;
u32 *lpm_req_regs;
- unsigned int mp, pmc_index;
+ unsigned int mp, pmc_idx;
int num_maps;
- for (pmc_index = 0; pmc_index < ARRAY_SIZE(pmcdev->pmcs); ++pmc_index) {
- struct pmc *pmc = pmcdev->pmcs[pmc_index];
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ struct pmc *pmc = pmcdev->pmcs[pmc_idx];
const struct pmc_bit_map **maps;
if (!pmc)
@@ -871,7 +956,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
continue;
/* Display the header */
- pmc_core_substate_req_header_show(s, pmc_index);
+ pmc_core_substate_req_header_show(s, pmc_idx, HEADER_STATUS);
/* Loop over maps */
for (mp = 0; mp < num_maps; mp++) {
@@ -879,14 +964,15 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
u32 lpm_status;
u32 lpm_status_live;
const struct pmc_bit_map *map;
- int mode, i, len = 32;
+ int i, len = 32;
+ u8 mode;
/*
* Capture the requirements and create a mask so that we only
* show an element if it's required for at least one of the
* enabled low power modes
*/
- pmc_for_each_mode(mode, pmcdev)
+ pmc_for_each_mode(mode, pmc)
req_mask |= lpm_req_regs[mp + (mode * num_maps)];
/* Get the last latched status for this map */
@@ -909,10 +995,10 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
}
/* Display the element name in the first column */
- seq_printf(s, "pmc%d: %26s |", pmc_index, map[i].name);
+ seq_printf(s, "pmc%d: %34s |", pmc_idx, map[i].name);
/* Loop over the enabled states and display if required */
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
bool required = lpm_req_regs[mp + (mode * num_maps)] &
bit_mask;
seq_printf(s, " %9s |", required ? "Required" : " ");
@@ -930,7 +1016,19 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
}
return 0;
}
-DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs);
+
+static int pmc_core_substate_req_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, pmc_core_substate_req_regs_show, inode->i_private);
+}
+
+const struct file_operations pmc_core_substate_req_regs_fops = {
+ .owner = THIS_MODULE,
+ .open = pmc_core_substate_req_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
static unsigned int pmc_core_get_crystal_freq(void)
{
@@ -979,7 +1077,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
bool c10;
u32 reg;
- int mode;
+ u8 mode;
reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset);
if (reg & LPM_STS_LATCH_MODE) {
@@ -990,7 +1088,7 @@ static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
c10 = true;
}
- pmc_for_each_mode(mode, pmcdev) {
+ pmc_for_each_mode(mode, pmc) {
if ((BIT(mode) & reg) && !c10)
seq_printf(s, " [%s]", pmc_lpm_modes[mode]);
else
@@ -1011,8 +1109,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
bool clear = false, c10 = false;
unsigned char buf[8];
- int m, mode;
+ int mode;
u32 reg;
+ u8 m;
if (count > sizeof(buf) - 1)
return -EINVAL;
@@ -1029,7 +1128,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
mode = sysfs_match_string(pmc_lpm_modes, buf);
/* Check string matches enabled mode */
- pmc_for_each_mode(m, pmcdev)
+ pmc_for_each_mode(m, pmc)
if (mode == m)
break;
@@ -1082,7 +1181,7 @@ static int pmc_core_pkgc_show(struct seq_file *s, void *unused)
unsigned int index;
for (index = 0; map[index].name ; index++) {
- if (rdmsrl_safe(map[index].bit_mask, &pcstate_count))
+ if (rdmsrq_safe(map[index].bit_mask, &pcstate_count))
continue;
pcstate_count *= 1000;
@@ -1125,15 +1224,15 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order)
return true;
}
-void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
+static void pmc_core_pmc_get_low_power_modes(struct pmc_dev *pmcdev, struct pmc *pmc)
{
- struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI;
u8 mode_order[LPM_MAX_NUM_MODES];
u32 lpm_pri;
u32 lpm_en;
+ u8 mode;
unsigned int i;
- int mode, p;
+ int p;
/* Use LPM Maps to indicate support for substates */
if (!pmc->map->lpm_num_maps)
@@ -1144,12 +1243,11 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
* Lower byte is enough to cover the number of lpm modes for all
* platforms and hence mask the upper 3 bytes.
*/
- pmcdev->num_lpm_modes = hweight32(lpm_en & 0xFF);
+ pmc->num_lpm_modes = hweight32(lpm_en & 0xFF);
/* Read 32 bit LPM_PRI register */
lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset);
-
/*
* If lpm_pri value passes verification, then override the default
* modes here. Otherwise stick with the default.
@@ -1159,7 +1257,7 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
for (mode = 0; mode < LPM_MAX_NUM_MODES; mode++)
pri_order[mode_order[mode]] = mode;
else
- dev_warn(&pmcdev->pdev->dev,
+ dev_dbg(&pmcdev->pdev->dev,
"Assuming a default substate order for this platform\n");
/*
@@ -1168,12 +1266,27 @@ void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
*/
i = 0;
for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) {
- int mode = pri_order[p];
+ u8 mode = pri_order[p];
if (!(BIT(mode) & lpm_en))
continue;
- pmcdev->lpm_en_modes[i++] = mode;
+ pmc->lpm_en_modes[i++] = mode;
+ }
+}
+
+static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
+{
+ unsigned int pmc_idx;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); pmc_idx++) {
+ struct pmc *pmc;
+
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc)
+ continue;
+
+ pmc_core_pmc_get_low_power_modes(pmcdev, pmc);
}
}
@@ -1196,7 +1309,20 @@ int get_primary_reg_base(struct pmc *pmc)
return 0;
}
-void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 guid)
+static struct telem_endpoint *pmc_core_register_endpoint(struct pci_dev *pcidev, u32 *guids)
+{
+ struct telem_endpoint *ep;
+ unsigned int i;
+
+ for (i = 0; guids[i]; i++) {
+ ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, guids[i], 0);
+ if (!IS_ERR(ep))
+ return ep;
+ }
+ return ERR_PTR(-ENODEV);
+}
+
+void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids)
{
struct telem_endpoint *ep;
struct pci_dev *pcidev;
@@ -1207,7 +1333,7 @@ void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 guid)
return;
}
- ep = pmt_telem_find_and_register_endpoint(pcidev, guid, 0);
+ ep = pmc_core_register_endpoint(pcidev, guids);
pci_dev_put(pcidev);
if (IS_ERR(ep)) {
dev_err(&pmcdev->pdev->dev,
@@ -1217,8 +1343,6 @@ void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 guid)
}
pmcdev->punit_ep = ep;
-
- pmcdev->has_die_c6 = true;
pmcdev->die_c6_offset = MTL_PMT_DMU_DIE_C6_OFFSET;
}
@@ -1263,7 +1387,7 @@ static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
debugfs_remove_recursive(pmcdev->dbgfs_dir);
}
-static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
+static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
{
struct pmc *primary_pmc = pmcdev->pmcs[PMC_IDX_MAIN];
struct dentry *dir;
@@ -1330,7 +1454,7 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
if (primary_pmc->lpm_req_regs) {
debugfs_create_file("substate_requirements", 0444,
pmcdev->dbgfs_dir, pmcdev,
- &pmc_core_substate_req_regs_fops);
+ pmc_dev_info->sub_req_show);
}
if (primary_pmc->map->pson_residency_offset && pmc_core_is_pson_residency_enabled(pmcdev)) {
@@ -1338,7 +1462,7 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
pmcdev->dbgfs_dir, primary_pmc, &pmc_core_pson_residency);
}
- if (pmcdev->has_die_c6) {
+ if (pmcdev->punit_ep) {
debugfs_create_file("die_c6_us_show", 0444,
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_die_c6_us_fops);
@@ -1346,6 +1470,213 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
}
/*
+ * This function retrieves low power mode requirement data from PMC Low
+ * Power Mode (LPM) table.
+ *
+ * In telemetry space, the LPM table contains a 4 byte header followed
+ * by 8 consecutive mode blocks (one for each LPM mode). Each block
+ * has a 4 byte header followed by a set of registers that describe the
+ * IP state requirements for the given mode. The IP mapping is platform
+ * specific but the same for each block, making for easy analysis.
+ * Platforms only use a subset of the space to track the requirements
+ * for their IPs. Callers provide the requirement registers they use as
+ * a list of indices. Each requirement register is associated with an
+ * IP map that's maintained by the caller.
+ *
+ * Header
+ * +----+----------------------------+----------------------------+
+ * | 0 | REVISION | ENABLED MODES |
+ * +----+--------------+-------------+-------------+--------------+
+ *
+ * Low Power Mode 0 Block
+ * +----+--------------+-------------+-------------+--------------+
+ * | 1 | SUB ID | SIZE | MAJOR | MINOR |
+ * +----+--------------+-------------+-------------+--------------+
+ * | 2 | LPM0 Requirements 0 |
+ * +----+---------------------------------------------------------+
+ * | | ... |
+ * +----+---------------------------------------------------------+
+ * | 29 | LPM0 Requirements 27 |
+ * +----+---------------------------------------------------------+
+ *
+ * ...
+ *
+ * Low Power Mode 7 Block
+ * +----+--------------+-------------+-------------+--------------+
+ * | | SUB ID | SIZE | MAJOR | MINOR |
+ * +----+--------------+-------------+-------------+--------------+
+ * | 60 | LPM7 Requirements 0 |
+ * +----+---------------------------------------------------------+
+ * | | ... |
+ * +----+---------------------------------------------------------+
+ * | 87 | LPM7 Requirements 27 |
+ * +----+---------------------------------------------------------+
+ *
+ */
+int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep)
+{
+ const u8 *lpm_indices;
+ int num_maps, mode_offset = 0;
+ int ret, lpm_size;
+ u8 mode;
+
+ lpm_indices = pmc->map->lpm_reg_index;
+ num_maps = pmc->map->lpm_num_maps;
+ lpm_size = LPM_MAX_NUM_MODES * num_maps;
+
+ pmc->lpm_req_regs = devm_kzalloc(&pmcdev->pdev->dev,
+ lpm_size * sizeof(u32),
+ GFP_KERNEL);
+ if (!pmc->lpm_req_regs)
+ return -ENOMEM;
+
+ mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET;
+ pmc_for_each_mode(mode, pmc) {
+ u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps);
+ int m;
+
+ for (m = 0; m < num_maps; m++) {
+ u8 sample_id = lpm_indices[m] + mode_offset;
+
+ ret = pmt_telem_read32(ep, sample_id, req_offset, 1);
+ if (ret) {
+ dev_err(&pmcdev->pdev->dev,
+ "couldn't read Low Power Mode requirements: %d\n", ret);
+ return ret;
+ }
+ ++req_offset;
+ }
+ mode_offset += LPM_REG_COUNT + LPM_MODE_OFFSET;
+ }
+ return ret;
+}
+
+int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
+ struct telem_endpoint *ep)
+{
+ u32 num_blocker, sample_offset;
+ unsigned int index;
+ u32 *req_offset;
+ int ret;
+
+ num_blocker = pmc->map->num_s0ix_blocker;
+ sample_offset = pmc->map->blocker_req_offset;
+
+ pmc->lpm_req_regs = devm_kcalloc(&pmcdev->pdev->dev, num_blocker,
+ sizeof(u32), GFP_KERNEL);
+ if (!pmc->lpm_req_regs)
+ return -ENOMEM;
+
+ req_offset = pmc->lpm_req_regs;
+ for (index = 0; index < num_blocker; index++, req_offset++) {
+ ret = pmt_telem_read32(ep, index + sample_offset, req_offset, 1);
+ if (ret) {
+ dev_err(&pmcdev->pdev->dev,
+ "couldn't read Low Power Mode requirements: %d\n", ret);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int pmc_core_get_telem_info(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
+{
+ struct pci_dev *pcidev __free(pci_dev_put) = NULL;
+ struct telem_endpoint *ep;
+ unsigned int pmc_idx;
+ int ret;
+
+ pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, pmc_dev_info->pci_func));
+ if (!pcidev)
+ return -ENODEV;
+
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ struct pmc *pmc;
+
+ pmc = pmcdev->pmcs[pmc_idx];
+ if (!pmc)
+ continue;
+
+ if (!pmc->map->lpm_req_guid)
+ return -ENXIO;
+
+ ep = pmt_telem_find_and_register_endpoint(&pcidev->dev, pmc->map->lpm_req_guid, 0);
+ if (IS_ERR(ep)) {
+ dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", ep);
+ return -EPROBE_DEFER;
+ }
+
+ ret = pmc_dev_info->sub_req(pmcdev, pmc, ep);
+ pmt_telem_unregister_endpoint(ep);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct pmc_reg_map *pmc_core_find_regmap(struct pmc_info *list, u16 devid)
+{
+ for (; list->map; ++list)
+ if (devid == list->devid)
+ return list->map;
+
+ return NULL;
+}
+
+static int pmc_core_pmc_add(struct pmc_dev *pmcdev, unsigned int pmc_idx)
+
+{
+ struct pmc_ssram_telemetry pmc_ssram_telemetry;
+ const struct pmc_reg_map *map;
+ struct pmc *pmc;
+ int ret;
+
+ ret = pmc_ssram_telemetry_get_pmc_info(pmc_idx, &pmc_ssram_telemetry);
+ if (ret)
+ return ret;
+
+ map = pmc_core_find_regmap(pmcdev->regmap_list, pmc_ssram_telemetry.devid);
+ if (!map)
+ return -ENODEV;
+
+ pmc = pmcdev->pmcs[pmc_idx];
+ /* Memory for primary PMC has been allocated */
+ if (!pmc) {
+ pmc = devm_kzalloc(&pmcdev->pdev->dev, sizeof(*pmc), GFP_KERNEL);
+ if (!pmc)
+ return -ENOMEM;
+ }
+
+ pmc->map = map;
+ pmc->base_addr = pmc_ssram_telemetry.base_addr;
+ pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length);
+
+ if (!pmc->regbase) {
+ devm_kfree(&pmcdev->pdev->dev, pmc);
+ return -ENOMEM;
+ }
+
+ pmcdev->pmcs[pmc_idx] = pmc;
+
+ return 0;
+}
+
+static int pmc_core_ssram_get_reg_base(struct pmc_dev *pmcdev)
+{
+ int ret;
+
+ ret = pmc_core_pmc_add(pmcdev, PMC_IDX_MAIN);
+ if (ret)
+ return ret;
+
+ pmc_core_pmc_add(pmcdev, PMC_IDX_IOE);
+ pmc_core_pmc_add(pmcdev, PMC_IDX_PCH);
+
+ return 0;
+}
+
+/*
* When supported, ssram init is used to achieve all available PMCs.
* If ssram init fails, this function uses legacy method to at least get the
* primary PMC.
@@ -1362,10 +1693,18 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
ssram = pmc_dev_info->regmap_list != NULL;
if (ssram) {
pmcdev->regmap_list = pmc_dev_info->regmap_list;
- ret = pmc_core_ssram_init(pmcdev, pmc_dev_info->pci_func);
+ ret = pmc_core_ssram_get_reg_base(pmcdev);
+ /*
+ * EAGAIN error code indicates Intel PMC SSRAM Telemetry driver
+ * has not finished probe and PMC info is not available yet. Try
+ * again later.
+ */
+ if (ret == -EAGAIN)
+ return -EPROBE_DEFER;
+
if (ret) {
dev_warn(&pmcdev->pdev->dev,
- "ssram init failed, %d, using legacy init\n", ret);
+ "Failed to get PMC info from SSRAM, %d, using legacy init\n", ret);
ssram = false;
}
}
@@ -1378,13 +1717,29 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
}
pmc_core_get_low_power_modes(pmcdev);
- if (pmc_dev_info->dmu_guid)
- pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guid);
+ if (pmc_dev_info->dmu_guids)
+ pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guids);
- if (ssram)
- return pmc_core_ssram_get_lpm_reqs(pmcdev);
+ if (ssram) {
+ ret = pmc_core_get_telem_info(pmcdev, pmc_dev_info);
+ if (ret)
+ goto unmap_regbase;
+ }
return 0;
+
+unmap_regbase:
+ for (unsigned int pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ struct pmc *pmc = pmcdev->pmcs[pmc_idx];
+
+ if (pmc && pmc->regbase)
+ iounmap(pmc->regbase);
+ }
+
+ if (pmcdev->punit_ep)
+ pmt_telem_unregister_endpoint(pmcdev->punit_ep);
+
+ return ret;
}
static const struct x86_cpu_id intel_pmc_core_ids[] = {
@@ -1408,12 +1763,14 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
X86_MATCH_VFM(INTEL_RAPTORLAKE_P, &tgl_l_pmc_dev),
X86_MATCH_VFM(INTEL_RAPTORLAKE, &adl_pmc_dev),
X86_MATCH_VFM(INTEL_RAPTORLAKE_S, &adl_pmc_dev),
+ X86_MATCH_VFM(INTEL_BARTLETTLAKE, &adl_pmc_dev),
X86_MATCH_VFM(INTEL_METEORLAKE_L, &mtl_pmc_dev),
X86_MATCH_VFM(INTEL_ARROWLAKE, &arl_pmc_dev),
X86_MATCH_VFM(INTEL_ARROWLAKE_H, &arl_h_pmc_dev),
X86_MATCH_VFM(INTEL_ARROWLAKE_U, &arl_h_pmc_dev),
X86_MATCH_VFM(INTEL_LUNARLAKE_M, &lnl_pmc_dev),
X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &ptl_pmc_dev),
+ X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &wcl_pmc_dev),
{}
};
@@ -1466,25 +1823,19 @@ static void pmc_core_do_dmi_quirks(struct pmc *pmc)
static void pmc_core_clean_structure(struct platform_device *pdev)
{
struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
- unsigned int i;
+ unsigned int pmc_idx;
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
- struct pmc *pmc = pmcdev->pmcs[i];
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ struct pmc *pmc = pmcdev->pmcs[pmc_idx];
- if (pmc)
+ if (pmc && pmc->regbase)
iounmap(pmc->regbase);
}
- if (pmcdev->ssram_pcidev) {
- pci_dev_put(pmcdev->ssram_pcidev);
- pci_disable_device(pmcdev->ssram_pcidev);
- }
-
if (pmcdev->punit_ep)
pmt_telem_unregister_endpoint(pmcdev->punit_ep);
platform_set_drvdata(pdev, NULL);
- mutex_destroy(&pmcdev->lock);
}
static int pmc_core_probe(struct platform_device *pdev)
@@ -1529,7 +1880,9 @@ static int pmc_core_probe(struct platform_device *pdev)
if (!pmcdev->pkgc_res_cnt)
return -ENOMEM;
- mutex_init(&pmcdev->lock);
+ ret = devm_mutex_init(&pdev->dev, &pmcdev->lock);
+ if (ret)
+ return ret;
if (pmc_dev_info->init)
ret = pmc_dev_info->init(pmcdev, pmc_dev_info);
@@ -1537,14 +1890,14 @@ static int pmc_core_probe(struct platform_device *pdev)
ret = generic_core_init(pmcdev, pmc_dev_info);
if (ret) {
- pmc_core_clean_structure(pdev);
+ platform_set_drvdata(pdev, NULL);
return ret;
}
pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(primary_pmc);
pmc_core_do_dmi_quirks(primary_pmc);
- pmc_core_dbgfs_register(pmcdev);
+ pmc_core_dbgfs_register(pmcdev, pmc_dev_info);
pm_report_max_hw_sleep(FIELD_MAX(SLP_S0_RES_COUNTER_MASK) *
pmc_core_adjust_slp_s0_step(primary_pmc, 1));
@@ -1587,7 +1940,7 @@ static __maybe_unused int pmc_core_suspend(struct device *dev)
/* Save PKGC residency for checking later */
for (i = 0; i < pmcdev->num_of_pkgc; i++) {
- if (rdmsrl_safe(msr_map[i].bit_mask, &pmcdev->pkgc_res_cnt[i]))
+ if (rdmsrq_safe(msr_map[i].bit_mask, &pmcdev->pkgc_res_cnt[i]))
return -EIO;
}
@@ -1603,7 +1956,7 @@ static inline bool pmc_core_is_deepest_pkgc_failed(struct pmc_dev *pmcdev)
u32 deepest_pkgc_msr = msr_map[pmcdev->num_of_pkgc - 1].bit_mask;
u64 deepest_pkgc_residency;
- if (rdmsrl_safe(deepest_pkgc_msr, &deepest_pkgc_residency))
+ if (rdmsrq_safe(deepest_pkgc_msr, &deepest_pkgc_residency))
return false;
if (deepest_pkgc_residency == pmcdev->pkgc_res_cnt[pmcdev->num_of_pkgc - 1])
@@ -1633,7 +1986,7 @@ int pmc_core_resume_common(struct pmc_dev *pmcdev)
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_bit_map **maps = pmc->map->lpm_sts;
int offset = pmc->map->lpm_status_offset;
- unsigned int i;
+ unsigned int pmc_idx, i;
/* Check if the syspend used S0ix */
if (pm_suspend_via_firmware())
@@ -1655,7 +2008,7 @@ int pmc_core_resume_common(struct pmc_dev *pmcdev)
for (i = 0; i < pmcdev->num_of_pkgc; i++) {
u64 pc_cnt;
- if (!rdmsrl_safe(msr_map[i].bit_mask, &pc_cnt)) {
+ if (!rdmsrq_safe(msr_map[i].bit_mask, &pc_cnt)) {
dev_info(dev, "Prev %s cnt = 0x%llx, Current %s cnt = 0x%llx\n",
msr_map[i].name, pmcdev->pkgc_res_cnt[i],
msr_map[i].name, pc_cnt);
@@ -1671,13 +2024,13 @@ int pmc_core_resume_common(struct pmc_dev *pmcdev)
if (pmc->map->slps0_dbg_maps)
pmc_core_slps0_display(pmc, dev, NULL);
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
- struct pmc *pmc = pmcdev->pmcs[i];
+ for (pmc_idx = 0; pmc_idx < ARRAY_SIZE(pmcdev->pmcs); ++pmc_idx) {
+ struct pmc *pmc = pmcdev->pmcs[pmc_idx];
if (!pmc)
continue;
if (pmc->map->lpm_sts)
- pmc_core_lpm_display(pmc, dev, NULL, offset, i, "STATUS", maps);
+ pmc_core_lpm_display(pmc, dev, NULL, offset, pmc_idx, "STATUS", maps);
}
return 0;
@@ -1719,5 +2072,6 @@ static struct platform_driver pmc_core_driver = {
module_platform_driver(pmc_core_driver);
+MODULE_IMPORT_NS("INTEL_PMT_TELEMETRY");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Intel PMC Core Driver");
diff --git a/drivers/platform/x86/intel/pmc/core.h b/drivers/platform/x86/intel/pmc/core.h
index 945a1c440cca..118c8740ad3a 100644
--- a/drivers/platform/x86/intel/pmc/core.h
+++ b/drivers/platform/x86/intel/pmc/core.h
@@ -24,6 +24,11 @@ struct telem_endpoint;
#define MAX_NUM_PMC 3
#define S0IX_BLK_SIZE 4
+/* PCH query */
+#define LPM_HEADER_OFFSET 1
+#define LPM_REG_COUNT 28
+#define LPM_MODE_OFFSET 1
+
/* Sunrise Point Power Management Controller PCI Device ID */
#define SPT_PMC_PCI_DEVICE_ID 0x9d21
#define SPT_PMC_BASE_ADDR_OFFSET 0x48
@@ -277,7 +282,8 @@ enum ppfear_regs {
/* Die C6 from PUNIT telemetry */
#define MTL_PMT_DMU_DIE_C6_OFFSET 15
#define MTL_PMT_DMU_GUID 0x1A067102
-#define ARL_PMT_DMU_GUID 0x1A06A000
+#define ARL_PMT_DMU_GUID 0x1A06A102
+#define ARL_H_PMT_DMU_GUID 0x1A06A101
#define LNL_PMC_MMIO_REG_LEN 0x2708
#define LNL_PMC_LTR_OSSE 0x1B88
@@ -292,6 +298,36 @@ enum ppfear_regs {
#define PTL_PMC_LTR_CUR_ASLT 0x1C28
#define PTL_PMC_LTR_CUR_PLT 0x1C2C
#define PTL_PCD_PMC_MMIO_REG_LEN 0x31A8
+#define PTL_NUM_S0IX_BLOCKER 106
+#define PTL_BLK_REQ_OFFSET 55
+
+/* Wildcat Lake */
+#define WCL_PMC_LTR_RESERVED 0x1B64
+#define WCL_PCD_PMC_MMIO_REG_LEN 0x3178
+#define WCL_NUM_S0IX_BLOCKER 94
+#define WCL_BLK_REQ_OFFSET 50
+
+/* SSRAM PMC Device ID */
+/* LNL */
+#define PMC_DEVID_LNL_SOCM 0xa87f
+
+/* PTL */
+#define PMC_DEVID_PTL_PCDH 0xe37f
+#define PMC_DEVID_PTL_PCDP 0xe47f
+
+/* WCL */
+#define PMC_DEVID_WCL_PCDN 0x4d7f
+
+/* ARL */
+#define PMC_DEVID_ARL_SOCM 0x777f
+#define PMC_DEVID_ARL_SOCS 0xae7f
+#define PMC_DEVID_ARL_IOEP 0x7ecf
+#define PMC_DEVID_ARL_PCHS 0x7f27
+
+/* MTL */
+#define PMC_DEVID_MTL_SOCM 0x7e7f
+#define PMC_DEVID_MTL_IOEP 0x7ecf
+#define PMC_DEVID_MTL_IOEM 0x7ebf
extern const char *pmc_lpm_modes[];
@@ -320,6 +356,9 @@ struct pmc_bit_map {
* @pm_read_disable_bit: Bit index to read PMC_READ_DISABLE
* @slps0_dbg_offset: PWRMBASE offset to SLP_S0_DEBUG_REG*
* @s0ix_blocker_offset PWRMBASE offset to S0ix blocker counter
+ * @num_s0ix_blocker: Number of S0ix blockers
+ * @blocker_req_offset: Telemetry offset to S0ix blocker low power mode substate requirement table
+ * @lpm_req_guid: Telemetry GUID to read low power mode substate requirement table
*
* Each PCH has unique set of register offsets and bit indexes. This structure
* captures them to have a common implementation.
@@ -345,6 +384,8 @@ struct pmc_reg_map {
const u32 ltr_ignore_max;
const u32 pm_vric1_offset;
const u32 s0ix_blocker_offset;
+ const u32 num_s0ix_blocker;
+ const u32 blocker_req_offset;
/* Low Power Mode registers */
const int lpm_num_maps;
const int lpm_num_modes;
@@ -359,6 +400,8 @@ struct pmc_reg_map {
const u8 *lpm_reg_index;
const u32 pson_residency_offset;
const u32 pson_residency_counter_step;
+ /* GUID for telemetry regions */
+ const u32 lpm_req_guid;
};
/**
@@ -368,7 +411,6 @@ struct pmc_reg_map {
* specific attributes
*/
struct pmc_info {
- u32 guid;
u16 devid;
const struct pmc_reg_map *map;
};
@@ -381,6 +423,8 @@ struct pmc_info {
* specific attributes
* @lpm_req_regs: List of substate requirements
* @ltr_ign: Holds LTR ignore data while suspended
+ * @num_lpm_modes: Count of enabled modes
+ * @lpm_en_modes: Array of enabled modes from lowest to highest priority
*
* pmc contains info about one power management controller device.
*/
@@ -390,13 +434,14 @@ struct pmc {
const struct pmc_reg_map *map;
u32 *lpm_req_regs;
u32 ltr_ign;
+ u8 num_lpm_modes;
+ u8 lpm_en_modes[LPM_MAX_NUM_MODES];
};
/**
* struct pmc_dev - pmc device structure
* @devs: pointer to an array of pmc pointers
* @pdev: pointer to platform_device struct
- * @ssram_pcidev: pointer to pci device struct for the PMC SSRAM
* @crystal_freq: crystal frequency from cpuid
* @dbgfs_dir: path to debugfs interface
* @pmc_xram_read_bit: flag to indicate whether PMC XRAM shadow registers
@@ -405,8 +450,6 @@ struct pmc {
* @pkgc_res_cnt: Array of PKGC residency counters
* @num_of_pkgc: Number of PKGC
* @s0ix_counter: S0ix residency (step adjusted)
- * @num_lpm_modes: Count of enabled modes
- * @lpm_en_modes: Array of enabled modes from lowest to highest priority
* @suspend: Function to perform platform specific suspend
* @resume: Function to perform platform specific resume
*
@@ -416,21 +459,17 @@ struct pmc_dev {
struct pmc *pmcs[MAX_NUM_PMC];
struct dentry *dbgfs_dir;
struct platform_device *pdev;
- struct pci_dev *ssram_pcidev;
unsigned int crystal_freq;
int pmc_xram_read_bit;
struct mutex lock; /* generic mutex lock for PMC Core */
u64 s0ix_counter;
- int num_lpm_modes;
- int lpm_en_modes[LPM_MAX_NUM_MODES];
void (*suspend)(struct pmc_dev *pmcdev);
int (*resume)(struct pmc_dev *pmcdev);
u64 *pkgc_res_cnt;
u8 num_of_pkgc;
- bool has_die_c6;
u32 die_c6_offset;
struct telem_endpoint *punit_ep;
struct pmc_info *regmap_list;
@@ -446,24 +485,28 @@ enum pmc_index {
/**
* struct pmc_dev_info - Structure to keep PMC device info
* @pci_func: Function number of the primary PMC
- * @dmu_guid: Die Management Unit GUID
+ * @dmu_guids: List of Die Management Unit GUID
* @regmap_list: Pointer to a list of pmc_info structure that could be
* available for the platform. When set, this field implies
* SSRAM support.
* @map: Pointer to a pmc_reg_map struct that contains platform
* specific attributes of the primary PMC
+ * @sub_req_show: File operations to show substate requirements
* @suspend: Function to perform platform specific suspend
* @resume: Function to perform platform specific resume
* @init: Function to perform platform specific init action
+ * @sub_req: Function to achieve low power mode substate requirements
*/
struct pmc_dev_info {
u8 pci_func;
- u32 dmu_guid;
+ u32 *dmu_guids;
struct pmc_info *regmap_list;
const struct pmc_reg_map *map;
+ const struct file_operations *sub_req_show;
void (*suspend)(struct pmc_dev *pmcdev);
int (*resume)(struct pmc_dev *pmcdev);
int (*init)(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
+ int (*sub_req)(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep);
};
extern const struct pmc_bit_map msr_map[];
@@ -483,19 +526,18 @@ extern const struct pmc_bit_map mtl_socm_vnn_misc_status_map[];
extern const struct pmc_bit_map mtl_socm_signal_status_map[];
extern const struct pmc_reg_map mtl_socm_reg_map;
extern const struct pmc_reg_map mtl_ioep_reg_map;
+extern const struct pmc_bit_map ptl_pcdp_clocksource_status_map[];
+extern const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[];
+extern const struct pmc_bit_map ptl_pcdp_signal_status_map[];
void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev);
-int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev);
int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value, int ignore);
int pmc_core_resume_common(struct pmc_dev *pmcdev);
int get_primary_reg_base(struct pmc *pmc);
-void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev);
-void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 guid);
+void pmc_core_punit_pmt_init(struct pmc_dev *pmcdev, u32 *guids);
void pmc_core_set_device_d3(unsigned int device);
-int pmc_core_ssram_init(struct pmc_dev *pmcdev, int func);
-
int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info);
extern struct pmc_dev_info spt_pmc_dev;
@@ -509,14 +551,21 @@ extern struct pmc_dev_info arl_pmc_dev;
extern struct pmc_dev_info arl_h_pmc_dev;
extern struct pmc_dev_info lnl_pmc_dev;
extern struct pmc_dev_info ptl_pmc_dev;
+extern struct pmc_dev_info wcl_pmc_dev;
void cnl_suspend(struct pmc_dev *pmcdev);
int cnl_resume(struct pmc_dev *pmcdev);
+int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct telem_endpoint *ep);
+int pmc_core_pmt_get_blk_sub_req(struct pmc_dev *pmcdev, struct pmc *pmc,
+ struct telem_endpoint *ep);
+
+extern const struct file_operations pmc_core_substate_req_regs_fops;
+extern const struct file_operations pmc_core_substate_blk_req_fops;
-#define pmc_for_each_mode(mode, pmcdev) \
+#define pmc_for_each_mode(mode, pmc) \
for (unsigned int __i = 0, __cond; \
- __cond = __i < (pmcdev)->num_lpm_modes, \
- __cond && ((mode) = (pmcdev)->lpm_en_modes[__i]), \
+ __cond = __i < (pmc)->num_lpm_modes, \
+ __cond && ((mode) = (pmc)->lpm_en_modes[__i]), \
__cond; \
__i++)
diff --git a/drivers/platform/x86/intel/pmc/core_ssram.c b/drivers/platform/x86/intel/pmc/core_ssram.c
deleted file mode 100644
index 739569803017..000000000000
--- a/drivers/platform/x86/intel/pmc/core_ssram.c
+++ /dev/null
@@ -1,332 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * This file contains functions to handle discovery of PMC metrics located
- * in the PMC SSRAM PCI device.
- *
- * Copyright (c) 2023, Intel Corporation.
- * All Rights Reserved.
- *
- */
-
-#include <linux/cleanup.h>
-#include <linux/intel_vsec.h>
-#include <linux/pci.h>
-#include <linux/io-64-nonatomic-lo-hi.h>
-
-#include "core.h"
-#include "../pmt/telemetry.h"
-
-#define SSRAM_HDR_SIZE 0x100
-#define SSRAM_PWRM_OFFSET 0x14
-#define SSRAM_DVSEC_OFFSET 0x1C
-#define SSRAM_DVSEC_SIZE 0x10
-#define SSRAM_PCH_OFFSET 0x60
-#define SSRAM_IOE_OFFSET 0x68
-#define SSRAM_DEVID_OFFSET 0x70
-
-/* PCH query */
-#define LPM_HEADER_OFFSET 1
-#define LPM_REG_COUNT 28
-#define LPM_MODE_OFFSET 1
-
-DEFINE_FREE(pmc_core_iounmap, void __iomem *, if (_T) iounmap(_T))
-
-static u32 pmc_core_find_guid(struct pmc_info *list, const struct pmc_reg_map *map)
-{
- for (; list->map; ++list)
- if (list->map == map)
- return list->guid;
-
- return 0;
-}
-
-static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc)
-{
- struct telem_endpoint *ep;
- const u8 *lpm_indices;
- int num_maps, mode_offset = 0;
- int ret, mode;
- int lpm_size;
- u32 guid;
-
- lpm_indices = pmc->map->lpm_reg_index;
- num_maps = pmc->map->lpm_num_maps;
- lpm_size = LPM_MAX_NUM_MODES * num_maps;
-
- guid = pmc_core_find_guid(pmcdev->regmap_list, pmc->map);
- if (!guid)
- return -ENXIO;
-
- ep = pmt_telem_find_and_register_endpoint(pmcdev->ssram_pcidev, guid, 0);
- if (IS_ERR(ep)) {
- dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %ld",
- PTR_ERR(ep));
- return -EPROBE_DEFER;
- }
-
- pmc->lpm_req_regs = devm_kzalloc(&pmcdev->pdev->dev,
- lpm_size * sizeof(u32),
- GFP_KERNEL);
- if (!pmc->lpm_req_regs) {
- ret = -ENOMEM;
- goto unregister_ep;
- }
-
- /*
- * PMC Low Power Mode (LPM) table
- *
- * In telemetry space, the LPM table contains a 4 byte header followed
- * by 8 consecutive mode blocks (one for each LPM mode). Each block
- * has a 4 byte header followed by a set of registers that describe the
- * IP state requirements for the given mode. The IP mapping is platform
- * specific but the same for each block, making for easy analysis.
- * Platforms only use a subset of the space to track the requirements
- * for their IPs. Callers provide the requirement registers they use as
- * a list of indices. Each requirement register is associated with an
- * IP map that's maintained by the caller.
- *
- * Header
- * +----+----------------------------+----------------------------+
- * | 0 | REVISION | ENABLED MODES |
- * +----+--------------+-------------+-------------+--------------+
- *
- * Low Power Mode 0 Block
- * +----+--------------+-------------+-------------+--------------+
- * | 1 | SUB ID | SIZE | MAJOR | MINOR |
- * +----+--------------+-------------+-------------+--------------+
- * | 2 | LPM0 Requirements 0 |
- * +----+---------------------------------------------------------+
- * | | ... |
- * +----+---------------------------------------------------------+
- * | 29 | LPM0 Requirements 27 |
- * +----+---------------------------------------------------------+
- *
- * ...
- *
- * Low Power Mode 7 Block
- * +----+--------------+-------------+-------------+--------------+
- * | | SUB ID | SIZE | MAJOR | MINOR |
- * +----+--------------+-------------+-------------+--------------+
- * | 60 | LPM7 Requirements 0 |
- * +----+---------------------------------------------------------+
- * | | ... |
- * +----+---------------------------------------------------------+
- * | 87 | LPM7 Requirements 27 |
- * +----+---------------------------------------------------------+
- *
- */
- mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET;
- pmc_for_each_mode(mode, pmcdev) {
- u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps);
- int m;
-
- for (m = 0; m < num_maps; m++) {
- u8 sample_id = lpm_indices[m] + mode_offset;
-
- ret = pmt_telem_read32(ep, sample_id, req_offset, 1);
- if (ret) {
- dev_err(&pmcdev->pdev->dev,
- "couldn't read Low Power Mode requirements: %d\n", ret);
- devm_kfree(&pmcdev->pdev->dev, pmc->lpm_req_regs);
- goto unregister_ep;
- }
- ++req_offset;
- }
- mode_offset += LPM_REG_COUNT + LPM_MODE_OFFSET;
- }
-
-unregister_ep:
- pmt_telem_unregister_endpoint(ep);
-
- return ret;
-}
-
-int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev)
-{
- int ret, i;
-
- if (!pmcdev->ssram_pcidev)
- return -ENODEV;
-
- for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
- if (!pmcdev->pmcs[i])
- continue;
-
- ret = pmc_core_get_lpm_req(pmcdev, pmcdev->pmcs[i]);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
-static void
-pmc_add_pmt(struct pmc_dev *pmcdev, u64 ssram_base, void __iomem *ssram)
-{
- struct pci_dev *pcidev = pmcdev->ssram_pcidev;
- struct intel_vsec_platform_info info = {};
- struct intel_vsec_header *headers[2] = {};
- struct intel_vsec_header header;
- void __iomem *dvsec;
- u32 dvsec_offset;
- u32 table, hdr;
-
- ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
- if (!ssram)
- return;
-
- dvsec_offset = readl(ssram + SSRAM_DVSEC_OFFSET);
- iounmap(ssram);
-
- dvsec = ioremap(ssram_base + dvsec_offset, SSRAM_DVSEC_SIZE);
- if (!dvsec)
- return;
-
- hdr = readl(dvsec + PCI_DVSEC_HEADER1);
- header.id = readw(dvsec + PCI_DVSEC_HEADER2);
- header.rev = PCI_DVSEC_HEADER1_REV(hdr);
- header.length = PCI_DVSEC_HEADER1_LEN(hdr);
- header.num_entries = readb(dvsec + INTEL_DVSEC_ENTRIES);
- header.entry_size = readb(dvsec + INTEL_DVSEC_SIZE);
-
- table = readl(dvsec + INTEL_DVSEC_TABLE);
- header.tbir = INTEL_DVSEC_TABLE_BAR(table);
- header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
- iounmap(dvsec);
-
- headers[0] = &header;
- info.caps = VSEC_CAP_TELEMETRY;
- info.headers = headers;
- info.base_addr = ssram_base;
- info.parent = &pmcdev->pdev->dev;
-
- intel_vsec_register(pcidev, &info);
-}
-
-static const struct pmc_reg_map *pmc_core_find_regmap(struct pmc_info *list, u16 devid)
-{
- for (; list->map; ++list)
- if (devid == list->devid)
- return list->map;
-
- return NULL;
-}
-
-static inline u64 get_base(void __iomem *addr, u32 offset)
-{
- return lo_hi_readq(addr + offset) & GENMASK_ULL(63, 3);
-}
-
-static int
-pmc_core_pmc_add(struct pmc_dev *pmcdev, u64 pwrm_base,
- const struct pmc_reg_map *reg_map, int pmc_index)
-{
- struct pmc *pmc = pmcdev->pmcs[pmc_index];
-
- if (!pwrm_base)
- return -ENODEV;
-
- /* Memory for primary PMC has been allocated in core.c */
- if (!pmc) {
- pmc = devm_kzalloc(&pmcdev->pdev->dev, sizeof(*pmc), GFP_KERNEL);
- if (!pmc)
- return -ENOMEM;
- }
-
- pmc->map = reg_map;
- pmc->base_addr = pwrm_base;
- pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length);
-
- if (!pmc->regbase) {
- devm_kfree(&pmcdev->pdev->dev, pmc);
- return -ENOMEM;
- }
-
- pmcdev->pmcs[pmc_index] = pmc;
-
- return 0;
-}
-
-static int
-pmc_core_ssram_get_pmc(struct pmc_dev *pmcdev, int pmc_idx, u32 offset)
-{
- struct pci_dev *ssram_pcidev = pmcdev->ssram_pcidev;
- void __iomem __free(pmc_core_iounmap) *tmp_ssram = NULL;
- void __iomem __free(pmc_core_iounmap) *ssram = NULL;
- const struct pmc_reg_map *map;
- u64 ssram_base, pwrm_base;
- u16 devid;
-
- if (!pmcdev->regmap_list)
- return -ENOENT;
-
- ssram_base = ssram_pcidev->resource[0].start;
- tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
- if (!tmp_ssram)
- return -ENOMEM;
-
- if (pmc_idx != PMC_IDX_MAIN) {
- /*
- * The secondary PMC BARS (which are behind hidden PCI devices)
- * are read from fixed offsets in MMIO of the primary PMC BAR.
- * If a device is not present, the value will be 0.
- */
- ssram_base = get_base(tmp_ssram, offset);
- if (!ssram_base)
- return 0;
-
- ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
- if (!ssram)
- return -ENOMEM;
-
- } else {
- ssram = no_free_ptr(tmp_ssram);
- }
-
- pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
- devid = readw(ssram + SSRAM_DEVID_OFFSET);
-
- /* Find and register and PMC telemetry entries */
- pmc_add_pmt(pmcdev, ssram_base, ssram);
-
- map = pmc_core_find_regmap(pmcdev->regmap_list, devid);
- if (!map)
- return -ENODEV;
-
- return pmc_core_pmc_add(pmcdev, pwrm_base, map, pmc_idx);
-}
-
-int pmc_core_ssram_init(struct pmc_dev *pmcdev, int func)
-{
- struct pci_dev *pcidev;
- int ret;
-
- pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, func));
- if (!pcidev)
- return -ENODEV;
-
- ret = pcim_enable_device(pcidev);
- if (ret)
- goto release_dev;
-
- pmcdev->ssram_pcidev = pcidev;
-
- ret = pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_MAIN, 0);
- if (ret)
- goto disable_dev;
-
- pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
- pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
-
- return 0;
-
-disable_dev:
- pmcdev->ssram_pcidev = NULL;
- pci_disable_device(pcidev);
-release_dev:
- pci_dev_put(pcidev);
-
- return ret;
-}
-MODULE_IMPORT_NS("INTEL_VSEC");
-MODULE_IMPORT_NS("INTEL_PMT_TELEMETRY");
diff --git a/drivers/platform/x86/intel/pmc/lnl.c b/drivers/platform/x86/intel/pmc/lnl.c
index da513c234714..1cd81ee54dcf 100644
--- a/drivers/platform/x86/intel/pmc/lnl.c
+++ b/drivers/platform/x86/intel/pmc/lnl.c
@@ -13,6 +13,10 @@
#include "core.h"
+#define SOCM_LPM_REQ_GUID 0x15099748
+
+static const u8 LNL_LPM_REG_INDEX[] = {0, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20};
+
static const struct pmc_bit_map lnl_ltr_show_map[] = {
{"SOUTHPORT_A", CNP_PMC_LTR_SPA},
{"SOUTHPORT_B", CNP_PMC_LTR_SPB},
@@ -528,6 +532,16 @@ static const struct pmc_reg_map lnl_socm_reg_map = {
.lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
.s0ix_blocker_maps = lnl_blk_maps,
.s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+ .lpm_reg_index = LNL_LPM_REG_INDEX,
+ .lpm_req_guid = SOCM_LPM_REQ_GUID,
+};
+
+static struct pmc_info lnl_pmc_info_list[] = {
+ {
+ .devid = PMC_DEVID_LNL_SOCM,
+ .map = &lnl_socm_reg_map,
+ },
+ {}
};
#define LNL_NPU_PCI_DEV 0x643e
@@ -557,8 +571,12 @@ static int lnl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
}
struct pmc_dev_info lnl_pmc_dev = {
+ .pci_func = 2,
+ .regmap_list = lnl_pmc_info_list,
.map = &lnl_socm_reg_map,
+ .sub_req_show = &pmc_core_substate_req_regs_fops,
.suspend = cnl_suspend,
.resume = lnl_resume,
.init = lnl_core_init,
+ .sub_req = pmc_core_pmt_get_lpm_req,
};
diff --git a/drivers/platform/x86/intel/pmc/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c
index 8862829694a7..57508cbf9cd4 100644
--- a/drivers/platform/x86/intel/pmc/mtl.c
+++ b/drivers/platform/x86/intel/pmc/mtl.c
@@ -10,7 +10,6 @@
#include <linux/pci.h>
#include "core.h"
-#include "../pmt/telemetry.h"
/* PMC SSRAM PMT Telemetry GUIDS */
#define SOCP_LPM_REQ_GUID 0x2625030
@@ -474,6 +473,7 @@ const struct pmc_reg_map mtl_socm_reg_map = {
.lpm_status_offset = MTL_LPM_STATUS_OFFSET,
.lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
.lpm_reg_index = MTL_LPM_REG_INDEX,
+ .lpm_req_guid = SOCP_LPM_REQ_GUID,
};
static const struct pmc_bit_map mtl_ioep_pfear_map[] = {
@@ -798,6 +798,7 @@ const struct pmc_reg_map mtl_ioep_reg_map = {
.lpm_en_offset = MTL_LPM_EN_OFFSET,
.lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET,
.lpm_reg_index = MTL_LPM_REG_INDEX,
+ .lpm_req_guid = IOEP_LPM_REQ_GUID,
};
static const struct pmc_bit_map mtl_ioem_pfear_map[] = {
@@ -945,25 +946,20 @@ static const struct pmc_reg_map mtl_ioem_reg_map = {
.lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
.lpm_residency_offset = MTL_LPM_RESIDENCY_OFFSET,
.lpm_reg_index = MTL_LPM_REG_INDEX,
+ .lpm_req_guid = IOEM_LPM_REQ_GUID,
};
-#define PMC_DEVID_SOCM 0x7e7f
-#define PMC_DEVID_IOEP 0x7ecf
-#define PMC_DEVID_IOEM 0x7ebf
static struct pmc_info mtl_pmc_info_list[] = {
{
- .guid = SOCP_LPM_REQ_GUID,
- .devid = PMC_DEVID_SOCM,
+ .devid = PMC_DEVID_MTL_SOCM,
.map = &mtl_socm_reg_map,
},
{
- .guid = IOEP_LPM_REQ_GUID,
- .devid = PMC_DEVID_IOEP,
+ .devid = PMC_DEVID_MTL_IOEP,
.map = &mtl_ioep_reg_map,
},
{
- .guid = IOEM_LPM_REQ_GUID,
- .devid = PMC_DEVID_IOEM,
+ .devid = PMC_DEVID_MTL_IOEM,
.map = &mtl_ioem_reg_map
},
{}
@@ -996,12 +992,15 @@ static int mtl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
return generic_core_init(pmcdev, pmc_dev_info);
}
+static u32 MTL_PMT_DMU_GUIDS[] = {MTL_PMT_DMU_GUID, 0x0};
struct pmc_dev_info mtl_pmc_dev = {
.pci_func = 2,
- .dmu_guid = MTL_PMT_DMU_GUID,
+ .dmu_guids = MTL_PMT_DMU_GUIDS,
.regmap_list = mtl_pmc_info_list,
.map = &mtl_socm_reg_map,
+ .sub_req_show = &pmc_core_substate_req_regs_fops,
.suspend = cnl_suspend,
.resume = mtl_resume,
.init = mtl_core_init,
+ .sub_req = pmc_core_pmt_get_lpm_req,
};
diff --git a/drivers/platform/x86/intel/pmc/pltdrv.c b/drivers/platform/x86/intel/pmc/pltdrv.c
index 3141d6cbc41b..71ebb9ea1be2 100644
--- a/drivers/platform/x86/intel/pmc/pltdrv.c
+++ b/drivers/platform/x86/intel/pmc/pltdrv.c
@@ -65,7 +65,7 @@ static int __init pmc_core_platform_init(void)
if (!x86_match_cpu(intel_pmc_core_platform_ids))
return -ENODEV;
- pmc_core_device = kzalloc(sizeof(*pmc_core_device), GFP_KERNEL);
+ pmc_core_device = kzalloc_obj(*pmc_core_device);
if (!pmc_core_device)
return -ENOMEM;
diff --git a/drivers/platform/x86/intel/pmc/ptl.c b/drivers/platform/x86/intel/pmc/ptl.c
index 394515af60d6..1f48e2bbc699 100644
--- a/drivers/platform/x86/intel/pmc/ptl.c
+++ b/drivers/platform/x86/intel/pmc/ptl.c
@@ -10,6 +10,17 @@
#include "core.h"
+/* PMC SSRAM PMT Telemetry GUIDS */
+#define PCDP_LPM_REQ_GUID 0x47179370
+
+/*
+ * Die Mapping to Product.
+ * Product PCDDie
+ * PTL-H PCD-H
+ * PTL-P PCD-P
+ * PTL-U PCD-P
+ */
+
static const struct pmc_bit_map ptl_pcdp_pfear_map[] = {
{"PMC_0", BIT(0)},
{"FUSE_OSSE", BIT(1)},
@@ -162,7 +173,7 @@ static const struct pmc_bit_map ptl_pcdp_ltr_show_map[] = {
{}
};
-static const struct pmc_bit_map ptl_pcdp_clocksource_status_map[] = {
+const struct pmc_bit_map ptl_pcdp_clocksource_status_map[] = {
{"AON2_OFF_STS", BIT(0), 1},
{"AON3_OFF_STS", BIT(1), 0},
{"AON4_OFF_STS", BIT(2), 1},
@@ -382,7 +393,7 @@ static const struct pmc_bit_map ptl_pcdp_vnn_req_status_2_map[] = {
{}
};
-static const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[] = {
+const struct pmc_bit_map ptl_pcdp_vnn_req_status_3_map[] = {
{"DTS0_VNN_REQ_STS", BIT(7), 0},
{"GPIOCOM5_VNN_REQ_STS", BIT(11), 1},
{}
@@ -421,7 +432,7 @@ static const struct pmc_bit_map ptl_pcdp_vnn_misc_status_map[] = {
{}
};
-static const struct pmc_bit_map ptl_pcdp_signal_status_map[] = {
+const struct pmc_bit_map ptl_pcdp_signal_status_map[] = {
{"LSX_Wake0_STS", BIT(0), 0},
{"LSX_Wake1_STS", BIT(1), 0},
{"LSX_Wake2_STS", BIT(2), 0},
@@ -515,6 +526,21 @@ static const struct pmc_reg_map ptl_pcdp_reg_map = {
.lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
.s0ix_blocker_maps = ptl_pcdp_blk_maps,
.s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+ .num_s0ix_blocker = PTL_NUM_S0IX_BLOCKER,
+ .blocker_req_offset = PTL_BLK_REQ_OFFSET,
+ .lpm_req_guid = PCDP_LPM_REQ_GUID,
+};
+
+static struct pmc_info ptl_pmc_info_list[] = {
+ {
+ .devid = PMC_DEVID_PTL_PCDH,
+ .map = &ptl_pcdp_reg_map,
+ },
+ {
+ .devid = PMC_DEVID_PTL_PCDP,
+ .map = &ptl_pcdp_reg_map,
+ },
+ {}
};
#define PTL_NPU_PCI_DEV 0xb03e
@@ -543,8 +569,12 @@ static int ptl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_in
}
struct pmc_dev_info ptl_pmc_dev = {
+ .pci_func = 2,
+ .regmap_list = ptl_pmc_info_list,
.map = &ptl_pcdp_reg_map,
+ .sub_req_show = &pmc_core_substate_blk_req_fops,
.suspend = cnl_suspend,
.resume = ptl_resume,
.init = ptl_core_init,
+ .sub_req = pmc_core_pmt_get_blk_sub_req,
};
diff --git a/drivers/platform/x86/intel/pmc/ssram_telemetry.c b/drivers/platform/x86/intel/pmc/ssram_telemetry.c
new file mode 100644
index 000000000000..6f6e83e70fc5
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/ssram_telemetry.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel PMC SSRAM TELEMETRY PCI Driver
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/intel_vsec.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+
+#include "core.h"
+#include "ssram_telemetry.h"
+
+#define SSRAM_HDR_SIZE 0x100
+#define SSRAM_PWRM_OFFSET 0x14
+#define SSRAM_DVSEC_OFFSET 0x1C
+#define SSRAM_DVSEC_SIZE 0x10
+#define SSRAM_PCH_OFFSET 0x60
+#define SSRAM_IOE_OFFSET 0x68
+#define SSRAM_DEVID_OFFSET 0x70
+
+DEFINE_FREE(pmc_ssram_telemetry_iounmap, void __iomem *, if (_T) iounmap(_T))
+
+static struct pmc_ssram_telemetry *pmc_ssram_telems;
+static bool device_probed;
+
+static int
+pmc_ssram_telemetry_add_pmt(struct pci_dev *pcidev, u64 ssram_base, void __iomem *ssram)
+{
+ struct intel_vsec_platform_info info = {};
+ struct intel_vsec_header *headers[2] = {};
+ struct intel_vsec_header header;
+ void __iomem *dvsec;
+ u32 dvsec_offset;
+ u32 table, hdr;
+
+ dvsec_offset = readl(ssram + SSRAM_DVSEC_OFFSET);
+ dvsec = ioremap(ssram_base + dvsec_offset, SSRAM_DVSEC_SIZE);
+ if (!dvsec)
+ return -ENOMEM;
+
+ hdr = readl(dvsec + PCI_DVSEC_HEADER1);
+ header.id = readw(dvsec + PCI_DVSEC_HEADER2);
+ header.rev = PCI_DVSEC_HEADER1_REV(hdr);
+ header.length = PCI_DVSEC_HEADER1_LEN(hdr);
+ header.num_entries = readb(dvsec + INTEL_DVSEC_ENTRIES);
+ header.entry_size = readb(dvsec + INTEL_DVSEC_SIZE);
+
+ table = readl(dvsec + INTEL_DVSEC_TABLE);
+ header.tbir = INTEL_DVSEC_TABLE_BAR(table);
+ header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
+ iounmap(dvsec);
+
+ headers[0] = &header;
+ info.caps = VSEC_CAP_TELEMETRY;
+ info.headers = headers;
+ info.base_addr = ssram_base;
+ info.parent = &pcidev->dev;
+
+ return intel_vsec_register(&pcidev->dev, &info);
+}
+
+static inline u64 get_base(void __iomem *addr, u32 offset)
+{
+ return lo_hi_readq(addr + offset) & GENMASK_ULL(63, 3);
+}
+
+static int
+pmc_ssram_telemetry_get_pmc(struct pci_dev *pcidev, unsigned int pmc_idx, u32 offset)
+{
+ void __iomem __free(pmc_ssram_telemetry_iounmap) *tmp_ssram = NULL;
+ void __iomem __free(pmc_ssram_telemetry_iounmap) *ssram = NULL;
+ u64 ssram_base, pwrm_base;
+ u16 devid;
+
+ ssram_base = pci_resource_start(pcidev, 0);
+ tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
+ if (!tmp_ssram)
+ return -ENOMEM;
+
+ if (pmc_idx != PMC_IDX_MAIN) {
+ /*
+ * The secondary PMC BARS (which are behind hidden PCI devices)
+ * are read from fixed offsets in MMIO of the primary PMC BAR.
+ * If a device is not present, the value will be 0.
+ */
+ ssram_base = get_base(tmp_ssram, offset);
+ if (!ssram_base)
+ return 0;
+
+ ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
+ if (!ssram)
+ return -ENOMEM;
+
+ } else {
+ ssram = no_free_ptr(tmp_ssram);
+ }
+
+ pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
+ devid = readw(ssram + SSRAM_DEVID_OFFSET);
+
+ pmc_ssram_telems[pmc_idx].devid = devid;
+ pmc_ssram_telems[pmc_idx].base_addr = pwrm_base;
+
+ /* Find and register and PMC telemetry entries */
+ return pmc_ssram_telemetry_add_pmt(pcidev, ssram_base, ssram);
+}
+
+/**
+ * pmc_ssram_telemetry_get_pmc_info() - Get a PMC devid and base_addr information
+ * @pmc_idx: Index of the PMC
+ * @pmc_ssram_telemetry: pmc_ssram_telemetry structure to store the PMC information
+ *
+ * Return:
+ * * 0 - Success
+ * * -EAGAIN - Probe function has not finished yet. Try again.
+ * * -EINVAL - Invalid pmc_idx
+ * * -ENODEV - PMC device is not available
+ */
+int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx,
+ struct pmc_ssram_telemetry *pmc_ssram_telemetry)
+{
+ /*
+ * PMCs are discovered in probe function. If this function is called before
+ * probe function complete, the result would be invalid. Use device_probed
+ * variable to avoid this case. Return -EAGAIN to inform the consumer to call
+ * again later.
+ */
+ if (!device_probed)
+ return -EAGAIN;
+
+ /*
+ * Memory barrier is used to ensure the correct read order between
+ * device_probed variable and PMC info.
+ */
+ smp_rmb();
+ if (pmc_idx >= MAX_NUM_PMC)
+ return -EINVAL;
+
+ if (!pmc_ssram_telems || !pmc_ssram_telems[pmc_idx].devid)
+ return -ENODEV;
+
+ pmc_ssram_telemetry->devid = pmc_ssram_telems[pmc_idx].devid;
+ pmc_ssram_telemetry->base_addr = pmc_ssram_telems[pmc_idx].base_addr;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pmc_ssram_telemetry_get_pmc_info);
+
+static int intel_pmc_ssram_telemetry_probe(struct pci_dev *pcidev, const struct pci_device_id *id)
+{
+ int ret;
+
+ pmc_ssram_telems = devm_kzalloc(&pcidev->dev, sizeof(*pmc_ssram_telems) * MAX_NUM_PMC,
+ GFP_KERNEL);
+ if (!pmc_ssram_telems) {
+ ret = -ENOMEM;
+ goto probe_finish;
+ }
+
+ ret = pcim_enable_device(pcidev);
+ if (ret) {
+ dev_dbg(&pcidev->dev, "failed to enable PMC SSRAM device\n");
+ goto probe_finish;
+ }
+
+ ret = pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_MAIN, 0);
+ if (ret)
+ goto probe_finish;
+
+ pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
+ pmc_ssram_telemetry_get_pmc(pcidev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
+
+probe_finish:
+ /*
+ * Memory barrier is used to ensure the correct write order between PMC info
+ * and device_probed variable.
+ */
+ smp_wmb();
+ device_probed = true;
+ return ret;
+}
+
+static const struct pci_device_id intel_pmc_ssram_telemetry_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_MTL_SOCM) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCS) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_ARL_SOCM) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_LNL_SOCM) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDH) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_PTL_PCDP) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PMC_DEVID_WCL_PCDN) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, intel_pmc_ssram_telemetry_pci_ids);
+
+static struct pci_driver intel_pmc_ssram_telemetry_driver = {
+ .name = "intel_pmc_ssram_telemetry",
+ .id_table = intel_pmc_ssram_telemetry_pci_ids,
+ .probe = intel_pmc_ssram_telemetry_probe,
+};
+module_pci_driver(intel_pmc_ssram_telemetry_driver);
+
+MODULE_IMPORT_NS("INTEL_VSEC");
+MODULE_AUTHOR("Xi Pardee <xi.pardee@intel.com>");
+MODULE_DESCRIPTION("Intel PMC SSRAM Telemetry driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/pmc/ssram_telemetry.h b/drivers/platform/x86/intel/pmc/ssram_telemetry.h
new file mode 100644
index 000000000000..daf8aeeb2275
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/ssram_telemetry.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Intel PMC SSRAM Telemetry PCI Driver Header File
+ *
+ * Copyright (c) 2024, Intel Corporation.
+ */
+
+#ifndef PMC_SSRAM_H
+#define PMC_SSRAM_H
+
+/**
+ * struct pmc_ssram_telemetry - Structure to keep pmc info in ssram device
+ * @devid: device id of the pmc device
+ * @base_addr: contains PWRM base address
+ */
+struct pmc_ssram_telemetry {
+ u16 devid;
+ u64 base_addr;
+};
+
+int pmc_ssram_telemetry_get_pmc_info(unsigned int pmc_idx,
+ struct pmc_ssram_telemetry *pmc_ssram_telemetry);
+
+#endif /* PMC_SSRAM_H */
diff --git a/drivers/platform/x86/intel/pmc/tgl.c b/drivers/platform/x86/intel/pmc/tgl.c
index 02e731ed3391..fc5b4cacc1c6 100644
--- a/drivers/platform/x86/intel/pmc/tgl.c
+++ b/drivers/platform/x86/intel/pmc/tgl.c
@@ -273,8 +273,8 @@ void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev)
addr = (u32 *)out_obj->buffer.pointer;
- lpm_req_regs = devm_kzalloc(&pdev->dev, lpm_size * sizeof(u32),
- GFP_KERNEL);
+ lpm_req_regs = devm_kcalloc(&pdev->dev, lpm_size, sizeof(u32),
+ GFP_KERNEL);
if (!lpm_req_regs)
goto free_acpi_obj;
diff --git a/drivers/platform/x86/intel/pmc/wcl.c b/drivers/platform/x86/intel/pmc/wcl.c
new file mode 100644
index 000000000000..a45707e6364f
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/wcl.c
@@ -0,0 +1,504 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file contains platform specific structure definitions
+ * and init function used by Wildcat Lake PCH.
+ *
+ * Copyright (c) 2025, Intel Corporation.
+ */
+
+#include <linux/bits.h>
+#include <linux/pci.h>
+
+#include "core.h"
+
+/* PMC SSRAM PMT Telemetry GUIDS */
+#define PCDN_LPM_REQ_GUID 0x33747648
+
+static const struct pmc_bit_map wcl_pcdn_pfear_map[] = {
+ {"PMC_0", BIT(0)},
+ {"FUSE_OSSE", BIT(1)},
+ {"ESPISPI", BIT(2)},
+ {"XHCI", BIT(3)},
+ {"SPA", BIT(4)},
+ {"RSVD", BIT(5)},
+ {"MPFPW2", BIT(6)},
+ {"GBE", BIT(7)},
+
+ {"SBR16B21", BIT(0)},
+ {"SBR16B5", BIT(1)},
+ {"SBR8B1", BIT(2)},
+ {"SBR8B0", BIT(3)},
+ {"P2SB0", BIT(4)},
+ {"D2D_DISP_1", BIT(5)},
+ {"LPSS", BIT(6)},
+ {"LPC", BIT(7)},
+
+ {"SMB", BIT(0)},
+ {"ISH", BIT(1)},
+ {"DBG_SBR16B", BIT(2)},
+ {"NPK_0", BIT(3)},
+ {"D2D_NOC_1", BIT(4)},
+ {"FIA_P", BIT(5)},
+ {"FUSE", BIT(6)},
+ {"DBG_PSF", BIT(7)},
+
+ {"DISP_PGA1", BIT(0)},
+ {"XDCI", BIT(1)},
+ {"EXI", BIT(2)},
+ {"CSE", BIT(3)},
+ {"KVMCC", BIT(4)},
+ {"PMT", BIT(5)},
+ {"CLINK", BIT(6)},
+ {"PTIO", BIT(7)},
+
+ {"USBR0", BIT(0)},
+ {"SBR16B22", BIT(1)},
+ {"SMT1", BIT(2)},
+ {"MPFPW1", BIT(3)},
+ {"SMS2", BIT(4)},
+ {"SMS1", BIT(5)},
+ {"CSMERTC", BIT(6)},
+ {"CSMEPSF", BIT(7)},
+
+ {"D2D_NOC_0", BIT(0)},
+ {"ESE", BIT(1)},
+ {"FIACPCB_P", BIT(2)},
+ {"RSVD", BIT(3)},
+ {"SBR8B2", BIT(4)},
+ {"OSSE_SMT1", BIT(5)},
+ {"D2D_DISP", BIT(6)},
+ {"P2SB1", BIT(7)},
+
+ {"U3FPW1", BIT(0)},
+ {"SBR16B3", BIT(1)},
+ {"PSF4", BIT(2)},
+ {"CNVI", BIT(3)},
+ {"UFSX2", BIT(4)},
+ {"ENDBG", BIT(5)},
+ {"DBC", BIT(6)},
+ {"SBRG", BIT(7)},
+
+ {"RSVD", BIT(0)},
+ {"NPK1", BIT(1)},
+ {"SBR16B7", BIT(2)},
+ {"SBR16B4", BIT(3)},
+ {"FIA_XG", BIT(4)},
+ {"PSF6", BIT(5)},
+ {"UFSPW1", BIT(6)},
+ {"FIA_U", BIT(7)},
+
+ {"PSF8", BIT(0)},
+ {"PSF0", BIT(1)},
+ {"RSVD", BIT(2)},
+ {"FIACPCB_U", BIT(3)},
+ {"TAM", BIT(4)},
+ {"SBR16B0", BIT(5)},
+ {"TBTLSX", BIT(6)},
+ {"THC0", BIT(7)},
+
+ {"THC1", BIT(0)},
+ {"PMC_1", BIT(1)},
+ {"FIACPCB_XG", BIT(2)},
+ {"TCSS", BIT(3)},
+ {"DISP_PGA", BIT(4)},
+ {"SBR16B20", BIT(5)},
+ {"SBR8B20", BIT(6)},
+ {"DBG_SBR", BIT(7)},
+
+ {"SPC", BIT(0)},
+ {"ACE_0", BIT(1)},
+ {"ACE_1", BIT(2)},
+ {"ACE_2", BIT(3)},
+ {"ACE_3", BIT(4)},
+ {"ACE_4", BIT(5)},
+ {"ACE_5", BIT(6)},
+ {"ACE_6", BIT(7)},
+
+ {"ACE_7", BIT(0)},
+ {"ACE_8", BIT(1)},
+ {"ACE_9", BIT(2)},
+ {"ACE_10", BIT(3)},
+ {"SBR16B2", BIT(4)},
+ {"SBR8B4", BIT(5)},
+ {"OSSE", BIT(6)},
+ {"SBR16B1", BIT(7)},
+ {}
+};
+
+static const struct pmc_bit_map *ext_wcl_pcdn_pfear_map[] = {
+ wcl_pcdn_pfear_map,
+ NULL
+};
+
+static const struct pmc_bit_map wcl_pcdn_ltr_show_map[] = {
+ {"SOUTHPORT_A", CNP_PMC_LTR_SPA},
+ {"RSVD", WCL_PMC_LTR_RESERVED},
+ {"SATA", CNP_PMC_LTR_SATA},
+ {"GIGABIT_ETHERNET", CNP_PMC_LTR_GBE},
+ {"XHCI", CNP_PMC_LTR_XHCI},
+ {"SOUTHPORT_F", ADL_PMC_LTR_SPF},
+ {"ME", CNP_PMC_LTR_ME},
+ {"SATA1", CNP_PMC_LTR_EVA},
+ {"SOUTHPORT_C", CNP_PMC_LTR_SPC},
+ {"HD_AUDIO", CNP_PMC_LTR_AZ},
+ {"CNV", CNP_PMC_LTR_CNV},
+ {"LPSS", CNP_PMC_LTR_LPSS},
+ {"SOUTHPORT_D", CNP_PMC_LTR_SPD},
+ {"SOUTHPORT_E", CNP_PMC_LTR_SPE},
+ {"SATA2", PTL_PMC_LTR_SATA2},
+ {"ESPI", CNP_PMC_LTR_ESPI},
+ {"SCC", CNP_PMC_LTR_SCC},
+ {"ISH", CNP_PMC_LTR_ISH},
+ {"UFSX2", CNP_PMC_LTR_UFSX2},
+ {"EMMC", CNP_PMC_LTR_EMMC},
+ {"WIGIG", ICL_PMC_LTR_WIGIG},
+ {"THC0", TGL_PMC_LTR_THC0},
+ {"THC1", TGL_PMC_LTR_THC1},
+ {"SOUTHPORT_G", MTL_PMC_LTR_SPG},
+ {"ESE", MTL_PMC_LTR_ESE},
+ {"IOE_PMC", MTL_PMC_LTR_IOE_PMC},
+ {"DMI3", ARL_PMC_LTR_DMI3},
+ {"OSSE", LNL_PMC_LTR_OSSE},
+
+ /* Below two cannot be used for LTR_IGNORE */
+ {"CURRENT_PLATFORM", PTL_PMC_LTR_CUR_PLT},
+ {"AGGREGATED_SYSTEM", PTL_PMC_LTR_CUR_ASLT},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_power_gating_status_0_map[] = {
+ {"PMC_PGD0_PG_STS", BIT(0), 0},
+ {"FUSE_OSSE_PGD0_PG_STS", BIT(1), 0},
+ {"ESPISPI_PGD0_PG_STS", BIT(2), 0},
+ {"XHCI_PGD0_PG_STS", BIT(3), 1},
+ {"SPA_PGD0_PG_STS", BIT(4), 1},
+ {"RSVD_5", BIT(5), 0},
+ {"MPFPW2_PGD0_PG_STS", BIT(6), 0},
+ {"GBE_PGD0_PG_STS", BIT(7), 1},
+ {"SBR16B21_PGD0_PG_STS", BIT(8), 0},
+ {"SBR16B5_PGD0_PG_STS", BIT(9), 0},
+ {"SBR8B1_PGD0_PG_STS", BIT(10), 0},
+ {"SBR8B0_PGD0_PG_STS", BIT(11), 0},
+ {"P2SB0_PG_STS", BIT(12), 1},
+ {"D2D_DISP_PGD1_PG_STS", BIT(13), 0},
+ {"LPSS_PGD0_PG_STS", BIT(14), 1},
+ {"LPC_PGD0_PG_STS", BIT(15), 0},
+ {"SMB_PGD0_PG_STS", BIT(16), 0},
+ {"ISH_PGD0_PG_STS", BIT(17), 0},
+ {"DBG_SBR16B_PGD0_PG_STS", BIT(18), 0},
+ {"NPK_PGD0_PG_STS", BIT(19), 0},
+ {"D2D_NOC_PGD1_PG_STS", BIT(20), 0},
+ {"FIA_P_PGD0_PG_STS", BIT(21), 0},
+ {"FUSE_PGD0_PG_STS", BIT(22), 0},
+ {"DBG_PSF_PGD0_PG_STS", BIT(23), 0},
+ {"DISP_PGA1_PGD0_PG_STS", BIT(24), 0},
+ {"XDCI_PGD0_PG_STS", BIT(25), 1},
+ {"EXI_PGD0_PG_STS", BIT(26), 0},
+ {"CSE_PGD0_PG_STS", BIT(27), 1},
+ {"KVMCC_PGD0_PG_STS", BIT(28), 1},
+ {"PMT_PGD0_PG_STS", BIT(29), 1},
+ {"CLINK_PGD0_PG_STS", BIT(30), 1},
+ {"PTIO_PGD0_PG_STS", BIT(31), 1},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_power_gating_status_1_map[] = {
+ {"USBR0_PGD0_PG_STS", BIT(0), 1},
+ {"SBR16B22_PGD0_PG_STS", BIT(1), 0},
+ {"SMT1_PGD0_PG_STS", BIT(2), 1},
+ {"MPFPW1_PGD0_PG_STS", BIT(3), 0},
+ {"SMS2_PGD0_PG_STS", BIT(4), 1},
+ {"SMS1_PGD0_PG_STS", BIT(5), 1},
+ {"CSMERTC_PGD0_PG_STS", BIT(6), 0},
+ {"CSMEPSF_PGD0_PG_STS", BIT(7), 0},
+ {"D2D_NOC_PGD0_PG_STS", BIT(8), 0},
+ {"ESE_PGD0_PG_STS", BIT(9), 1},
+ {"FIACPCB_P_PGD0_PG_STS", BIT(10), 0},
+ {"SBR8B2_PGD0_PG_STS", BIT(12), 0},
+ {"OSSE_SMT1_PGD0_PG_STS", BIT(13), 1},
+ {"D2D_DISP_PGD0_PG_STS", BIT(14), 0},
+ {"P2SB1_PGD0_PG_STS", BIT(15), 1},
+ {"U3FPW1_PGD0_PG_STS", BIT(16), 0},
+ {"SBR16B3_PGD0_PG_STS", BIT(17), 0},
+ {"PSF4_PGD0_PG_STS", BIT(18), 0},
+ {"CNVI_PGD0_PG_STS", BIT(19), 0},
+ {"UFSX2_PGD0_PG_STS", BIT(20), 1},
+ {"ENDBG_PGD0_PG_STS", BIT(21), 0},
+ {"DBC_PGD0_PG_STS", BIT(22), 0},
+ {"SBRG_PGD0_PG_STS", BIT(23), 0},
+ {"NPK_PGD1_PG_STS", BIT(25), 0},
+ {"SBR16B7_PGD0_PG_STS", BIT(26), 0},
+ {"SBR16B4_PGD0_PG_STS", BIT(27), 0},
+ {"FIA_XG_PSF_PGD0_PG_STS", BIT(28), 0},
+ {"PSF6_PGD0_PG_STS", BIT(29), 0},
+ {"UFSPW1_PGD0_PG_STS", BIT(30), 0},
+ {"FIA_U_PGD0_PG_STS", BIT(31), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_power_gating_status_2_map[] = {
+ {"PSF8_PGD0_PG_STS", BIT(0), 0},
+ {"PSF0_PGD0_PG_STS", BIT(1), 0},
+ {"FIACPCB_U_PGD0_PG_STS", BIT(3), 0},
+ {"TAM_PGD0_PG_STS", BIT(4), 1},
+ {"SBR16B0_PGD0_PG_STS", BIT(5), 0},
+ {"TBTLSX_PGD0_PG_STS", BIT(6), 1},
+ {"THC0_PGD0_PG_STS", BIT(7), 1},
+ {"THC1_PGD0_PG_STS", BIT(8), 1},
+ {"PMC_PGD1_PG_STS", BIT(9), 0},
+ {"FIACPCB_XG_PGD0_PG_STS", BIT(10), 0},
+ {"TCSS_PGD0_PG_STS", BIT(11), 0},
+ {"DISP_PGA_PGD0_PG_STS", BIT(12), 0},
+ {"SBR8B4_PGD0_PG_STS", BIT(13), 0},
+ {"SBR8B20_PGD0_PG_STS", BIT(14), 0},
+ {"DBG_PGD0_PG_STS", BIT(15), 0},
+ {"SPC_PGD0_PG_STS", BIT(16), 1},
+ {"ACE_PGD0_PG_STS", BIT(17), 0},
+ {"ACE_PGD1_PG_STS", BIT(18), 0},
+ {"ACE_PGD2_PG_STS", BIT(19), 0},
+ {"ACE_PGD3_PG_STS", BIT(20), 0},
+ {"ACE_PGD4_PG_STS", BIT(21), 0},
+ {"ACE_PGD5_PG_STS", BIT(22), 0},
+ {"ACE_PGD6_PG_STS", BIT(23), 0},
+ {"ACE_PGD7_PG_STS", BIT(24), 0},
+ {"ACE_PGD8_PG_STS", BIT(25), 0},
+ {"ACE_PGD9_PG_STS", BIT(26), 0},
+ {"ACE_PGD10_PG_STS", BIT(27), 0},
+ {"SBR16B2_PG_PGD0_PG_STS", BIT(28), 0},
+ {"SBR16B20_PGD0_PG_STS", BIT(29), 0},
+ {"OSSE_PGD0_PG_STS", BIT(30), 1},
+ {"SBR16B1_PGD0_PG_STS", BIT(31), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_d3_status_0_map[] = {
+ {"LPSS_D3_STS", BIT(3), 1},
+ {"XDCI_D3_STS", BIT(4), 1},
+ {"XHCI_D3_STS", BIT(5), 1},
+ {"SPA_D3_STS", BIT(12), 0},
+ {"SPC_D3_STS", BIT(14), 0},
+ {"OSSE_D3_STS", BIT(15), 0},
+ {"ESPISPI_D3_STS", BIT(18), 0},
+ {"PSTH_D3_STS", BIT(21), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_d3_status_1_map[] = {
+ {"OSSE_SMT1_D3_STS", BIT(16), 0},
+ {"GBE_D3_STS", BIT(19), 0},
+ {"ITSS_D3_STS", BIT(23), 0},
+ {"CNVI_D3_STS", BIT(27), 0},
+ {"UFSX2_D3_STS", BIT(28), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_d3_status_2_map[] = {
+ {"CSMERTC_D3_STS", BIT(1), 0},
+ {"ESE_D3_STS", BIT(2), 0},
+ {"CSE_D3_STS", BIT(4), 0},
+ {"KVMCC_D3_STS", BIT(5), 0},
+ {"USBR0_D3_STS", BIT(6), 0},
+ {"ISH_D3_STS", BIT(7), 0},
+ {"SMT1_D3_STS", BIT(8), 0},
+ {"SMT2_D3_STS", BIT(9), 0},
+ {"SMT3_D3_STS", BIT(10), 0},
+ {"CLINK_D3_STS", BIT(14), 0},
+ {"PTIO_D3_STS", BIT(16), 0},
+ {"PMT_D3_STS", BIT(17), 0},
+ {"SMS1_D3_STS", BIT(18), 0},
+ {"SMS2_D3_STS", BIT(19), 0},
+ {"OSSE_SMT2_D3_STS", BIT(22), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_d3_status_3_map[] = {
+ {"THC0_D3_STS", BIT(14), 1},
+ {"THC1_D3_STS", BIT(15), 1},
+ {"OSSE_SMT3_D3_STS", BIT(16), 0},
+ {"ACE_D3_STS", BIT(23), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_vnn_req_status_0_map[] = {
+ {"LPSS_VNN_REQ_STS", BIT(3), 1},
+ {"OSSE_VNN_REQ_STS", BIT(15), 1},
+ {"ESPISPI_VNN_REQ_STS", BIT(18), 1},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_vnn_req_status_1_map[] = {
+ {"NPK_VNN_REQ_STS", BIT(4), 1},
+ {"DFXAGG_VNN_REQ_STS", BIT(8), 0},
+ {"EXI_VNN_REQ_STS", BIT(9), 1},
+ {"OSSE_SMT1_VNN_REQ_STS", BIT(16), 1},
+ {"P2D_VNN_REQ_STS", BIT(18), 1},
+ {"GBE_VNN_REQ_STS", BIT(19), 1},
+ {"SMB_VNN_REQ_STS", BIT(25), 1},
+ {"LPC_VNN_REQ_STS", BIT(26), 0},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_vnn_req_status_2_map[] = {
+ {"CSMERTC_VNN_REQ_STS", BIT(1), 1},
+ {"ESE_VNN_REQ_STS", BIT(2), 1},
+ {"CSE_VNN_REQ_STS", BIT(4), 1},
+ {"ISH_VNN_REQ_STS", BIT(7), 1},
+ {"SMT1_VNN_REQ_STS", BIT(8), 1},
+ {"CLINK_VNN_REQ_STS", BIT(14), 1},
+ {"SMS1_VNN_REQ_STS", BIT(18), 1},
+ {"SMS2_VNN_REQ_STS", BIT(19), 1},
+ {"GPIOCOM4_VNN_REQ_STS", BIT(20), 1},
+ {"GPIOCOM3_VNN_REQ_STS", BIT(21), 1},
+ {"GPIOCOM1_VNN_REQ_STS", BIT(23), 1},
+ {"GPIOCOM0_VNN_REQ_STS", BIT(24), 1},
+ {"DISP_SHIM_VNN_REQ_STS", BIT(31), 1},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_vnn_misc_status_map[] = {
+ {"CPU_C10_REQ_STS", BIT(0), 0},
+ {"TS_OFF_REQ_STS", BIT(1), 0},
+ {"PNDE_MET_REQ_STS", BIT(2), 1},
+ {"FW_THROTTLE_ALLOWED_REQ_STS", BIT(4), 0},
+ {"VNN_SOC_REQ_STS", BIT(6), 1},
+ {"ISH_VNNAON_REQ_STS", BIT(7), 0},
+ {"D2D_NOC_CFI_QACTIVE_REQ_STS", BIT(8), 1},
+ {"D2D_NOC_GPSB_QACTIVE_REQ_STS", BIT(9), 1},
+ {"PLT_GREATER_REQ_STS", BIT(11), 1},
+ {"ALL_SBR_IDLE_REQ_STS", BIT(12), 0},
+ {"PMC_IDLE_FB_OCP_REQ_STS", BIT(13), 0},
+ {"PM_SYNC_STATES_REQ_STS", BIT(14), 0},
+ {"EA_REQ_STS", BIT(15), 0},
+ {"MPHY_CORE_OFF_REQ_STS", BIT(16), 0},
+ {"BRK_EV_EN_REQ_STS", BIT(17), 0},
+ {"AUTO_DEMO_EN_REQ_STS", BIT(18), 0},
+ {"ITSS_CLK_SRC_REQ_STS", BIT(19), 1},
+ {"ARC_IDLE_REQ_STS", BIT(21), 0},
+ {"FIA_DEEP_PM_REQ_STS", BIT(23), 0},
+ {"XDCI_ATTACHED_REQ_STS", BIT(24), 1},
+ {"ARC_INTERRUPT_WAKE_REQ_STS", BIT(25), 0},
+ {"D2D_DISP_DDI_QACTIVE_REQ_STS", BIT(26), 1},
+ {"PRE_WAKE0_REQ_STS", BIT(27), 1},
+ {"PRE_WAKE1_REQ_STS", BIT(28), 1},
+ {"PRE_WAKE2_REQ_STS", BIT(29), 1},
+ {}
+};
+
+static const struct pmc_bit_map wcl_pcdn_rsc_status_map[] = {
+ {"Memory", 0, 1},
+ {"PSF0", 0, 1},
+ {"PSF6", 0, 1},
+ {"PSF8", 0, 1},
+ {"SAF_CFI_LINK", 0, 1},
+ {"SB", 0, 1},
+ {}
+};
+
+static const struct pmc_bit_map *wcl_pcdn_lpm_maps[] = {
+ ptl_pcdp_clocksource_status_map,
+ wcl_pcdn_power_gating_status_0_map,
+ wcl_pcdn_power_gating_status_1_map,
+ wcl_pcdn_power_gating_status_2_map,
+ wcl_pcdn_d3_status_0_map,
+ wcl_pcdn_d3_status_1_map,
+ wcl_pcdn_d3_status_2_map,
+ wcl_pcdn_d3_status_3_map,
+ wcl_pcdn_vnn_req_status_0_map,
+ wcl_pcdn_vnn_req_status_1_map,
+ wcl_pcdn_vnn_req_status_2_map,
+ ptl_pcdp_vnn_req_status_3_map,
+ wcl_pcdn_vnn_misc_status_map,
+ ptl_pcdp_signal_status_map,
+ NULL
+};
+
+static const struct pmc_bit_map *wcl_pcdn_blk_maps[] = {
+ wcl_pcdn_power_gating_status_0_map,
+ wcl_pcdn_power_gating_status_1_map,
+ wcl_pcdn_power_gating_status_2_map,
+ wcl_pcdn_rsc_status_map,
+ wcl_pcdn_vnn_req_status_0_map,
+ wcl_pcdn_vnn_req_status_1_map,
+ wcl_pcdn_vnn_req_status_2_map,
+ ptl_pcdp_vnn_req_status_3_map,
+ wcl_pcdn_d3_status_0_map,
+ wcl_pcdn_d3_status_1_map,
+ wcl_pcdn_d3_status_2_map,
+ wcl_pcdn_d3_status_3_map,
+ ptl_pcdp_clocksource_status_map,
+ wcl_pcdn_vnn_misc_status_map,
+ ptl_pcdp_signal_status_map,
+ NULL
+};
+
+static const struct pmc_reg_map wcl_pcdn_reg_map = {
+ .pfear_sts = ext_wcl_pcdn_pfear_map,
+ .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET,
+ .slp_s0_res_counter_step = TGL_PMC_SLP_S0_RES_COUNTER_STEP,
+ .ltr_show_sts = wcl_pcdn_ltr_show_map,
+ .msr_sts = msr_map,
+ .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET,
+ .regmap_length = WCL_PCD_PMC_MMIO_REG_LEN,
+ .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A,
+ .ppfear_buckets = LNL_PPFEAR_NUM_ENTRIES,
+ .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
+ .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
+ .lpm_num_maps = PTL_LPM_NUM_MAPS,
+ .ltr_ignore_max = LNL_NUM_IP_IGN_ALLOWED,
+ .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
+ .etr3_offset = ETR3_OFFSET,
+ .lpm_sts_latch_en_offset = MTL_LPM_STATUS_LATCH_EN_OFFSET,
+ .lpm_priority_offset = MTL_LPM_PRI_OFFSET,
+ .lpm_en_offset = MTL_LPM_EN_OFFSET,
+ .lpm_residency_offset = MTL_LPM_RESIDENCY_OFFSET,
+ .lpm_sts = wcl_pcdn_lpm_maps,
+ .lpm_status_offset = MTL_LPM_STATUS_OFFSET,
+ .lpm_live_status_offset = MTL_LPM_LIVE_STATUS_OFFSET,
+ .s0ix_blocker_maps = wcl_pcdn_blk_maps,
+ .s0ix_blocker_offset = LNL_S0IX_BLOCKER_OFFSET,
+ .num_s0ix_blocker = WCL_NUM_S0IX_BLOCKER,
+ .blocker_req_offset = WCL_BLK_REQ_OFFSET,
+ .lpm_req_guid = PCDN_LPM_REQ_GUID,
+};
+
+static struct pmc_info wcl_pmc_info_list[] = {
+ {
+ .devid = PMC_DEVID_WCL_PCDN,
+ .map = &wcl_pcdn_reg_map,
+ },
+ {}
+};
+
+#define WCL_NPU_PCI_DEV 0xfd3e
+
+/*
+ * Set power state of select devices that do not have drivers to D3
+ * so that they do not block Package C entry.
+ */
+static void wcl_d3_fixup(void)
+{
+ pmc_core_set_device_d3(WCL_NPU_PCI_DEV);
+}
+
+static int wcl_resume(struct pmc_dev *pmcdev)
+{
+ wcl_d3_fixup();
+ return cnl_resume(pmcdev);
+}
+
+static int wcl_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
+{
+ wcl_d3_fixup();
+ return generic_core_init(pmcdev, pmc_dev_info);
+}
+
+struct pmc_dev_info wcl_pmc_dev = {
+ .pci_func = 2,
+ .regmap_list = wcl_pmc_info_list,
+ .map = &wcl_pcdn_reg_map,
+ .sub_req_show = &pmc_core_substate_blk_req_fops,
+ .suspend = cnl_suspend,
+ .resume = wcl_resume,
+ .init = wcl_core_init,
+ .sub_req = pmc_core_pmt_get_blk_sub_req,
+};
diff --git a/drivers/platform/x86/intel/pmt/Kconfig b/drivers/platform/x86/intel/pmt/Kconfig
index e916fc966221..7363446b7773 100644
--- a/drivers/platform/x86/intel/pmt/Kconfig
+++ b/drivers/platform/x86/intel/pmt/Kconfig
@@ -18,6 +18,7 @@ config INTEL_PMT_CLASS
config INTEL_PMT_TELEMETRY
tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver"
depends on INTEL_VSEC
+ select INTEL_PMT_DISCOVERY
select INTEL_PMT_CLASS
help
The Intel Platform Monitory Technology (PMT) Telemetry driver provides
@@ -38,3 +39,30 @@ config INTEL_PMT_CRASHLOG
To compile this driver as a module, choose M here: the module
will be called intel_pmt_crashlog.
+
+config INTEL_PMT_DISCOVERY
+ tristate "Intel Platform Monitoring Technology (PMT) Discovery driver"
+ depends on INTEL_VSEC
+ select INTEL_PMT_CLASS
+ help
+ The Intel Platform Monitoring Technology (PMT) discovery driver provides
+ access to details about the various PMT features and feature specific
+ attributes.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pmt_discovery.
+
+config INTEL_PMT_KUNIT_TEST
+ tristate "KUnit tests for Intel PMT driver"
+ depends on INTEL_PMT_DISCOVERY
+ depends on INTEL_PMT_TELEMETRY || !INTEL_PMT_TELEMETRY
+ depends on KUNIT
+ help
+ Enable this option to compile and run a suite of KUnit tests for the Intel
+ Platform Monitoring Technology (PMT) driver. These tests are designed to
+ validate the driver's functionality, error handling, and overall stability,
+ helping developers catch regressions and ensure code quality during changes.
+
+ This option is intended for development and testing environments. It is
+ recommended to disable it in production builds. To compile this driver as a
+ module, choose M here: the module will be called pmt-discovery-kunit.
diff --git a/drivers/platform/x86/intel/pmt/Makefile b/drivers/platform/x86/intel/pmt/Makefile
index 279e158c7c23..47f692c091c9 100644
--- a/drivers/platform/x86/intel/pmt/Makefile
+++ b/drivers/platform/x86/intel/pmt/Makefile
@@ -10,3 +10,7 @@ obj-$(CONFIG_INTEL_PMT_TELEMETRY) += pmt_telemetry.o
pmt_telemetry-y := telemetry.o
obj-$(CONFIG_INTEL_PMT_CRASHLOG) += pmt_crashlog.o
pmt_crashlog-y := crashlog.o
+obj-$(CONFIG_INTEL_PMT_DISCOVERY) += pmt_discovery.o
+pmt_discovery-y := discovery.o features.o
+obj-$(CONFIG_INTEL_PMT_KUNIT_TEST) += pmt-discovery-kunit.o
+pmt-discovery-kunit-y := discovery-kunit.o
diff --git a/drivers/platform/x86/intel/pmt/class.c b/drivers/platform/x86/intel/pmt/class.c
index 7233b654bbad..b4c9964df807 100644
--- a/drivers/platform/x86/intel/pmt/class.c
+++ b/drivers/platform/x86/intel/pmt/class.c
@@ -9,11 +9,13 @@
*/
#include <linux/kernel.h>
+#include <linux/log2.h>
#include <linux/intel_vsec.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/pci.h>
+#include <linux/sysfs.h>
#include "class.h"
@@ -58,11 +60,11 @@ pmt_memcpy64_fromio(void *to, const u64 __iomem *from, size_t count)
return count;
}
-int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf,
+int pmt_telem_read_mmio(struct device *dev, struct pmt_callbacks *cb, u32 guid, void *buf,
void __iomem *addr, loff_t off, u32 count)
{
if (cb && cb->read_telem)
- return cb->read_telem(pdev, guid, buf, off, count);
+ return cb->read_telem(dev, guid, buf, off, count);
addr += off;
@@ -97,7 +99,7 @@ intel_pmt_read(struct file *filp, struct kobject *kobj,
if (count > entry->size - off)
count = entry->size - off;
- count = pmt_telem_read_mmio(entry->ep->pcidev, entry->cb, entry->header.guid, buf,
+ count = pmt_telem_read_mmio(entry->ep->dev, entry->cb, entry->header.guid, buf,
entry->base, off, count);
return count;
@@ -138,7 +140,7 @@ guid_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "0x%x\n", entry->guid);
+ return sysfs_emit(buf, "0x%x\n", entry->guid);
}
static DEVICE_ATTR_RO(guid);
@@ -147,7 +149,7 @@ static ssize_t size_show(struct device *dev, struct device_attribute *attr,
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "%zu\n", entry->size);
+ return sysfs_emit(buf, "%zu\n", entry->size);
}
static DEVICE_ATTR_RO(size);
@@ -156,7 +158,7 @@ offset_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr));
+ return sysfs_emit(buf, "%lu\n", offset_in_page(entry->base_addr));
}
static DEVICE_ATTR_RO(offset);
@@ -166,18 +168,47 @@ static struct attribute *intel_pmt_attrs[] = {
&dev_attr_offset.attr,
NULL
};
-ATTRIBUTE_GROUPS(intel_pmt);
-static struct class intel_pmt_class = {
+static umode_t intel_pmt_attr_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev->parent);
+ struct intel_vsec_device *ivdev = auxdev_to_ivdev(auxdev);
+
+ /*
+ * Place the discovery features folder in /sys/class/intel_pmt, but
+ * exclude the common attributes as they are not applicable.
+ */
+ if (ivdev->cap_id == ilog2(VSEC_CAP_DISCOVERY))
+ return 0;
+
+ return attr->mode;
+}
+
+static bool intel_pmt_group_visible(struct kobject *kobj)
+{
+ return true;
+}
+DEFINE_SYSFS_GROUP_VISIBLE(intel_pmt);
+
+static const struct attribute_group intel_pmt_group = {
+ .attrs = intel_pmt_attrs,
+ .is_visible = SYSFS_GROUP_VISIBLE(intel_pmt),
+};
+__ATTRIBUTE_GROUPS(intel_pmt);
+
+struct class intel_pmt_class = {
.name = "intel_pmt",
.dev_groups = intel_pmt_groups,
};
+EXPORT_SYMBOL_GPL(intel_pmt_class);
static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
struct intel_vsec_device *ivdev,
struct resource *disc_res)
{
- struct pci_dev *pci_dev = ivdev->pcidev;
+ struct pci_dev *pci_dev = to_pci_dev(ivdev->dev);
struct device *dev = &ivdev->auxdev.dev;
struct intel_pmt_header *header = &entry->header;
u8 bir;
@@ -252,6 +283,7 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
return -EINVAL;
}
+ entry->pcidev = pci_dev;
entry->guid = header->guid;
entry->size = header->size;
entry->cb = ivdev->priv_data;
@@ -284,8 +316,8 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
entry->kobj = &dev->kobj;
- if (ns->attr_grp) {
- ret = sysfs_create_group(entry->kobj, ns->attr_grp);
+ if (entry->attr_grp) {
+ ret = sysfs_create_group(entry->kobj, entry->attr_grp);
if (ret)
goto fail_sysfs_create_group;
}
@@ -308,7 +340,7 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
entry->pmt_bin_attr.attr.name = ns->name;
entry->pmt_bin_attr.attr.mode = 0440;
entry->pmt_bin_attr.mmap = intel_pmt_mmap;
- entry->pmt_bin_attr.read_new = intel_pmt_read;
+ entry->pmt_bin_attr.read = intel_pmt_read;
entry->pmt_bin_attr.size = entry->size;
ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr);
@@ -326,8 +358,8 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry,
fail_add_endpoint:
sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
fail_ioremap:
- if (ns->attr_grp)
- sysfs_remove_group(entry->kobj, ns->attr_grp);
+ if (entry->attr_grp)
+ sysfs_remove_group(entry->kobj, entry->attr_grp);
fail_sysfs_create_group:
device_unregister(dev);
fail_dev_create:
@@ -369,8 +401,8 @@ void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
if (entry->size)
sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr);
- if (ns->attr_grp)
- sysfs_remove_group(entry->kobj, ns->attr_grp);
+ if (entry->attr_grp)
+ sysfs_remove_group(entry->kobj, entry->attr_grp);
device_unregister(dev);
xa_erase(ns->xa, entry->devid);
diff --git a/drivers/platform/x86/intel/pmt/class.h b/drivers/platform/x86/intel/pmt/class.h
index b2006d57779d..1ae56a5baad2 100644
--- a/drivers/platform/x86/intel/pmt/class.h
+++ b/drivers/platform/x86/intel/pmt/class.h
@@ -19,10 +19,12 @@
#define GET_BIR(v) ((v) & GENMASK(2, 0))
#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
+struct device;
struct pci_dev;
+extern struct class intel_pmt_class;
struct telem_endpoint {
- struct pci_dev *pcidev;
+ struct device *dev;
struct telem_header header;
struct pmt_callbacks *cb;
void __iomem *base;
@@ -39,29 +41,32 @@ struct intel_pmt_header {
struct intel_pmt_entry {
struct telem_endpoint *ep;
+ struct pci_dev *pcidev;
struct intel_pmt_header header;
struct bin_attribute pmt_bin_attr;
+ const struct attribute_group *attr_grp;
struct kobject *kobj;
void __iomem *disc_table;
void __iomem *base;
struct pmt_callbacks *cb;
unsigned long base_addr;
size_t size;
+ u64 feature_flags;
u32 guid;
+ u32 num_rmids; /* Number of Resource Monitoring IDs */
int devid;
};
struct intel_pmt_namespace {
const char *name;
struct xarray *xa;
- const struct attribute_group *attr_grp;
int (*pmt_header_decode)(struct intel_pmt_entry *entry,
struct device *dev);
int (*pmt_add_endpoint)(struct intel_vsec_device *ivdev,
struct intel_pmt_entry *entry);
};
-int pmt_telem_read_mmio(struct pci_dev *pdev, struct pmt_callbacks *cb, u32 guid, void *buf,
+int pmt_telem_read_mmio(struct device *dev, struct pmt_callbacks *cb, u32 guid, void *buf,
void __iomem *addr, loff_t off, u32 count);
bool intel_pmt_is_early_client_hw(struct device *dev);
int intel_pmt_dev_create(struct intel_pmt_entry *entry,
@@ -69,4 +74,10 @@ int intel_pmt_dev_create(struct intel_pmt_entry *entry,
struct intel_vsec_device *dev, int idx);
void intel_pmt_dev_destroy(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns);
+#if IS_ENABLED(CONFIG_INTEL_PMT_DISCOVERY)
+void intel_pmt_get_features(struct intel_pmt_entry *entry);
+#else
+static inline void intel_pmt_get_features(struct intel_pmt_entry *entry) {}
+#endif
+
#endif
diff --git a/drivers/platform/x86/intel/pmt/crashlog.c b/drivers/platform/x86/intel/pmt/crashlog.c
index 6a9eb3c4b313..b0393c9c5b4b 100644
--- a/drivers/platform/x86/intel/pmt/crashlog.c
+++ b/drivers/platform/x86/intel/pmt/crashlog.c
@@ -9,9 +9,11 @@
*/
#include <linux/auxiliary_bus.h>
+#include <linux/cleanup.h>
#include <linux/intel_vsec.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
@@ -22,21 +24,6 @@
/* Crashlog discovery header types */
#define CRASH_TYPE_OOBMSM 1
-/* Control Flags */
-#define CRASHLOG_FLAG_DISABLE BIT(28)
-
-/*
- * Bits 29 and 30 control the state of bit 31.
- *
- * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured.
- * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.
- * Bit 31 is the read-only status with a 1 indicating log is complete.
- */
-#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(29)
-#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(30)
-#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31)
-#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28)
-
/* Crashlog Discovery Header */
#define CONTROL_OFFSET 0x0
#define GUID_OFFSET 0x4
@@ -48,10 +35,84 @@
/* size is in bytes */
#define GET_SIZE(v) ((v) * sizeof(u32))
+/*
+ * Type 1 Version 0
+ * status and control registers are combined.
+ *
+ * Bits 29 and 30 control the state of bit 31.
+ * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured.
+ * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31.
+ * Bit 31 is the read-only status with a 1 indicating log is complete.
+ */
+#define TYPE1_VER0_STATUS_OFFSET 0x00
+#define TYPE1_VER0_CONTROL_OFFSET 0x00
+
+#define TYPE1_VER0_DISABLE BIT(28)
+#define TYPE1_VER0_CLEAR BIT(29)
+#define TYPE1_VER0_EXECUTE BIT(30)
+#define TYPE1_VER0_COMPLETE BIT(31)
+#define TYPE1_VER0_TRIGGER_MASK GENMASK(31, 28)
+
+/*
+ * Type 1 Version 2
+ * status and control are different registers
+ */
+#define TYPE1_VER2_STATUS_OFFSET 0x00
+#define TYPE1_VER2_CONTROL_OFFSET 0x14
+
+/* status register */
+#define TYPE1_VER2_CLEAR_SUPPORT BIT(20)
+#define TYPE1_VER2_REARMED BIT(25)
+#define TYPE1_VER2_ERROR BIT(26)
+#define TYPE1_VER2_CONSUMED BIT(27)
+#define TYPE1_VER2_DISABLED BIT(28)
+#define TYPE1_VER2_CLEARED BIT(29)
+#define TYPE1_VER2_IN_PROGRESS BIT(30)
+#define TYPE1_VER2_COMPLETE BIT(31)
+
+/* control register */
+#define TYPE1_VER2_CONSUME BIT(25)
+#define TYPE1_VER2_REARM BIT(28)
+#define TYPE1_VER2_EXECUTE BIT(29)
+#define TYPE1_VER2_CLEAR BIT(30)
+#define TYPE1_VER2_DISABLE BIT(31)
+#define TYPE1_VER2_TRIGGER_MASK \
+ (TYPE1_VER2_EXECUTE | TYPE1_VER2_CLEAR | TYPE1_VER2_DISABLE)
+
+/* After offset, order alphabetically, not bit ordered */
+struct crashlog_status {
+ u32 offset;
+ u32 clear_supported;
+ u32 cleared;
+ u32 complete;
+ u32 consumed;
+ u32 disabled;
+ u32 error;
+ u32 in_progress;
+ u32 rearmed;
+};
+
+struct crashlog_control {
+ u32 offset;
+ u32 trigger_mask;
+ u32 clear;
+ u32 consume;
+ u32 disable;
+ u32 manual;
+ u32 rearm;
+};
+
+struct crashlog_info {
+ const struct crashlog_status status;
+ const struct crashlog_control control;
+ const struct attribute_group *attr_grp;
+};
+
struct crashlog_entry {
/* entry must be first member of struct */
struct intel_pmt_entry entry;
struct mutex control_mutex;
+ const struct crashlog_info *info;
};
struct pmt_crashlog_priv {
@@ -62,180 +123,397 @@ struct pmt_crashlog_priv {
/*
* I/O
*/
-static bool pmt_crashlog_complete(struct intel_pmt_entry *entry)
+
+/* Read, modify, write the control register, setting or clearing @bit based on @set */
+static void pmt_crashlog_rmw(struct crashlog_entry *crashlog, u32 bit, bool set)
{
- u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+ const struct crashlog_control *control = &crashlog->info->control;
+ struct intel_pmt_entry *entry = &crashlog->entry;
+ u32 reg = readl(entry->disc_table + control->offset);
- /* return current value of the crashlog complete flag */
- return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE);
+ reg &= ~control->trigger_mask;
+
+ if (set)
+ reg |= bit;
+ else
+ reg &= ~bit;
+
+ writel(reg, entry->disc_table + control->offset);
}
-static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry)
+/* Read the status register and see if the specified @bit is set */
+static bool pmt_crashlog_rc(struct crashlog_entry *crashlog, u32 bit)
{
- u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+ const struct crashlog_status *status = &crashlog->info->status;
+ u32 reg = readl(crashlog->entry.disc_table + status->offset);
+ return !!(reg & bit);
+}
+
+static bool pmt_crashlog_complete(struct crashlog_entry *crashlog)
+{
+ /* return current value of the crashlog complete flag */
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.complete);
+}
+
+static bool pmt_crashlog_disabled(struct crashlog_entry *crashlog)
+{
/* return current value of the crashlog disabled flag */
- return !!(control & CRASHLOG_FLAG_DISABLE);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.disabled);
}
-static bool pmt_crashlog_supported(struct intel_pmt_entry *entry)
+static bool pmt_crashlog_supported(struct intel_pmt_entry *entry, u32 *crash_type, u32 *version)
{
u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET);
- u32 crash_type, version;
- crash_type = GET_TYPE(discovery_header);
- version = GET_VERSION(discovery_header);
+ *crash_type = GET_TYPE(discovery_header);
+ *version = GET_VERSION(discovery_header);
/*
- * Currently we only recognize OOBMSM version 0 devices.
- * We can ignore all other crashlog devices in the system.
+ * Currently we only recognize OOBMSM (type 1) and version 0 or 2
+ * devices.
+ *
+ * Ignore all other crashlog devices in the system.
*/
- return crash_type == CRASH_TYPE_OOBMSM && version == 0;
+ if (*crash_type == CRASH_TYPE_OOBMSM && (*version == 0 || *version == 2))
+ return true;
+
+ return false;
}
-static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry,
+static void pmt_crashlog_set_disable(struct crashlog_entry *crashlog,
bool disable)
{
- u32 control = readl(entry->disc_table + CONTROL_OFFSET);
-
- /* clear trigger bits so we are only modifying disable flag */
- control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
+ pmt_crashlog_rmw(crashlog, crashlog->info->control.disable, disable);
+}
- if (disable)
- control |= CRASHLOG_FLAG_DISABLE;
- else
- control &= ~CRASHLOG_FLAG_DISABLE;
+static void pmt_crashlog_set_clear(struct crashlog_entry *crashlog)
+{
+ pmt_crashlog_rmw(crashlog, crashlog->info->control.clear, true);
+}
- writel(control, entry->disc_table + CONTROL_OFFSET);
+static void pmt_crashlog_set_execute(struct crashlog_entry *crashlog)
+{
+ pmt_crashlog_rmw(crashlog, crashlog->info->control.manual, true);
}
-static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry)
+static bool pmt_crashlog_cleared(struct crashlog_entry *crashlog)
{
- u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.cleared);
+}
- control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
- control |= CRASHLOG_FLAG_TRIGGER_CLEAR;
+static bool pmt_crashlog_consumed(struct crashlog_entry *crashlog)
+{
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.consumed);
+}
- writel(control, entry->disc_table + CONTROL_OFFSET);
+static void pmt_crashlog_set_consumed(struct crashlog_entry *crashlog)
+{
+ pmt_crashlog_rmw(crashlog, crashlog->info->control.consume, true);
}
-static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry)
+static bool pmt_crashlog_error(struct crashlog_entry *crashlog)
{
- u32 control = readl(entry->disc_table + CONTROL_OFFSET);
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.error);
+}
- control &= ~CRASHLOG_FLAG_TRIGGER_MASK;
- control |= CRASHLOG_FLAG_TRIGGER_EXECUTE;
+static bool pmt_crashlog_rearm(struct crashlog_entry *crashlog)
+{
+ return pmt_crashlog_rc(crashlog, crashlog->info->status.rearmed);
+}
- writel(control, entry->disc_table + CONTROL_OFFSET);
+static void pmt_crashlog_set_rearm(struct crashlog_entry *crashlog)
+{
+ pmt_crashlog_rmw(crashlog, crashlog->info->control.rearm, true);
}
/*
* sysfs
*/
static ssize_t
+clear_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct crashlog_entry *crashlog = dev_get_drvdata(dev);
+ bool cleared = pmt_crashlog_cleared(crashlog);
+
+ return sysfs_emit(buf, "%d\n", cleared);
+}
+
+static ssize_t
+clear_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct crashlog_entry *crashlog;
+ bool clear;
+ int result;
+
+ crashlog = dev_get_drvdata(dev);
+
+ result = kstrtobool(buf, &clear);
+ if (result)
+ return result;
+
+ /* set bit only */
+ if (!clear)
+ return -EINVAL;
+
+ guard(mutex)(&crashlog->control_mutex);
+
+ pmt_crashlog_set_clear(crashlog);
+
+ return count;
+}
+static DEVICE_ATTR_RW(clear);
+
+static ssize_t
+consumed_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct crashlog_entry *crashlog = dev_get_drvdata(dev);
+ bool consumed = pmt_crashlog_consumed(crashlog);
+
+ return sysfs_emit(buf, "%d\n", consumed);
+}
+
+static ssize_t
+consumed_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct crashlog_entry *crashlog;
+ bool consumed;
+ int result;
+
+ crashlog = dev_get_drvdata(dev);
+
+ result = kstrtobool(buf, &consumed);
+ if (result)
+ return result;
+
+ /* set bit only */
+ if (!consumed)
+ return -EINVAL;
+
+ guard(mutex)(&crashlog->control_mutex);
+
+ if (pmt_crashlog_disabled(crashlog))
+ return -EBUSY;
+
+ if (!pmt_crashlog_complete(crashlog))
+ return -EEXIST;
+
+ pmt_crashlog_set_consumed(crashlog);
+
+ return count;
+}
+static DEVICE_ATTR_RW(consumed);
+
+static ssize_t
enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct intel_pmt_entry *entry = dev_get_drvdata(dev);
- int enabled = !pmt_crashlog_disabled(entry);
+ struct crashlog_entry *crashlog = dev_get_drvdata(dev);
+ bool enabled = !pmt_crashlog_disabled(crashlog);
return sprintf(buf, "%d\n", enabled);
}
static ssize_t
enable_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+ const char *buf, size_t count)
{
- struct crashlog_entry *entry;
+ struct crashlog_entry *crashlog;
bool enabled;
int result;
- entry = dev_get_drvdata(dev);
+ crashlog = dev_get_drvdata(dev);
result = kstrtobool(buf, &enabled);
if (result)
return result;
- mutex_lock(&entry->control_mutex);
- pmt_crashlog_set_disable(&entry->entry, !enabled);
- mutex_unlock(&entry->control_mutex);
+ guard(mutex)(&crashlog->control_mutex);
+
+ pmt_crashlog_set_disable(crashlog, !enabled);
return count;
}
static DEVICE_ATTR_RW(enable);
static ssize_t
+error_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct crashlog_entry *crashlog = dev_get_drvdata(dev);
+ bool error = pmt_crashlog_error(crashlog);
+
+ return sysfs_emit(buf, "%d\n", error);
+}
+static DEVICE_ATTR_RO(error);
+
+static ssize_t
+rearm_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct crashlog_entry *crashlog = dev_get_drvdata(dev);
+ int rearmed = pmt_crashlog_rearm(crashlog);
+
+ return sysfs_emit(buf, "%d\n", rearmed);
+}
+
+static ssize_t
+rearm_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct crashlog_entry *crashlog;
+ bool rearm;
+ int result;
+
+ crashlog = dev_get_drvdata(dev);
+
+ result = kstrtobool(buf, &rearm);
+ if (result)
+ return result;
+
+ /* set only */
+ if (!rearm)
+ return -EINVAL;
+
+ guard(mutex)(&crashlog->control_mutex);
+
+ pmt_crashlog_set_rearm(crashlog);
+
+ return count;
+}
+static DEVICE_ATTR_RW(rearm);
+
+static ssize_t
trigger_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- struct intel_pmt_entry *entry;
- int trigger;
+ struct crashlog_entry *crashlog;
+ bool trigger;
- entry = dev_get_drvdata(dev);
- trigger = pmt_crashlog_complete(entry);
+ crashlog = dev_get_drvdata(dev);
+ trigger = pmt_crashlog_complete(crashlog);
return sprintf(buf, "%d\n", trigger);
}
static ssize_t
trigger_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+ const char *buf, size_t count)
{
- struct crashlog_entry *entry;
+ struct crashlog_entry *crashlog;
bool trigger;
int result;
- entry = dev_get_drvdata(dev);
+ crashlog = dev_get_drvdata(dev);
result = kstrtobool(buf, &trigger);
if (result)
return result;
- mutex_lock(&entry->control_mutex);
+ guard(mutex)(&crashlog->control_mutex);
+
+ /* if device is currently disabled, return busy */
+ if (pmt_crashlog_disabled(crashlog))
+ return -EBUSY;
if (!trigger) {
- pmt_crashlog_set_clear(&entry->entry);
- } else if (pmt_crashlog_complete(&entry->entry)) {
- /* we cannot trigger a new crash if one is still pending */
- result = -EEXIST;
- goto err;
- } else if (pmt_crashlog_disabled(&entry->entry)) {
- /* if device is currently disabled, return busy */
- result = -EBUSY;
- goto err;
- } else {
- pmt_crashlog_set_execute(&entry->entry);
+ pmt_crashlog_set_clear(crashlog);
+ return count;
}
- result = count;
-err:
- mutex_unlock(&entry->control_mutex);
- return result;
+ /* we cannot trigger a new crash if one is still pending */
+ if (pmt_crashlog_complete(crashlog))
+ return -EEXIST;
+
+ pmt_crashlog_set_execute(crashlog);
+
+ return count;
}
static DEVICE_ATTR_RW(trigger);
-static struct attribute *pmt_crashlog_attrs[] = {
+static struct attribute *pmt_crashlog_type1_ver0_attrs[] = {
&dev_attr_enable.attr,
&dev_attr_trigger.attr,
NULL
};
-static const struct attribute_group pmt_crashlog_group = {
- .attrs = pmt_crashlog_attrs,
+static struct attribute *pmt_crashlog_type1_ver2_attrs[] = {
+ &dev_attr_clear.attr,
+ &dev_attr_consumed.attr,
+ &dev_attr_enable.attr,
+ &dev_attr_error.attr,
+ &dev_attr_rearm.attr,
+ &dev_attr_trigger.attr,
+ NULL
+};
+
+static const struct attribute_group pmt_crashlog_type1_ver0_group = {
+ .attrs = pmt_crashlog_type1_ver0_attrs,
+};
+
+static const struct attribute_group pmt_crashlog_type1_ver2_group = {
+ .attrs = pmt_crashlog_type1_ver2_attrs,
+};
+
+static const struct crashlog_info crashlog_type1_ver0 = {
+ .status.offset = TYPE1_VER0_STATUS_OFFSET,
+ .status.cleared = TYPE1_VER0_CLEAR,
+ .status.complete = TYPE1_VER0_COMPLETE,
+ .status.disabled = TYPE1_VER0_DISABLE,
+
+ .control.offset = TYPE1_VER0_CONTROL_OFFSET,
+ .control.trigger_mask = TYPE1_VER0_TRIGGER_MASK,
+ .control.clear = TYPE1_VER0_CLEAR,
+ .control.disable = TYPE1_VER0_DISABLE,
+ .control.manual = TYPE1_VER0_EXECUTE,
+ .attr_grp = &pmt_crashlog_type1_ver0_group,
+};
+
+static const struct crashlog_info crashlog_type1_ver2 = {
+ .status.offset = TYPE1_VER2_STATUS_OFFSET,
+ .status.clear_supported = TYPE1_VER2_CLEAR_SUPPORT,
+ .status.cleared = TYPE1_VER2_CLEARED,
+ .status.complete = TYPE1_VER2_COMPLETE,
+ .status.consumed = TYPE1_VER2_CONSUMED,
+ .status.disabled = TYPE1_VER2_DISABLED,
+ .status.error = TYPE1_VER2_ERROR,
+ .status.in_progress = TYPE1_VER2_IN_PROGRESS,
+ .status.rearmed = TYPE1_VER2_REARMED,
+
+ .control.offset = TYPE1_VER2_CONTROL_OFFSET,
+ .control.trigger_mask = TYPE1_VER2_TRIGGER_MASK,
+ .control.clear = TYPE1_VER2_CLEAR,
+ .control.consume = TYPE1_VER2_CONSUME,
+ .control.disable = TYPE1_VER2_DISABLE,
+ .control.manual = TYPE1_VER2_EXECUTE,
+ .control.rearm = TYPE1_VER2_REARM,
+ .attr_grp = &pmt_crashlog_type1_ver2_group,
};
+static const struct crashlog_info *select_crashlog_info(u32 type, u32 version)
+{
+ if (version == 0)
+ return &crashlog_type1_ver0;
+
+ return &crashlog_type1_ver2;
+}
+
static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
struct device *dev)
{
void __iomem *disc_table = entry->disc_table;
struct intel_pmt_header *header = &entry->header;
struct crashlog_entry *crashlog;
+ u32 version;
+ u32 type;
- if (!pmt_crashlog_supported(entry))
+ if (!pmt_crashlog_supported(entry, &type, &version))
return 1;
- /* initialize control mutex */
+ /* initialize the crashlog struct */
crashlog = container_of(entry, struct crashlog_entry, entry);
mutex_init(&crashlog->control_mutex);
+ crashlog->info = select_crashlog_info(type, version);
+
header->access_type = GET_ACCESS(readl(disc_table));
header->guid = readl(disc_table + GUID_OFFSET);
header->base_offset = readl(disc_table + BASE_OFFSET);
@@ -243,6 +521,8 @@ static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry,
/* Size is measured in DWORDS, but accessor returns bytes */
header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET));
+ entry->attr_grp = crashlog->info->attr_grp;
+
return 0;
}
@@ -250,7 +530,6 @@ static DEFINE_XARRAY_ALLOC(crashlog_array);
static struct intel_pmt_namespace pmt_crashlog_ns = {
.name = "crashlog",
.xa = &crashlog_array,
- .attr_grp = &pmt_crashlog_group,
.pmt_header_decode = pmt_crashlog_header_decode,
};
@@ -262,8 +541,12 @@ static void pmt_crashlog_remove(struct auxiliary_device *auxdev)
struct pmt_crashlog_priv *priv = auxiliary_get_drvdata(auxdev);
int i;
- for (i = 0; i < priv->num_entries; i++)
- intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns);
+ for (i = 0; i < priv->num_entries; i++) {
+ struct crashlog_entry *crashlog = &priv->entry[i];
+
+ intel_pmt_dev_destroy(&crashlog->entry, &pmt_crashlog_ns);
+ mutex_destroy(&crashlog->control_mutex);
+ }
}
static int pmt_crashlog_probe(struct auxiliary_device *auxdev,
diff --git a/drivers/platform/x86/intel/pmt/discovery-kunit.c b/drivers/platform/x86/intel/pmt/discovery-kunit.c
new file mode 100644
index 000000000000..f44eb41d58f6
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/discovery-kunit.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitory Technology Discovery KUNIT tests
+ *
+ * Copyright (c) 2025, Intel Corporation.
+ * All Rights Reserved.
+ */
+
+#include <kunit/test.h>
+#include <linux/err.h>
+#include <linux/intel_pmt_features.h>
+#include <linux/intel_vsec.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define PMT_FEATURE_COUNT (FEATURE_MAX + 1)
+
+static void
+validate_pmt_regions(struct kunit *test, struct pmt_feature_group *feature_group, int feature_id)
+{
+ int i;
+
+ kunit_info(test, "Feature ID %d [%s] has %d regions.\n", feature_id,
+ pmt_feature_names[feature_id], feature_group->count);
+
+ for (i = 0; i < feature_group->count; i++) {
+ struct telemetry_region *region = &feature_group->regions[i];
+
+ kunit_info(test, " - Region %d: cdie_mask=%u, package_id=%u, partition=%u, segment=%u,",
+ i, region->plat_info.cdie_mask, region->plat_info.package_id,
+ region->plat_info.partition, region->plat_info.segment);
+ kunit_info(test, "\t\tbus=%u, device=%u, function=%u, guid=0x%x,",
+ region->plat_info.bus_number, region->plat_info.device_number,
+ region->plat_info.function_number, region->guid);
+ kunit_info(test, "\t\taddr=%p, size=%zu, num_rmids=%u", region->addr, region->size,
+ region->num_rmids);
+
+
+ KUNIT_ASSERT_GE(test, region->plat_info.cdie_mask, 0);
+ KUNIT_ASSERT_GE(test, region->plat_info.package_id, 0);
+ KUNIT_ASSERT_GE(test, region->plat_info.partition, 0);
+ KUNIT_ASSERT_GE(test, region->plat_info.segment, 0);
+ KUNIT_ASSERT_GE(test, region->plat_info.bus_number, 0);
+ KUNIT_ASSERT_GE(test, region->plat_info.device_number, 0);
+ KUNIT_ASSERT_GE(test, region->plat_info.function_number, 0);
+
+ KUNIT_ASSERT_NE(test, region->guid, 0);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, (__force const void *)region->addr);
+ }
+}
+
+static void linebreak(struct kunit *test)
+{
+ kunit_info(test, "*****************************************************************************\n");
+}
+
+static void test_intel_pmt_get_regions_by_feature(struct kunit *test)
+{
+ struct pmt_feature_group *feature_group;
+ int num_available = 0;
+ int feature_id;
+
+ /* Iterate through all possible feature IDs */
+ for (feature_id = 1; feature_id < PMT_FEATURE_COUNT; feature_id++, linebreak(test)) {
+ const char *name;
+
+ if (!pmt_feature_id_is_valid(feature_id))
+ continue;
+
+ name = pmt_feature_names[feature_id];
+
+ feature_group = intel_pmt_get_regions_by_feature(feature_id);
+ if (IS_ERR(feature_group)) {
+ if (PTR_ERR(feature_group) == -ENOENT)
+ kunit_warn(test, "intel_pmt_get_regions_by_feature() reporting feature %d [%s] is not present.\n",
+ feature_id, name);
+ else
+ kunit_warn(test, "intel_pmt_get_regions_by_feature() returned error %ld while attempt to lookup %d [%s].\n",
+ PTR_ERR(feature_group), feature_id, name);
+
+ continue;
+ }
+
+ if (!feature_group) {
+ kunit_warn(test, "Feature ID %d: %s is not available.\n", feature_id, name);
+ continue;
+ }
+
+ num_available++;
+
+ validate_pmt_regions(test, feature_group, feature_id);
+
+ intel_pmt_put_feature_group(feature_group);
+ }
+
+ if (num_available == 0)
+ kunit_warn(test, "No PMT region groups were available for any feature ID (0-10).\n");
+}
+
+static struct kunit_case intel_pmt_discovery_test_cases[] = {
+ KUNIT_CASE(test_intel_pmt_get_regions_by_feature),
+ {}
+};
+
+static struct kunit_suite intel_pmt_discovery_test_suite = {
+ .name = "pmt_discovery_test",
+ .test_cases = intel_pmt_discovery_test_cases,
+};
+
+kunit_test_suite(intel_pmt_discovery_test_suite);
+
+MODULE_IMPORT_NS("INTEL_PMT_DISCOVERY");
+MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
+MODULE_DESCRIPTION("Intel PMT Discovery KUNIT test driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/pmt/discovery.c b/drivers/platform/x86/intel/pmt/discovery.c
new file mode 100644
index 000000000000..c482368bfaae
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/discovery.c
@@ -0,0 +1,637 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Platform Monitory Technology Discovery driver
+ *
+ * Copyright (c) 2025, Intel Corporation.
+ * All Rights Reserved.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kdev_t.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/string_choices.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+
+#include <linux/intel_pmt_features.h>
+#include <linux/intel_vsec.h>
+
+#include "class.h"
+
+#define MAX_FEATURE_VERSION 0
+#define DT_TBIR GENMASK(2, 0)
+#define FEAT_ATTR_SIZE(x) ((x) * sizeof(u32))
+#define PMT_GUID_SIZE(x) ((x) * sizeof(u32))
+#define PMT_ACCESS_TYPE_RSVD 0xF
+#define SKIP_FEATURE 1
+
+struct feature_discovery_table {
+ u32 access_type:4;
+ u32 version:8;
+ u32 size:16;
+ u32 reserved:4;
+ u32 id;
+ u32 offset;
+ u32 reserved2;
+};
+
+/* Common feature table header */
+struct feature_header {
+ u32 attr_size:8;
+ u32 num_guids:8;
+ u32 reserved:16;
+};
+
+/* Feature attribute fields */
+struct caps {
+ u32 caps;
+};
+
+struct command {
+ u32 max_stream_size:16;
+ u32 max_command_size:16;
+};
+
+struct watcher {
+ u32 reserved:21;
+ u32 period:11;
+ struct command command;
+};
+
+struct rmid {
+ u32 num_rmids:16; /* Number of Resource Monitoring IDs */
+ u32 reserved:16;
+ struct watcher watcher;
+};
+
+struct feature_table {
+ struct feature_header header;
+ struct caps caps;
+ union {
+ struct command command;
+ struct watcher watcher;
+ struct rmid rmid;
+ };
+ u32 *guids;
+};
+
+/* For backreference in struct feature */
+struct pmt_features_priv;
+
+struct feature {
+ struct feature_table table;
+ struct kobject kobj;
+ struct pmt_features_priv *priv;
+ struct list_head list;
+ const struct attribute_group *attr_group;
+ enum pmt_feature_id id;
+};
+
+struct pmt_features_priv {
+ struct device *parent;
+ struct device *dev;
+ int count;
+ u32 mask;
+ struct feature feature[];
+};
+
+static LIST_HEAD(pmt_feature_list);
+static DEFINE_MUTEX(feature_list_lock);
+
+#define to_pmt_feature(x) container_of(x, struct feature, kobj)
+static void pmt_feature_release(struct kobject *kobj)
+{
+}
+
+static ssize_t caps_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct feature *feature = to_pmt_feature(kobj);
+ struct pmt_cap **pmt_caps;
+ u32 caps = feature->table.caps.caps;
+ ssize_t ret = 0;
+
+ switch (feature->id) {
+ case FEATURE_PER_CORE_PERF_TELEM:
+ pmt_caps = pmt_caps_pcpt;
+ break;
+ case FEATURE_PER_CORE_ENV_TELEM:
+ pmt_caps = pmt_caps_pcet;
+ break;
+ case FEATURE_PER_RMID_PERF_TELEM:
+ pmt_caps = pmt_caps_rmid_perf;
+ break;
+ case FEATURE_ACCEL_TELEM:
+ pmt_caps = pmt_caps_accel;
+ break;
+ case FEATURE_UNCORE_TELEM:
+ pmt_caps = pmt_caps_uncore;
+ break;
+ case FEATURE_CRASH_LOG:
+ pmt_caps = pmt_caps_crashlog;
+ break;
+ case FEATURE_PETE_LOG:
+ pmt_caps = pmt_caps_pete;
+ break;
+ case FEATURE_TPMI_CTRL:
+ pmt_caps = pmt_caps_tpmi;
+ break;
+ case FEATURE_TRACING:
+ pmt_caps = pmt_caps_tracing;
+ break;
+ case FEATURE_PER_RMID_ENERGY_TELEM:
+ pmt_caps = pmt_caps_rmid_energy;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ while (*pmt_caps) {
+ struct pmt_cap *pmt_cap = *pmt_caps;
+
+ while (pmt_cap->name) {
+ ret += sysfs_emit_at(buf, ret, "%-40s Available: %s\n", pmt_cap->name,
+ str_yes_no(pmt_cap->mask & caps));
+ pmt_cap++;
+ }
+ pmt_caps++;
+ }
+
+ return ret;
+}
+static struct kobj_attribute caps_attribute = __ATTR_RO(caps);
+
+static struct watcher *get_watcher(struct feature *feature)
+{
+ switch (feature_layout[feature->id]) {
+ case LAYOUT_RMID:
+ return &feature->table.rmid.watcher;
+ case LAYOUT_WATCHER:
+ return &feature->table.watcher;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static struct command *get_command(struct feature *feature)
+{
+ switch (feature_layout[feature->id]) {
+ case LAYOUT_RMID:
+ return &feature->table.rmid.watcher.command;
+ case LAYOUT_WATCHER:
+ return &feature->table.watcher.command;
+ case LAYOUT_COMMAND:
+ return &feature->table.command;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static ssize_t num_rmids_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct feature *feature = to_pmt_feature(kobj);
+
+ return sysfs_emit(buf, "%u\n", feature->table.rmid.num_rmids);
+}
+static struct kobj_attribute num_rmids_attribute = __ATTR_RO(num_rmids);
+
+static ssize_t min_watcher_period_ms_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct feature *feature = to_pmt_feature(kobj);
+ struct watcher *watcher = get_watcher(feature);
+
+ if (IS_ERR(watcher))
+ return PTR_ERR(watcher);
+
+ return sysfs_emit(buf, "%u\n", watcher->period);
+}
+static struct kobj_attribute min_watcher_period_ms_attribute =
+ __ATTR_RO(min_watcher_period_ms);
+
+static ssize_t max_stream_size_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct feature *feature = to_pmt_feature(kobj);
+ struct command *command = get_command(feature);
+
+ if (IS_ERR(command))
+ return PTR_ERR(command);
+
+ return sysfs_emit(buf, "%u\n", command->max_stream_size);
+}
+static struct kobj_attribute max_stream_size_attribute =
+ __ATTR_RO(max_stream_size);
+
+static ssize_t max_command_size_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct feature *feature = to_pmt_feature(kobj);
+ struct command *command = get_command(feature);
+
+ if (IS_ERR(command))
+ return PTR_ERR(command);
+
+ return sysfs_emit(buf, "%u\n", command->max_command_size);
+}
+static struct kobj_attribute max_command_size_attribute =
+ __ATTR_RO(max_command_size);
+
+static ssize_t guids_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct feature *feature = to_pmt_feature(kobj);
+ int i, count = 0;
+
+ for (i = 0; i < feature->table.header.num_guids; i++)
+ count += sysfs_emit_at(buf, count, "0x%x\n",
+ feature->table.guids[i]);
+
+ return count;
+}
+static struct kobj_attribute guids_attribute = __ATTR_RO(guids);
+
+static struct attribute *pmt_feature_rmid_attrs[] = {
+ &caps_attribute.attr,
+ &num_rmids_attribute.attr,
+ &min_watcher_period_ms_attribute.attr,
+ &max_stream_size_attribute.attr,
+ &max_command_size_attribute.attr,
+ &guids_attribute.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(pmt_feature_rmid);
+
+static const struct kobj_type pmt_feature_rmid_ktype = {
+ .sysfs_ops = &kobj_sysfs_ops,
+ .release = pmt_feature_release,
+ .default_groups = pmt_feature_rmid_groups,
+};
+
+static struct attribute *pmt_feature_watcher_attrs[] = {
+ &caps_attribute.attr,
+ &min_watcher_period_ms_attribute.attr,
+ &max_stream_size_attribute.attr,
+ &max_command_size_attribute.attr,
+ &guids_attribute.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(pmt_feature_watcher);
+
+static const struct kobj_type pmt_feature_watcher_ktype = {
+ .sysfs_ops = &kobj_sysfs_ops,
+ .release = pmt_feature_release,
+ .default_groups = pmt_feature_watcher_groups,
+};
+
+static struct attribute *pmt_feature_command_attrs[] = {
+ &caps_attribute.attr,
+ &max_stream_size_attribute.attr,
+ &max_command_size_attribute.attr,
+ &guids_attribute.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(pmt_feature_command);
+
+static const struct kobj_type pmt_feature_command_ktype = {
+ .sysfs_ops = &kobj_sysfs_ops,
+ .release = pmt_feature_release,
+ .default_groups = pmt_feature_command_groups,
+};
+
+static struct attribute *pmt_feature_guids_attrs[] = {
+ &caps_attribute.attr,
+ &guids_attribute.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(pmt_feature_guids);
+
+static const struct kobj_type pmt_feature_guids_ktype = {
+ .sysfs_ops = &kobj_sysfs_ops,
+ .release = pmt_feature_release,
+ .default_groups = pmt_feature_guids_groups,
+};
+
+static int
+pmt_feature_get_disc_table(struct pmt_features_priv *priv,
+ struct resource *disc_res,
+ struct feature_discovery_table *disc_tbl)
+{
+ void __iomem *disc_base;
+
+ disc_base = devm_ioremap_resource(priv->dev, disc_res);
+ if (IS_ERR(disc_base))
+ return PTR_ERR(disc_base);
+
+ memcpy_fromio(disc_tbl, disc_base, sizeof(*disc_tbl));
+
+ devm_iounmap(priv->dev, disc_base);
+
+ if (priv->mask & BIT(disc_tbl->id))
+ return dev_err_probe(priv->dev, -EINVAL, "Duplicate feature: %s\n",
+ pmt_feature_names[disc_tbl->id]);
+
+ /*
+ * Some devices may expose non-functioning entries that are
+ * reserved for future use. They have zero size. Do not fail
+ * probe for these. Just ignore them.
+ */
+ if (disc_tbl->size == 0 || disc_tbl->access_type == PMT_ACCESS_TYPE_RSVD)
+ return SKIP_FEATURE;
+
+ if (disc_tbl->version > MAX_FEATURE_VERSION)
+ return SKIP_FEATURE;
+
+ if (!pmt_feature_id_is_valid(disc_tbl->id))
+ return SKIP_FEATURE;
+
+ priv->mask |= BIT(disc_tbl->id);
+
+ return 0;
+}
+
+static int
+pmt_feature_get_feature_table(struct pmt_features_priv *priv,
+ struct feature *feature,
+ struct feature_discovery_table *disc_tbl,
+ struct resource *disc_res)
+{
+ struct feature_table *feat_tbl = &feature->table;
+ struct feature_header *header;
+ struct resource res = {};
+ resource_size_t res_size;
+ void __iomem *feat_base, *feat_offset;
+ void *tbl_offset;
+ size_t size;
+ u32 *guids;
+ u8 tbir;
+
+ tbir = FIELD_GET(DT_TBIR, disc_tbl->offset);
+
+ switch (disc_tbl->access_type) {
+ case ACCESS_LOCAL:
+ if (tbir)
+ return dev_err_probe(priv->dev, -EINVAL,
+ "Unsupported BAR index %u for access type %u\n",
+ tbir, disc_tbl->access_type);
+
+
+ /*
+ * For access_type LOCAL, the base address is as follows:
+ * base address = end of discovery region + base offset + 1
+ */
+ res = DEFINE_RES_MEM(disc_res->end + disc_tbl->offset + 1,
+ disc_tbl->size * sizeof(u32));
+ break;
+
+ default:
+ return dev_err_probe(priv->dev, -EINVAL, "Unrecognized access_type %u\n",
+ disc_tbl->access_type);
+ }
+
+ feature->id = disc_tbl->id;
+
+ /* Get the feature table */
+ feat_base = devm_ioremap_resource(priv->dev, &res);
+ if (IS_ERR(feat_base))
+ return PTR_ERR(feat_base);
+
+ feat_offset = feat_base;
+ tbl_offset = feat_tbl;
+
+ /* Get the header */
+ header = &feat_tbl->header;
+ memcpy_fromio(header, feat_offset, sizeof(*header));
+
+ /* Validate fields fit within mapped resource */
+ size = sizeof(*header) + FEAT_ATTR_SIZE(header->attr_size) +
+ PMT_GUID_SIZE(header->num_guids);
+ res_size = resource_size(&res);
+ if (WARN(size > res_size, "Bad table size %zu > %pa", size, &res_size))
+ return -EINVAL;
+
+ /* Get the feature attributes, including capability fields */
+ tbl_offset += sizeof(*header);
+ feat_offset += sizeof(*header);
+
+ memcpy_fromio(tbl_offset, feat_offset, FEAT_ATTR_SIZE(header->attr_size));
+
+ /* Finally, get the guids */
+ guids = devm_kmalloc(priv->dev, PMT_GUID_SIZE(header->num_guids), GFP_KERNEL);
+ if (!guids)
+ return -ENOMEM;
+
+ feat_offset += FEAT_ATTR_SIZE(header->attr_size);
+
+ memcpy_fromio(guids, feat_offset, PMT_GUID_SIZE(header->num_guids));
+
+ feat_tbl->guids = guids;
+
+ devm_iounmap(priv->dev, feat_base);
+
+ return 0;
+}
+
+static void pmt_features_add_feat(struct feature *feature)
+{
+ guard(mutex)(&feature_list_lock);
+ list_add(&feature->list, &pmt_feature_list);
+}
+
+static void pmt_features_remove_feat(struct feature *feature)
+{
+ guard(mutex)(&feature_list_lock);
+ list_del(&feature->list);
+}
+
+/* Get the discovery table and use it to get the feature table */
+static int pmt_features_discovery(struct pmt_features_priv *priv,
+ struct feature *feature,
+ struct intel_vsec_device *ivdev,
+ int idx)
+{
+ struct feature_discovery_table disc_tbl = {}; /* Avoid false warning */
+ struct resource *disc_res = &ivdev->resource[idx];
+ const struct kobj_type *ktype;
+ int ret;
+
+ ret = pmt_feature_get_disc_table(priv, disc_res, &disc_tbl);
+ if (ret)
+ return ret;
+
+ ret = pmt_feature_get_feature_table(priv, feature, &disc_tbl, disc_res);
+ if (ret)
+ return ret;
+
+ switch (feature_layout[feature->id]) {
+ case LAYOUT_RMID:
+ ktype = &pmt_feature_rmid_ktype;
+ feature->attr_group = &pmt_feature_rmid_group;
+ break;
+ case LAYOUT_WATCHER:
+ ktype = &pmt_feature_watcher_ktype;
+ feature->attr_group = &pmt_feature_watcher_group;
+ break;
+ case LAYOUT_COMMAND:
+ ktype = &pmt_feature_command_ktype;
+ feature->attr_group = &pmt_feature_command_group;
+ break;
+ case LAYOUT_CAPS_ONLY:
+ ktype = &pmt_feature_guids_ktype;
+ feature->attr_group = &pmt_feature_guids_group;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = kobject_init_and_add(&feature->kobj, ktype, &priv->dev->kobj,
+ "%s", pmt_feature_names[feature->id]);
+ if (ret) {
+ kobject_put(&feature->kobj);
+ return ret;
+ }
+
+ kobject_uevent(&feature->kobj, KOBJ_ADD);
+ pmt_features_add_feat(feature);
+
+ return 0;
+}
+
+static void pmt_features_remove(struct auxiliary_device *auxdev)
+{
+ struct pmt_features_priv *priv = auxiliary_get_drvdata(auxdev);
+ int i;
+
+ for (i = 0; i < priv->count; i++) {
+ struct feature *feature = &priv->feature[i];
+
+ pmt_features_remove_feat(feature);
+ sysfs_remove_group(&feature->kobj, feature->attr_group);
+ kobject_put(&feature->kobj);
+ }
+
+ device_unregister(priv->dev);
+}
+
+static int pmt_features_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
+{
+ struct intel_vsec_device *ivdev = auxdev_to_ivdev(auxdev);
+ struct pmt_features_priv *priv;
+ size_t size;
+ int ret, i;
+
+ size = struct_size(priv, feature, ivdev->num_resources);
+ priv = devm_kzalloc(&auxdev->dev, size, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->parent = ivdev->dev;
+ auxiliary_set_drvdata(auxdev, priv);
+
+ priv->dev = device_create(&intel_pmt_class, &auxdev->dev, MKDEV(0, 0), priv,
+ "%s-%s", "features", dev_name(priv->parent));
+ if (IS_ERR(priv->dev))
+ return dev_err_probe(&auxdev->dev, PTR_ERR(priv->dev),
+ "Could not create %s-%s device node\n",
+ "features", dev_name(priv->parent));
+
+ /* Initialize each feature */
+ for (i = 0; i < ivdev->num_resources; i++) {
+ struct feature *feature = &priv->feature[priv->count];
+
+ ret = pmt_features_discovery(priv, feature, ivdev, i);
+ if (ret == SKIP_FEATURE)
+ continue;
+ if (ret != 0)
+ goto abort_probe;
+
+ feature->priv = priv;
+ priv->count++;
+ }
+
+ return 0;
+
+abort_probe:
+ /*
+ * Only fully initialized features are tracked in priv->count, which is
+ * incremented only after a feature is completely set up (i.e., after
+ * discovery and sysfs registration). If feature initialization fails,
+ * the failing feature's state is local and does not require rollback.
+ *
+ * Therefore, on error, we can safely call the driver's remove() routine
+ * pmt_features_remove() to clean up only those features that were
+ * fully initialized and counted. All other resources are device-managed
+ * and will be cleaned up automatically during device_unregister().
+ */
+ pmt_features_remove(auxdev);
+
+ return ret;
+}
+
+static void pmt_get_features(struct intel_pmt_entry *entry, struct feature *f)
+{
+ int num_guids = f->table.header.num_guids;
+ int i;
+
+ for (i = 0; i < num_guids; i++) {
+ if (f->table.guids[i] != entry->guid)
+ continue;
+
+ entry->feature_flags |= BIT(f->id);
+
+ if (feature_layout[f->id] == LAYOUT_RMID)
+ entry->num_rmids = f->table.rmid.num_rmids;
+ else
+ entry->num_rmids = 0; /* entry is kzalloc but set anyway */
+ }
+}
+
+void intel_pmt_get_features(struct intel_pmt_entry *entry)
+{
+ struct feature *feature;
+
+ mutex_lock(&feature_list_lock);
+ list_for_each_entry(feature, &pmt_feature_list, list) {
+ if (feature->priv->parent != entry->ep->dev)
+ continue;
+
+ pmt_get_features(entry, feature);
+ }
+ mutex_unlock(&feature_list_lock);
+}
+EXPORT_SYMBOL_NS_GPL(intel_pmt_get_features, "INTEL_PMT");
+
+static const struct auxiliary_device_id pmt_features_id_table[] = {
+ { .name = "intel_vsec.discovery" },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, pmt_features_id_table);
+
+static struct auxiliary_driver pmt_features_aux_driver = {
+ .id_table = pmt_features_id_table,
+ .remove = pmt_features_remove,
+ .probe = pmt_features_probe,
+};
+module_auxiliary_driver(pmt_features_aux_driver);
+
+MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
+MODULE_DESCRIPTION("Intel PMT Discovery driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("INTEL_PMT");
diff --git a/drivers/platform/x86/intel/pmt/features.c b/drivers/platform/x86/intel/pmt/features.c
new file mode 100644
index 000000000000..8a39cddc75c8
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/features.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * Author: "David E. Box" <david.e.box@linux.intel.com>
+ */
+
+#include <linux/export.h>
+#include <linux/types.h>
+
+#include <linux/intel_pmt_features.h>
+
+const char * const pmt_feature_names[] = {
+ [FEATURE_PER_CORE_PERF_TELEM] = "per_core_performance_telemetry",
+ [FEATURE_PER_CORE_ENV_TELEM] = "per_core_environment_telemetry",
+ [FEATURE_PER_RMID_PERF_TELEM] = "per_rmid_perf_telemetry",
+ [FEATURE_ACCEL_TELEM] = "accelerator_telemetry",
+ [FEATURE_UNCORE_TELEM] = "uncore_telemetry",
+ [FEATURE_CRASH_LOG] = "crash_log",
+ [FEATURE_PETE_LOG] = "pete_log",
+ [FEATURE_TPMI_CTRL] = "tpmi_control",
+ [FEATURE_TRACING] = "tracing",
+ [FEATURE_PER_RMID_ENERGY_TELEM] = "per_rmid_energy_telemetry",
+};
+EXPORT_SYMBOL_NS_GPL(pmt_feature_names, "INTEL_PMT_DISCOVERY");
+
+enum feature_layout feature_layout[] = {
+ [FEATURE_PER_CORE_PERF_TELEM] = LAYOUT_WATCHER,
+ [FEATURE_PER_CORE_ENV_TELEM] = LAYOUT_WATCHER,
+ [FEATURE_PER_RMID_PERF_TELEM] = LAYOUT_RMID,
+ [FEATURE_ACCEL_TELEM] = LAYOUT_WATCHER,
+ [FEATURE_UNCORE_TELEM] = LAYOUT_WATCHER,
+ [FEATURE_CRASH_LOG] = LAYOUT_COMMAND,
+ [FEATURE_PETE_LOG] = LAYOUT_COMMAND,
+ [FEATURE_TPMI_CTRL] = LAYOUT_CAPS_ONLY,
+ [FEATURE_TRACING] = LAYOUT_CAPS_ONLY,
+ [FEATURE_PER_RMID_ENERGY_TELEM] = LAYOUT_RMID,
+};
+
+struct pmt_cap pmt_cap_common[] = {
+ {PMT_CAP_TELEM, "telemetry"},
+ {PMT_CAP_WATCHER, "watcher"},
+ {PMT_CAP_CRASHLOG, "crashlog"},
+ {PMT_CAP_STREAMING, "streaming"},
+ {PMT_CAP_THRESHOLD, "threshold"},
+ {PMT_CAP_WINDOW, "window"},
+ {PMT_CAP_CONFIG, "config"},
+ {PMT_CAP_TRACING, "tracing"},
+ {PMT_CAP_INBAND, "inband"},
+ {PMT_CAP_OOB, "oob"},
+ {PMT_CAP_SECURED_CHAN, "secure_chan"},
+ {PMT_CAP_PMT_SP, "pmt_sp"},
+ {PMT_CAP_PMT_SP_POLICY, "pmt_sp_policy"},
+ {}
+};
+
+struct pmt_cap pmt_cap_pcpt[] = {
+ {PMT_CAP_PCPT_CORE_PERF, "core_performance"},
+ {PMT_CAP_PCPT_CORE_C0_RES, "core_c0_residency"},
+ {PMT_CAP_PCPT_CORE_ACTIVITY, "core_activity"},
+ {PMT_CAP_PCPT_CACHE_PERF, "cache_performance"},
+ {PMT_CAP_PCPT_QUALITY_TELEM, "quality_telemetry"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_pcpt[] = {
+ pmt_cap_common,
+ pmt_cap_pcpt,
+ NULL
+};
+
+struct pmt_cap pmt_cap_pcet[] = {
+ {PMT_CAP_PCET_WORKPOINT_HIST, "workpoint_histogram"},
+ {PMT_CAP_PCET_CORE_CURR_TEMP, "core_current_temp"},
+ {PMT_CAP_PCET_CORE_INST_RES, "core_inst_residency"},
+ {PMT_CAP_PCET_QUALITY_TELEM, "quality_telemetry"},
+ {PMT_CAP_PCET_CORE_CDYN_LVL, "core_cdyn_level"},
+ {PMT_CAP_PCET_CORE_STRESS_LVL, "core_stress_level"},
+ {PMT_CAP_PCET_CORE_DAS, "core_digital_aging_sensor"},
+ {PMT_CAP_PCET_FIVR_HEALTH, "fivr_health"},
+ {PMT_CAP_PCET_ENERGY, "energy"},
+ {PMT_CAP_PCET_PEM_STATUS, "pem_status"},
+ {PMT_CAP_PCET_CORE_C_STATE, "core_c_state"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_pcet[] = {
+ pmt_cap_common,
+ pmt_cap_pcet,
+ NULL
+};
+
+struct pmt_cap pmt_cap_rmid_perf[] = {
+ {PMT_CAP_RMID_CORES_PERF, "core_performance"},
+ {PMT_CAP_RMID_CACHE_PERF, "cache_performance"},
+ {PMT_CAP_RMID_PERF_QUAL, "performance_quality"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_rmid_perf[] = {
+ pmt_cap_common,
+ pmt_cap_rmid_perf,
+ NULL
+};
+
+struct pmt_cap pmt_cap_accel[] = {
+ {PMT_CAP_ACCEL_CPM_TELEM, "content_processing_module"},
+ {PMT_CAP_ACCEL_TIP_TELEM, "content_turbo_ip"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_accel[] = {
+ pmt_cap_common,
+ pmt_cap_accel,
+ NULL
+};
+
+struct pmt_cap pmt_cap_uncore[] = {
+ {PMT_CAP_UNCORE_IO_CA_TELEM, "io_ca"},
+ {PMT_CAP_UNCORE_RMID_TELEM, "rmid"},
+ {PMT_CAP_UNCORE_D2D_ULA_TELEM, "d2d_ula"},
+ {PMT_CAP_UNCORE_PKGC_TELEM, "package_c"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_uncore[] = {
+ pmt_cap_common,
+ pmt_cap_uncore,
+ NULL
+};
+
+struct pmt_cap pmt_cap_crashlog[] = {
+ {PMT_CAP_CRASHLOG_MAN_TRIG, "manual_trigger"},
+ {PMT_CAP_CRASHLOG_CORE, "core"},
+ {PMT_CAP_CRASHLOG_UNCORE, "uncore"},
+ {PMT_CAP_CRASHLOG_TOR, "tor"},
+ {PMT_CAP_CRASHLOG_S3M, "s3m"},
+ {PMT_CAP_CRASHLOG_PERSISTENCY, "persistency"},
+ {PMT_CAP_CRASHLOG_CLIP_GPIO, "crashlog_in_progress"},
+ {PMT_CAP_CRASHLOG_PRE_RESET, "pre_reset_extraction"},
+ {PMT_CAP_CRASHLOG_POST_RESET, "post_reset_extraction"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_crashlog[] = {
+ pmt_cap_common,
+ pmt_cap_crashlog,
+ NULL
+};
+
+struct pmt_cap pmt_cap_pete[] = {
+ {PMT_CAP_PETE_MAN_TRIG, "manual_trigger"},
+ {PMT_CAP_PETE_ENCRYPTION, "encryption"},
+ {PMT_CAP_PETE_PERSISTENCY, "persistency"},
+ {PMT_CAP_PETE_REQ_TOKENS, "required_tokens"},
+ {PMT_CAP_PETE_PROD_ENABLED, "production_enabled"},
+ {PMT_CAP_PETE_DEBUG_ENABLED, "debug_enabled"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_pete[] = {
+ pmt_cap_common,
+ pmt_cap_pete,
+ NULL
+};
+
+struct pmt_cap pmt_cap_tpmi[] = {
+ {PMT_CAP_TPMI_MAILBOX, "mailbox"},
+ {PMT_CAP_TPMI_LOCK, "bios_lock"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_tpmi[] = {
+ pmt_cap_common,
+ pmt_cap_tpmi,
+ NULL
+};
+
+struct pmt_cap pmt_cap_tracing[] = {
+ {PMT_CAP_TRACE_SRAR, "srar_errors"},
+ {PMT_CAP_TRACE_CORRECTABLE, "correctable_errors"},
+ {PMT_CAP_TRACE_MCTP, "mctp"},
+ {PMT_CAP_TRACE_MRT, "memory_resiliency"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_tracing[] = {
+ pmt_cap_common,
+ pmt_cap_tracing,
+ NULL
+};
+
+struct pmt_cap pmt_cap_rmid_energy[] = {
+ {PMT_CAP_RMID_ENERGY, "energy"},
+ {PMT_CAP_RMID_ACTIVITY, "activity"},
+ {PMT_CAP_RMID_ENERGY_QUAL, "energy_quality"},
+ {}
+};
+
+struct pmt_cap *pmt_caps_rmid_energy[] = {
+ pmt_cap_common,
+ pmt_cap_rmid_energy,
+ NULL
+};
diff --git a/drivers/platform/x86/intel/pmt/telemetry.c b/drivers/platform/x86/intel/pmt/telemetry.c
index ac3a9bdf5601..bdc7c24a3678 100644
--- a/drivers/platform/x86/intel/pmt/telemetry.c
+++ b/drivers/platform/x86/intel/pmt/telemetry.c
@@ -9,13 +9,21 @@
*/
#include <linux/auxiliary_bus.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/intel_pmt_features.h>
#include <linux/intel_vsec.h>
#include <linux/kernel.h>
+#include <linux/kref.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/slab.h>
+#include <linux/types.h>
#include <linux/uaccess.h>
-#include <linux/overflow.h>
+#include <linux/xarray.h>
#include "class.h"
@@ -99,12 +107,12 @@ static int pmt_telem_add_endpoint(struct intel_vsec_device *ivdev,
struct telem_endpoint *ep;
/* Endpoint lifetimes are managed by kref, not devres */
- entry->ep = kzalloc(sizeof(*(entry->ep)), GFP_KERNEL);
+ entry->ep = kzalloc_obj(*(entry->ep));
if (!entry->ep)
return -ENOMEM;
ep = entry->ep;
- ep->pcidev = ivdev->pcidev;
+ ep->dev = ivdev->dev;
ep->header.access_type = entry->header.access_type;
ep->header.guid = entry->header.guid;
ep->header.base_offset = entry->header.base_offset;
@@ -196,7 +204,7 @@ int pmt_telem_get_endpoint_info(int devid, struct telem_endpoint_info *info)
goto unlock;
}
- info->pdev = entry->ep->pcidev;
+ info->dev = entry->ep->dev;
info->header = entry->ep->header;
unlock:
@@ -206,6 +214,88 @@ unlock:
}
EXPORT_SYMBOL_NS_GPL(pmt_telem_get_endpoint_info, "INTEL_PMT_TELEMETRY");
+static int pmt_copy_region(struct telemetry_region *region,
+ struct intel_pmt_entry *entry)
+{
+
+ struct pci_dev *pdev = to_pci_dev(entry->ep->dev);
+ struct oobmsm_plat_info *plat_info;
+
+ plat_info = intel_vsec_get_mapping(pdev);
+ if (IS_ERR(plat_info))
+ return PTR_ERR(plat_info);
+
+ region->plat_info = *plat_info;
+ region->guid = entry->guid;
+ region->addr = entry->ep->base;
+ region->size = entry->size;
+ region->num_rmids = entry->num_rmids;
+
+ return 0;
+}
+
+static void pmt_feature_group_release(struct kref *kref)
+{
+ struct pmt_feature_group *feature_group;
+
+ feature_group = container_of(kref, struct pmt_feature_group, kref);
+ kfree(feature_group);
+}
+
+struct pmt_feature_group *intel_pmt_get_regions_by_feature(enum pmt_feature_id id)
+{
+ struct pmt_feature_group *feature_group __free(kfree) = NULL;
+ struct telemetry_region *region;
+ struct intel_pmt_entry *entry;
+ unsigned long idx;
+ int count = 0;
+ size_t size;
+
+ if (!pmt_feature_id_is_valid(id))
+ return ERR_PTR(-EINVAL);
+
+ guard(mutex)(&ep_lock);
+ xa_for_each(&telem_array, idx, entry) {
+ if (entry->feature_flags & BIT(id))
+ count++;
+ }
+
+ if (!count)
+ return ERR_PTR(-ENOENT);
+
+ size = struct_size(feature_group, regions, count);
+ feature_group = kzalloc(size, GFP_KERNEL);
+ if (!feature_group)
+ return ERR_PTR(-ENOMEM);
+
+ feature_group->count = count;
+
+ region = feature_group->regions;
+ xa_for_each(&telem_array, idx, entry) {
+ int ret;
+
+ if (!(entry->feature_flags & BIT(id)))
+ continue;
+
+ ret = pmt_copy_region(region, entry);
+ if (ret)
+ return ERR_PTR(ret);
+
+ region++;
+ }
+
+ kref_init(&feature_group->kref);
+
+ return no_free_ptr(feature_group);
+}
+EXPORT_SYMBOL(intel_pmt_get_regions_by_feature);
+
+void intel_pmt_put_feature_group(struct pmt_feature_group *feature_group)
+{
+ kref_put(&feature_group->kref, pmt_feature_group_release);
+}
+EXPORT_SYMBOL(intel_pmt_put_feature_group);
+
int pmt_telem_read(struct telem_endpoint *ep, u32 id, u64 *data, u32 count)
{
u32 offset, size;
@@ -219,7 +309,7 @@ int pmt_telem_read(struct telem_endpoint *ep, u32 id, u64 *data, u32 count)
if (offset + NUM_BYTES_QWORD(count) > size)
return -EINVAL;
- pmt_telem_read_mmio(ep->pcidev, ep->cb, ep->header.guid, data, ep->base, offset,
+ pmt_telem_read_mmio(ep->dev, ep->cb, ep->header.guid, data, ep->base, offset,
NUM_BYTES_QWORD(count));
return ep->present ? 0 : -EPIPE;
@@ -246,7 +336,7 @@ int pmt_telem_read32(struct telem_endpoint *ep, u32 id, u32 *data, u32 count)
EXPORT_SYMBOL_NS_GPL(pmt_telem_read32, "INTEL_PMT_TELEMETRY");
struct telem_endpoint *
-pmt_telem_find_and_register_endpoint(struct pci_dev *pcidev, u32 guid, u16 pos)
+pmt_telem_find_and_register_endpoint(struct device *dev, u32 guid, u16 pos)
{
int devid = 0;
int inst = 0;
@@ -259,7 +349,7 @@ pmt_telem_find_and_register_endpoint(struct pci_dev *pcidev, u32 guid, u16 pos)
if (err)
return ERR_PTR(err);
- if (ep_info.header.guid == guid && ep_info.pdev == pcidev) {
+ if (ep_info.header.guid == guid && ep_info.dev == dev) {
if (inst == pos)
return pmt_telem_register_endpoint(devid);
++inst;
@@ -311,6 +401,8 @@ static int pmt_telem_probe(struct auxiliary_device *auxdev, const struct auxilia
continue;
priv->num_entries++;
+
+ intel_pmt_get_features(entry);
}
return 0;
@@ -348,3 +440,4 @@ MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>");
MODULE_DESCRIPTION("Intel PMT Telemetry driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("INTEL_PMT");
+MODULE_IMPORT_NS("INTEL_VSEC");
diff --git a/drivers/platform/x86/intel/pmt/telemetry.h b/drivers/platform/x86/intel/pmt/telemetry.h
index d45af5512b4e..0f88c5e7d90e 100644
--- a/drivers/platform/x86/intel/pmt/telemetry.h
+++ b/drivers/platform/x86/intel/pmt/telemetry.h
@@ -6,8 +6,8 @@
#define PMT_TELEM_TELEMETRY 0
#define PMT_TELEM_CRASHLOG 1
+struct device;
struct telem_endpoint;
-struct pci_dev;
struct telem_header {
u8 access_type;
@@ -17,7 +17,7 @@ struct telem_header {
};
struct telem_endpoint_info {
- struct pci_dev *pdev;
+ struct device *dev;
struct telem_header header;
};
@@ -71,8 +71,8 @@ int pmt_telem_get_endpoint_info(int devid, struct telem_endpoint_info *info);
/**
* pmt_telem_find_and_register_endpoint() - Get a telemetry endpoint from
- * pci_dev device, guid and pos
- * @pdev: PCI device inside the Intel vsec
+ * device, guid and pos
+ * @dev: device inside the Intel vsec
* @guid: GUID of the telemetry space
* @pos: Instance of the guid
*
@@ -80,8 +80,8 @@ int pmt_telem_get_endpoint_info(int devid, struct telem_endpoint_info *info);
* * endpoint - On success returns pointer to the telemetry endpoint
* * -ENXIO - telemetry endpoint not found
*/
-struct telem_endpoint *pmt_telem_find_and_register_endpoint(struct pci_dev *pcidev,
- u32 guid, u16 pos);
+struct telem_endpoint *
+pmt_telem_find_and_register_endpoint(struct device *dev, u32 guid, u16 pos);
/**
* pmt_telem_read() - Read qwords from counter sram using sample id
diff --git a/drivers/platform/x86/intel/punit_ipc.c b/drivers/platform/x86/intel/punit_ipc.c
index bafac8aa2baf..14513010daad 100644
--- a/drivers/platform/x86/intel/punit_ipc.c
+++ b/drivers/platform/x86/intel/punit_ipc.c
@@ -250,7 +250,7 @@ static int intel_punit_ipc_probe(struct platform_device *pdev)
} else {
ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc,
IRQF_NO_SUSPEND, "intel_punit_ipc",
- &punit_ipcdev);
+ punit_ipcdev);
if (ret) {
dev_err(&pdev->dev, "Failed to request irq: %d\n", irq);
return ret;
diff --git a/drivers/platform/x86/intel/rst.c b/drivers/platform/x86/intel/rst.c
index f3a60e14d4c1..bb19f0d89305 100644
--- a/drivers/platform/x86/intel/rst.c
+++ b/drivers/platform/x86/intel/rst.c
@@ -5,6 +5,7 @@
#include <linux/acpi.h>
#include <linux/module.h>
+#include <linux/platform_device.h>
#include <linux/slab.h>
MODULE_DESCRIPTION("Intel Rapid Start Technology Driver");
@@ -99,10 +100,15 @@ static struct device_attribute irst_timeout_attr = {
.store = irst_store_wakeup_time
};
-static int irst_add(struct acpi_device *acpi)
+static int irst_probe(struct platform_device *pdev)
{
+ struct acpi_device *acpi;
int error;
+ acpi = ACPI_COMPANION(&pdev->dev);
+ if (!acpi)
+ return -ENODEV;
+
error = device_create_file(&acpi->dev, &irst_timeout_attr);
if (unlikely(error))
return error;
@@ -114,8 +120,10 @@ static int irst_add(struct acpi_device *acpi)
return error;
}
-static void irst_remove(struct acpi_device *acpi)
+static void irst_remove(struct platform_device *pdev)
{
+ struct acpi_device *acpi = ACPI_COMPANION(&pdev->dev);
+
device_remove_file(&acpi->dev, &irst_wakeup_attr);
device_remove_file(&acpi->dev, &irst_timeout_attr);
}
@@ -125,16 +133,15 @@ static const struct acpi_device_id irst_ids[] = {
{"", 0}
};
-static struct acpi_driver irst_driver = {
- .name = "intel_rapid_start",
- .class = "intel_rapid_start",
- .ids = irst_ids,
- .ops = {
- .add = irst_add,
- .remove = irst_remove,
+static struct platform_driver irst_driver = {
+ .probe = irst_probe,
+ .remove = irst_remove,
+ .driver = {
+ .name = "intel_rapid_start",
+ .acpi_match_table = irst_ids,
},
};
-module_acpi_driver(irst_driver);
+module_platform_driver(irst_driver);
MODULE_DEVICE_TABLE(acpi, irst_ids);
diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c
index 30d1c2caf984..d7e37d4ace23 100644
--- a/drivers/platform/x86/intel/sdsi.c
+++ b/drivers/platform/x86/intel/sdsi.c
@@ -576,7 +576,7 @@ static struct attribute *sdsi_attrs[] = {
static const struct attribute_group sdsi_group = {
.attrs = sdsi_attrs,
- .bin_attrs_new = sdsi_bin_attrs,
+ .bin_attrs = sdsi_bin_attrs,
.is_bin_visible = sdsi_battr_is_visible,
};
__ATTRIBUTE_GROUPS(sdsi);
@@ -599,13 +599,14 @@ static int sdsi_get_layout(struct sdsi_priv *priv, struct disc_table *table)
return 0;
}
-static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent,
+static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct device *dev,
struct disc_table *disc_table, struct resource *disc_res)
{
u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info);
u32 size = FIELD_GET(DT_SIZE, disc_table->access_info);
u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset);
u32 offset = DT_OFFSET(disc_table->offset);
+ struct pci_dev *parent = to_pci_dev(dev);
struct resource res = {};
/* Starting location of SDSi MMIO region based on access type */
@@ -681,7 +682,7 @@ static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_de
return ret;
/* Map the SDSi mailbox registers */
- ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res);
+ ret = sdsi_map_mbox_registers(priv, intel_cap_dev->dev, &disc_table, disc_res);
if (ret)
return ret;
diff --git a/drivers/platform/x86/intel/smartconnect.c b/drivers/platform/x86/intel/smartconnect.c
index 31019a1a6d5e..71e91ac60e5d 100644
--- a/drivers/platform/x86/intel/smartconnect.c
+++ b/drivers/platform/x86/intel/smartconnect.c
@@ -5,22 +5,28 @@
#include <linux/acpi.h>
#include <linux/module.h>
+#include <linux/platform_device.h>
MODULE_DESCRIPTION("Intel Smart Connect disabling driver");
MODULE_LICENSE("GPL");
-static int smartconnect_acpi_init(struct acpi_device *acpi)
+static int smartconnect_acpi_probe(struct platform_device *pdev)
{
unsigned long long value;
+ acpi_handle handle;
acpi_status status;
- status = acpi_evaluate_integer(acpi->handle, "GAOS", NULL, &value);
+ handle = ACPI_HANDLE(&pdev->dev);
+ if (!handle)
+ return -ENODEV;
+
+ status = acpi_evaluate_integer(handle, "GAOS", NULL, &value);
if (ACPI_FAILURE(status))
return -EINVAL;
if (value & 0x1) {
- dev_info(&acpi->dev, "Disabling Intel Smart Connect\n");
- status = acpi_execute_simple_method(acpi->handle, "SAOS", 0);
+ dev_info(&pdev->dev, "Disabling Intel Smart Connect\n");
+ status = acpi_execute_simple_method(handle, "SAOS", 0);
}
return 0;
@@ -32,13 +38,12 @@ static const struct acpi_device_id smartconnect_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, smartconnect_ids);
-static struct acpi_driver smartconnect_driver = {
- .name = "intel_smart_connect",
- .class = "intel_smart_connect",
- .ids = smartconnect_ids,
- .ops = {
- .add = smartconnect_acpi_init,
+static struct platform_driver smartconnect_driver = {
+ .probe = smartconnect_acpi_probe,
+ .driver = {
+ .name = "intel_smart_connect",
+ .acpi_match_table = smartconnect_ids,
},
};
-module_acpi_driver(smartconnect_driver);
+module_platform_driver(smartconnect_driver);
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
index dbcd3087aaa4..1c48bf6d5457 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
@@ -21,6 +21,7 @@
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
+#include <asm/msr.h>
#include "isst_if_common.h"
@@ -84,11 +85,11 @@ static DECLARE_HASHTABLE(isst_hash, 8);
static DEFINE_MUTEX(isst_hash_lock);
static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param,
- u32 data)
+ u64 data)
{
struct isst_cmd *sst_cmd;
- sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL);
+ sst_cmd = kmalloc_obj(*sst_cmd);
if (!sst_cmd)
return -ENOMEM;
@@ -191,32 +192,13 @@ void isst_resume_common(void)
if (cb->registered)
isst_mbox_resume_command(cb, sst_cmd);
} else {
- wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd,
+ wrmsrq_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd,
sst_cmd->data);
}
}
}
EXPORT_SYMBOL_GPL(isst_resume_common);
-static void isst_restore_msr_local(int cpu)
-{
- struct isst_cmd *sst_cmd;
- int i;
-
- mutex_lock(&isst_hash_lock);
- for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
- if (!punit_msr_white_list[i])
- break;
-
- hash_for_each_possible(isst_hash, sst_cmd, hnode,
- punit_msr_white_list[i]) {
- if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu)
- wrmsrl_safe(sst_cmd->cmd, sst_cmd->data);
- }
- }
- mutex_unlock(&isst_hash_lock);
-}
-
/**
* isst_if_mbox_cmd_invalid() - Check invalid mailbox commands
* @cmd: Pointer to the command structure to verify.
@@ -406,7 +388,7 @@ static int isst_if_cpu_online(unsigned int cpu)
isst_cpu_info[cpu].numa_node = cpu_to_node(cpu);
- ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data);
+ ret = rdmsrq_safe(MSR_CPU_BUS_NUMBER, &data);
if (ret) {
/* This is not a fatal error on MSR mailbox only I/F */
isst_cpu_info[cpu].bus_info[0] = -1;
@@ -420,12 +402,12 @@ static int isst_if_cpu_online(unsigned int cpu)
if (isst_hpm_support) {
- ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
+ ret = rdmsrq_safe(MSR_PM_LOGICAL_ID, &data);
if (!ret)
goto set_punit_id;
}
- ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data);
+ ret = rdmsrq_safe(MSR_THREAD_ID_INFO, &data);
if (ret) {
isst_cpu_info[cpu].punit_cpu_id = -1;
return ret;
@@ -434,8 +416,6 @@ static int isst_if_cpu_online(unsigned int cpu)
set_punit_id:
isst_cpu_info[cpu].punit_cpu_id = data;
- isst_restore_msr_local(cpu);
-
return 0;
}
@@ -445,15 +425,11 @@ static int isst_if_cpu_info_init(void)
{
int ret;
- isst_cpu_info = kcalloc(num_possible_cpus(),
- sizeof(*isst_cpu_info),
- GFP_KERNEL);
+ isst_cpu_info = kzalloc_objs(*isst_cpu_info, num_possible_cpus());
if (!isst_cpu_info)
return -ENOMEM;
- isst_pkg_info = kcalloc(topology_max_packages(),
- sizeof(*isst_pkg_info),
- GFP_KERNEL);
+ isst_pkg_info = kzalloc_objs(*isst_pkg_info, topology_max_packages());
if (!isst_pkg_info) {
kfree(isst_cpu_info);
return -ENOMEM;
@@ -524,7 +500,7 @@ static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume)
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
- ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu,
+ ret = wrmsrq_safe_on_cpu(msr_cmd->logical_cpu,
msr_cmd->msr,
msr_cmd->data);
*write_only = 1;
@@ -535,7 +511,7 @@ static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume)
} else {
u64 data;
- ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu,
+ ret = rdmsrq_safe_on_cpu(msr_cmd->logical_cpu,
msr_cmd->msr, &data);
if (!ret) {
msr_cmd->data = data;
@@ -810,7 +786,7 @@ static const struct x86_cpu_id isst_cpu_ids[] = {
X86_MATCH_VFM(INTEL_GRANITERAPIDS_X, SST_HPM_SUPPORTED),
X86_MATCH_VFM(INTEL_ICELAKE_D, 0),
X86_MATCH_VFM(INTEL_ICELAKE_X, 0),
- X86_MATCH_VFM(INTEL_PANTHERCOVE_X, SST_HPM_SUPPORTED),
+ X86_MATCH_VFM(INTEL_DIAMONDRAPIDS_X, SST_HPM_SUPPORTED),
X86_MATCH_VFM(INTEL_SAPPHIRERAPIDS_X, 0),
X86_MATCH_VFM(INTEL_SKYLAKE_X, SST_MBOX_SUPPORTED),
{}
@@ -831,8 +807,8 @@ static int __init isst_if_common_init(void)
u64 data;
/* Can fail only on some Skylake-X generations */
- if (rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data) ||
- rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data))
+ if (rdmsrq_safe(MSR_OS_MAILBOX_INTERFACE, &data) ||
+ rdmsrq_safe(MSR_OS_MAILBOX_DATA, &data))
return -ENODEV;
}
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
index c4b7af00352b..22745b217c6f 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
@@ -18,6 +18,7 @@
#include <uapi/linux/isst_if.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
+#include <asm/msr.h>
#include "isst_if_common.h"
@@ -39,7 +40,7 @@ static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
/* Poll for rb bit == 0 */
retries = OS_MAILBOX_RETRY_COUNT;
do {
- rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+ rdmsrq(MSR_OS_MAILBOX_INTERFACE, data);
if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
ret = -EBUSY;
continue;
@@ -52,19 +53,19 @@ static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
return ret;
/* Write DATA register */
- wrmsrl(MSR_OS_MAILBOX_DATA, command_data);
+ wrmsrq(MSR_OS_MAILBOX_DATA, command_data);
/* Write command register */
data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) |
(parameter & GENMASK_ULL(13, 0)) << 16 |
(sub_command << 8) |
command;
- wrmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+ wrmsrq(MSR_OS_MAILBOX_INTERFACE, data);
/* Poll for rb bit == 0 */
retries = OS_MAILBOX_RETRY_COUNT;
do {
- rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+ rdmsrq(MSR_OS_MAILBOX_INTERFACE, data);
if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
ret = -EBUSY;
continue;
@@ -74,7 +75,7 @@ static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
return -ENXIO;
if (response_data) {
- rdmsrl(MSR_OS_MAILBOX_DATA, data);
+ rdmsrq(MSR_OS_MAILBOX_DATA, data);
*response_data = data;
}
ret = 0;
@@ -176,11 +177,11 @@ static int __init isst_if_mbox_init(void)
return -ENODEV;
/* Check presence of mailbox MSRs */
- ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data);
+ ret = rdmsrq_safe(MSR_OS_MAILBOX_INTERFACE, &data);
if (ret)
return ret;
- ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data);
+ ret = rdmsrq_safe(MSR_OS_MAILBOX_DATA, &data);
if (ret)
return ret;
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c
index 3f4343147dad..950ede5eab76 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c
@@ -108,11 +108,11 @@ static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
ret = pci_read_config_dword(pdev, 0xD0, &mmio_base);
if (ret)
- return ret;
+ return pcibios_err_to_errno(ret);
ret = pci_read_config_dword(pdev, 0xFC, &pcu_base);
if (ret)
- return ret;
+ return pcibios_err_to_errno(ret);
pcu_base &= GENMASK(10, 0);
base_addr = (u64)mmio_base << 23 | (u64) pcu_base << 12;
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
index 9978cdd19851..b804cb753f94 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
@@ -22,11 +22,13 @@
#include <linux/auxiliary_bus.h>
#include <linux/delay.h>
#include <linux/intel_tpmi.h>
+#include <linux/intel_vsec.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/minmax.h>
#include <linux/module.h>
+#include <asm/msr.h>
#include <uapi/linux/isst_if.h>
#include "isst_tpmi_core.h"
@@ -34,7 +36,7 @@
/* Supported SST hardware version by this driver */
#define ISST_MAJOR_VERSION 0
-#define ISST_MINOR_VERSION 1
+#define ISST_MINOR_VERSION 3
/*
* Used to indicate if value read from MMIO needs to get multiplied
@@ -380,7 +382,7 @@ static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domai
return -ENODEV;
}
- if (TPMI_MINOR_VERSION(pd_info->sst_header.interface_version) != ISST_MINOR_VERSION)
+ if (TPMI_MINOR_VERSION(pd_info->sst_header.interface_version) > ISST_MINOR_VERSION)
dev_info(dev, "SST: Ignore: Unsupported minor version:%lx\n",
TPMI_MINOR_VERSION(pd_info->sst_header.interface_version));
@@ -556,7 +558,10 @@ static bool disable_dynamic_sst_features(void)
{
u64 value;
- rdmsrl(MSR_PM_ENABLE, value);
+ if (!static_cpu_has(X86_FEATURE_HWP))
+ return true;
+
+ rdmsrq(MSR_PM_ENABLE, value);
return !(value & 0x1);
}
@@ -610,6 +615,9 @@ static long isst_if_core_power_state(void __user *argp)
return -EINVAL;
if (core_power.get_set) {
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
_write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET,
SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
_write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET,
@@ -654,7 +662,7 @@ static long isst_if_clos_param(void __user *argp)
return -EINVAL;
if (clos_param.get_set) {
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
_write_cp_info("clos.min_freq", clos_param.min_freq_mhz,
@@ -746,7 +754,8 @@ static long isst_if_clos_assoc(void __user *argp)
power_domain_info = &sst_inst->power_domain_info[part][punit_id];
- if (assoc_cmds.get_set && power_domain_info->write_blocked)
+ if (assoc_cmds.get_set && (power_domain_info->write_blocked ||
+ !capable(CAP_SYS_ADMIN)))
return -EPERM;
offset = SST_CLOS_ASSOC_0_OFFSET +
@@ -863,7 +872,7 @@ static int isst_if_get_perf_level(void __user *argp)
_read_pp_info("current_level", perf_level.current_level, SST_PP_STATUS_OFFSET,
SST_PP_LEVEL_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE)
_read_pp_info("locked", perf_level.locked, SST_PP_STATUS_OFFSET,
- SST_PP_LOCK_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE)
+ SST_PP_LOCK_START, SST_PP_LOCK_WIDTH, SST_MUL_FACTOR_NONE)
_read_pp_info("feature_state", perf_level.feature_state, SST_PP_STATUS_OFFSET,
SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE)
perf_level.enabled = !!(power_domain_info->sst_header.cap_mask & BIT(1));
@@ -923,7 +932,7 @@ static int isst_if_set_perf_level(void __user *argp)
if (!power_domain_info)
return -EINVAL;
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
if (!(power_domain_info->pp_header.allowed_level_mask & BIT(perf_level.level)))
@@ -983,7 +992,7 @@ static int isst_if_set_perf_feature(void __user *argp)
if (!power_domain_info)
return -EINVAL;
- if (power_domain_info->write_blocked)
+ if (power_domain_info->write_blocked || !capable(CAP_SYS_ADMIN))
return -EPERM;
_write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET,
@@ -1016,6 +1025,7 @@ static int isst_if_set_perf_feature(void __user *argp)
#define SST_PP_INFO_10_OFFSET 80
#define SST_PP_INFO_11_OFFSET 88
+#define SST_PP_INFO_12_OFFSET 96
#define SST_PP_P1_SSE_START 0
#define SST_PP_P1_SSE_WIDTH 8
@@ -1068,6 +1078,15 @@ static int isst_if_set_perf_feature(void __user *argp)
#define SST_PP_CORE_RATIO_PM_FABRIC_START 48
#define SST_PP_CORE_RATIO_PM_FABRIC_WIDTH 8
+#define SST_PP_CORE_RATIO_P0_FABRIC_1_START 0
+#define SST_PP_CORE_RATIO_P0_FABRIC_1_WIDTH 8
+
+#define SST_PP_CORE_RATIO_P1_FABRIC_1_START 8
+#define SST_PP_CORE_RATIO_P1_FABRIC_1_WIDTH 8
+
+#define SST_PP_CORE_RATIO_PM_FABRIC_1_START 16
+#define SST_PP_CORE_RATIO_PM_FABRIC_1_WIDTH 8
+
static int isst_if_get_perf_level_info(void __user *argp)
{
struct isst_perf_level_data_info perf_level;
@@ -1167,6 +1186,59 @@ static int isst_if_get_perf_level_info(void __user *argp)
return 0;
}
+static int isst_if_get_perf_level_fabric_info(void __user *argp)
+{
+ struct isst_perf_level_fabric_info perf_level_fabric;
+ struct tpmi_per_power_domain_info *power_domain_info;
+ int start = SST_PP_CORE_RATIO_P0_FABRIC_START;
+ int width = SST_PP_CORE_RATIO_P0_FABRIC_WIDTH;
+ int offset = SST_PP_INFO_11_OFFSET;
+ int i;
+
+ if (copy_from_user(&perf_level_fabric, argp, sizeof(perf_level_fabric)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(perf_level_fabric.socket_id,
+ perf_level_fabric.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ if (perf_level_fabric.level > power_domain_info->max_level)
+ return -EINVAL;
+
+ if (power_domain_info->pp_header.feature_rev < 2)
+ return -EINVAL;
+
+ if (!(power_domain_info->pp_header.level_en_mask & BIT(perf_level_fabric.level)))
+ return -EINVAL;
+
+ /* For revision 2, maximum number of fabrics is 2 */
+ perf_level_fabric.max_fabrics = 2;
+
+ for (i = 0; i < perf_level_fabric.max_fabrics; i++) {
+ _read_pp_level_info("p0_fabric_freq_mhz", perf_level_fabric.p0_fabric_freq_mhz[i],
+ perf_level_fabric.level, offset, start, width,
+ SST_MUL_FACTOR_FREQ)
+ start += width;
+
+ _read_pp_level_info("p1_fabric_freq_mhz", perf_level_fabric.p1_fabric_freq_mhz[i],
+ perf_level_fabric.level, offset, start, width,
+ SST_MUL_FACTOR_FREQ)
+ start += width;
+
+ _read_pp_level_info("pm_fabric_freq_mhz", perf_level_fabric.pm_fabric_freq_mhz[i],
+ perf_level_fabric.level, offset, start, width,
+ SST_MUL_FACTOR_FREQ)
+ offset = SST_PP_INFO_12_OFFSET;
+ start = SST_PP_CORE_RATIO_P0_FABRIC_1_START;
+ }
+
+ if (copy_to_user(argp, &perf_level_fabric, sizeof(perf_level_fabric)))
+ return -EFAULT;
+
+ return 0;
+}
+
#define SST_PP_FUSED_CORE_COUNT_START 0
#define SST_PP_FUSED_CORE_COUNT_WIDTH 8
@@ -1328,9 +1400,14 @@ static int isst_if_get_tpmi_instance_count(void __user *argp)
#define SST_TF_INFO_0_OFFSET 0
#define SST_TF_INFO_1_OFFSET 8
#define SST_TF_INFO_2_OFFSET 16
+#define SST_TF_INFO_8_OFFSET 64
+#define SST_TF_INFO_8_BUCKETS 3
#define SST_TF_MAX_LP_CLIP_RATIOS TRL_MAX_LEVELS
+#define SST_TF_FEATURE_REV_START 4
+#define SST_TF_FEATURE_REV_WIDTH 8
+
#define SST_TF_LP_CLIP_RATIO_0_START 16
#define SST_TF_LP_CLIP_RATIO_0_WIDTH 8
@@ -1340,10 +1417,14 @@ static int isst_if_get_tpmi_instance_count(void __user *argp)
#define SST_TF_NUM_CORE_0_START 0
#define SST_TF_NUM_CORE_0_WIDTH 8
+#define SST_TF_NUM_MOD_0_START 0
+#define SST_TF_NUM_MOD_0_WIDTH 16
+
static int isst_if_get_turbo_freq_info(void __user *argp)
{
static struct isst_turbo_freq_info turbo_freq;
struct tpmi_per_power_domain_info *power_domain_info;
+ u8 feature_rev;
int i, j;
if (copy_from_user(&turbo_freq, argp, sizeof(turbo_freq)))
@@ -1360,6 +1441,10 @@ static int isst_if_get_turbo_freq_info(void __user *argp)
turbo_freq.max_trl_levels = TRL_MAX_LEVELS;
turbo_freq.max_clip_freqs = SST_TF_MAX_LP_CLIP_RATIOS;
+ _read_tf_level_info("feature_rev", feature_rev, turbo_freq.level,
+ SST_TF_INFO_0_OFFSET, SST_TF_FEATURE_REV_START,
+ SST_TF_FEATURE_REV_WIDTH, SST_MUL_FACTOR_NONE);
+
for (i = 0; i < turbo_freq.max_clip_freqs; ++i)
_read_tf_level_info("lp_clip*", turbo_freq.lp_clip_freq_mhz[i],
turbo_freq.level, SST_TF_INFO_0_OFFSET,
@@ -1376,12 +1461,34 @@ static int isst_if_get_turbo_freq_info(void __user *argp)
SST_MUL_FACTOR_FREQ)
}
+ memset(turbo_freq.bucket_core_counts, 0, sizeof(turbo_freq.bucket_core_counts));
+
+ if (feature_rev >= 2) {
+ bool has_tf_info_8 = false;
+
+ for (i = 0; i < SST_TF_INFO_8_BUCKETS; ++i) {
+ _read_tf_level_info("bucket_*_mod_count", turbo_freq.bucket_core_counts[i],
+ turbo_freq.level, SST_TF_INFO_8_OFFSET,
+ SST_TF_NUM_MOD_0_WIDTH * i, SST_TF_NUM_MOD_0_WIDTH,
+ SST_MUL_FACTOR_NONE)
+
+ if (turbo_freq.bucket_core_counts[i])
+ has_tf_info_8 = true;
+ }
+
+ if (has_tf_info_8)
+ goto done_core_count;
+ }
+
for (i = 0; i < TRL_MAX_BUCKETS; ++i)
_read_tf_level_info("bucket_*_core_count", turbo_freq.bucket_core_counts[i],
turbo_freq.level, SST_TF_INFO_1_OFFSET,
SST_TF_NUM_CORE_0_WIDTH * i, SST_TF_NUM_CORE_0_WIDTH,
SST_MUL_FACTOR_NONE)
+
+done_core_count:
+
if (copy_to_user(argp, &turbo_freq, sizeof(turbo_freq)))
return -EFAULT;
@@ -1420,6 +1527,9 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
case ISST_IF_GET_PERF_LEVEL_INFO:
ret = isst_if_get_perf_level_info(argp);
break;
+ case ISST_IF_GET_PERF_LEVEL_FABRIC_INFO:
+ ret = isst_if_get_perf_level_fabric_info(argp);
+ break;
case ISST_IF_GET_PERF_LEVEL_CPU_MASK:
ret = isst_if_get_perf_level_mask(argp);
break;
@@ -1446,7 +1556,7 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev)
{
struct tpmi_per_power_domain_info *pd_info;
bool read_blocked = 0, write_blocked = 0;
- struct intel_tpmi_plat_info *plat_info;
+ struct oobmsm_plat_info *plat_info;
struct device *dev = &auxdev->dev;
struct tpmi_sst_struct *tpmi_sst;
u8 i, num_resources, io_die_cnt;
@@ -1498,7 +1608,7 @@ int tpmi_sst_dev_add(struct auxiliary_device *auxdev)
* devm_* allocation here as each partition is a
* different device, which can be unbound.
*/
- tpmi_sst = kzalloc(sizeof(*tpmi_sst), GFP_KERNEL);
+ tpmi_sst = kzalloc_obj(*tpmi_sst);
if (!tpmi_sst) {
ret = -ENOMEM;
goto unlock_exit;
@@ -1598,7 +1708,7 @@ EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_add, "INTEL_TPMI_SST");
void tpmi_sst_dev_remove(struct auxiliary_device *auxdev)
{
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
- struct intel_tpmi_plat_info *plat_info;
+ struct oobmsm_plat_info *plat_info;
plat_info = tpmi_get_platform_data(auxdev);
if (!plat_info)
@@ -1616,58 +1726,87 @@ void tpmi_sst_dev_remove(struct auxiliary_device *auxdev)
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, "INTEL_TPMI_SST");
+#define SST_PP_CAP_CP_ENABLE BIT(0)
+#define SST_PP_CAP_PP_ENABLE BIT(1)
+
void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev)
{
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
- struct tpmi_per_power_domain_info *power_domain_info;
- struct intel_tpmi_plat_info *plat_info;
+ struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
+ struct oobmsm_plat_info *plat_info;
void __iomem *cp_base;
+ int num_resources, i;
plat_info = tpmi_get_platform_data(auxdev);
if (!plat_info)
return;
power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
+ num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
- cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
- power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
+ for (i = 0; i < num_resources; i++) {
+ pd_info = &power_domain_info[i];
+ if (!pd_info || !pd_info->sst_base)
+ continue;
+
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
+ goto process_pp_suspend;
- memcpy_fromio(power_domain_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
- sizeof(power_domain_info->saved_clos_configs));
+ cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
+ pd_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
+ memcpy_fromio(pd_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
+ sizeof(pd_info->saved_clos_configs));
+ memcpy_fromio(pd_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
+ sizeof(pd_info->saved_clos_assocs));
- memcpy_fromio(power_domain_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
- sizeof(power_domain_info->saved_clos_assocs));
+process_pp_suspend:
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
+ continue;
- power_domain_info->saved_pp_control = readq(power_domain_info->sst_base +
- power_domain_info->sst_header.pp_offset +
- SST_PP_CONTROL_OFFSET);
+ pd_info->saved_pp_control = readq(pd_info->sst_base +
+ pd_info->sst_header.pp_offset +
+ SST_PP_CONTROL_OFFSET);
+ }
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, "INTEL_TPMI_SST");
void tpmi_sst_dev_resume(struct auxiliary_device *auxdev)
{
struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
- struct tpmi_per_power_domain_info *power_domain_info;
- struct intel_tpmi_plat_info *plat_info;
+ struct tpmi_per_power_domain_info *power_domain_info, *pd_info;
+ struct oobmsm_plat_info *plat_info;
void __iomem *cp_base;
+ int num_resources, i;
plat_info = tpmi_get_platform_data(auxdev);
if (!plat_info)
return;
power_domain_info = tpmi_sst->power_domain_info[plat_info->partition];
+ num_resources = tpmi_sst->number_of_power_domains[plat_info->partition];
+
+ for (i = 0; i < num_resources; i++) {
+ pd_info = &power_domain_info[i];
+ if (!pd_info || !pd_info->sst_base)
+ continue;
- cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
- writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_CP_ENABLE))
+ goto process_pp_resume;
- memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, power_domain_info->saved_clos_configs,
- sizeof(power_domain_info->saved_clos_configs));
+ cp_base = pd_info->sst_base + pd_info->sst_header.cp_offset;
+ writeq(pd_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
+ memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, pd_info->saved_clos_configs,
+ sizeof(pd_info->saved_clos_configs));
+ memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, pd_info->saved_clos_assocs,
+ sizeof(pd_info->saved_clos_assocs));
- memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, power_domain_info->saved_clos_assocs,
- sizeof(power_domain_info->saved_clos_assocs));
+process_pp_resume:
+ if (!(pd_info->sst_header.cap_mask & SST_PP_CAP_PP_ENABLE))
+ continue;
- writeq(power_domain_info->saved_pp_control, power_domain_info->sst_base +
- power_domain_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
+ writeq(pd_info->saved_pp_control, power_domain_info->sst_base +
+ pd_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
+ }
}
EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, "INTEL_TPMI_SST");
@@ -1685,9 +1824,8 @@ int tpmi_sst_init(void)
goto init_done;
}
- isst_common.sst_inst = kcalloc(topology_max_packages(),
- sizeof(*isst_common.sst_inst),
- GFP_KERNEL);
+ isst_common.sst_inst = kzalloc_objs(*isst_common.sst_inst,
+ topology_max_packages());
if (!isst_common.sst_inst) {
ret = -ENOMEM;
goto init_done;
diff --git a/drivers/platform/x86/intel/telemetry/core.c b/drivers/platform/x86/intel/telemetry/core.c
index e4be40f73eeb..f312864b8d07 100644
--- a/drivers/platform/x86/intel/telemetry/core.c
+++ b/drivers/platform/x86/intel/telemetry/core.c
@@ -21,33 +21,6 @@ struct telemetry_core_config {
static struct telemetry_core_config telm_core_conf;
-static int telemetry_def_update_events(struct telemetry_evtconfig pss_evtconfig,
- struct telemetry_evtconfig ioss_evtconfig)
-{
- return 0;
-}
-
-static int telemetry_def_set_sampling_period(u8 pss_period, u8 ioss_period)
-{
- return 0;
-}
-
-static int telemetry_def_get_sampling_period(u8 *pss_min_period,
- u8 *pss_max_period,
- u8 *ioss_min_period,
- u8 *ioss_max_period)
-{
- return 0;
-}
-
-static int telemetry_def_get_eventconfig(
- struct telemetry_evtconfig *pss_evtconfig,
- struct telemetry_evtconfig *ioss_evtconfig,
- int pss_len, int ioss_len)
-{
- return 0;
-}
-
static int telemetry_def_get_trace_verbosity(enum telemetry_unit telem_unit,
u32 *verbosity)
{
@@ -75,145 +48,14 @@ static int telemetry_def_read_eventlog(enum telemetry_unit telem_unit,
return 0;
}
-static int telemetry_def_add_events(u8 num_pss_evts, u8 num_ioss_evts,
- u32 *pss_evtmap, u32 *ioss_evtmap)
-{
- return 0;
-}
-
-static int telemetry_def_reset_events(void)
-{
- return 0;
-}
-
static const struct telemetry_core_ops telm_defpltops = {
- .set_sampling_period = telemetry_def_set_sampling_period,
- .get_sampling_period = telemetry_def_get_sampling_period,
.get_trace_verbosity = telemetry_def_get_trace_verbosity,
.set_trace_verbosity = telemetry_def_set_trace_verbosity,
.raw_read_eventlog = telemetry_def_raw_read_eventlog,
- .get_eventconfig = telemetry_def_get_eventconfig,
.read_eventlog = telemetry_def_read_eventlog,
- .update_events = telemetry_def_update_events,
- .reset_events = telemetry_def_reset_events,
- .add_events = telemetry_def_add_events,
};
/**
- * telemetry_update_events() - Update telemetry Configuration
- * @pss_evtconfig: PSS related config. No change if num_evts = 0.
- * @ioss_evtconfig: IOSS related config. No change if num_evts = 0.
- *
- * This API updates the IOSS & PSS Telemetry configuration. Old config
- * is overwritten. Call telemetry_reset_events when logging is over
- * All sample period values should be in the form of:
- * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
- *
- * Return: 0 success, < 0 for failure
- */
-int telemetry_update_events(struct telemetry_evtconfig pss_evtconfig,
- struct telemetry_evtconfig ioss_evtconfig)
-{
- return telm_core_conf.telem_ops->update_events(pss_evtconfig,
- ioss_evtconfig);
-}
-EXPORT_SYMBOL_GPL(telemetry_update_events);
-
-
-/**
- * telemetry_set_sampling_period() - Sets the IOSS & PSS sampling period
- * @pss_period: placeholder for PSS Period to be set.
- * Set to 0 if not required to be updated
- * @ioss_period: placeholder for IOSS Period to be set
- * Set to 0 if not required to be updated
- *
- * All values should be in the form of:
- * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
- *
- * Return: 0 success, < 0 for failure
- */
-int telemetry_set_sampling_period(u8 pss_period, u8 ioss_period)
-{
- return telm_core_conf.telem_ops->set_sampling_period(pss_period,
- ioss_period);
-}
-EXPORT_SYMBOL_GPL(telemetry_set_sampling_period);
-
-/**
- * telemetry_get_sampling_period() - Get IOSS & PSS min & max sampling period
- * @pss_min_period: placeholder for PSS Min Period supported
- * @pss_max_period: placeholder for PSS Max Period supported
- * @ioss_min_period: placeholder for IOSS Min Period supported
- * @ioss_max_period: placeholder for IOSS Max Period supported
- *
- * All values should be in the form of:
- * bits[6:3] -> value; bits [0:2]-> Exponent; Period = (Value *16^Exponent)
- *
- * Return: 0 success, < 0 for failure
- */
-int telemetry_get_sampling_period(u8 *pss_min_period, u8 *pss_max_period,
- u8 *ioss_min_period, u8 *ioss_max_period)
-{
- return telm_core_conf.telem_ops->get_sampling_period(pss_min_period,
- pss_max_period,
- ioss_min_period,
- ioss_max_period);
-}
-EXPORT_SYMBOL_GPL(telemetry_get_sampling_period);
-
-
-/**
- * telemetry_reset_events() - Restore the IOSS & PSS configuration to default
- *
- * Return: 0 success, < 0 for failure
- */
-int telemetry_reset_events(void)
-{
- return telm_core_conf.telem_ops->reset_events();
-}
-EXPORT_SYMBOL_GPL(telemetry_reset_events);
-
-/**
- * telemetry_get_eventconfig() - Returns the pss and ioss events enabled
- * @pss_evtconfig: Pointer to PSS related configuration.
- * @ioss_evtconfig: Pointer to IOSS related configuration.
- * @pss_len: Number of u32 elements allocated for pss_evtconfig array
- * @ioss_len: Number of u32 elements allocated for ioss_evtconfig array
- *
- * Return: 0 success, < 0 for failure
- */
-int telemetry_get_eventconfig(struct telemetry_evtconfig *pss_evtconfig,
- struct telemetry_evtconfig *ioss_evtconfig,
- int pss_len, int ioss_len)
-{
- return telm_core_conf.telem_ops->get_eventconfig(pss_evtconfig,
- ioss_evtconfig,
- pss_len, ioss_len);
-}
-EXPORT_SYMBOL_GPL(telemetry_get_eventconfig);
-
-/**
- * telemetry_add_events() - Add IOSS & PSS configuration to existing settings.
- * @num_pss_evts: Number of PSS Events (<29) in pss_evtmap. Can be 0.
- * @num_ioss_evts: Number of IOSS Events (<29) in ioss_evtmap. Can be 0.
- * @pss_evtmap: Array of PSS Event-IDs to Enable
- * @ioss_evtmap: Array of PSS Event-IDs to Enable
- *
- * Events are appended to Old Configuration. In case of total events > 28, it
- * returns error. Call telemetry_reset_events to reset after eventlog done
- *
- * Return: 0 success, < 0 for failure
- */
-int telemetry_add_events(u8 num_pss_evts, u8 num_ioss_evts,
- u32 *pss_evtmap, u32 *ioss_evtmap)
-{
- return telm_core_conf.telem_ops->add_events(num_pss_evts,
- num_ioss_evts, pss_evtmap,
- ioss_evtmap);
-}
-EXPORT_SYMBOL_GPL(telemetry_add_events);
-
-/**
* telemetry_read_events() - Fetches samples as specified by evtlog.telem_evt_id
* @telem_unit: Specify whether IOSS or PSS Read
* @evtlog: Array of telemetry_evtlog structs to fill data
@@ -231,25 +73,6 @@ int telemetry_read_events(enum telemetry_unit telem_unit,
EXPORT_SYMBOL_GPL(telemetry_read_events);
/**
- * telemetry_raw_read_events() - Fetch samples specified by evtlog.telem_evt_id
- * @telem_unit: Specify whether IOSS or PSS Read
- * @evtlog: Array of telemetry_evtlog structs to fill data
- * evtlog.telem_evt_id specifies the ids to read
- * @len: Length of array of evtlog
- *
- * The caller must take care of locking in this case.
- *
- * Return: number of eventlogs read for success, < 0 for failure
- */
-int telemetry_raw_read_events(enum telemetry_unit telem_unit,
- struct telemetry_evtlog *evtlog, int len)
-{
- return telm_core_conf.telem_ops->raw_read_eventlog(telem_unit, evtlog,
- len, 0);
-}
-EXPORT_SYMBOL_GPL(telemetry_raw_read_events);
-
-/**
* telemetry_read_eventlog() - Fetch the Telemetry log from PSS or IOSS
* @telem_unit: Specify whether IOSS or PSS Read
* @evtlog: Array of telemetry_evtlog structs to fill data
diff --git a/drivers/platform/x86/intel/telemetry/debugfs.c b/drivers/platform/x86/intel/telemetry/debugfs.c
index 70e5736c44c7..189c61ff7ff0 100644
--- a/drivers/platform/x86/intel/telemetry/debugfs.c
+++ b/drivers/platform/x86/intel/telemetry/debugfs.c
@@ -449,7 +449,7 @@ static int telem_pss_states_show(struct seq_file *s, void *unused)
for (index = 0; index < debugfs_conf->pss_ltr_evts; index++) {
seq_printf(s, "%-32s\t%u\n",
debugfs_conf->pss_ltr_data[index].name,
- pss_s0ix_wakeup[index]);
+ pss_ltr_blkd[index]);
}
seq_puts(s, "\n--------------------------------------\n");
@@ -459,7 +459,7 @@ static int telem_pss_states_show(struct seq_file *s, void *unused)
for (index = 0; index < debugfs_conf->pss_wakeup_evts; index++) {
seq_printf(s, "%-32s\t%u\n",
debugfs_conf->pss_wakeup[index].name,
- pss_ltr_blkd[index]);
+ pss_s0ix_wakeup[index]);
}
return 0;
diff --git a/drivers/platform/x86/intel/telemetry/pltdrv.c b/drivers/platform/x86/intel/telemetry/pltdrv.c
index 9a499efa1e4d..d9aa349f81e4 100644
--- a/drivers/platform/x86/intel/telemetry/pltdrv.c
+++ b/drivers/platform/x86/intel/telemetry/pltdrv.c
@@ -610,7 +610,7 @@ static int telemetry_setup(struct platform_device *pdev)
/* Get telemetry Info */
events = (read_buf & TELEM_INFO_SRAMEVTS_MASK) >>
TELEM_INFO_SRAMEVTS_SHIFT;
- event_regs = read_buf & TELEM_INFO_SRAMEVTS_MASK;
+ event_regs = read_buf & TELEM_INFO_NENABLES_MASK;
if ((events < TELEM_MAX_EVENTS_SRAM) ||
(event_regs < TELEM_MAX_EVENTS_SRAM)) {
dev_err(&pdev->dev, "PSS:Insufficient Space for SRAM Trace\n");
@@ -639,231 +639,6 @@ static int telemetry_setup(struct platform_device *pdev)
return 0;
}
-static int telemetry_plt_update_events(struct telemetry_evtconfig pss_evtconfig,
- struct telemetry_evtconfig ioss_evtconfig)
-{
- int ret;
-
- if ((pss_evtconfig.num_evts > 0) &&
- (TELEM_SAMPLE_PERIOD_INVALID(pss_evtconfig.period))) {
- pr_err("PSS Sampling Period Out of Range\n");
- return -EINVAL;
- }
-
- if ((ioss_evtconfig.num_evts > 0) &&
- (TELEM_SAMPLE_PERIOD_INVALID(ioss_evtconfig.period))) {
- pr_err("IOSS Sampling Period Out of Range\n");
- return -EINVAL;
- }
-
- ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
- TELEM_UPDATE);
- if (ret)
- pr_err("TELEMETRY Config Failed\n");
-
- return ret;
-}
-
-
-static int telemetry_plt_set_sampling_period(u8 pss_period, u8 ioss_period)
-{
- u32 telem_ctrl = 0;
- int ret = 0;
-
- mutex_lock(&(telm_conf->telem_lock));
- if (ioss_period) {
- struct intel_scu_ipc_dev *scu = telm_conf->scu;
-
- if (TELEM_SAMPLE_PERIOD_INVALID(ioss_period)) {
- pr_err("IOSS Sampling Period Out of Range\n");
- ret = -EINVAL;
- goto out;
- }
-
- /* Get telemetry EVENT CTL */
- ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
- IOSS_TELEM_EVENT_CTL_READ, NULL, 0,
- &telem_ctrl, sizeof(telem_ctrl));
- if (ret) {
- pr_err("IOSS TELEM_CTRL Read Failed\n");
- goto out;
- }
-
- /* Disable Telemetry */
- TELEM_DISABLE(telem_ctrl);
-
- ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
- IOSS_TELEM_EVENT_CTL_WRITE,
- &telem_ctrl, sizeof(telem_ctrl),
- NULL, 0);
- if (ret) {
- pr_err("IOSS TELEM_CTRL Event Disable Write Failed\n");
- goto out;
- }
-
- /* Enable Periodic Telemetry Events and enable SRAM trace */
- TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
- TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
- TELEM_ENABLE_PERIODIC(telem_ctrl);
- telem_ctrl |= ioss_period;
-
- ret = intel_scu_ipc_dev_command(scu, IOSS_TELEM,
- IOSS_TELEM_EVENT_CTL_WRITE,
- &telem_ctrl, sizeof(telem_ctrl),
- NULL, 0);
- if (ret) {
- pr_err("IOSS TELEM_CTRL Event Enable Write Failed\n");
- goto out;
- }
- telm_conf->ioss_config.curr_period = ioss_period;
- }
-
- if (pss_period) {
- if (TELEM_SAMPLE_PERIOD_INVALID(pss_period)) {
- pr_err("PSS Sampling Period Out of Range\n");
- ret = -EINVAL;
- goto out;
- }
-
- /* Get telemetry EVENT CTL */
- ret = intel_punit_ipc_command(
- IPC_PUNIT_BIOS_READ_TELE_EVENT_CTRL,
- 0, 0, NULL, &telem_ctrl);
- if (ret) {
- pr_err("PSS TELEM_CTRL Read Failed\n");
- goto out;
- }
-
- /* Disable Telemetry */
- TELEM_DISABLE(telem_ctrl);
- ret = intel_punit_ipc_command(
- IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
- 0, 0, &telem_ctrl, NULL);
- if (ret) {
- pr_err("PSS TELEM_CTRL Event Disable Write Failed\n");
- goto out;
- }
-
- /* Enable Periodic Telemetry Events and enable SRAM trace */
- TELEM_CLEAR_SAMPLE_PERIOD(telem_ctrl);
- TELEM_ENABLE_SRAM_EVT_TRACE(telem_ctrl);
- TELEM_ENABLE_PERIODIC(telem_ctrl);
- telem_ctrl |= pss_period;
-
- ret = intel_punit_ipc_command(
- IPC_PUNIT_BIOS_WRITE_TELE_EVENT_CTRL,
- 0, 0, &telem_ctrl, NULL);
- if (ret) {
- pr_err("PSS TELEM_CTRL Event Enable Write Failed\n");
- goto out;
- }
- telm_conf->pss_config.curr_period = pss_period;
- }
-
-out:
- mutex_unlock(&(telm_conf->telem_lock));
- return ret;
-}
-
-
-static int telemetry_plt_get_sampling_period(u8 *pss_min_period,
- u8 *pss_max_period,
- u8 *ioss_min_period,
- u8 *ioss_max_period)
-{
- *pss_min_period = telm_conf->pss_config.min_period;
- *pss_max_period = telm_conf->pss_config.max_period;
- *ioss_min_period = telm_conf->ioss_config.min_period;
- *ioss_max_period = telm_conf->ioss_config.max_period;
-
- return 0;
-}
-
-
-static int telemetry_plt_reset_events(void)
-{
- struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
- int ret;
-
- pss_evtconfig.evtmap = NULL;
- pss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
- pss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
-
- ioss_evtconfig.evtmap = NULL;
- ioss_evtconfig.num_evts = TELEM_MAX_OS_ALLOCATED_EVENTS;
- ioss_evtconfig.period = TELEM_SAMPLING_DEFAULT_PERIOD;
-
- ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
- TELEM_RESET);
- if (ret)
- pr_err("TELEMETRY Reset Failed\n");
-
- return ret;
-}
-
-
-static int telemetry_plt_get_eventconfig(struct telemetry_evtconfig *pss_config,
- struct telemetry_evtconfig *ioss_config,
- int pss_len, int ioss_len)
-{
- u32 *pss_evtmap, *ioss_evtmap;
- u32 index;
-
- pss_evtmap = pss_config->evtmap;
- ioss_evtmap = ioss_config->evtmap;
-
- mutex_lock(&(telm_conf->telem_lock));
- pss_config->num_evts = telm_conf->pss_config.ssram_evts_used;
- ioss_config->num_evts = telm_conf->ioss_config.ssram_evts_used;
-
- pss_config->period = telm_conf->pss_config.curr_period;
- ioss_config->period = telm_conf->ioss_config.curr_period;
-
- if ((pss_len < telm_conf->pss_config.ssram_evts_used) ||
- (ioss_len < telm_conf->ioss_config.ssram_evts_used)) {
- mutex_unlock(&(telm_conf->telem_lock));
- return -EINVAL;
- }
-
- for (index = 0; index < telm_conf->pss_config.ssram_evts_used;
- index++) {
- pss_evtmap[index] =
- telm_conf->pss_config.telem_evts[index].evt_id;
- }
-
- for (index = 0; index < telm_conf->ioss_config.ssram_evts_used;
- index++) {
- ioss_evtmap[index] =
- telm_conf->ioss_config.telem_evts[index].evt_id;
- }
-
- mutex_unlock(&(telm_conf->telem_lock));
- return 0;
-}
-
-
-static int telemetry_plt_add_events(u8 num_pss_evts, u8 num_ioss_evts,
- u32 *pss_evtmap, u32 *ioss_evtmap)
-{
- struct telemetry_evtconfig pss_evtconfig, ioss_evtconfig;
- int ret;
-
- pss_evtconfig.evtmap = pss_evtmap;
- pss_evtconfig.num_evts = num_pss_evts;
- pss_evtconfig.period = telm_conf->pss_config.curr_period;
-
- ioss_evtconfig.evtmap = ioss_evtmap;
- ioss_evtconfig.num_evts = num_ioss_evts;
- ioss_evtconfig.period = telm_conf->ioss_config.curr_period;
-
- ret = telemetry_setup_evtconfig(pss_evtconfig, ioss_evtconfig,
- TELEM_ADD);
- if (ret)
- pr_err("TELEMETRY ADD Failed\n");
-
- return ret;
-}
-
static int telem_evtlog_read(enum telemetry_unit telem_unit,
struct telem_ssram_region *ssram_region, u8 len)
{
@@ -1093,14 +868,8 @@ out:
static const struct telemetry_core_ops telm_pltops = {
.get_trace_verbosity = telemetry_plt_get_trace_verbosity,
.set_trace_verbosity = telemetry_plt_set_trace_verbosity,
- .set_sampling_period = telemetry_plt_set_sampling_period,
- .get_sampling_period = telemetry_plt_get_sampling_period,
.raw_read_eventlog = telemetry_plt_raw_read_eventlog,
- .get_eventconfig = telemetry_plt_get_eventconfig,
- .update_events = telemetry_plt_update_events,
.read_eventlog = telemetry_plt_read_eventlog,
- .reset_events = telemetry_plt_reset_events,
- .add_events = telemetry_plt_add_events,
};
static int telemetry_pltdrv_probe(struct platform_device *pdev)
diff --git a/drivers/platform/x86/intel/tpmi_power_domains.c b/drivers/platform/x86/intel/tpmi_power_domains.c
index 2f01cd22a6ee..833052dc34d2 100644
--- a/drivers/platform/x86/intel/tpmi_power_domains.c
+++ b/drivers/platform/x86/intel/tpmi_power_domains.c
@@ -74,6 +74,8 @@ static enum cpuhp_state tpmi_hp_state __read_mostly;
static cpumask_t *tpmi_power_domain_mask;
+static u16 *domain_die_map;
+
/* Lock to protect tpmi_power_domain_mask and tpmi_cpu_hash */
static DEFINE_MUTEX(tpmi_lock);
@@ -83,7 +85,7 @@ static const struct x86_cpu_id tpmi_cpu_ids[] = {
X86_MATCH_VFM(INTEL_ATOM_CRESTMONT, NULL),
X86_MATCH_VFM(INTEL_ATOM_DARKMONT_X, NULL),
X86_MATCH_VFM(INTEL_GRANITERAPIDS_D, NULL),
- X86_MATCH_VFM(INTEL_PANTHERCOVE_X, NULL),
+ X86_MATCH_VFM(INTEL_DIAMONDRAPIDS_X, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, tpmi_cpu_ids);
@@ -152,12 +154,21 @@ cpumask_t *tpmi_get_power_domain_mask(int cpu_no)
}
EXPORT_SYMBOL_NS_GPL(tpmi_get_power_domain_mask, "INTEL_TPMI_POWER_DOMAIN");
+int tpmi_get_linux_die_id(int pkg_id, int domain_id)
+{
+ if (pkg_id >= topology_max_packages() || domain_id >= MAX_POWER_DOMAINS)
+ return -EINVAL;
+
+ return domain_die_map[pkg_id * MAX_POWER_DOMAINS + domain_id];
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_get_linux_die_id, "INTEL_TPMI_POWER_DOMAIN");
+
static int tpmi_get_logical_id(unsigned int cpu, struct tpmi_cpu_info *info)
{
u64 data;
int ret;
- ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
+ ret = rdmsrq_safe(MSR_PM_LOGICAL_ID, &data);
if (ret)
return ret;
@@ -167,7 +178,7 @@ static int tpmi_get_logical_id(unsigned int cpu, struct tpmi_cpu_info *info)
info->punit_thread_id = FIELD_GET(LP_ID_MASK, data);
info->punit_core_id = FIELD_GET(MODULE_ID_MASK, data);
- info->pkg_id = topology_physical_package_id(cpu);
+ info->pkg_id = topology_logical_package_id(cpu);
info->linux_cpu = cpu;
return 0;
@@ -189,6 +200,9 @@ static int tpmi_cpu_online(unsigned int cpu)
cpumask_set_cpu(cpu, &tpmi_power_domain_mask[index]);
hash_add(tpmi_cpu_hash, &info->hnode, info->punit_core_id);
+ domain_die_map[info->pkg_id * MAX_POWER_DOMAINS + info->punit_domain_id] =
+ topology_die_id(cpu);
+
return 0;
}
@@ -203,26 +217,39 @@ static int __init tpmi_init(void)
return -ENODEV;
/* Check for MSR 0x54 presence */
- ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
+ ret = rdmsrq_safe(MSR_PM_LOGICAL_ID, &data);
if (ret)
return ret;
- tpmi_power_domain_mask = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS),
- sizeof(*tpmi_power_domain_mask), GFP_KERNEL);
+ tpmi_power_domain_mask = kzalloc_objs(*tpmi_power_domain_mask,
+ size_mul(topology_max_packages(), MAX_POWER_DOMAINS));
if (!tpmi_power_domain_mask)
return -ENOMEM;
+ domain_die_map = kcalloc(size_mul(topology_max_packages(), MAX_POWER_DOMAINS),
+ sizeof(*domain_die_map), GFP_KERNEL);
+ if (!domain_die_map) {
+ ret = -ENOMEM;
+ goto free_domain_mask;
+ }
+
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
"platform/x86/tpmi_power_domains:online",
tpmi_cpu_online, NULL);
- if (ret < 0) {
- kfree(tpmi_power_domain_mask);
- return ret;
- }
+ if (ret < 0)
+ goto free_domain_map;
tpmi_hp_state = ret;
return 0;
+
+free_domain_map:
+ kfree(domain_die_map);
+
+free_domain_mask:
+ kfree(tpmi_power_domain_mask);
+
+ return ret;
}
module_init(tpmi_init)
@@ -230,6 +257,7 @@ static void __exit tpmi_exit(void)
{
cpuhp_remove_state(tpmi_hp_state);
kfree(tpmi_power_domain_mask);
+ kfree(domain_die_map);
}
module_exit(tpmi_exit)
diff --git a/drivers/platform/x86/intel/tpmi_power_domains.h b/drivers/platform/x86/intel/tpmi_power_domains.h
index e35750dd9273..2fd0dd7afbd2 100644
--- a/drivers/platform/x86/intel/tpmi_power_domains.h
+++ b/drivers/platform/x86/intel/tpmi_power_domains.h
@@ -14,5 +14,6 @@ int tpmi_get_linux_cpu_number(int package_id, int die_id, int punit_core_id);
int tpmi_get_punit_core_number(int cpu_no);
int tpmi_get_power_domain_id(int cpu_no);
cpumask_t *tpmi_get_power_domain_mask(int cpu_no);
+int tpmi_get_linux_die_id(int pkg_id, int domain_id);
#endif
diff --git a/drivers/platform/x86/intel/turbo_max_3.c b/drivers/platform/x86/intel/turbo_max_3.c
index 79a0bcdeffb8..b5af3e91ba04 100644
--- a/drivers/platform/x86/intel/turbo_max_3.c
+++ b/drivers/platform/x86/intel/turbo_max_3.c
@@ -17,6 +17,7 @@
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
+#include <asm/msr.h>
#define MSR_OC_MAILBOX 0x150
#define MSR_OC_MAILBOX_CMD_OFFSET 32
@@ -41,14 +42,14 @@ static int get_oc_core_priority(unsigned int cpu)
value = cmd << MSR_OC_MAILBOX_CMD_OFFSET;
/* Set the busy bit to indicate OS is trying to issue command */
value |= BIT_ULL(MSR_OC_MAILBOX_BUSY_BIT);
- ret = wrmsrl_safe(MSR_OC_MAILBOX, value);
+ ret = wrmsrq_safe(MSR_OC_MAILBOX, value);
if (ret) {
pr_debug("cpu %d OC mailbox write failed\n", cpu);
return ret;
}
for (i = 0; i < OC_MAILBOX_RETRY_COUNT; ++i) {
- ret = rdmsrl_safe(MSR_OC_MAILBOX, &value);
+ ret = rdmsrq_safe(MSR_OC_MAILBOX, &value);
if (ret) {
pr_debug("cpu %d OC mailbox read failed\n", cpu);
break;
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
index 4e2c6a2d7e6e..7070c94324e0 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
@@ -26,21 +26,44 @@ static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr,
{
struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr);
- return sprintf(buf, "%u\n", data->domain_id);
+ return sysfs_emit(buf, "%u\n", data->domain_id);
}
static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr);
- return sprintf(buf, "%u\n", data->cluster_id);
+ return sysfs_emit(buf, "%u\n", data->cluster_id);
}
static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr);
- return sprintf(buf, "%u\n", data->package_id);
+ return sysfs_emit(buf, "%u\n", data->package_id);
+}
+
+#define MAX_UNCORE_AGENT_TYPES 4
+
+/* The order follows AGENT_TYPE_* defines */
+static const char *agent_name[MAX_UNCORE_AGENT_TYPES] = {"core", "cache", "memory", "io"};
+
+static ssize_t show_agent_types(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ struct uncore_data *data = container_of(attr, struct uncore_data, agent_types_kobj_attr);
+ unsigned long agent_mask = data->agent_type_mask;
+ int agent, length = 0;
+
+ for_each_set_bit(agent, &agent_mask, MAX_UNCORE_AGENT_TYPES) {
+ if (length)
+ length += sysfs_emit_at(buf, length, " ");
+
+ length += sysfs_emit_at(buf, length, "%s", agent_name[agent]);
+ }
+
+ length += sysfs_emit_at(buf, length, "\n");
+
+ return length;
}
static ssize_t show_attr(struct uncore_data *data, char *buf, enum uncore_index index)
@@ -54,7 +77,7 @@ static ssize_t show_attr(struct uncore_data *data, char *buf, enum uncore_index
if (ret)
return ret;
- return sprintf(buf, "%u\n", value);
+ return sysfs_emit(buf, "%u\n", value);
}
static ssize_t store_attr(struct uncore_data *data, const char *buf, ssize_t count,
@@ -120,6 +143,8 @@ show_uncore_attr(elc_high_threshold_enable,
UNCORE_INDEX_EFF_LAT_CTRL_HIGH_THRESHOLD_ENABLE);
show_uncore_attr(elc_floor_freq_khz, UNCORE_INDEX_EFF_LAT_CTRL_FREQ);
+show_uncore_attr(die_id, UNCORE_INDEX_DIE_ID);
+
#define show_uncore_data(member_name) \
static ssize_t show_##member_name(struct kobject *kobj, \
struct kobj_attribute *attr, char *buf)\
@@ -179,6 +204,15 @@ static int create_attr_group(struct uncore_data *data, char *name)
data->uncore_attrs[index++] = &data->fabric_cluster_id_kobj_attr.attr;
init_attribute_root_ro(package_id);
data->uncore_attrs[index++] = &data->package_id_kobj_attr.attr;
+ if (data->agent_type_mask) {
+ init_attribute_ro(agent_types);
+ data->uncore_attrs[index++] = &data->agent_types_kobj_attr.attr;
+ }
+ if (topology_max_dies_per_package() > 1 &&
+ data->agent_type_mask & AGENT_TYPE_CORE) {
+ init_attribute_ro(die_id);
+ data->uncore_attrs[index++] = &data->die_id_kobj_attr.attr;
+ }
}
data->uncore_attrs[index++] = &data->max_freq_khz_kobj_attr.attr;
@@ -235,9 +269,10 @@ int uncore_freq_add_entry(struct uncore_data *data, int cpu)
goto uncore_unlock;
data->instance_id = ret;
- sprintf(data->name, "uncore%02d", ret);
+ scnprintf(data->name, sizeof(data->name), "uncore%02d", ret);
} else {
- sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
+ scnprintf(data->name, sizeof(data->name), "package_%02d_die_%02d",
+ data->package_id, data->die_id);
}
uncore_read(data, &data->initial_min_freq_khz, UNCORE_INDEX_MIN_FREQ);
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h
index 26c854cd5d97..0abe850ef54e 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h
@@ -11,6 +11,18 @@
#include <linux/device.h>
+/*
+ * Define uncore agents, which are under uncore frequency control.
+ * Defined in the same order as specified in the TPMI UFS Specifications.
+ * It is possible that there are common uncore frequency control to more than
+ * one hardware agents. So, these defines are used as a bit mask.
+*/
+
+#define AGENT_TYPE_CORE 0x01
+#define AGENT_TYPE_CACHE 0x02
+#define AGENT_TYPE_MEMORY 0x04
+#define AGENT_TYPE_IO 0x08
+
/**
* struct uncore_data - Encapsulate all uncore data
* @stored_uncore_data: Last user changed MSR 620 value, which will be restored
@@ -25,9 +37,10 @@
* @cluster_id: cluster id in a domain
* @instance_id: Unique instance id to append to directory name
* @name: Sysfs entry name for this instance
+ * @agent_type_mask: Bit mask of all hardware agents for this domain
* @uncore_attr_group: Attribute group storage
* @max_freq_khz_kobj_attr: Storage for kobject attribute max_freq_khz
- * @mix_freq_khz_kobj_attr: Storage for kobject attribute min_freq_khz
+ * @min_freq_khz_kobj_attr: Storage for kobject attribute min_freq_khz
* @initial_max_freq_khz_kobj_attr: Storage for kobject attribute initial_max_freq_khz
* @initial_min_freq_khz_kobj_attr: Storage for kobject attribute initial_min_freq_khz
* @current_freq_khz_kobj_attr: Storage for kobject attribute current_freq_khz
@@ -35,12 +48,14 @@
* @fabric_cluster_id_kobj_attr: Storage for kobject attribute fabric_cluster_id
* @package_id_kobj_attr: Storage for kobject attribute package_id
* @elc_low_threshold_percent_kobj_attr:
- Storage for kobject attribute elc_low_threshold_percent
+ * Storage for kobject attribute elc_low_threshold_percent
* @elc_high_threshold_percent_kobj_attr:
- Storage for kobject attribute elc_high_threshold_percent
+ * Storage for kobject attribute elc_high_threshold_percent
* @elc_high_threshold_enable_kobj_attr:
- Storage for kobject attribute elc_high_threshold_enable
+ * Storage for kobject attribute elc_high_threshold_enable
* @elc_floor_freq_khz_kobj_attr: Storage for kobject attribute elc_floor_freq_khz
+ * @agent_types_kobj_attr: Storage for kobject attribute agent_type
+ * @die_id_kobj_attr: Attribute storage for die_id information
* @uncore_attrs: Attribute storage for group creation
*
* This structure is used to encapsulate all data related to uncore sysfs
@@ -58,6 +73,7 @@ struct uncore_data {
int cluster_id;
int instance_id;
char name[32];
+ u16 agent_type_mask;
struct attribute_group uncore_attr_group;
struct kobj_attribute max_freq_khz_kobj_attr;
@@ -72,7 +88,9 @@ struct uncore_data {
struct kobj_attribute elc_high_threshold_percent_kobj_attr;
struct kobj_attribute elc_high_threshold_enable_kobj_attr;
struct kobj_attribute elc_floor_freq_khz_kobj_attr;
- struct attribute *uncore_attrs[13];
+ struct kobj_attribute agent_types_kobj_attr;
+ struct kobj_attribute die_id_kobj_attr;
+ struct attribute *uncore_attrs[15];
};
#define UNCORE_DOMAIN_ID_INVALID -1
@@ -85,6 +103,7 @@ enum uncore_index {
UNCORE_INDEX_EFF_LAT_CTRL_HIGH_THRESHOLD,
UNCORE_INDEX_EFF_LAT_CTRL_HIGH_THRESHOLD_ENABLE,
UNCORE_INDEX_EFF_LAT_CTRL_FREQ,
+ UNCORE_INDEX_DIE_ID,
};
int uncore_freq_common_init(int (*read)(struct uncore_data *data, unsigned int *value,
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
index 4aa6c227ec82..88015a2c6a0d 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
@@ -22,14 +22,16 @@
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/intel_tpmi.h>
+#include <linux/intel_vsec.h>
#include <linux/io.h>
#include <linux/module.h>
-#include <linux/intel_tpmi.h>
+#include "../tpmi_power_domains.h"
#include "uncore-frequency-common.h"
#define UNCORE_MAJOR_VERSION 0
-#define UNCORE_MINOR_VERSION 2
+#define UNCORE_MINOR_VERSION 3
#define UNCORE_ELC_SUPPORTED_VERSION 2
#define UNCORE_HEADER_INDEX 0
#define UNCORE_FABRIC_CLUSTER_OFFSET 8
@@ -49,6 +51,7 @@ struct tpmi_uncore_cluster_info {
bool root_domain;
bool elc_supported;
u8 __iomem *cluster_base;
+ u16 cdie_id;
struct uncore_data uncore_data;
struct tpmi_uncore_struct *uncore_root;
};
@@ -189,9 +192,14 @@ static int uncore_read_control_freq(struct uncore_data *data, unsigned int *valu
static int write_eff_lat_ctrl(struct uncore_data *data, unsigned int val, enum uncore_index index)
{
struct tpmi_uncore_cluster_info *cluster_info;
+ struct tpmi_uncore_struct *uncore_root;
u64 control;
cluster_info = container_of(data, struct tpmi_uncore_cluster_info, uncore_data);
+ uncore_root = cluster_info->uncore_root;
+
+ if (uncore_root->write_blocked)
+ return -EPERM;
if (cluster_info->root_domain)
return -ENODATA;
@@ -347,9 +355,102 @@ static int uncore_read_freq(struct uncore_data *data, unsigned int *freq)
return 0;
}
+/*
+ * Agent types as per the TPMI UFS Specification for UFS_STATUS
+ * Agent Type - Core Bit: 23
+ * Agent Type - Cache Bit: 24
+ * Agent Type - Memory Bit: 25
+ * Agent Type - IO Bit: 26
+ */
+
+#define UNCORE_AGENT_TYPES GENMASK_ULL(26, 23)
+
+/* Helper function to read agent type over MMIO and set the agent type mask */
+static void uncore_set_agent_type(struct tpmi_uncore_cluster_info *cluster_info)
+{
+ u64 status;
+
+ status = readq((u8 __iomem *)cluster_info->cluster_base + UNCORE_STATUS_INDEX);
+ cluster_info->uncore_data.agent_type_mask = FIELD_GET(UNCORE_AGENT_TYPES, status);
+}
+
+#define MAX_PARTITIONS 2
+
+/* IO domain ID start index for a partition */
+static u8 io_die_start[MAX_PARTITIONS];
+
+/* Next IO domain ID index after the current partition IO die IDs */
+static u8 io_die_index_next;
+
+/* Lock to protect io_die_start, io_die_index_next */
+static DEFINE_MUTEX(domain_lock);
+
+static void set_domain_id(int id, int num_resources,
+ struct oobmsm_plat_info *plat_info,
+ struct tpmi_uncore_cluster_info *cluster_info)
+{
+ u8 part_io_index, cdie_range, pkg_io_index, max_dies;
+
+ if (plat_info->partition >= MAX_PARTITIONS) {
+ cluster_info->uncore_data.domain_id = id;
+ return;
+ }
+
+ if (cluster_info->uncore_data.agent_type_mask & AGENT_TYPE_CORE) {
+ cluster_info->uncore_data.domain_id = cluster_info->cdie_id;
+ return;
+ }
+
+ /* Unlikely but cdie_mask may have holes, so take range */
+ cdie_range = fls(plat_info->cdie_mask) - ffs(plat_info->cdie_mask) + 1;
+ max_dies = topology_max_dies_per_package();
+
+ /*
+ * If the CPU doesn't enumerate dies, then use current cdie range
+ * as the max.
+ */
+ if (cdie_range > max_dies)
+ max_dies = cdie_range;
+
+ guard(mutex)(&domain_lock);
+
+ if (!io_die_index_next)
+ io_die_index_next = max_dies;
+
+ if (!io_die_start[plat_info->partition]) {
+ io_die_start[plat_info->partition] = io_die_index_next;
+ /*
+ * number of IO dies = num_resources - cdie_range. Hence
+ * next partition io_die_index_next is set after IO dies
+ * in the current partition.
+ */
+ io_die_index_next += (num_resources - cdie_range);
+ }
+
+ /*
+ * Index from IO die start within the partition:
+ * This is the first valid domain after the cdies.
+ * For example the current resource index 5 and cdies end at
+ * index 3 (cdie_cnt = 4). Then the IO only index 5 - 4 = 1.
+ */
+ part_io_index = id - cdie_range;
+
+ /*
+ * Add to the IO die start index for this partition in this package
+ * to make unique in the package.
+ */
+ pkg_io_index = io_die_start[plat_info->partition] + part_io_index;
+
+ /* Assign this to domain ID */
+ cluster_info->uncore_data.domain_id = pkg_io_index;
+}
+
/* Callback for sysfs read for TPMI uncore values. Called under mutex locks. */
static int uncore_read(struct uncore_data *data, unsigned int *value, enum uncore_index index)
{
+ struct tpmi_uncore_cluster_info *cluster_info;
+ int ret;
+
switch (index) {
case UNCORE_INDEX_MIN_FREQ:
case UNCORE_INDEX_MAX_FREQ:
@@ -364,6 +465,16 @@ static int uncore_read(struct uncore_data *data, unsigned int *value, enum uncor
case UNCORE_INDEX_EFF_LAT_CTRL_FREQ:
return read_eff_lat_ctrl(data, value, index);
+ case UNCORE_INDEX_DIE_ID:
+ cluster_info = container_of(data, struct tpmi_uncore_cluster_info, uncore_data);
+ ret = tpmi_get_linux_die_id(cluster_info->uncore_data.package_id,
+ cluster_info->cdie_id);
+ if (ret < 0)
+ return ret;
+
+ *value = ret;
+ return 0;
+
default:
break;
}
@@ -413,15 +524,26 @@ static void remove_cluster_entries(struct tpmi_uncore_struct *tpmi_uncore)
}
}
+static void set_cdie_id(int domain_id, struct tpmi_uncore_cluster_info *cluster_info,
+ struct oobmsm_plat_info *plat_info)
+{
+
+ cluster_info->cdie_id = domain_id;
+
+ if (plat_info->cdie_mask && cluster_info->uncore_data.agent_type_mask & AGENT_TYPE_CORE)
+ cluster_info->cdie_id = domain_id + ffs(plat_info->cdie_mask) - 1;
+}
+
#define UNCORE_VERSION_MASK GENMASK_ULL(7, 0)
#define UNCORE_LOCAL_FABRIC_CLUSTER_ID_MASK GENMASK_ULL(15, 8)
#define UNCORE_CLUSTER_OFF_MASK GENMASK_ULL(7, 0)
+#define UNCORE_AUTONOMOUS_UFS_DISABLED BIT(32)
#define UNCORE_MAX_CLUSTER_PER_DOMAIN 8
static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
{
bool read_blocked = 0, write_blocked = 0;
- struct intel_tpmi_plat_info *plat_info;
+ struct oobmsm_plat_info *plat_info;
struct tpmi_uncore_struct *tpmi_uncore;
bool uncore_sysfs_added = false;
int ret, i, pkg = 0;
@@ -467,13 +589,17 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_
/* Get the package ID from the TPMI core */
plat_info = tpmi_get_platform_data(auxdev);
- if (plat_info)
- pkg = plat_info->package_id;
- else
+ if (unlikely(!plat_info)) {
dev_info(&auxdev->dev, "Platform information is NULL\n");
+ ret = -ENODEV;
+ goto err_rem_common;
+ }
+
+ pkg = plat_info->package_id;
for (i = 0; i < num_resources; ++i) {
struct tpmi_uncore_power_domain_info *pd_info;
+ bool auto_ufs_enabled;
struct resource *res;
u64 cluster_offset;
u8 cluster_mask;
@@ -523,6 +649,8 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_
continue;
}
+ auto_ufs_enabled = !(header & UNCORE_AUTONOMOUS_UFS_DISABLED);
+
/* Find out number of clusters in this resource */
pd_info->cluster_count = hweight8(cluster_mask);
@@ -552,15 +680,22 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_
cluster_info->cluster_base = pd_info->uncore_base + mask;
+ uncore_set_agent_type(cluster_info);
+
cluster_info->uncore_data.package_id = pkg;
/* There are no dies like Cascade Lake */
cluster_info->uncore_data.die_id = 0;
- cluster_info->uncore_data.domain_id = i;
cluster_info->uncore_data.cluster_id = j;
+ set_cdie_id(i, cluster_info, plat_info);
+
+ set_domain_id(i, num_resources, plat_info, cluster_info);
+
cluster_info->uncore_root = tpmi_uncore;
- if (TPMI_MINOR_VERSION(pd_info->ufs_header_ver) >= UNCORE_ELC_SUPPORTED_VERSION)
+ if ((TPMI_MINOR_VERSION(pd_info->ufs_header_ver) >=
+ UNCORE_ELC_SUPPORTED_VERSION) &&
+ auto_ufs_enabled)
cluster_info->elc_supported = true;
ret = uncore_freq_add_entry(&cluster_info->uncore_data, 0);
@@ -581,7 +716,7 @@ static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_
auxiliary_set_drvdata(auxdev, tpmi_uncore);
- if (topology_max_dies_per_package() > 1)
+ if (topology_max_dies_per_package() > 1 || plat_info->partition)
return 0;
tpmi_uncore->root_cluster.root_domain = true;
@@ -631,5 +766,6 @@ module_auxiliary_driver(intel_uncore_aux_driver);
MODULE_IMPORT_NS("INTEL_TPMI");
MODULE_IMPORT_NS("INTEL_UNCORE_FREQUENCY");
+MODULE_IMPORT_NS("INTEL_TPMI_POWER_DOMAIN");
MODULE_DESCRIPTION("Intel TPMI UFS Driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
index 40bbf8e45fa4..667f2c8b9594 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
@@ -21,6 +21,7 @@
#include <linux/suspend.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
+#include <asm/msr.h>
#include "uncore-frequency-common.h"
@@ -51,7 +52,7 @@ static int uncore_read_control_freq(struct uncore_data *data, unsigned int *valu
if (data->control_cpu < 0)
return -ENXIO;
- ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
+ ret = rdmsrq_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
if (ret)
return ret;
@@ -76,7 +77,7 @@ static int uncore_write_control_freq(struct uncore_data *data, unsigned int inpu
if (data->control_cpu < 0)
return -ENXIO;
- ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
+ ret = rdmsrq_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
if (ret)
return ret;
@@ -88,7 +89,7 @@ static int uncore_write_control_freq(struct uncore_data *data, unsigned int inpu
cap |= FIELD_PREP(UNCORE_MIN_RATIO_MASK, input);
}
- ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap);
+ ret = wrmsrq_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap);
if (ret)
return ret;
@@ -105,7 +106,7 @@ static int uncore_read_freq(struct uncore_data *data, unsigned int *freq)
if (data->control_cpu < 0)
return -ENXIO;
- ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_PERF_STATUS, &ratio);
+ ret = rdmsrq_on_cpu(data->control_cpu, MSR_UNCORE_PERF_STATUS, &ratio);
if (ret)
return ret;
@@ -146,15 +147,13 @@ static int uncore_event_cpu_online(unsigned int cpu)
{
struct uncore_data *data;
int target;
+ int ret;
/* Check if there is an online cpu in the package for uncore MSR */
target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu));
if (target < nr_cpu_ids)
return 0;
- /* Use this CPU on this die as a control CPU */
- cpumask_set_cpu(cpu, &uncore_cpu_mask);
-
data = uncore_get_instance(cpu);
if (!data)
return 0;
@@ -163,7 +162,14 @@ static int uncore_event_cpu_online(unsigned int cpu)
data->die_id = topology_die_id(cpu);
data->domain_id = UNCORE_DOMAIN_ID_INVALID;
- return uncore_freq_add_entry(data, cpu);
+ ret = uncore_freq_add_entry(data, cpu);
+ if (ret)
+ return ret;
+
+ /* Use this CPU on this die as a control CPU */
+ cpumask_set_cpu(cpu, &uncore_cpu_mask);
+
+ return 0;
}
static int uncore_event_cpu_offline(unsigned int cpu)
@@ -207,7 +213,7 @@ static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode,
if (!data || !data->valid || !data->stored_uncore_data)
return 0;
- wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT,
+ wrmsrq_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT,
data->stored_uncore_data);
}
break;
@@ -250,6 +256,10 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = {
X86_MATCH_VFM(INTEL_ARROWLAKE, NULL),
X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL),
X86_MATCH_VFM(INTEL_LUNARLAKE_M, NULL),
+ X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL),
+ X86_MATCH_VFM(INTEL_NOVALAKE, NULL),
+ X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, intel_uncore_cpu_ids);
@@ -268,8 +278,7 @@ static int __init intel_uncore_init(void)
uncore_max_entries = topology_max_packages() *
topology_max_dies_per_package();
- uncore_instances = kcalloc(uncore_max_entries,
- sizeof(*uncore_instances), GFP_KERNEL);
+ uncore_instances = kzalloc_objs(*uncore_instances, uncore_max_entries);
if (!uncore_instances)
return -ENOMEM;
diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c
index 232cd12e3c9f..874023c38fd1 100644
--- a/drivers/platform/x86/intel/vbtn.c
+++ b/drivers/platform/x86/intel/vbtn.c
@@ -275,12 +275,16 @@ static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel)
static int intel_vbtn_probe(struct platform_device *device)
{
- acpi_handle handle = ACPI_HANDLE(&device->dev);
bool dual_accel, has_buttons, has_switches;
struct intel_vbtn_priv *priv;
+ acpi_handle handle;
acpi_status status;
int err;
+ handle = ACPI_HANDLE(&device->dev);
+ if (!handle)
+ return -ENODEV;
+
dual_accel = dual_accel_detect();
has_buttons = acpi_has_method(handle, "VBDL");
has_switches = intel_vbtn_has_switches(handle, dual_accel);
@@ -390,32 +394,4 @@ static struct platform_driver intel_vbtn_pl_driver = {
.remove = intel_vbtn_remove,
};
-static acpi_status __init
-check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv)
-{
- const struct acpi_device_id *ids = context;
- struct acpi_device *dev = acpi_fetch_acpi_dev(handle);
-
- if (dev && acpi_match_device_ids(dev, ids) == 0)
- if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL)))
- dev_info(&dev->dev,
- "intel-vbtn: created platform device\n");
-
- return AE_OK;
-}
-
-static int __init intel_vbtn_init(void)
-{
- acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
- ACPI_UINT32_MAX, check_acpi_dev, NULL,
- (void *)intel_vbtn_ids, NULL);
-
- return platform_driver_register(&intel_vbtn_pl_driver);
-}
-module_init(intel_vbtn_init);
-
-static void __exit intel_vbtn_exit(void)
-{
- platform_driver_unregister(&intel_vbtn_pl_driver);
-}
-module_exit(intel_vbtn_exit);
+module_platform_driver(intel_vbtn_pl_driver);
diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c
index db3c031d1757..18e4a892bf0f 100644
--- a/drivers/platform/x86/intel/vsec.c
+++ b/drivers/platform/x86/intel/vsec.c
@@ -15,13 +15,18 @@
#include <linux/auxiliary_bus.h>
#include <linux/bits.h>
+#include <linux/bitops.h>
+#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/idr.h>
+#include <linux/log2.h>
#include <linux/intel_vsec.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
+#include <linux/string.h>
#include <linux/types.h>
#define PMT_XA_START 0
@@ -32,6 +37,20 @@ static DEFINE_IDA(intel_vsec_ida);
static DEFINE_IDA(intel_vsec_sdsi_ida);
static DEFINE_XARRAY_ALLOC(auxdev_array);
+enum vsec_device_state {
+ STATE_NOT_FOUND,
+ STATE_REGISTERED,
+ STATE_SKIP,
+};
+
+struct vsec_priv {
+ const struct intel_vsec_platform_info *info;
+ struct device *suppliers[VSEC_FEATURE_COUNT];
+ struct oobmsm_plat_info plat_info;
+ enum vsec_device_state state[VSEC_FEATURE_COUNT];
+ unsigned long found_caps;
+};
+
static const char *intel_vsec_name(enum intel_vsec_id id)
{
switch (id) {
@@ -50,6 +69,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id)
case VSEC_ID_TPMI:
return "tpmi";
+ case VSEC_ID_DISCOVERY:
+ return "discovery";
+
default:
return NULL;
}
@@ -68,6 +90,8 @@ static bool intel_vsec_supported(u16 id, unsigned long caps)
return !!(caps & VSEC_CAP_SDSI);
case VSEC_ID_TPMI:
return !!(caps & VSEC_CAP_TPMI);
+ case VSEC_ID_DISCOVERY:
+ return !!(caps & VSEC_CAP_DISCOVERY);
default:
return false;
}
@@ -87,11 +111,108 @@ static void intel_vsec_dev_release(struct device *dev)
ida_free(intel_vsec_dev->ida, intel_vsec_dev->auxdev.id);
+ kfree(intel_vsec_dev->acpi_disc);
kfree(intel_vsec_dev->resource);
kfree(intel_vsec_dev);
}
-int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
+static const struct vsec_feature_dependency *
+get_consumer_dependencies(struct vsec_priv *priv, int cap_id)
+{
+ const struct vsec_feature_dependency *deps = priv->info->deps;
+ int consumer_id = priv->info->num_deps;
+
+ if (!deps)
+ return NULL;
+
+ while (consumer_id--)
+ if (deps[consumer_id].feature == BIT(cap_id))
+ return &deps[consumer_id];
+
+ return NULL;
+}
+
+static bool vsec_driver_present(int cap_id)
+{
+ unsigned long bit = BIT(cap_id);
+
+ switch (bit) {
+ case VSEC_CAP_TELEMETRY:
+ return IS_ENABLED(CONFIG_INTEL_PMT_TELEMETRY);
+ case VSEC_CAP_WATCHER:
+ return IS_ENABLED(CONFIG_INTEL_PMT_WATCHER);
+ case VSEC_CAP_CRASHLOG:
+ return IS_ENABLED(CONFIG_INTEL_PMT_CRASHLOG);
+ case VSEC_CAP_SDSI:
+ return IS_ENABLED(CONFIG_INTEL_SDSI);
+ case VSEC_CAP_TPMI:
+ return IS_ENABLED(CONFIG_INTEL_TPMI);
+ case VSEC_CAP_DISCOVERY:
+ return IS_ENABLED(CONFIG_INTEL_PMT_DISCOVERY);
+ default:
+ return false;
+ }
+}
+
+/*
+ * Although pci_device_id table is available in the pdev, this prototype is
+ * necessary because the code using it can be called by an exported API that
+ * might pass a different pdev.
+ */
+static const struct pci_device_id intel_vsec_pci_ids[];
+
+static int intel_vsec_link_devices(struct device *parent, struct device *dev,
+ int consumer_id)
+{
+ const struct vsec_feature_dependency *deps;
+ enum vsec_device_state *state;
+ struct device **suppliers;
+ struct vsec_priv *priv;
+ struct pci_dev *pdev;
+ int supplier_id;
+
+ if (!consumer_id)
+ return 0;
+
+ if (!dev_is_pci(parent))
+ return 0;
+
+ pdev = to_pci_dev(parent);
+ if (!pci_match_id(intel_vsec_pci_ids, pdev))
+ return 0;
+
+ priv = pci_get_drvdata(pdev);
+ state = priv->state;
+ suppliers = priv->suppliers;
+
+ priv->suppliers[consumer_id] = dev;
+
+ deps = get_consumer_dependencies(priv, consumer_id);
+ if (!deps)
+ return 0;
+
+ for_each_set_bit(supplier_id, &deps->supplier_bitmap, VSEC_FEATURE_COUNT) {
+ struct device_link *link;
+
+ if (state[supplier_id] != STATE_REGISTERED ||
+ !vsec_driver_present(supplier_id))
+ continue;
+
+ if (!suppliers[supplier_id]) {
+ dev_err(dev, "Bad supplier list\n");
+ return -EINVAL;
+ }
+
+ link = device_link_add(dev, suppliers[supplier_id],
+ DL_FLAG_AUTOPROBE_CONSUMER);
+ if (!link)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int intel_vsec_add_aux(struct device *parent,
struct intel_vsec_device *intel_vsec_dev,
const char *name)
{
@@ -128,68 +249,87 @@ int intel_vsec_add_aux(struct pci_dev *pdev, struct device *parent,
return ret;
}
+ /*
+ * Assign a name now to ensure that the device link doesn't contain
+ * a null string for the consumer name. This is a problem when a supplier
+ * supplies more than one consumer and can lead to a duplicate name error
+ * when the link is created in sysfs.
+ */
+ ret = dev_set_name(&auxdev->dev, "%s.%s.%d", KBUILD_MODNAME, auxdev->name,
+ auxdev->id);
+ if (ret)
+ goto cleanup_aux;
+
+ ret = intel_vsec_link_devices(parent, &auxdev->dev, intel_vsec_dev->cap_id);
+ if (ret)
+ goto cleanup_aux;
+
ret = auxiliary_device_add(auxdev);
- if (ret < 0) {
- auxiliary_device_uninit(auxdev);
- return ret;
- }
+ if (ret)
+ goto cleanup_aux;
return devm_add_action_or_reset(parent, intel_vsec_remove_aux,
auxdev);
+
+cleanup_aux:
+ auxiliary_device_uninit(auxdev);
+ return ret;
}
EXPORT_SYMBOL_NS_GPL(intel_vsec_add_aux, "INTEL_VSEC");
-static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *header,
- struct intel_vsec_platform_info *info)
+static int intel_vsec_add_dev(struct device *dev, struct intel_vsec_header *header,
+ const struct intel_vsec_platform_info *info,
+ unsigned long cap_id, u64 base_addr)
{
struct intel_vsec_device __free(kfree) *intel_vsec_dev = NULL;
struct resource __free(kfree) *res = NULL;
struct resource *tmp;
struct device *parent;
unsigned long quirks = info->quirks;
- u64 base_addr;
int i;
if (info->parent)
parent = info->parent;
else
- parent = &pdev->dev;
+ parent = dev;
if (!intel_vsec_supported(header->id, info->caps))
return -EINVAL;
if (!header->num_entries) {
- dev_dbg(&pdev->dev, "Invalid 0 entry count for header id %d\n", header->id);
+ dev_dbg(dev, "Invalid 0 entry count for header id %d\n", header->id);
return -EINVAL;
}
if (!header->entry_size) {
- dev_dbg(&pdev->dev, "Invalid 0 entry size for header id %d\n", header->id);
+ dev_dbg(dev, "Invalid 0 entry size for header id %d\n", header->id);
return -EINVAL;
}
- intel_vsec_dev = kzalloc(sizeof(*intel_vsec_dev), GFP_KERNEL);
+ intel_vsec_dev = kzalloc_obj(*intel_vsec_dev);
if (!intel_vsec_dev)
return -ENOMEM;
- res = kcalloc(header->num_entries, sizeof(*res), GFP_KERNEL);
+ res = kzalloc_objs(*res, header->num_entries);
if (!res)
return -ENOMEM;
if (quirks & VSEC_QUIRK_TABLE_SHIFT)
header->offset >>= TABLE_OFFSET_SHIFT;
- if (info->base_addr)
- base_addr = info->base_addr;
- else
- base_addr = pdev->resource[header->tbir].start;
-
/*
* The DVSEC/VSEC contains the starting offset and count for a block of
* discovery tables. Create a resource array of these tables to the
* auxiliary device driver.
*/
for (i = 0, tmp = res; i < header->num_entries; i++, tmp++) {
+ /*
+ * Skip resource mapping check for ACPI-based discovery
+ * since those tables are read from _DSD, not MMIO.
+ */
+ if (info->src == INTEL_VSEC_DISC_ACPI)
+ break;
+
tmp->start = base_addr + header->offset + i * (header->entry_size * sizeof(u32));
tmp->end = tmp->start + (header->entry_size * sizeof(u32)) - 1;
tmp->flags = IORESOURCE_MEM;
@@ -201,12 +341,26 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
release_mem_region(tmp->start, resource_size(tmp));
}
- intel_vsec_dev->pcidev = pdev;
+ intel_vsec_dev->dev = dev;
intel_vsec_dev->resource = no_free_ptr(res);
intel_vsec_dev->num_resources = header->num_entries;
intel_vsec_dev->quirks = info->quirks;
intel_vsec_dev->base_addr = info->base_addr;
intel_vsec_dev->priv_data = info->priv_data;
+ intel_vsec_dev->cap_id = cap_id;
+ intel_vsec_dev->src = info->src;
+
+ if (info->src == INTEL_VSEC_DISC_ACPI) {
+ size_t bytes;
+
+ if (check_mul_overflow(intel_vsec_dev->num_resources,
+ sizeof(*info->acpi_disc), &bytes))
+ return -EOVERFLOW;
+
+ intel_vsec_dev->acpi_disc = kmemdup(info->acpi_disc, bytes, GFP_KERNEL);
+ if (!intel_vsec_dev->acpi_disc)
+ return -ENOMEM;
+ }
if (header->id == VSEC_ID_SDSI)
intel_vsec_dev->ida = &intel_vsec_sdsi_ida;
@@ -217,28 +371,136 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
* Pass the ownership of intel_vsec_dev and resource within it to
* intel_vsec_add_aux()
*/
- return intel_vsec_add_aux(pdev, parent, no_free_ptr(intel_vsec_dev),
+ return intel_vsec_add_aux(parent, no_free_ptr(intel_vsec_dev),
intel_vsec_name(header->id));
}
-static bool intel_vsec_walk_header(struct pci_dev *pdev,
- struct intel_vsec_platform_info *info)
+static bool suppliers_ready(struct vsec_priv *priv,
+ const struct vsec_feature_dependency *consumer_deps,
+ int cap_id)
+{
+ enum vsec_device_state *state = priv->state;
+ int supplier_id;
+
+ if (WARN_ON_ONCE(consumer_deps->feature != BIT(cap_id)))
+ return false;
+
+ /*
+ * Verify that all required suppliers have been found. Return false
+ * immediately if any are still missing.
+ */
+ for_each_set_bit(supplier_id, &consumer_deps->supplier_bitmap, VSEC_FEATURE_COUNT) {
+ if (state[supplier_id] == STATE_SKIP)
+ continue;
+
+ if (state[supplier_id] == STATE_NOT_FOUND)
+ return false;
+ }
+
+ /*
+ * All suppliers have been found and the consumer is ready to be
+ * registered.
+ */
+ return true;
+}
+
+static int get_cap_id(u32 header_id, unsigned long *cap_id)
+{
+ switch (header_id) {
+ case VSEC_ID_TELEMETRY:
+ *cap_id = ilog2(VSEC_CAP_TELEMETRY);
+ break;
+ case VSEC_ID_WATCHER:
+ *cap_id = ilog2(VSEC_CAP_WATCHER);
+ break;
+ case VSEC_ID_CRASHLOG:
+ *cap_id = ilog2(VSEC_CAP_CRASHLOG);
+ break;
+ case VSEC_ID_SDSI:
+ *cap_id = ilog2(VSEC_CAP_SDSI);
+ break;
+ case VSEC_ID_TPMI:
+ *cap_id = ilog2(VSEC_CAP_TPMI);
+ break;
+ case VSEC_ID_DISCOVERY:
+ *cap_id = ilog2(VSEC_CAP_DISCOVERY);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int intel_vsec_register_device(struct device *dev,
+ struct intel_vsec_header *header,
+ const struct intel_vsec_platform_info *info,
+ u64 base_addr)
+{
+ const struct vsec_feature_dependency *consumer_deps;
+ struct vsec_priv *priv;
+ struct pci_dev *pdev;
+ unsigned long cap_id;
+ int ret;
+
+ ret = get_cap_id(header->id, &cap_id);
+ if (ret)
+ return ret;
+
+ /*
+ * Only track dependencies for devices probed by the VSEC driver.
+ * For others using the exported APIs, add the device directly.
+ */
+ if (!dev_is_pci(dev))
+ return intel_vsec_add_dev(dev, header, info, cap_id, base_addr);
+
+ pdev = to_pci_dev(dev);
+ if (!pci_match_id(intel_vsec_pci_ids, pdev))
+ return intel_vsec_add_dev(dev, header, info, cap_id, base_addr);
+
+ priv = pci_get_drvdata(pdev);
+ if (priv->state[cap_id] == STATE_REGISTERED ||
+ priv->state[cap_id] == STATE_SKIP)
+ return -EEXIST;
+
+ priv->found_caps |= BIT(cap_id);
+
+ if (!vsec_driver_present(cap_id)) {
+ priv->state[cap_id] = STATE_SKIP;
+ return -ENODEV;
+ }
+
+ consumer_deps = get_consumer_dependencies(priv, cap_id);
+ if (!consumer_deps || suppliers_ready(priv, consumer_deps, cap_id)) {
+ ret = intel_vsec_add_dev(dev, header, info, cap_id, base_addr);
+ if (ret)
+ priv->state[cap_id] = STATE_SKIP;
+ else
+ priv->state[cap_id] = STATE_REGISTERED;
+
+ return ret;
+ }
+
+ return -EAGAIN;
+}
+
+static int intel_vsec_walk_header(struct device *dev,
+ const struct intel_vsec_platform_info *info)
{
struct intel_vsec_header **header = info->headers;
- bool have_devices = false;
int ret;
for ( ; *header; header++) {
- ret = intel_vsec_add_dev(pdev, *header, info);
- if (!ret)
- have_devices = true;
+ ret = intel_vsec_register_device(dev, *header, info, info->base_addr);
+ if (ret)
+ return ret;
}
- return have_devices;
+ return 0;
}
static bool intel_vsec_walk_dvsec(struct pci_dev *pdev,
- struct intel_vsec_platform_info *info)
+ const struct intel_vsec_platform_info *info)
{
bool have_devices = false;
int pos = 0;
@@ -277,7 +539,8 @@ static bool intel_vsec_walk_dvsec(struct pci_dev *pdev,
pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER2, &hdr);
header.id = PCI_DVSEC_HEADER2_ID(hdr);
- ret = intel_vsec_add_dev(pdev, &header, info);
+ ret = intel_vsec_register_device(&pdev->dev, &header, info,
+ pci_resource_start(pdev, header.tbir));
if (ret)
continue;
@@ -288,7 +551,7 @@ static bool intel_vsec_walk_dvsec(struct pci_dev *pdev,
}
static bool intel_vsec_walk_vsec(struct pci_dev *pdev,
- struct intel_vsec_platform_info *info)
+ const struct intel_vsec_platform_info *info)
{
bool have_devices = false;
int pos = 0;
@@ -322,7 +585,8 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev,
header.tbir = INTEL_DVSEC_TABLE_BAR(table);
header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
- ret = intel_vsec_add_dev(pdev, &header, info);
+ ret = intel_vsec_register_device(&pdev->dev, &header, info,
+ pci_resource_start(pdev, header.tbir));
if (ret)
continue;
@@ -332,20 +596,90 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev,
return have_devices;
}
-void intel_vsec_register(struct pci_dev *pdev,
- struct intel_vsec_platform_info *info)
+int intel_vsec_register(struct device *dev,
+ const struct intel_vsec_platform_info *info)
{
- if (!pdev || !info || !info->headers)
- return;
+ if (!dev || !info || !info->headers)
+ return -EINVAL;
- intel_vsec_walk_header(pdev, info);
+ return intel_vsec_walk_header(dev, info);
}
EXPORT_SYMBOL_NS_GPL(intel_vsec_register, "INTEL_VSEC");
+static bool intel_vsec_get_features(struct pci_dev *pdev,
+ const struct intel_vsec_platform_info *info)
+{
+ bool found = false;
+
+ /*
+ * Both DVSEC and VSEC capabilities can exist on the same device,
+ * so both intel_vsec_walk_dvsec() and intel_vsec_walk_vsec() must be
+ * called independently. Additionally, intel_vsec_walk_header() is
+ * needed for devices that do not have VSEC/DVSEC but provide the
+ * information via device_data.
+ */
+ if (intel_vsec_walk_dvsec(pdev, info))
+ found = true;
+
+ if (intel_vsec_walk_vsec(pdev, info))
+ found = true;
+
+ if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) &&
+ intel_vsec_walk_header(&pdev->dev, info))
+ found = true;
+
+ return found;
+}
+
+static void intel_vsec_skip_missing_dependencies(struct pci_dev *pdev)
+{
+ struct vsec_priv *priv = pci_get_drvdata(pdev);
+ const struct vsec_feature_dependency *deps = priv->info->deps;
+ int consumer_id = priv->info->num_deps;
+
+ while (consumer_id--) {
+ int supplier_id;
+
+ deps = &priv->info->deps[consumer_id];
+
+ for_each_set_bit(supplier_id, &deps->supplier_bitmap, VSEC_FEATURE_COUNT) {
+ if (!(BIT(supplier_id) & priv->found_caps))
+ priv->state[supplier_id] = STATE_SKIP;
+ }
+ }
+}
+
+static int intel_vsec_pci_init(struct pci_dev *pdev)
+{
+ struct vsec_priv *priv = pci_get_drvdata(pdev);
+ const struct intel_vsec_platform_info *info = priv->info;
+ int run_once = 0;
+ bool found_any = false;
+ int num_caps;
+
+ num_caps = hweight_long(info->caps);
+ while (num_caps--) {
+ found_any |= intel_vsec_get_features(pdev, info);
+
+ if (priv->found_caps == info->caps)
+ break;
+
+ if (!run_once) {
+ intel_vsec_skip_missing_dependencies(pdev);
+ run_once = 1;
+ }
+ }
+
+ if (!found_any)
+ return -ENODEV;
+
+ return 0;
+}
+
static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
- struct intel_vsec_platform_info *info;
- bool have_devices = false;
+ const struct intel_vsec_platform_info *info;
+ struct vsec_priv *priv;
int ret;
ret = pcim_enable_device(pdev);
@@ -353,25 +687,52 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id
return ret;
pci_save_state(pdev);
- info = (struct intel_vsec_platform_info *)id->driver_data;
+ info = (const struct intel_vsec_platform_info *)id->driver_data;
if (!info)
return -EINVAL;
- if (intel_vsec_walk_dvsec(pdev, info))
- have_devices = true;
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
- if (intel_vsec_walk_vsec(pdev, info))
- have_devices = true;
+ priv->info = info;
+ pci_set_drvdata(pdev, priv);
- if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) &&
- intel_vsec_walk_header(pdev, info))
- have_devices = true;
+ return intel_vsec_pci_init(pdev);
+}
- if (!have_devices)
+int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info,
+ struct intel_vsec_device *vsec_dev)
+{
+ struct vsec_priv *priv;
+
+ if (!dev_is_pci(vsec_dev->dev))
return -ENODEV;
+ priv = pci_get_drvdata(to_pci_dev(vsec_dev->dev));
+ if (!priv)
+ return -EINVAL;
+
+ priv->plat_info = *plat_info;
+
return 0;
}
+EXPORT_SYMBOL_NS_GPL(intel_vsec_set_mapping, "INTEL_VSEC");
+
+struct oobmsm_plat_info *intel_vsec_get_mapping(struct pci_dev *pdev)
+{
+ struct vsec_priv *priv;
+
+ if (!pci_match_id(intel_vsec_pci_ids, pdev))
+ return ERR_PTR(-EINVAL);
+
+ priv = pci_get_drvdata(pdev);
+ if (!priv)
+ return ERR_PTR(-EINVAL);
+
+ return &priv->plat_info;
+}
+EXPORT_SYMBOL_NS_GPL(intel_vsec_get_mapping, "INTEL_VSEC");
/* DG1 info */
static struct intel_vsec_header dg1_header = {
@@ -399,14 +760,26 @@ static const struct intel_vsec_platform_info mtl_info = {
.caps = VSEC_CAP_TELEMETRY,
};
+static const struct vsec_feature_dependency oobmsm_deps[] = {
+ {
+ .feature = VSEC_CAP_TELEMETRY,
+ .supplier_bitmap = VSEC_CAP_DISCOVERY | VSEC_CAP_TPMI,
+ },
+};
+
/* OOBMSM info */
static const struct intel_vsec_platform_info oobmsm_info = {
- .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI,
+ .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_SDSI | VSEC_CAP_TPMI |
+ VSEC_CAP_DISCOVERY,
+ .deps = oobmsm_deps,
+ .num_deps = ARRAY_SIZE(oobmsm_deps),
};
/* DMR OOBMSM info */
static const struct intel_vsec_platform_info dmr_oobmsm_info = {
- .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_TPMI,
+ .caps = VSEC_CAP_TELEMETRY | VSEC_CAP_TPMI | VSEC_CAP_DISCOVERY,
+ .deps = oobmsm_deps,
+ .num_deps = ARRAY_SIZE(oobmsm_deps),
};
/* TGL info */
@@ -430,6 +803,8 @@ static const struct intel_vsec_platform_info lnl_info = {
#define PCI_DEVICE_ID_INTEL_VSEC_TGL 0x9a0d
#define PCI_DEVICE_ID_INTEL_VSEC_LNL_M 0x647d
#define PCI_DEVICE_ID_INTEL_VSEC_PTL 0xb07d
+#define PCI_DEVICE_ID_INTEL_VSEC_WCL 0xfd7d
+#define PCI_DEVICE_ID_INTEL_VSEC_NVL 0xd70d
static const struct pci_device_id intel_vsec_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, VSEC_ADL, &tgl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_DG1, &dg1_info) },
@@ -441,6 +816,8 @@ static const struct pci_device_id intel_vsec_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, VSEC_TGL, &tgl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_LNL_M, &lnl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_PTL, &mtl_info) },
+ { PCI_DEVICE_DATA(INTEL, VSEC_WCL, &mtl_info) },
+ { PCI_DEVICE_DATA(INTEL, VSEC_NVL, &mtl_info) },
{ }
};
MODULE_DEVICE_TABLE(pci, intel_vsec_pci_ids);
@@ -464,7 +841,6 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev)
{
struct intel_vsec_device *intel_vsec_dev;
pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT;
- const struct pci_device_id *pci_dev_id;
unsigned long index;
dev_info(&pdev->dev, "Resetting PCI slot\n");
@@ -480,15 +856,13 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev)
xa_for_each(&auxdev_array, index, intel_vsec_dev) {
/* check if pdev doesn't match */
- if (pdev != intel_vsec_dev->pcidev)
+ if (&pdev->dev != intel_vsec_dev->dev)
continue;
devm_release_action(&pdev->dev, intel_vsec_remove_aux,
&intel_vsec_dev->auxdev);
}
- pci_disable_device(pdev);
pci_restore_state(pdev);
- pci_dev_id = pci_match_id(intel_vsec_pci_ids, pdev);
- intel_vsec_pci_probe(pdev, pci_dev_id);
+ intel_vsec_pci_init(pdev);
out:
return status;
diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c
index 5c383a27bbe8..16fd7aa41f20 100644
--- a/drivers/platform/x86/intel/vsec_tpmi.c
+++ b/drivers/platform/x86/intel/vsec_tpmi.c
@@ -46,6 +46,7 @@
* provided by the Intel VSEC driver.
*/
+#include <linux/align.h>
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/debugfs.h>
@@ -55,6 +56,7 @@
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
+#include <linux/notifier.h>
#include <linux/pci.h>
#include <linux/security.h>
#include <linux/sizes.h>
@@ -116,7 +118,7 @@ struct intel_tpmi_info {
struct intel_vsec_device *vsec_dev;
int feature_count;
u64 pfs_start;
- struct intel_tpmi_plat_info plat_info;
+ struct oobmsm_plat_info plat_info;
void __iomem *tpmi_control_mem;
struct dentry *dbgfs_dir;
};
@@ -187,7 +189,21 @@ struct tpmi_feature_state {
/* Used during auxbus device creation */
static DEFINE_IDA(intel_vsec_tpmi_ida);
-struct intel_tpmi_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev)
+static BLOCKING_NOTIFIER_HEAD(tpmi_notify_list);
+
+int tpmi_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&tpmi_notify_list, nb);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_register_notifier, "INTEL_TPMI");
+
+int tpmi_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&tpmi_notify_list, nb);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_unregister_notifier, "INTEL_TPMI");
+
+struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev)
{
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
@@ -479,6 +495,9 @@ static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t l
addr = array[2];
value = array[3];
+ if (!IS_ALIGNED(addr, sizeof(u32)))
+ return -EINVAL;
+
if (punit >= pfs->pfs_header.num_entries) {
ret = -EINVAL;
goto exit_write;
@@ -530,7 +549,7 @@ static const struct file_operations mem_write_ops = {
.release = single_release,
};
-#define tpmi_to_dev(info) (&info->vsec_dev->pcidev->dev)
+#define tpmi_to_dev(info) ((info)->vsec_dev->dev)
static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info)
{
@@ -622,11 +641,11 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
if (!name)
return -EOPNOTSUPP;
- res = kcalloc(pfs->pfs_header.num_entries, sizeof(*res), GFP_KERNEL);
+ res = kzalloc_objs(*res, pfs->pfs_header.num_entries);
if (!res)
return -ENOMEM;
- feature_vsec_dev = kzalloc(sizeof(*feature_vsec_dev), GFP_KERNEL);
+ feature_vsec_dev = kzalloc_obj(*feature_vsec_dev);
if (!feature_vsec_dev) {
kfree(res);
return -ENOMEM;
@@ -642,7 +661,7 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
tmp->flags = IORESOURCE_MEM;
}
- feature_vsec_dev->pcidev = vsec_dev->pcidev;
+ feature_vsec_dev->dev = vsec_dev->dev;
feature_vsec_dev->resource = res;
feature_vsec_dev->num_resources = pfs->pfs_header.num_entries;
feature_vsec_dev->priv_data = &tpmi_info->plat_info;
@@ -655,7 +674,7 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
* feature_vsec_dev and res memory are also freed as part of
* device deletion.
*/
- return intel_vsec_add_aux(vsec_dev->pcidev, &vsec_dev->auxdev.dev,
+ return intel_vsec_add_aux(&vsec_dev->auxdev.dev,
feature_vsec_dev, feature_id_name);
}
@@ -742,7 +761,7 @@ static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, i
static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
{
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
- struct pci_dev *pci_dev = vsec_dev->pcidev;
+ struct pci_dev *pci_dev = to_pci_dev(vsec_dev->dev);
struct intel_tpmi_info *tpmi_info;
u64 pfs_start = 0;
int ret, i;
@@ -799,6 +818,10 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
ret = tpmi_process_info(tpmi_info, pfs);
if (ret)
return ret;
+
+ ret = intel_vsec_set_mapping(&tpmi_info->plat_info, vsec_dev);
+ if (ret)
+ return ret;
}
if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID)
@@ -809,10 +832,6 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
auxiliary_set_drvdata(auxdev, 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
@@ -822,6 +841,14 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO))
tpmi_dbgfs_register(tpmi_info);
+ ret = tpmi_create_devices(tpmi_info);
+ if (ret) {
+ debugfs_remove_recursive(tpmi_info->dbgfs_dir);
+ return ret;
+ }
+
+ blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_INIT, auxdev);
+
return 0;
}
@@ -835,6 +862,8 @@ static void tpmi_remove(struct auxiliary_device *auxdev)
{
struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(auxdev);
+ blocking_notifier_call_chain(&tpmi_notify_list, TPMI_CORE_EXIT, auxdev);
+
debugfs_remove_recursive(tpmi_info->dbgfs_dir);
}
diff --git a/drivers/platform/x86/intel/wmi/sbl-fw-update.c b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
index 75c82c08117f..62c9c7f1842b 100644
--- a/drivers/platform/x86/intel/wmi/sbl-fw-update.c
+++ b/drivers/platform/x86/intel/wmi/sbl-fw-update.c
@@ -14,7 +14,6 @@
* https://slimbootloader.github.io/security/firmware-update.html
*/
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/slab.h>
@@ -25,41 +24,30 @@
static int get_fwu_request(struct device *dev, u32 *out)
{
- union acpi_object *obj;
-
- obj = wmidev_block_query(to_wmi_device(dev), 0);
- if (!obj)
- return -ENODEV;
+ struct wmi_buffer buffer;
+ __le32 *result;
+ int ret;
- if (obj->type != ACPI_TYPE_INTEGER) {
- dev_warn(dev, "wmidev_block_query returned invalid value\n");
- kfree(obj);
- return -EINVAL;
- }
+ ret = wmidev_query_block(to_wmi_device(dev), 0, &buffer, sizeof(*result));
+ if (ret < 0)
+ return ret;
- *out = obj->integer.value;
- kfree(obj);
+ result = buffer.data;
+ *out = le32_to_cpu(*result);
+ kfree(result);
return 0;
}
static int set_fwu_request(struct device *dev, u32 in)
{
- struct acpi_buffer input;
- acpi_status status;
- u32 value;
+ __le32 value = cpu_to_le32(in);
+ struct wmi_buffer buffer = {
+ .length = sizeof(value),
+ .data = &value,
+ };
- value = in;
- input.length = sizeof(u32);
- input.pointer = &value;
-
- status = wmidev_block_set(to_wmi_device(dev), 0, &input);
- if (ACPI_FAILURE(status)) {
- dev_err(dev, "wmidev_block_set failed\n");
- return -ENODEV;
- }
-
- return 0;
+ return wmidev_set_block(to_wmi_device(dev), 0, &buffer);
}
static ssize_t firmware_update_request_show(struct device *dev,
diff --git a/drivers/platform/x86/intel/wmi/thunderbolt.c b/drivers/platform/x86/intel/wmi/thunderbolt.c
index 08df560a2c7a..9b1920d61674 100644
--- a/drivers/platform/x86/intel/wmi/thunderbolt.c
+++ b/drivers/platform/x86/intel/wmi/thunderbolt.c
@@ -7,9 +7,9 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/fs.h>
+#include <linux/hex.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
@@ -23,24 +23,21 @@ static ssize_t force_power_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
- struct acpi_buffer input;
- acpi_status status;
+ struct wmi_buffer buffer;
+ int ret;
u8 mode;
- input.length = sizeof(u8);
- input.pointer = &mode;
+ buffer.length = sizeof(mode);
+ buffer.data = &mode;
+
mode = hex_to_bin(buf[0]);
- dev_dbg(dev, "force_power: storing %#x\n", mode);
- if (mode == 0 || mode == 1) {
- status = wmidev_evaluate_method(to_wmi_device(dev), 0, 1, &input, NULL);
- if (ACPI_FAILURE(status)) {
- dev_dbg(dev, "force_power: failed to evaluate ACPI method\n");
- return -ENODEV;
- }
- } else {
- dev_dbg(dev, "force_power: unsupported mode\n");
+ if (mode > 1)
return -EINVAL;
- }
+
+ ret = wmidev_invoke_procedure(to_wmi_device(dev), 0, 1, &buffer);
+ if (ret < 0)
+ return ret;
+
return count;
}
diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c
index 79a7b68c7373..b1b2d9caba7b 100644
--- a/drivers/platform/x86/intel_ips.c
+++ b/drivers/platform/x86/intel_ips.c
@@ -370,7 +370,7 @@ static void ips_cpu_raise(struct ips_driver *ips)
if (!ips->cpu_turbo_enabled)
return;
- rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
cur_tdp_limit = turbo_override & TURBO_TDP_MASK;
new_tdp_limit = cur_tdp_limit + 8; /* 1W increase */
@@ -382,12 +382,12 @@ static void ips_cpu_raise(struct ips_driver *ips)
thm_writew(THM_MPCPC, (new_tdp_limit * 10) / 8);
turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN;
- wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ wrmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
turbo_override &= ~TURBO_TDP_MASK;
turbo_override |= new_tdp_limit;
- wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ wrmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
}
/**
@@ -405,7 +405,7 @@ static void ips_cpu_lower(struct ips_driver *ips)
u64 turbo_override;
u16 cur_limit, new_limit;
- rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
cur_limit = turbo_override & TURBO_TDP_MASK;
new_limit = cur_limit - 8; /* 1W decrease */
@@ -417,12 +417,12 @@ static void ips_cpu_lower(struct ips_driver *ips)
thm_writew(THM_MPCPC, (new_limit * 10) / 8);
turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN;
- wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ wrmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
turbo_override &= ~TURBO_TDP_MASK;
turbo_override |= new_limit;
- wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ wrmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
}
/**
@@ -437,10 +437,10 @@ static void do_enable_cpu_turbo(void *data)
{
u64 perf_ctl;
- rdmsrl(IA32_PERF_CTL, perf_ctl);
+ rdmsrq(IA32_PERF_CTL, perf_ctl);
if (perf_ctl & IA32_PERF_TURBO_DIS) {
perf_ctl &= ~IA32_PERF_TURBO_DIS;
- wrmsrl(IA32_PERF_CTL, perf_ctl);
+ wrmsrq(IA32_PERF_CTL, perf_ctl);
}
}
@@ -475,10 +475,10 @@ static void do_disable_cpu_turbo(void *data)
{
u64 perf_ctl;
- rdmsrl(IA32_PERF_CTL, perf_ctl);
+ rdmsrq(IA32_PERF_CTL, perf_ctl);
if (!(perf_ctl & IA32_PERF_TURBO_DIS)) {
perf_ctl |= IA32_PERF_TURBO_DIS;
- wrmsrl(IA32_PERF_CTL, perf_ctl);
+ wrmsrq(IA32_PERF_CTL, perf_ctl);
}
}
@@ -934,7 +934,7 @@ static u32 calc_avg_power(struct ips_driver *ips, u32 *array)
static void monitor_timeout(struct timer_list *t)
{
- struct ips_driver *ips = from_timer(ips, t, timer);
+ struct ips_driver *ips = timer_container_of(ips, t, timer);
wake_up_process(ips->monitor);
}
@@ -1108,7 +1108,7 @@ static int ips_monitor(void *data)
last_sample_period = 1;
} while (!kthread_should_stop());
- del_timer_sync(&ips->timer);
+ timer_delete_sync(&ips->timer);
dev_dbg(ips->dev, "ips-monitor thread stopped\n");
@@ -1215,7 +1215,7 @@ static int cpu_clamp_show(struct seq_file *m, void *data)
u64 turbo_override;
int tdp, tdc;
- rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
tdp = (int)(turbo_override & TURBO_TDP_MASK);
tdc = (int)((turbo_override & TURBO_TDC_MASK) >> TURBO_TDC_SHIFT);
@@ -1290,7 +1290,7 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips)
return NULL;
}
- rdmsrl(IA32_MISC_ENABLE, misc_en);
+ rdmsrq(IA32_MISC_ENABLE, misc_en);
/*
* If the turbo enable bit isn't set, we shouldn't try to enable/disable
* turbo manually or we'll get an illegal MSR access, even though
@@ -1312,7 +1312,7 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips)
return NULL;
}
- rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_power);
+ rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_power);
tdp = turbo_power & TURBO_TDP_MASK;
/* Sanity check TDP against CPU */
@@ -1496,7 +1496,7 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id)
* Check PLATFORM_INFO MSR to make sure this chip is
* turbo capable.
*/
- rdmsrl(PLATFORM_INFO, platform_info);
+ rdmsrq(PLATFORM_INFO, platform_info);
if (!(platform_info & PLATFORM_TDP)) {
dev_err(&dev->dev, "platform indicates TDP override unavailable, aborting\n");
return -ENODEV;
@@ -1529,7 +1529,7 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id)
ips->mgta_val = thm_readw(THM_MGTA);
/* Save turbo limits & ratios */
- rdmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
+ rdmsrq(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
ips_disable_cpu_turbo(ips);
ips->cpu_turbo_enabled = false;
@@ -1596,10 +1596,10 @@ static void ips_remove(struct pci_dev *dev)
if (ips->gpu_turbo_disable)
symbol_put(i915_gpu_turbo_disable);
- rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
turbo_override &= ~(TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN);
- wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override);
- wrmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
+ wrmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+ wrmsrq(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
free_irq(ips->irq, ips);
pci_free_irq_vectors(dev);
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c
index 3acf6149a9ec..a2472ad10f37 100644
--- a/drivers/platform/x86/intel_scu_ipc.c
+++ b/drivers/platform/x86/intel_scu_ipc.c
@@ -573,7 +573,7 @@ __intel_scu_ipc_register(struct device *parent,
if (ipcdev)
return ERR_PTR(-EBUSY);
- scu = kzalloc(sizeof(*scu), GFP_KERNEL);
+ scu = kzalloc_obj(*scu);
if (!scu)
return ERR_PTR(-ENOMEM);
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
new file mode 100644
index 000000000000..09b1b055d2e0
--- /dev/null
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -0,0 +1,276 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Lenovo X86 Platform Specific Drivers
+#
+
+config IDEAPAD_LAPTOP
+ tristate "Lenovo IdeaPad Laptop Extras"
+ depends on ACPI
+ depends on ACPI_BATTERY
+ depends on RFKILL && INPUT
+ depends on SERIO_I8042
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
+ depends on ACPI_WMI || ACPI_WMI = n
+ select ACPI_PLATFORM_PROFILE
+ select INPUT_SPARSEKMAP
+ select NEW_LEDS
+ select LEDS_CLASS
+ help
+ This is a driver for Lenovo IdeaPad netbooks contains drivers for
+ rfkill switch, hotkey, fan control and backlight control.
+
+config LENOVO_WMI_HOTKEY_UTILITIES
+ tristate "Lenovo Hotkey Utility WMI extras driver"
+ depends on ACPI_WMI
+ select NEW_LEDS
+ select LEDS_CLASS
+ imply IDEAPAD_LAPTOP
+ help
+ This driver provides WMI support for Lenovo customized hotkeys function,
+ such as LED control for audio/mic mute event for Ideapad, YOGA, XiaoXin,
+ Gaming, ThinkBook and so on.
+
+config LENOVO_WMI_CAMERA
+ tristate "Lenovo WMI Camera Button driver"
+ depends on ACPI_WMI
+ depends on INPUT
+ help
+ This driver provides support for Lenovo camera button. The Camera
+ button is a GPIO device. This driver receives ACPI notifications when
+ the camera button is switched on/off.
+
+ To compile this driver as a module, choose M here: the module
+ will be called lenovo-wmi-camera.
+
+config LENOVO_YMC
+ tristate "Lenovo Yoga Tablet Mode Control"
+ depends on ACPI_WMI
+ depends on INPUT
+ depends on IDEAPAD_LAPTOP
+ select INPUT_SPARSEKMAP
+ help
+ This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input
+ events for Lenovo Yoga notebooks.
+
+config THINKPAD_ACPI
+ tristate "ThinkPad ACPI Laptop Extras"
+ depends on ACPI_EC
+ depends on ACPI_BATTERY
+ depends on INPUT
+ depends on RFKILL || RFKILL = n
+ depends on ACPI_VIDEO || ACPI_VIDEO = n
+ depends on BACKLIGHT_CLASS_DEVICE
+ depends on I2C
+ depends on DRM
+ select ACPI_PLATFORM_PROFILE
+ select DRM_PRIVACY_SCREEN
+ select HWMON
+ select NVRAM
+ select NEW_LEDS
+ select LEDS_CLASS
+ select INPUT_SPARSEKMAP
+ help
+ This is a driver for the IBM and Lenovo ThinkPad laptops. It adds
+ support for Fn-Fx key combinations, Bluetooth control, video
+ output switching, ThinkLight control, UltraBay eject and more.
+ For more information about this driver see
+ <file:Documentation/admin-guide/laptops/thinkpad-acpi.rst> and
+ <http://ibm-acpi.sf.net/> .
+
+ This driver was formerly known as ibm-acpi.
+
+ Extra functionality will be available if the rfkill (CONFIG_RFKILL)
+ and/or ALSA (CONFIG_SND) subsystems are available in the kernel.
+ Note that if you want ThinkPad-ACPI to be built-in instead of
+ modular, ALSA and rfkill will also have to be built-in.
+
+ If you have an IBM or Lenovo ThinkPad laptop, say Y or M here.
+
+config THINKPAD_ACPI_ALSA_SUPPORT
+ bool "Console audio control ALSA interface"
+ depends on THINKPAD_ACPI
+ depends on SND
+ depends on SND = y || THINKPAD_ACPI = SND
+ default y
+ help
+ Enables monitoring of the built-in console audio output control
+ (headphone and speakers), which is operated by the mute and (in
+ some ThinkPad models) volume hotkeys.
+
+ If this option is enabled, ThinkPad-ACPI will export an ALSA card
+ with a single read-only mixer control, which should be used for
+ on-screen-display feedback purposes by the Desktop Environment.
+
+ Optionally, the driver will also allow software control (the
+ ALSA mixer will be made read-write). Please refer to the driver
+ documentation for details.
+
+ All IBM models have both volume and mute control. Newer Lenovo
+ models only have mute control (the volume hotkeys are just normal
+ keys and volume control is done through the main HDA mixer).
+
+config THINKPAD_ACPI_DEBUGFACILITIES
+ bool "Maintainer debug facilities"
+ depends on THINKPAD_ACPI
+ help
+ Enables extra stuff in the thinkpad-acpi which is completely useless
+ for normal use. Read the driver source to find out what it does.
+
+ Say N here, unless you were told by a kernel maintainer to do
+ otherwise.
+
+config THINKPAD_ACPI_DEBUG
+ bool "Verbose debug mode"
+ depends on THINKPAD_ACPI
+ help
+ Enables extra debugging information, at the expense of a slightly
+ increase in driver size.
+
+ If you are not sure, say N here.
+
+config THINKPAD_ACPI_UNSAFE_LEDS
+ bool "Allow control of important LEDs (unsafe)"
+ depends on THINKPAD_ACPI
+ help
+ Overriding LED state on ThinkPads can mask important
+ firmware alerts (like critical battery condition), or misled
+ the user into damaging the hardware (undocking or ejecting
+ the bay while buses are still active), etc.
+
+ LED control on the ThinkPad is write-only (with very few
+ exceptions on very ancient models), which makes it
+ impossible to know beforehand if important information will
+ be lost when one changes LED state.
+
+ Users that know what they are doing can enable this option
+ and the driver will allow control of every LED, including
+ the ones on the dock stations.
+
+ Never enable this option on a distribution kernel.
+
+ Say N here, unless you are building a kernel for your own
+ use, and need to control the important firmware LEDs.
+
+config THINKPAD_ACPI_VIDEO
+ bool "Video output control support"
+ depends on THINKPAD_ACPI
+ default y
+ help
+ Allows the thinkpad_acpi driver to provide an interface to control
+ the various video output ports.
+
+ This feature often won't work well, depending on ThinkPad model,
+ display state, video output devices in use, whether there is a X
+ server running, phase of the moon, and the current mood of
+ Schroedinger's cat. If you can use X.org's RandR to control
+ your ThinkPad's video output ports instead of this feature,
+ don't think twice: do it and say N here to save memory and avoid
+ bad interactions with X.org.
+
+ NOTE: access to this feature is limited to processes with the
+ CAP_SYS_ADMIN capability, to avoid local DoS issues in platforms
+ where it interacts badly with X.org.
+
+ If you are not sure, say Y here but do try to check if you could
+ be using X.org RandR instead.
+
+config THINKPAD_ACPI_HOTKEY_POLL
+ bool "Support NVRAM polling for hot keys"
+ depends on THINKPAD_ACPI
+ default y
+ help
+ Some thinkpad models benefit from NVRAM polling to detect a few of
+ the hot key press events. If you know your ThinkPad model does not
+ need to do NVRAM polling to support any of the hot keys you use,
+ unselecting this option will save about 1kB of memory.
+
+ ThinkPads T40 and newer, R52 and newer, and X31 and newer are
+ unlikely to need NVRAM polling in their latest BIOS versions.
+
+ NVRAM polling can detect at most the following keys: ThinkPad/Access
+ IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute,
+ Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12).
+
+ If you are not sure, say Y here. The driver enables polling only if
+ it is strictly necessary to do so.
+
+config THINKPAD_LMI
+ tristate "Lenovo WMI-based systems management driver"
+ depends on ACPI_WMI
+ depends on DMI
+ select FW_ATTR_CLASS
+ help
+ This driver allows changing BIOS settings on Lenovo machines whose
+ BIOS support the WMI interface.
+
+ To compile this driver as a module, choose M here: the module will
+ be called think-lmi.
+
+config YOGABOOK
+ tristate "Lenovo Yoga Book tablet key driver"
+ depends on ACPI_WMI
+ depends on INPUT
+ depends on I2C
+ select LEDS_CLASS
+ select NEW_LEDS
+ help
+ Say Y here if you want to support the 'Pen' key and keyboard backlight
+ control on the Lenovo Yoga Book tablets.
+
+ To compile this driver as a module, choose M here: the module will
+ be called lenovo-yogabook.
+
+config YT2_1380
+ tristate "Lenovo Yoga Tablet 2 1380 fast charge driver"
+ depends on SERIAL_DEV_BUS
+ depends on EXTCON
+ depends on ACPI
+ help
+ Say Y here to enable support for the custom fast charging protocol
+ found on the Lenovo Yoga Tablet 2 1380F / 1380L models.
+
+ To compile this driver as a module, choose M here: the module will
+ be called lenovo-yogabook.
+
+config LENOVO_WMI_CAPDATA
+ tristate
+ depends on ACPI_WMI
+
+config LENOVO_WMI_EVENTS
+ tristate
+ depends on ACPI_WMI
+
+config LENOVO_WMI_HELPERS
+ tristate
+ depends on ACPI_WMI
+
+config LENOVO_WMI_GAMEZONE
+ tristate "Lenovo GameZone WMI Driver"
+ depends on ACPI_WMI
+ depends on DMI
+ select ACPI_PLATFORM_PROFILE
+ select LENOVO_WMI_EVENTS
+ select LENOVO_WMI_HELPERS
+ help
+ Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
+ platform-profile firmware interface to manage power usage.
+
+ To compile this driver as a module, choose M here: the module will
+ be called lenovo-wmi-gamezone.
+
+config LENOVO_WMI_TUNING
+ tristate "Lenovo Other Mode WMI Driver"
+ depends on ACPI_WMI
+ select HWMON
+ select FW_ATTR_CLASS
+ select LENOVO_WMI_CAPDATA
+ select LENOVO_WMI_EVENTS
+ select LENOVO_WMI_HELPERS
+ help
+ Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
+ firmware_attributes API to control various tunable settings typically exposed by
+ Lenovo software in Windows.
+
+ To compile this driver as a module, choose M here: the module will
+ be called lenovo-wmi-other.
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
new file mode 100644
index 000000000000..91a9370f11b3
--- /dev/null
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/x86/lenovo
+# Lenovo x86 Platform Specific Drivers
+#
+obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
+obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
+obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
+
+lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
+lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
+lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
+lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o
+lenovo-target-$(CONFIG_LENOVO_WMI_CAPDATA) += wmi-capdata.o
+lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o
+lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o
+lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o
+lenovo-target-$(CONFIG_LENOVO_WMI_TUNING) += wmi-other.o
+
+# Add 'lenovo' prefix to each module listed in lenovo-target-*
+define LENOVO_OBJ_TARGET
+lenovo-$(1)-y := $(1).o
+obj-$(2) += lenovo-$(1).o
+endef
+
+$(foreach target, $(basename $(lenovo-target-y)), $(eval $(call LENOVO_OBJ_TARGET,$(target),y)))
+$(foreach target, $(basename $(lenovo-target-m)), $(eval $(call LENOVO_OBJ_TARGET,$(target),m)))
diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 17a09b7784ed..4fbc904f1fc3 100644
--- a/drivers/platform/x86/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -15,6 +15,7 @@
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/i8042.h>
@@ -27,13 +28,16 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
+#include <linux/power_supply.h>
#include <linux/rfkill.h>
#include <linux/seq_file.h>
+#include <linux/string_choices.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "ideapad-laptop.h"
+#include <acpi/battery.h>
#include <acpi/video.h>
#include <dt-bindings/leds/common.h>
@@ -59,13 +63,27 @@ enum {
CFG_OSD_CAM_BIT = 31,
};
+/*
+ * There are two charge modes supported by the GBMD/SBMC interface:
+ * - "Rapid Charge": increase power to speed up charging
+ * - "Conservation Mode": stop charging at 60-80% (depends on model)
+ *
+ * The interface doesn't prohibit enabling both modes at the same time.
+ * However, doing so is essentially meaningless, and the manufacturer utilities
+ * on Windows always make them mutually exclusive.
+ */
+
enum {
+ GBMD_RAPID_CHARGE_STATE_BIT = 2,
GBMD_CONSERVATION_STATE_BIT = 5,
+ GBMD_RAPID_CHARGE_SUPPORTED_BIT = 17,
};
enum {
SBMC_CONSERVATION_ON = 3,
SBMC_CONSERVATION_OFF = 5,
+ SBMC_RAPID_CHARGE_ON = 7,
+ SBMC_RAPID_CHARGE_OFF = 8,
};
enum {
@@ -155,6 +173,7 @@ struct ideapad_rfk_priv {
struct ideapad_private {
struct acpi_device *adev;
struct mutex vpc_mutex; /* protects the VPC calls */
+ struct mutex gbmd_sbmc_mutex; /* protects GBMD/SBMC calls */
struct rfkill *rfk[IDEAPAD_RFKILL_DEV_NUM];
struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM];
struct platform_device *platform_device;
@@ -162,9 +181,12 @@ struct ideapad_private {
struct backlight_device *blightdev;
struct ideapad_dytc_priv *dytc;
struct dentry *debug;
+ struct acpi_battery_hook battery_hook;
+ const struct power_supply_ext *battery_ext;
unsigned long cfg;
unsigned long r_touchpad_val;
struct {
+ bool rapid_charge : 1;
bool conservation_mode : 1;
bool dytc : 1;
bool fan_mode : 1;
@@ -197,38 +219,32 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth.");
static bool allow_v4_dytc;
module_param(allow_v4_dytc, bool, 0444);
MODULE_PARM_DESC(allow_v4_dytc,
- "Enable DYTC version 4 platform-profile support. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable DYTC version 4 platform-profile support. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool hw_rfkill_switch;
module_param(hw_rfkill_switch, bool, 0444);
MODULE_PARM_DESC(hw_rfkill_switch,
- "Enable rfkill support for laptops with a hw on/off wifi switch/slider. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable rfkill support for laptops with a hw on/off wifi switch/slider. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool set_fn_lock_led;
module_param(set_fn_lock_led, bool, 0444);
MODULE_PARM_DESC(set_fn_lock_led,
- "Enable driver based updates of the fn-lock LED on fn-lock changes. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable driver based updates of the fn-lock LED on fn-lock changes. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool ctrl_ps2_aux_port;
module_param(ctrl_ps2_aux_port, bool, 0444);
MODULE_PARM_DESC(ctrl_ps2_aux_port,
- "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable driver based PS/2 aux port en-/dis-abling on touchpad on/off toggle. If you need this please report this to: platform-driver-x86@vger.kernel.org");
static bool touchpad_ctrl_via_ec;
module_param(touchpad_ctrl_via_ec, bool, 0444);
MODULE_PARM_DESC(touchpad_ctrl_via_ec,
- "Enable registering a 'touchpad' sysfs-attribute which can be used to manually "
- "tell the EC to enable/disable the touchpad. This may not work on all models.");
+ "Enable registering a 'touchpad' sysfs-attribute which can be used to manually tell the EC to enable/disable the touchpad. This may not work on all models.");
static bool ymc_ec_trigger __read_mostly;
module_param(ymc_ec_trigger, bool, 0444);
MODULE_PARM_DESC(ymc_ec_trigger,
- "Enable EC triggering work-around to force emitting tablet mode events. "
- "If you need this please report this to: platform-driver-x86@vger.kernel.org");
+ "Enable EC triggering work-around to force emitting tablet mode events. If you need this please report this to: platform-driver-x86@vger.kernel.org");
/*
* shared data
@@ -267,6 +283,20 @@ static void ideapad_shared_exit(struct ideapad_private *priv)
*/
#define IDEAPAD_EC_TIMEOUT 200 /* in ms */
+/*
+ * Some models (e.g., ThinkBook since 2024) have a low tolerance for being
+ * polled too frequently. Doing so may break the state machine in the EC,
+ * resulting in a hard shutdown.
+ *
+ * It is also observed that frequent polls may disturb the ongoing operation
+ * and notably delay the availability of EC response.
+ *
+ * These values are used as the delay before the first poll and the interval
+ * between subsequent polls to solve the above issues.
+ */
+#define IDEAPAD_EC_POLL_MIN_US 150
+#define IDEAPAD_EC_POLL_MAX_US 300
+
static int eval_int(acpi_handle handle, const char *name, unsigned long *res)
{
unsigned long long result;
@@ -383,7 +413,7 @@ static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *da
end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1;
while (time_before(jiffies, end_jiffies)) {
- schedule();
+ usleep_range(IDEAPAD_EC_POLL_MIN_US, IDEAPAD_EC_POLL_MAX_US);
err = eval_vpcr(handle, 1, &val);
if (err)
@@ -414,7 +444,7 @@ static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long dat
end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1;
while (time_before(jiffies, end_jiffies)) {
- schedule();
+ usleep_range(IDEAPAD_EC_POLL_MIN_US, IDEAPAD_EC_POLL_MAX_US);
err = eval_vpcr(handle, 1, &val);
if (err)
@@ -437,37 +467,40 @@ static int debugfs_status_show(struct seq_file *s, void *data)
struct ideapad_private *priv = s->private;
unsigned long value;
- guard(mutex)(&priv->vpc_mutex);
-
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value))
- seq_printf(s, "Backlight max: %lu\n", value);
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value))
- seq_printf(s, "Backlight now: %lu\n", value);
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value))
- seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value);
-
- seq_puts(s, "=====================\n");
-
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value))
- seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value);
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value))
- seq_printf(s, "Wifi status: %s (%lu)\n", value ? "on" : "off", value);
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value))
- seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value);
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value))
- seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value);
+ scoped_guard(mutex, &priv->vpc_mutex) {
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value))
+ seq_printf(s, "Backlight max: %lu\n", value);
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value))
+ seq_printf(s, "Backlight now: %lu\n", value);
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value))
+ seq_printf(s, "BL power value: %s (%lu)\n", str_on_off(value), value);
+
+ seq_puts(s, "=====================\n");
+
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value))
+ seq_printf(s, "Radio status: %s (%lu)\n", str_on_off(value), value);
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value))
+ seq_printf(s, "Wifi status: %s (%lu)\n", str_on_off(value), value);
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value))
+ seq_printf(s, "BT status: %s (%lu)\n", str_on_off(value), value);
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value))
+ seq_printf(s, "3G status: %s (%lu)\n", str_on_off(value), value);
+
+ seq_puts(s, "=====================\n");
+
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value))
+ seq_printf(s, "Touchpad status: %s (%lu)\n", str_on_off(value), value);
+ if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value))
+ seq_printf(s, "Camera status: %s (%lu)\n", str_on_off(value), value);
+ }
seq_puts(s, "=====================\n");
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value))
- seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value);
- if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value))
- seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value);
-
- seq_puts(s, "=====================\n");
+ scoped_guard(mutex, &priv->gbmd_sbmc_mutex) {
+ if (!eval_gbmd(priv->adev->handle, &value))
+ seq_printf(s, "GBMD: %#010lx\n", value);
+ }
- if (!eval_gbmd(priv->adev->handle, &value))
- seq_printf(s, "GBMD: %#010lx\n", value);
if (!eval_hals(priv->adev->handle, &value))
seq_printf(s, "HALS: %#010lx\n", value);
@@ -589,6 +622,11 @@ static ssize_t camera_power_store(struct device *dev,
static DEVICE_ATTR_RW(camera_power);
+static void show_conservation_mode_deprecation_warning(struct device *dev)
+{
+ dev_warn_once(dev, "conservation_mode attribute has been deprecated, see charge_types.\n");
+}
+
static ssize_t conservation_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
@@ -597,10 +635,18 @@ static ssize_t conservation_mode_show(struct device *dev,
unsigned long result;
int err;
- err = eval_gbmd(priv->adev->handle, &result);
- if (err)
- return err;
+ show_conservation_mode_deprecation_warning(dev);
+ scoped_guard(mutex, &priv->gbmd_sbmc_mutex) {
+ err = eval_gbmd(priv->adev->handle, &result);
+ if (err)
+ return err;
+ }
+
+ /*
+ * For backward compatibility, ignore Rapid Charge while reporting the
+ * state of Conservation Mode.
+ */
return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result));
}
@@ -612,10 +658,24 @@ static ssize_t conservation_mode_store(struct device *dev,
bool state;
int err;
+ show_conservation_mode_deprecation_warning(dev);
+
err = kstrtobool(buf, &state);
if (err)
return err;
+ guard(mutex)(&priv->gbmd_sbmc_mutex);
+
+ /*
+ * Prevent mutually exclusive modes from being set at the same time,
+ * but do not disable Rapid Charge while disabling Conservation Mode.
+ */
+ if (priv->features.rapid_charge && state) {
+ err = exec_sbmc(priv->adev->handle, SBMC_RAPID_CHARGE_OFF);
+ if (err)
+ return err;
+ }
+
err = exec_sbmc(priv->adev->handle, state ? SBMC_CONSERVATION_ON : SBMC_CONSERVATION_OFF);
if (err)
return err;
@@ -1112,7 +1172,7 @@ static int ideapad_dytc_profile_init(struct ideapad_private *priv)
return -ENODEV;
}
- priv->dytc = kzalloc(sizeof(*priv->dytc), GFP_KERNEL);
+ priv->dytc = kzalloc_obj(*priv->dytc);
if (!priv->dytc)
return -ENOMEM;
@@ -1294,6 +1354,16 @@ static const struct key_entry ideapad_keymap[] = {
/* Specific to some newer models */
{ KE_KEY, 0x3e | IDEAPAD_WMI_KEY, { KEY_MICMUTE } },
{ KE_KEY, 0x3f | IDEAPAD_WMI_KEY, { KEY_RFKILL } },
+ /* Star- (User Assignable Key) */
+ { KE_KEY, 0x44 | IDEAPAD_WMI_KEY, { KEY_PROG1 } },
+ /* Eye */
+ { KE_KEY, 0x45 | IDEAPAD_WMI_KEY, { KEY_PROG3 } },
+ /* Performance toggle also Fn+Q, handled inside ideapad_wmi_notify() */
+ { KE_KEY, 0x3d | IDEAPAD_WMI_KEY, { KEY_PROG4 } },
+ /* shift + prtsc */
+ { KE_KEY, 0x2d | IDEAPAD_WMI_KEY, { KEY_SELECTIVE_SCREENSHOT } },
+ { KE_KEY, 0x29 | IDEAPAD_WMI_KEY, { KEY_TOUCHPAD_TOGGLE } },
+ { KE_KEY, 0x2a | IDEAPAD_WMI_KEY, { KEY_ROOT_MENU } },
{ KE_END },
};
@@ -1370,7 +1440,7 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv)
if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value))
return;
- for_each_set_bit (bit, &value, 16) {
+ for_each_set_bit(bit, &value, 16) {
switch (bit) {
case 6: /* Z570 */
case 0: /* Z580 */
@@ -1630,11 +1700,10 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
if (WARN_ON(priv->kbd_bl.initialized))
return -EEXIST;
- if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type)) {
+ if (ideapad_kbd_bl_check_tristate(priv->kbd_bl.type))
priv->kbd_bl.led.max_brightness = 2;
- } else {
+ else
priv->kbd_bl.led.max_brightness = 1;
- }
brightness = ideapad_kbd_bl_brightness_get(priv);
if (brightness < 0)
@@ -1644,7 +1713,7 @@ static int ideapad_kbd_bl_init(struct ideapad_private *priv)
priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT;
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;
+ priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED | LED_RETAIN_AT_SHUTDOWN;
err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led);
if (err)
@@ -1676,7 +1745,7 @@ static enum led_brightness ideapad_fn_lock_led_cdev_get(struct led_classdev *led
}
static int ideapad_fn_lock_led_cdev_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+ enum led_brightness brightness)
{
struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, fn_lock.led);
@@ -1703,7 +1772,7 @@ static int ideapad_fn_lock_led_init(struct ideapad_private *priv)
priv->fn_lock.led.name = "platform::" LED_FUNCTION_FNLOCK;
priv->fn_lock.led.brightness_get = ideapad_fn_lock_led_cdev_get;
priv->fn_lock.led.brightness_set_blocking = ideapad_fn_lock_led_cdev_set;
- priv->fn_lock.led.flags = LED_BRIGHT_HW_CHANGED;
+ priv->fn_lock.led.flags = LED_BRIGHT_HW_CHANGED | LED_RETAIN_AT_SHUTDOWN;
err = led_classdev_register(&priv->platform_device->dev, &priv->fn_lock.led);
if (err)
@@ -1852,7 +1921,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
vpc1 = (vpc2 << 8) | vpc1;
- for_each_set_bit (bit, &vpc1, 16) {
+ for_each_set_bit(bit, &vpc1, 16) {
switch (bit) {
case 13:
case 11:
@@ -1963,10 +2032,142 @@ static const struct dmi_system_id ctrl_ps2_aux_port_list[] = {
{}
};
-static void ideapad_check_features(struct ideapad_private *priv)
+static int ideapad_psy_ext_set_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct ideapad_private *priv = ext_data;
+ unsigned long op1, op2;
+ int err;
+
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_TYPE_FAST:
+ if (WARN_ON(!priv->features.rapid_charge))
+ return -EINVAL;
+
+ op1 = SBMC_CONSERVATION_OFF;
+ op2 = SBMC_RAPID_CHARGE_ON;
+ break;
+ case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
+ op1 = SBMC_RAPID_CHARGE_OFF;
+ op2 = SBMC_CONSERVATION_ON;
+ break;
+ case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
+ op1 = SBMC_RAPID_CHARGE_OFF;
+ op2 = SBMC_CONSERVATION_OFF;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&priv->gbmd_sbmc_mutex);
+
+ /* If !rapid_charge, op1 must be SBMC_RAPID_CHARGE_OFF. Skip it. */
+ if (priv->features.rapid_charge) {
+ err = exec_sbmc(priv->adev->handle, op1);
+ if (err)
+ return err;
+ }
+
+ return exec_sbmc(priv->adev->handle, op2);
+}
+
+static int ideapad_psy_ext_get_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ideapad_private *priv = ext_data;
+ bool is_rapid_charge, is_conservation;
+ unsigned long result;
+ int err;
+
+ scoped_guard(mutex, &priv->gbmd_sbmc_mutex) {
+ err = eval_gbmd(priv->adev->handle, &result);
+ if (err)
+ return err;
+ }
+
+ is_rapid_charge = (priv->features.rapid_charge &&
+ test_bit(GBMD_RAPID_CHARGE_STATE_BIT, &result));
+ is_conservation = test_bit(GBMD_CONSERVATION_STATE_BIT, &result);
+
+ if (unlikely(is_rapid_charge && is_conservation)) {
+ dev_err(&priv->platform_device->dev,
+ "unexpected charge_types: both [Fast] and [Long_Life] are enabled\n");
+ return -EINVAL;
+ }
+
+ if (is_rapid_charge)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else if (is_conservation)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+
+ return 0;
+}
+
+static int ideapad_psy_prop_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp)
+{
+ return true;
+}
+
+static const enum power_supply_property ideapad_power_supply_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPES,
+};
+
+#define DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(_name, _charge_types) \
+ static const struct power_supply_ext _name = { \
+ .name = "ideapad_laptop", \
+ .properties = ideapad_power_supply_props, \
+ .num_properties = ARRAY_SIZE(ideapad_power_supply_props), \
+ .charge_types = _charge_types, \
+ .get_property = ideapad_psy_ext_get_prop, \
+ .set_property = ideapad_psy_ext_set_prop, \
+ .property_is_writeable = ideapad_psy_prop_is_writeable, \
+ }
+
+DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v1,
+ (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
+);
+
+DEFINE_IDEAPAD_POWER_SUPPLY_EXTENSION(ideapad_battery_ext_v2,
+ (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
+);
+
+static int ideapad_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ struct ideapad_private *priv = container_of(hook, struct ideapad_private, battery_hook);
+
+ return power_supply_register_extension(battery, priv->battery_ext,
+ &priv->platform_device->dev, priv);
+}
+
+static int ideapad_battery_remove(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ struct ideapad_private *priv = container_of(hook, struct ideapad_private, battery_hook);
+
+ power_supply_unregister_extension(battery, priv->battery_ext);
+
+ return 0;
+}
+
+static int ideapad_check_features(struct ideapad_private *priv)
{
acpi_handle handle = priv->adev->handle;
unsigned long val;
+ int err;
priv->features.set_fn_lock_led =
set_fn_lock_led || dmi_check_system(set_fn_lock_led_list);
@@ -1981,8 +2182,27 @@ static void ideapad_check_features(struct ideapad_private *priv)
if (!read_ec_data(handle, VPCCMD_R_FAN, &val))
priv->features.fan_mode = true;
- if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC"))
- priv->features.conservation_mode = true;
+ if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
+ /* Not acquiring gbmd_sbmc_mutex as race condition is impossible on init */
+ if (!eval_gbmd(handle, &val)) {
+ priv->features.conservation_mode = true;
+ priv->features.rapid_charge = test_bit(GBMD_RAPID_CHARGE_SUPPORTED_BIT,
+ &val);
+
+ priv->battery_ext = priv->features.rapid_charge
+ ? &ideapad_battery_ext_v2
+ : &ideapad_battery_ext_v1;
+
+ priv->battery_hook.add_battery = ideapad_battery_add;
+ priv->battery_hook.remove_battery = ideapad_battery_remove;
+ priv->battery_hook.name = "Ideapad Battery Extension";
+
+ err = devm_battery_hook_register(&priv->platform_device->dev,
+ &priv->battery_hook);
+ if (err)
+ return err;
+ }
+ }
if (acpi_has_method(handle, "DYTC"))
priv->features.dytc = true;
@@ -2017,6 +2237,8 @@ static void ideapad_check_features(struct ideapad_private *priv)
}
}
}
+
+ return 0;
}
#if IS_ENABLED(CONFIG_ACPI_WMI)
@@ -2080,6 +2302,12 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
dev_dbg(&wdev->dev, "WMI fn-key event: 0x%llx\n",
data->integer.value);
+ /* performance button triggered by 0x3d */
+ if (data->integer.value == 0x3d && priv->dytc) {
+ platform_profile_cycle();
+ break;
+ }
+
/* 0x02 FnLock, 0x03 Esc */
if (data->integer.value == 0x02 || data->integer.value == 0x03)
ideapad_fn_lock_led_notify(priv, data->integer.value == 0x02);
@@ -2112,6 +2340,7 @@ static struct wmi_driver ideapad_wmi_driver = {
.name = "ideapad_wmi",
},
.id_table = ideapad_wmi_ids,
+ .min_event_size = sizeof(u32),
.probe = ideapad_wmi_probe,
.notify = ideapad_wmi_notify,
};
@@ -2159,7 +2388,13 @@ static int ideapad_acpi_add(struct platform_device *pdev)
if (err)
return err;
- ideapad_check_features(priv);
+ err = devm_mutex_init(&pdev->dev, &priv->gbmd_sbmc_mutex);
+ if (err)
+ return err;
+
+ err = ideapad_check_features(priv);
+ if (err)
+ return err;
ideapad_debugfs_init(priv);
diff --git a/drivers/platform/x86/ideapad-laptop.h b/drivers/platform/x86/lenovo/ideapad-laptop.h
index 1e52f2aa0aac..1e52f2aa0aac 100644
--- a/drivers/platform/x86/ideapad-laptop.h
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.h
diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c
index 0fc275e461be..e215e86e3db7 100644
--- a/drivers/platform/x86/think-lmi.c
+++ b/drivers/platform/x86/lenovo/think-lmi.c
@@ -20,7 +20,7 @@
#include <linux/types.h>
#include <linux/dmi.h>
#include <linux/wmi.h>
-#include "firmware_attributes_class.h"
+#include "../firmware_attributes_class.h"
#include "think-lmi.h"
static bool debug_support;
@@ -119,6 +119,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* You must reboot the computer before the changes will take effect.
*/
#define LENOVO_SET_BIOS_CERT_GUID "26861C9F-47E9-44C4-BD8B-DFE7FA2610FE"
+#define LENOVO_TC_SET_BIOS_CERT_GUID "955aaf7d-8bc4-4f04-90aa-97469512f167"
/*
* Name: UpdateBiosCert
@@ -128,6 +129,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* You must reboot the computer before the changes will take effect.
*/
#define LENOVO_UPDATE_BIOS_CERT_GUID "9AA3180A-9750-41F7-B9F7-D5D3B1BAC3CE"
+#define LENOVO_TC_UPDATE_BIOS_CERT_GUID "5f5bbbb2-c72f-4fb8-8129-228eef4fdbed"
/*
* Name: ClearBiosCert
@@ -137,6 +139,8 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* You must reboot the computer before the changes will take effect.
*/
#define LENOVO_CLEAR_BIOS_CERT_GUID "B2BC39A7-78DD-4D71-B059-A510DEC44890"
+#define LENOVO_TC_CLEAR_BIOS_CERT_GUID "97849cb6-cb44-42d1-a750-26a596a9eec4"
+
/*
* Name: CertToPassword
* Description: Switch from certificate to password authentication.
@@ -145,6 +149,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* You must reboot the computer before the changes will take effect.
*/
#define LENOVO_CERT_TO_PASSWORD_GUID "0DE8590D-5510-4044-9621-77C227F5A70D"
+#define LENOVO_TC_CERT_TO_PASSWORD_GUID "ef65480d-38c9-420d-b700-ab3d6c8ebaca"
/*
* Name: SetBiosSettingCert
@@ -153,6 +158,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* Format: "Item,Value,Signature"
*/
#define LENOVO_SET_BIOS_SETTING_CERT_GUID "34A008CC-D205-4B62-9E67-31DFA8B90003"
+#define LENOVO_TC_SET_BIOS_SETTING_CERT_GUID "19ecba3b-b318-4192-a89b-43d94bc60cea"
/*
* Name: SaveBiosSettingCert
@@ -161,6 +167,7 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
* Format: "Signature"
*/
#define LENOVO_SAVE_BIOS_SETTING_CERT_GUID "C050FB9D-DF5F-4606-B066-9EFC401B2551"
+#define LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID "0afaf46f-7cca-450a-b455-a826a0bf1af5"
/*
* Name: CertThumbprint
@@ -177,12 +184,43 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
#define TLMI_CERT_SVC BIT(7) /* Admin Certificate Based */
#define TLMI_CERT_SMC BIT(8) /* System Certificate Based */
+static const struct tlmi_cert_guids thinkpad_cert_guid = {
+ .thumbprint = LENOVO_CERT_THUMBPRINT_GUID,
+ .set_bios_setting = LENOVO_SET_BIOS_SETTING_CERT_GUID,
+ .save_bios_setting = LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
+ .cert_to_password = LENOVO_CERT_TO_PASSWORD_GUID,
+ .clear_bios_cert = LENOVO_CLEAR_BIOS_CERT_GUID,
+ .update_bios_cert = LENOVO_UPDATE_BIOS_CERT_GUID,
+ .set_bios_cert = LENOVO_SET_BIOS_CERT_GUID,
+};
+
+static const struct tlmi_cert_guids thinkcenter_cert_guid = {
+ .thumbprint = LENOVO_CERT_THUMBPRINT_GUID, /* Same GUID as TP */
+ .set_bios_setting = LENOVO_TC_SET_BIOS_SETTING_CERT_GUID,
+ .save_bios_setting = LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID,
+ .cert_to_password = LENOVO_TC_CERT_TO_PASSWORD_GUID,
+ .clear_bios_cert = LENOVO_TC_CLEAR_BIOS_CERT_GUID,
+ .update_bios_cert = LENOVO_TC_UPDATE_BIOS_CERT_GUID,
+ .set_bios_cert = LENOVO_TC_SET_BIOS_CERT_GUID,
+};
+
static const struct tlmi_err_codes tlmi_errs[] = {
{"Success", 0},
+ {"Set Certificate operation was successful.", 0},
{"Not Supported", -EOPNOTSUPP},
{"Invalid Parameter", -EINVAL},
{"Access Denied", -EACCES},
{"System Busy", -EBUSY},
+ {"Set Certificate operation failed with status:Invalid Parameter.", -EINVAL},
+ {"Set Certificate operation failed with status:Invalid certificate type.", -EINVAL},
+ {"Set Certificate operation failed with status:Invalid password format.", -EINVAL},
+ {"Set Certificate operation failed with status:Password retry count exceeded.", -EACCES},
+ {"Set Certificate operation failed with status:Password Invalid.", -EACCES},
+ {"Set Certificate operation failed with status:Operation aborted.", -EBUSY},
+ {"Set Certificate operation failed with status:No free slots to write.", -ENOSPC},
+ {"Set Certificate operation failed with status:Certificate not found.", -EEXIST},
+ {"Set Certificate operation failed with status:Internal error.", -EFAULT},
+ {"Set Certificate operation failed with status:Certificate too large.", -EFBIG},
};
static const char * const encoding_options[] = {
@@ -668,7 +706,14 @@ static ssize_t cert_thumbprint(char *buf, const char *arg, int count)
const union acpi_object *obj;
acpi_status status;
- status = wmi_evaluate_method(LENOVO_CERT_THUMBPRINT_GUID, 0, 0, &input, &output);
+ if (!tlmi_priv.cert_guid->thumbprint)
+ return -EOPNOTSUPP;
+
+ /* Older ThinkCenter BIOS may not have support */
+ if (!wmi_has_guid(tlmi_priv.cert_guid->thumbprint))
+ return -EOPNOTSUPP;
+
+ status = wmi_evaluate_method(tlmi_priv.cert_guid->thumbprint, 0, 0, &input, &output);
if (ACPI_FAILURE(status)) {
kfree(output.pointer);
return -EIO;
@@ -751,7 +796,7 @@ static ssize_t cert_to_password_store(struct kobject *kobj,
kfree_sensitive(passwd);
return -ENOMEM;
}
- ret = tlmi_simple_call(LENOVO_CERT_TO_PASSWORD_GUID, auth_str);
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->cert_to_password, auth_str);
kfree(auth_str);
kfree_sensitive(passwd);
@@ -772,8 +817,9 @@ static ssize_t certificate_store(struct kobject *kobj,
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
enum cert_install_mode install_mode = TLMI_CERT_INSTALL;
char *auth_str, *new_cert;
+ const char *serial;
char *signature;
- char *guid;
+ const char *guid;
int ret;
if (!capable(CAP_SYS_ADMIN))
@@ -789,13 +835,14 @@ static ssize_t certificate_store(struct kobject *kobj,
return -EACCES;
/* Format: 'serial#, signature' */
- auth_str = cert_command(setting,
- dmi_get_system_info(DMI_PRODUCT_SERIAL),
- setting->signature);
+ serial = dmi_get_system_info(DMI_PRODUCT_SERIAL);
+ if (!serial)
+ return -ENODEV;
+ auth_str = cert_command(setting, serial, setting->signature);
if (!auth_str)
return -ENOMEM;
- ret = tlmi_simple_call(LENOVO_CLEAR_BIOS_CERT_GUID, auth_str);
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->clear_bios_cert, auth_str);
kfree(auth_str);
return ret ?: count;
@@ -832,7 +879,7 @@ static ssize_t certificate_store(struct kobject *kobj,
kfree(new_cert);
return -EACCES;
}
- guid = LENOVO_UPDATE_BIOS_CERT_GUID;
+ guid = tlmi_priv.cert_guid->update_bios_cert;
/* Format: 'Certificate,Signature' */
auth_str = cert_command(setting, new_cert, signature);
} else {
@@ -843,9 +890,17 @@ static ssize_t certificate_store(struct kobject *kobj,
kfree(new_cert);
return -EACCES;
}
- guid = LENOVO_SET_BIOS_CERT_GUID;
- /* Format: 'Certificate, password' */
- auth_str = cert_command(setting, new_cert, setting->password);
+ guid = tlmi_priv.cert_guid->set_bios_cert;
+ if (tlmi_priv.thinkcenter_mode) {
+ /* Format: 'Certificate, password, encoding, kbdlang' */
+ auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s,%s", new_cert,
+ setting->password,
+ encoding_options[setting->encoding],
+ setting->kbdlang);
+ } else {
+ /* Format: 'Certificate, password' */
+ auth_str = cert_command(setting, new_cert, setting->password);
+ }
}
kfree(new_cert);
if (!auth_str)
@@ -973,6 +1028,7 @@ static const struct attribute_group auth_attr_group = {
.is_visible = auth_attr_is_visible,
.attrs = auth_attrs,
};
+__ATTRIBUTE_GROUPS(auth_attr);
/* ---- Attributes sysfs --------------------------------------------------------- */
static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr,
@@ -1061,20 +1117,20 @@ static ssize_t current_value_store(struct kobject *kobj,
ret = -EINVAL;
goto out;
}
- set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name,
- new_setting, tlmi_priv.pwd_admin->signature);
+ set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->name,
+ new_setting, tlmi_priv.pwd_admin->signature);
if (!set_str) {
ret = -ENOMEM;
goto out;
}
- ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str);
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->set_bios_setting, set_str);
if (ret)
goto out;
if (tlmi_priv.save_mode == TLMI_SAVE_BULK)
tlmi_priv.save_required = true;
else
- ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->save_bios_setting,
tlmi_priv.pwd_admin->save_signature);
} else if (tlmi_priv.opcode_support) {
/*
@@ -1092,7 +1148,7 @@ static ssize_t current_value_store(struct kobject *kobj,
goto out;
}
- set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name,
+ set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->name,
new_setting);
if (!set_str) {
ret = -ENOMEM;
@@ -1120,11 +1176,11 @@ static ssize_t current_value_store(struct kobject *kobj,
}
if (auth_str)
- set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name,
- new_setting, auth_str);
+ set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->name,
+ new_setting, auth_str);
else
- set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name,
- new_setting);
+ set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->name,
+ new_setting);
if (!set_str) {
ret = -ENOMEM;
goto out;
@@ -1188,6 +1244,7 @@ static const struct attribute_group tlmi_attr_group = {
.is_visible = attr_is_visible,
.attrs = tlmi_attrs,
};
+__ATTRIBUTE_GROUPS(tlmi_attr);
static void tlmi_attr_setting_release(struct kobject *kobj)
{
@@ -1207,11 +1264,13 @@ static void tlmi_pwd_setting_release(struct kobject *kobj)
static const struct kobj_type tlmi_attr_setting_ktype = {
.release = &tlmi_attr_setting_release,
.sysfs_ops = &kobj_sysfs_ops,
+ .default_groups = tlmi_attr_groups,
};
static const struct kobj_type tlmi_pwd_setting_ktype = {
.release = &tlmi_pwd_setting_release,
.sysfs_ops = &kobj_sysfs_ops,
+ .default_groups = auth_attr_groups,
};
static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr,
@@ -1276,7 +1335,7 @@ static ssize_t save_settings_store(struct kobject *kobj, struct kobj_attribute *
ret = -EINVAL;
goto out;
}
- ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID,
+ ret = tlmi_simple_call(tlmi_priv.cert_guid->save_bios_setting,
tlmi_priv.pwd_admin->save_signature);
if (ret)
goto out;
@@ -1380,21 +1439,18 @@ static struct kobj_attribute debug_cmd = __ATTR_WO(debug_cmd);
/* ---- Initialisation --------------------------------------------------------- */
static void tlmi_release_attr(void)
{
- int i;
+ struct kobject *pos, *n;
/* Attribute structures */
- for (i = 0; i < TLMI_SETTINGS_COUNT; i++) {
- if (tlmi_priv.setting[i]) {
- sysfs_remove_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group);
- kobject_put(&tlmi_priv.setting[i]->kobj);
- }
- }
sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr);
sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &save_settings.attr);
if (tlmi_priv.can_debug_cmd && debug_support)
sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr);
+ list_for_each_entry_safe(pos, n, &tlmi_priv.attribute_kset->list, entry)
+ kobject_put(pos);
+
kset_unregister(tlmi_priv.attribute_kset);
/* Free up any saved signatures */
@@ -1402,19 +1458,8 @@ static void tlmi_release_attr(void)
kfree(tlmi_priv.pwd_admin->save_signature);
/* Authentication structures */
- sysfs_remove_group(&tlmi_priv.pwd_admin->kobj, &auth_attr_group);
- kobject_put(&tlmi_priv.pwd_admin->kobj);
- sysfs_remove_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group);
- kobject_put(&tlmi_priv.pwd_power->kobj);
-
- if (tlmi_priv.opcode_support) {
- sysfs_remove_group(&tlmi_priv.pwd_system->kobj, &auth_attr_group);
- kobject_put(&tlmi_priv.pwd_system->kobj);
- sysfs_remove_group(&tlmi_priv.pwd_hdd->kobj, &auth_attr_group);
- kobject_put(&tlmi_priv.pwd_hdd->kobj);
- sysfs_remove_group(&tlmi_priv.pwd_nvme->kobj, &auth_attr_group);
- kobject_put(&tlmi_priv.pwd_nvme->kobj);
- }
+ list_for_each_entry_safe(pos, n, &tlmi_priv.authentication_kset->list, entry)
+ kobject_put(pos);
kset_unregister(tlmi_priv.authentication_kset);
}
@@ -1455,6 +1500,14 @@ static int tlmi_sysfs_init(void)
goto fail_device_created;
}
+ tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL,
+ &tlmi_priv.class_dev->kobj);
+ if (!tlmi_priv.authentication_kset) {
+ kset_unregister(tlmi_priv.attribute_kset);
+ ret = -ENOMEM;
+ goto fail_device_created;
+ }
+
for (i = 0; i < TLMI_SETTINGS_COUNT; i++) {
/* Check if index is a valid setting - skip if it isn't */
if (!tlmi_priv.setting[i])
@@ -1471,12 +1524,8 @@ static int tlmi_sysfs_init(void)
/* Build attribute */
tlmi_priv.setting[i]->kobj.kset = tlmi_priv.attribute_kset;
- ret = kobject_add(&tlmi_priv.setting[i]->kobj, NULL,
- "%s", tlmi_priv.setting[i]->display_name);
- if (ret)
- goto fail_create_attr;
-
- ret = sysfs_create_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group);
+ ret = kobject_init_and_add(&tlmi_priv.setting[i]->kobj, &tlmi_attr_setting_ktype,
+ NULL, "%s", tlmi_priv.setting[i]->display_name);
if (ret)
goto fail_create_attr;
}
@@ -1496,55 +1545,34 @@ static int tlmi_sysfs_init(void)
}
/* Create authentication entries */
- tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL,
- &tlmi_priv.class_dev->kobj);
- if (!tlmi_priv.authentication_kset) {
- ret = -ENOMEM;
- goto fail_create_attr;
- }
tlmi_priv.pwd_admin->kobj.kset = tlmi_priv.authentication_kset;
- ret = kobject_add(&tlmi_priv.pwd_admin->kobj, NULL, "%s", "Admin");
- if (ret)
- goto fail_create_attr;
-
- ret = sysfs_create_group(&tlmi_priv.pwd_admin->kobj, &auth_attr_group);
+ ret = kobject_init_and_add(&tlmi_priv.pwd_admin->kobj, &tlmi_pwd_setting_ktype,
+ NULL, "%s", "Admin");
if (ret)
goto fail_create_attr;
tlmi_priv.pwd_power->kobj.kset = tlmi_priv.authentication_kset;
- ret = kobject_add(&tlmi_priv.pwd_power->kobj, NULL, "%s", "Power-on");
- if (ret)
- goto fail_create_attr;
-
- ret = sysfs_create_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group);
+ ret = kobject_init_and_add(&tlmi_priv.pwd_power->kobj, &tlmi_pwd_setting_ktype,
+ NULL, "%s", "Power-on");
if (ret)
goto fail_create_attr;
if (tlmi_priv.opcode_support) {
tlmi_priv.pwd_system->kobj.kset = tlmi_priv.authentication_kset;
- ret = kobject_add(&tlmi_priv.pwd_system->kobj, NULL, "%s", "System");
- if (ret)
- goto fail_create_attr;
-
- ret = sysfs_create_group(&tlmi_priv.pwd_system->kobj, &auth_attr_group);
+ ret = kobject_init_and_add(&tlmi_priv.pwd_system->kobj, &tlmi_pwd_setting_ktype,
+ NULL, "%s", "System");
if (ret)
goto fail_create_attr;
tlmi_priv.pwd_hdd->kobj.kset = tlmi_priv.authentication_kset;
- ret = kobject_add(&tlmi_priv.pwd_hdd->kobj, NULL, "%s", "HDD");
- if (ret)
- goto fail_create_attr;
-
- ret = sysfs_create_group(&tlmi_priv.pwd_hdd->kobj, &auth_attr_group);
+ ret = kobject_init_and_add(&tlmi_priv.pwd_hdd->kobj, &tlmi_pwd_setting_ktype,
+ NULL, "%s", "HDD");
if (ret)
goto fail_create_attr;
tlmi_priv.pwd_nvme->kobj.kset = tlmi_priv.authentication_kset;
- ret = kobject_add(&tlmi_priv.pwd_nvme->kobj, NULL, "%s", "NVMe");
- if (ret)
- goto fail_create_attr;
-
- ret = sysfs_create_group(&tlmi_priv.pwd_nvme->kobj, &auth_attr_group);
+ ret = kobject_init_and_add(&tlmi_priv.pwd_nvme->kobj, &tlmi_pwd_setting_ktype,
+ NULL, "%s", "NVMe");
if (ret)
goto fail_create_attr;
}
@@ -1554,7 +1582,7 @@ static int tlmi_sysfs_init(void)
fail_create_attr:
tlmi_release_attr();
fail_device_created:
- device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ device_unregister(tlmi_priv.class_dev);
fail_class_created:
return ret;
}
@@ -1565,7 +1593,7 @@ static struct tlmi_pwd_setting *tlmi_create_auth(const char *pwd_type,
{
struct tlmi_pwd_setting *new_pwd;
- new_pwd = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL);
+ new_pwd = kzalloc_obj(struct tlmi_pwd_setting);
if (!new_pwd)
return NULL;
@@ -1577,8 +1605,6 @@ static struct tlmi_pwd_setting *tlmi_create_auth(const char *pwd_type,
new_pwd->maxlen = tlmi_priv.pwdcfg.core.max_length;
new_pwd->index = 0;
- kobject_init(&new_pwd->kobj, &tlmi_pwd_setting_ktype);
-
return new_pwd;
}
@@ -1610,6 +1636,15 @@ static int tlmi_analyze(struct wmi_device *wdev)
wmi_has_guid(LENOVO_SAVE_BIOS_SETTING_CERT_GUID))
tlmi_priv.certificate_support = true;
+ /* ThinkCenter uses different GUIDs for certificate support */
+ if (wmi_has_guid(LENOVO_TC_SET_BIOS_CERT_GUID) &&
+ wmi_has_guid(LENOVO_TC_SET_BIOS_SETTING_CERT_GUID) &&
+ wmi_has_guid(LENOVO_TC_SAVE_BIOS_SETTING_CERT_GUID)) {
+ tlmi_priv.certificate_support = true;
+ tlmi_priv.thinkcenter_mode = true;
+ pr_info("ThinkCenter modified support being used\n");
+ }
+
/*
* Try to find the number of valid settings of this machine
* and use it to create sysfs attributes.
@@ -1629,14 +1664,11 @@ static int tlmi_analyze(struct wmi_device *wdev)
continue;
}
- /* It is not allowed to have '/' for file name. Convert it into '\'. */
- strreplace(item, '/', '\\');
-
/* Remove the value part */
strreplace(item, ',', '\0');
/* Create a setting entry */
- setting = kzalloc(sizeof(*setting), GFP_KERNEL);
+ setting = kzalloc_obj(*setting);
if (!setting) {
ret = -ENOMEM;
kfree(item);
@@ -1644,11 +1676,16 @@ static int tlmi_analyze(struct wmi_device *wdev)
}
setting->wdev = wdev;
setting->index = i;
+
+ strscpy(setting->name, item);
+ /* It is not allowed to have '/' for file name. Convert it into '\'. */
+ strreplace(item, '/', '\\');
strscpy(setting->display_name, item);
+
/* If BIOS selections supported, load those */
if (tlmi_priv.can_get_bios_selections) {
- ret = tlmi_get_bios_selections(setting->display_name,
- &setting->possible_values);
+ ret = tlmi_get_bios_selections(setting->name,
+ &setting->possible_values);
if (ret || !setting->possible_values)
pr_info("Error retrieving possible values for %d : %s\n",
i, setting->display_name);
@@ -1681,7 +1718,6 @@ static int tlmi_analyze(struct wmi_device *wdev)
if (setting->possible_values)
strreplace(setting->possible_values, ',', ';');
- kobject_init(&setting->kobj, &tlmi_attr_setting_ktype);
tlmi_priv.setting[i] = setting;
kfree(item);
}
@@ -1754,10 +1790,16 @@ static int tlmi_analyze(struct wmi_device *wdev)
}
if (tlmi_priv.certificate_support) {
- tlmi_priv.pwd_admin->cert_installed =
- tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SVC;
- tlmi_priv.pwd_system->cert_installed =
- tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SMC;
+ if (tlmi_priv.thinkcenter_mode) {
+ tlmi_priv.cert_guid = &thinkcenter_cert_guid;
+ tlmi_priv.pwd_admin->cert_installed = tlmi_priv.pwdcfg.core.password_mode;
+ } else {
+ tlmi_priv.cert_guid = &thinkpad_cert_guid;
+ tlmi_priv.pwd_admin->cert_installed =
+ tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SVC;
+ tlmi_priv.pwd_system->cert_installed =
+ tlmi_priv.pwdcfg.core.password_state & TLMI_CERT_SMC;
+ }
}
return 0;
@@ -1779,7 +1821,7 @@ fail_clear_attr:
static void tlmi_remove(struct wmi_device *wdev)
{
tlmi_release_attr();
- device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+ device_unregister(tlmi_priv.class_dev);
}
static int tlmi_probe(struct wmi_device *wdev, const void *context)
diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/lenovo/think-lmi.h
index a80452482227..017644323d46 100644
--- a/drivers/platform/x86/think-lmi.h
+++ b/drivers/platform/x86/lenovo/think-lmi.h
@@ -41,6 +41,17 @@ enum save_mode {
TLMI_SAVE_SAVE,
};
+/* GUIDs can differ between platforms */
+struct tlmi_cert_guids {
+ const char *thumbprint;
+ const char *set_bios_setting;
+ const char *save_bios_setting;
+ const char *cert_to_password;
+ const char *clear_bios_cert;
+ const char *update_bios_cert;
+ const char *set_bios_cert;
+};
+
/* password configuration details */
#define TLMI_PWDCFG_MODE_LEGACY 0
#define TLMI_PWDCFG_MODE_PASSWORD 1
@@ -90,6 +101,7 @@ struct tlmi_attr_setting {
struct kobject kobj;
struct wmi_device *wdev;
int index;
+ char name[TLMI_SETTINGS_MAXLEN];
char display_name[TLMI_SETTINGS_MAXLEN];
char *possible_values;
};
@@ -108,6 +120,7 @@ struct think_lmi {
enum save_mode save_mode;
bool save_required;
bool reboot_required;
+ bool thinkcenter_mode;
struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT];
struct device *class_dev;
@@ -120,6 +133,8 @@ struct think_lmi {
struct tlmi_pwd_setting *pwd_system;
struct tlmi_pwd_setting *pwd_hdd;
struct tlmi_pwd_setting *pwd_nvme;
+
+ const struct tlmi_cert_guids *cert_guid;
};
#endif /* !_THINK_LMI_H_ */
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index 0384cf311878..e1cee42a1683 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -36,6 +36,7 @@
#include <linux/acpi.h>
#include <linux/backlight.h>
+#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/dmi.h>
@@ -81,7 +82,7 @@
#include <sound/core.h>
#include <sound/initval.h>
-#include "dual_accel_detect.h"
+#include "../dual_accel_detect.h"
/* ThinkPad CMOS commands */
#define TP_CMOS_VOLUME_DOWN 0
@@ -182,6 +183,7 @@ enum tpacpi_hkey_event_t {
* directly in the sparse-keymap.
*/
TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */
+ TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */
TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */
TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */
TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */
@@ -231,6 +233,7 @@ enum tpacpi_hkey_event_t {
/* Thermal events */
TP_HKEY_EV_ALARM_BAT_HOT = 0x6011, /* battery too hot */
TP_HKEY_EV_ALARM_BAT_XHOT = 0x6012, /* battery critically hot */
+ TP_HKEY_EV_ALARM_BAT_LIM_CHANGE = 0x6013, /* battery charge limit changed*/
TP_HKEY_EV_ALARM_SENSOR_HOT = 0x6021, /* sensor too hot */
TP_HKEY_EV_ALARM_SENSOR_XHOT = 0x6022, /* sensor critically hot */
TP_HKEY_EV_THM_TABLE_CHANGED = 0x6030, /* windows; thermal table changed */
@@ -296,7 +299,6 @@ struct ibm_struct;
struct tp_acpi_drv_struct {
const struct acpi_device_id *hid;
- struct acpi_driver *driver;
void (*notify) (struct ibm_struct *, u32);
acpi_handle *handle;
@@ -319,7 +321,6 @@ struct ibm_struct {
struct tp_acpi_drv_struct *acpi;
struct {
- u8 acpi_driver_registered:1;
u8 acpi_notify_installed:1;
u8 proc_created:1;
u8 init_called:1;
@@ -367,10 +368,11 @@ static struct {
u32 beep_needs_two_args:1;
u32 mixer_no_level_control:1;
u32 battery_force_primary:1;
+ u32 platform_drv_registered:1;
u32 hotkey_poll_active:1;
u32 has_adaptive_kbd:1;
u32 kbd_lang:1;
- u32 trackpoint_doubletap:1;
+ u32 trackpoint_doubletap_enable:1;
struct quirk_entry *quirks;
} tp_features;
@@ -556,12 +558,12 @@ static unsigned long __init tpacpi_check_quirks(
return 0;
}
-static inline bool __pure __init tpacpi_is_lenovo(void)
+static __always_inline bool __pure __init tpacpi_is_lenovo(void)
{
return thinkpad_id.vendor == PCI_VENDOR_ID_LENOVO;
}
-static inline bool __pure __init tpacpi_is_ibm(void)
+static __always_inline bool __pure __init tpacpi_is_ibm(void)
{
return thinkpad_id.vendor == PCI_VENDOR_ID_IBM;
}
@@ -828,16 +830,16 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
vdbg_printk(TPACPI_DBG_INIT,
"setting up ACPI notify for %s\n", ibm->name);
- ibm->acpi->device = acpi_fetch_acpi_dev(*ibm->acpi->handle);
+ ibm->acpi->device = acpi_get_acpi_dev(*ibm->acpi->handle);
if (!ibm->acpi->device) {
- pr_err("acpi_fetch_acpi_dev(%s) failed\n", ibm->name);
+ pr_err("acpi_get_acpi_dev(%s) failed\n", ibm->name);
return -ENODEV;
}
ibm->acpi->device->driver_data = ibm;
- sprintf(acpi_device_class(ibm->acpi->device), "%s/%s",
- TPACPI_ACPI_EVENT_PREFIX,
- ibm->name);
+ scnprintf(acpi_device_class(ibm->acpi->device),
+ sizeof(acpi_device_class(ibm->acpi->device)),
+ "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name);
status = acpi_install_notify_handler(*ibm->acpi->handle,
ibm->acpi->type, dispatch_acpi_notify, ibm);
@@ -855,44 +857,6 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
return 0;
}
-static int __init tpacpi_device_add(struct acpi_device *device)
-{
- return 0;
-}
-
-static int __init register_tpacpi_subdriver(struct ibm_struct *ibm)
-{
- int rc;
-
- dbg_printk(TPACPI_DBG_INIT,
- "registering %s as an ACPI driver\n", ibm->name);
-
- BUG_ON(!ibm->acpi);
-
- ibm->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
- if (!ibm->acpi->driver) {
- pr_err("failed to allocate memory for ibm->acpi->driver\n");
- return -ENOMEM;
- }
-
- sprintf(ibm->acpi->driver->name, "%s_%s", TPACPI_NAME, ibm->name);
- ibm->acpi->driver->ids = ibm->acpi->hid;
-
- ibm->acpi->driver->ops.add = &tpacpi_device_add;
-
- rc = acpi_bus_register_driver(ibm->acpi->driver);
- if (rc < 0) {
- pr_err("acpi_bus_register_driver(%s) failed: %d\n",
- ibm->name, rc);
- kfree(ibm->acpi->driver);
- ibm->acpi->driver = NULL;
- } else if (!rc)
- ibm->flags.acpi_driver_registered = 1;
-
- return rc;
-}
-
-
/****************************************************************************
****************************************************************************
*
@@ -1193,7 +1157,7 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
- atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
+ atp_rfk = kzalloc_obj(struct tpacpi_rfk);
if (atp_rfk)
atp_rfk->rfkill = rfkill_alloc(name,
&tpacpi_pdev->dev,
@@ -1311,7 +1275,7 @@ static ssize_t tpacpi_rfk_sysfs_enable_store(const enum tpacpi_rfk_id id,
static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *m)
{
if (id >= TPACPI_RFK_SW_MAX)
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
else {
int status;
@@ -1326,7 +1290,7 @@ static int tpacpi_rfk_procfs_read(const enum tpacpi_rfk_id id, struct seq_file *
}
seq_printf(m, "status:\t\t%s\n", str_enabled_disabled(status == TPACPI_RFK_RADIO_ON));
- seq_printf(m, "commands:\tenable, disable\n");
+ seq_puts(m, "commands:\tenable, disable\n");
}
return 0;
@@ -2249,6 +2213,25 @@ static void tpacpi_input_send_tabletsw(void)
}
}
+#define GCES_NO_SHUTTER_DEVICE BIT(31)
+
+static int get_camera_shutter(void)
+{
+ acpi_handle gces_handle;
+ int output;
+
+ if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GCES", &gces_handle)))
+ return -ENODEV;
+
+ if (!acpi_evalf(gces_handle, &output, NULL, "dd", 0))
+ return -EIO;
+
+ if (output & GCES_NO_SHUTTER_DEVICE)
+ return -ENODEV;
+
+ return output;
+}
+
static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev)
{
bool known_ev;
@@ -2996,6 +2979,31 @@ static const struct attribute_group adaptive_kbd_attr_group = {
.attrs = adaptive_kbd_attributes,
};
+/* sysfs doubletap enable --------------------------------------------- */
+static ssize_t doubletap_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%d\n", tp_features.trackpoint_doubletap_enable);
+}
+
+static ssize_t doubletap_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ bool enable;
+ int err;
+
+ err = kstrtobool(buf, &enable);
+ if (err)
+ return err;
+
+ tp_features.trackpoint_doubletap_enable = enable;
+ return count;
+}
+
+static DEVICE_ATTR_RW(doubletap_enable);
+
/* --------------------------------------------------------------------- */
static struct attribute *hotkey_attributes[] = {
@@ -3010,6 +3018,7 @@ static struct attribute *hotkey_attributes[] = {
&dev_attr_hotkey_recommended_mask.attr,
&dev_attr_hotkey_tablet_mode.attr,
&dev_attr_hotkey_radio_sw.attr,
+ &dev_attr_doubletap_enable.attr,
#ifdef CONFIG_THINKPAD_ACPI_HOTKEY_POLL
&dev_attr_hotkey_source_mask.attr,
&dev_attr_hotkey_poll_freq.attr,
@@ -3273,6 +3282,7 @@ static const struct key_entry keymap_lenovo[] __initconst = {
*/
{ KE_KEY, 0x131d, { KEY_VENDOR } }, /* System debug info, similar to old ThinkPad key */
{ KE_KEY, 0x1320, { KEY_LINK_PHONE } },
+ { KE_KEY, 0x1402, { KEY_LINK_PHONE } },
{ KE_KEY, TP_HKEY_EV_TRACK_DOUBLETAP /* 0x8036 */, { KEY_PROG4 } },
{ KE_END }
};
@@ -3302,7 +3312,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
const struct key_entry *keymap;
bool radiosw_state = false;
bool tabletsw_state = false;
- int hkeyv, res, status;
+ int hkeyv, res, status, camera_shutter_state;
vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY,
"initializing hotkey subdriver\n");
@@ -3466,6 +3476,12 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
if (res)
return res;
+ camera_shutter_state = get_camera_shutter();
+ if (camera_shutter_state >= 0) {
+ input_set_capability(tpacpi_inputdev, EV_SW, SW_CAMERA_LENS_COVER);
+ input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state);
+ }
+
if (tp_features.hotkey_wlsw) {
input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL);
input_report_switch(tpacpi_inputdev,
@@ -3528,8 +3544,8 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)
hotkey_poll_setup_safe(true);
- /* Enable doubletap by default */
- tp_features.trackpoint_doubletap = 1;
+ /* Enable TrackPoint doubletap event reporting by default. */
+ tp_features.trackpoint_doubletap_enable = 1;
return 0;
}
@@ -3776,6 +3792,10 @@ static bool hotkey_notify_6xxx(const u32 hkey, bool *send_acpi_ev)
pr_alert("THERMAL EMERGENCY: battery is extremely hot!\n");
/* recommended action: immediate sleep/hibernate */
break;
+ case TP_HKEY_EV_ALARM_BAT_LIM_CHANGE:
+ pr_debug("Battery Info: battery charge threshold changed\n");
+ /* User changed charging threshold. No action needed */
+ return true;
case TP_HKEY_EV_ALARM_SENSOR_HOT:
pr_crit("THERMAL ALARM: a sensor reports something is too hot!\n");
/* recommended action: warn user through gui, that */
@@ -3830,9 +3850,9 @@ static bool hotkey_notify_8xxx(const u32 hkey, bool *send_acpi_ev)
{
switch (hkey) {
case TP_HKEY_EV_TRACK_DOUBLETAP:
- if (tp_features.trackpoint_doubletap)
- tpacpi_input_send_key(hkey, send_acpi_ev);
-
+ /* Only send event if doubletap is enabled */
+ if (!tp_features.trackpoint_doubletap_enable)
+ *send_acpi_ev = false;
return true;
default:
return false;
@@ -3983,7 +4003,7 @@ static int hotkey_read(struct seq_file *m)
int res, status;
if (!tp_features.hotkey) {
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
return 0;
}
@@ -3999,10 +4019,10 @@ static int hotkey_read(struct seq_file *m)
seq_printf(m, "status:\t\t%s\n", str_enabled_disabled(status & BIT(0)));
if (hotkey_all_mask) {
seq_printf(m, "mask:\t\t0x%08x\n", hotkey_user_mask);
- seq_printf(m, "commands:\tenable, disable, reset, <mask>\n");
+ seq_puts(m, "commands:\tenable, disable, reset, <mask>\n");
} else {
- seq_printf(m, "mask:\t\tnot supported\n");
- seq_printf(m, "commands:\tenable, disable, reset\n");
+ seq_puts(m, "mask:\t\tnot supported\n");
+ seq_puts(m, "commands:\tenable, disable, reset\n");
}
return 0;
@@ -4899,7 +4919,7 @@ static int video_read(struct seq_file *m)
int status, autosw;
if (video_supported == TPACPI_VIDEO_NONE) {
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
return 0;
}
@@ -4915,18 +4935,18 @@ static int video_read(struct seq_file *m)
if (autosw < 0)
return autosw;
- seq_printf(m, "status:\t\tsupported\n");
+ seq_puts(m, "status:\t\tsupported\n");
seq_printf(m, "lcd:\t\t%s\n", str_enabled_disabled(status & BIT(0)));
seq_printf(m, "crt:\t\t%s\n", str_enabled_disabled(status & BIT(1)));
if (video_supported == TPACPI_VIDEO_NEW)
seq_printf(m, "dvi:\t\t%s\n", str_enabled_disabled(status & BIT(3)));
seq_printf(m, "auto:\t\t%s\n", str_enabled_disabled(autosw & BIT(0)));
- seq_printf(m, "commands:\tlcd_enable, lcd_disable\n");
- seq_printf(m, "commands:\tcrt_enable, crt_disable\n");
+ seq_puts(m, "commands:\tlcd_enable, lcd_disable\n");
+ seq_puts(m, "commands:\tcrt_enable, crt_disable\n");
if (video_supported == TPACPI_VIDEO_NEW)
- seq_printf(m, "commands:\tdvi_enable, dvi_disable\n");
- seq_printf(m, "commands:\tauto_enable, auto_disable\n");
- seq_printf(m, "commands:\tvideo_switch, expand_toggle\n");
+ seq_puts(m, "commands:\tdvi_enable, dvi_disable\n");
+ seq_puts(m, "commands:\tauto_enable, auto_disable\n");
+ seq_puts(m, "commands:\tvideo_switch, expand_toggle\n");
return 0;
}
@@ -5170,14 +5190,14 @@ static int kbdlight_read(struct seq_file *m)
int level;
if (!tp_features.kbdlight) {
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
} else {
level = kbdlight_get_level();
if (level < 0)
seq_printf(m, "status:\t\terror %d\n", level);
else
seq_printf(m, "status:\t\t%d\n", level);
- seq_printf(m, "commands:\t0, 1, 2\n");
+ seq_puts(m, "commands:\t0, 1, 2\n");
}
return 0;
@@ -5344,16 +5364,16 @@ static int light_read(struct seq_file *m)
int status;
if (!tp_features.light) {
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
} else if (!tp_features.light_status) {
- seq_printf(m, "status:\t\tunknown\n");
- seq_printf(m, "commands:\ton, off\n");
+ seq_puts(m, "status:\t\tunknown\n");
+ seq_puts(m, "commands:\ton, off\n");
} else {
status = light_get_status();
if (status < 0)
return status;
seq_printf(m, "status:\t\t%s\n", str_on_off(status & BIT(0)));
- seq_printf(m, "commands:\ton, off\n");
+ seq_puts(m, "commands:\ton, off\n");
}
return 0;
@@ -5443,10 +5463,10 @@ static int cmos_read(struct seq_file *m)
/* cmos not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p,
R30, R31, T20-22, X20-21 */
if (!cmos_handle)
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
else {
- seq_printf(m, "status:\t\tsupported\n");
- seq_printf(m, "commands:\t<cmd> (<cmd> is 0-21)\n");
+ seq_puts(m, "status:\t\tsupported\n");
+ seq_puts(m, "commands:\t<cmd> (<cmd> is 0-21)\n");
}
return 0;
@@ -5783,8 +5803,7 @@ static int __init led_init(struct ibm_init_struct *iibm)
if (led_supported == TPACPI_LED_NONE)
return -ENODEV;
- tpacpi_leds = kcalloc(TPACPI_LED_NUMLEDS, sizeof(*tpacpi_leds),
- GFP_KERNEL);
+ tpacpi_leds = kzalloc_objs(*tpacpi_leds, TPACPI_LED_NUMLEDS);
if (!tpacpi_leds) {
pr_err("Out of memory for LED data\n");
return -ENOMEM;
@@ -5813,10 +5832,10 @@ static int __init led_init(struct ibm_init_struct *iibm)
static int led_read(struct seq_file *m)
{
if (!led_supported) {
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
return 0;
}
- seq_printf(m, "status:\t\tsupported\n");
+ seq_puts(m, "status:\t\tsupported\n");
if (led_supported == TPACPI_LED_570) {
/* 570 */
@@ -5829,7 +5848,7 @@ static int led_read(struct seq_file *m)
}
}
- seq_printf(m, "commands:\t<led> on, <led> off, <led> blink (<led> is 0-15)\n");
+ seq_puts(m, "commands:\t<led> on, <led> off, <led> blink (<led> is 0-15)\n");
return 0;
}
@@ -5913,10 +5932,10 @@ static int __init beep_init(struct ibm_init_struct *iibm)
static int beep_read(struct seq_file *m)
{
if (!beep_handle)
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
else {
- seq_printf(m, "status:\t\tsupported\n");
- seq_printf(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
+ seq_puts(m, "status:\t\tsupported\n");
+ seq_puts(m, "commands:\t<cmd> (<cmd> is 0-17)\n");
}
return 0;
@@ -6365,14 +6384,14 @@ static int thermal_read(struct seq_file *m)
if (unlikely(n < 0))
return n;
- seq_printf(m, "temperatures:\t");
+ seq_puts(m, "temperatures:\t");
if (n > 0) {
for (i = 0; i < (n - 1); i++)
seq_printf(m, "%d ", t.temp[i] / 1000);
seq_printf(m, "%d\n", t.temp[i] / 1000);
} else
- seq_printf(m, "not supported\n");
+ seq_puts(m, "not supported\n");
return 0;
}
@@ -6885,10 +6904,10 @@ static int brightness_read(struct seq_file *m)
level = brightness_get(NULL);
if (level < 0) {
- seq_printf(m, "level:\t\tunreadable\n");
+ seq_puts(m, "level:\t\tunreadable\n");
} else {
seq_printf(m, "level:\t\t%d\n", level);
- seq_printf(m, "commands:\tup, down\n");
+ seq_puts(m, "commands:\tup, down\n");
seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
bright_maxlvl);
}
@@ -7604,10 +7623,10 @@ static int volume_read(struct seq_file *m)
u8 status;
if (volume_get_status(&status) < 0) {
- seq_printf(m, "level:\t\tunreadable\n");
+ seq_puts(m, "level:\t\tunreadable\n");
} else {
if (tp_features.mixer_no_level_control)
- seq_printf(m, "level:\t\tunsupported\n");
+ seq_puts(m, "level:\t\tunsupported\n");
else
seq_printf(m, "level:\t\t%d\n",
status & TP_EC_AUDIO_LVL_MSK);
@@ -7615,9 +7634,9 @@ static int volume_read(struct seq_file *m)
seq_printf(m, "mute:\t\t%s\n", str_on_off(status & BIT(TP_EC_AUDIO_MUTESW)));
if (volume_control_allowed) {
- seq_printf(m, "commands:\tunmute, mute\n");
+ seq_puts(m, "commands:\tunmute, mute\n");
if (!tp_features.mixer_no_level_control) {
- seq_printf(m, "commands:\tup, down\n");
+ seq_puts(m, "commands:\tup, down\n");
seq_printf(m, "commands:\tlevel <level> (<level> is 0-%d)\n",
TP_EC_VOLUME_MAX);
}
@@ -8793,6 +8812,7 @@ static const struct attribute_group fan_driver_attr_group = {
#define TPACPI_FAN_NS 0x0010 /* For EC with non-Standard register addresses */
#define TPACPI_FAN_DECRPM 0x0020 /* For ECFW's with RPM in register as decimal */
#define TPACPI_FAN_TPR 0x0040 /* Fan speed is in Ticks Per Revolution */
+#define TPACPI_FAN_NOACPI 0x0080 /* Don't use ACPI methods even if detected */
static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
TPACPI_QEC_IBM('1', 'Y', TPACPI_FAN_Q1),
@@ -8823,6 +8843,9 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = {
TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */
TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */
TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */
+ TPACPI_Q_LNV3('R', '0', '0', TPACPI_FAN_NOACPI),/* E560 */
+ TPACPI_Q_LNV3('R', '1', '2', TPACPI_FAN_NOACPI),/* T495 */
+ TPACPI_Q_LNV3('R', '1', '3', TPACPI_FAN_NOACPI),/* T495s */
};
static int __init fan_init(struct ibm_init_struct *iibm)
@@ -8874,6 +8897,13 @@ static int __init fan_init(struct ibm_init_struct *iibm)
tp_features.fan_ctrl_status_undef = 1;
}
+ if (quirks & TPACPI_FAN_NOACPI) {
+ /* E560, T495, T495s */
+ pr_info("Ignoring buggy ACPI fan access method\n");
+ fang_handle = NULL;
+ fanw_handle = NULL;
+ }
+
if (gfan_handle) {
/* 570, 600e/x, 770e, 770x */
fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;
@@ -9112,9 +9142,9 @@ static int fan_read(struct seq_file *m)
} else if (fan_status_access_mode == TPACPI_FAN_RD_TPEC) {
if (status & TP_EC_FAN_FULLSPEED)
/* Disengaged mode takes precedence */
- seq_printf(m, "level:\t\tdisengaged\n");
+ seq_puts(m, "level:\t\tdisengaged\n");
else if (status & TP_EC_FAN_AUTO)
- seq_printf(m, "level:\t\tauto\n");
+ seq_puts(m, "level:\t\tauto\n");
else
seq_printf(m, "level:\t\t%d\n", status);
}
@@ -9122,19 +9152,19 @@ static int fan_read(struct seq_file *m)
case TPACPI_FAN_NONE:
default:
- seq_printf(m, "status:\t\tnot supported\n");
+ seq_puts(m, "status:\t\tnot supported\n");
}
if (fan_control_commands & TPACPI_FAN_CMD_LEVEL) {
- seq_printf(m, "commands:\tlevel <level>");
+ seq_puts(m, "commands:\tlevel <level>");
switch (fan_control_access_mode) {
case TPACPI_FAN_WR_ACPI_SFAN:
- seq_printf(m, " (<level> is 0-7)\n");
+ seq_puts(m, " (<level> is 0-7)\n");
break;
default:
- seq_printf(m, " (<level> is 0-7, auto, disengaged, full-speed)\n");
+ seq_puts(m, " (<level> is 0-7, auto, disengaged, full-speed)\n");
break;
}
}
@@ -9144,7 +9174,7 @@ static int fan_read(struct seq_file *m)
"commands:\twatchdog <timeout> (<timeout> is 0 (off), 1-120 (seconds))\n");
if (fan_control_commands & TPACPI_FAN_CMD_SPEED)
- seq_printf(m, "commands:\tspeed <speed> (<speed> is 0-65535)\n");
+ seq_puts(m, "commands:\tspeed <speed> (<speed> is 0-65535)\n");
return 0;
}
@@ -9205,9 +9235,6 @@ static int fan_write_cmd_speed(const char *cmd, int *rc)
{
int speed;
- /* TODO:
- * Support speed <low> <medium> <high> ? */
-
if (sscanf(cmd, "speed %d", &speed) != 1)
return 0;
@@ -9481,14 +9508,16 @@ static int tpacpi_battery_get(int what, int battery, int *ret)
{
switch (what) {
case THRESHOLD_START:
- if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_START, ret, battery))
+ if (!battery_info.batteries[battery].start_support ||
+ ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_START, ret, battery)))
return -ENODEV;
/* The value is in the low 8 bits of the response */
*ret = *ret & 0xFF;
return 0;
case THRESHOLD_STOP:
- if ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_STOP, ret, battery))
+ if (!battery_info.batteries[battery].stop_support ||
+ ACPI_FAILURE(tpacpi_battery_acpi_eval(GET_STOP, ret, battery)))
return -ENODEV;
/* Value is in lower 8 bits */
*ret = *ret & 0xFF;
@@ -11036,6 +11065,206 @@ static const struct attribute_group auxmac_attr_group = {
.attrs = auxmac_attributes,
};
+/*************************************************************************
+ * HWDD subdriver, for the Lenovo Hardware Damage Detection feature.
+ */
+
+#define HWDD_GET_DMG_USBC 0x80000001
+#define HWDD_GET_CAP 0
+#define HWDD_NOT_SUPPORTED BIT(31)
+#define HWDD_SUPPORT_USBC BIT(0)
+
+#define PORT_STATUS GENMASK(7, 4)
+#define LID_STATUS GENMASK(11, 8)
+#define BASE_STATUS GENMASK(15, 12)
+#define POS_STATUS GENMASK(3, 2)
+#define PANEL_STATUS GENMASK(1, 0)
+
+#define PORT_DETAIL_OFFSET 16
+
+#define PANEL_TOP 0
+#define PANEL_BASE 1
+#define PANEL_LEFT 2
+#define PANEL_RIGHT 3
+
+#define POS_LEFT 0
+#define POS_CENTER 1
+#define POS_RIGHT 2
+
+#define NUM_PORTS 4
+
+static bool hwdd_support_available;
+static bool ucdd_supported;
+
+static int hwdd_command(int command, int *output)
+{
+ acpi_handle hwdd_handle;
+
+ if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "HWDD", &hwdd_handle)))
+ return -ENODEV;
+
+ if (!acpi_evalf(hwdd_handle, output, NULL, "dd", command))
+ return -EIO;
+
+ return 0;
+}
+
+static bool display_damage(char *buf, int *count, char *type, unsigned int dmg_status)
+{
+ unsigned char lid_status, base_status, port_status;
+ unsigned char loc_status, pos_status, panel_status;
+ bool damage_detected = false;
+ int i;
+
+ port_status = FIELD_GET(PORT_STATUS, dmg_status);
+ lid_status = FIELD_GET(LID_STATUS, dmg_status);
+ base_status = FIELD_GET(BASE_STATUS, dmg_status);
+ for (i = 0; i < NUM_PORTS; i++) {
+ if (!(dmg_status & BIT(i)) || !(port_status & BIT(i)))
+ continue;
+
+ *count += sysfs_emit_at(buf, *count, "%s: ", type);
+ loc_status = (dmg_status >> (PORT_DETAIL_OFFSET + (4 * i))) & 0xF;
+ pos_status = FIELD_GET(POS_STATUS, loc_status);
+ panel_status = FIELD_GET(PANEL_STATUS, loc_status);
+
+ if (lid_status & BIT(i))
+ *count += sysfs_emit_at(buf, *count, "Lid, ");
+ if (base_status & BIT(i))
+ *count += sysfs_emit_at(buf, *count, "Base, ");
+
+ switch (pos_status) {
+ case PANEL_TOP:
+ *count += sysfs_emit_at(buf, *count, "Top, ");
+ break;
+ case PANEL_BASE:
+ *count += sysfs_emit_at(buf, *count, "Bottom, ");
+ break;
+ case PANEL_LEFT:
+ *count += sysfs_emit_at(buf, *count, "Left, ");
+ break;
+ case PANEL_RIGHT:
+ *count += sysfs_emit_at(buf, *count, "Right, ");
+ break;
+ default:
+ pr_err("Unexpected value %d in switch statement\n", pos_status);
+ }
+
+ switch (panel_status) {
+ case POS_LEFT:
+ *count += sysfs_emit_at(buf, *count, "Left port\n");
+ break;
+ case POS_CENTER:
+ *count += sysfs_emit_at(buf, *count, "Center port\n");
+ break;
+ case POS_RIGHT:
+ *count += sysfs_emit_at(buf, *count, "Right port\n");
+ break;
+ default:
+ *count += sysfs_emit_at(buf, *count, "Undefined\n");
+ break;
+ }
+ damage_detected = true;
+ }
+ return damage_detected;
+}
+
+/* sysfs type-c damage detection detail */
+static ssize_t hwdd_detail_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int damage_status;
+ int err, count = 0;
+
+ if (!ucdd_supported)
+ return -ENODEV;
+
+ /* Get USB TYPE-C damage status */
+ err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
+ if (err)
+ return err;
+
+ if (!display_damage(buf, &count, "Type-C", damage_status))
+ count += sysfs_emit_at(buf, count, "No damage detected\n");
+
+ return count;
+}
+
+/* sysfs type-c damage detection capability */
+static ssize_t hwdd_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int damage_status, port_status;
+ int err, i;
+
+ if (!ucdd_supported)
+ return -ENODEV;
+
+ /* Get USB TYPE-C damage status */
+ err = hwdd_command(HWDD_GET_DMG_USBC, &damage_status);
+ if (err)
+ return err;
+
+ port_status = FIELD_GET(PORT_STATUS, damage_status);
+ for (i = 0; i < NUM_PORTS; i++) {
+ if (!(damage_status & BIT(i)))
+ continue;
+ if (port_status & BIT(i))
+ return sysfs_emit(buf, "1\n");
+ }
+
+ return sysfs_emit(buf, "0\n");
+}
+static DEVICE_ATTR_RO(hwdd_status);
+static DEVICE_ATTR_RO(hwdd_detail);
+
+static struct attribute *hwdd_attributes[] = {
+ &dev_attr_hwdd_status.attr,
+ &dev_attr_hwdd_detail.attr,
+ NULL
+};
+
+static umode_t hwdd_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ return hwdd_support_available ? attr->mode : 0;
+}
+
+static const struct attribute_group hwdd_attr_group = {
+ .is_visible = hwdd_attr_is_visible,
+ .attrs = hwdd_attributes,
+};
+
+static int tpacpi_hwdd_init(struct ibm_init_struct *iibm)
+{
+ int err, output;
+
+ /* Below command checks the HWDD damage capability */
+ err = hwdd_command(HWDD_GET_CAP, &output);
+ if (err)
+ return err;
+
+ if (!(output & HWDD_NOT_SUPPORTED))
+ return -ENODEV;
+
+ hwdd_support_available = true;
+
+ /*
+ * BIT(0) is assigned to check capability of damage detection is
+ * supported for USB Type-C port or not.
+ */
+ if (output & HWDD_SUPPORT_USBC)
+ ucdd_supported = true;
+
+ return err;
+}
+
+static struct ibm_struct hwdd_driver_data = {
+ .name = "hwdd",
+};
+
/* --------------------------------------------------------------------- */
static struct attribute *tpacpi_driver_attributes[] = {
@@ -11095,6 +11324,7 @@ static const struct attribute_group *tpacpi_groups[] = {
&kbdlang_attr_group,
&dprc_attr_group,
&auxmac_attr_group,
+ &hwdd_attr_group,
NULL,
};
@@ -11149,6 +11379,8 @@ static struct platform_driver tpacpi_hwmon_pdriver = {
*/
static bool tpacpi_driver_event(const unsigned int hkey_event)
{
+ int camera_shutter_state;
+
switch (hkey_event) {
case TP_HKEY_EV_BRGHT_UP:
case TP_HKEY_EV_BRGHT_DOWN:
@@ -11225,8 +11457,23 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
dytc_control_amt(!dytc_amt_active);
return true;
+ case TP_HKEY_EV_CAMERASHUTTER_TOGGLE:
+ camera_shutter_state = get_camera_shutter();
+ if (camera_shutter_state < 0) {
+ pr_err("Error retrieving camera shutter state after shutter event\n");
+ return true;
+ }
+ mutex_lock(&tpacpi_inputdev_send_mutex);
+
+ input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state);
+ input_sync(tpacpi_inputdev);
+
+ mutex_unlock(&tpacpi_inputdev_send_mutex);
+ return true;
case TP_HKEY_EV_DOUBLETAP_TOGGLE:
- tp_features.trackpoint_doubletap = !tp_features.trackpoint_doubletap;
+ /* Toggle kernel-level doubletap event filtering */
+ tp_features.trackpoint_doubletap_enable =
+ !tp_features.trackpoint_doubletap_enable;
return true;
case TP_HKEY_EV_PROFILE_TOGGLE:
case TP_HKEY_EV_PROFILE_TOGGLE2:
@@ -11270,6 +11517,8 @@ static void ibm_exit(struct ibm_struct *ibm)
acpi_remove_notify_handler(*ibm->acpi->handle,
ibm->acpi->type,
dispatch_acpi_notify);
+ ibm->acpi->device->driver_data = NULL;
+ acpi_dev_put(ibm->acpi->device);
ibm->flags.acpi_notify_installed = 0;
}
@@ -11280,16 +11529,6 @@ static void ibm_exit(struct ibm_struct *ibm)
ibm->flags.proc_created = 0;
}
- if (ibm->flags.acpi_driver_registered) {
- dbg_printk(TPACPI_DBG_EXIT,
- "%s: acpi_bus_unregister_driver\n", ibm->name);
- BUG_ON(!ibm->acpi);
- acpi_bus_unregister_driver(ibm->acpi->driver);
- kfree(ibm->acpi->driver);
- ibm->acpi->driver = NULL;
- ibm->flags.acpi_driver_registered = 0;
- }
-
if (ibm->flags.init_called && ibm->exit) {
ibm->exit();
ibm->flags.init_called = 0;
@@ -11325,12 +11564,6 @@ static int __init ibm_init(struct ibm_init_struct *iibm)
}
if (ibm->acpi) {
- if (ibm->acpi->hid) {
- ret = register_tpacpi_subdriver(ibm);
- if (ret)
- goto err_out;
- }
-
if (ibm->acpi->notify) {
ret = setup_acpi_notify(ibm);
if (ret == -ENODEV) {
@@ -11466,6 +11699,8 @@ static int __must_check __init get_thinkpad_model_data(
tp->vendor = PCI_VENDOR_ID_IBM;
else if (dmi_name_in_vendors("LENOVO"))
tp->vendor = PCI_VENDOR_ID_LENOVO;
+ else if (dmi_name_in_vendors("NEC"))
+ tp->vendor = PCI_VENDOR_ID_LENOVO;
else
return 0;
@@ -11691,6 +11926,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.init = auxmac_init,
.data = &auxmac_data,
},
+ {
+ .init = tpacpi_hwdd_init,
+ .data = &hwdd_driver_data,
+ },
};
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
@@ -11820,10 +12059,10 @@ static void thinkpad_acpi_module_exit(void)
platform_device_unregister(tpacpi_sensors_pdev);
}
- if (tpacpi_pdev) {
+ if (tp_features.platform_drv_registered)
platform_driver_unregister(&tpacpi_pdriver);
+ if (tpacpi_pdev)
platform_device_unregister(tpacpi_pdev);
- }
if (proc_dir)
remove_proc_entry(TPACPI_PROC_DIR, acpi_root_dir);
@@ -11893,9 +12132,8 @@ static int __init tpacpi_pdriver_probe(struct platform_device *pdev)
static int __init tpacpi_hwmon_pdriver_probe(struct platform_device *pdev)
{
- tpacpi_hwmon = devm_hwmon_device_register_with_groups(
- &tpacpi_sensors_pdev->dev, TPACPI_NAME, NULL, tpacpi_hwmon_groups);
-
+ tpacpi_hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, TPACPI_NAME,
+ NULL, tpacpi_hwmon_groups);
if (IS_ERR(tpacpi_hwmon))
pr_err("unable to register hwmon device\n");
@@ -11965,15 +12203,23 @@ static int __init thinkpad_acpi_module_init(void)
tp_features.quirks = dmi_id->driver_data;
/* Device initialization */
- tpacpi_pdev = platform_create_bundle(&tpacpi_pdriver, tpacpi_pdriver_probe,
- NULL, 0, NULL, 0);
+ tpacpi_pdev = platform_device_register_simple(TPACPI_DRVR_NAME, PLATFORM_DEVID_NONE,
+ NULL, 0);
if (IS_ERR(tpacpi_pdev)) {
ret = PTR_ERR(tpacpi_pdev);
tpacpi_pdev = NULL;
- pr_err("unable to register platform device/driver bundle\n");
+ pr_err("unable to register platform device\n");
+ thinkpad_acpi_module_exit();
+ return ret;
+ }
+
+ ret = platform_driver_probe(&tpacpi_pdriver, tpacpi_pdriver_probe);
+ if (ret) {
+ pr_err("unable to register main platform driver\n");
thinkpad_acpi_module_exit();
return ret;
}
+ tp_features.platform_drv_registered = 1;
tpacpi_sensors_pdev = platform_create_bundle(&tpacpi_hwmon_pdriver,
tpacpi_hwmon_pdriver_probe,
diff --git a/drivers/platform/x86/lenovo-wmi-camera.c b/drivers/platform/x86/lenovo/wmi-camera.c
index eb60fb9a5b3f..89ecbce60bf4 100644
--- a/drivers/platform/x86/lenovo-wmi-camera.c
+++ b/drivers/platform/x86/lenovo/wmi-camera.c
@@ -134,6 +134,7 @@ static struct wmi_driver lenovo_wmi_driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = lenovo_wmi_id_table,
+ .min_event_size = sizeof(u8),
.no_singleton = true,
.probe = lenovo_wmi_probe,
.notify = lenovo_wmi_notify,
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
new file mode 100644
index 000000000000..714aa6fd6f1f
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -0,0 +1,828 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Capability Data WMI Data Block driver.
+ *
+ * Lenovo Capability Data provides information on tunable attributes used by
+ * the "Other Mode" WMI interface.
+ *
+ * Capability Data 00 includes if the attribute is supported by the hardware,
+ * and the default_value. All attributes are independent of thermal modes.
+ *
+ * Capability Data 01 includes if the attribute is supported by the hardware,
+ * and the default_value, max_value, min_value, and step increment. Each
+ * attribute has multiple pages, one for each of the thermal modes managed by
+ * the Gamezone interface.
+ *
+ * Fan Test Data includes the max/min fan speed RPM for each fan. This is
+ * reference data for self-test. If the fan is in good condition, it is capable
+ * to spin faster than max RPM or slower than min RPM.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ * - Initial implementation (formerly named lenovo-wmi-capdata01)
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - Unified implementation
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mutex_types.h>
+#include <linux/notifier.h>
+#include <linux/overflow.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-capdata.h"
+#include "wmi-helpers.h"
+
+#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
+
+#define ACPI_AC_NOTIFY_STATUS 0x80
+
+#define LWMI_FEATURE_ID_FAN_TEST 0x05
+
+#define LWMI_ATTR_ID_FAN_TEST \
+ lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_TEST, \
+ LWMI_GZ_THERMAL_MODE_NONE, LWMI_TYPE_ID_NONE)
+
+enum lwmi_cd_type {
+ LENOVO_CAPABILITY_DATA_00,
+ LENOVO_CAPABILITY_DATA_01,
+ LENOVO_FAN_TEST_DATA,
+ CD_TYPE_NONE = -1,
+};
+
+#define LWMI_CD_TABLE_ITEM(_type) \
+ [_type] = { \
+ .name = #_type, \
+ .type = _type, \
+ }
+
+static const struct lwmi_cd_info {
+ const char *name;
+ enum lwmi_cd_type type;
+} lwmi_cd_table[] = {
+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
+ LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01),
+ LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA),
+};
+
+struct lwmi_cd_priv {
+ struct notifier_block acpi_nb; /* ACPI events */
+ struct wmi_device *wdev;
+ struct cd_list *list;
+
+ /*
+ * A capdata device may be a component master of another capdata device.
+ * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan
+ * |- master |- component
+ * |- sub-master
+ * |- sub-component
+ */
+ struct lwmi_cd_sub_master_priv {
+ struct device *master_dev;
+ cd_list_cb_t master_cb;
+ struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */
+ bool registered; /* Has the sub-master been registered? */
+ } *sub_master;
+};
+
+struct cd_list {
+ struct mutex list_mutex; /* list R/W mutex */
+ enum lwmi_cd_type type;
+ u8 count;
+
+ union {
+ DECLARE_FLEX_ARRAY(struct capdata00, cd00);
+ DECLARE_FLEX_ARRAY(struct capdata01, cd01);
+ DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan);
+ };
+};
+
+static struct wmi_driver lwmi_cd_driver;
+
+/**
+ * lwmi_cd_match() - Match rule for the master driver.
+ * @dev: Pointer to the capability data parent device.
+ * @type: Pointer to capability data type (enum lwmi_cd_type *) to match.
+ *
+ * Return: int.
+ */
+static int lwmi_cd_match(struct device *dev, void *type)
+{
+ struct lwmi_cd_priv *priv;
+
+ if (dev->driver != &lwmi_cd_driver.driver)
+ return false;
+
+ priv = dev_get_drvdata(dev);
+ return priv->list->type == *(enum lwmi_cd_type *)type;
+}
+
+/**
+ * lwmi_cd_match_add_all() - Add all match rule for the master driver.
+ * @master: Pointer to the master device.
+ * @matchptr: Pointer to the returned component_match pointer.
+ *
+ * Adds all component matches to the list stored in @matchptr for the @master
+ * device. @matchptr must be initialized to NULL.
+ */
+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
+{
+ int i;
+
+ if (WARN_ON(*matchptr))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
+ /* Skip sub-components. */
+ if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
+ continue;
+
+ component_match_add(master, matchptr, lwmi_cd_match,
+ (void *)&lwmi_cd_table[i].type);
+ if (IS_ERR(*matchptr))
+ return;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
+
+/**
+ * lwmi_cd_call_master_cb() - Call the master callback for the sub-component.
+ * @priv: Pointer to the capability data private data.
+ *
+ * Call the master callback and pass the sub-component list to it if the
+ * dependency chain (master <-> sub-master <-> sub-component) is complete.
+ */
+static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv)
+{
+ struct cd_list *sub_component_list = priv->sub_master->sub_component_list;
+
+ /*
+ * Call the callback only if the dependency chain is ready:
+ * - Binding between master and sub-master: fills master_dev and master_cb
+ * - Binding between sub-master and sub-component: fills sub_component_list
+ *
+ * If a binding has been unbound before the other binding is bound, the
+ * corresponding members filled by the former are guaranteed to be cleared.
+ *
+ * This function is only called in bind callbacks, and the component
+ * framework guarantees bind/unbind callbacks may never execute
+ * simultaneously, which implies that it's impossible to have a race
+ * condition.
+ *
+ * Hence, this check is sufficient to ensure that the callback is called
+ * at most once and with the correct state, without relying on a specific
+ * sequence of binding establishment.
+ */
+ if (!sub_component_list ||
+ !priv->sub_master->master_dev ||
+ !priv->sub_master->master_cb)
+ return;
+
+ if (PTR_ERR(sub_component_list) == -ENODEV)
+ sub_component_list = NULL;
+ else if (WARN_ON(IS_ERR(sub_component_list)))
+ return;
+
+ priv->sub_master->master_cb(priv->sub_master->master_dev,
+ sub_component_list);
+
+ /*
+ * Userspace may unbind a device from its driver and bind it again
+ * through sysfs. Let's call this operation "reprobe" to distinguish it
+ * from component "rebind".
+ *
+ * When reprobing capdata00/01 or the master device, the master device
+ * is unbound from us with appropriate cleanup before we bind to it and
+ * call master_cb. Everything is fine in this case.
+ *
+ * When reprobing capdata_fan, the master device has never been unbound
+ * from us (hence no cleanup is done)[1], but we call master_cb the
+ * second time. To solve this issue, we clear master_cb and master_dev
+ * so we won't call master_cb twice while a binding is still complete.
+ *
+ * Note that we can't clear sub_component_list, otherwise reprobing
+ * capdata01 or the master device causes master_cb to be never called
+ * after we rebind to the master device.
+ *
+ * [1]: The master device does not need capdata_fan in run time, so
+ * losing capdata_fan will not break the binding to the master device.
+ */
+ priv->sub_master->master_cb = NULL;
+ priv->sub_master->master_dev = NULL;
+}
+
+/**
+ * lwmi_cd_component_bind() - Bind component to master device.
+ * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: lwmi_cd_binder object pointer used to return the capability data.
+ *
+ * On lenovo-wmi-other's master bind, provide a pointer to the local capdata
+ * list. This is used to call lwmi_cd*_get_data to look up attribute data
+ * from the lenovo-wmi-other driver.
+ *
+ * If cd_dev is a sub-master, try to call the master callback.
+ *
+ * Return: 0
+ */
+static int lwmi_cd_component_bind(struct device *cd_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
+ struct lwmi_cd_binder *binder = data;
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ binder->cd00_list = priv->list;
+
+ priv->sub_master->master_dev = om_dev;
+ priv->sub_master->master_cb = binder->cd_fan_list_cb;
+ lwmi_cd_call_master_cb(priv);
+
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ binder->cd01_list = priv->list;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_component_unbind() - Unbind component to master device.
+ * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: Unused.
+ *
+ * If cd_dev is a sub-master, clear the collected data from the master device to
+ * prevent the binding establishment between the sub-master and the sub-
+ * component (if it's about to happen) from calling the master callback.
+ */
+static void lwmi_cd_component_unbind(struct device *cd_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ priv->sub_master->master_dev = NULL;
+ priv->sub_master->master_cb = NULL;
+ return;
+ default:
+ return;
+ }
+}
+
+static const struct component_ops lwmi_cd_component_ops = {
+ .bind = lwmi_cd_component_bind,
+ .unbind = lwmi_cd_component_unbind,
+};
+
+/**
+ * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device
+ * @dev: The sub-master capdata basic device.
+ *
+ * Call component_bind_all to bind the sub-component device to the sub-master
+ * device. On success, collect the pointer to the sub-component list and try
+ * to call the master callback.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_sub_master_bind(struct device *dev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
+ struct cd_list *sub_component_list;
+ int ret;
+
+ ret = component_bind_all(dev, &sub_component_list);
+ if (ret)
+ return ret;
+
+ priv->sub_master->sub_component_list = sub_component_list;
+ lwmi_cd_call_master_cb(priv);
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device
+ * @dev: The sub-master capdata basic device
+ *
+ * Clear the collected pointer to the sub-component list to prevent the binding
+ * establishment between the sub-master and the sub-component (if it's about to
+ * happen) from calling the master callback. Then, call component_unbind_all to
+ * unbind the sub-component device from the sub-master device.
+ */
+static void lwmi_cd_sub_master_unbind(struct device *dev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
+
+ priv->sub_master->sub_component_list = NULL;
+
+ component_unbind_all(dev, NULL);
+}
+
+static const struct component_master_ops lwmi_cd_sub_master_ops = {
+ .bind = lwmi_cd_sub_master_bind,
+ .unbind = lwmi_cd_sub_master_unbind,
+};
+
+/**
+ * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component
+ * @priv: Pointer to the sub-master capdata device private data.
+ * @sub_component_type: Type of the sub-component.
+ *
+ * Match the sub-component type and register the current capdata device as a
+ * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub-
+ * component as non-existent without registering sub-master.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
+ enum lwmi_cd_type sub_component_type)
+{
+ struct component_match *master_match = NULL;
+ int ret;
+
+ priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
+ if (!priv->sub_master)
+ return -ENOMEM;
+
+ if (sub_component_type == CD_TYPE_NONE) {
+ /* The master callback will be called with NULL on bind. */
+ priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
+ priv->sub_master->registered = false;
+ return 0;
+ }
+
+ /*
+ * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
+ * data cannot be used here. Steal one from lwmi_cd_table.
+ */
+ component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match,
+ (void *)&lwmi_cd_table[sub_component_type].type);
+ if (IS_ERR(master_match))
+ return PTR_ERR(master_match);
+
+ ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops,
+ master_match);
+ if (ret)
+ return ret;
+
+ priv->sub_master->registered = true;
+ return 0;
+}
+
+/**
+ * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered
+ * @priv: Pointer to the sub-master capdata device private data.
+ */
+static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
+{
+ if (!priv->sub_master->registered)
+ return;
+
+ component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
+ priv->sub_master->registered = false;
+}
+
+/**
+ * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device.
+ * @sc_dev: Pointer to the sub-component capdata parent device.
+ * @sm_dev: Pointer to the sub-master capdata parent device.
+ * @data: Pointer used to return the capability data list pointer.
+ *
+ * On sub-master's bind, provide a pointer to the local capdata list.
+ * This is used by the sub-master to call the master callback.
+ *
+ * Return: 0
+ */
+static int lwmi_cd_sub_component_bind(struct device *sc_dev,
+ struct device *sm_dev, void *data)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev);
+ struct cd_list **listp = data;
+
+ *listp = priv->list;
+
+ return 0;
+}
+
+static const struct component_ops lwmi_cd_sub_component_ops = {
+ .bind = lwmi_cd_sub_component_bind,
+};
+
+/*
+ * lwmi_cd*_get_data - Get the data of the specified attribute
+ * @list: The lenovo-wmi-capdata pointer to its cd_list struct.
+ * @attribute_id: The capdata attribute ID to be found.
+ * @output: Pointer to a capdata* struct to return the data.
+ *
+ * Retrieves the capability data struct pointer for the given
+ * attribute.
+ *
+ * Return: 0 on success, or -EINVAL.
+ */
+#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \
+ int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \
+ { \
+ u8 idx; \
+ \
+ if (WARN_ON(list->type != _cd_type)) \
+ return -EINVAL; \
+ \
+ guard(mutex)(&list->list_mutex); \
+ for (idx = 0; idx < list->count; idx++) { \
+ if (list->_cdxx[idx].id != attribute_id) \
+ continue; \
+ memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \
+ return 0; \
+ } \
+ return -EINVAL; \
+ }
+
+DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
+
+DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
+
+DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
+EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
+
+/**
+ * lwmi_cd_cache() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata driver data.
+ *
+ * Loop through each WMI data block and cache the data.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
+{
+ size_t size;
+ int idx;
+ void *p;
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ p = &priv->list->cd00[0];
+ size = sizeof(priv->list->cd00[0]);
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ p = &priv->list->cd01[0];
+ size = sizeof(priv->list->cd01[0]);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&priv->list->list_mutex);
+ for (idx = 0; idx < priv->list->count; idx++, p += size) {
+ union acpi_object *ret_obj __free(kfree) = NULL;
+
+ ret_obj = wmidev_block_query(priv->wdev, idx);
+ if (!ret_obj)
+ return -ENODEV;
+
+ if (ret_obj->type != ACPI_TYPE_BUFFER ||
+ ret_obj->buffer.length < size)
+ continue;
+
+ memcpy(p, ret_obj->buffer.pointer, size);
+ }
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list
+ * @priv: lenovo-wmi-capdata driver data.
+ * @listptr: Pointer to returned cd_list pointer.
+ *
+ * Return: count of fans found, or an error.
+ */
+static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr)
+{
+ struct cd_list *list;
+ size_t size;
+ u32 count;
+ int idx;
+
+ /* Emit unaligned access to u8 buffer with __packed. */
+ struct cd_fan_block {
+ u32 nr;
+ u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
+ } __packed * block;
+
+ union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
+ if (!ret_obj)
+ return -ENODEV;
+
+ if (ret_obj->type == ACPI_TYPE_BUFFER) {
+ block = (struct cd_fan_block *)ret_obj->buffer.pointer;
+ size = ret_obj->buffer.length;
+
+ count = size >= sizeof(*block) ? block->nr : 0;
+ if (size < struct_size(block, data, count * 3)) {
+ dev_warn(&priv->wdev->dev,
+ "incomplete fan test data block: %zu < %zu, ignoring\n",
+ size, struct_size(block, data, count * 3));
+ count = 0;
+ } else if (count > U8_MAX) {
+ dev_warn(&priv->wdev->dev,
+ "too many fans reported: %u > %u, truncating\n",
+ count, U8_MAX);
+ count = U8_MAX;
+ }
+ } else {
+ /*
+ * This is usually caused by a dummy ACPI method. Do not return an error
+ * as failing to probe this device will result in sub-master device being
+ * unbound. This behavior aligns with lwmi_cd_cache().
+ */
+ count = 0;
+ }
+
+ list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ for (idx = 0; idx < count; idx++) {
+ /* Do not calculate array index using count, as it may be truncated. */
+ list->cd_fan[idx] = (struct capdata_fan) {
+ .id = block->data[idx],
+ .max_rpm = block->data[idx + block->nr],
+ .min_rpm = block->data[idx + (2 * block->nr)],
+ };
+ }
+
+ *listptr = list;
+ return count;
+}
+
+/**
+ * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
+ * @priv: lenovo-wmi-capdata driver data.
+ * @type: The type of capability data.
+ *
+ * Allocate a cd_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+{
+ struct cd_list *list;
+ size_t list_size;
+ int count, ret;
+
+ count = wmidev_instance_count(priv->wdev);
+
+ switch (type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ list_size = struct_size(list, cd00, count);
+ break;
+ case LENOVO_CAPABILITY_DATA_01:
+ list_size = struct_size(list, cd01, count);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ count = lwmi_cd_fan_list_alloc_cache(priv, &list);
+ if (count < 0)
+ return count;
+
+ goto got_list;
+ default:
+ return -EINVAL;
+ }
+
+ list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+got_list:
+ ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
+ if (ret)
+ return ret;
+
+ list->type = type;
+ list->count = count;
+ priv->list = list;
+
+ return 0;
+}
+
+/**
+ * lwmi_cd_setup() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata driver data.
+ * @type: The type of capability data.
+ *
+ * Allocate a cd_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface. Then loop through each data block and
+ * cache the data.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
+{
+ int ret;
+
+ ret = lwmi_cd_alloc(priv, type);
+ if (ret)
+ return ret;
+
+ return lwmi_cd_cache(priv);
+}
+
+/**
+ * lwmi_cd01_notifier_call() - Call method for cd01 notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @action: Unused.
+ * @data: The ACPI event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct acpi_bus_event *event = data;
+ struct lwmi_cd_priv *priv;
+ int ret;
+
+ if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
+ return NOTIFY_DONE;
+
+ priv = container_of(nb, struct lwmi_cd_priv, acpi_nb);
+
+ switch (event->type) {
+ case ACPI_AC_NOTIFY_STATUS:
+ ret = lwmi_cd_cache(priv);
+ if (ret)
+ return NOTIFY_BAD;
+
+ return NOTIFY_OK;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+/**
+ * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
+ * @data: The ACPI event notifier_block to unregister.
+ */
+static void lwmi_cd01_unregister(void *data)
+{
+ struct notifier_block *acpi_nb = data;
+
+ unregister_acpi_notifier(acpi_nb);
+}
+
+static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
+{
+ const struct lwmi_cd_info *info = context;
+ struct lwmi_cd_priv *priv;
+ int ret;
+
+ if (!info)
+ return -EINVAL;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wdev = wdev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ ret = lwmi_cd_setup(priv, info->type);
+ if (ret)
+ goto out;
+
+ switch (info->type) {
+ case LENOVO_CAPABILITY_DATA_00: {
+ enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
+ struct capdata00 capdata00;
+
+ ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
+ if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
+ dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
+ sub_component_type = CD_TYPE_NONE;
+ }
+
+ /* Sub-master (capdata00) <-> sub-component (capdata_fan) */
+ ret = lwmi_cd_sub_master_add(priv, sub_component_type);
+ if (ret)
+ goto out;
+
+ /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
+ if (ret)
+ lwmi_cd_sub_master_del(priv);
+
+ goto out;
+ }
+ case LENOVO_CAPABILITY_DATA_01:
+ priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
+
+ ret = register_acpi_notifier(&priv->acpi_nb);
+ if (ret)
+ goto out;
+
+ ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
+ &priv->acpi_nb);
+ if (ret)
+ goto out;
+
+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
+ goto out;
+ case LENOVO_FAN_TEST_DATA:
+ ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
+ goto out;
+ default:
+ return -EINVAL;
+ }
+out:
+ if (ret) {
+ dev_err(&wdev->dev, "failed to register %s: %d\n",
+ info->name, ret);
+ } else {
+ dev_dbg(&wdev->dev, "registered %s with %u items\n",
+ info->name, priv->list->count);
+ }
+ return ret;
+}
+
+static void lwmi_cd_remove(struct wmi_device *wdev)
+{
+ struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
+
+ switch (priv->list->type) {
+ case LENOVO_CAPABILITY_DATA_00:
+ lwmi_cd_sub_master_del(priv);
+ fallthrough;
+ case LENOVO_CAPABILITY_DATA_01:
+ component_del(&wdev->dev, &lwmi_cd_component_ops);
+ break;
+ case LENOVO_FAN_TEST_DATA:
+ component_del(&wdev->dev, &lwmi_cd_sub_component_ops);
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
+
+#define LWMI_CD_WDEV_ID(_type) \
+ .guid_string = _type##_GUID, \
+ .context = &lwmi_cd_table[_type],
+
+static const struct wmi_device_id lwmi_cd_id_table[] = {
+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) },
+ { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) },
+ { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) },
+ {}
+};
+
+static struct wmi_driver lwmi_cd_driver = {
+ .driver = {
+ .name = "lenovo_wmi_capdata",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lwmi_cd_id_table,
+ .probe = lwmi_cd_probe,
+ .remove = lwmi_cd_remove,
+ .no_singleton = true,
+};
+
+module_wmi_driver(lwmi_cd_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
+MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
new file mode 100644
index 000000000000..c3e760b8c3c3
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_CAPDATA_H_
+#define _LENOVO_WMI_CAPDATA_H_
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/types.h>
+
+#define LWMI_SUPP_VALID BIT(0)
+#define LWMI_SUPP_GET BIT(1)
+#define LWMI_SUPP_SET BIT(2)
+
+#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
+#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
+#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
+#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
+
+#define LWMI_DEVICE_ID_FAN 0x04
+
+#define LWMI_TYPE_ID_NONE 0x00
+
+struct component_match;
+struct device;
+struct cd_list;
+
+struct capdata00 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+};
+
+struct capdata01 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+ u32 step;
+ u32 min_value;
+ u32 max_value;
+};
+
+struct capdata_fan {
+ u32 id;
+ u32 min_rpm;
+ u32 max_rpm;
+};
+
+typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd_list);
+
+struct lwmi_cd_binder {
+ struct cd_list *cd00_list;
+ struct cd_list *cd01_list;
+ /*
+ * May be called during or after the bind callback.
+ * Will be called with NULL if capdata_fan does not exist.
+ * The pointer is only valid in the callback; never keep it for later use!
+ */
+ cd_list_cb_t cd_fan_list_cb;
+};
+
+/**
+ * lwmi_attr_id() - Formats a capability data attribute ID
+ * @dev_id: The u8 corresponding to the device ID.
+ * @feat_id: The u8 corresponding to the feature ID on the device.
+ * @mode_id: The u8 corresponding to the wmi-gamezone mode for set/get.
+ * @type_id: The u8 corresponding to the sub-device.
+ *
+ * Return: encoded capability data attribute ID.
+ */
+static inline u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
+{
+ return (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, dev_id) |
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, feat_id) |
+ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode_id) |
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type_id));
+}
+
+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
+int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output);
+int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
+int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output);
+
+#endif /* !_LENOVO_WMI_CAPDATA_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-events.c b/drivers/platform/x86/lenovo/wmi-events.c
new file mode 100644
index 000000000000..fc25bba68a7c
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-events.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
+ * hardware triggered events that many drivers need to have propagated.
+ * This driver provides a uniform entrypoint for these events so that
+ * any driver that needs to respond to these events can subscribe to a
+ * notifier chain.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-events.h"
+#include "wmi-helpers.h"
+
+#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
+
+#define LWMI_EVENT_DEVICE(guid, type) \
+ .guid_string = (guid), .context = &(enum lwmi_events_type) \
+ { \
+ type \
+ }
+
+static BLOCKING_NOTIFIER_HEAD(events_chain_head);
+
+struct lwmi_events_priv {
+ struct wmi_device *wdev;
+ enum lwmi_events_type type;
+};
+
+/**
+ * lwmi_events_register_notifier() - Add a notifier to the notifier chain.
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_register to register the notifier block to the
+ * lenovo-wmi-events driver blocking notifier chain.
+ *
+ * Return: 0 on success, %-EEXIST on error.
+ */
+int lwmi_events_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&events_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
+
+/**
+ * lwmi_events_unregister_notifier() - Remove a notifier from the notifier
+ * chain.
+ * @nb: The notifier_block struct to unregister
+ *
+ * Call blocking_notifier_chain_unregister to unregister the notifier block
+ * from the lenovo-wmi-events driver blocking notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+int lwmi_events_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&events_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
+
+/**
+ * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
+ * chain.
+ * @data: Void pointer to the notifier_block struct to unregister.
+ *
+ * Call lwmi_events_unregister_notifier to unregister the notifier block from
+ * the lenovo-wmi-events driver blocking notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+static void devm_lwmi_events_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ lwmi_events_unregister_notifier(nb);
+}
+
+/**
+ * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
+ * @dev: The parent device of the notifier_block struct.
+ * @nb: The notifier_block struct to register
+ *
+ * Call lwmi_events_register_notifier to register the notifier block to the
+ * lenovo-wmi-events driver blocking notifier chain. Then add, as a device
+ * managed action, unregister_notifier to automatically unregister the
+ * notifier block upon its parent device removal.
+ *
+ * Return: 0 on success, or an error code.
+ */
+int devm_lwmi_events_register_notifier(struct device *dev,
+ struct notifier_block *nb)
+{
+ int ret;
+
+ ret = lwmi_events_register_notifier(nb);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_lwmi_events_unregister_notifier, nb);
+}
+EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
+
+/**
+ * lwmi_events_notify() - Call functions for the notifier call chain.
+ * @wdev: The parent WMI device of the driver.
+ * @obj: ACPI object passed by the registered WMI Event.
+ *
+ * Validate WMI event data and notify all registered drivers of the event and
+ * its output.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
+{
+ struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
+ int sel_prof;
+ int ret;
+
+ switch (priv->type) {
+ case LWMI_EVENT_THERMAL_MODE:
+ if (obj->type != ACPI_TYPE_INTEGER)
+ return;
+
+ sel_prof = obj->integer.value;
+
+ switch (sel_prof) {
+ case LWMI_GZ_THERMAL_MODE_QUIET:
+ case LWMI_GZ_THERMAL_MODE_BALANCED:
+ case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
+ case LWMI_GZ_THERMAL_MODE_EXTREME:
+ case LWMI_GZ_THERMAL_MODE_CUSTOM:
+ ret = blocking_notifier_call_chain(&events_chain_head,
+ LWMI_EVENT_THERMAL_MODE,
+ &sel_prof);
+ if (ret == NOTIFY_BAD)
+ dev_err(&wdev->dev,
+ "Failed to send notification to call chain for WMI Events\n");
+ return;
+ default:
+ dev_err(&wdev->dev, "Got invalid thermal mode: %x",
+ sel_prof);
+ return;
+ }
+ break;
+ default:
+ return;
+ }
+}
+
+static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
+{
+ struct lwmi_events_priv *priv;
+
+ if (!context)
+ return -EINVAL;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wdev = wdev;
+ priv->type = *(enum lwmi_events_type *)context;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ return 0;
+}
+
+static const struct wmi_device_id lwmi_events_id_table[] = {
+ { LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
+ {}
+};
+
+static struct wmi_driver lwmi_events_driver = {
+ .driver = {
+ .name = "lenovo_wmi_events",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lwmi_events_id_table,
+ .min_event_size = sizeof(u32),
+ .probe = lwmi_events_probe,
+ .notify = lwmi_events_notify,
+ .no_singleton = true,
+};
+
+module_wmi_driver(lwmi_events_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo WMI Events Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-events.h b/drivers/platform/x86/lenovo/wmi-events.h
new file mode 100644
index 000000000000..cd34e886912c
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-events.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_EVENTS_H_
+#define _LENOVO_WMI_EVENTS_H_
+
+struct device;
+struct notifier_block;
+
+enum lwmi_events_type {
+ LWMI_EVENT_THERMAL_MODE = 1,
+};
+
+int lwmi_events_register_notifier(struct notifier_block *nb);
+int lwmi_events_unregister_notifier(struct notifier_block *nb);
+int devm_lwmi_events_register_notifier(struct device *dev,
+ struct notifier_block *nb);
+
+#endif /* !_LENOVO_WMI_EVENTS_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.c b/drivers/platform/x86/lenovo/wmi-gamezone.c
new file mode 100644
index 000000000000..109c0b564a9f
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-gamezone.c
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo GameZone WMI interface driver.
+ *
+ * The GameZone WMI interface provides platform profile and fan curve settings
+ * for devices that fall under the "Gaming Series" of Lenovo Legion devices.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/export.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/spinlock.h>
+#include <linux/spinlock_types.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-events.h"
+#include "wmi-helpers.h"
+
+#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+
+#define LWMI_GZ_METHOD_ID_SMARTFAN_SUP 43
+#define LWMI_GZ_METHOD_ID_SMARTFAN_SET 44
+#define LWMI_GZ_METHOD_ID_SMARTFAN_GET 45
+
+struct lwmi_gz_priv {
+ enum thermal_mode current_mode;
+ struct notifier_block event_nb;
+ struct notifier_block mode_nb;
+ spinlock_t gz_mode_lock; /* current_mode lock */
+ struct wmi_device *wdev;
+ int extreme_supported;
+ struct device *ppdev;
+};
+
+struct quirk_entry {
+ bool extreme_supported;
+};
+
+static struct quirk_entry quirk_no_extreme_bug = {
+ .extreme_supported = false,
+};
+
+/**
+ * lwmi_gz_mode_call() - Call method for lenovo-wmi-other driver notifier.
+ *
+ * @nb: The notifier_block registered to lenovo-wmi-other driver.
+ * @cmd: The event type.
+ * @data: Thermal mode enum pointer pointer for returning the thermal mode.
+ *
+ * For LWMI_GZ_GET_THERMAL_MODE, retrieve the current thermal mode.
+ *
+ * Return: Notifier_block status.
+ */
+static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
+ void *data)
+{
+ enum thermal_mode **mode = data;
+ struct lwmi_gz_priv *priv;
+
+ priv = container_of(nb, struct lwmi_gz_priv, mode_nb);
+
+ switch (cmd) {
+ case LWMI_GZ_GET_THERMAL_MODE:
+ scoped_guard(spinlock, &priv->gz_mode_lock) {
+ **mode = priv->current_mode;
+ }
+ return NOTIFY_OK;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+/**
+ * lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @cmd: The event type.
+ * @data: The data to be updated by the event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
+ void *data)
+{
+ enum thermal_mode *mode = data;
+ struct lwmi_gz_priv *priv;
+
+ priv = container_of(nb, struct lwmi_gz_priv, event_nb);
+
+ switch (cmd) {
+ case LWMI_EVENT_THERMAL_MODE:
+ scoped_guard(spinlock, &priv->gz_mode_lock) {
+ priv->current_mode = *mode;
+ }
+ platform_profile_notify(priv->ppdev);
+ return NOTIFY_STOP;
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+/**
+ * lwmi_gz_thermal_mode_supported() - Get the version of the WMI
+ * interface to determine the support level.
+ * @wdev: The Gamezone WMI device.
+ * @supported: Pointer to return the support level with.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_thermal_mode_supported(struct wmi_device *wdev,
+ int *supported)
+{
+ return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_SUP,
+ NULL, 0, supported);
+}
+
+/**
+ * lwmi_gz_thermal_mode_get() - Get the current thermal mode.
+ * @wdev: The Gamezone interface WMI device.
+ * @mode: Pointer to return the thermal mode with.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
+ enum thermal_mode *mode)
+{
+ return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_GET,
+ NULL, 0, mode);
+}
+
+/**
+ * lwmi_gz_profile_get() - Get the current platform profile.
+ * @dev: the Gamezone interface parent device.
+ * @profile: Pointer to provide the current platform profile with.
+ *
+ * Call lwmi_gz_thermal_mode_get and convert the thermal mode into a platform
+ * profile based on the support level of the interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_profile_get(struct device *dev,
+ enum platform_profile_option *profile)
+{
+ struct lwmi_gz_priv *priv = dev_get_drvdata(dev);
+ enum thermal_mode mode;
+ int ret;
+
+ ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
+ if (ret)
+ return ret;
+
+ switch (mode) {
+ case LWMI_GZ_THERMAL_MODE_QUIET:
+ *profile = PLATFORM_PROFILE_LOW_POWER;
+ break;
+ case LWMI_GZ_THERMAL_MODE_BALANCED:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ break;
+ case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case LWMI_GZ_THERMAL_MODE_EXTREME:
+ *profile = PLATFORM_PROFILE_MAX_POWER;
+ break;
+ case LWMI_GZ_THERMAL_MODE_CUSTOM:
+ *profile = PLATFORM_PROFILE_CUSTOM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(spinlock)(&priv->gz_mode_lock);
+ priv->current_mode = mode;
+
+ return 0;
+}
+
+/**
+ * lwmi_gz_profile_set() - Set the current platform profile.
+ * @dev: The Gamezone interface parent device.
+ * @profile: Pointer to the desired platform profile.
+ *
+ * Convert the given platform profile into a thermal mode based on the support
+ * level of the interface, then call the WMI method to set the thermal mode.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_profile_set(struct device *dev,
+ enum platform_profile_option profile)
+{
+ struct lwmi_gz_priv *priv = dev_get_drvdata(dev);
+ struct wmi_method_args_32 args = {};
+ enum thermal_mode mode;
+ int ret;
+
+ switch (profile) {
+ case PLATFORM_PROFILE_LOW_POWER:
+ mode = LWMI_GZ_THERMAL_MODE_QUIET;
+ break;
+ case PLATFORM_PROFILE_BALANCED:
+ mode = LWMI_GZ_THERMAL_MODE_BALANCED;
+ break;
+ case PLATFORM_PROFILE_PERFORMANCE:
+ mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_MAX_POWER:
+ mode = LWMI_GZ_THERMAL_MODE_EXTREME;
+ break;
+ case PLATFORM_PROFILE_CUSTOM:
+ mode = LWMI_GZ_THERMAL_MODE_CUSTOM;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ args.arg0 = mode;
+
+ ret = lwmi_dev_evaluate_int(priv->wdev, 0x0,
+ LWMI_GZ_METHOD_ID_SMARTFAN_SET,
+ (u8 *)&args, sizeof(args), NULL);
+ if (ret)
+ return ret;
+
+ guard(spinlock)(&priv->gz_mode_lock);
+ priv->current_mode = mode;
+
+ return 0;
+}
+
+static const struct dmi_system_id fwbug_list[] = {
+ {
+ .ident = "Legion Go 8APU1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
+ },
+ .driver_data = &quirk_no_extreme_bug,
+ },
+ {
+ .ident = "Legion Go S 8APU1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
+ },
+ .driver_data = &quirk_no_extreme_bug,
+ },
+ {
+ .ident = "Legion Go S 8ARP1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
+ },
+ .driver_data = &quirk_no_extreme_bug,
+ },
+ {
+ .ident = "Legion Go 8ASP2",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8ASP2"),
+ },
+ .driver_data = &quirk_no_extreme_bug,
+ },
+ {
+ .ident = "Legion Go 8AHP2",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8AHP2"),
+ },
+ .driver_data = &quirk_no_extreme_bug,
+ },
+ {},
+};
+
+/**
+ * lwmi_gz_extreme_supported() - Evaluate if a device supports extreme thermal mode.
+ * @profile_support_ver: Version of the WMI interface.
+ *
+ * Determine if the extreme thermal mode is supported by the hardware.
+ * Anything version 5 or lower does not. For devices with a version 6 or
+ * greater do a DMI check, as some devices report a version that supports
+ * extreme mode but have an incomplete entry in the BIOS. To ensure this
+ * cannot be set, quirk them to prevent assignment.
+ *
+ * Return: bool.
+ */
+static bool lwmi_gz_extreme_supported(int profile_support_ver)
+{
+ const struct dmi_system_id *dmi_id;
+ struct quirk_entry *quirks;
+
+ if (profile_support_ver < 6)
+ return false;
+
+ dmi_id = dmi_first_match(fwbug_list);
+ if (!dmi_id)
+ return true;
+
+ quirks = dmi_id->driver_data;
+
+ return quirks->extreme_supported;
+}
+
+/**
+ * lwmi_gz_platform_profile_probe - Enable and set up the platform profile
+ * device.
+ * @drvdata: Driver data for the interface.
+ * @choices: Container for enabled platform profiles.
+ *
+ * Determine if thermal mode is supported, and if so to what feature level.
+ * Then enable all supported platform profiles.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+ struct lwmi_gz_priv *priv = drvdata;
+ int profile_support_ver;
+ int ret;
+
+ ret = lwmi_gz_thermal_mode_supported(priv->wdev, &profile_support_ver);
+ if (ret)
+ return ret;
+
+ if (profile_support_ver < 1)
+ return -ENODEV;
+
+ set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+ set_bit(PLATFORM_PROFILE_CUSTOM, choices);
+
+ priv->extreme_supported = lwmi_gz_extreme_supported(profile_support_ver);
+ if (priv->extreme_supported)
+ set_bit(PLATFORM_PROFILE_MAX_POWER, choices);
+
+ return 0;
+}
+
+static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
+ .probe = lwmi_gz_platform_profile_probe,
+ .profile_get = lwmi_gz_profile_get,
+ .profile_set = lwmi_gz_profile_set,
+};
+
+static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
+{
+ struct lwmi_gz_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->wdev = wdev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ priv->ppdev = devm_platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
+ priv, &lwmi_gz_platform_profile_ops);
+ if (IS_ERR(priv->ppdev))
+ return -ENODEV;
+
+ spin_lock_init(&priv->gz_mode_lock);
+
+ ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
+ if (ret)
+ return ret;
+
+ priv->event_nb.notifier_call = lwmi_gz_event_call;
+ ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
+ if (ret)
+ return ret;
+
+ priv->mode_nb.notifier_call = lwmi_gz_mode_call;
+ return devm_lwmi_tm_register_notifier(&wdev->dev, &priv->mode_nb);
+}
+
+static const struct wmi_device_id lwmi_gz_id_table[] = {
+ { LENOVO_GAMEZONE_GUID, NULL },
+ {}
+};
+
+static struct wmi_driver lwmi_gz_driver = {
+ .driver = {
+ .name = "lenovo_wmi_gamezone",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lwmi_gz_id_table,
+ .probe = lwmi_gz_probe,
+ .no_singleton = true,
+};
+
+module_wmi_driver(lwmi_gz_driver);
+
+MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
+MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
+MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
new file mode 100644
index 000000000000..7a198259e393
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Legion WMI helpers driver.
+ *
+ * The Lenovo Legion WMI interface is broken up into multiple GUID interfaces
+ * that require cross-references between GUID's for some functionality. The
+ * "Custom Mode" interface is a legacy interface for managing and displaying
+ * CPU & GPU power and hwmon settings and readings. The "Other Mode" interface
+ * is a modern interface that replaces or extends the "Custom Mode" interface
+ * methods. The "Gamezone" interface adds advanced features such as fan
+ * profiles and overclocking. The "Lighting" interface adds control of various
+ * status lights related to different hardware components. Each of these
+ * drivers uses a common procedure to get data from the WMI interface,
+ * enumerated here.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/unaligned.h>
+#include <linux/wmi.h>
+
+#include "wmi-helpers.h"
+
+/* Thermal mode notifier chain. */
+static BLOCKING_NOTIFIER_HEAD(tm_chain_head);
+
+/**
+ * lwmi_dev_evaluate_int() - Helper function for calling WMI methods that
+ * return an integer.
+ * @wdev: Pointer to the WMI device to be called.
+ * @instance: Instance of the called method.
+ * @method_id: WMI Method ID for the method to be called.
+ * @buf: Buffer of all arguments for the given method_id.
+ * @size: Length of the buffer.
+ * @retval: Pointer for the return value to be assigned.
+ *
+ * Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI
+ * integer. Validates the return value type and assigns the value to the
+ * retval pointer.
+ *
+ * Return: 0 on success, or an error code.
+ */
+int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
+ unsigned char *buf, size_t size, u32 *retval)
+{
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct acpi_buffer input = { size, buf };
+ acpi_status status;
+
+ status = wmidev_evaluate_method(wdev, instance, method_id, &input,
+ &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ union acpi_object *ret_obj __free(kfree) = output.pointer;
+
+ if (retval) {
+ if (!ret_obj)
+ return -ENODATA;
+
+ switch (ret_obj->type) {
+ /*
+ * The ACPI method may simply return a buffer when a u32
+ * is expected. This is valid on Windows as its WMI-ACPI
+ * driver converts everything to a common buffer.
+ */
+ case ACPI_TYPE_BUFFER:
+ if (ret_obj->buffer.length < sizeof(u32))
+ return -ENXIO;
+
+ *retval = get_unaligned_le32(ret_obj->buffer.pointer);
+ return 0;
+ case ACPI_TYPE_INTEGER:
+ *retval = (u32)ret_obj->integer.value;
+ return 0;
+ default:
+ return -ENXIO;
+ }
+ }
+
+ return 0;
+};
+EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_int, "LENOVO_WMI_HELPERS");
+
+/**
+ * lwmi_tm_register_notifier() - Add a notifier to the blocking notifier chain
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_register to register the notifier block to the
+ * thermal mode notifier chain.
+ *
+ * Return: 0 on success, %-EEXIST on error.
+ */
+int lwmi_tm_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&tm_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_tm_register_notifier, "LENOVO_WMI_HELPERS");
+
+/**
+ * lwmi_tm_unregister_notifier() - Remove a notifier from the blocking notifier
+ * chain.
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_unregister to unregister the notifier block from the
+ * thermal mode notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+int lwmi_tm_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&tm_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_tm_unregister_notifier, "LENOVO_WMI_HELPERS");
+
+/**
+ * devm_lwmi_tm_unregister_notifier() - Remove a notifier from the blocking
+ * notifier chain.
+ * @data: Void pointer to the notifier_block struct to register.
+ *
+ * Call lwmi_tm_unregister_notifier to unregister the notifier block from the
+ * thermal mode notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+static void devm_lwmi_tm_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ lwmi_tm_unregister_notifier(nb);
+}
+
+/**
+ * devm_lwmi_tm_register_notifier() - Add a notifier to the blocking notifier
+ * chain.
+ * @dev: The parent device of the notifier_block struct.
+ * @nb: The notifier_block struct to register
+ *
+ * Call lwmi_tm_register_notifier to register the notifier block to the
+ * thermal mode notifier chain. Then add devm_lwmi_tm_unregister_notifier
+ * as a device managed action to automatically unregister the notifier block
+ * upon parent device removal.
+ *
+ * Return: 0 on success, or an error code.
+ */
+int devm_lwmi_tm_register_notifier(struct device *dev,
+ struct notifier_block *nb)
+{
+ int ret;
+
+ ret = lwmi_tm_register_notifier(nb);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_lwmi_tm_unregister_notifier,
+ nb);
+}
+EXPORT_SYMBOL_NS_GPL(devm_lwmi_tm_register_notifier, "LENOVO_WMI_HELPERS");
+
+/**
+ * lwmi_tm_notifier_call() - Call functions for the notifier call chain.
+ * @mode: Pointer to a thermal mode enum to retrieve the data from.
+ *
+ * Call blocking_notifier_call_chain to retrieve the thermal mode from the
+ * lenovo-wmi-gamezone driver.
+ *
+ * Return: 0 on success, or an error code.
+ */
+int lwmi_tm_notifier_call(enum thermal_mode *mode)
+{
+ int ret;
+
+ ret = blocking_notifier_call_chain(&tm_chain_head,
+ LWMI_GZ_GET_THERMAL_MODE, &mode);
+ if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK)
+ return -EINVAL;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_tm_notifier_call, "LENOVO_WMI_HELPERS");
+
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.h b/drivers/platform/x86/lenovo/wmi-helpers.h
new file mode 100644
index 000000000000..ed7db3ebba6c
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-helpers.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_HELPERS_H_
+#define _LENOVO_WMI_HELPERS_H_
+
+#include <linux/types.h>
+
+struct device;
+struct notifier_block;
+struct wmi_device;
+
+struct wmi_method_args_32 {
+ u32 arg0;
+ u32 arg1;
+};
+
+enum lwmi_event_type {
+ LWMI_GZ_GET_THERMAL_MODE = 0x01,
+};
+
+enum thermal_mode {
+ LWMI_GZ_THERMAL_MODE_NONE = 0x00,
+ LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
+ LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
+ LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
+ LWMI_GZ_THERMAL_MODE_EXTREME = 0xE0, /* Ver 6+ */
+ LWMI_GZ_THERMAL_MODE_CUSTOM = 0xFF,
+};
+
+int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
+ unsigned char *buf, size_t size, u32 *retval);
+
+int lwmi_tm_register_notifier(struct notifier_block *nb);
+int lwmi_tm_unregister_notifier(struct notifier_block *nb);
+int devm_lwmi_tm_register_notifier(struct device *dev,
+ struct notifier_block *nb);
+int lwmi_tm_notifier_call(enum thermal_mode *mode);
+
+#endif /* !_LENOVO_WMI_HELPERS_H_ */
diff --git a/drivers/platform/x86/lenovo-wmi-hotkey-utilities.c b/drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
index 89153afd7015..7b9bad1978ff 100644
--- a/drivers/platform/x86/lenovo-wmi-hotkey-utilities.c
+++ b/drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
@@ -122,26 +122,35 @@ static int lenovo_super_hotkey_wmi_led_init(enum mute_led_type led_type, struct
return -EIO;
union acpi_object *obj __free(kfree) = output.pointer;
- if (obj && obj->type == ACPI_TYPE_INTEGER)
- led_version = obj->integer.value;
- else
+ if (!obj || obj->type != ACPI_TYPE_INTEGER)
return -EIO;
- wpriv->cdev[led_type].max_brightness = LED_ON;
- wpriv->cdev[led_type].flags = LED_CORE_SUSPENDRESUME;
+ led_version = obj->integer.value;
+
+ /*
+ * Output parameters define: 0 means mute LED is not supported, Non-zero means
+ * mute LED can be supported.
+ */
+ if (led_version == 0)
+ return 0;
+
switch (led_type) {
case MIC_MUTE:
- if (led_version != WMI_LUD_SUPPORT_MICMUTE_LED_VER)
- return -EIO;
+ if (led_version != WMI_LUD_SUPPORT_MICMUTE_LED_VER) {
+ pr_warn("The MIC_MUTE LED of this device isn't supported.\n");
+ return 0;
+ }
wpriv->cdev[led_type].name = "platform::micmute";
wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_micmute_led_set;
wpriv->cdev[led_type].default_trigger = "audio-micmute";
break;
case AUDIO_MUTE:
- if (led_version != WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER)
- return -EIO;
+ if (led_version != WMI_LUD_SUPPORT_AUDIOMUTE_LED_VER) {
+ pr_warn("The AUDIO_MUTE LED of this device isn't supported.\n");
+ return 0;
+ }
wpriv->cdev[led_type].name = "platform::mute";
wpriv->cdev[led_type].brightness_set_blocking = &lsh_wmi_audiomute_led_set;
@@ -152,6 +161,9 @@ static int lenovo_super_hotkey_wmi_led_init(enum mute_led_type led_type, struct
return -EINVAL;
}
+ wpriv->cdev[led_type].max_brightness = LED_ON;
+ wpriv->cdev[led_type].flags = LED_CORE_SUSPENDRESUME;
+
err = devm_led_classdev_register(dev, &wpriv->cdev[led_type]);
if (err < 0) {
dev_err(dev, "Could not register mute LED %d : %d\n", led_type, err);
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
new file mode 100644
index 000000000000..d318ba432fdc
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -0,0 +1,1135 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Other Mode WMI interface driver.
+ *
+ * This driver uses the fw_attributes class to expose the various WMI functions
+ * provided by the "Other Mode" WMI interface. This enables CPU and GPU power
+ * limit as well as various other attributes for devices that fall under the
+ * "Gaming Series" of Lenovo laptop devices. Each attribute exposed by the
+ * "Other Mode" interface has a corresponding Capability Data struct that
+ * allows the driver to probe details about the attribute such as if it is
+ * supported by the hardware, the default_value, max_value, min_value, and step
+ * increment.
+ *
+ * These attributes typically don't fit anywhere else in the sysfs and are set
+ * in Windows using one of Lenovo's multiple user applications.
+ *
+ * Additionally, this driver also exports tunable fan speed RPM to HWMON.
+ * Min/max RPM are also provided for reference.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ * - fw_attributes
+ * - binding to Capability Data 01
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - HWMON
+ * - binding to Capability Data 00 and Fan
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/hwmon.h>
+#include <linux/idr.h>
+#include <linux/kdev_t.h>
+#include <linux/kobject.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/platform_profile.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-capdata.h"
+#include "wmi-events.h"
+#include "wmi-helpers.h"
+#include "../firmware_attributes_class.h"
+
+#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+
+#define LWMI_DEVICE_ID_CPU 0x01
+
+#define LWMI_FEATURE_ID_CPU_SPPT 0x01
+#define LWMI_FEATURE_ID_CPU_SPL 0x02
+#define LWMI_FEATURE_ID_CPU_FPPT 0x03
+
+#define LWMI_FEATURE_ID_FAN_RPM 0x03
+
+#define LWMI_FEATURE_VALUE_GET 17
+#define LWMI_FEATURE_VALUE_SET 18
+
+#define LWMI_FAN_ID_BASE 1
+#define LWMI_FAN_NR 4
+#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE)
+
+#define LWMI_FAN_DIV 100
+
+#define LWMI_ATTR_ID_FAN_RPM(x) \
+ lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
+ LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
+
+#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
+#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
+
+static DEFINE_IDA(lwmi_om_ida);
+
+enum attribute_property {
+ DEFAULT_VAL,
+ MAX_VAL,
+ MIN_VAL,
+ STEP_VAL,
+ SUPPORTED,
+};
+
+struct lwmi_fan_info {
+ u32 supported;
+ u32 last_target;
+ long min_rpm;
+ long max_rpm;
+};
+
+struct lwmi_om_priv {
+ struct component_master_ops *ops;
+
+ /* only valid after capdata bind */
+ struct cd_list *cd00_list;
+ struct cd_list *cd01_list;
+
+ struct device *hwmon_dev;
+ struct device *fw_attr_dev;
+ struct kset *fw_attr_kset;
+ struct wmi_device *wdev;
+ int ida_id;
+
+ struct lwmi_fan_info fan_info[LWMI_FAN_NR];
+
+ struct {
+ bool capdata00_collected : 1;
+ bool capdata_fan_collected : 1;
+ } fan_flags;
+};
+
+/*
+ * Visibility of fan channels:
+ *
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | | default | +expose_all_fans | +relax_fan_constraint | +both |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | canonical | RW | RW | RW+relaxed | RW+relaxed |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ * | -capdata_fan[idx] | N | RO | N | RW+relaxed |
+ * +-------------------+---------+------------------+-----------------------+------------+
+ *
+ * Note:
+ * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposing a channel.
+ * 2. -capdata_fan implies -capdata_fan[idx].
+ */
+static bool expose_all_fans;
+module_param(expose_all_fans, bool, 0444);
+MODULE_PARM_DESC(expose_all_fans,
+ "This option skips some capability checks and solely relies on per-channel ones "
+ "to expose fan attributes. Use with caution.");
+
+static bool relax_fan_constraint;
+module_param(relax_fan_constraint, bool, 0444);
+MODULE_PARM_DESC(relax_fan_constraint,
+ "Do not enforce fan RPM constraint (div/min/max) "
+ "and enables fan tuning when such data is missing. "
+ "Enabling this may results in HWMON attributes being out-of-sync, "
+ "and setting a too low RPM stops the fan. Use with caution.");
+
+/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
+
+/**
+ * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan
+ * @priv: Driver private data structure
+ * @channel: Fan channel index (0-based)
+ * @val: Pointer to value (input for set, output for get)
+ * @set: True to set value, false to get value
+ *
+ * Communicates with WMI interface to either retrieve current fan RPM
+ * or set target fan RPM.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set)
+{
+ struct wmi_method_args_32 args = {};
+ u32 method_id, retval;
+ int err;
+
+ method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET;
+ args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel);
+ args.arg1 = set ? *val : 0;
+
+ err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id,
+ (unsigned char *)&args, sizeof(args), &retval);
+ if (err)
+ return err;
+
+ if (!set) {
+ *val = retval;
+ return 0;
+ }
+
+ /*
+ * It seems that 0 means "no error" and 1 means "done". Apparently
+ * different firmware teams have different thoughts on indicating
+ * success, so we accepts both.
+ */
+ return (retval == 0 || retval == 1) ? 0 : -EIO;
+}
+
+/**
+ * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes
+ * @drvdata: Driver private data
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ *
+ * Determines whether an HWMON attribute should be visible in sysfs
+ * based on hardware capabilities and current configuration.
+ *
+ * Return: permission mode, or 0 if invisible.
+ */
+static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata;
+ bool visible = false;
+
+ if (type == hwmon_fan) {
+ if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID))
+ return 0;
+
+ switch (attr) {
+ case hwmon_fan_target:
+ if (!(priv->fan_info[channel].supported & LWMI_SUPP_SET))
+ return 0;
+
+ if (relax_fan_constraint ||
+ (priv->fan_info[channel].min_rpm >= 0 &&
+ priv->fan_info[channel].max_rpm >= 0))
+ return 0644;
+
+ /*
+ * Reaching here implies expose_all_fans is set.
+ * See lwmi_om_hwmon_add().
+ */
+ dev_warn_once(&priv->wdev->dev,
+ "fan tuning disabled due to missing RPM constraint\n");
+ return 0;
+ case hwmon_fan_div:
+ case hwmon_fan_input:
+ visible = priv->fan_info[channel].supported & LWMI_SUPP_GET;
+ break;
+ case hwmon_fan_min:
+ visible = priv->fan_info[channel].min_rpm >= 0;
+ break;
+ case hwmon_fan_max:
+ visible = priv->fan_info[channel].max_rpm >= 0;
+ break;
+ }
+ }
+
+ return visible ? 0444 : 0;
+}
+
+/**
+ * lwmi_om_hwmon_read() - Read HWMON sensor data
+ * @dev: Device pointer
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ * @val: Pointer to store value
+ *
+ * Reads current sensor values from hardware through WMI interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ u32 retval = 0;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ /*
+ * The EC has an internal RPM divisor (i.e., the raw register value is
+ * RPM / fanY_div). For fanY_input, the WMI method reads the register
+ * value and returns raw * fanY_div. For fanY_target, the WMI method
+ * divides the written value by fanY_div before writing it to the EC.
+ *
+ * As a result, reading fanY_input always returns a multiple of fanY_div,
+ * while writing to fanY_target loses the remainder.
+ */
+ case hwmon_fan_div:
+ *val = LWMI_FAN_DIV;
+ return 0;
+ case hwmon_fan_input:
+ err = lwmi_om_fan_get_set(priv, channel, &retval, false);
+ if (err)
+ return err;
+
+ *val = retval;
+ return 0;
+ case hwmon_fan_target:
+ *val = priv->fan_info[channel].last_target;
+ return 0;
+ case hwmon_fan_min:
+ *val = priv->fan_info[channel].min_rpm;
+ return 0;
+ case hwmon_fan_max:
+ *val = priv->fan_info[channel].max_rpm;
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * lwmi_om_hwmon_write() - Write HWMON sensor data
+ * @dev: Device pointer
+ * @type: Sensor type
+ * @attr: Attribute identifier
+ * @channel: Channel index
+ * @val: Value to write
+ *
+ * Writes configuration values to hardware through WMI interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ u32 raw, min_rpm, max_rpm;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ case hwmon_fan_target:
+ if (relax_fan_constraint) {
+ min_rpm = 1;
+ max_rpm = U16_MAX;
+ } else {
+ min_rpm = priv->fan_info[channel].min_rpm;
+ max_rpm = priv->fan_info[channel].max_rpm;
+ }
+
+ /* 0 means "auto". */
+ if (val != 0 && (val < min_rpm || val > max_rpm))
+ return -EINVAL;
+
+ /*
+ * The effective fanY_target is always a multiple of fanY_div
+ * due to the EC's internal RPM divisor (see lwmi_om_hwmon_read).
+ *
+ * Round down the written value to the nearest multiple of fanY_div
+ * to prevent mismatch between the effective value and last_target.
+ *
+ * For relax_fan_constraint, skip this conversion as setting a
+ * sub-fanY_div value is necessary to completely stop the fan on
+ * some devices.
+ */
+ if (!relax_fan_constraint)
+ raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV;
+ else
+ raw = val;
+
+ err = lwmi_om_fan_get_set(priv, channel, &raw, true);
+ if (err)
+ return err;
+
+ priv->fan_info[channel].last_target = raw;
+ return 0;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
+ /* Must match LWMI_FAN_NR. */
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV |
+ HWMON_F_MIN | HWMON_F_MAX),
+ NULL
+};
+
+static const struct hwmon_ops lwmi_om_hwmon_ops = {
+ .is_visible = lwmi_om_hwmon_is_visible,
+ .read = lwmi_om_hwmon_read,
+ .write = lwmi_om_hwmon_write,
+};
+
+static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
+ .ops = &lwmi_om_hwmon_ops,
+ .info = lwmi_om_hwmon_info,
+};
+
+/**
+ * lwmi_om_hwmon_add() - Register HWMON device if all info is collected
+ * @priv: Driver private data
+ */
+static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
+{
+ int i, valid;
+
+ if (WARN_ON(priv->hwmon_dev))
+ return;
+
+ if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) {
+ dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n",
+ priv->fan_flags.capdata00_collected,
+ priv->fan_flags.capdata_fan_collected);
+ return;
+ }
+
+ if (expose_all_fans)
+ dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n");
+
+ if (relax_fan_constraint)
+ dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n");
+
+ valid = 0;
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID))
+ continue;
+
+ valid++;
+
+ if (!expose_all_fans &&
+ (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) {
+ dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n",
+ LWMI_FAN_ID(i));
+ priv->fan_info[i].supported = 0;
+ valid--;
+ }
+ }
+
+ if (valid == 0) {
+ dev_warn(&priv->wdev->dev,
+ "fan reporting/tuning is unsupported on this device\n");
+ return;
+ }
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev,
+ LWMI_OM_HWMON_NAME, priv,
+ &lwmi_om_hwmon_chip_info,
+ NULL);
+ if (IS_ERR(priv->hwmon_dev)) {
+ dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n",
+ PTR_ERR(priv->hwmon_dev));
+ priv->hwmon_dev = NULL;
+ return;
+ }
+
+ dev_dbg(&priv->wdev->dev, "registered HWMON device\n");
+}
+
+/**
+ * lwmi_om_hwmon_remove() - Unregister HWMON device
+ * @priv: Driver private data
+ *
+ * Unregisters the HWMON device if applicable.
+ */
+static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
+{
+ if (!priv->hwmon_dev)
+ return;
+
+ hwmon_device_unregister(priv->hwmon_dev);
+ priv->hwmon_dev = NULL;
+}
+
+/**
+ * lwmi_om_fan_info_init() - Initialzie fan info
+ * @priv: Driver private data
+ *
+ * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() may be
+ * called in an arbitrary order. Hence, initializion must be done before.
+ */
+static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ priv->fan_info[i] = (struct lwmi_fan_info) {
+ .supported = 0,
+ /*
+ * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot.
+ *
+ * Note that S0ix (s2idle) preserves the RPM target, so we don't need
+ * suspend/resume callbacks. This behavior has not been tested on S3-
+ * capable devices, but I doubt if such devices even have this interface.
+ */
+ .last_target = 0,
+ .min_rpm = -ENODATA,
+ .max_rpm = -ENODATA,
+ };
+ }
+
+ priv->fan_flags.capdata00_collected = false;
+ priv->fan_flags.capdata_fan_collected = false;
+}
+
+/**
+ * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00
+ * @priv: Driver private data
+ */
+static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv)
+{
+ struct capdata00 capdata00;
+ int i, err;
+
+ dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n");
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00);
+ priv->fan_info[i].supported = err ? 0 : capdata00.supported;
+ }
+
+ priv->fan_flags.capdata00_collected = true;
+ lwmi_om_hwmon_add(priv);
+}
+
+/**
+ * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan
+ * @dev: Pointer to the lenovo-wmi-other device
+ * @cd_fan_list: Pointer to the capdata fan list
+ */
+static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ struct capdata_fan capdata_fan;
+ int i, err;
+
+ dev_dbg(dev, "Collecting fan info from capdata_fan\n");
+
+ if (!cd_fan_list)
+ goto out;
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan);
+ if (err)
+ continue;
+
+ priv->fan_info[i].min_rpm = capdata_fan.min_rpm;
+ priv->fan_info[i].max_rpm = capdata_fan.max_rpm;
+ }
+
+out:
+ priv->fan_flags.capdata_fan_collected = true;
+ lwmi_om_hwmon_add(priv);
+}
+
+/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
+
+struct tunable_attr_01 {
+ struct device *dev;
+ u8 feature_id;
+ u8 device_id;
+ u8 type_id;
+ u8 cd_mode_id; /* mode arg for searching capdata */
+ u8 cv_mode_id; /* mode arg for set/get current_value */
+};
+
+/**
+ * tunable_attr_01_id() - Formats a tunable_attr_01 to a capdata attribute ID
+ * @attr: The tunable_attr_01 to format.
+ * @mode: The u8 corresponding to the wmi-gamezone mode for set/get.
+ *
+ * Return: encoded capability data attribute ID.
+ */
+static u32 tunable_attr_01_id(struct tunable_attr_01 *attr, u8 mode)
+{
+ return lwmi_attr_id(attr->device_id, attr->feature_id, mode, attr->type_id);
+}
+
+static struct tunable_attr_01 ppt_pl1_spl = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_SPL,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_pl2_sppt = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_SPPT,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_pl3_fppt = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_FPPT,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+struct capdata01_attr_group {
+ const struct attribute_group *attr_group;
+ struct tunable_attr_01 *tunable_attr;
+};
+
+/* Attribute Methods */
+
+/**
+ * int_type_show() - Emit the data type for an integer attribute
+ * @kobj: Pointer to the driver object.
+ * @kattr: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ *
+ * Return: Number of characters written to buf.
+ */
+static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
+ char *buf)
+{
+ return sysfs_emit(buf, "integer\n");
+}
+
+/**
+ * attr_capdata01_show() - Get the value of the specified attribute property
+ *
+ * @kobj: Pointer to the driver object.
+ * @kattr: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ * @tunable_attr: The attribute to be read.
+ * @prop: The property of this attribute to be read.
+ *
+ * Retrieves the given property from the capability data 01 struct for the
+ * specified attribute's "custom" thermal mode. This function is intended
+ * to be generic so it can be called from any integer attributes "_show"
+ * function.
+ *
+ * If the WMI is success the sysfs attribute is notified.
+ *
+ * Return: Either number of characters written to buf, or an error code.
+ */
+static ssize_t attr_capdata01_show(struct kobject *kobj,
+ struct kobj_attribute *kattr, char *buf,
+ struct tunable_attr_01 *tunable_attr,
+ enum attribute_property prop)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+ struct capdata01 capdata;
+ u32 attribute_id;
+ int value, ret;
+
+ attribute_id = tunable_attr_01_id(tunable_attr, tunable_attr->cd_mode_id);
+
+ ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
+ if (ret)
+ return ret;
+
+ switch (prop) {
+ case DEFAULT_VAL:
+ value = capdata.default_value;
+ break;
+ case MAX_VAL:
+ value = capdata.max_value;
+ break;
+ case MIN_VAL:
+ value = capdata.min_value;
+ break;
+ case STEP_VAL:
+ value = capdata.step;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return sysfs_emit(buf, "%d\n", value);
+}
+
+/**
+ * attr_current_value_store() - Set the current value of the given attribute
+ * @kobj: Pointer to the driver object.
+ * @kattr: Pointer to the attribute calling this function.
+ * @buf: The buffer to read from, this is parsed to `int` type.
+ * @count: Required by sysfs attribute macros, pass in from the callee attr.
+ * @tunable_attr: The attribute to be stored.
+ *
+ * Sets the value of the given attribute when operating under the "custom"
+ * smartfan profile. The current smartfan profile is retrieved from the
+ * lenovo-wmi-gamezone driver and error is returned if the result is not
+ * "custom". This function is intended to be generic so it can be called from
+ * any integer attribute's "_store" function. The integer to be sent to the WMI
+ * method is range checked and an error code is returned if out of range.
+ *
+ * If the value is valid and WMI is success, then the sysfs attribute is
+ * notified.
+ *
+ * Return: Either count, or an error code.
+ */
+static ssize_t attr_current_value_store(struct kobject *kobj,
+ struct kobj_attribute *kattr,
+ const char *buf, size_t count,
+ struct tunable_attr_01 *tunable_attr)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+ struct wmi_method_args_32 args = {};
+ struct capdata01 capdata;
+ enum thermal_mode mode;
+ u32 value;
+ int ret;
+
+ ret = lwmi_tm_notifier_call(&mode);
+ if (ret)
+ return ret;
+
+ if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
+ return -EBUSY;
+
+ args.arg0 = tunable_attr_01_id(tunable_attr, tunable_attr->cd_mode_id);
+
+ ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
+ if (ret)
+ return ret;
+
+ ret = kstrtouint(buf, 10, &value);
+ if (ret)
+ return ret;
+
+ if (value < capdata.min_value || value > capdata.max_value)
+ return -EINVAL;
+
+ args.arg0 = tunable_attr_01_id(tunable_attr, tunable_attr->cv_mode_id);
+ args.arg1 = value;
+
+ ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
+ (unsigned char *)&args, sizeof(args), NULL);
+ if (ret)
+ return ret;
+
+ return count;
+};
+
+/**
+ * attr_current_value_show() - Get the current value of the given attribute
+ * @kobj: Pointer to the driver object.
+ * @kattr: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ * @tunable_attr: The attribute to be read.
+ *
+ * Retrieves the value of the given attribute for the current smartfan profile.
+ * The current smartfan profile is retrieved from the lenovo-wmi-gamezone driver.
+ * This function is intended to be generic so it can be called from any integer
+ * attribute's "_show" function.
+ *
+ * If the WMI is success the sysfs attribute is notified.
+ *
+ * Return: Either number of characters written to buf, or an error code.
+ */
+static ssize_t attr_current_value_show(struct kobject *kobj,
+ struct kobj_attribute *kattr, char *buf,
+ struct tunable_attr_01 *tunable_attr)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+ struct wmi_method_args_32 args = {};
+ enum thermal_mode mode;
+ int retval;
+ int ret;
+
+ ret = lwmi_tm_notifier_call(&mode);
+ if (ret)
+ return ret;
+
+ /* If "no-mode" is the supported mode, ensure we never send current mode */
+ if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE)
+ mode = tunable_attr->cv_mode_id;
+
+ args.arg0 = tunable_attr_01_id(tunable_attr, mode);
+
+ ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
+ (unsigned char *)&args, sizeof(args),
+ &retval);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", retval);
+}
+
+/**
+ * lwmi_attr_01_is_supported() - Determine if the given attribute is supported.
+ * @tunable_attr: The attribute to verify.
+ *
+ * For an attribute to be supported it must have a functional get/set method,
+ * as well as associated capability data stored in the capdata01 table.
+ *
+ * First check if the attribute has a corresponding data table under custom mode
+ * (0xff), then under no mode (0x00). If either of those passes, check if the
+ * supported field of the capdata struct is > 0. If it is supported, store the
+ * successful mode in the cd_mode_id field of tunable_attr.
+ *
+ * If the attribute capdata shows it is supported, attempt to determine the mode
+ * for the current value property get/set methods using a similar pattern to the
+ * capdata table check. If the value returned by either mode is 0 or an error,
+ * assume that mode is not supported. Otherwise, store the successful mode in the
+ * cv_mode_id field of tunable_attr.
+ *
+ * If any of the above checks fail then the attribute is not fully supported.
+ *
+ * Return: true if capdata and set/get modes are found, otherwise false.
+ */
+static bool lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
+{
+ u8 modes[2] = { LWMI_GZ_THERMAL_MODE_CUSTOM, LWMI_GZ_THERMAL_MODE_NONE };
+ struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+ struct wmi_method_args_32 args = {};
+ bool cd_mode_found = false;
+ bool cv_mode_found = false;
+ struct capdata01 capdata;
+ int retval, ret, i;
+
+ /* Determine tunable_attr->cd_mode_id */
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ args.arg0 = tunable_attr_01_id(tunable_attr, modes[i]);
+
+ ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
+ if (ret || !capdata.supported)
+ continue;
+
+ tunable_attr->cd_mode_id = modes[i];
+ cd_mode_found = true;
+ break;
+ }
+
+ if (!cd_mode_found)
+ return cd_mode_found;
+
+ dev_dbg(tunable_attr->dev,
+ "cd_mode_id: %#010x\n", args.arg0);
+
+ /* Determine tunable_attr->cv_mode_id, returns 1 if supported */
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ args.arg0 = tunable_attr_01_id(tunable_attr, modes[i]);
+
+ ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
+ (u8 *)&args, sizeof(args),
+ &retval);
+ if (ret || !retval)
+ continue;
+
+ tunable_attr->cv_mode_id = modes[i];
+ cv_mode_found = true;
+ break;
+ }
+
+ if (!cv_mode_found)
+ return cv_mode_found;
+
+ dev_dbg(tunable_attr->dev, "cv_mode_id: %#010x, attribute support level: %#010x\n",
+ args.arg0, capdata.supported);
+
+ return capdata.supported > 0;
+}
+
+/* Lenovo WMI Other Mode Attribute macros */
+#define __LWMI_ATTR_RO(_func, _name) \
+ { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = _func##_##_name##_show, \
+ }
+
+#define __LWMI_ATTR_RO_AS(_name, _show) \
+ { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = _show, \
+ }
+
+#define __LWMI_ATTR_RW(_func, _name) \
+ __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
+
+/* Shows a formatted static variable */
+#define __LWMI_ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) \
+ static ssize_t _attrname##_##_prop##_show( \
+ struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+ { \
+ return sysfs_emit(buf, _fmt, _val); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_##_prop = \
+ __LWMI_ATTR_RO(_attrname, _prop)
+
+/* Attribute current value read/write */
+#define __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname) \
+ static ssize_t _attrname##_current_value_store( \
+ struct kobject *kobj, struct kobj_attribute *kattr, \
+ const char *buf, size_t count) \
+ { \
+ return attr_current_value_store(kobj, kattr, buf, count, \
+ &_attrname); \
+ } \
+ static ssize_t _attrname##_current_value_show( \
+ struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+ { \
+ return attr_current_value_show(kobj, kattr, buf, &_attrname); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_current_value = \
+ __LWMI_ATTR_RW(_attrname, current_value)
+
+/* Attribute property read only */
+#define __LWMI_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) \
+ static ssize_t _attrname##_##_prop##_show( \
+ struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+ { \
+ return attr_capdata01_show(kobj, kattr, buf, &_attrname, \
+ _prop_type); \
+ } \
+ static struct kobj_attribute attr_##_attrname##_##_prop = \
+ __LWMI_ATTR_RO(_attrname, _prop)
+
+#define LWMI_ATTR_GROUP_TUNABLE_CAP01(_attrname, _fsname, _dispname) \
+ __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname); \
+ __LWMI_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \
+ __LWMI_ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+ __LWMI_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \
+ __LWMI_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \
+ __LWMI_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL); \
+ static struct kobj_attribute attr_##_attrname##_type = \
+ __LWMI_ATTR_RO_AS(type, int_type_show); \
+ static struct attribute *_attrname##_attrs[] = { \
+ &attr_##_attrname##_current_value.attr, \
+ &attr_##_attrname##_default_value.attr, \
+ &attr_##_attrname##_display_name.attr, \
+ &attr_##_attrname##_max_value.attr, \
+ &attr_##_attrname##_min_value.attr, \
+ &attr_##_attrname##_scalar_increment.attr, \
+ &attr_##_attrname##_type.attr, \
+ NULL, \
+ }; \
+ static const struct attribute_group _attrname##_attr_group = { \
+ .name = _fsname, .attrs = _attrname##_attrs \
+ }
+
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
+ "Set the CPU sustained power limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
+ "Set the CPU slow package power tracking limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
+ "Set the CPU fast package power tracking limit");
+
+static struct capdata01_attr_group cd01_attr_groups[] = {
+ { &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
+ { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
+ { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
+ {},
+};
+
+/**
+ * lwmi_om_fw_attr_add() - Register all firmware_attributes_class members
+ * @priv: The Other Mode driver data.
+ */
+static void lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
+{
+ unsigned int i;
+ int err;
+
+ err = ida_alloc(&lwmi_om_ida, GFP_KERNEL);
+ if (err < 0)
+ goto err_no_ida;
+
+ priv->ida_id = err;
+
+ priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
+ MKDEV(0, 0), NULL, "%s-%u",
+ LWMI_OM_FW_ATTR_BASE_PATH,
+ priv->ida_id);
+ if (IS_ERR(priv->fw_attr_dev)) {
+ err = PTR_ERR(priv->fw_attr_dev);
+ goto err_free_ida;
+ }
+
+ priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
+ &priv->fw_attr_dev->kobj);
+ if (!priv->fw_attr_kset) {
+ err = -ENOMEM;
+ goto err_destroy_classdev;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
+ cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
+ if (!lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr))
+ continue;
+
+ err = sysfs_create_group(&priv->fw_attr_kset->kobj,
+ cd01_attr_groups[i].attr_group);
+ if (err)
+ goto err_remove_groups;
+ }
+ return;
+
+err_remove_groups:
+ while (i--)
+ sysfs_remove_group(&priv->fw_attr_kset->kobj,
+ cd01_attr_groups[i].attr_group);
+
+ kset_unregister(priv->fw_attr_kset);
+
+err_destroy_classdev:
+ device_unregister(priv->fw_attr_dev);
+
+err_free_ida:
+ ida_free(&lwmi_om_ida, priv->ida_id);
+
+err_no_ida:
+ priv->ida_id = -EIDRM;
+
+ dev_warn(&priv->wdev->dev,
+ "failed to register firmware-attributes device: %d\n", err);
+}
+
+/**
+ * lwmi_om_fw_attr_remove() - Unregister all capability data attribute groups
+ * @priv: the lenovo-wmi-other driver data.
+ */
+static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
+{
+ if (priv->ida_id < 0)
+ return;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++)
+ sysfs_remove_group(&priv->fw_attr_kset->kobj,
+ cd01_attr_groups[i].attr_group);
+
+ kset_unregister(priv->fw_attr_kset);
+ device_unregister(priv->fw_attr_dev);
+ ida_free(&lwmi_om_ida, priv->ida_id);
+ priv->ida_id = -EIDRM;
+}
+
+/* ======== Self (master: lenovo-wmi-other) ======== */
+
+/**
+ * lwmi_om_master_bind() - Bind all components of the other mode driver
+ * @dev: The lenovo-wmi-other driver basic device.
+ *
+ * Call component_bind_all to bind the lenovo-wmi-capdata devices to the
+ * lenovo-wmi-other master driver, with a callback to collect fan info from
+ * capdata_fan. On success, assign the capability data list pointers to the
+ * driver data struct for later access. These pointers are only valid while the
+ * capdata interfaces exist. Finally, collect fan info from capdata00 and
+ * register all firmware attribute groups. Note that the HWMON device is
+ * registered only if all fan info is collected. Hence, it is not registered
+ * here. See lwmi_om_fan_info_collect_cd00() and
+ * lwmi_om_fan_info_collect_cd_fan().
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_master_bind(struct device *dev)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+ struct lwmi_cd_binder binder = {
+ .cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan,
+ };
+ int ret;
+
+ lwmi_om_fan_info_init(priv);
+
+ ret = component_bind_all(dev, &binder);
+ if (ret)
+ return ret;
+
+ priv->cd00_list = binder.cd00_list;
+ priv->cd01_list = binder.cd01_list;
+ if (!priv->cd00_list || !priv->cd01_list) {
+ component_unbind_all(dev, NULL);
+
+ return -ENODEV;
+ }
+
+ lwmi_om_fan_info_collect_cd00(priv);
+
+ lwmi_om_fw_attr_add(priv);
+
+ return 0;
+}
+
+/**
+ * lwmi_om_master_unbind() - Unbind all components of the other mode driver
+ * @dev: The lenovo-wmi-other driver basic device
+ *
+ * Unregister all firmware attribute groups and the HWMON device. Then call
+ * component_unbind_all to unbind lenovo-wmi-capdata devices from the
+ * lenovo-wmi-other master driver.
+ */
+static void lwmi_om_master_unbind(struct device *dev)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+
+ lwmi_om_fw_attr_remove(priv);
+
+ lwmi_om_hwmon_remove(priv);
+
+ component_unbind_all(dev, NULL);
+}
+
+static const struct component_master_ops lwmi_om_master_ops = {
+ .bind = lwmi_om_master_bind,
+ .unbind = lwmi_om_master_unbind,
+};
+
+static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
+{
+ struct component_match *master_match = NULL;
+ struct lwmi_om_priv *priv;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Sentinel for on-demand ida_free(). */
+ priv->ida_id = -EIDRM;
+
+ priv->wdev = wdev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ lwmi_cd_match_add_all(&wdev->dev, &master_match);
+ if (IS_ERR(master_match))
+ return PTR_ERR(master_match);
+
+ return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
+ master_match);
+}
+
+static void lwmi_other_remove(struct wmi_device *wdev)
+{
+ component_master_del(&wdev->dev, &lwmi_om_master_ops);
+}
+
+static const struct wmi_device_id lwmi_other_id_table[] = {
+ { LENOVO_OTHER_MODE_GUID, NULL },
+ {}
+};
+
+static struct wmi_driver lwmi_other_driver = {
+ .driver = {
+ .name = "lenovo_wmi_other",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lwmi_other_id_table,
+ .probe = lwmi_other_probe,
+ .remove = lwmi_other_remove,
+ .no_singleton = true,
+};
+
+module_wmi_driver(lwmi_other_driver);
+
+MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA");
+MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
+MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
+MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-ymc.c b/drivers/platform/x86/lenovo/ymc.c
index 470d53e3c9d2..1b73a55f1b89 100644
--- a/drivers/platform/x86/lenovo-ymc.c
+++ b/drivers/platform/x86/lenovo/ymc.c
@@ -153,6 +153,7 @@ static struct wmi_driver lenovo_ymc_driver = {
.name = "lenovo-ymc",
},
.id_table = lenovo_ymc_wmi_id_table,
+ .min_event_size = sizeof(u32),
.probe = lenovo_ymc_probe,
.notify = lenovo_ymc_notify,
};
diff --git a/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c
index 25933cd018d1..8551ab4d2c7d 100644
--- a/drivers/platform/x86/lenovo-yoga-tab2-pro-1380-fastcharger.c
+++ b/drivers/platform/x86/lenovo/yoga-tab2-pro-1380-fastcharger.c
@@ -21,7 +21,7 @@
#include <linux/time.h>
#include <linux/types.h>
#include <linux/workqueue.h>
-#include "serdev_helpers.h"
+#include "../serdev_helpers.h"
#define YT2_1380_FC_PDEV_NAME "lenovo-yoga-tab2-pro-1380-fastcharger"
#define YT2_1380_FC_SERDEV_CTRL "serial0"
@@ -240,30 +240,30 @@ static int yt2_1380_fc_pdev_probe(struct platform_device *pdev)
int ret;
/* Register pinctrl mappings for setting the UART3 pins mode */
- ret = pinctrl_register_mappings(yt2_1380_fc_pinctrl_map,
- ARRAY_SIZE(yt2_1380_fc_pinctrl_map));
+ ret = devm_pinctrl_register_mappings(&pdev->dev, yt2_1380_fc_pinctrl_map,
+ ARRAY_SIZE(yt2_1380_fc_pinctrl_map));
if (ret)
return ret;
/* And create the serdev to talk to the charger over the UART3 pins */
ctrl_dev = get_serdev_controller("PNP0501", "1", 0, YT2_1380_FC_SERDEV_CTRL);
- if (IS_ERR(ctrl_dev)) {
- ret = PTR_ERR(ctrl_dev);
- goto out_pinctrl_unregister_mappings;
- }
+ if (IS_ERR(ctrl_dev))
+ return PTR_ERR(ctrl_dev);
serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
put_device(ctrl_dev);
- if (!serdev) {
- ret = -ENOMEM;
- goto out_pinctrl_unregister_mappings;
- }
+ if (!serdev)
+ return -ENOMEM;
+
+ /* Propagate pdev-fwnode set by x86-android-tablets to serdev */
+ device_set_node(&serdev->dev, dev_fwnode(&pdev->dev));
+ /* The fwnode is a managed node, so it will be auto-put on serdev_device_put() */
+ fwnode_handle_get(dev_fwnode(&serdev->dev));
ret = serdev_device_add(serdev);
if (ret) {
- dev_err_probe(&pdev->dev, ret, "adding serdev\n");
serdev_device_put(serdev);
- goto out_pinctrl_unregister_mappings;
+ return dev_err_probe(&pdev->dev, ret, "adding serdev\n");
}
/*
@@ -273,20 +273,15 @@ static int yt2_1380_fc_pdev_probe(struct platform_device *pdev)
ret = device_driver_attach(&yt2_1380_fc_serdev_driver.driver, &serdev->dev);
if (ret) {
/* device_driver_attach() maps EPROBE_DEFER to EAGAIN, map it back */
- ret = (ret == -EAGAIN) ? -EPROBE_DEFER : ret;
- dev_err_probe(&pdev->dev, ret, "attaching serdev driver\n");
- goto out_serdev_device_remove;
+ serdev_device_remove(serdev);
+ return dev_err_probe(&pdev->dev,
+ (ret == -EAGAIN) ? -EPROBE_DEFER : ret,
+ "attaching serdev driver\n");
}
/* So that yt2_1380_fc_pdev_remove() can remove the serdev */
platform_set_drvdata(pdev, serdev);
return 0;
-
-out_serdev_device_remove:
- serdev_device_remove(serdev);
-out_pinctrl_unregister_mappings:
- pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map);
- return ret;
}
static void yt2_1380_fc_pdev_remove(struct platform_device *pdev)
@@ -294,7 +289,6 @@ static void yt2_1380_fc_pdev_remove(struct platform_device *pdev)
struct serdev_device *serdev = platform_get_drvdata(pdev);
serdev_device_remove(serdev);
- pinctrl_unregister_mappings(yt2_1380_fc_pinctrl_map);
}
static struct platform_driver yt2_1380_fc_pdev_driver = {
diff --git a/drivers/platform/x86/lenovo-yogabook.c b/drivers/platform/x86/lenovo/yogabook.c
index 31b298dc5046..1a4b2ab1f35d 100644
--- a/drivers/platform/x86/lenovo-yogabook.c
+++ b/drivers/platform/x86/lenovo/yogabook.c
@@ -57,7 +57,7 @@ struct yogabook_data {
struct work_struct work;
struct led_classdev kbd_bl_led;
unsigned long flags;
- uint8_t brightness;
+ u8 brightness;
};
static void yogabook_work(struct work_struct *work)
@@ -338,16 +338,18 @@ static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context)
int r;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
- if (data == NULL)
+ if (!data)
return -ENOMEM;
data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1);
if (!data->kbd_adev)
- return dev_err_probe(dev, -ENODEV, "Cannot find the touchpad device in ACPI tables\n");
+ return dev_err_probe(dev, -ENODEV,
+ "Cannot find the touchpad device in ACPI tables\n");
data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1);
if (!data->dig_adev) {
- r = dev_err_probe(dev, -ENODEV, "Cannot find the digitizer device in ACPI tables\n");
+ r = dev_err_probe(dev, -ENODEV,
+ "Cannot find the digitizer device in ACPI tables\n");
goto error_put_devs;
}
@@ -409,8 +411,8 @@ static struct wmi_driver yogabook_wmi_driver = {
.name = "yogabook-wmi",
.pm = pm_sleep_ptr(&yogabook_pm_ops),
},
- .no_notify_data = true,
.id_table = yogabook_wmi_id_table,
+ .min_event_size = 0,
.probe = yogabook_wmi_probe,
.remove = yogabook_wmi_remove,
.notify = yogabook_wmi_notify,
@@ -453,7 +455,7 @@ static int yogabook_pdev_probe(struct platform_device *pdev)
int r;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
- if (data == NULL)
+ if (!data)
return -ENOMEM;
data->kbd_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-goodix_ts");
diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c
index 4b57102c7f62..a8f2f465ef3f 100644
--- a/drivers/platform/x86/lg-laptop.c
+++ b/drivers/platform/x86/lg-laptop.c
@@ -8,6 +8,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
+#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
@@ -18,6 +19,7 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/string_choices.h>
#include <linux/types.h>
#include <acpi/battery.h>
@@ -41,6 +43,7 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages");
#define LG_ADDRESS_SPACE_ID 0x8F
#define LG_ADDRESS_SPACE_DEBUG_FLAG_ADR 0x00
+#define LG_ADDRESS_SPACE_HD_AUDIO_POWER_ADDR 0x01
#define LG_ADDRESS_SPACE_FAN_MODE_ADR 0x03
#define LG_ADDRESS_SPACE_DTTM_FLAG_ADR 0x20
@@ -75,6 +78,9 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages");
#define WMBB_USB_CHARGE 0x10B
#define WMBB_BATT_LIMIT 0x10C
+#define FAN_MODE_LOWER GENMASK(1, 0)
+#define FAN_MODE_UPPER GENMASK(5, 4)
+
#define PLATFORM_NAME "lg-laptop"
MODULE_ALIAS("wmi:" WMI_EVENT_GUID0);
@@ -265,38 +271,23 @@ static void wmi_input_setup(void)
}
}
-static void acpi_notify(struct acpi_device *device, u32 event)
-{
- acpi_handle_debug(device->handle, "notify: %d\n", event);
-}
-
static ssize_t fan_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buffer, size_t count)
{
- bool value;
+ unsigned long value;
union acpi_object *r;
- u32 m;
int ret;
- ret = kstrtobool(buffer, &value);
+ ret = kstrtoul(buffer, 10, &value);
if (ret)
return ret;
+ if (value >= 3)
+ return -EINVAL;
- r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);
- if (!r)
- return -EIO;
-
- if (r->type != ACPI_TYPE_INTEGER) {
- kfree(r);
- return -EIO;
- }
-
- m = r->integer.value;
- kfree(r);
- r = lg_wmab(dev, WM_FAN_MODE, WM_SET, (m & 0xffffff0f) | (value << 4));
- kfree(r);
- r = lg_wmab(dev, WM_FAN_MODE, WM_SET, (m & 0xfffffff0) | value);
+ r = lg_wmab(dev, WM_FAN_MODE, WM_SET,
+ FIELD_PREP(FAN_MODE_LOWER, value) |
+ FIELD_PREP(FAN_MODE_UPPER, value));
kfree(r);
return count;
@@ -305,7 +296,7 @@ static ssize_t fan_mode_store(struct device *dev,
static ssize_t fan_mode_show(struct device *dev,
struct device_attribute *attr, char *buffer)
{
- unsigned int status;
+ unsigned int mode;
union acpi_object *r;
r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0);
@@ -317,10 +308,10 @@ static ssize_t fan_mode_show(struct device *dev,
return -EIO;
}
- status = r->integer.value & 0x01;
+ mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value);
kfree(r);
- return sysfs_emit(buffer, "%d\n", status);
+ return sysfs_emit(buffer, "%d\n", mode);
}
static ssize_t usb_charge_store(struct device *dev,
@@ -674,6 +665,15 @@ static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physic
byte = value & 0xFF;
switch (address) {
+ case LG_ADDRESS_SPACE_HD_AUDIO_POWER_ADDR:
+ /*
+ * The HD audio power field is not affected by the DTTM flag,
+ * so we have to manually check fw_debug.
+ */
+ if (fw_debug)
+ dev_dbg(dev, "HD audio power %s\n", str_enabled_disabled(byte));
+
+ return AE_OK;
case LG_ADDRESS_SPACE_FAN_MODE_ADR:
/*
* The fan mode field is not affected by the DTTM flag, so we
@@ -759,13 +759,13 @@ static void lg_laptop_remove_address_space_handler(void *data)
&lg_laptop_address_space_handler);
}
-static int acpi_add(struct acpi_device *device)
+static int acpi_probe(struct platform_device *pdev)
{
struct platform_device_info pdev_info = {
- .fwnode = acpi_fwnode_handle(device),
.name = PLATFORM_NAME,
.id = PLATFORM_DEVID_NONE,
};
+ struct acpi_device *device;
acpi_status status;
int ret;
const char *product;
@@ -774,13 +774,19 @@ static int acpi_add(struct acpi_device *device)
if (pf_device)
return 0;
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
+ pdev_info.fwnode = acpi_fwnode_handle(device),
+
status = acpi_install_address_space_handler(device->handle, LG_ADDRESS_SPACE_ID,
&lg_laptop_address_space_handler,
- NULL, &device->dev);
+ NULL, &pdev->dev);
if (ACPI_FAILURE(status))
return -ENODEV;
- ret = devm_add_action_or_reset(&device->dev, lg_laptop_remove_address_space_handler,
+ ret = devm_add_action_or_reset(&pdev->dev, lg_laptop_remove_address_space_handler,
device);
if (ret < 0)
return ret;
@@ -833,8 +839,17 @@ static int acpi_add(struct acpi_device *device)
case 'P':
year = 2021;
break;
- default:
+ case 'Q':
year = 2022;
+ break;
+ case 'R':
+ year = 2023;
+ break;
+ case 'S':
+ year = 2024;
+ break;
+ default:
+ year = 2025;
}
break;
default:
@@ -865,7 +880,7 @@ out_platform_registered:
return ret;
}
-static void acpi_remove(struct acpi_device *device)
+static void acpi_remove(struct platform_device *pdev)
{
sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);
@@ -885,34 +900,13 @@ static const struct acpi_device_id device_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, device_ids);
-static struct acpi_driver acpi_driver = {
- .name = "LG Gram Laptop Support",
- .class = "lg-laptop",
- .ids = device_ids,
- .ops = {
- .add = acpi_add,
- .remove = acpi_remove,
- .notify = acpi_notify,
- },
+static struct platform_driver acpi_driver = {
+ .probe = acpi_probe,
+ .remove = acpi_remove,
+ .driver = {
+ .name = "LG Gram Laptop Support",
+ .acpi_match_table = device_ids,
+ },
};
-static int __init acpi_init(void)
-{
- int result;
-
- result = acpi_bus_register_driver(&acpi_driver);
- if (result < 0) {
- pr_debug("Error registering driver\n");
- return -ENODEV;
- }
-
- return 0;
-}
-
-static void __exit acpi_exit(void)
-{
- acpi_bus_unregister_driver(&acpi_driver);
-}
-
-module_init(acpi_init);
-module_exit(acpi_exit);
+module_platform_driver(acpi_driver);
diff --git a/drivers/platform/x86/meraki-mx100.c b/drivers/platform/x86/meraki-mx100.c
index 3751ed36a980..8c5276d98512 100644
--- a/drivers/platform/x86/meraki-mx100.c
+++ b/drivers/platform/x86/meraki-mx100.c
@@ -15,135 +15,256 @@
#include <linux/dmi.h>
#include <linux/err.h>
-#include <linux/gpio_keys.h>
#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/io.h>
#include <linux/kernel.h>
-#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#define TINK_GPIO_DRIVER_NAME "gpio_ich"
+static const struct software_node gpio_ich_node = {
+ .name = TINK_GPIO_DRIVER_NAME,
+};
+
/* LEDs */
-static const struct gpio_led tink_leds[] = {
- {
- .name = "mx100:green:internet",
- .default_trigger = "default-on",
- },
- {
- .name = "mx100:green:lan2",
- },
- {
- .name = "mx100:green:lan3",
- },
- {
- .name = "mx100:green:lan4",
- },
- {
- .name = "mx100:green:lan5",
- },
- {
- .name = "mx100:green:lan6",
- },
- {
- .name = "mx100:green:lan7",
- },
- {
- .name = "mx100:green:lan8",
- },
- {
- .name = "mx100:green:lan9",
- },
- {
- .name = "mx100:green:lan10",
- },
- {
- .name = "mx100:green:lan11",
- },
- {
- .name = "mx100:green:ha",
- },
- {
- .name = "mx100:orange:ha",
- },
- {
- .name = "mx100:green:usb",
- },
- {
- .name = "mx100:orange:usb",
- },
+static const struct software_node tink_gpio_leds_node = {
+ .name = "meraki-mx100-leds",
};
-static const struct gpio_led_platform_data tink_leds_pdata = {
- .num_leds = ARRAY_SIZE(tink_leds),
- .leds = tink_leds,
-};
-
-static struct gpiod_lookup_table tink_leds_table = {
- .dev_id = "leds-gpio",
- .table = {
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 11,
- NULL, 0, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 18,
- NULL, 1, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 20,
- NULL, 2, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 22,
- NULL, 3, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 23,
- NULL, 4, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 32,
- NULL, 5, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 34,
- NULL, 6, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 35,
- NULL, 7, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 36,
- NULL, 8, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 37,
- NULL, 9, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 48,
- NULL, 10, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 16,
- NULL, 11, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 7,
- NULL, 12, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 21,
- NULL, 13, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 19,
- NULL, 14, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- }
+static const struct property_entry tink_internet_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:internet"),
+ PROPERTY_ENTRY_STRING("linux,default-trigger", "default-on"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 11, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_internet_led_node = {
+ .name = "internet-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_internet_led_props,
+};
+
+static const struct property_entry tink_lan2_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan2"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 18, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan2_led_node = {
+ .name = "lan2-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan2_led_props,
+};
+
+static const struct property_entry tink_lan3_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan3"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 20, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan3_led_node = {
+ .name = "lan3-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan3_led_props,
+};
+
+static const struct property_entry tink_lan4_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan4"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 22, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan4_led_node = {
+ .name = "lan4-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan4_led_props,
+};
+
+static const struct property_entry tink_lan5_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan5"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 23, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan5_led_node = {
+ .name = "lan5-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan5_led_props,
+};
+
+static const struct property_entry tink_lan6_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan6"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 32, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan6_led_node = {
+ .name = "lan6-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan6_led_props,
+};
+
+static const struct property_entry tink_lan7_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan7"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 34, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan7_led_node = {
+ .name = "lan7-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan7_led_props,
+};
+
+static const struct property_entry tink_lan8_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan8"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 35, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan8_led_node = {
+ .name = "lan8-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan8_led_props,
+};
+
+static const struct property_entry tink_lan9_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan9"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 36, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan9_led_node = {
+ .name = "lan9-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan9_led_props,
+};
+
+static const struct property_entry tink_lan10_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan10"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 37, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan10_led_node = {
+ .name = "lan10-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan10_led_props,
+};
+
+static const struct property_entry tink_lan11_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:lan11"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 48, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node tink_lan11_led_node = {
+ .name = "lan11-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_lan11_led_props,
+};
+
+static const struct property_entry tink_ha_green_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:ha"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 16, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_ha_green_led_node = {
+ .name = "ha-green-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_ha_green_led_props,
+};
+
+static const struct property_entry tink_ha_orange_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:orange:ha"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 7, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_ha_orange_led_node = {
+ .name = "ha-orange-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_ha_orange_led_props,
+};
+
+static const struct property_entry tink_usb_green_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:green:usb"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 21, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_usb_green_led_node = {
+ .name = "usb-green-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_usb_green_led_props,
+};
+
+static const struct property_entry tink_usb_orange_led_props[] = {
+ PROPERTY_ENTRY_STRING("label", "mx100:orange:usb"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 19, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node tink_usb_orange_led_node = {
+ .name = "usb-orange-led",
+ .parent = &tink_gpio_leds_node,
+ .properties = tink_usb_orange_led_props,
};
/* Reset Button */
-static struct gpio_keys_button tink_buttons[] = {
- {
- .desc = "Reset",
- .type = EV_KEY,
- .code = KEY_RESTART,
- .active_low = 1,
- .debounce_interval = 100,
- },
+static const struct property_entry tink_gpio_keys_props[] = {
+ PROPERTY_ENTRY_U32("poll-interval", 20),
+ { }
};
-static const struct gpio_keys_platform_data tink_buttons_pdata = {
- .buttons = tink_buttons,
- .nbuttons = ARRAY_SIZE(tink_buttons),
- .poll_interval = 20,
- .rep = 0,
- .name = "mx100-keys",
+static const struct software_node tink_gpio_keys_node = {
+ .name = "mx100-keys",
+ .properties = tink_gpio_keys_props,
};
-static struct gpiod_lookup_table tink_keys_table = {
- .dev_id = "gpio-keys-polled",
- .table = {
- GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 60,
- NULL, 0, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- }
+static const struct property_entry tink_reset_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_RESTART),
+ PROPERTY_ENTRY_STRING("label", "Reset"),
+ PROPERTY_ENTRY_GPIO("gpios", &gpio_ich_node, 60, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("linux,input-type", EV_KEY),
+ PROPERTY_ENTRY_U32("debounce-interval", 100),
+ { }
+};
+
+static const struct software_node tink_reset_key_node = {
+ .name = "reset",
+ .parent = &tink_gpio_keys_node,
+ .properties = tink_reset_key_props,
+};
+
+static const struct software_node *tink_swnodes[] = {
+ &gpio_ich_node,
+ /* LEDs nodes */
+ &tink_gpio_leds_node,
+ &tink_internet_led_node,
+ &tink_lan2_led_node,
+ &tink_lan3_led_node,
+ &tink_lan4_led_node,
+ &tink_lan5_led_node,
+ &tink_lan6_led_node,
+ &tink_lan7_led_node,
+ &tink_lan8_led_node,
+ &tink_lan9_led_node,
+ &tink_lan10_led_node,
+ &tink_lan11_led_node,
+ &tink_ha_green_led_node,
+ &tink_ha_orange_led_node,
+ &tink_usb_green_led_node,
+ &tink_usb_orange_led_node,
+ /* Keys nodes */
+ &tink_gpio_keys_node,
+ &tink_reset_key_node,
+ NULL
};
/* Board setup */
@@ -161,22 +282,17 @@ MODULE_DEVICE_TABLE(dmi, tink_systems);
static struct platform_device *tink_leds_pdev;
static struct platform_device *tink_keys_pdev;
-static struct platform_device * __init tink_create_dev(
- const char *name, const void *pdata, size_t sz)
-{
- struct platform_device *pdev;
-
- pdev = platform_device_register_data(NULL,
- name, PLATFORM_DEVID_NONE, pdata, sz);
- if (IS_ERR(pdev))
- pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev));
-
- return pdev;
-}
-
static int __init tink_board_init(void)
{
- int ret;
+ struct platform_device_info keys_info = {
+ .name = "gpio-keys-polled",
+ .id = PLATFORM_DEVID_NONE,
+ };
+ struct platform_device_info leds_info = {
+ .name = "leds-gpio",
+ .id = PLATFORM_DEVID_NONE,
+ };
+ int err;
if (!dmi_first_match(tink_systems))
return -ENODEV;
@@ -188,30 +304,35 @@ static int __init tink_board_init(void)
*/
outl(inl(0x530) | BIT(28), 0x530);
- gpiod_add_lookup_table(&tink_leds_table);
- gpiod_add_lookup_table(&tink_keys_table);
+ err = software_node_register_node_group(tink_swnodes);
+ if (err) {
+ pr_err("failed to register software nodes: %d\n", err);
+ return err;
+ }
- tink_leds_pdev = tink_create_dev("leds-gpio",
- &tink_leds_pdata, sizeof(tink_leds_pdata));
+ leds_info.fwnode = software_node_fwnode(&tink_gpio_leds_node);
+ tink_leds_pdev = platform_device_register_full(&leds_info);
if (IS_ERR(tink_leds_pdev)) {
- ret = PTR_ERR(tink_leds_pdev);
- goto err;
+ err = PTR_ERR(tink_leds_pdev);
+ pr_err("failed to create LED device: %d\n", err);
+ goto err_unregister_swnodes;
}
- tink_keys_pdev = tink_create_dev("gpio-keys-polled",
- &tink_buttons_pdata, sizeof(tink_buttons_pdata));
+ keys_info.fwnode = software_node_fwnode(&tink_gpio_keys_node);
+ tink_keys_pdev = platform_device_register_full(&keys_info);
if (IS_ERR(tink_keys_pdev)) {
- ret = PTR_ERR(tink_keys_pdev);
- platform_device_unregister(tink_leds_pdev);
- goto err;
+ err = PTR_ERR(tink_keys_pdev);
+ pr_err("failed to create key device: %d\n", err);
+ goto err_unregister_leds;
}
return 0;
-err:
- gpiod_remove_lookup_table(&tink_keys_table);
- gpiod_remove_lookup_table(&tink_leds_table);
- return ret;
+err_unregister_leds:
+ platform_device_unregister(tink_leds_pdev);
+err_unregister_swnodes:
+ software_node_unregister_node_group(tink_swnodes);
+ return err;
}
module_init(tink_board_init);
@@ -219,8 +340,7 @@ static void __exit tink_board_exit(void)
{
platform_device_unregister(tink_keys_pdev);
platform_device_unregister(tink_leds_pdev);
- gpiod_remove_lookup_table(&tink_keys_table);
- gpiod_remove_lookup_table(&tink_leds_table);
+ software_node_unregister_node_group(tink_swnodes);
}
module_exit(tink_board_exit);
diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c
index c4b150fa093f..ddef6b78d2fa 100644
--- a/drivers/platform/x86/msi-laptop.c
+++ b/drivers/platform/x86/msi-laptop.c
@@ -1130,6 +1130,9 @@ static void __exit msi_cleanup(void)
sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
if (!quirks->old_ec_model && threeg_exists)
device_remove_file(&msipf_device->dev, &dev_attr_threeg);
+ if (quirks->old_ec_model)
+ sysfs_remove_group(&msipf_device->dev.kobj,
+ &msipf_old_attribute_group);
platform_device_unregister(msipf_device);
platform_driver_unregister(&msipf_driver);
backlight_device_unregister(msibl_device);
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index 9b5c7f8c79b0..e912fcc12d12 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -10,13 +10,16 @@
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/device/driver.h>
+#include <linux/dmi.h>
#include <linux/errno.h>
#include <linux/hwmon.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/printk.h>
#include <linux/rwsem.h>
#include <linux/types.h>
@@ -26,7 +29,7 @@
#define DRIVER_NAME "msi-wmi-platform"
-#define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
+#define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11D1-00A0-C90629100000"
#define MSI_WMI_PLATFORM_INTERFACE_VERSION 2
@@ -76,8 +79,13 @@ enum msi_wmi_platform_method {
MSI_PLATFORM_GET_WMI = 0x1d,
};
-struct msi_wmi_platform_debugfs_data {
+struct msi_wmi_platform_data {
struct wmi_device *wdev;
+ struct mutex wmi_lock; /* Necessary when calling WMI methods */
+};
+
+struct msi_wmi_platform_debugfs_data {
+ struct msi_wmi_platform_data *data;
enum msi_wmi_platform_method method;
struct rw_semaphore buffer_lock; /* Protects debugfs buffer */
size_t length;
@@ -132,8 +140,9 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
return 0;
}
-static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform_method method,
- u8 *input, size_t input_length, u8 *output, size_t output_length)
+static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
+ enum msi_wmi_platform_method method, u8 *input,
+ size_t input_length, u8 *output, size_t output_length)
{
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer in = {
@@ -147,9 +156,15 @@ static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform
if (!input_length || !output_length)
return -EINVAL;
- status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
- if (ACPI_FAILURE(status))
- return -EIO;
+ /*
+ * The ACPI control method responsible for handling the WMI method calls
+ * is not thread-safe. Because of this we have to do the locking ourself.
+ */
+ scoped_guard(mutex, &data->wmi_lock) {
+ status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+ }
obj = out.pointer;
if (!obj)
@@ -170,22 +185,22 @@ static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_senso
static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
int channel, long *val)
{
- struct wmi_device *wdev = dev_get_drvdata(dev);
+ struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
u8 input[32] = { 0 };
u8 output[32];
- u16 data;
+ u16 value;
int ret;
- ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
sizeof(output));
if (ret < 0)
return ret;
- data = get_unaligned_be16(&output[channel * 2 + 1]);
- if (!data)
+ value = get_unaligned_be16(&output[channel * 2 + 1]);
+ if (!value)
*val = 0;
else
- *val = 480000 / data;
+ *val = 480000 / value;
return 0;
}
@@ -231,7 +246,7 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
return ret;
down_write(&data->buffer_lock);
- ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer,
+ ret = msi_wmi_platform_query(data->data, data->method, payload, data->length, data->buffer,
data->length);
up_write(&data->buffer_lock);
@@ -277,17 +292,17 @@ static void msi_wmi_platform_debugfs_remove(void *data)
debugfs_remove_recursive(dir);
}
-static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir,
+static void msi_wmi_platform_debugfs_add(struct msi_wmi_platform_data *drvdata, struct dentry *dir,
const char *name, enum msi_wmi_platform_method method)
{
struct msi_wmi_platform_debugfs_data *data;
struct dentry *entry;
- data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
+ data = devm_kzalloc(&drvdata->wdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return;
- data->wdev = wdev;
+ data->data = drvdata;
data->method = method;
init_rwsem(&data->buffer_lock);
@@ -298,82 +313,82 @@ static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry
entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops);
if (IS_ERR(entry))
- devm_kfree(&wdev->dev, data);
+ devm_kfree(&drvdata->wdev->dev, data);
}
-static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev)
+static void msi_wmi_platform_debugfs_init(struct msi_wmi_platform_data *data)
{
struct dentry *dir;
char dir_name[64];
int ret, method;
- scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev));
+ scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&data->wdev->dev));
dir = debugfs_create_dir(dir_name, NULL);
if (IS_ERR(dir))
return;
- ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir);
+ ret = devm_add_action_or_reset(&data->wdev->dev, msi_wmi_platform_debugfs_remove, dir);
if (ret < 0)
return;
for (method = MSI_PLATFORM_GET_PACKAGE; method <= MSI_PLATFORM_GET_WMI; method++)
- msi_wmi_platform_debugfs_add(wdev, dir, msi_wmi_platform_debugfs_names[method - 1],
+ msi_wmi_platform_debugfs_add(data, dir, msi_wmi_platform_debugfs_names[method - 1],
method);
}
-static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev)
+static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
{
struct device *hdev;
- hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev,
+ hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data,
&msi_wmi_platform_chip_info, NULL);
return PTR_ERR_OR_ZERO(hdev);
}
-static int msi_wmi_platform_ec_init(struct wmi_device *wdev)
+static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data)
{
u8 input[32] = { 0 };
u8 output[32];
u8 flags;
int ret;
- ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
sizeof(output));
if (ret < 0)
return ret;
flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
- dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n",
+ dev_dbg(&data->wdev->dev, "EC RAM version %lu.%lu\n",
FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
- dev_dbg(&wdev->dev, "EC firmware version %.28s\n",
+ dev_dbg(&data->wdev->dev, "EC firmware version %.28s\n",
&output[MSI_PLATFORM_EC_VERSION_OFFSET]);
if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
if (!force)
return -ENODEV;
- dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n");
+ dev_warn(&data->wdev->dev, "Loading on a non-Tigerlake platform\n");
}
return 0;
}
-static int msi_wmi_platform_init(struct wmi_device *wdev)
+static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
{
u8 input[32] = { 0 };
u8 output[32];
int ret;
- ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
+ ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
sizeof(output));
if (ret < 0)
return ret;
- dev_dbg(&wdev->dev, "WMI interface version %u.%u\n",
+ dev_dbg(&data->wdev->dev, "WMI interface version %u.%u\n",
output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
@@ -381,7 +396,8 @@ static int msi_wmi_platform_init(struct wmi_device *wdev)
if (!force)
return -ENODEV;
- dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n",
+ dev_warn(&data->wdev->dev,
+ "Loading despite unsupported WMI interface version (%u.%u)\n",
output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
}
@@ -391,19 +407,31 @@ static int msi_wmi_platform_init(struct wmi_device *wdev)
static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
{
+ struct msi_wmi_platform_data *data;
int ret;
- ret = msi_wmi_platform_init(wdev);
+ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->wdev = wdev;
+ dev_set_drvdata(&wdev->dev, data);
+
+ ret = devm_mutex_init(&wdev->dev, &data->wmi_lock);
if (ret < 0)
return ret;
- ret = msi_wmi_platform_ec_init(wdev);
+ ret = msi_wmi_platform_init(data);
if (ret < 0)
return ret;
- msi_wmi_platform_debugfs_init(wdev);
+ ret = msi_wmi_platform_ec_init(data);
+ if (ret < 0)
+ return ret;
- return msi_wmi_platform_hwmon_init(wdev);
+ msi_wmi_platform_debugfs_init(data);
+
+ return msi_wmi_platform_hwmon_init(data);
}
static const struct wmi_device_id msi_wmi_platform_id_table[] = {
@@ -421,7 +449,45 @@ static struct wmi_driver msi_wmi_platform_driver = {
.probe = msi_wmi_platform_probe,
.no_singleton = true,
};
-module_wmi_driver(msi_wmi_platform_driver);
+
+/*
+ * MSI reused the WMI GUID from the WMI-ACPI sample code provided by Microsoft,
+ * so other manufacturers might use it as well for their WMI-ACPI implementations.
+ */
+static const struct dmi_system_id msi_wmi_platform_whitelist[] __initconst = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"),
+ },
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+ },
+ },
+ { }
+};
+
+static int __init msi_wmi_platform_module_init(void)
+{
+ if (!dmi_check_system(msi_wmi_platform_whitelist)) {
+ if (!force)
+ return -ENODEV;
+
+ pr_warn("Ignoring DMI whitelist\n");
+ }
+
+ return wmi_driver_register(&msi_wmi_platform_driver);
+}
+
+static void __exit msi_wmi_platform_module_exit(void)
+{
+ wmi_driver_unregister(&msi_wmi_platform_driver);
+}
+
+module_init(msi_wmi_platform_module_init);
+module_exit(msi_wmi_platform_module_exit);
+
MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
MODULE_DESCRIPTION("MSI WMI platform features");
diff --git a/drivers/platform/x86/mxm-wmi.c b/drivers/platform/x86/mxm-wmi.c
index 9a457956025a..dbc5e35ec38b 100644
--- a/drivers/platform/x86/mxm-wmi.c
+++ b/drivers/platform/x86/mxm-wmi.c
@@ -80,15 +80,3 @@ bool mxm_wmi_supported(void)
return guid_valid;
}
EXPORT_SYMBOL_GPL(mxm_wmi_supported);
-
-static int __init mxm_wmi_init(void)
-{
- return 0;
-}
-
-static void __exit mxm_wmi_exit(void)
-{
-}
-
-module_init(mxm_wmi_init);
-module_exit(mxm_wmi_exit);
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
new file mode 100644
index 000000000000..6d4a53a2ed60
--- /dev/null
+++ b/drivers/platform/x86/oxpec.c
@@ -0,0 +1,1001 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Platform driver for OneXPlayer and AOKZOE devices.
+ *
+ * Fan control is provided via pwm interface in the range [0-255].
+ * Old AMD boards use [0-100] as range in the EC, the written value is
+ * scaled to accommodate for that. Newer boards like the mini PRO and
+ * AOKZOE are not scaled but have the same EC layout. Newer models
+ * like the 2 and X1 are [0-184] and are scaled to 0-255. OrangePi
+ * are [1-244] and scaled to 0-255.
+ *
+ * Copyright (C) 2022 Joaquín I. Aramendía <samsagax@gmail.com>
+ * Copyright (C) 2024 Derek J. Clark <derekjohn.clark@gmail.com>
+ * Copyright (C) 2025-2026 Antheas Kapenekakis <lkml@antheas.dev>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/hwmon.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/processor.h>
+#include <acpi/battery.h>
+
+/* Handle ACPI lock mechanism */
+static u32 oxp_mutex;
+
+#define ACPI_LOCK_DELAY_MS 500
+
+static bool lock_global_acpi_lock(void)
+{
+ return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, &oxp_mutex));
+}
+
+static bool unlock_global_acpi_lock(void)
+{
+ return ACPI_SUCCESS(acpi_release_global_lock(oxp_mutex));
+}
+
+enum oxp_board {
+ aok_zoe_a1 = 1,
+ orange_pi_neo,
+ oxp_2,
+ oxp_fly,
+ oxp_mini_amd,
+ oxp_mini_amd_a07,
+ oxp_mini_amd_pro,
+ oxp_x1,
+ oxp_g1_i,
+ oxp_g1_a,
+};
+
+static enum oxp_board board;
+static struct device *oxp_dev;
+
+/* Fan reading and PWM */
+#define OXP_SENSOR_FAN_REG 0x76 /* Fan reading is 2 registers long */
+#define OXP_2_SENSOR_FAN_REG 0x58 /* Fan reading is 2 registers long */
+#define OXP_SENSOR_PWM_ENABLE_REG 0x4A /* PWM enable is 1 register long */
+#define OXP_SENSOR_PWM_REG 0x4B /* PWM reading is 1 register long */
+#define PWM_MODE_AUTO 0x00
+#define PWM_MODE_MANUAL 0x01
+
+/* OrangePi fan reading and PWM */
+#define ORANGEPI_SENSOR_FAN_REG 0x78 /* Fan reading is 2 registers long */
+#define ORANGEPI_SENSOR_PWM_ENABLE_REG 0x40 /* PWM enable is 1 register long */
+#define ORANGEPI_SENSOR_PWM_REG 0x38 /* PWM reading is 1 register long */
+
+/* Turbo button takeover function
+ * Different boards have different values and EC registers
+ * for the same function
+ */
+#define OXP_TURBO_SWITCH_REG 0xF1 /* Mini Pro, OneXFly, AOKZOE */
+#define OXP_2_TURBO_SWITCH_REG 0xEB /* OXP2 and X1 */
+#define OXP_MINI_TURBO_SWITCH_REG 0x1E /* Mini AO7 */
+
+#define OXP_MINI_TURBO_TAKE_VAL 0x01 /* Mini AO7 */
+#define OXP_TURBO_TAKE_VAL 0x40 /* All other models */
+
+/* X1 Turbo LED */
+#define OXP_X1_TURBO_LED_REG 0x57
+
+#define OXP_X1_TURBO_LED_OFF 0x01
+#define OXP_X1_TURBO_LED_ON 0x02
+
+/* Battery extension settings */
+#define EC_CHARGE_CONTROL_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE))
+
+#define OXP_X1_CHARGE_LIMIT_REG 0xA3 /* X1 charge limit (%) */
+#define OXP_X1_CHARGE_INHIBIT_REG 0xA4 /* X1 bypass charging */
+
+#define OXP_X1_CHARGE_INHIBIT_MASK_AWAKE 0x01
+/* X1 Mask is 0x0A, F1Pro is 0x02 but the extra bit on the X1 does nothing. */
+#define OXP_X1_CHARGE_INHIBIT_MASK_OFF 0x02
+#define OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS (OXP_X1_CHARGE_INHIBIT_MASK_AWAKE | \
+ OXP_X1_CHARGE_INHIBIT_MASK_OFF)
+
+static const struct dmi_system_id dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 AR07"),
+ },
+ .driver_data = (void *)aok_zoe_a1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1 Pro"),
+ },
+ .driver_data = (void *)aok_zoe_a1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A2 Pro"),
+ },
+ .driver_data = (void *)aok_zoe_a1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AOKZOE"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AOKZOE A1X"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "OrangePi"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "NEO-01"),
+ },
+ .driver_data = (void *)orange_pi_neo,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONE XPLAYER"),
+ },
+ .driver_data = (void *)oxp_mini_amd,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_MATCH(DMI_BOARD_NAME, "ONEXPLAYER 2"),
+ },
+ .driver_data = (void *)oxp_2,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER APEX"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 EVA-01"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 OLED"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1L"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1Pro"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER F1 EVA-02"),
+ },
+ .driver_data = (void *)oxp_fly,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 A"),
+ },
+ .driver_data = (void *)oxp_g1_a,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER G1 i"),
+ },
+ .driver_data = (void *)oxp_g1_i,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER mini A07"),
+ },
+ .driver_data = (void *)oxp_mini_amd_a07,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER Mini Pro"),
+ },
+ .driver_data = (void *)oxp_mini_amd_pro,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1z"),
+ },
+ .driver_data = (void *)oxp_x1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 A"),
+ },
+ .driver_data = (void *)oxp_x1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 i"),
+ },
+ .driver_data = (void *)oxp_x1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Air"),
+ },
+ .driver_data = (void *)oxp_x1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1 mini"),
+ },
+ .driver_data = (void *)oxp_x1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Mini Pro"),
+ },
+ .driver_data = (void *)oxp_x1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Pro"),
+ },
+ .driver_data = (void *)oxp_x1,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1Pro EVA-02"),
+ },
+ .driver_data = (void *)oxp_x1,
+ },
+ {},
+};
+
+/* Helper functions to handle EC read/write */
+static int read_from_ec(u8 reg, int size, long *val)
+{
+ u8 buffer;
+ int ret;
+ int i;
+
+ if (!lock_global_acpi_lock())
+ return -EBUSY;
+
+ *val = 0;
+ for (i = 0; i < size; i++) {
+ ret = ec_read(reg + i, &buffer);
+ if (ret)
+ return ret;
+ *val <<= i * 8;
+ *val += buffer;
+ }
+
+ if (!unlock_global_acpi_lock())
+ return -EBUSY;
+
+ return 0;
+}
+
+static int write_to_ec(u8 reg, u8 value)
+{
+ int ret;
+
+ if (!lock_global_acpi_lock())
+ return -EBUSY;
+
+ ret = ec_write(reg, value);
+
+ if (!unlock_global_acpi_lock())
+ return -EBUSY;
+
+ return ret;
+}
+
+/* Callbacks for turbo toggle attribute */
+static umode_t tt_toggle_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ switch (board) {
+ case aok_zoe_a1:
+ case oxp_2:
+ case oxp_fly:
+ case oxp_mini_amd_a07:
+ case oxp_mini_amd_pro:
+ case oxp_x1:
+ case oxp_g1_i:
+ case oxp_g1_a:
+ return attr->mode;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static ssize_t tt_toggle_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u8 reg, mask, val;
+ long raw_val;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret)
+ return ret;
+
+ switch (board) {
+ case oxp_mini_amd_a07:
+ reg = OXP_MINI_TURBO_SWITCH_REG;
+ mask = OXP_MINI_TURBO_TAKE_VAL;
+ break;
+ case aok_zoe_a1:
+ case oxp_fly:
+ case oxp_mini_amd_pro:
+ case oxp_g1_a:
+ reg = OXP_TURBO_SWITCH_REG;
+ mask = OXP_TURBO_TAKE_VAL;
+ break;
+ case oxp_2:
+ case oxp_x1:
+ case oxp_g1_i:
+ reg = OXP_2_TURBO_SWITCH_REG;
+ mask = OXP_TURBO_TAKE_VAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = read_from_ec(reg, 1, &raw_val);
+ if (ret)
+ return ret;
+
+ val = raw_val;
+ if (enable)
+ val |= mask;
+ else
+ val &= ~mask;
+
+ ret = write_to_ec(reg, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t tt_toggle_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 reg, mask;
+ int retval;
+ long val;
+
+ switch (board) {
+ case oxp_mini_amd_a07:
+ reg = OXP_MINI_TURBO_SWITCH_REG;
+ mask = OXP_MINI_TURBO_TAKE_VAL;
+ break;
+ case aok_zoe_a1:
+ case oxp_fly:
+ case oxp_mini_amd_pro:
+ case oxp_g1_a:
+ reg = OXP_TURBO_SWITCH_REG;
+ mask = OXP_TURBO_TAKE_VAL;
+ break;
+ case oxp_2:
+ case oxp_x1:
+ case oxp_g1_i:
+ reg = OXP_2_TURBO_SWITCH_REG;
+ mask = OXP_TURBO_TAKE_VAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ retval = read_from_ec(reg, 1, &val);
+ if (retval)
+ return retval;
+
+ return sysfs_emit(buf, "%d\n", (val & mask) == mask);
+}
+
+static DEVICE_ATTR_RW(tt_toggle);
+
+/* Callbacks for turbo LED attribute */
+static umode_t tt_led_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ switch (board) {
+ case oxp_x1:
+ return attr->mode;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static ssize_t tt_led_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u8 reg, val;
+ bool value;
+ int ret;
+
+ ret = kstrtobool(buf, &value);
+ if (ret)
+ return ret;
+
+ switch (board) {
+ case oxp_x1:
+ reg = OXP_X1_TURBO_LED_REG;
+ val = value ? OXP_X1_TURBO_LED_ON : OXP_X1_TURBO_LED_OFF;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = write_to_ec(reg, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t tt_led_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ long enval;
+ long val;
+ int ret;
+ u8 reg;
+
+ switch (board) {
+ case oxp_x1:
+ reg = OXP_X1_TURBO_LED_REG;
+ enval = OXP_X1_TURBO_LED_ON;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = read_from_ec(reg, 1, &val);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", val == enval);
+}
+
+static DEVICE_ATTR_RW(tt_led);
+
+/* Callbacks for charge behaviour attributes */
+static bool oxp_psy_ext_supported(void)
+{
+ switch (board) {
+ case oxp_x1:
+ case oxp_g1_i:
+ case oxp_g1_a:
+ case oxp_fly:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static int oxp_psy_ext_get_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ long raw_val;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ ret = read_from_ec(OXP_X1_CHARGE_LIMIT_REG, 1, &raw_val);
+ if (ret)
+ return ret;
+ if (raw_val < 0 || raw_val > 100)
+ return -EINVAL;
+ val->intval = raw_val;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ ret = read_from_ec(OXP_X1_CHARGE_INHIBIT_REG, 1, &raw_val);
+ if (ret)
+ return ret;
+ if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS) ==
+ OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ else if ((raw_val & OXP_X1_CHARGE_INHIBIT_MASK_AWAKE) ==
+ OXP_X1_CHARGE_INHIBIT_MASK_AWAKE)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int oxp_psy_ext_set_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ long raw_val;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ if (val->intval < 0 || val->intval > 100)
+ return -EINVAL;
+ return write_to_ec(OXP_X1_CHARGE_LIMIT_REG, val->intval);
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ raw_val = 0;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE:
+ raw_val = OXP_X1_CHARGE_INHIBIT_MASK_AWAKE;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ raw_val = OXP_X1_CHARGE_INHIBIT_MASK_ALWAYS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return write_to_ec(OXP_X1_CHARGE_INHIBIT_REG, raw_val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int oxp_psy_prop_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp)
+{
+ return true;
+}
+
+static const enum power_supply_property oxp_psy_ext_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_ext oxp_psy_ext = {
+ .name = "oxp-charge-control",
+ .properties = oxp_psy_ext_props,
+ .num_properties = ARRAY_SIZE(oxp_psy_ext_props),
+ .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS,
+ .get_property = oxp_psy_ext_get_prop,
+ .set_property = oxp_psy_ext_set_prop,
+ .property_is_writeable = oxp_psy_prop_is_writeable,
+};
+
+static int oxp_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ return power_supply_register_extension(battery, &oxp_psy_ext, oxp_dev, NULL);
+}
+
+static int oxp_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &oxp_psy_ext);
+ return 0;
+}
+
+static struct acpi_battery_hook battery_hook = {
+ .add_battery = oxp_add_battery,
+ .remove_battery = oxp_remove_battery,
+ .name = "OneXPlayer Battery",
+};
+
+/* PWM enable/disable functions */
+static int oxp_pwm_enable(void)
+{
+ switch (board) {
+ case orange_pi_neo:
+ return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
+ case aok_zoe_a1:
+ case oxp_2:
+ case oxp_fly:
+ case oxp_mini_amd:
+ case oxp_mini_amd_a07:
+ case oxp_mini_amd_pro:
+ case oxp_x1:
+ case oxp_g1_i:
+ case oxp_g1_a:
+ return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int oxp_pwm_disable(void)
+{
+ switch (board) {
+ case orange_pi_neo:
+ return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
+ case aok_zoe_a1:
+ case oxp_2:
+ case oxp_fly:
+ case oxp_mini_amd:
+ case oxp_mini_amd_a07:
+ case oxp_mini_amd_pro:
+ case oxp_x1:
+ case oxp_g1_i:
+ case oxp_g1_a:
+ return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int oxp_pwm_read(long *val)
+{
+ switch (board) {
+ case orange_pi_neo:
+ return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, 1, val);
+ case aok_zoe_a1:
+ case oxp_2:
+ case oxp_fly:
+ case oxp_mini_amd:
+ case oxp_mini_amd_a07:
+ case oxp_mini_amd_pro:
+ case oxp_x1:
+ case oxp_g1_i:
+ case oxp_g1_a:
+ return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* Callbacks for hwmon interface */
+static umode_t oxp_ec_hwmon_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type, u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ return 0444;
+ case hwmon_pwm:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+/* Fan speed read function */
+static int oxp_pwm_fan_speed(long *val)
+{
+ switch (board) {
+ case orange_pi_neo:
+ return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val);
+ case oxp_2:
+ case oxp_x1:
+ case oxp_g1_i:
+ return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val);
+ case aok_zoe_a1:
+ case oxp_fly:
+ case oxp_mini_amd:
+ case oxp_mini_amd_a07:
+ case oxp_mini_amd_pro:
+ case oxp_g1_a:
+ return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/* PWM input read/write functions */
+static int oxp_pwm_input_write(long val)
+{
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ switch (board) {
+ case orange_pi_neo:
+ /* scale to range [1-244] */
+ val = ((val - 1) * 243 / 254) + 1;
+ return write_to_ec(ORANGEPI_SENSOR_PWM_REG, val);
+ case oxp_2:
+ case oxp_x1:
+ case oxp_g1_i:
+ /* scale to range [0-184] */
+ val = (val * 184) / 255;
+ return write_to_ec(OXP_SENSOR_PWM_REG, val);
+ case oxp_mini_amd:
+ case oxp_mini_amd_a07:
+ /* scale to range [0-100] */
+ val = (val * 100) / 255;
+ return write_to_ec(OXP_SENSOR_PWM_REG, val);
+ case aok_zoe_a1:
+ case oxp_fly:
+ case oxp_mini_amd_pro:
+ case oxp_g1_a:
+ return write_to_ec(OXP_SENSOR_PWM_REG, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int oxp_pwm_input_read(long *val)
+{
+ int ret;
+
+ switch (board) {
+ case orange_pi_neo:
+ ret = read_from_ec(ORANGEPI_SENSOR_PWM_REG, 1, val);
+ if (ret)
+ return ret;
+ /* scale from range [1-244] */
+ *val = ((*val - 1) * 254 / 243) + 1;
+ break;
+ case oxp_2:
+ case oxp_x1:
+ case oxp_g1_i:
+ ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
+ if (ret)
+ return ret;
+ /* scale from range [0-184] */
+ *val = (*val * 255) / 184;
+ break;
+ case oxp_mini_amd:
+ case oxp_mini_amd_a07:
+ ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
+ if (ret)
+ return ret;
+ /* scale from range [0-100] */
+ *val = (*val * 255) / 100;
+ break;
+ case aok_zoe_a1:
+ case oxp_fly:
+ case oxp_mini_amd_pro:
+ case oxp_g1_a:
+ default:
+ ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
+ if (ret)
+ return ret;
+ break;
+ }
+ return 0;
+}
+
+static int oxp_platform_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ int ret;
+
+ switch (type) {
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ return oxp_pwm_fan_speed(val);
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ return oxp_pwm_input_read(val);
+ case hwmon_pwm_enable:
+ ret = oxp_pwm_read(val);
+ if (ret)
+ return ret;
+
+ /* Check for auto and return 2 */
+ if (!*val) {
+ *val = 2;
+ return 0;
+ }
+
+ /* Return 0 if at full fan speed, 1 otherwise */
+ ret = oxp_pwm_fan_speed(val);
+ if (ret)
+ return ret;
+
+ if (*val == 255)
+ *val = 0;
+ else
+ *val = 1;
+
+ return 0;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static int oxp_platform_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ int ret;
+
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ if (val == 1)
+ return oxp_pwm_enable();
+ else if (val == 2)
+ return oxp_pwm_disable();
+ else if (val != 0)
+ return -EINVAL;
+
+ /* Enable PWM and set to max speed */
+ ret = oxp_pwm_enable();
+ if (ret)
+ return ret;
+ return oxp_pwm_input_write(255);
+ case hwmon_pwm_input:
+ return oxp_pwm_input_write(val);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+/* Known sensors in the OXP EC controllers */
+static const struct hwmon_channel_info * const oxp_platform_sensors[] = {
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+ NULL,
+};
+
+static struct attribute *oxp_tt_toggle_attrs[] = {
+ &dev_attr_tt_toggle.attr,
+ NULL
+};
+
+static const struct attribute_group oxp_tt_toggle_attribute_group = {
+ .is_visible = tt_toggle_is_visible,
+ .attrs = oxp_tt_toggle_attrs,
+};
+
+static struct attribute *oxp_tt_led_attrs[] = {
+ &dev_attr_tt_led.attr,
+ NULL
+};
+
+static const struct attribute_group oxp_tt_led_attribute_group = {
+ .is_visible = tt_led_is_visible,
+ .attrs = oxp_tt_led_attrs,
+};
+
+static const struct attribute_group *oxp_ec_groups[] = {
+ &oxp_tt_toggle_attribute_group,
+ &oxp_tt_led_attribute_group,
+ NULL
+};
+
+static const struct hwmon_ops oxp_ec_hwmon_ops = {
+ .is_visible = oxp_ec_hwmon_is_visible,
+ .read = oxp_platform_read,
+ .write = oxp_platform_write,
+};
+
+static const struct hwmon_chip_info oxp_ec_chip_info = {
+ .ops = &oxp_ec_hwmon_ops,
+ .info = oxp_platform_sensors,
+};
+
+/* Initialization logic */
+static int oxp_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device *hwdev;
+ int ret;
+
+ oxp_dev = dev;
+ hwdev = devm_hwmon_device_register_with_info(dev, "oxp_ec", NULL,
+ &oxp_ec_chip_info, NULL);
+
+ if (IS_ERR(hwdev))
+ return PTR_ERR(hwdev);
+
+ if (oxp_psy_ext_supported()) {
+ ret = devm_battery_hook_register(dev, &battery_hook);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver oxp_platform_driver = {
+ .driver = {
+ .name = "oxp-platform",
+ .dev_groups = oxp_ec_groups,
+ },
+ .probe = oxp_platform_probe,
+};
+
+static struct platform_device *oxp_platform_device;
+
+static int __init oxp_platform_init(void)
+{
+ const struct dmi_system_id *dmi_entry;
+
+ dmi_entry = dmi_first_match(dmi_table);
+ if (!dmi_entry)
+ return -ENODEV;
+
+ board = (enum oxp_board)(unsigned long)dmi_entry->driver_data;
+
+ /*
+ * Have to check for AMD processor here because DMI strings are the same
+ * between Intel and AMD boards on older OneXPlayer devices, the only way
+ * to tell them apart is the CPU. Old Intel boards have an unsupported EC.
+ */
+ if (board == oxp_mini_amd && boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
+ return -ENODEV;
+
+ oxp_platform_device =
+ platform_create_bundle(&oxp_platform_driver,
+ oxp_platform_probe, NULL, 0, NULL, 0);
+
+ return PTR_ERR_OR_ZERO(oxp_platform_device);
+}
+
+static void __exit oxp_platform_exit(void)
+{
+ platform_device_unregister(oxp_platform_device);
+ platform_driver_unregister(&oxp_platform_driver);
+}
+
+MODULE_DEVICE_TABLE(dmi, dmi_table);
+
+module_init(oxp_platform_init);
+module_exit(oxp_platform_exit);
+
+MODULE_AUTHOR("Joaquín Ignacio Aramendía <samsagax@gmail.com>");
+MODULE_DESCRIPTION("Platform driver that handles EC sensors of OneXPlayer devices");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 2987b4db6009..b83113c26f88 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -183,9 +183,9 @@ enum SINF_BITS { SINF_NUM_BATTERIES = 0,
};
/* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */
-static int acpi_pcc_hotkey_add(struct acpi_device *device);
-static void acpi_pcc_hotkey_remove(struct acpi_device *device);
-static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event);
+static int acpi_pcc_hotkey_probe(struct platform_device *pdev);
+static void acpi_pcc_hotkey_remove(struct platform_device *pdev);
+static void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data);
static const struct acpi_device_id pcc_device_ids[] = {
{ "MAT0012", 0},
@@ -201,16 +201,14 @@ static int acpi_pcc_hotkey_resume(struct device *dev);
#endif
static SIMPLE_DEV_PM_OPS(acpi_pcc_hotkey_pm, NULL, acpi_pcc_hotkey_resume);
-static struct acpi_driver acpi_pcc_driver = {
- .name = ACPI_PCC_DRIVER_NAME,
- .class = ACPI_PCC_CLASS,
- .ids = pcc_device_ids,
- .ops = {
- .add = acpi_pcc_hotkey_add,
- .remove = acpi_pcc_hotkey_remove,
- .notify = acpi_pcc_hotkey_notify,
- },
- .drv.pm = &acpi_pcc_hotkey_pm,
+static struct platform_driver acpi_pcc_driver = {
+ .probe = acpi_pcc_hotkey_probe,
+ .remove = acpi_pcc_hotkey_remove,
+ .driver = {
+ .name = ACPI_PCC_DRIVER_NAME,
+ .acpi_match_table = pcc_device_ids,
+ .pm = &acpi_pcc_hotkey_pm,
+ },
};
static const struct key_entry panasonic_keymap[] = {
@@ -869,9 +867,9 @@ static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc)
pr_err("Unknown hotkey event: 0x%04llx\n", result);
}
-static void acpi_pcc_hotkey_notify(struct acpi_device *device, u32 event)
+static void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data)
{
- struct pcc_acpi *pcc = acpi_driver_data(device);
+ struct pcc_acpi *pcc = data;
switch (event) {
case HKEY_NOTIFY:
@@ -891,7 +889,7 @@ static void pcc_optd_notify(acpi_handle handle, u32 event, void *data)
set_optd_power_state(0);
}
-static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node)
+static void pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node)
{
acpi_status status;
acpi_handle handle;
@@ -904,10 +902,7 @@ static int pcc_register_optd_notifier(struct pcc_acpi *pcc, char *node)
pcc_optd_notify, pcc);
if (ACPI_FAILURE(status))
pr_err("Failed to register notify on %s\n", node);
- } else
- return -ENODEV;
-
- return 0;
+ }
}
static void pcc_unregister_optd_notifier(struct pcc_acpi *pcc, char *node)
@@ -968,14 +963,7 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc)
#ifdef CONFIG_PM_SLEEP
static int acpi_pcc_hotkey_resume(struct device *dev)
{
- struct pcc_acpi *pcc;
-
- if (!dev)
- return -EINVAL;
-
- pcc = acpi_driver_data(to_acpi_device(dev));
- if (!pcc)
- return -EINVAL;
+ struct pcc_acpi *pcc = acpi_driver_data(ACPI_COMPANION(dev));
if (pcc->num_sifr > SINF_MUTE)
acpi_pcc_write_sset(pcc, SINF_MUTE, pcc->mute);
@@ -991,14 +979,16 @@ static int acpi_pcc_hotkey_resume(struct device *dev)
}
#endif
-static int acpi_pcc_hotkey_add(struct acpi_device *device)
+static int acpi_pcc_hotkey_probe(struct platform_device *pdev)
{
struct backlight_properties props;
+ struct acpi_device *device;
struct pcc_acpi *pcc;
int num_sifr, result;
+ device = ACPI_COMPANION(&pdev->dev);
if (!device)
- return -EINVAL;
+ return -ENODEV;
num_sifr = acpi_pcc_get_sqty(device);
@@ -1017,7 +1007,7 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
*/
num_sifr++;
- pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL);
+ pcc = kzalloc_obj(struct pcc_acpi);
if (!pcc) {
pr_err("Couldn't allocate mem for pcc");
return -ENOMEM;
@@ -1033,8 +1023,8 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
pcc->handle = device->handle;
pcc->num_sifr = num_sifr;
device->driver_data = pcc;
- strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
- strcpy(acpi_device_class(device), ACPI_PCC_CLASS);
+ strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME);
+ strscpy(acpi_device_class(device), ACPI_PCC_CLASS);
result = acpi_pcc_init_input(pcc);
if (result) {
@@ -1083,19 +1073,25 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
if (result)
goto out_backlight;
+ result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ acpi_pcc_hotkey_notify, pcc);
+ if (result)
+ goto out_sysfs;
+
/* optical drive initialization */
if (ACPI_SUCCESS(check_optd_present())) {
pcc->platform = platform_device_register_simple("panasonic",
PLATFORM_DEVID_NONE, NULL, 0);
if (IS_ERR(pcc->platform)) {
result = PTR_ERR(pcc->platform);
- goto out_backlight;
+ goto out_notify_handler;
}
result = device_create_file(&pcc->platform->dev,
&dev_attr_cdpower);
- pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
if (result)
goto out_platform;
+
+ pcc_register_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
} else {
pcc->platform = NULL;
}
@@ -1105,11 +1101,17 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
out_platform:
platform_device_unregister(pcc->platform);
+out_notify_handler:
+ acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ acpi_pcc_hotkey_notify);
+out_sysfs:
+ sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
out_backlight:
backlight_device_unregister(pcc->backlight);
out_input:
input_unregister_device(pcc->input_dev);
out_sinf:
+ device->driver_data = NULL;
kfree(pcc->sinf);
out_hotkey:
kfree(pcc);
@@ -1117,20 +1119,21 @@ out_hotkey:
return result;
}
-static void acpi_pcc_hotkey_remove(struct acpi_device *device)
+static void acpi_pcc_hotkey_remove(struct platform_device *pdev)
{
+ struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
struct pcc_acpi *pcc = acpi_driver_data(device);
- if (!device || !pcc)
- return;
-
i8042_remove_filter(panasonic_i8042_filter);
if (pcc->platform) {
+ pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
device_remove_file(&pcc->platform->dev, &dev_attr_cdpower);
platform_device_unregister(pcc->platform);
}
- pcc_unregister_optd_notifier(pcc, "\\_SB.PCI0.EHCI.ERHB.OPTD");
+
+ acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ acpi_pcc_hotkey_notify);
sysfs_remove_group(&device->dev.kobj, &pcc_attr_group);
@@ -1138,8 +1141,10 @@ static void acpi_pcc_hotkey_remove(struct acpi_device *device)
input_unregister_device(pcc->input_dev);
+ device->driver_data = NULL;
+
kfree(pcc->sinf);
kfree(pcc);
}
-module_acpi_driver(acpi_pcc_driver);
+module_platform_driver(acpi_pcc_driver);
diff --git a/drivers/platform/x86/pcengines-apuv2.c b/drivers/platform/x86/pcengines-apuv2.c
index 3aa63b18a2e1..3f19589d1ba0 100644
--- a/drivers/platform/x86/pcengines-apuv2.c
+++ b/drivers/platform/x86/pcengines-apuv2.c
@@ -12,13 +12,13 @@
#include <linux/dmi.h>
#include <linux/err.h>
+#include <linux/gpio/machine.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/kernel.h>
-#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
-#include <linux/gpio_keys.h>
-#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/property.h>
#include <linux/platform_data/gpio/gpio-amd-fch.h>
/*
@@ -72,60 +72,91 @@ static const struct amd_fch_gpio_pdata board_apu2 = {
.gpio_names = apu2_gpio_names,
};
+static const struct software_node apu2_gpiochip_node = {
+ .name = AMD_FCH_GPIO_DRIVER_NAME,
+};
+
/* GPIO LEDs device */
+static const struct software_node apu2_leds_node = {
+ .name = "apu2-leds",
+};
-static const struct gpio_led apu2_leds[] = {
- { .name = "apu:green:1" },
- { .name = "apu:green:2" },
- { .name = "apu:green:3" },
+static const struct property_entry apu2_led1_props[] = {
+ PROPERTY_ENTRY_STRING("label", "apu:green:1"),
+ PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node,
+ APU2_GPIO_LINE_LED1, GPIO_ACTIVE_LOW),
+ { }
};
-static const struct gpio_led_platform_data apu2_leds_pdata = {
- .num_leds = ARRAY_SIZE(apu2_leds),
- .leds = apu2_leds,
+static const struct software_node apu2_led1_swnode = {
+ .name = "led-1",
+ .parent = &apu2_leds_node,
+ .properties = apu2_led1_props,
};
-static struct gpiod_lookup_table gpios_led_table = {
- .dev_id = "leds-gpio",
- .table = {
- GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED1,
- NULL, 0, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED2,
- NULL, 1, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED3,
- NULL, 2, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- }
+static const struct property_entry apu2_led2_props[] = {
+ PROPERTY_ENTRY_STRING("label", "apu:green:2"),
+ PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node,
+ APU2_GPIO_LINE_LED2, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node apu2_led2_swnode = {
+ .name = "led-2",
+ .parent = &apu2_leds_node,
+ .properties = apu2_led2_props,
+};
+
+static const struct property_entry apu2_led3_props[] = {
+ PROPERTY_ENTRY_STRING("label", "apu:green:3"),
+ PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node,
+ APU2_GPIO_LINE_LED3, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node apu2_led3_swnode = {
+ .name = "led-3",
+ .parent = &apu2_leds_node,
+ .properties = apu2_led3_props,
};
/* GPIO keyboard device */
+static const struct property_entry apu2_keys_props[] = {
+ PROPERTY_ENTRY_U32("poll-interval", 100),
+ { }
+};
-static struct gpio_keys_button apu2_keys_buttons[] = {
- {
- .code = KEY_RESTART,
- .active_low = 1,
- .desc = "front button",
- .type = EV_KEY,
- .debounce_interval = 10,
- .value = 1,
- },
+static const struct software_node apu2_keys_node = {
+ .name = "apu2-keys",
+ .properties = apu2_keys_props,
};
-static const struct gpio_keys_platform_data apu2_keys_pdata = {
- .buttons = apu2_keys_buttons,
- .nbuttons = ARRAY_SIZE(apu2_keys_buttons),
- .poll_interval = 100,
- .rep = 0,
- .name = "apu2-keys",
+static const struct property_entry apu2_front_button_props[] = {
+ PROPERTY_ENTRY_STRING("label", "front button"),
+ PROPERTY_ENTRY_U32("linux,code", KEY_RESTART),
+ PROPERTY_ENTRY_GPIO("gpios", &apu2_gpiochip_node,
+ APU2_GPIO_LINE_MODESW, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 10),
+ { }
};
-static struct gpiod_lookup_table gpios_key_table = {
- .dev_id = "gpio-keys-polled",
- .table = {
- GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_MODESW,
- NULL, 0, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- }
+static const struct software_node apu2_front_button_swnode = {
+ .name = "front-button",
+ .parent = &apu2_keys_node,
+ .properties = apu2_front_button_props,
+};
+
+static const struct software_node *apu2_swnodes[] = {
+ &apu2_gpiochip_node,
+ /* LEDs nodes */
+ &apu2_leds_node,
+ &apu2_led1_swnode,
+ &apu2_led2_swnode,
+ &apu2_led3_swnode,
+ /* Keys nodes */
+ &apu2_keys_node,
+ &apu2_front_button_swnode,
+ NULL
};
/* Board setup */
@@ -222,23 +253,25 @@ static struct platform_device *apu_gpio_pdev;
static struct platform_device *apu_leds_pdev;
static struct platform_device *apu_keys_pdev;
-static struct platform_device * __init apu_create_pdev(
- const char *name,
- const void *pdata,
- size_t sz)
+static struct platform_device * __init apu_create_pdev(const char *name,
+ const void *data, size_t size,
+ const struct software_node *swnode)
{
+ struct platform_device_info pdev_info = {
+ .name = name,
+ .id = PLATFORM_DEVID_NONE,
+ .data = data,
+ .size_data = size,
+ .fwnode = software_node_fwnode(swnode),
+ };
struct platform_device *pdev;
+ int err;
- pdev = platform_device_register_resndata(NULL,
- name,
- PLATFORM_DEVID_NONE,
- NULL,
- 0,
- pdata,
- sz);
+ pdev = platform_device_register_full(&pdev_info);
- if (IS_ERR(pdev))
- pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev));
+ err = PTR_ERR_OR_ZERO(pdev);
+ if (err)
+ pr_err("failed registering %s: %d\n", name, err);
return pdev;
}
@@ -246,6 +279,7 @@ static struct platform_device * __init apu_create_pdev(
static int __init apu_board_init(void)
{
const struct dmi_system_id *id;
+ int err;
id = dmi_first_match(apu_gpio_dmi_table);
if (!id) {
@@ -253,35 +287,46 @@ static int __init apu_board_init(void)
return -ENODEV;
}
- gpiod_add_lookup_table(&gpios_led_table);
- gpiod_add_lookup_table(&gpios_key_table);
+ err = software_node_register_node_group(apu2_swnodes);
+ if (err) {
+ pr_err("failed to register software nodes: %d\n", err);
+ return err;
+ }
- apu_gpio_pdev = apu_create_pdev(
- AMD_FCH_GPIO_DRIVER_NAME,
- id->driver_data,
- sizeof(struct amd_fch_gpio_pdata));
+ apu_gpio_pdev = apu_create_pdev(AMD_FCH_GPIO_DRIVER_NAME,
+ id->driver_data, sizeof(struct amd_fch_gpio_pdata),
+ &apu2_gpiochip_node);
+ err = PTR_ERR_OR_ZERO(apu_gpio_pdev);
+ if (err)
+ goto err_unregister_swnodes;
- apu_leds_pdev = apu_create_pdev(
- "leds-gpio",
- &apu2_leds_pdata,
- sizeof(apu2_leds_pdata));
+ apu_leds_pdev = apu_create_pdev("leds-gpio", NULL, 0, &apu2_leds_node);
+ err = PTR_ERR_OR_ZERO(apu_leds_pdev);
+ if (err)
+ goto err_unregister_gpio;
- apu_keys_pdev = apu_create_pdev(
- "gpio-keys-polled",
- &apu2_keys_pdata,
- sizeof(apu2_keys_pdata));
+ apu_keys_pdev = apu_create_pdev("gpio-keys-polled", NULL, 0, &apu2_keys_node);
+ err = PTR_ERR_OR_ZERO(apu_keys_pdev);
+ if (err)
+ goto err_unregister_leds;
return 0;
+
+err_unregister_leds:
+ platform_device_unregister(apu_leds_pdev);
+err_unregister_gpio:
+ platform_device_unregister(apu_gpio_pdev);
+err_unregister_swnodes:
+ software_node_unregister_node_group(apu2_swnodes);
+ return err;
}
static void __exit apu_board_exit(void)
{
- gpiod_remove_lookup_table(&gpios_led_table);
- gpiod_remove_lookup_table(&gpios_key_table);
-
platform_device_unregister(apu_keys_pdev);
platform_device_unregister(apu_leds_pdev);
platform_device_unregister(apu_gpio_pdev);
+ software_node_unregister_node_group(apu2_swnodes);
}
module_init(apu_board_init);
diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c
index 0aa7076bc9cc..48c2a0e59d18 100644
--- a/drivers/platform/x86/pmc_atom.c
+++ b/drivers/platform/x86/pmc_atom.c
@@ -428,7 +428,7 @@ static int pmc_setup_clks(struct pci_dev *pdev, void __iomem *pmc_regmap,
struct platform_device *clkdev;
struct pmc_clk_data *clk_data;
- clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
+ clk_data = kzalloc_obj(*clk_data);
if (!clk_data)
return -ENOMEM;
diff --git a/drivers/platform/x86/portwell-ec.c b/drivers/platform/x86/portwell-ec.c
new file mode 100644
index 000000000000..ac506ea40eff
--- /dev/null
+++ b/drivers/platform/x86/portwell-ec.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * portwell-ec.c: Portwell embedded controller driver.
+ *
+ * Tested on:
+ * - Portwell NANO-6064
+ *
+ * This driver supports Portwell boards with an ITE embedded controller (EC).
+ * The EC is accessed through I/O ports and provides:
+ * - Temperature and voltage readings (hwmon)
+ * - 8 GPIO pins for control and monitoring
+ * - Hardware watchdog with 1-15300 second timeout range
+ *
+ * It integrates with the Linux hwmon, GPIO and Watchdog subsystems.
+ *
+ * (C) Copyright 2025 Portwell, Inc.
+ * Author: Yen-Chi Huang (jesse.huang@portwell.com.tw)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/dmi.h>
+#include <linux/gpio/driver.h>
+#include <linux/hwmon.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/sizes.h>
+#include <linux/string.h>
+#include <linux/units.h>
+#include <linux/watchdog.h>
+
+#define PORTWELL_EC_IOSPACE 0xe300
+#define PORTWELL_EC_IOSPACE_LEN SZ_256
+
+#define PORTWELL_GPIO_PINS 8
+#define PORTWELL_GPIO_DIR_REG 0x2b
+#define PORTWELL_GPIO_VAL_REG 0x2c
+
+#define PORTWELL_HWMON_TEMP_NUM 3
+#define PORTWELL_HWMON_VOLT_NUM 5
+
+#define PORTWELL_WDT_EC_CONFIG_ADDR 0x06
+#define PORTWELL_WDT_CONFIG_ENABLE 0x1
+#define PORTWELL_WDT_CONFIG_DISABLE 0x0
+#define PORTWELL_WDT_EC_COUNT_MIN_ADDR 0x07
+#define PORTWELL_WDT_EC_COUNT_SEC_ADDR 0x08
+#define PORTWELL_WDT_EC_MAX_COUNT_SECOND (255 * 60)
+
+#define PORTWELL_EC_FW_VENDOR_ADDRESS 0x4d
+#define PORTWELL_EC_FW_VENDOR_LENGTH 3
+#define PORTWELL_EC_FW_VENDOR_NAME "PWG"
+
+#define PORTWELL_EC_ADC_MAX 1023
+
+static bool force;
+module_param(force, bool, 0444);
+MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname");
+
+/* A sensor's metadata (label, scale, and register) */
+struct pwec_sensor_prop {
+ const char *label;
+ u8 reg;
+ u32 scale;
+};
+
+/* Master configuration with properties for all possible sensors */
+static const struct {
+ const struct pwec_sensor_prop temp_props[PORTWELL_HWMON_TEMP_NUM];
+ const struct pwec_sensor_prop in_props[PORTWELL_HWMON_VOLT_NUM];
+} pwec_master_data = {
+ .temp_props = {
+ { "CPU Temperature", 0x00, 0 },
+ { "System Temperature", 0x02, 0 },
+ { "Aux Temperature", 0x04, 0 },
+ },
+ .in_props = {
+ { "Vcore", 0x20, 3000 },
+ { "3.3V", 0x22, 6000 },
+ { "5V", 0x24, 9600 },
+ { "12V", 0x30, 19800 },
+ { "VDIMM", 0x32, 3000 },
+ },
+};
+
+struct pwec_board_info {
+ u32 temp_mask; /* bit N = temperature channel N */
+ u32 in_mask; /* bit N = voltage channel N */
+};
+
+static const struct pwec_board_info pwec_board_info_default = {
+ .temp_mask = GENMASK(PORTWELL_HWMON_TEMP_NUM - 1, 0),
+ .in_mask = GENMASK(PORTWELL_HWMON_VOLT_NUM - 1, 0),
+};
+
+static const struct pwec_board_info pwec_board_info_nano = {
+ .temp_mask = BIT(0) | BIT(1),
+ .in_mask = GENMASK(4, 0),
+};
+
+static const struct dmi_system_id pwec_dmi_table[] = {
+ {
+ .ident = "NANO-6064 series",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"),
+ },
+ .driver_data = (void *)&pwec_board_info_nano,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(dmi, pwec_dmi_table);
+
+/* Functions for access EC via IOSPACE */
+
+static void pwec_write(u8 index, u8 data)
+{
+ outb(data, PORTWELL_EC_IOSPACE + index);
+}
+
+static u8 pwec_read(u8 address)
+{
+ return inb(PORTWELL_EC_IOSPACE + address);
+}
+
+/* Ensure consistent 16-bit read across potential MSB rollover. */
+static u16 pwec_read16_stable(u8 lsb_reg)
+{
+ u8 lsb, msb, old_msb;
+
+ do {
+ old_msb = pwec_read(lsb_reg + 1);
+ lsb = pwec_read(lsb_reg);
+ msb = pwec_read(lsb_reg + 1);
+ } while (msb != old_msb);
+
+ return (msb << 8) | lsb;
+}
+
+/* GPIO functions */
+
+static int pwec_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ return pwec_read(PORTWELL_GPIO_VAL_REG) & BIT(offset) ? 1 : 0;
+}
+
+static int pwec_gpio_set(struct gpio_chip *chip, unsigned int offset, int val)
+{
+ u8 tmp = pwec_read(PORTWELL_GPIO_VAL_REG);
+
+ if (val)
+ tmp |= BIT(offset);
+ else
+ tmp &= ~BIT(offset);
+ pwec_write(PORTWELL_GPIO_VAL_REG, tmp);
+
+ return 0;
+}
+
+static int pwec_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ u8 direction = pwec_read(PORTWELL_GPIO_DIR_REG) & BIT(offset);
+
+ if (direction)
+ return GPIO_LINE_DIRECTION_IN;
+
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+/*
+ * Changing direction causes issues on some boards,
+ * so direction_input and direction_output are disabled for now.
+ */
+
+static int pwec_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+ return -EOPNOTSUPP;
+}
+
+static int pwec_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value)
+{
+ return -EOPNOTSUPP;
+}
+
+static struct gpio_chip pwec_gpio_chip = {
+ .label = "portwell-ec-gpio",
+ .get_direction = pwec_gpio_get_direction,
+ .direction_input = pwec_gpio_direction_input,
+ .direction_output = pwec_gpio_direction_output,
+ .get = pwec_gpio_get,
+ .set = pwec_gpio_set,
+ .base = -1,
+ .ngpio = PORTWELL_GPIO_PINS,
+};
+
+/* Watchdog functions */
+
+static void pwec_wdt_write_timeout(unsigned int timeout)
+{
+ pwec_write(PORTWELL_WDT_EC_COUNT_MIN_ADDR, timeout / 60);
+ pwec_write(PORTWELL_WDT_EC_COUNT_SEC_ADDR, timeout % 60);
+}
+
+static int pwec_wdt_trigger(struct watchdog_device *wdd)
+{
+ pwec_wdt_write_timeout(wdd->timeout);
+ pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_ENABLE);
+
+ return 0;
+}
+
+static int pwec_wdt_start(struct watchdog_device *wdd)
+{
+ return pwec_wdt_trigger(wdd);
+}
+
+static int pwec_wdt_stop(struct watchdog_device *wdd)
+{
+ pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_DISABLE);
+ return 0;
+}
+
+static int pwec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
+{
+ wdd->timeout = timeout;
+ pwec_wdt_write_timeout(wdd->timeout);
+
+ return 0;
+}
+
+/* Ensure consistent min/sec read in case of second rollover. */
+static unsigned int pwec_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ u8 sec, min, old_min;
+
+ do {
+ old_min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR);
+ sec = pwec_read(PORTWELL_WDT_EC_COUNT_SEC_ADDR);
+ min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR);
+ } while (min != old_min);
+
+ return min * 60 + sec;
+}
+
+static const struct watchdog_ops pwec_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = pwec_wdt_start,
+ .stop = pwec_wdt_stop,
+ .ping = pwec_wdt_trigger,
+ .set_timeout = pwec_wdt_set_timeout,
+ .get_timeleft = pwec_wdt_get_timeleft,
+};
+
+static struct watchdog_device ec_wdt_dev = {
+ .info = &(struct watchdog_info){
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "Portwell EC watchdog",
+ },
+ .ops = &pwec_wdt_ops,
+ .timeout = 60,
+ .min_timeout = 1,
+ .max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND,
+};
+
+/* HWMON functions */
+
+static umode_t pwec_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct pwec_board_info *info = drvdata;
+
+ switch (type) {
+ case hwmon_temp:
+ return (info->temp_mask & BIT(channel)) ? 0444 : 0;
+ case hwmon_in:
+ return (info->in_mask & BIT(channel)) ? 0444 : 0;
+ default:
+ return 0;
+ }
+}
+
+static int pwec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ u16 tmp16;
+
+ switch (type) {
+ case hwmon_temp:
+ *val = pwec_read(pwec_master_data.temp_props[channel].reg) * MILLIDEGREE_PER_DEGREE;
+ return 0;
+ case hwmon_in:
+ tmp16 = pwec_read16_stable(pwec_master_data.in_props[channel].reg);
+ *val = (tmp16 * pwec_master_data.in_props[channel].scale) / PORTWELL_EC_ADC_MAX;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int pwec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = pwec_master_data.temp_props[channel].label;
+ return 0;
+ case hwmon_in:
+ *str = pwec_master_data.in_props[channel].label;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_channel_info *pwec_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops pwec_hwmon_ops = {
+ .is_visible = pwec_hwmon_is_visible,
+ .read = pwec_hwmon_read,
+ .read_string = pwec_hwmon_read_string,
+};
+
+static const struct hwmon_chip_info pwec_chip_info = {
+ .ops = &pwec_hwmon_ops,
+ .info = pwec_hwmon_info,
+};
+
+static int pwec_firmware_vendor_check(void)
+{
+ u8 buf[PORTWELL_EC_FW_VENDOR_LENGTH + 1];
+ u8 i;
+
+ for (i = 0; i < PORTWELL_EC_FW_VENDOR_LENGTH; i++)
+ buf[i] = pwec_read(PORTWELL_EC_FW_VENDOR_ADDRESS + i);
+ buf[PORTWELL_EC_FW_VENDOR_LENGTH] = '\0';
+
+ return !strcmp(PORTWELL_EC_FW_VENDOR_NAME, buf) ? 0 : -ENODEV;
+}
+
+static int pwec_probe(struct platform_device *pdev)
+{
+ struct device *hwmon_dev;
+ void *drvdata = dev_get_platdata(&pdev->dev);
+ int ret;
+
+ if (!devm_request_region(&pdev->dev, PORTWELL_EC_IOSPACE,
+ PORTWELL_EC_IOSPACE_LEN, dev_name(&pdev->dev))) {
+ dev_err(&pdev->dev, "failed to get IO region\n");
+ return -EBUSY;
+ }
+
+ ret = pwec_firmware_vendor_check();
+ if (ret < 0)
+ return ret;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &pwec_gpio_chip, NULL);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register Portwell EC GPIO\n");
+ return ret;
+ }
+
+ if (IS_REACHABLE(CONFIG_HWMON)) {
+ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "portwell_ec", drvdata, &pwec_chip_info, NULL);
+ ret = PTR_ERR_OR_ZERO(hwmon_dev);
+ if (ret)
+ return ret;
+ }
+
+ ec_wdt_dev.parent = &pdev->dev;
+ return devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev);
+}
+
+static int pwec_suspend(struct device *dev)
+{
+ if (watchdog_active(&ec_wdt_dev))
+ return pwec_wdt_stop(&ec_wdt_dev);
+
+ return 0;
+}
+
+static int pwec_resume(struct device *dev)
+{
+ if (watchdog_active(&ec_wdt_dev))
+ return pwec_wdt_start(&ec_wdt_dev);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(pwec_dev_pm_ops, pwec_suspend, pwec_resume);
+
+static struct platform_driver pwec_driver = {
+ .driver = {
+ .name = "portwell-ec",
+ .pm = pm_sleep_ptr(&pwec_dev_pm_ops),
+ },
+ .probe = pwec_probe,
+};
+
+static struct platform_device *pwec_dev;
+
+static int __init pwec_init(void)
+{
+ const struct dmi_system_id *match;
+ const struct pwec_board_info *hwmon_data;
+ int ret;
+
+ match = dmi_first_match(pwec_dmi_table);
+ if (!match) {
+ if (!force)
+ return -ENODEV;
+ hwmon_data = &pwec_board_info_default;
+ pr_warn("force load portwell-ec without DMI check, using full display config\n");
+ } else {
+ hwmon_data = match->driver_data;
+ }
+
+ ret = platform_driver_register(&pwec_driver);
+ if (ret)
+ return ret;
+
+ pwec_dev = platform_device_register_data(NULL, "portwell-ec", PLATFORM_DEVID_NONE,
+ hwmon_data, sizeof(*hwmon_data));
+ if (IS_ERR(pwec_dev)) {
+ platform_driver_unregister(&pwec_driver);
+ return PTR_ERR(pwec_dev);
+ }
+
+ return 0;
+}
+
+static void __exit pwec_exit(void)
+{
+ platform_device_unregister(pwec_dev);
+ platform_driver_unregister(&pwec_driver);
+}
+
+module_init(pwec_init);
+module_exit(pwec_exit);
+
+MODULE_AUTHOR("Yen-Chi Huang <jesse.huang@portwell.com.tw>");
+MODULE_DESCRIPTION("Portwell EC Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/quickstart.c b/drivers/platform/x86/quickstart.c
index c332c7cdaff5..acb58518be37 100644
--- a/drivers/platform/x86/quickstart.c
+++ b/drivers/platform/x86/quickstart.c
@@ -154,13 +154,6 @@ static void quickstart_notify_remove(void *context)
acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY, quickstart_notify);
}
-static void quickstart_mutex_destroy(void *data)
-{
- struct mutex *lock = data;
-
- mutex_destroy(lock);
-}
-
static int quickstart_probe(struct platform_device *pdev)
{
struct quickstart_data *data;
@@ -179,8 +172,7 @@ static int quickstart_probe(struct platform_device *pdev)
data->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, data);
- mutex_init(&data->input_lock);
- ret = devm_add_action_or_reset(&pdev->dev, quickstart_mutex_destroy, &data->input_lock);
+ ret = devm_mutex_init(&pdev->dev, &data->input_lock);
if (ret < 0)
return ret;
diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c
new file mode 100644
index 000000000000..58898630eda6
--- /dev/null
+++ b/drivers/platform/x86/redmi-wmi.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/* WMI driver for Xiaomi Redmibooks */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/unaligned.h>
+#include <linux/wmi.h>
+
+#include <uapi/linux/input-event-codes.h>
+
+#define WMI_REDMIBOOK_KEYBOARD_EVENT_GUID "46C93E13-EE9B-4262-8488-563BCA757FEF"
+
+#define AI_KEY_VALUE_MASK BIT(8)
+
+static const struct key_entry redmi_wmi_keymap[] = {
+ {KE_KEY, 0x00000201, {KEY_SELECTIVE_SCREENSHOT}},
+ {KE_KEY, 0x00000301, {KEY_ALL_APPLICATIONS}},
+ {KE_KEY, 0x00001b01, {KEY_CONFIG}},
+ {KE_KEY, 0x00011b01, {KEY_CONFIG}},
+ {KE_KEY, 0x00010101, {KEY_SWITCHVIDEOMODE}},
+ {KE_KEY, 0x00001a01, {KEY_REFRESH_RATE_TOGGLE}},
+
+ /* AI button has code for each position */
+ {KE_KEY, 0x00011801, {KEY_ASSISTANT}},
+ {KE_KEY, 0x00011901, {KEY_ASSISTANT}},
+
+ /* Keyboard backlight */
+ {KE_IGNORE, 0x00000501, {}},
+ {KE_IGNORE, 0x00800501, {}},
+ {KE_IGNORE, 0x00050501, {}},
+ {KE_IGNORE, 0x000a0501, {}},
+
+ /* Xiaomi G Command Center */
+ {KE_KEY, 0x00010a01, {KEY_VENDOR}},
+
+ /* OEM preset power mode */
+ {KE_IGNORE, 0x00011601, {}},
+ {KE_IGNORE, 0x00021601, {}},
+ {KE_IGNORE, 0x00031601, {}},
+ {KE_IGNORE, 0x00041601, {}},
+
+ /* Fn Lock state */
+ {KE_IGNORE, 0x00000701, {}},
+ {KE_IGNORE, 0x00010701, {}},
+
+ /* Fn+`/1/2/3/4 */
+ {KE_KEY, 0x00011101, {KEY_F13}},
+ {KE_KEY, 0x00011201, {KEY_F14}},
+ {KE_KEY, 0x00011301, {KEY_F15}},
+ {KE_KEY, 0x00011401, {KEY_F16}},
+ {KE_KEY, 0x00011501, {KEY_F17}},
+
+ {KE_END}
+};
+
+struct redmi_wmi {
+ struct input_dev *input_dev;
+ /* Protects the key event sequence */
+ struct mutex key_lock;
+};
+
+static int redmi_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+ struct redmi_wmi *data;
+ int err;
+
+ /* Init dev */
+ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ dev_set_drvdata(&wdev->dev, data);
+
+ err = devm_mutex_init(&wdev->dev, &data->key_lock);
+ if (err)
+ return err;
+
+ data->input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!data->input_dev)
+ return -ENOMEM;
+
+ data->input_dev->name = "Redmibook WMI keys";
+ data->input_dev->phys = "wmi/input0";
+
+ err = sparse_keymap_setup(data->input_dev, redmi_wmi_keymap, NULL);
+ if (err)
+ return err;
+
+ return input_register_device(data->input_dev);
+}
+
+static void redmi_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
+{
+ struct key_entry *entry;
+ struct redmi_wmi *data = dev_get_drvdata(&wdev->dev);
+ bool autorelease = true;
+ u32 payload;
+ int value = 1;
+
+ if (obj->type != ACPI_TYPE_BUFFER) {
+ dev_err(&wdev->dev, "Bad response type %u\n", obj->type);
+ return;
+ }
+
+ if (obj->buffer.length < 32) {
+ dev_err(&wdev->dev, "Invalid buffer length %u\n", obj->buffer.length);
+ return;
+ }
+
+ payload = get_unaligned_le32(obj->buffer.pointer);
+ entry = sparse_keymap_entry_from_scancode(data->input_dev, payload);
+
+ if (!entry) {
+ dev_dbg(&wdev->dev, "Unknown WMI event with payload %u", payload);
+ return;
+ }
+
+ /* AI key quirk */
+ if (entry->keycode == KEY_ASSISTANT) {
+ value = !(payload & AI_KEY_VALUE_MASK);
+ autorelease = false;
+ }
+
+ guard(mutex)(&data->key_lock);
+ sparse_keymap_report_entry(data->input_dev, entry, value, autorelease);
+}
+
+static const struct wmi_device_id redmi_wmi_id_table[] = {
+ { WMI_REDMIBOOK_KEYBOARD_EVENT_GUID, NULL },
+ { }
+};
+
+static struct wmi_driver redmi_wmi_driver = {
+ .driver = {
+ .name = "redmi-wmi",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = redmi_wmi_id_table,
+ .min_event_size = 32,
+ .probe = redmi_wmi_probe,
+ .notify = redmi_wmi_notify,
+ .no_singleton = true,
+};
+module_wmi_driver(redmi_wmi_driver);
+
+MODULE_DEVICE_TABLE(wmi, redmi_wmi_id_table);
+MODULE_AUTHOR("Gladyshev Ilya <foxido@foxido.dev>");
+MODULE_DESCRIPTION("Redmibook WMI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c
index 5878a351993e..6382af0b106c 100644
--- a/drivers/platform/x86/samsung-galaxybook.c
+++ b/drivers/platform/x86/samsung-galaxybook.c
@@ -53,7 +53,7 @@ struct samsung_galaxybook {
void *i8042_filter_ptr;
struct work_struct block_recording_hotkey_work;
- struct input_dev *camera_lens_cover_switch;
+ struct input_dev *input;
struct acpi_battery_hook battery_hook;
@@ -197,6 +197,9 @@ static const guid_t performance_mode_guid =
#define GB_ACPI_NOTIFY_DEVICE_ON_TABLE 0x6c
#define GB_ACPI_NOTIFY_DEVICE_OFF_TABLE 0x6d
#define GB_ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE 0x70
+#define GB_ACPI_NOTIFY_HOTKEY_KBD_BACKLIGHT 0x7d
+#define GB_ACPI_NOTIFY_HOTKEY_MICMUTE 0x6e
+#define GB_ACPI_NOTIFY_HOTKEY_CAMERA 0x6f
#define GB_KEY_KBD_BACKLIGHT_KEYDOWN 0x2c
#define GB_KEY_KBD_BACKLIGHT_KEYUP 0xac
@@ -442,12 +445,13 @@ static int galaxybook_battery_ext_property_get(struct power_supply *psy,
union power_supply_propval *val)
{
struct samsung_galaxybook *galaxybook = ext_data;
+ u8 value;
int err;
if (psp != POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD)
return -EINVAL;
- err = charge_control_end_threshold_acpi_get(galaxybook, (u8 *)&val->intval);
+ err = charge_control_end_threshold_acpi_get(galaxybook, &value);
if (err)
return err;
@@ -455,8 +459,10 @@ static int galaxybook_battery_ext_property_get(struct power_supply *psy,
* device stores "no end threshold" as 0 instead of 100;
* if device has 0, report 100
*/
- if (val->intval == 0)
- val->intval = 100;
+ if (value == 0)
+ value = 100;
+
+ val->intval = value;
return 0;
}
@@ -856,13 +862,29 @@ static int block_recording_acpi_set(struct samsung_galaxybook *galaxybook, const
if (err)
return err;
- input_report_switch(galaxybook->camera_lens_cover_switch,
+ input_report_switch(galaxybook->input,
SW_CAMERA_LENS_COVER, value ? 1 : 0);
- input_sync(galaxybook->camera_lens_cover_switch);
+ input_sync(galaxybook->input);
return 0;
}
+static int galaxybook_input_init(struct samsung_galaxybook *galaxybook)
+{
+ galaxybook->input = devm_input_allocate_device(&galaxybook->platform->dev);
+ if (!galaxybook->input)
+ return -ENOMEM;
+
+ galaxybook->input->name = "Samsung Galaxy Book Camera Lens Cover";
+ galaxybook->input->phys = DRIVER_NAME "/input0";
+ galaxybook->input->id.bustype = BUS_HOST;
+
+ input_set_capability(galaxybook->input, EV_KEY, KEY_MICMUTE);
+ input_set_capability(galaxybook->input, EV_SW, SW_CAMERA_LENS_COVER);
+
+ return input_register_device(galaxybook->input);
+}
+
static int galaxybook_block_recording_init(struct samsung_galaxybook *galaxybook)
{
bool value;
@@ -884,24 +906,8 @@ static int galaxybook_block_recording_init(struct samsung_galaxybook *galaxybook
return GB_NOT_SUPPORTED;
}
- galaxybook->camera_lens_cover_switch =
- devm_input_allocate_device(&galaxybook->platform->dev);
- if (!galaxybook->camera_lens_cover_switch)
- return -ENOMEM;
-
- galaxybook->camera_lens_cover_switch->name = "Samsung Galaxy Book Camera Lens Cover";
- galaxybook->camera_lens_cover_switch->phys = DRIVER_NAME "/input0";
- galaxybook->camera_lens_cover_switch->id.bustype = BUS_HOST;
-
- input_set_capability(galaxybook->camera_lens_cover_switch, EV_SW, SW_CAMERA_LENS_COVER);
-
- err = input_register_device(galaxybook->camera_lens_cover_switch);
- if (err)
- return err;
-
- input_report_switch(galaxybook->camera_lens_cover_switch,
- SW_CAMERA_LENS_COVER, value ? 1 : 0);
- input_sync(galaxybook->camera_lens_cover_switch);
+ input_report_switch(galaxybook->input, SW_CAMERA_LENS_COVER, value ? 1 : 0);
+ input_sync(galaxybook->input);
return 0;
}
@@ -1257,6 +1263,25 @@ static void galaxybook_acpi_notify(acpi_handle handle, u32 event, void *data)
if (galaxybook->has_performance_mode)
platform_profile_cycle();
break;
+ case GB_ACPI_NOTIFY_HOTKEY_KBD_BACKLIGHT:
+ if (galaxybook->has_kbd_backlight)
+ schedule_work(&galaxybook->kbd_backlight_hotkey_work);
+ break;
+ case GB_ACPI_NOTIFY_HOTKEY_MICMUTE:
+ input_report_key(galaxybook->input, KEY_MICMUTE, 1);
+ input_sync(galaxybook->input);
+ input_report_key(galaxybook->input, KEY_MICMUTE, 0);
+ input_sync(galaxybook->input);
+ break;
+ case GB_ACPI_NOTIFY_HOTKEY_CAMERA:
+ if (galaxybook->has_block_recording) {
+ schedule_work(&galaxybook->block_recording_hotkey_work);
+ } else {
+ input_report_switch(galaxybook->input, SW_CAMERA_LENS_COVER,
+ !test_bit(SW_CAMERA_LENS_COVER, galaxybook->input->sw));
+ input_sync(galaxybook->input);
+ }
+ break;
default:
dev_warn(&galaxybook->platform->dev,
"unknown ACPI notification event: 0x%x\n", event);
@@ -1389,6 +1414,11 @@ static int galaxybook_probe(struct platform_device *pdev)
return dev_err_probe(&galaxybook->platform->dev, err,
"failed to initialize kbd_backlight\n");
+ err = galaxybook_input_init(galaxybook);
+ if (err)
+ return dev_err_probe(&galaxybook->platform->dev, err,
+ "failed to initialize input device\n");
+
err = galaxybook_fw_attrs_init(galaxybook);
if (err)
return dev_err_probe(&galaxybook->platform->dev, err,
@@ -1403,6 +1433,7 @@ static int galaxybook_probe(struct platform_device *pdev)
}
static const struct acpi_device_id galaxybook_device_ids[] = {
+ { "SAM0426" },
{ "SAM0427" },
{ "SAM0428" },
{ "SAM0429" },
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c
index decde4c9a3d9..710f3d5bf84c 100644
--- a/drivers/platform/x86/samsung-laptop.c
+++ b/drivers/platform/x86/samsung-laptop.c
@@ -16,6 +16,7 @@
#include <linux/leds.h>
#include <linux/dmi.h>
#include <linux/platform_device.h>
+#include <linux/power_supply.h>
#include <linux/rfkill.h>
#include <linux/acpi.h>
#include <linux/seq_file.h>
@@ -23,6 +24,7 @@
#include <linux/ctype.h>
#include <linux/efi.h>
#include <linux/suspend.h>
+#include <acpi/battery.h>
#include <acpi/video.h>
/*
@@ -348,6 +350,8 @@ struct samsung_laptop {
struct notifier_block pm_nb;
+ struct acpi_battery_hook battery_hook;
+
bool handle_backlight;
bool has_stepping_quirk;
@@ -697,6 +701,11 @@ static ssize_t set_performance_level(struct device *dev,
static DEVICE_ATTR(performance_level, 0644,
get_performance_level, set_performance_level);
+static void show_battery_life_extender_deprecation_warning(struct device *dev)
+{
+ dev_warn_once(dev, "battery_life_extender attribute has been deprecated, see charge_types.\n");
+}
+
static int read_battery_life_extender(struct samsung_laptop *samsung)
{
const struct sabi_commands *commands = &samsung->config->commands;
@@ -739,6 +748,8 @@ static ssize_t get_battery_life_extender(struct device *dev,
struct samsung_laptop *samsung = dev_get_drvdata(dev);
int ret;
+ show_battery_life_extender_deprecation_warning(dev);
+
ret = read_battery_life_extender(samsung);
if (ret < 0)
return ret;
@@ -753,6 +764,8 @@ static ssize_t set_battery_life_extender(struct device *dev,
struct samsung_laptop *samsung = dev_get_drvdata(dev);
int ret, value;
+ show_battery_life_extender_deprecation_warning(dev);
+
if (!count || kstrtoint(buf, 0, &value) != 0)
return -EINVAL;
@@ -766,6 +779,84 @@ static ssize_t set_battery_life_extender(struct device *dev,
static DEVICE_ATTR(battery_life_extender, 0644,
get_battery_life_extender, set_battery_life_extender);
+static int samsung_psy_ext_set_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct samsung_laptop *samsung = ext_data;
+
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
+ return write_battery_life_extender(samsung, 1);
+ case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
+ return write_battery_life_extender(samsung, 0);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int samsung_psy_ext_get_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct samsung_laptop *samsung = ext_data;
+ int ret;
+
+ ret = read_battery_life_extender(samsung);
+ if (ret < 0)
+ return ret;
+
+ if (ret == 1)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+
+ return 0;
+}
+
+static int samsung_psy_prop_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp)
+{
+ return true;
+}
+
+static const enum power_supply_property samsung_power_supply_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPES,
+};
+
+static const struct power_supply_ext samsung_battery_ext = {
+ .name = "samsung_laptop",
+ .properties = samsung_power_supply_props,
+ .num_properties = ARRAY_SIZE(samsung_power_supply_props),
+ .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
+ .get_property = samsung_psy_ext_get_prop,
+ .set_property = samsung_psy_ext_set_prop,
+ .property_is_writeable = samsung_psy_prop_is_writeable,
+};
+
+static int samsung_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ struct samsung_laptop *samsung = container_of(hook, struct samsung_laptop, battery_hook);
+
+ return power_supply_register_extension(battery, &samsung_battery_ext,
+ &samsung->platform_device->dev, samsung);
+}
+
+static int samsung_battery_remove(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &samsung_battery_ext);
+
+ return 0;
+}
+
static int read_usb_charge(struct samsung_laptop *samsung)
{
const struct sabi_commands *commands = &samsung->config->commands;
@@ -1043,6 +1134,21 @@ static int __init samsung_lid_handling_init(struct samsung_laptop *samsung)
return retval;
}
+static int __init samsung_battery_hook_init(struct samsung_laptop *samsung)
+{
+ int retval = 0;
+
+ if (samsung->config->commands.get_battery_life_extender != 0xFFFF) {
+ samsung->battery_hook.add_battery = samsung_battery_add;
+ samsung->battery_hook.remove_battery = samsung_battery_remove;
+ samsung->battery_hook.name = "Samsung Battery Extension";
+ retval = devm_battery_hook_register(&samsung->platform_device->dev,
+ &samsung->battery_hook);
+ }
+
+ return retval;
+}
+
static int kbd_backlight_enable(struct samsung_laptop *samsung)
{
const struct sabi_commands *commands = &samsung->config->commands;
@@ -1565,7 +1671,7 @@ static int __init samsung_init(void)
if (!force && !dmi_check_system(samsung_dmi_table))
return -ENODEV;
- samsung = kzalloc(sizeof(*samsung), GFP_KERNEL);
+ samsung = kzalloc_obj(*samsung);
if (!samsung)
return -ENOMEM;
@@ -1604,6 +1710,10 @@ static int __init samsung_init(void)
if (ret)
goto error_lid_handling;
+ ret = samsung_battery_hook_init(samsung);
+ if (ret)
+ goto error_lid_handling;
+
samsung_debugfs_init(samsung);
samsung->pm_nb.notifier_call = samsung_pm_notification;
diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c
index db030b0f176a..1a369334f9cb 100644
--- a/drivers/platform/x86/serial-multi-instantiate.c
+++ b/drivers/platform/x86/serial-multi-instantiate.c
@@ -22,6 +22,7 @@
#define IRQ_RESOURCE_GPIO 1
#define IRQ_RESOURCE_APIC 2
#define IRQ_RESOURCE_AUTO 3
+#define IRQ_RESOURCE_OPT BIT(2)
enum smi_bus_type {
SMI_I2C,
@@ -64,6 +65,10 @@ static int smi_get_irq(struct platform_device *pdev, struct acpi_device *adev,
dev_dbg(&pdev->dev, "Using platform irq\n");
break;
}
+ if (inst->flags & IRQ_RESOURCE_OPT) {
+ dev_dbg(&pdev->dev, "No irq\n");
+ return 0;
+ }
break;
case IRQ_RESOURCE_GPIO:
ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx);
@@ -386,10 +391,10 @@ static const struct smi_node cs35l57_hda = {
static const struct smi_node tas2781_hda = {
.instances = {
- { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 },
- { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 },
- { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 },
- { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 },
+ { "tas2781-hda", IRQ_RESOURCE_AUTO | IRQ_RESOURCE_OPT, 0 },
+ { "tas2781-hda", IRQ_RESOURCE_AUTO | IRQ_RESOURCE_OPT, 0 },
+ { "tas2781-hda", IRQ_RESOURCE_AUTO | IRQ_RESOURCE_OPT, 0 },
+ { "tas2781-hda", IRQ_RESOURCE_AUTO | IRQ_RESOURCE_OPT, 0 },
{}
},
.bus_type = SMI_AUTO_DETECT,
diff --git a/drivers/platform/x86/silicom-platform.c b/drivers/platform/x86/silicom-platform.c
index c0910af16a3a..266f7bc5e416 100644
--- a/drivers/platform/x86/silicom-platform.c
+++ b/drivers/platform/x86/silicom-platform.c
@@ -245,18 +245,15 @@ static int silicom_gpio_direction_input(struct gpio_chip *gc,
return direction == GPIO_LINE_DIRECTION_IN ? 0 : -EINVAL;
}
-static void silicom_gpio_set(struct gpio_chip *gc,
- unsigned int offset,
- int value)
+static int silicom_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
{
- int direction = silicom_gpio_get_direction(gc, offset);
u8 *channels = gpiochip_get_data(gc);
int channel = channels[offset];
- if (direction == GPIO_LINE_DIRECTION_IN)
- return;
-
silicom_mec_port_set(channel, !value);
+
+ return 0;
}
static int silicom_gpio_direction_output(struct gpio_chip *gc,
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
index 3197aaa69da7..67370967df6f 100644
--- a/drivers/platform/x86/sony-laptop.c
+++ b/drivers/platform/x86/sony-laptop.c
@@ -48,7 +48,6 @@
#include <linux/acpi.h>
#include <linux/slab.h>
#include <linux/sonypi.h>
-#include <linux/sony-laptop.h>
#include <linux/rfkill.h>
#ifdef CONFIG_SONYPI_COMPAT
#include <linux/poll.h>
@@ -179,8 +178,7 @@ enum sony_nc_rfkill {
static int sony_rfkill_handle;
static struct rfkill *sony_rfkill_devices[N_SONY_RFKILL];
static int sony_rfkill_address[N_SONY_RFKILL] = {0x300, 0x500, 0x700, 0x900};
-static int sony_nc_rfkill_setup(struct acpi_device *device,
- unsigned int handle);
+static int sony_nc_rfkill_setup(struct device *dev, unsigned int handle);
static void sony_nc_rfkill_cleanup(void);
static void sony_nc_rfkill_update(void);
@@ -436,7 +434,7 @@ static void sony_laptop_report_input_event(u8 event)
dprintk("unknown input event %.2x\n", event);
}
-static int sony_laptop_setup_input(struct acpi_device *acpi_device)
+static int sony_laptop_setup_input(struct device *parent)
{
struct input_dev *jog_dev;
struct input_dev *key_dev;
@@ -469,7 +467,7 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device)
key_dev->name = "Sony Vaio Keys";
key_dev->id.bustype = BUS_ISA;
key_dev->id.vendor = PCI_VENDOR_ID_SONY;
- key_dev->dev.parent = &acpi_device->dev;
+ key_dev->dev.parent = parent;
/* Initialize the Input Drivers: special keys */
input_set_capability(key_dev, EV_MSC, MSC_SCAN);
@@ -498,7 +496,7 @@ static int sony_laptop_setup_input(struct acpi_device *acpi_device)
jog_dev->name = "Sony Vaio Jogdial";
jog_dev->id.bustype = BUS_ISA;
jog_dev->id.vendor = PCI_VENDOR_ID_SONY;
- jog_dev->dev.parent = &acpi_device->dev;
+ jog_dev->dev.parent = parent;
input_set_capability(jog_dev, EV_KEY, BTN_MIDDLE);
input_set_capability(jog_dev, EV_REL, REL_WHEEL);
@@ -538,7 +536,7 @@ static void sony_laptop_remove_input(void)
if (!atomic_dec_and_test(&sony_laptop_input.users))
return;
- del_timer_sync(&sony_laptop_input.release_key_timer);
+ timer_delete_sync(&sony_laptop_input.release_key_timer);
/*
* Generate key-up events for remaining keys. Note that we don't
@@ -830,7 +828,7 @@ static int sony_nc_handles_setup(struct platform_device *pd)
{
int i, r, result, arg;
- handles = kzalloc(sizeof(*handles), GFP_KERNEL);
+ handles = kzalloc_obj(*handles);
if (!handles)
return -ENOMEM;
@@ -1177,7 +1175,7 @@ enum event_types {
KILLSWITCH,
GFX_SWITCH
};
-static void sony_nc_notify(struct acpi_device *device, u32 event)
+static void sony_nc_notify(acpi_handle ah, u32 event, void *data)
{
u32 real_ev = event;
u8 ev_type = 0;
@@ -1288,7 +1286,7 @@ static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
/*
* ACPI device
*/
-static void sony_nc_function_setup(struct acpi_device *device,
+static void sony_nc_function_setup(struct device *dev,
struct platform_device *pf_device)
{
unsigned int i, result, bitmask, arg;
@@ -1361,7 +1359,7 @@ static void sony_nc_function_setup(struct acpi_device *device,
break;
case 0x0124:
case 0x0135:
- result = sony_nc_rfkill_setup(device, handle);
+ result = sony_nc_rfkill_setup(dev, handle);
if (result)
pr_err("couldn't set up rfkill support (%d)\n",
result);
@@ -1601,8 +1599,7 @@ static const struct rfkill_ops sony_rfkill_ops = {
.set_block = sony_nc_rfkill_set,
};
-static int sony_nc_setup_rfkill(struct acpi_device *device,
- enum sony_nc_rfkill nc_type)
+static int sony_nc_setup_rfkill(struct device *parent, enum sony_nc_rfkill nc_type)
{
int err;
struct rfkill *rfk;
@@ -1632,8 +1629,7 @@ static int sony_nc_setup_rfkill(struct acpi_device *device,
return -EINVAL;
}
- rfk = rfkill_alloc(name, &device->dev, type,
- &sony_rfkill_ops, (void *)nc_type);
+ rfk = rfkill_alloc(name, parent, type, &sony_rfkill_ops, (void *)nc_type);
if (!rfk)
return -ENOMEM;
@@ -1693,8 +1689,7 @@ static void sony_nc_rfkill_update(void)
}
}
-static int sony_nc_rfkill_setup(struct acpi_device *device,
- unsigned int handle)
+static int sony_nc_rfkill_setup(struct device *parent, unsigned int handle)
{
u64 offset;
int i;
@@ -1735,18 +1730,18 @@ static int sony_nc_rfkill_setup(struct acpi_device *device,
dprintk("Radio devices, found 0x%.2x\n", buffer[i]);
if (buffer[i] == 0 && !sony_rfkill_devices[SONY_WIFI])
- sony_nc_setup_rfkill(device, SONY_WIFI);
+ sony_nc_setup_rfkill(parent, SONY_WIFI);
if (buffer[i] == 0x10 && !sony_rfkill_devices[SONY_BLUETOOTH])
- sony_nc_setup_rfkill(device, SONY_BLUETOOTH);
+ sony_nc_setup_rfkill(parent, SONY_BLUETOOTH);
if (((0xf0 & buffer[i]) == 0x20 ||
(0xf0 & buffer[i]) == 0x50) &&
!sony_rfkill_devices[SONY_WWAN])
- sony_nc_setup_rfkill(device, SONY_WWAN);
+ sony_nc_setup_rfkill(parent, SONY_WWAN);
if (buffer[i] == 0x30 && !sony_rfkill_devices[SONY_WIMAX])
- sony_nc_setup_rfkill(device, SONY_WIMAX);
+ sony_nc_setup_rfkill(parent, SONY_WIMAX);
}
return 0;
}
@@ -1903,7 +1898,7 @@ static int sony_nc_kbd_backlight_setup(struct platform_device *pd,
}
}
- kbdbl_ctl = kzalloc(sizeof(*kbdbl_ctl), GFP_KERNEL);
+ kbdbl_ctl = kzalloc_obj(*kbdbl_ctl);
if (!kbdbl_ctl)
return -ENOMEM;
@@ -2071,7 +2066,7 @@ static int sony_nc_battery_care_setup(struct platform_device *pd,
{
int ret = 0;
- bcare_ctl = kzalloc(sizeof(struct battery_care_control), GFP_KERNEL);
+ bcare_ctl = kzalloc_obj(struct battery_care_control);
if (!bcare_ctl)
return -ENOMEM;
@@ -2223,7 +2218,7 @@ static ssize_t sony_nc_thermal_mode_show(struct device *dev,
static int sony_nc_thermal_setup(struct platform_device *pd)
{
int ret = 0;
- th_handle = kzalloc(sizeof(struct snc_thermal_ctrl), GFP_KERNEL);
+ th_handle = kzalloc_obj(struct snc_thermal_ctrl);
if (!th_handle)
return -ENOMEM;
@@ -2371,7 +2366,7 @@ static int sony_nc_lid_resume_setup(struct platform_device *pd,
if (sony_call_snc_handle(handle, 0x0000, &result))
return -EIO;
- lid_ctl = kzalloc(sizeof(struct snc_lid_resume_control), GFP_KERNEL);
+ lid_ctl = kzalloc_obj(struct snc_lid_resume_control);
if (!lid_ctl)
return -ENOMEM;
@@ -2498,7 +2493,7 @@ static int sony_nc_gfx_switch_setup(struct platform_device *pd,
{
unsigned int result;
- gfxs_ctl = kzalloc(sizeof(struct snc_gfx_switch_control), GFP_KERNEL);
+ gfxs_ctl = kzalloc_obj(struct snc_gfx_switch_control);
if (!gfxs_ctl)
return -ENOMEM;
@@ -2577,7 +2572,7 @@ static int sony_nc_highspeed_charging_setup(struct platform_device *pd)
return 0;
}
- hsc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+ hsc_handle = kzalloc_obj(struct device_attribute);
if (!hsc_handle)
return -ENOMEM;
@@ -2643,7 +2638,7 @@ static int sony_nc_lowbatt_setup(struct platform_device *pd)
{
unsigned int result;
- lowbatt_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+ lowbatt_handle = kzalloc_obj(struct device_attribute);
if (!lowbatt_handle)
return -ENOMEM;
@@ -2720,11 +2715,11 @@ static int sony_nc_fanspeed_setup(struct platform_device *pd)
{
unsigned int result;
- fan_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+ fan_handle = kzalloc_obj(struct device_attribute);
if (!fan_handle)
return -ENOMEM;
- hsf_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+ hsf_handle = kzalloc_obj(struct device_attribute);
if (!hsf_handle) {
result = -ENOMEM;
goto out_hsf_handle_alloc;
@@ -2824,7 +2819,7 @@ static int sony_nc_usb_charge_setup(struct platform_device *pd)
return 0;
}
- uc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+ uc_handle = kzalloc_obj(struct device_attribute);
if (!uc_handle)
return -ENOMEM;
@@ -2871,7 +2866,7 @@ static int sony_nc_panelid_setup(struct platform_device *pd)
{
unsigned int result;
- panel_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+ panel_handle = kzalloc_obj(struct device_attribute);
if (!panel_handle)
return -ENOMEM;
@@ -2926,7 +2921,7 @@ static int sony_nc_smart_conn_setup(struct platform_device *pd)
{
unsigned int result;
- sc_handle = kzalloc(sizeof(struct device_attribute), GFP_KERNEL);
+ sc_handle = kzalloc_obj(struct device_attribute);
if (!sc_handle)
return -ENOMEM;
@@ -3000,7 +2995,7 @@ static int sony_nc_touchpad_setup(struct platform_device *pd,
{
int ret = 0;
- tp_ctl = kzalloc(sizeof(struct touchpad_control), GFP_KERNEL);
+ tp_ctl = kzalloc_obj(struct touchpad_control);
if (!tp_ctl)
return -ENOMEM;
@@ -3150,14 +3145,19 @@ static void sony_nc_backlight_cleanup(void)
backlight_device_unregister(sony_bl_props.dev);
}
-static int sony_nc_add(struct acpi_device *device)
+static int sony_nc_probe(struct platform_device *pdev)
{
+ struct acpi_device *device;
acpi_status status;
int result = 0;
struct sony_nc_value *item;
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
sony_nc_acpi_device = device;
- strcpy(acpi_device_class(device), "sony/hotkey");
+ strscpy(acpi_device_class(device), "sony/hotkey");
sony_nc_acpi_handle = device->handle;
@@ -3185,7 +3185,7 @@ static int sony_nc_add(struct acpi_device *device)
}
}
- result = sony_laptop_setup_input(device);
+ result = sony_laptop_setup_input(&pdev->dev);
if (result) {
pr_err("Unable to create input devices\n");
goto outplatform;
@@ -3202,7 +3202,7 @@ static int sony_nc_add(struct acpi_device *device)
/* retrieve the available handles */
result = sony_nc_handles_setup(sony_pf_device);
if (!result)
- sony_nc_function_setup(device, sony_pf_device);
+ sony_nc_function_setup(&pdev->dev, sony_pf_device);
}
if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
@@ -3245,6 +3245,11 @@ static int sony_nc_add(struct acpi_device *device)
}
}
+ result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ sony_nc_notify, NULL);
+ if (result)
+ goto out_sysfs;
+
pr_info("SNC setup done.\n");
return 0;
@@ -3267,10 +3272,13 @@ outwalk:
return result;
}
-static void sony_nc_remove(struct acpi_device *device)
+static void sony_nc_remove(struct platform_device *pdev)
{
struct sony_nc_value *item;
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
+ ACPI_DEVICE_NOTIFY, sony_nc_notify);
+
sony_nc_backlight_cleanup();
sony_nc_acpi_device = NULL;
@@ -3298,16 +3306,14 @@ static const struct acpi_device_id sony_nc_device_ids[] = {
{"", 0},
};
-static struct acpi_driver sony_nc_driver = {
- .name = SONY_NC_DRIVER_NAME,
- .class = SONY_NC_CLASS,
- .ids = sony_nc_device_ids,
- .ops = {
- .add = sony_nc_add,
- .remove = sony_nc_remove,
- .notify = sony_nc_notify,
- },
- .drv.pm = &sony_nc_pm,
+static struct platform_driver sony_nc_driver = {
+ .probe = sony_nc_probe,
+ .remove = sony_nc_remove,
+ .driver = {
+ .name = SONY_NC_DRIVER_NAME,
+ .acpi_match_table = sony_nc_device_ids,
+ .pm = &sony_nc_pm,
+ },
};
/*********** SPIC (SNY6001) Device ***********/
@@ -3327,8 +3333,10 @@ struct sony_pic_ioport {
};
struct sony_pic_irq {
- struct acpi_resource_irq irq;
struct list_head list;
+
+ /* Must be last --ends in a flexible-array member. */
+ struct acpi_resource_irq irq;
};
struct sonypi_eventtypes {
@@ -3619,22 +3627,6 @@ static u8 sony_pic_call2(u8 dev, u8 fn)
return v1;
}
-static u8 sony_pic_call3(u8 dev, u8 fn, u8 v)
-{
- u8 v1;
-
- wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
- outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
- wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
- outb(fn, spic_dev.cur_ioport->io1.minimum);
- wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
- outb(v, spic_dev.cur_ioport->io1.minimum);
- v1 = inb_p(spic_dev.cur_ioport->io1.minimum);
- dprintk("sony_pic_call3(0x%.2x - 0x%.2x - 0x%.2x): 0x%.4x\n",
- dev, fn, v, v1);
- return v1;
-}
-
/*
* minidrivers for SPIC models
*/
@@ -3722,156 +3714,6 @@ out:
dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3);
}
-/* camera tests and poweron/poweroff */
-#define SONYPI_CAMERA_PICTURE 5
-#define SONYPI_CAMERA_CONTROL 0x10
-
-#define SONYPI_CAMERA_BRIGHTNESS 0
-#define SONYPI_CAMERA_CONTRAST 1
-#define SONYPI_CAMERA_HUE 2
-#define SONYPI_CAMERA_COLOR 3
-#define SONYPI_CAMERA_SHARPNESS 4
-
-#define SONYPI_CAMERA_EXPOSURE_MASK 0xC
-#define SONYPI_CAMERA_WHITE_BALANCE_MASK 0x3
-#define SONYPI_CAMERA_PICTURE_MODE_MASK 0x30
-#define SONYPI_CAMERA_MUTE_MASK 0x40
-
-/* the rest don't need a loop until not 0xff */
-#define SONYPI_CAMERA_AGC 6
-#define SONYPI_CAMERA_AGC_MASK 0x30
-#define SONYPI_CAMERA_SHUTTER_MASK 0x7
-
-#define SONYPI_CAMERA_SHUTDOWN_REQUEST 7
-#define SONYPI_CAMERA_CONTROL 0x10
-
-#define SONYPI_CAMERA_STATUS 7
-#define SONYPI_CAMERA_STATUS_READY 0x2
-#define SONYPI_CAMERA_STATUS_POSITION 0x4
-
-#define SONYPI_DIRECTION_BACKWARDS 0x4
-
-#define SONYPI_CAMERA_REVISION 8
-#define SONYPI_CAMERA_ROMVERSION 9
-
-static int __sony_pic_camera_ready(void)
-{
- u8 v;
-
- v = sony_pic_call2(0x8f, SONYPI_CAMERA_STATUS);
- return (v != 0xff && (v & SONYPI_CAMERA_STATUS_READY));
-}
-
-static int __sony_pic_camera_off(void)
-{
- if (!camera) {
- pr_warn("camera control not enabled\n");
- return -ENODEV;
- }
-
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE,
- SONYPI_CAMERA_MUTE_MASK),
- ITERATIONS_SHORT);
-
- if (spic_dev.camera_power) {
- sony_pic_call2(0x91, 0);
- spic_dev.camera_power = 0;
- }
- return 0;
-}
-
-static int __sony_pic_camera_on(void)
-{
- int i, j, x;
-
- if (!camera) {
- pr_warn("camera control not enabled\n");
- return -ENODEV;
- }
-
- if (spic_dev.camera_power)
- return 0;
-
- for (j = 5; j > 0; j--) {
-
- for (x = 0; x < 100 && sony_pic_call2(0x91, 0x1); x++)
- msleep(10);
- sony_pic_call1(0x93);
-
- for (i = 400; i > 0; i--) {
- if (__sony_pic_camera_ready())
- break;
- msleep(10);
- }
- if (i)
- break;
- }
-
- if (j == 0) {
- pr_warn("failed to power on camera\n");
- return -ENODEV;
- }
-
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTROL,
- 0x5a),
- ITERATIONS_SHORT);
-
- spic_dev.camera_power = 1;
- return 0;
-}
-
-/* External camera command (exported to the motion eye v4l driver) */
-int sony_pic_camera_command(int command, u8 value)
-{
- if (!camera)
- return -EIO;
-
- mutex_lock(&spic_dev.lock);
-
- switch (command) {
- case SONY_PIC_COMMAND_SETCAMERA:
- if (value)
- __sony_pic_camera_on();
- else
- __sony_pic_camera_off();
- break;
- case SONY_PIC_COMMAND_SETCAMERABRIGHTNESS:
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_BRIGHTNESS, value),
- ITERATIONS_SHORT);
- break;
- case SONY_PIC_COMMAND_SETCAMERACONTRAST:
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_CONTRAST, value),
- ITERATIONS_SHORT);
- break;
- case SONY_PIC_COMMAND_SETCAMERAHUE:
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_HUE, value),
- ITERATIONS_SHORT);
- break;
- case SONY_PIC_COMMAND_SETCAMERACOLOR:
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_COLOR, value),
- ITERATIONS_SHORT);
- break;
- case SONY_PIC_COMMAND_SETCAMERASHARPNESS:
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_SHARPNESS, value),
- ITERATIONS_SHORT);
- break;
- case SONY_PIC_COMMAND_SETCAMERAPICTURE:
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE, value),
- ITERATIONS_SHORT);
- break;
- case SONY_PIC_COMMAND_SETCAMERAAGC:
- wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_AGC, value),
- ITERATIONS_SHORT);
- break;
- default:
- pr_err("sony_pic_camera_command invalid: %d\n", command);
- break;
- }
- mutex_unlock(&spic_dev.lock);
- return 0;
-}
-EXPORT_SYMBOL(sony_pic_camera_command);
-
/* gprs/edge modem (SZ460N and SZ210P), thanks to Joshua Wise */
static void __sony_pic_set_wwanpower(u8 state)
{
@@ -4326,7 +4168,7 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
case ACPI_RESOURCE_TYPE_START_DEPENDENT:
{
/* start IO enumeration */
- struct sony_pic_ioport *ioport = kzalloc(sizeof(*ioport), GFP_KERNEL);
+ struct sony_pic_ioport *ioport = kzalloc_obj(*ioport);
if (!ioport)
return AE_ERROR;
@@ -4356,8 +4198,7 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
p->interrupts[i]);
continue;
}
- interrupt = kzalloc(sizeof(*interrupt),
- GFP_KERNEL);
+ interrupt = kzalloc_obj(*interrupt);
if (!interrupt)
return AE_ERROR;
@@ -4442,9 +4283,9 @@ end:
/*
* Disable the spic device by calling its _DIS method
*/
-static int sony_pic_disable(struct acpi_device *device)
+static int sony_pic_disable(struct device *dev)
{
- acpi_status ret = acpi_evaluate_object(device->handle, "_DIS", NULL,
+ acpi_status ret = acpi_evaluate_object(ACPI_HANDLE(dev), "_DIS", NULL,
NULL);
if (ACPI_FAILURE(ret) && ret != AE_NOT_FOUND)
@@ -4460,7 +4301,7 @@ static int sony_pic_disable(struct acpi_device *device)
*
* Call _SRS to set current resources
*/
-static int sony_pic_enable(struct acpi_device *device,
+static int sony_pic_enable(struct device *dev,
struct sony_pic_ioport *ioport, struct sony_pic_irq *irq)
{
acpi_status status;
@@ -4542,7 +4383,7 @@ static int sony_pic_enable(struct acpi_device *device,
/* Attempt to set the resource */
dprintk("Evaluating _SRS\n");
- status = acpi_set_current_resources(device->handle, &buffer);
+ status = acpi_set_current_resources(ACPI_HANDLE(dev), &buffer);
/* check for total failure */
if (ACPI_FAILURE(status)) {
@@ -4631,12 +4472,12 @@ found:
* ACPI driver
*
*****************/
-static void sony_pic_remove(struct acpi_device *device)
+static void sony_pic_remove(struct platform_device *pdev)
{
struct sony_pic_ioport *io, *tmp_io;
struct sony_pic_irq *irq, *tmp_irq;
- if (sony_pic_disable(device)) {
+ if (sony_pic_disable(&pdev->dev)) {
pr_err("Couldn't disable device\n");
return;
}
@@ -4670,14 +4511,19 @@ static void sony_pic_remove(struct acpi_device *device)
dprintk(SONY_PIC_DRIVER_NAME " removed.\n");
}
-static int sony_pic_add(struct acpi_device *device)
+static int sony_pic_probe(struct platform_device *pdev)
{
- int result;
struct sony_pic_ioport *io, *tmp_io;
struct sony_pic_irq *irq, *tmp_irq;
+ struct acpi_device *device;
+ int result;
+
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
spic_dev.acpi_dev = device;
- strcpy(acpi_device_class(device), "sony/hotkey");
+ strscpy(acpi_device_class(device), "sony/hotkey");
sony_pic_detect_device_type(&spic_dev);
mutex_init(&spic_dev.lock);
@@ -4689,7 +4535,7 @@ static int sony_pic_add(struct acpi_device *device)
}
/* setup input devices and helper fifo */
- result = sony_laptop_setup_input(device);
+ result = sony_laptop_setup_input(&pdev->dev);
if (result) {
pr_err("Unable to create input devices\n");
goto err_free_resources;
@@ -4759,7 +4605,7 @@ static int sony_pic_add(struct acpi_device *device)
}
/* set resource status _SRS */
- result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq);
+ result = sony_pic_enable(&pdev->dev, spic_dev.cur_ioport, spic_dev.cur_irq);
if (result) {
pr_err("Couldn't enable device\n");
goto err_free_irq;
@@ -4782,7 +4628,7 @@ err_remove_pf:
sony_pf_remove();
err_disable_device:
- sony_pic_disable(device);
+ sony_pic_disable(&pdev->dev);
err_free_irq:
free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev);
@@ -4818,15 +4664,14 @@ err_free_resources:
#ifdef CONFIG_PM_SLEEP
static int sony_pic_suspend(struct device *dev)
{
- if (sony_pic_disable(to_acpi_device(dev)))
+ if (sony_pic_disable(dev))
return -ENXIO;
return 0;
}
static int sony_pic_resume(struct device *dev)
{
- sony_pic_enable(to_acpi_device(dev),
- spic_dev.cur_ioport, spic_dev.cur_irq);
+ sony_pic_enable(dev, spic_dev.cur_ioport, spic_dev.cur_irq);
return 0;
}
#endif
@@ -4838,15 +4683,14 @@ static const struct acpi_device_id sony_pic_device_ids[] = {
{"", 0},
};
-static struct acpi_driver sony_pic_driver = {
- .name = SONY_PIC_DRIVER_NAME,
- .class = SONY_PIC_CLASS,
- .ids = sony_pic_device_ids,
- .ops = {
- .add = sony_pic_add,
- .remove = sony_pic_remove,
- },
- .drv.pm = &sony_pic_pm,
+static struct platform_driver sony_pic_driver = {
+ .probe = sony_pic_probe,
+ .remove = sony_pic_remove,
+ .driver = {
+ .name = SONY_PIC_DRIVER_NAME,
+ .acpi_match_table = sony_pic_device_ids,
+ .pm = &sony_pic_pm,
+ },
};
static const struct dmi_system_id sonypi_dmi_table[] __initconst = {
@@ -4872,7 +4716,7 @@ static int __init sony_laptop_init(void)
int result;
if (!no_spic && dmi_check_system(sonypi_dmi_table)) {
- result = acpi_bus_register_driver(&sony_pic_driver);
+ result = platform_driver_register(&sony_pic_driver);
if (result) {
pr_err("Unable to register SPIC driver\n");
goto out;
@@ -4880,7 +4724,7 @@ static int __init sony_laptop_init(void)
spic_drv_registered = 1;
}
- result = acpi_bus_register_driver(&sony_nc_driver);
+ result = platform_driver_register(&sony_nc_driver);
if (result) {
pr_err("Unable to register SNC driver\n");
goto out_unregister_pic;
@@ -4890,16 +4734,16 @@ static int __init sony_laptop_init(void)
out_unregister_pic:
if (spic_drv_registered)
- acpi_bus_unregister_driver(&sony_pic_driver);
+ platform_driver_unregister(&sony_pic_driver);
out:
return result;
}
static void __exit sony_laptop_exit(void)
{
- acpi_bus_unregister_driver(&sony_nc_driver);
+ platform_driver_unregister(&sony_nc_driver);
if (spic_drv_registered)
- acpi_bus_unregister_driver(&sony_pic_driver);
+ platform_driver_unregister(&sony_pic_driver);
}
module_init(sony_laptop_init);
diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c
index 3da753b3d00d..dd7b1b07c316 100644
--- a/drivers/platform/x86/system76_acpi.c
+++ b/drivers/platform/x86/system76_acpi.c
@@ -18,6 +18,7 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/pci_ids.h>
+#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/sysfs.h>
#include <linux/types.h>
@@ -644,11 +645,10 @@ static void input_key(struct system76_data *data, unsigned int code)
}
// Handle ACPI notification
-static void system76_notify(struct acpi_device *acpi_dev, u32 event)
+static void system76_notify(acpi_handle handle, u32 event, void *context)
{
- struct system76_data *data;
+ struct system76_data *data = context;
- data = acpi_driver_data(acpi_dev);
switch (event) {
case 0x80:
kb_led_hotkey_hardware(data);
@@ -671,16 +671,23 @@ static void system76_notify(struct acpi_device *acpi_dev, u32 event)
}
}
-// Add a System76 ACPI device
-static int system76_add(struct acpi_device *acpi_dev)
+// Probe a System76 platform device
+static int system76_probe(struct platform_device *pdev)
{
+ struct acpi_device *acpi_dev;
struct system76_data *data;
int err;
- data = devm_kzalloc(&acpi_dev->dev, sizeof(*data), GFP_KERNEL);
+ acpi_dev = ACPI_COMPANION(&pdev->dev);
+ if (!acpi_dev)
+ return -ENODEV;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- acpi_dev->driver_data = data;
+
+ platform_set_drvdata(pdev, data);
+
data->acpi_dev = acpi_dev;
// Some models do not run open EC firmware. Check for an ACPI method
@@ -696,7 +703,7 @@ static int system76_add(struct acpi_device *acpi_dev)
data->ap_led.brightness_set_blocking = ap_led_set;
data->ap_led.max_brightness = 1;
data->ap_led.default_trigger = "rfkill-none";
- err = devm_led_classdev_register(&acpi_dev->dev, &data->ap_led);
+ err = devm_led_classdev_register(&pdev->dev, &data->ap_led);
if (err)
return err;
@@ -740,24 +747,29 @@ static int system76_add(struct acpi_device *acpi_dev)
}
if (data->kbled_type != KBLED_NONE) {
- err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led);
+ err = devm_led_classdev_register(&pdev->dev, &data->kb_led);
if (err)
return err;
}
- data->input = devm_input_allocate_device(&acpi_dev->dev);
+ data->input = devm_input_allocate_device(&pdev->dev);
if (!data->input)
return -ENOMEM;
data->input->name = "System76 ACPI Hotkeys";
data->input->phys = "system76_acpi/input0";
data->input->id.bustype = BUS_HOST;
- data->input->dev.parent = &acpi_dev->dev;
+ data->input->dev.parent = &pdev->dev;
input_set_capability(data->input, EV_KEY, KEY_SCREENLOCK);
err = input_register_device(data->input);
if (err)
- goto error;
+ return err;
+
+ err = acpi_dev_install_notify_handler(acpi_dev, ACPI_DEVICE_NOTIFY,
+ system76_notify, data);
+ if (err)
+ return err;
if (data->has_open_ec) {
err = system76_get_object(data, "NFAN", &data->nfan);
@@ -768,7 +780,7 @@ static int system76_add(struct acpi_device *acpi_dev)
if (err)
goto error;
- data->therm = devm_hwmon_device_register_with_info(&acpi_dev->dev,
+ data->therm = devm_hwmon_device_register_with_info(&pdev->dev,
"system76_acpi", data, &thermal_chip_info, NULL);
err = PTR_ERR_OR_ZERO(data->therm);
if (err)
@@ -784,15 +796,14 @@ error:
kfree(data->ntmp);
kfree(data->nfan);
}
+ acpi_dev_remove_notify_handler(acpi_dev, ACPI_DEVICE_NOTIFY, system76_notify);
return err;
}
-// Remove a System76 ACPI device
-static void system76_remove(struct acpi_device *acpi_dev)
+// Remove a System76 platform device
+static void system76_remove(struct platform_device *pdev)
{
- struct system76_data *data;
-
- data = acpi_driver_data(acpi_dev);
+ struct system76_data *data = platform_get_drvdata(pdev);
if (data->has_open_ec) {
system76_battery_exit();
@@ -800,23 +811,21 @@ static void system76_remove(struct acpi_device *acpi_dev)
kfree(data->ntmp);
}
- devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led);
- devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led);
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
+ ACPI_DEVICE_NOTIFY, system76_notify);
system76_get(data, "FINI");
}
-static struct acpi_driver system76_driver = {
- .name = "System76 ACPI Driver",
- .class = "hotkey",
- .ids = device_ids,
- .ops = {
- .add = system76_add,
- .remove = system76_remove,
- .notify = system76_notify,
+static struct platform_driver system76_driver = {
+ .probe = system76_probe,
+ .remove = system76_remove,
+ .driver = {
+ .name = "System76 ACPI Driver",
+ .acpi_match_table = device_ids,
},
};
-module_acpi_driver(system76_driver);
+module_platform_driver(system76_driver);
MODULE_DESCRIPTION("System76 ACPI Driver");
MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>");
diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c
index 20df1ebefc30..e09d7f8ce45f 100644
--- a/drivers/platform/x86/topstar-laptop.c
+++ b/drivers/platform/x86/topstar-laptop.c
@@ -232,9 +232,9 @@ static int topstar_acpi_fncx_switch(struct acpi_device *device, bool state)
return 0;
}
-static void topstar_acpi_notify(struct acpi_device *device, u32 event)
+static void topstar_acpi_notify(acpi_handle handle, u32 event, void *data)
{
- struct topstar_laptop *topstar = acpi_driver_data(device);
+ struct topstar_laptop *topstar = data;
static bool dup_evnt[2];
bool *dup;
@@ -285,20 +285,22 @@ static const struct dmi_system_id topstar_dmi_ids[] = {
{}
};
-static int topstar_acpi_add(struct acpi_device *device)
+static int topstar_acpi_probe(struct platform_device *pdev)
{
+ struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
struct topstar_laptop *topstar;
int err;
dmi_check_system(topstar_dmi_ids);
- topstar = kzalloc(sizeof(struct topstar_laptop), GFP_KERNEL);
+ topstar = kzalloc_obj(struct topstar_laptop);
if (!topstar)
return -ENOMEM;
- strcpy(acpi_device_name(device), "Topstar TPSACPI");
- strcpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS);
- device->driver_data = topstar;
+ platform_set_drvdata(pdev, topstar);
+
+ strscpy(acpi_device_name(device), "Topstar TPSACPI");
+ strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS);
topstar->device = device;
err = topstar_acpi_init(topstar);
@@ -313,14 +315,21 @@ static int topstar_acpi_add(struct acpi_device *device)
if (err)
goto err_platform_exit;
+ err = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ topstar_acpi_notify, topstar);
+ if (err)
+ goto err_input_exit;
+
if (led_workaround) {
err = topstar_led_init(topstar);
if (err)
- goto err_input_exit;
+ goto err_notify_handler_exit;
}
return 0;
+err_notify_handler_exit:
+ acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, topstar_acpi_notify);
err_input_exit:
topstar_input_exit(topstar);
err_platform_exit:
@@ -332,13 +341,15 @@ err_free:
return err;
}
-static void topstar_acpi_remove(struct acpi_device *device)
+static void topstar_acpi_remove(struct platform_device *pdev)
{
- struct topstar_laptop *topstar = acpi_driver_data(device);
+ struct topstar_laptop *topstar = platform_get_drvdata(pdev);
if (led_workaround)
topstar_led_exit(topstar);
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
+ ACPI_DEVICE_NOTIFY, topstar_acpi_notify);
topstar_input_exit(topstar);
topstar_platform_exit(topstar);
topstar_acpi_exit(topstar);
@@ -353,14 +364,12 @@ static const struct acpi_device_id topstar_device_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, topstar_device_ids);
-static struct acpi_driver topstar_acpi_driver = {
- .name = "Topstar laptop ACPI driver",
- .class = TOPSTAR_LAPTOP_CLASS,
- .ids = topstar_device_ids,
- .ops = {
- .add = topstar_acpi_add,
- .remove = topstar_acpi_remove,
- .notify = topstar_acpi_notify,
+static struct platform_driver topstar_acpi_driver = {
+ .probe = topstar_acpi_probe,
+ .remove = topstar_acpi_remove,
+ .driver = {
+ .name = "Topstar laptop ACPI driver",
+ .acpi_match_table = topstar_device_ids,
},
};
@@ -372,7 +381,7 @@ static int __init topstar_laptop_init(void)
if (ret < 0)
return ret;
- ret = acpi_bus_register_driver(&topstar_acpi_driver);
+ ret = platform_driver_register(&topstar_acpi_driver);
if (ret < 0)
goto err_driver_unreg;
@@ -386,7 +395,7 @@ err_driver_unreg:
static void __exit topstar_laptop_exit(void)
{
- acpi_bus_unregister_driver(&topstar_acpi_driver);
+ platform_driver_unregister(&topstar_acpi_driver);
platform_driver_unregister(&topstar_platform_driver);
}
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
index 5ad3a7183d33..7cecb3a70b9c 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -44,6 +44,7 @@
#include <linux/rfkill.h>
#include <linux/hwmon.h>
#include <linux/iio/iio.h>
+#include <linux/platform_device.h>
#include <linux/toshiba.h>
#include <acpi/battery.h>
#include <acpi/video.h>
@@ -223,6 +224,7 @@ struct toshiba_acpi_dev {
unsigned int cooling_method_supported:1;
unsigned int battery_charge_mode_supported:1;
unsigned int sysfs_created:1;
+ unsigned int notify_handler_installed:1;
unsigned int special_functions;
bool kbd_event_generated;
@@ -3193,14 +3195,80 @@ static void print_supported_features(struct toshiba_acpi_dev *dev)
pr_cont("\n");
}
-static void toshiba_acpi_remove(struct acpi_device *acpi_dev)
+static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data)
{
- struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
+ struct toshiba_acpi_dev *dev = data;
+ struct acpi_device *acpi_dev = dev->acpi_dev;
+
+ switch (event) {
+ case 0x80: /* Hotkeys and some system events */
+ /*
+ * Machines with this WMI GUID aren't supported due to bugs in
+ * their AML.
+ *
+ * Return silently to avoid triggering a netlink event.
+ */
+ if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID))
+ return;
+ toshiba_acpi_process_hotkeys(dev);
+ break;
+ case 0x81: /* Dock events */
+ case 0x82:
+ case 0x83:
+ pr_info("Dock event received %x\n", event);
+ break;
+ case 0x88: /* Thermal events */
+ pr_info("Thermal event received\n");
+ break;
+ case 0x8f: /* LID closed */
+ case 0x90: /* LID is closed and Dock has been ejected */
+ break;
+ case 0x8c: /* SATA power events */
+ case 0x8b:
+ pr_info("SATA power event received %x\n", event);
+ break;
+ case 0x92: /* Keyboard backlight mode changed */
+ dev->kbd_event_generated = true;
+ /* Update sysfs entries */
+ if (sysfs_update_group(&acpi_dev->dev.kobj,
+ &toshiba_attr_group))
+ pr_err("Unable to update sysfs entries\n");
+ /* Notify LED subsystem about keyboard backlight change */
+ if (dev->kbd_type == 2 && dev->kbd_mode != SCI_KBD_MODE_AUTO)
+ led_classdev_notify_brightness_hw_changed(&dev->kbd_led,
+ (dev->kbd_mode == SCI_KBD_MODE_ON) ?
+ LED_FULL : LED_OFF);
+ break;
+ case 0x8e: /* Power button pressed */
+ break;
+ case 0x85: /* Unknown */
+ case 0x8d: /* Unknown */
+ case 0x94: /* Unknown */
+ case 0x95: /* Unknown */
+ default:
+ pr_info("Unknown event received %x\n", event);
+ break;
+ }
+
+ acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
+ dev_name(&acpi_dev->dev),
+ event, (event == 0x80) ?
+ dev->last_key_event : 0);
+}
+
+static void toshiba_acpi_remove(struct platform_device *pdev)
+{
+ struct toshiba_acpi_dev *dev = platform_get_drvdata(pdev);
misc_deregister(&dev->miscdev);
remove_toshiba_proc_entries(dev);
+ if (dev->notify_handler_installed)
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
+ ACPI_DEVICE_NOTIFY,
+ toshiba_acpi_notify);
+
#if IS_ENABLED(CONFIG_HWMON)
if (dev->hwmon_device)
hwmon_device_unregister(dev->hwmon_device);
@@ -3240,6 +3308,8 @@ static void toshiba_acpi_remove(struct acpi_device *acpi_dev)
if (toshiba_acpi)
toshiba_acpi = NULL;
+ dev_set_drvdata(&dev->acpi_dev->dev, NULL);
+
kfree(dev);
}
@@ -3302,8 +3372,9 @@ static const struct dmi_system_id toshiba_dmi_quirks[] __initconst = {
{ }
};
-static int toshiba_acpi_add(struct acpi_device *acpi_dev)
+static int toshiba_acpi_probe(struct platform_device *pdev)
{
+ struct acpi_device *acpi_dev;
struct toshiba_acpi_dev *dev;
const char *hci_method;
u32 dummy;
@@ -3312,6 +3383,10 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
if (toshiba_acpi)
return -EBUSY;
+ acpi_dev = ACPI_COMPANION(&pdev->dev);
+ if (!acpi_dev)
+ return -ENODEV;
+
pr_info("Toshiba Laptop ACPI Extras version %s\n",
TOSHIBA_ACPI_VERSION);
@@ -3321,7 +3396,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
return -ENODEV;
}
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ dev = kzalloc_obj(*dev);
if (!dev)
return -ENOMEM;
dev->acpi_dev = acpi_dev;
@@ -3337,7 +3412,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
return ret;
}
- acpi_dev->driver_data = dev;
+ platform_set_drvdata(pdev, dev);
dev_set_drvdata(&acpi_dev->dev, dev);
/* Query the BIOS for supported features */
@@ -3368,7 +3443,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
dev->led_dev.max_brightness = 1;
dev->led_dev.brightness_set = toshiba_illumination_set;
dev->led_dev.brightness_get = toshiba_illumination_get;
- led_classdev_register(&acpi_dev->dev, &dev->led_dev);
+ led_classdev_register(&pdev->dev, &dev->led_dev);
}
toshiba_eco_mode_available(dev);
@@ -3377,7 +3452,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
dev->eco_led.max_brightness = 1;
dev->eco_led.brightness_set = toshiba_eco_mode_set_status;
dev->eco_led.brightness_get = toshiba_eco_mode_get_status;
- led_classdev_register(&dev->acpi_dev->dev, &dev->eco_led);
+ led_classdev_register(&pdev->dev, &dev->eco_led);
}
toshiba_kbd_illum_available(dev);
@@ -3393,7 +3468,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
dev->kbd_led.max_brightness = 1;
dev->kbd_led.brightness_set = toshiba_kbd_backlight_set;
dev->kbd_led.brightness_get = toshiba_kbd_backlight_get;
- led_classdev_register(&dev->acpi_dev->dev, &dev->kbd_led);
+ led_classdev_register(&pdev->dev, &dev->kbd_led);
}
ret = toshiba_touchpad_get(dev, &dummy);
@@ -3401,7 +3476,7 @@ static int toshiba_acpi_add(struct acpi_device *acpi_dev)
toshiba_accelerometer_available(dev);
if (dev->accelerometer_supported) {
- dev->indio_dev = iio_device_alloc(&acpi_dev->dev, sizeof(*dev));
+ dev->indio_dev = iio_device_alloc(&pdev->dev, sizeof(*dev));
if (!dev->indio_dev) {
pr_err("Unable to allocate iio device\n");
goto iio_error;
@@ -3450,7 +3525,7 @@ iio_error:
#if IS_ENABLED(CONFIG_HWMON)
if (dev->fan_rpm_supported) {
dev->hwmon_device = hwmon_device_register_with_info(
- &dev->acpi_dev->dev, "toshiba_acpi_sensors", NULL,
+ &pdev->dev, "toshiba_acpi_sensors", NULL,
&toshiba_acpi_hwmon_chip_info, NULL);
if (IS_ERR(dev->hwmon_device)) {
dev->hwmon_device = NULL;
@@ -3477,6 +3552,13 @@ iio_error:
}
dev->sysfs_created = !ret;
+ ret = acpi_dev_install_notify_handler(acpi_dev, ACPI_DEVICE_NOTIFY,
+ toshiba_acpi_notify, dev);
+ if (ret)
+ goto error;
+
+ dev->notify_handler_installed = 1;
+
create_toshiba_proc_entries(dev);
toshiba_acpi = dev;
@@ -3491,74 +3573,14 @@ iio_error:
return 0;
error:
- toshiba_acpi_remove(acpi_dev);
+ toshiba_acpi_remove(pdev);
return ret;
}
-static void toshiba_acpi_notify(struct acpi_device *acpi_dev, u32 event)
-{
- struct toshiba_acpi_dev *dev = acpi_driver_data(acpi_dev);
-
- switch (event) {
- case 0x80: /* Hotkeys and some system events */
- /*
- * Machines with this WMI GUID aren't supported due to bugs in
- * their AML.
- *
- * Return silently to avoid triggering a netlink event.
- */
- if (wmi_has_guid(TOSHIBA_WMI_EVENT_GUID))
- return;
- toshiba_acpi_process_hotkeys(dev);
- break;
- case 0x81: /* Dock events */
- case 0x82:
- case 0x83:
- pr_info("Dock event received %x\n", event);
- break;
- case 0x88: /* Thermal events */
- pr_info("Thermal event received\n");
- break;
- case 0x8f: /* LID closed */
- case 0x90: /* LID is closed and Dock has been ejected */
- break;
- case 0x8c: /* SATA power events */
- case 0x8b:
- pr_info("SATA power event received %x\n", event);
- break;
- case 0x92: /* Keyboard backlight mode changed */
- dev->kbd_event_generated = true;
- /* Update sysfs entries */
- if (sysfs_update_group(&acpi_dev->dev.kobj,
- &toshiba_attr_group))
- pr_err("Unable to update sysfs entries\n");
- /* Notify LED subsystem about keyboard backlight change */
- if (dev->kbd_type == 2 && dev->kbd_mode != SCI_KBD_MODE_AUTO)
- led_classdev_notify_brightness_hw_changed(&dev->kbd_led,
- (dev->kbd_mode == SCI_KBD_MODE_ON) ?
- LED_FULL : LED_OFF);
- break;
- case 0x8e: /* Power button pressed */
- break;
- case 0x85: /* Unknown */
- case 0x8d: /* Unknown */
- case 0x94: /* Unknown */
- case 0x95: /* Unknown */
- default:
- pr_info("Unknown event received %x\n", event);
- break;
- }
-
- acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class,
- dev_name(&acpi_dev->dev),
- event, (event == 0x80) ?
- dev->last_key_event : 0);
-}
-
#ifdef CONFIG_PM_SLEEP
static int toshiba_acpi_suspend(struct device *device)
{
- struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device));
+ struct toshiba_acpi_dev *dev = dev_get_drvdata(device);
if (dev->hotkey_dev) {
u32 result;
@@ -3573,7 +3595,7 @@ static int toshiba_acpi_suspend(struct device *device)
static int toshiba_acpi_resume(struct device *device)
{
- struct toshiba_acpi_dev *dev = acpi_driver_data(to_acpi_device(device));
+ struct toshiba_acpi_dev *dev = dev_get_drvdata(device);
if (dev->hotkey_dev) {
if (toshiba_acpi_enable_hotkeys(dev))
@@ -3595,16 +3617,14 @@ static int toshiba_acpi_resume(struct device *device)
static SIMPLE_DEV_PM_OPS(toshiba_acpi_pm,
toshiba_acpi_suspend, toshiba_acpi_resume);
-static struct acpi_driver toshiba_acpi_driver = {
- .name = "Toshiba ACPI driver",
- .ids = toshiba_device_ids,
- .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
- .ops = {
- .add = toshiba_acpi_add,
- .remove = toshiba_acpi_remove,
- .notify = toshiba_acpi_notify,
+static struct platform_driver toshiba_acpi_driver = {
+ .probe = toshiba_acpi_probe,
+ .remove = toshiba_acpi_remove,
+ .driver = {
+ .name = "Toshiba ACPI driver",
+ .acpi_match_table = toshiba_device_ids,
+ .pm = &toshiba_acpi_pm,
},
- .drv.pm = &toshiba_acpi_pm,
};
static void __init toshiba_dmi_init(void)
@@ -3634,7 +3654,7 @@ static int __init toshiba_acpi_init(void)
return -ENODEV;
}
- ret = acpi_bus_register_driver(&toshiba_acpi_driver);
+ ret = platform_driver_register(&toshiba_acpi_driver);
if (ret) {
pr_err("Failed to register ACPI driver: %d\n", ret);
remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
@@ -3645,7 +3665,7 @@ static int __init toshiba_acpi_init(void)
static void __exit toshiba_acpi_exit(void)
{
- acpi_bus_unregister_driver(&toshiba_acpi_driver);
+ platform_driver_unregister(&toshiba_acpi_driver);
if (toshiba_proc_dir)
remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
}
diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c
index dad2c3e55904..e00abba58c7c 100644
--- a/drivers/platform/x86/toshiba_bluetooth.c
+++ b/drivers/platform/x86/toshiba_bluetooth.c
@@ -17,6 +17,7 @@
#include <linux/types.h>
#include <linux/acpi.h>
#include <linux/rfkill.h>
+#include <linux/platform_device.h>
#define BT_KILLSWITCH_MASK 0x01
#define BT_PLUGGED_MASK 0x40
@@ -35,9 +36,9 @@ struct toshiba_bluetooth_dev {
bool powered;
};
-static int toshiba_bt_rfkill_add(struct acpi_device *device);
-static void toshiba_bt_rfkill_remove(struct acpi_device *device);
-static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event);
+static int toshiba_bt_rfkill_probe(struct platform_device *pdev);
+static void toshiba_bt_rfkill_remove(struct platform_device *pdev);
+static void toshiba_bt_rfkill_notify(acpi_handle handle, u32 event, void *data);
static const struct acpi_device_id bt_device_ids[] = {
{ "TOS6205", 0},
@@ -50,16 +51,14 @@ static int toshiba_bt_resume(struct device *dev);
#endif
static SIMPLE_DEV_PM_OPS(toshiba_bt_pm, NULL, toshiba_bt_resume);
-static struct acpi_driver toshiba_bt_rfkill_driver = {
- .name = "Toshiba BT",
- .class = "Toshiba",
- .ids = bt_device_ids,
- .ops = {
- .add = toshiba_bt_rfkill_add,
- .remove = toshiba_bt_rfkill_remove,
- .notify = toshiba_bt_rfkill_notify,
- },
- .drv.pm = &toshiba_bt_pm,
+static struct platform_driver toshiba_bt_rfkill_driver = {
+ .probe = toshiba_bt_rfkill_probe,
+ .remove = toshiba_bt_rfkill_remove,
+ .driver = {
+ .name = "Toshiba BT",
+ .acpi_match_table = bt_device_ids,
+ .pm = &toshiba_bt_pm,
+ },
};
static int toshiba_bluetooth_present(acpi_handle handle)
@@ -203,9 +202,9 @@ static const struct rfkill_ops rfk_ops = {
};
/* ACPI driver functions */
-static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event)
+static void toshiba_bt_rfkill_notify(acpi_handle handle, u32 event, void *data)
{
- struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device);
+ struct toshiba_bluetooth_dev *bt_dev = data;
if (toshiba_bluetooth_sync_status(bt_dev))
return;
@@ -216,11 +215,9 @@ static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event)
#ifdef CONFIG_PM_SLEEP
static int toshiba_bt_resume(struct device *dev)
{
- struct toshiba_bluetooth_dev *bt_dev;
+ struct toshiba_bluetooth_dev *bt_dev = dev_get_drvdata(dev);
int ret;
- bt_dev = acpi_driver_data(to_acpi_device(dev));
-
ret = toshiba_bluetooth_sync_status(bt_dev);
if (ret)
return ret;
@@ -231,23 +228,28 @@ static int toshiba_bt_resume(struct device *dev)
}
#endif
-static int toshiba_bt_rfkill_add(struct acpi_device *device)
+static int toshiba_bt_rfkill_probe(struct platform_device *pdev)
{
struct toshiba_bluetooth_dev *bt_dev;
+ struct acpi_device *device;
int result;
+ device = ACPI_COMPANION(&pdev->dev);
+ if (!device)
+ return -ENODEV;
+
result = toshiba_bluetooth_present(device->handle);
if (result)
return result;
pr_info("Toshiba ACPI Bluetooth device driver\n");
- bt_dev = kzalloc(sizeof(*bt_dev), GFP_KERNEL);
+ bt_dev = kzalloc_obj(*bt_dev);
if (!bt_dev)
return -ENOMEM;
bt_dev->acpi_dev = device;
- device->driver_data = bt_dev;
- dev_set_drvdata(&device->dev, bt_dev);
+
+ platform_set_drvdata(pdev, bt_dev);
result = toshiba_bluetooth_sync_status(bt_dev);
if (result) {
@@ -256,14 +258,14 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device)
}
bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth",
- &device->dev,
+ &pdev->dev,
RFKILL_TYPE_BLUETOOTH,
&rfk_ops,
bt_dev);
if (!bt_dev->rfk) {
pr_err("Unable to allocate rfkill device\n");
- kfree(bt_dev);
- return -ENOMEM;
+ result = -ENOMEM;
+ goto err_free_bt_dev;
}
rfkill_set_hw_state(bt_dev->rfk, !bt_dev->killswitch);
@@ -271,18 +273,36 @@ static int toshiba_bt_rfkill_add(struct acpi_device *device)
result = rfkill_register(bt_dev->rfk);
if (result) {
pr_err("Unable to register rfkill device\n");
- rfkill_destroy(bt_dev->rfk);
- kfree(bt_dev);
+ goto err_rfkill_destroy;
+ }
+
+ result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ toshiba_bt_rfkill_notify, bt_dev);
+ if (result) {
+ pr_err("Unable to register ACPI notify handler\n");
+ goto err_rfkill_unregister;
}
+ return 0;
+
+err_rfkill_unregister:
+ rfkill_unregister(bt_dev->rfk);
+err_rfkill_destroy:
+ rfkill_destroy(bt_dev->rfk);
+err_free_bt_dev:
+ kfree(bt_dev);
return result;
}
-static void toshiba_bt_rfkill_remove(struct acpi_device *device)
+static void toshiba_bt_rfkill_remove(struct platform_device *pdev)
{
- struct toshiba_bluetooth_dev *bt_dev = acpi_driver_data(device);
+ struct toshiba_bluetooth_dev *bt_dev = platform_get_drvdata(pdev);
+ struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
/* clean up */
+ acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ toshiba_bt_rfkill_notify);
+
if (bt_dev->rfk) {
rfkill_unregister(bt_dev->rfk);
rfkill_destroy(bt_dev->rfk);
@@ -293,4 +313,4 @@ static void toshiba_bt_rfkill_remove(struct acpi_device *device)
toshiba_bluetooth_disable(device->handle);
}
-module_acpi_driver(toshiba_bt_rfkill_driver);
+module_platform_driver(toshiba_bt_rfkill_driver);
diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c
index 03dfddeee0c0..8d12241924df 100644
--- a/drivers/platform/x86/toshiba_haps.c
+++ b/drivers/platform/x86/toshiba_haps.c
@@ -12,6 +12,7 @@
#include <linux/init.h>
#include <linux/types.h>
#include <linux/acpi.h>
+#include <linux/platform_device.h>
MODULE_AUTHOR("Azael Avalos <coproscefalo@gmail.com>");
MODULE_DESCRIPTION("Toshiba HDD Active Protection Sensor");
@@ -129,8 +130,10 @@ static const struct attribute_group haps_attr_group = {
/*
* ACPI stuff
*/
-static void toshiba_haps_notify(struct acpi_device *device, u32 event)
+static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data)
{
+ struct acpi_device *device = data;
+
pr_debug("Received event: 0x%x\n", event);
acpi_bus_generate_netlink_event(device->pnp.device_class,
@@ -138,12 +141,19 @@ static void toshiba_haps_notify(struct acpi_device *device, u32 event)
event, 0);
}
-static void toshiba_haps_remove(struct acpi_device *device)
+static void toshiba_haps_remove(struct platform_device *pdev)
{
+ struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
+
+ acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY,
+ toshiba_haps_notify);
+
sysfs_remove_group(&device->dev.kobj, &haps_attr_group);
if (toshiba_haps)
toshiba_haps = NULL;
+
+ dev_set_drvdata(&device->dev, NULL);
}
/* Helper function */
@@ -170,27 +180,33 @@ static int toshiba_haps_available(acpi_handle handle)
return 1;
}
-static int toshiba_haps_add(struct acpi_device *acpi_dev)
+static int toshiba_haps_probe(struct platform_device *pdev)
{
struct toshiba_haps_dev *haps;
+ struct acpi_device *acpi_dev;
int ret;
if (toshiba_haps)
return -EBUSY;
+ acpi_dev = ACPI_COMPANION(&pdev->dev);
+ if (!acpi_dev)
+ return -ENODEV;
+
if (!toshiba_haps_available(acpi_dev->handle))
return -ENODEV;
pr_info("Toshiba HDD Active Protection Sensor device\n");
- haps = kzalloc(sizeof(struct toshiba_haps_dev), GFP_KERNEL);
+ haps = devm_kzalloc(&pdev->dev, sizeof(*haps), GFP_KERNEL);
if (!haps)
return -ENOMEM;
haps->acpi_dev = acpi_dev;
haps->protection_level = 2;
- acpi_dev->driver_data = haps;
+
dev_set_drvdata(&acpi_dev->dev, haps);
+ platform_set_drvdata(pdev, haps);
/* Set the protection level, currently at level 2 (Medium) */
ret = toshiba_haps_protection_level(acpi_dev->handle, 2);
@@ -201,19 +217,26 @@ static int toshiba_haps_add(struct acpi_device *acpi_dev)
if (ret)
return ret;
+ ret = acpi_dev_install_notify_handler(acpi_dev, ACPI_DEVICE_NOTIFY,
+ toshiba_haps_notify, acpi_dev);
+ if (ret)
+ goto err;
+
toshiba_haps = haps;
return 0;
+
+err:
+ sysfs_remove_group(&acpi_dev->dev.kobj, &haps_attr_group);
+ return ret;
}
#ifdef CONFIG_PM_SLEEP
static int toshiba_haps_suspend(struct device *device)
{
- struct toshiba_haps_dev *haps;
+ struct toshiba_haps_dev *haps = dev_get_drvdata(device);
int ret;
- haps = acpi_driver_data(to_acpi_device(device));
-
/* Deactivate the protection on suspend */
ret = toshiba_haps_protection_level(haps->acpi_dev->handle, 0);
@@ -222,11 +245,9 @@ static int toshiba_haps_suspend(struct device *device)
static int toshiba_haps_resume(struct device *device)
{
- struct toshiba_haps_dev *haps;
+ struct toshiba_haps_dev *haps = dev_get_drvdata(device);
int ret;
- haps = acpi_driver_data(to_acpi_device(device));
-
/* Set the stored protection level */
ret = toshiba_haps_protection_level(haps->acpi_dev->handle,
haps->protection_level);
@@ -249,16 +270,14 @@ static const struct acpi_device_id haps_device_ids[] = {
};
MODULE_DEVICE_TABLE(acpi, haps_device_ids);
-static struct acpi_driver toshiba_haps_driver = {
- .name = "Toshiba HAPS",
- .ids = haps_device_ids,
- .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS,
- .ops = {
- .add = toshiba_haps_add,
- .remove = toshiba_haps_remove,
- .notify = toshiba_haps_notify,
+static struct platform_driver toshiba_haps_driver = {
+ .probe = toshiba_haps_probe,
+ .remove = toshiba_haps_remove,
+ .driver = {
+ .name = "Toshiba HAPS",
+ .acpi_match_table = haps_device_ids,
+ .pm = &toshiba_haps_pm,
},
- .drv.pm = &toshiba_haps_pm,
};
-module_acpi_driver(toshiba_haps_driver);
+module_platform_driver(toshiba_haps_driver);
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index bdc19cd8d3ed..d83c387821ea 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -410,6 +410,16 @@ static const struct ts_dmi_data gdix1002_upside_down_data = {
.properties = gdix1001_upside_down_props,
};
+static const struct property_entry gdix1001_y_inverted_props[] = {
+ PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"),
+ { }
+};
+
+static const struct ts_dmi_data gdix1001_y_inverted_data = {
+ .acpi_name = "GDIX1001",
+ .properties = gdix1001_y_inverted_props,
+};
+
static const struct property_entry gp_electronic_t701_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 960),
PROPERTY_ENTRY_U32("touchscreen-size-y", 640),
@@ -1659,6 +1669,14 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
},
},
{
+ /* SUPI S10 */
+ .driver_data = (void *)&gdix1001_y_inverted_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SUPI"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "S10"),
+ },
+ },
+ {
/* Techbite Arc 11.6 */
.driver_data = (void *)&techbite_arc_11_6_data,
.matches = {
diff --git a/drivers/platform/x86/tuxedo/Kconfig b/drivers/platform/x86/tuxedo/Kconfig
new file mode 100644
index 000000000000..80be0947dddc
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/Kconfig
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com
+#
+# TUXEDO X86 Platform Specific Drivers
+#
+
+source "drivers/platform/x86/tuxedo/nb04/Kconfig"
diff --git a/drivers/platform/x86/tuxedo/Makefile b/drivers/platform/x86/tuxedo/Makefile
new file mode 100644
index 000000000000..0afe0d0f455e
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com
+#
+# TUXEDO X86 Platform Specific Drivers
+#
+
+obj-y += nb04/
diff --git a/drivers/platform/x86/tuxedo/nb04/Kconfig b/drivers/platform/x86/tuxedo/nb04/Kconfig
new file mode 100644
index 000000000000..9e7a9f9230d1
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/nb04/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com
+#
+# TUXEDO X86 Platform Specific Drivers
+#
+
+config TUXEDO_NB04_WMI_AB
+ tristate "TUXEDO NB04 WMI AB Platform Driver"
+ depends on ACPI_WMI
+ depends on HID
+ help
+ This driver implements the WMI AB device found on TUXEDO notebooks
+ with board vendor NB04. This enables keyboard backlight control via a
+ virtual HID LampArray device.
+
+ When compiled as a module it will be called tuxedo_nb04_wmi_ab.
diff --git a/drivers/platform/x86/tuxedo/nb04/Makefile b/drivers/platform/x86/tuxedo/nb04/Makefile
new file mode 100644
index 000000000000..c963e0d60505
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/nb04/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2024-2025 Werner Sembach wse@tuxedocomputers.com
+#
+# TUXEDO X86 Platform Specific Drivers
+#
+
+tuxedo_nb04_wmi_ab-y := wmi_ab.o
+tuxedo_nb04_wmi_ab-y += wmi_util.o
+obj-$(CONFIG_TUXEDO_NB04_WMI_AB) += tuxedo_nb04_wmi_ab.o
diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_ab.c b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c
new file mode 100644
index 000000000000..32d7756022c2
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/nb04/wmi_ab.c
@@ -0,0 +1,923 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This driver implements the WMI AB device found on TUXEDO notebooks with board
+ * vendor NB04.
+ *
+ * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com>
+ */
+
+#include <linux/dmi.h>
+#include <linux/hid.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/wmi.h>
+
+#include "wmi_util.h"
+
+static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = {
+ { .guid_string = "80C9BAA6-AC48-4538-9234-9F81A55E7C85" },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids);
+
+enum {
+ LAMP_ARRAY_ATTRIBUTES_REPORT_ID = 0x01,
+ LAMP_ATTRIBUTES_REQUEST_REPORT_ID = 0x02,
+ LAMP_ATTRIBUTES_RESPONSE_REPORT_ID = 0x03,
+ LAMP_MULTI_UPDATE_REPORT_ID = 0x04,
+ LAMP_RANGE_UPDATE_REPORT_ID = 0x05,
+ LAMP_ARRAY_CONTROL_REPORT_ID = 0x06,
+};
+
+static u8 tux_report_descriptor[327] = {
+ 0x05, 0x59, // Usage Page (Lighting and Illumination)
+ 0x09, 0x01, // Usage (Lamp Array)
+ 0xa1, 0x01, // Collection (Application)
+ 0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, // Report ID (1)
+ 0x09, 0x02, // Usage (Lamp Array Attributes Report)
+ 0xa1, 0x02, // Collection (Logical)
+ 0x09, 0x03, // Usage (Lamp Count)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x01, // Report Count (1)
+ 0xb1, 0x03, // Feature (Cnst,Var,Abs)
+ 0x09, 0x04, // Usage (Bounding Box Width In Micrometers)
+ 0x09, 0x05, // Usage (Bounding Box Height In Micrometers)
+ 0x09, 0x06, // Usage (Bounding Box Depth In Micrometers)
+ 0x09, 0x07, // Usage (Lamp Array Kind)
+ 0x09, 0x08, // Usage (Min Update Interval In Microseconds)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647)
+ 0x75, 0x20, // Report Size (32)
+ 0x95, 0x05, // Report Count (5)
+ 0xb1, 0x03, // Feature (Cnst,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, // Report ID (2)
+ 0x09, 0x20, // Usage (Lamp Attributes Request Report)
+ 0xa1, 0x02, // Collection (Logical)
+ 0x09, 0x21, // Usage (Lamp Id)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x01, // Report Count (1)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, // Report ID (3)
+ 0x09, 0x22, // Usage (Lamp Attributes Response Report)
+ 0xa1, 0x02, // Collection (Logical)
+ 0x09, 0x21, // Usage (Lamp Id)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x01, // Report Count (1)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0x09, 0x23, // Usage (Position X In Micrometers)
+ 0x09, 0x24, // Usage (Position Y In Micrometers)
+ 0x09, 0x25, // Usage (Position Z In Micrometers)
+ 0x09, 0x27, // Usage (Update Latency In Microseconds)
+ 0x09, 0x26, // Usage (Lamp Purposes)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647)
+ 0x75, 0x20, // Report Size (32)
+ 0x95, 0x05, // Report Count (5)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0x09, 0x28, // Usage (Red Level Count)
+ 0x09, 0x29, // Usage (Green Level Count)
+ 0x09, 0x2a, // Usage (Blue Level Count)
+ 0x09, 0x2b, // Usage (Intensity Level Count)
+ 0x09, 0x2c, // Usage (Is Programmable)
+ 0x09, 0x2d, // Usage (Input Binding)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x06, // Report Count (6)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, LAMP_MULTI_UPDATE_REPORT_ID, // Report ID (4)
+ 0x09, 0x50, // Usage (Lamp Multi Update Report)
+ 0xa1, 0x02, // Collection (Logical)
+ 0x09, 0x03, // Usage (Lamp Count)
+ 0x09, 0x55, // Usage (Lamp Update Flags)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x08, // Logical Maximum (8)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x02, // Report Count (2)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0x09, 0x21, // Usage (Lamp Id)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x08, // Report Count (8)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x20, // Report Count (32)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, LAMP_RANGE_UPDATE_REPORT_ID, // Report ID (5)
+ 0x09, 0x60, // Usage (Lamp Range Update Report)
+ 0xa1, 0x02, // Collection (Logical)
+ 0x09, 0x55, // Usage (Lamp Update Flags)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x08, // Logical Maximum (8)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x01, // Report Count (1)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0x09, 0x61, // Usage (Lamp Id Start)
+ 0x09, 0x62, // Usage (Lamp Id End)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x02, // Report Count (2)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0x09, 0x51, // Usage (Red Update Channel)
+ 0x09, 0x52, // Usage (Green Update Channel)
+ 0x09, 0x53, // Usage (Blue Update Channel)
+ 0x09, 0x54, // Usage (Intensity Update Channel)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x04, // Report Count (4)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0x85, LAMP_ARRAY_CONTROL_REPORT_ID, // Report ID (6)
+ 0x09, 0x70, // Usage (Lamp Array Control Report)
+ 0xa1, 0x02, // Collection (Logical)
+ 0x09, 0x71, // Usage (Autonomous Mode)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x01, // Report Count (1)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0xc0, // End Collection
+ 0xc0 // End Collection
+};
+
+struct tux_kbl_map_entry_t {
+ u8 code;
+ struct {
+ u32 x;
+ u32 y;
+ u32 z;
+ } pos;
+};
+
+static const struct tux_kbl_map_entry_t sirius_16_ansii_kbl_map[] = {
+ { 0x29, { 25000, 53000, 5000 } },
+ { 0x3a, { 41700, 53000, 5000 } },
+ { 0x3b, { 58400, 53000, 5000 } },
+ { 0x3c, { 75100, 53000, 5000 } },
+ { 0x3d, { 91800, 53000, 5000 } },
+ { 0x3e, { 108500, 53000, 5000 } },
+ { 0x3f, { 125200, 53000, 5000 } },
+ { 0x40, { 141900, 53000, 5000 } },
+ { 0x41, { 158600, 53000, 5000 } },
+ { 0x42, { 175300, 53000, 5000 } },
+ { 0x43, { 192000, 53000, 5000 } },
+ { 0x44, { 208700, 53000, 5000 } },
+ { 0x45, { 225400, 53000, 5000 } },
+ { 0xf1, { 242100, 53000, 5000 } },
+ { 0x46, { 258800, 53000, 5000 } },
+ { 0x4c, { 275500, 53000, 5000 } },
+ { 0x4a, { 294500, 53000, 5000 } },
+ { 0x4d, { 311200, 53000, 5000 } },
+ { 0x4b, { 327900, 53000, 5000 } },
+ { 0x4e, { 344600, 53000, 5000 } },
+ { 0x35, { 24500, 67500, 5250 } },
+ { 0x1e, { 42500, 67500, 5250 } },
+ { 0x1f, { 61000, 67500, 5250 } },
+ { 0x20, { 79500, 67500, 5250 } },
+ { 0x21, { 98000, 67500, 5250 } },
+ { 0x22, { 116500, 67500, 5250 } },
+ { 0x23, { 135000, 67500, 5250 } },
+ { 0x24, { 153500, 67500, 5250 } },
+ { 0x25, { 172000, 67500, 5250 } },
+ { 0x26, { 190500, 67500, 5250 } },
+ { 0x27, { 209000, 67500, 5250 } },
+ { 0x2d, { 227500, 67500, 5250 } },
+ { 0x2e, { 246000, 67500, 5250 } },
+ { 0x2a, { 269500, 67500, 5250 } },
+ { 0x53, { 294500, 67500, 5250 } },
+ { 0x55, { 311200, 67500, 5250 } },
+ { 0x54, { 327900, 67500, 5250 } },
+ { 0x56, { 344600, 67500, 5250 } },
+ { 0x2b, { 31000, 85500, 5500 } },
+ { 0x14, { 51500, 85500, 5500 } },
+ { 0x1a, { 70000, 85500, 5500 } },
+ { 0x08, { 88500, 85500, 5500 } },
+ { 0x15, { 107000, 85500, 5500 } },
+ { 0x17, { 125500, 85500, 5500 } },
+ { 0x1c, { 144000, 85500, 5500 } },
+ { 0x18, { 162500, 85500, 5500 } },
+ { 0x0c, { 181000, 85500, 5500 } },
+ { 0x12, { 199500, 85500, 5500 } },
+ { 0x13, { 218000, 85500, 5500 } },
+ { 0x2f, { 236500, 85500, 5500 } },
+ { 0x30, { 255000, 85500, 5500 } },
+ { 0x31, { 273500, 85500, 5500 } },
+ { 0x5f, { 294500, 85500, 5500 } },
+ { 0x60, { 311200, 85500, 5500 } },
+ { 0x61, { 327900, 85500, 5500 } },
+ { 0x39, { 33000, 103500, 5750 } },
+ { 0x04, { 57000, 103500, 5750 } },
+ { 0x16, { 75500, 103500, 5750 } },
+ { 0x07, { 94000, 103500, 5750 } },
+ { 0x09, { 112500, 103500, 5750 } },
+ { 0x0a, { 131000, 103500, 5750 } },
+ { 0x0b, { 149500, 103500, 5750 } },
+ { 0x0d, { 168000, 103500, 5750 } },
+ { 0x0e, { 186500, 103500, 5750 } },
+ { 0x0f, { 205000, 103500, 5750 } },
+ { 0x33, { 223500, 103500, 5750 } },
+ { 0x34, { 242000, 103500, 5750 } },
+ { 0x28, { 267500, 103500, 5750 } },
+ { 0x5c, { 294500, 103500, 5750 } },
+ { 0x5d, { 311200, 103500, 5750 } },
+ { 0x5e, { 327900, 103500, 5750 } },
+ { 0x57, { 344600, 94500, 5625 } },
+ { 0xe1, { 37000, 121500, 6000 } },
+ { 0x1d, { 66000, 121500, 6000 } },
+ { 0x1b, { 84500, 121500, 6000 } },
+ { 0x06, { 103000, 121500, 6000 } },
+ { 0x19, { 121500, 121500, 6000 } },
+ { 0x05, { 140000, 121500, 6000 } },
+ { 0x11, { 158500, 121500, 6000 } },
+ { 0x10, { 177000, 121500, 6000 } },
+ { 0x36, { 195500, 121500, 6000 } },
+ { 0x37, { 214000, 121500, 6000 } },
+ { 0x38, { 232500, 121500, 6000 } },
+ { 0xe5, { 251500, 121500, 6000 } },
+ { 0x52, { 273500, 129000, 6125 } },
+ { 0x59, { 294500, 121500, 6000 } },
+ { 0x5a, { 311200, 121500, 6000 } },
+ { 0x5b, { 327900, 121500, 6000 } },
+ { 0xe0, { 28000, 139500, 6250 } },
+ { 0xfe, { 47500, 139500, 6250 } },
+ { 0xe3, { 66000, 139500, 6250 } },
+ { 0xe2, { 84500, 139500, 6250 } },
+ { 0x2c, { 140000, 139500, 6250 } },
+ { 0xe6, { 195500, 139500, 6250 } },
+ { 0x65, { 214000, 139500, 6250 } },
+ { 0xe4, { 234000, 139500, 6250 } },
+ { 0x50, { 255000, 147000, 6375 } },
+ { 0x51, { 273500, 147000, 6375 } },
+ { 0x4f, { 292000, 147000, 6375 } },
+ { 0x62, { 311200, 139500, 6250 } },
+ { 0x63, { 327900, 139500, 6250 } },
+ { 0x58, { 344600, 130500, 6125 } },
+};
+
+static const struct tux_kbl_map_entry_t sirius_16_iso_kbl_map[] = {
+ { 0x29, { 25000, 53000, 5000 } },
+ { 0x3a, { 41700, 53000, 5000 } },
+ { 0x3b, { 58400, 53000, 5000 } },
+ { 0x3c, { 75100, 53000, 5000 } },
+ { 0x3d, { 91800, 53000, 5000 } },
+ { 0x3e, { 108500, 53000, 5000 } },
+ { 0x3f, { 125200, 53000, 5000 } },
+ { 0x40, { 141900, 53000, 5000 } },
+ { 0x41, { 158600, 53000, 5000 } },
+ { 0x42, { 175300, 53000, 5000 } },
+ { 0x43, { 192000, 53000, 5000 } },
+ { 0x44, { 208700, 53000, 5000 } },
+ { 0x45, { 225400, 53000, 5000 } },
+ { 0xf1, { 242100, 53000, 5000 } },
+ { 0x46, { 258800, 53000, 5000 } },
+ { 0x4c, { 275500, 53000, 5000 } },
+ { 0x4a, { 294500, 53000, 5000 } },
+ { 0x4d, { 311200, 53000, 5000 } },
+ { 0x4b, { 327900, 53000, 5000 } },
+ { 0x4e, { 344600, 53000, 5000 } },
+ { 0x35, { 24500, 67500, 5250 } },
+ { 0x1e, { 42500, 67500, 5250 } },
+ { 0x1f, { 61000, 67500, 5250 } },
+ { 0x20, { 79500, 67500, 5250 } },
+ { 0x21, { 98000, 67500, 5250 } },
+ { 0x22, { 116500, 67500, 5250 } },
+ { 0x23, { 135000, 67500, 5250 } },
+ { 0x24, { 153500, 67500, 5250 } },
+ { 0x25, { 172000, 67500, 5250 } },
+ { 0x26, { 190500, 67500, 5250 } },
+ { 0x27, { 209000, 67500, 5250 } },
+ { 0x2d, { 227500, 67500, 5250 } },
+ { 0x2e, { 246000, 67500, 5250 } },
+ { 0x2a, { 269500, 67500, 5250 } },
+ { 0x53, { 294500, 67500, 5250 } },
+ { 0x55, { 311200, 67500, 5250 } },
+ { 0x54, { 327900, 67500, 5250 } },
+ { 0x56, { 344600, 67500, 5250 } },
+ { 0x2b, { 31000, 85500, 5500 } },
+ { 0x14, { 51500, 85500, 5500 } },
+ { 0x1a, { 70000, 85500, 5500 } },
+ { 0x08, { 88500, 85500, 5500 } },
+ { 0x15, { 107000, 85500, 5500 } },
+ { 0x17, { 125500, 85500, 5500 } },
+ { 0x1c, { 144000, 85500, 5500 } },
+ { 0x18, { 162500, 85500, 5500 } },
+ { 0x0c, { 181000, 85500, 5500 } },
+ { 0x12, { 199500, 85500, 5500 } },
+ { 0x13, { 218000, 85500, 5500 } },
+ { 0x2f, { 234500, 85500, 5500 } },
+ { 0x30, { 251000, 85500, 5500 } },
+ { 0x5f, { 294500, 85500, 5500 } },
+ { 0x60, { 311200, 85500, 5500 } },
+ { 0x61, { 327900, 85500, 5500 } },
+ { 0x39, { 33000, 103500, 5750 } },
+ { 0x04, { 57000, 103500, 5750 } },
+ { 0x16, { 75500, 103500, 5750 } },
+ { 0x07, { 94000, 103500, 5750 } },
+ { 0x09, { 112500, 103500, 5750 } },
+ { 0x0a, { 131000, 103500, 5750 } },
+ { 0x0b, { 149500, 103500, 5750 } },
+ { 0x0d, { 168000, 103500, 5750 } },
+ { 0x0e, { 186500, 103500, 5750 } },
+ { 0x0f, { 205000, 103500, 5750 } },
+ { 0x33, { 223500, 103500, 5750 } },
+ { 0x34, { 240000, 103500, 5750 } },
+ { 0x32, { 256500, 103500, 5750 } },
+ { 0x28, { 271500, 94500, 5750 } },
+ { 0x5c, { 294500, 103500, 5750 } },
+ { 0x5d, { 311200, 103500, 5750 } },
+ { 0x5e, { 327900, 103500, 5750 } },
+ { 0x57, { 344600, 94500, 5625 } },
+ { 0xe1, { 28000, 121500, 6000 } },
+ { 0x64, { 47500, 121500, 6000 } },
+ { 0x1d, { 66000, 121500, 6000 } },
+ { 0x1b, { 84500, 121500, 6000 } },
+ { 0x06, { 103000, 121500, 6000 } },
+ { 0x19, { 121500, 121500, 6000 } },
+ { 0x05, { 140000, 121500, 6000 } },
+ { 0x11, { 158500, 121500, 6000 } },
+ { 0x10, { 177000, 121500, 6000 } },
+ { 0x36, { 195500, 121500, 6000 } },
+ { 0x37, { 214000, 121500, 6000 } },
+ { 0x38, { 232500, 121500, 6000 } },
+ { 0xe5, { 251500, 121500, 6000 } },
+ { 0x52, { 273500, 129000, 6125 } },
+ { 0x59, { 294500, 121500, 6000 } },
+ { 0x5a, { 311200, 121500, 6000 } },
+ { 0x5b, { 327900, 121500, 6000 } },
+ { 0xe0, { 28000, 139500, 6250 } },
+ { 0xfe, { 47500, 139500, 6250 } },
+ { 0xe3, { 66000, 139500, 6250 } },
+ { 0xe2, { 84500, 139500, 6250 } },
+ { 0x2c, { 140000, 139500, 6250 } },
+ { 0xe6, { 195500, 139500, 6250 } },
+ { 0x65, { 214000, 139500, 6250 } },
+ { 0xe4, { 234000, 139500, 6250 } },
+ { 0x50, { 255000, 147000, 6375 } },
+ { 0x51, { 273500, 147000, 6375 } },
+ { 0x4f, { 292000, 147000, 6375 } },
+ { 0x62, { 311200, 139500, 6250 } },
+ { 0x63, { 327900, 139500, 6250 } },
+ { 0x58, { 344600, 130500, 6125 } },
+};
+
+struct tux_driver_data_t {
+ struct hid_device *hdev;
+};
+
+struct tux_hdev_driver_data_t {
+ u8 lamp_count;
+ const struct tux_kbl_map_entry_t *kbl_map;
+ u8 next_lamp_id;
+ union tux_wmi_xx_496in_80out_in_t next_kbl_set_multiple_keys_in;
+};
+
+static int tux_ll_start(struct hid_device *hdev)
+{
+ struct wmi_device *wdev = to_wmi_device(hdev->dev.parent);
+ struct tux_hdev_driver_data_t *driver_data;
+ union tux_wmi_xx_8in_80out_out_t out;
+ union tux_wmi_xx_8in_80out_in_t in;
+ u8 keyboard_type;
+ int ret;
+
+ driver_data = devm_kzalloc(&hdev->dev, sizeof(*driver_data), GFP_KERNEL);
+ if (!driver_data)
+ return -ENOMEM;
+
+ in.get_device_status_in.device_type = TUX_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD;
+ ret = tux_wmi_xx_8in_80out(wdev, TUX_GET_DEVICE_STATUS, &in, &out);
+ if (ret)
+ return ret;
+
+ keyboard_type = out.get_device_status_out.keyboard_physical_layout;
+ if (keyboard_type == TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) {
+ driver_data->lamp_count = ARRAY_SIZE(sirius_16_ansii_kbl_map);
+ driver_data->kbl_map = sirius_16_ansii_kbl_map;
+ } else if (keyboard_type == TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) {
+ driver_data->lamp_count = ARRAY_SIZE(sirius_16_iso_kbl_map);
+ driver_data->kbl_map = sirius_16_iso_kbl_map;
+ } else {
+ return -EINVAL;
+ }
+ driver_data->next_lamp_id = 0;
+
+ dev_set_drvdata(&hdev->dev, driver_data);
+
+ return ret;
+}
+
+static void tux_ll_stop(struct hid_device *hdev __always_unused)
+{
+}
+
+static int tux_ll_open(struct hid_device *hdev __always_unused)
+{
+ return 0;
+}
+
+static void tux_ll_close(struct hid_device *hdev __always_unused)
+{
+}
+
+static int tux_ll_parse(struct hid_device *hdev)
+{
+ return hid_parse_report(hdev, tux_report_descriptor,
+ sizeof(tux_report_descriptor));
+}
+
+struct __packed lamp_array_attributes_report_t {
+ const u8 report_id;
+ u16 lamp_count;
+ u32 bounding_box_width_in_micrometers;
+ u32 bounding_box_height_in_micrometers;
+ u32 bounding_box_depth_in_micrometers;
+ u32 lamp_array_kind;
+ u32 min_update_interval_in_microseconds;
+};
+
+static int handle_lamp_array_attributes_report(struct hid_device *hdev,
+ struct lamp_array_attributes_report_t *rep)
+{
+ struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+
+ rep->lamp_count = driver_data->lamp_count;
+ rep->bounding_box_width_in_micrometers = 368000;
+ rep->bounding_box_height_in_micrometers = 266000;
+ rep->bounding_box_depth_in_micrometers = 30000;
+ /*
+ * LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of
+ * "HID Usage Tables v1.5"
+ */
+ rep->lamp_array_kind = 1;
+ // Some guessed value for interval microseconds
+ rep->min_update_interval_in_microseconds = 500;
+
+ return sizeof(*rep);
+}
+
+struct __packed lamp_attributes_request_report_t {
+ const u8 report_id;
+ u16 lamp_id;
+};
+
+static int handle_lamp_attributes_request_report(struct hid_device *hdev,
+ struct lamp_attributes_request_report_t *rep)
+{
+ struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+
+ if (rep->lamp_id < driver_data->lamp_count)
+ driver_data->next_lamp_id = rep->lamp_id;
+ else
+ driver_data->next_lamp_id = 0;
+
+ return sizeof(*rep);
+}
+
+struct __packed lamp_attributes_response_report_t {
+ const u8 report_id;
+ u16 lamp_id;
+ u32 position_x_in_micrometers;
+ u32 position_y_in_micrometers;
+ u32 position_z_in_micrometers;
+ u32 update_latency_in_microseconds;
+ u32 lamp_purpose;
+ u8 red_level_count;
+ u8 green_level_count;
+ u8 blue_level_count;
+ u8 intensity_level_count;
+ u8 is_programmable;
+ u8 input_binding;
+};
+
+static int handle_lamp_attributes_response_report(struct hid_device *hdev,
+ struct lamp_attributes_response_report_t *rep)
+{
+ struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+ u16 lamp_id = driver_data->next_lamp_id;
+
+ rep->lamp_id = lamp_id;
+ // Some guessed value for latency microseconds
+ rep->update_latency_in_microseconds = 100;
+ /*
+ * LampPurposeControl, see "26.3.1 LampPurposes Flags" of
+ * "HID Usage Tables v1.5"
+ */
+ rep->lamp_purpose = 1;
+ rep->red_level_count = 0xff;
+ rep->green_level_count = 0xff;
+ rep->blue_level_count = 0xff;
+ rep->intensity_level_count = 0xff;
+ rep->is_programmable = 1;
+
+ if (driver_data->kbl_map[lamp_id].code <= 0xe8) {
+ rep->input_binding = driver_data->kbl_map[lamp_id].code;
+ } else {
+ /*
+ * Everything bigger is reserved/undefined, see
+ * "10 Keyboard/Keypad Page (0x07)" of "HID Usage Tables v1.5"
+ * and should return 0, see "26.8.3 Lamp Attributes" of the same
+ * document.
+ */
+ rep->input_binding = 0;
+ }
+ rep->position_x_in_micrometers = driver_data->kbl_map[lamp_id].pos.x;
+ rep->position_y_in_micrometers = driver_data->kbl_map[lamp_id].pos.y;
+ rep->position_z_in_micrometers = driver_data->kbl_map[lamp_id].pos.z;
+
+ driver_data->next_lamp_id = (driver_data->next_lamp_id + 1) % driver_data->lamp_count;
+
+ return sizeof(*rep);
+}
+
+#define LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE BIT(0)
+
+struct __packed lamp_rgbi_tuple_t {
+ u8 red;
+ u8 green;
+ u8 blue;
+ u8 intensity;
+};
+
+#define LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX 8
+
+struct __packed lamp_multi_update_report_t {
+ const u8 report_id;
+ u8 lamp_count;
+ u8 lamp_update_flags;
+ u16 lamp_id[LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX];
+ struct lamp_rgbi_tuple_t update_channels[LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX];
+};
+
+static int handle_lamp_multi_update_report(struct hid_device *hdev,
+ struct lamp_multi_update_report_t *rep)
+{
+ struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+ union tux_wmi_xx_496in_80out_in_t *next = &driver_data->next_kbl_set_multiple_keys_in;
+ struct tux_kbl_set_multiple_keys_in_rgb_config_t *rgb_configs_j;
+ struct wmi_device *wdev = to_wmi_device(hdev->dev.parent);
+ union tux_wmi_xx_496in_80out_out_t out;
+ u8 key_id, key_id_j, intensity_i, red_i, green_i, blue_i;
+ int ret;
+
+ /*
+ * Catching misformatted lamp_multi_update_report and fail silently
+ * according to "HID Usage Tables v1.5"
+ */
+ for (unsigned int i = 0; i < rep->lamp_count; ++i) {
+ if (rep->lamp_id[i] > driver_data->lamp_count) {
+ hid_dbg(hdev, "Out of bounds lamp_id in lamp_multi_update_report. Skipping whole report!\n");
+ return sizeof(*rep);
+ }
+
+ for (unsigned int j = i + 1; j < rep->lamp_count; ++j) {
+ if (rep->lamp_id[i] == rep->lamp_id[j]) {
+ hid_dbg(hdev, "Duplicate lamp_id in lamp_multi_update_report. Skipping whole report!\n");
+ return sizeof(*rep);
+ }
+ }
+ }
+
+ for (unsigned int i = 0; i < rep->lamp_count; ++i) {
+ key_id = driver_data->kbl_map[rep->lamp_id[i]].code;
+
+ for (unsigned int j = 0;
+ j < TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX;
+ ++j) {
+ rgb_configs_j = &next->kbl_set_multiple_keys_in.rgb_configs[j];
+ key_id_j = rgb_configs_j->key_id;
+ if (key_id_j != 0x00 && key_id_j != key_id)
+ continue;
+
+ if (key_id_j == 0x00)
+ next->kbl_set_multiple_keys_in.rgb_configs_cnt =
+ j + 1;
+ rgb_configs_j->key_id = key_id;
+ /*
+ * While this driver respects update_channel.intensity
+ * according to "HID Usage Tables v1.5" also on RGB
+ * leds, the Microsoft MacroPad reference implementation
+ * (https://github.com/microsoft/RP2040MacropadHidSample
+ * 1d6c3ad) does not and ignores it. If it turns out
+ * that Windows writes intensity = 0 for RGB leds
+ * instead of intensity = 255, this driver should also
+ * ignore the update_channel.intensity.
+ */
+ intensity_i = rep->update_channels[i].intensity;
+ red_i = rep->update_channels[i].red;
+ green_i = rep->update_channels[i].green;
+ blue_i = rep->update_channels[i].blue;
+ rgb_configs_j->red = red_i * intensity_i / 0xff;
+ rgb_configs_j->green = green_i * intensity_i / 0xff;
+ rgb_configs_j->blue = blue_i * intensity_i / 0xff;
+
+ break;
+ }
+ }
+
+ if (rep->lamp_update_flags & LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE) {
+ ret = tux_wmi_xx_496in_80out(wdev, TUX_KBL_SET_MULTIPLE_KEYS,
+ next, &out);
+ memset(next, 0, sizeof(*next));
+ if (ret)
+ return ret;
+ }
+
+ return sizeof(*rep);
+}
+
+struct __packed lamp_range_update_report_t {
+ const u8 report_id;
+ u8 lamp_update_flags;
+ u16 lamp_id_start;
+ u16 lamp_id_end;
+ struct lamp_rgbi_tuple_t update_channel;
+};
+
+static int handle_lamp_range_update_report(struct hid_device *hdev,
+ struct lamp_range_update_report_t *rep)
+{
+ struct tux_hdev_driver_data_t *driver_data = dev_get_drvdata(&hdev->dev);
+ struct lamp_multi_update_report_t lamp_multi_update_report = {
+ .report_id = LAMP_MULTI_UPDATE_REPORT_ID,
+ };
+ struct lamp_rgbi_tuple_t *update_channels_j;
+ int ret;
+
+ /*
+ * Catching misformatted lamp_range_update_report and fail silently
+ * according to "HID Usage Tables v1.5"
+ */
+ if (rep->lamp_id_start > rep->lamp_id_end) {
+ hid_dbg(hdev, "lamp_id_start > lamp_id_end in lamp_range_update_report. Skipping whole report!\n");
+ return sizeof(*rep);
+ }
+
+ if (rep->lamp_id_end > driver_data->lamp_count - 1) {
+ hid_dbg(hdev, "Out of bounds lamp_id_end in lamp_range_update_report. Skipping whole report!\n");
+ return sizeof(*rep);
+ }
+
+ /*
+ * Break handle_lamp_range_update_report call down to multiple
+ * handle_lamp_multi_update_report calls to easily ensure that mixing
+ * handle_lamp_range_update_report and handle_lamp_multi_update_report
+ * does not break things.
+ */
+ for (unsigned int i = rep->lamp_id_start; i < rep->lamp_id_end + 1;
+ i = i + LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX) {
+ lamp_multi_update_report.lamp_count =
+ min(rep->lamp_id_end + 1 - i,
+ LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX);
+ lamp_multi_update_report.lamp_update_flags =
+ i + LAMP_MULTI_UPDATE_REPORT_LAMP_COUNT_MAX >=
+ rep->lamp_id_end + 1 ?
+ LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE : 0;
+
+ for (unsigned int j = 0; j < lamp_multi_update_report.lamp_count; ++j) {
+ lamp_multi_update_report.lamp_id[j] = i + j;
+ update_channels_j =
+ &lamp_multi_update_report.update_channels[j];
+ update_channels_j->red = rep->update_channel.red;
+ update_channels_j->green = rep->update_channel.green;
+ update_channels_j->blue = rep->update_channel.blue;
+ update_channels_j->intensity = rep->update_channel.intensity;
+ }
+
+ ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report);
+ if (ret < 0)
+ return ret;
+ if (ret != sizeof(lamp_multi_update_report))
+ return -EIO;
+ }
+
+ return sizeof(*rep);
+}
+
+struct __packed lamp_array_control_report_t {
+ const u8 report_id;
+ u8 autonomous_mode;
+};
+
+static int handle_lamp_array_control_report(struct hid_device *hdev __always_unused,
+ struct lamp_array_control_report_t *rep)
+{
+ /*
+ * The keyboards firmware doesn't have any built in controls and the
+ * built in effects are not implemented so this is a NOOP.
+ * According to the HID Documentation (HID Usage Tables v1.5) this
+ * function is optional and can be removed from the HID Report
+ * Descriptor, but it should first be confirmed that userspace respects
+ * this possibility too. The Microsoft MacroPad reference implementation
+ * (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad)
+ * already deviates from the spec at another point, see
+ * handle_lamp_*_update_report.
+ */
+
+ return sizeof(*rep);
+}
+
+static int tux_ll_raw_request(struct hid_device *hdev, u8 reportnum, u8 *buf,
+ size_t len, unsigned char rtype, int reqtype)
+{
+ if (rtype != HID_FEATURE_REPORT)
+ return -EINVAL;
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ switch (reportnum) {
+ case LAMP_ARRAY_ATTRIBUTES_REPORT_ID:
+ if (len != sizeof(struct lamp_array_attributes_report_t))
+ return -EINVAL;
+ return handle_lamp_array_attributes_report(hdev,
+ (struct lamp_array_attributes_report_t *)buf);
+ case LAMP_ATTRIBUTES_RESPONSE_REPORT_ID:
+ if (len != sizeof(struct lamp_attributes_response_report_t))
+ return -EINVAL;
+ return handle_lamp_attributes_response_report(hdev,
+ (struct lamp_attributes_response_report_t *)buf);
+ }
+ break;
+ case HID_REQ_SET_REPORT:
+ switch (reportnum) {
+ case LAMP_ATTRIBUTES_REQUEST_REPORT_ID:
+ if (len != sizeof(struct lamp_attributes_request_report_t))
+ return -EINVAL;
+ return handle_lamp_attributes_request_report(hdev,
+ (struct lamp_attributes_request_report_t *)buf);
+ case LAMP_MULTI_UPDATE_REPORT_ID:
+ if (len != sizeof(struct lamp_multi_update_report_t))
+ return -EINVAL;
+ return handle_lamp_multi_update_report(hdev,
+ (struct lamp_multi_update_report_t *)buf);
+ case LAMP_RANGE_UPDATE_REPORT_ID:
+ if (len != sizeof(struct lamp_range_update_report_t))
+ return -EINVAL;
+ return handle_lamp_range_update_report(hdev,
+ (struct lamp_range_update_report_t *)buf);
+ case LAMP_ARRAY_CONTROL_REPORT_ID:
+ if (len != sizeof(struct lamp_array_control_report_t))
+ return -EINVAL;
+ return handle_lamp_array_control_report(hdev,
+ (struct lamp_array_control_report_t *)buf);
+ }
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static const struct hid_ll_driver tux_ll_driver = {
+ .start = &tux_ll_start,
+ .stop = &tux_ll_stop,
+ .open = &tux_ll_open,
+ .close = &tux_ll_close,
+ .parse = &tux_ll_parse,
+ .raw_request = &tux_ll_raw_request,
+};
+
+static int tux_virt_lamparray_add_device(struct wmi_device *wdev,
+ struct hid_device **hdev_out)
+{
+ struct hid_device *hdev;
+ int ret;
+
+ dev_dbg(&wdev->dev, "Adding TUXEDO NB04 Virtual LampArray device.\n");
+
+ hdev = hid_allocate_device();
+ if (IS_ERR(hdev))
+ return PTR_ERR(hdev);
+ *hdev_out = hdev;
+
+ strscpy(hdev->name, "TUXEDO NB04 RGB Lighting", sizeof(hdev->name));
+
+ hdev->ll_driver = &tux_ll_driver;
+ hdev->bus = BUS_VIRTUAL;
+ hdev->vendor = 0x21ba;
+ hdev->product = 0x0400;
+ hdev->dev.parent = &wdev->dev;
+
+ ret = hid_add_device(hdev);
+ if (ret)
+ hid_destroy_device(hdev);
+ return ret;
+}
+
+static int tux_probe(struct wmi_device *wdev, const void *context __always_unused)
+{
+ struct tux_driver_data_t *driver_data;
+
+ driver_data = devm_kzalloc(&wdev->dev, sizeof(*driver_data), GFP_KERNEL);
+ if (!driver_data)
+ return -ENOMEM;
+
+ dev_set_drvdata(&wdev->dev, driver_data);
+
+ return tux_virt_lamparray_add_device(wdev, &driver_data->hdev);
+}
+
+static void tux_remove(struct wmi_device *wdev)
+{
+ struct tux_driver_data_t *driver_data = dev_get_drvdata(&wdev->dev);
+
+ hid_destroy_device(driver_data->hdev);
+}
+
+static struct wmi_driver tuxedo_nb04_wmi_tux_driver = {
+ .driver = {
+ .name = "tuxedo_nb04_wmi_ab",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = tuxedo_nb04_wmi_ab_device_ids,
+ .probe = tux_probe,
+ .remove = tux_remove,
+ .no_singleton = true,
+};
+
+/*
+ * We don't know if the WMI API is stable and how unique the GUID is for this
+ * ODM. To be on the safe side we therefore only run this driver on tested
+ * devices defined by this list.
+ */
+static const struct dmi_system_id tested_devices_dmi_table[] __initconst = {
+ {
+ // TUXEDO Sirius 16 Gen1
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"),
+ },
+ },
+ {
+ // TUXEDO Sirius 16 Gen2
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"),
+ },
+ },
+ { }
+};
+
+static int __init tuxedo_nb04_wmi_tux_init(void)
+{
+ if (!dmi_check_system(tested_devices_dmi_table))
+ return -ENODEV;
+
+ return wmi_driver_register(&tuxedo_nb04_wmi_tux_driver);
+}
+module_init(tuxedo_nb04_wmi_tux_init);
+
+static void __exit tuxedo_nb04_wmi_tux_exit(void)
+{
+ return wmi_driver_unregister(&tuxedo_nb04_wmi_tux_driver);
+}
+module_exit(tuxedo_nb04_wmi_tux_exit);
+
+MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices");
+MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_util.c b/drivers/platform/x86/tuxedo/nb04/wmi_util.c
new file mode 100644
index 000000000000..e894690da1a8
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/nb04/wmi_util.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This code gives functions to avoid code duplication while interacting with
+ * the TUXEDO NB04 wmi interfaces.
+ *
+ * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cleanup.h>
+#include <linux/wmi.h>
+
+#include "wmi_util.h"
+
+static int __wmi_method_acpi_object_out(struct wmi_device *wdev,
+ u32 wmi_method_id,
+ u8 *in,
+ acpi_size in_len,
+ union acpi_object **out)
+{
+ struct acpi_buffer acpi_buffer_in = { in_len, in };
+ struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL };
+
+ dev_dbg(&wdev->dev, "Evaluate WMI method: %u in:\n", wmi_method_id);
+ print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len);
+
+ acpi_status status = wmidev_evaluate_method(wdev, 0, wmi_method_id,
+ &acpi_buffer_in,
+ &acpi_buffer_out);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&wdev->dev, "Failed to evaluate WMI method.\n");
+ return -EIO;
+ }
+ if (!acpi_buffer_out.pointer) {
+ dev_err(&wdev->dev, "Unexpected empty out buffer.\n");
+ return -ENODATA;
+ }
+
+ *out = acpi_buffer_out.pointer;
+
+ return 0;
+}
+
+static int __wmi_method_buffer_out(struct wmi_device *wdev,
+ u32 wmi_method_id,
+ u8 *in,
+ acpi_size in_len,
+ u8 *out,
+ acpi_size out_len)
+{
+ int ret;
+
+ union acpi_object *acpi_object_out __free(kfree) = NULL;
+
+ ret = __wmi_method_acpi_object_out(wdev, wmi_method_id,
+ in, in_len,
+ &acpi_object_out);
+ if (ret)
+ return ret;
+
+ if (acpi_object_out->type != ACPI_TYPE_BUFFER) {
+ dev_err(&wdev->dev, "Unexpected out buffer type. Expected: %u Got: %u\n",
+ ACPI_TYPE_BUFFER, acpi_object_out->type);
+ return -EIO;
+ }
+ if (acpi_object_out->buffer.length < out_len) {
+ dev_err(&wdev->dev, "Unexpected out buffer length.\n");
+ return -EIO;
+ }
+
+ memcpy(out, acpi_object_out->buffer.pointer, out_len);
+
+ return 0;
+}
+
+int tux_wmi_xx_8in_80out(struct wmi_device *wdev,
+ enum tux_wmi_xx_8in_80out_methods method,
+ union tux_wmi_xx_8in_80out_in_t *in,
+ union tux_wmi_xx_8in_80out_out_t *out)
+{
+ return __wmi_method_buffer_out(wdev, method, in->raw, 8, out->raw, 80);
+}
+
+int tux_wmi_xx_496in_80out(struct wmi_device *wdev,
+ enum tux_wmi_xx_496in_80out_methods method,
+ union tux_wmi_xx_496in_80out_in_t *in,
+ union tux_wmi_xx_496in_80out_out_t *out)
+{
+ return __wmi_method_buffer_out(wdev, method, in->raw, 496, out->raw, 80);
+}
diff --git a/drivers/platform/x86/tuxedo/nb04/wmi_util.h b/drivers/platform/x86/tuxedo/nb04/wmi_util.h
new file mode 100644
index 000000000000..c44093fd5093
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/nb04/wmi_util.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * This code gives functions to avoid code duplication while interacting with
+ * the TUXEDO NB04 wmi interfaces.
+ *
+ * Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com>
+ */
+
+#ifndef TUXEDO_NB04_WMI_UTIL_H
+#define TUXEDO_NB04_WMI_UTIL_H
+
+#include <linux/wmi.h>
+
+#define TUX_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD 1
+#define TUX_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD 2
+#define TUX_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES 3
+
+#define TUX_GET_DEVICE_STATUS_KBL_TYPE_NONE 0
+#define TUX_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY 1
+#define TUX_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE 2
+#define TUX_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY 3
+
+#define TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII 0
+#define TUX_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO 1
+
+#define TUX_GET_DEVICE_STATUS_COLOR_ID_RED 1
+#define TUX_GET_DEVICE_STATUS_COLOR_ID_GREEN 2
+#define TUX_GET_DEVICE_STATUS_COLOR_ID_YELLOW 3
+#define TUX_GET_DEVICE_STATUS_COLOR_ID_BLUE 4
+#define TUX_GET_DEVICE_STATUS_COLOR_ID_PURPLE 5
+#define TUX_GET_DEVICE_STATUS_COLOR_ID_INDIGO 6
+#define TUX_GET_DEVICE_STATUS_COLOR_ID_WHITE 7
+
+#define TUX_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD BIT(0)
+#define TUX_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS BIT(1)
+#define TUX_GET_DEVICE_STATUS_APP_PAGES_KBL BIT(2)
+#define TUX_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS BIT(3)
+
+union tux_wmi_xx_8in_80out_in_t {
+ u8 raw[8];
+ struct __packed {
+ u8 device_type;
+ u8 reserved[7];
+ } get_device_status_in;
+};
+
+union tux_wmi_xx_8in_80out_out_t {
+ u8 raw[80];
+ struct __packed {
+ u16 return_status;
+ u8 device_enabled;
+ u8 kbl_type;
+ u8 kbl_side_bar_supported;
+ u8 keyboard_physical_layout;
+ u8 app_pages;
+ u8 per_key_kbl_default_color;
+ u8 four_zone_kbl_default_color_1;
+ u8 four_zone_kbl_default_color_2;
+ u8 four_zone_kbl_default_color_3;
+ u8 four_zone_kbl_default_color_4;
+ u8 light_bar_kbl_default_color;
+ u8 reserved_0[1];
+ u16 dedicated_gpu_id;
+ u8 reserved_1[64];
+ } get_device_status_out;
+};
+
+enum tux_wmi_xx_8in_80out_methods {
+ TUX_GET_DEVICE_STATUS = 2,
+};
+
+#define TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX 120
+
+union tux_wmi_xx_496in_80out_in_t {
+ u8 raw[496];
+ struct __packed {
+ u8 reserved[15];
+ u8 rgb_configs_cnt;
+ struct tux_kbl_set_multiple_keys_in_rgb_config_t {
+ u8 key_id;
+ u8 red;
+ u8 green;
+ u8 blue;
+ } rgb_configs[TUX_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX];
+ } kbl_set_multiple_keys_in;
+};
+
+union tux_wmi_xx_496in_80out_out_t {
+ u8 raw[80];
+ struct __packed {
+ u8 return_value;
+ u8 reserved[79];
+ } kbl_set_multiple_keys_out;
+};
+
+enum tux_wmi_xx_496in_80out_methods {
+ TUX_KBL_SET_MULTIPLE_KEYS = 6,
+};
+
+int tux_wmi_xx_8in_80out(struct wmi_device *wdev,
+ enum tux_wmi_xx_8in_80out_methods method,
+ union tux_wmi_xx_8in_80out_in_t *in,
+ union tux_wmi_xx_8in_80out_out_t *out);
+int tux_wmi_xx_496in_80out(struct wmi_device *wdev,
+ enum tux_wmi_xx_496in_80out_methods method,
+ union tux_wmi_xx_496in_80out_in_t *in,
+ union tux_wmi_xx_496in_80out_out_t *out);
+
+#endif
diff --git a/drivers/platform/x86/uniwill/Kconfig b/drivers/platform/x86/uniwill/Kconfig
new file mode 100644
index 000000000000..d07cc8440188
--- /dev/null
+++ b/drivers/platform/x86/uniwill/Kconfig
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Uniwill X86 Platform Specific Drivers
+#
+
+menuconfig X86_PLATFORM_DRIVERS_UNIWILL
+ bool "Uniwill X86 Platform Specific Device Drivers"
+ depends on X86_PLATFORM_DEVICES
+ help
+ Say Y here to see options for device drivers for various
+ Uniwill x86 platforms, including many OEM laptops originally
+ manufactured by Uniwill.
+ This option alone does not add any kernel code.
+
+ If you say N, all options in this submenu will be skipped and disabled.
+
+if X86_PLATFORM_DRIVERS_UNIWILL
+
+config UNIWILL_LAPTOP
+ tristate "Uniwill Laptop Extras"
+ default m
+ depends on ACPI
+ depends on ACPI_WMI
+ depends on ACPI_BATTERY
+ depends on HWMON
+ depends on INPUT
+ depends on LEDS_CLASS_MULTICOLOR
+ depends on DMI
+ select REGMAP
+ select INPUT_SPARSEKMAP
+ help
+ This driver adds support for various extra features found on Uniwill laptops,
+ like the lightbar, hwmon sensors and hotkeys. It also supports many OEM laptops
+ originally manufactured by Uniwill.
+
+ If you have such a laptop, say Y or M here.
+
+endif
diff --git a/drivers/platform/x86/uniwill/Makefile b/drivers/platform/x86/uniwill/Makefile
new file mode 100644
index 000000000000..05cd1747a240
--- /dev/null
+++ b/drivers/platform/x86/uniwill/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for linux/drivers/platform/x86/uniwill
+# Uniwill X86 Platform Specific Drivers
+#
+
+obj-$(CONFIG_UNIWILL_LAPTOP) += uniwill-laptop.o
+uniwill-laptop-y := uniwill-acpi.o uniwill-wmi.o
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
new file mode 100644
index 000000000000..8cc01bec77b9
--- /dev/null
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -0,0 +1,2538 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Linux driver for Uniwill notebooks.
+ *
+ * Special thanks go to Pőcze Barnabás, Christoffer Sandberg and Werner Sembach
+ * for supporting the development of this driver either through prior work or
+ * by answering questions regarding the underlying ACPI and WMI interfaces.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/device/driver.h>
+#include <linux/dmi.h>
+#include <linux/errno.h>
+#include <linux/fixp-arith.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/kernel.h>
+#include <linux/kstrtox.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/printk.h>
+#include <linux/regmap.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <acpi/battery.h>
+
+#include "uniwill-wmi.h"
+
+#define EC_ADDR_BAT_POWER_UNIT_1 0x0400
+
+#define EC_ADDR_BAT_POWER_UNIT_2 0x0401
+
+#define EC_ADDR_BAT_DESIGN_CAPACITY_1 0x0402
+
+#define EC_ADDR_BAT_DESIGN_CAPACITY_2 0x0403
+
+#define EC_ADDR_BAT_FULL_CAPACITY_1 0x0404
+
+#define EC_ADDR_BAT_FULL_CAPACITY_2 0x0405
+
+#define EC_ADDR_BAT_DESIGN_VOLTAGE_1 0x0408
+
+#define EC_ADDR_BAT_DESIGN_VOLTAGE_2 0x0409
+
+#define EC_ADDR_BAT_STATUS_1 0x0432
+#define BAT_DISCHARGING BIT(0)
+
+#define EC_ADDR_BAT_STATUS_2 0x0433
+
+#define EC_ADDR_BAT_CURRENT_1 0x0434
+
+#define EC_ADDR_BAT_CURRENT_2 0x0435
+
+#define EC_ADDR_BAT_REMAIN_CAPACITY_1 0x0436
+
+#define EC_ADDR_BAT_REMAIN_CAPACITY_2 0x0437
+
+#define EC_ADDR_BAT_VOLTAGE_1 0x0438
+
+#define EC_ADDR_BAT_VOLTAGE_2 0x0439
+
+#define EC_ADDR_CPU_TEMP 0x043E
+
+#define EC_ADDR_GPU_TEMP 0x044F
+
+#define EC_ADDR_SYSTEM_ID 0x0456
+#define HAS_GPU BIT(7)
+
+#define EC_ADDR_MAIN_FAN_RPM_1 0x0464
+
+#define EC_ADDR_MAIN_FAN_RPM_2 0x0465
+
+#define EC_ADDR_SECOND_FAN_RPM_1 0x046C
+
+#define EC_ADDR_SECOND_FAN_RPM_2 0x046D
+
+#define EC_ADDR_DEVICE_STATUS 0x047B
+#define WIFI_STATUS_ON BIT(7)
+/* BIT(5) is also unset depending on the rfkill state (bluetooth?) */
+
+#define EC_ADDR_BAT_ALERT 0x0494
+
+#define EC_ADDR_BAT_CYCLE_COUNT_1 0x04A6
+
+#define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7
+
+#define EC_ADDR_PROJECT_ID 0x0740
+#define PROJECT_ID_PH4TRX1 0x12
+#define PROJECT_ID_PH6TRX1 0x15
+
+#define EC_ADDR_AP_OEM 0x0741
+#define ENABLE_MANUAL_CTRL BIT(0)
+#define ITE_KBD_EFFECT_REACTIVE BIT(3)
+#define FAN_ABNORMAL BIT(5)
+
+#define EC_ADDR_SUPPORT_5 0x0742
+#define FAN_TURBO_SUPPORTED BIT(4)
+#define FAN_SUPPORT BIT(5)
+
+#define EC_ADDR_CTGP_DB_CTRL 0x0743
+#define CTGP_DB_GENERAL_ENABLE BIT(0)
+#define CTGP_DB_DB_ENABLE BIT(1)
+#define CTGP_DB_CTGP_ENABLE BIT(2)
+
+#define EC_ADDR_CTGP_DB_CTGP_OFFSET 0x0744
+
+#define EC_ADDR_CTGP_DB_TPP_OFFSET 0x0745
+
+#define EC_ADDR_CTGP_DB_DB_OFFSET 0x0746
+
+#define EC_ADDR_LIGHTBAR_AC_CTRL 0x0748
+#define LIGHTBAR_APP_EXISTS BIT(0)
+#define LIGHTBAR_POWER_SAVE BIT(1)
+#define LIGHTBAR_S0_OFF BIT(2)
+#define LIGHTBAR_S3_OFF BIT(3) // Breathing animation when suspended
+#define LIGHTBAR_WELCOME BIT(7) // Rainbow animation
+
+#define EC_ADDR_LIGHTBAR_AC_RED 0x0749
+
+#define EC_ADDR_LIGHTBAR_AC_GREEN 0x074A
+
+#define EC_ADDR_LIGHTBAR_AC_BLUE 0x074B
+
+#define EC_ADDR_BIOS_OEM 0x074E
+#define FN_LOCK_STATUS BIT(4)
+
+#define EC_ADDR_MANUAL_FAN_CTRL 0x0751
+#define FAN_LEVEL_MASK GENMASK(2, 0)
+#define FAN_MODE_TURBO BIT(4)
+#define FAN_MODE_HIGH BIT(5)
+#define FAN_MODE_BOOST BIT(6)
+#define FAN_MODE_USER BIT(7)
+
+#define EC_ADDR_PWM_1 0x075B
+
+#define EC_ADDR_PWM_2 0x075C
+
+/* Unreliable */
+#define EC_ADDR_SUPPORT_1 0x0765
+#define AIRPLANE_MODE BIT(0)
+#define GPS_SWITCH BIT(1)
+#define OVERCLOCK BIT(2)
+#define MACRO_KEY BIT(3)
+#define SHORTCUT_KEY BIT(4)
+#define SUPER_KEY_LOCK BIT(5)
+#define LIGHTBAR BIT(6)
+#define FAN_BOOST BIT(7)
+
+#define EC_ADDR_SUPPORT_2 0x0766
+#define SILENT_MODE BIT(0)
+#define USB_CHARGING BIT(1)
+#define RGB_KEYBOARD BIT(2)
+#define CHINA_MODE BIT(5)
+#define MY_BATTERY BIT(6)
+
+#define EC_ADDR_TRIGGER 0x0767
+#define TRIGGER_SUPER_KEY_LOCK BIT(0)
+#define TRIGGER_LIGHTBAR BIT(1)
+#define TRIGGER_FAN_BOOST BIT(2)
+#define TRIGGER_SILENT_MODE BIT(3)
+#define TRIGGER_USB_CHARGING BIT(4)
+#define RGB_APPLY_COLOR BIT(5)
+#define RGB_LOGO_EFFECT BIT(6)
+#define RGB_RAINBOW_EFFECT BIT(7)
+
+#define EC_ADDR_SWITCH_STATUS 0x0768
+#define SUPER_KEY_LOCK_STATUS BIT(0)
+#define LIGHTBAR_STATUS BIT(1)
+#define FAN_BOOST_STATUS BIT(2)
+#define MACRO_KEY_STATUS BIT(3)
+#define MY_BAT_POWER_BAT_STATUS BIT(4)
+
+#define EC_ADDR_RGB_RED 0x0769
+
+#define EC_ADDR_RGB_GREEN 0x076A
+
+#define EC_ADDR_RGB_BLUE 0x076B
+
+#define EC_ADDR_ROMID_START 0x0770
+#define ROMID_LENGTH 14
+
+#define EC_ADDR_ROMID_EXTRA_1 0x077E
+
+#define EC_ADDR_ROMID_EXTRA_2 0x077F
+
+#define EC_ADDR_BIOS_OEM_2 0x0782
+#define FAN_V2_NEW BIT(0)
+#define FAN_QKEY BIT(1)
+#define FAN_TABLE_OFFICE_MODE BIT(2)
+#define FAN_V3 BIT(3)
+#define DEFAULT_MODE BIT(4)
+
+#define EC_ADDR_PL1_SETTING 0x0783
+
+#define EC_ADDR_PL2_SETTING 0x0784
+
+#define EC_ADDR_PL4_SETTING 0x0785
+
+#define EC_ADDR_FAN_DEFAULT 0x0786
+#define FAN_CURVE_LENGTH 5
+
+#define EC_ADDR_KBD_STATUS 0x078C
+#define KBD_WHITE_ONLY BIT(0) // ~single color
+#define KBD_SINGLE_COLOR_OFF BIT(1)
+#define KBD_TURBO_LEVEL_MASK GENMASK(3, 2)
+#define KBD_APPLY BIT(4)
+#define KBD_BRIGHTNESS GENMASK(7, 5)
+
+#define EC_ADDR_FAN_CTRL 0x078E
+#define FAN3P5 BIT(1)
+#define CHARGING_PROFILE BIT(3)
+#define UNIVERSAL_FAN_CTRL BIT(6)
+
+#define EC_ADDR_BIOS_OEM_3 0x07A3
+#define FAN_REDUCED_DURY_CYCLE BIT(5)
+#define FAN_ALWAYS_ON BIT(6)
+
+#define EC_ADDR_BIOS_BYTE 0x07A4
+#define FN_LOCK_SWITCH BIT(3)
+
+#define EC_ADDR_OEM_3 0x07A5
+#define POWER_LED_MASK GENMASK(1, 0)
+#define POWER_LED_LEFT 0x00
+#define POWER_LED_BOTH 0x01
+#define POWER_LED_NONE 0x02
+#define FAN_QUIET BIT(2)
+#define OVERBOOST BIT(4)
+#define HIGH_POWER BIT(7)
+
+#define EC_ADDR_OEM_4 0x07A6
+#define OVERBOOST_DYN_TEMP_OFF BIT(1)
+#define TOUCHPAD_TOGGLE_OFF BIT(6)
+
+#define EC_ADDR_CHARGE_CTRL 0x07B9
+#define CHARGE_CTRL_MASK GENMASK(6, 0)
+#define CHARGE_CTRL_REACHED BIT(7)
+
+#define EC_ADDR_UNIVERSAL_FAN_CTRL 0x07C5
+#define SPLIT_TABLES BIT(7)
+
+#define EC_ADDR_AP_OEM_6 0x07C6
+#define ENABLE_UNIVERSAL_FAN_CTRL BIT(2)
+#define BATTERY_CHARGE_FULL_OVER_24H BIT(3)
+#define BATTERY_ERM_STATUS_REACHED BIT(4)
+
+#define EC_ADDR_USB_C_POWER_PRIORITY 0x07CC
+#define USB_C_POWER_PRIORITY BIT(7)
+
+/* Same bits as EC_ADDR_LIGHTBAR_AC_CTRL except LIGHTBAR_S3_OFF */
+#define EC_ADDR_LIGHTBAR_BAT_CTRL 0x07E2
+
+#define EC_ADDR_LIGHTBAR_BAT_RED 0x07E3
+
+#define EC_ADDR_LIGHTBAR_BAT_GREEN 0x07E4
+
+#define EC_ADDR_LIGHTBAR_BAT_BLUE 0x07E5
+
+#define EC_ADDR_CPU_TEMP_END_TABLE 0x0F00
+
+#define EC_ADDR_CPU_TEMP_START_TABLE 0x0F10
+
+#define EC_ADDR_CPU_FAN_SPEED_TABLE 0x0F20
+
+#define EC_ADDR_GPU_TEMP_END_TABLE 0x0F30
+
+#define EC_ADDR_GPU_TEMP_START_TABLE 0x0F40
+
+#define EC_ADDR_GPU_FAN_SPEED_TABLE 0x0F50
+
+/*
+ * Those two registers technically allow for manual fan control,
+ * but are unstable on some models and are likely not meant to
+ * be used by applications as they are only accessible when using
+ * the WMI interface.
+ */
+#define EC_ADDR_PWM_1_WRITEABLE 0x1804
+
+#define EC_ADDR_PWM_2_WRITEABLE 0x1809
+
+#define DRIVER_NAME "uniwill"
+
+/*
+ * The OEM software always sleeps up to 6 ms after reading/writing EC
+ * registers, so we emulate this behaviour for maximum compatibility.
+ */
+#define UNIWILL_EC_DELAY_US 6000
+
+#define PWM_MAX 200
+#define FAN_TABLE_LENGTH 16
+
+#define LED_CHANNELS 3
+#define LED_MAX_BRIGHTNESS 200
+
+#define UNIWILL_FEATURE_FN_LOCK BIT(0)
+#define UNIWILL_FEATURE_SUPER_KEY BIT(1)
+#define UNIWILL_FEATURE_TOUCHPAD_TOGGLE BIT(2)
+#define UNIWILL_FEATURE_LIGHTBAR BIT(3)
+#define UNIWILL_FEATURE_BATTERY BIT(4)
+#define UNIWILL_FEATURE_CPU_TEMP BIT(5)
+#define UNIWILL_FEATURE_GPU_TEMP BIT(6)
+#define UNIWILL_FEATURE_PRIMARY_FAN BIT(7)
+#define UNIWILL_FEATURE_SECONDARY_FAN BIT(8)
+#define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(9)
+#define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(10)
+
+enum usb_c_power_priority_options {
+ USB_C_POWER_PRIORITY_CHARGING = 0,
+ USB_C_POWER_PRIORITY_PERFORMANCE,
+};
+
+struct uniwill_data {
+ struct device *dev;
+ acpi_handle handle;
+ struct regmap *regmap;
+ unsigned int features;
+ struct acpi_battery_hook hook;
+ unsigned int last_charge_ctrl;
+ struct mutex battery_lock; /* Protects the list of currently registered batteries */
+ unsigned int last_status;
+ unsigned int last_switch_status;
+ struct mutex super_key_lock; /* Protects the toggling of the super key lock state */
+ struct list_head batteries;
+ struct mutex led_lock; /* Protects writes to the lightbar registers */
+ struct led_classdev_mc led_mc_cdev;
+ struct mc_subled led_mc_subled_info[LED_CHANNELS];
+ struct mutex input_lock; /* Protects input sequence during notify */
+ struct input_dev *input_device;
+ struct notifier_block nb;
+ struct mutex usb_c_power_priority_lock; /* Protects dependent bit write and state safe */
+ enum usb_c_power_priority_options last_usb_c_power_priority_option;
+};
+
+struct uniwill_battery_entry {
+ struct list_head head;
+ struct power_supply *battery;
+};
+
+struct uniwill_device_descriptor {
+ unsigned int features;
+ /* Executed during driver probing */
+ int (*probe)(struct uniwill_data *data);
+};
+
+static bool force;
+module_param_unsafe(force, bool, 0);
+MODULE_PARM_DESC(force, "Force loading without checking for supported devices\n");
+
+/*
+ * Contains device specific data like the feature bitmap since
+ * the associated registers are not always reliable.
+ */
+static struct uniwill_device_descriptor device_descriptor __ro_after_init;
+
+static const char * const uniwill_temp_labels[] = {
+ "CPU",
+ "GPU",
+};
+
+static const char * const uniwill_fan_labels[] = {
+ "Main",
+ "Secondary",
+};
+
+static const struct key_entry uniwill_keymap[] = {
+ /* Reported via keyboard controller */
+ { KE_IGNORE, UNIWILL_OSD_CAPSLOCK, { KEY_CAPSLOCK }},
+ { KE_IGNORE, UNIWILL_OSD_NUMLOCK, { KEY_NUMLOCK }},
+
+ /*
+ * Reported when the user enables/disables the super key.
+ * Those events might even be reported when the change was done
+ * using the sysfs attribute!
+ */
+ { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_DISABLE, { KEY_UNKNOWN }},
+ { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_ENABLE, { KEY_UNKNOWN }},
+ /* Optional, might not be reported by all devices */
+ { KE_IGNORE, UNIWILL_OSD_SUPER_KEY_STATE_CHANGED, { KEY_UNKNOWN }},
+
+ /* Reported in manual mode when toggling the airplane mode status */
+ { KE_KEY, UNIWILL_OSD_RFKILL, { KEY_RFKILL }},
+ { KE_IGNORE, UNIWILL_OSD_RADIOON, { KEY_UNKNOWN }},
+ { KE_IGNORE, UNIWILL_OSD_RADIOOFF, { KEY_UNKNOWN }},
+
+ /* Reported when user wants to cycle the platform profile */
+ { KE_KEY, UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE, { KEY_F14 }},
+
+ /* Reported when the user wants to adjust the brightness of the keyboard */
+ { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }},
+ { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }},
+
+ /* Reported when the user wants to toggle the microphone mute status */
+ { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }},
+
+ /* Reported when the user wants to toggle the mute status */
+ { KE_IGNORE, UNIWILL_OSD_MUTE, { KEY_MUTE }},
+
+ /* Reported when the user wants to toggle the brightness of the keyboard */
+ { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }},
+ { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }},
+ { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }},
+ { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }},
+ { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }},
+ { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }},
+
+ /* FIXME: find out the exact meaning of those events */
+ { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }},
+ { KE_IGNORE, UNIWILL_OSD_BAT_ERM_UPDATE, { KEY_UNKNOWN }},
+
+ /* Reported when the user wants to toggle the benchmark mode status */
+ { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }},
+
+ /* Reported when the user wants to toggle the webcam */
+ { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }},
+
+ { KE_END }
+};
+
+static inline bool uniwill_device_supports(const struct uniwill_data *data,
+ unsigned int features)
+{
+ return (data->features & features) == features;
+}
+
+static int uniwill_ec_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ union acpi_object params[2] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = reg,
+ },
+ },
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = val,
+ },
+ },
+ };
+ struct uniwill_data *data = context;
+ struct acpi_object_list input = {
+ .count = ARRAY_SIZE(params),
+ .pointer = params,
+ };
+ acpi_status status;
+
+ status = acpi_evaluate_object(data->handle, "ECRW", &input, NULL);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ usleep_range(UNIWILL_EC_DELAY_US, UNIWILL_EC_DELAY_US * 2);
+
+ return 0;
+}
+
+static int uniwill_ec_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ union acpi_object params[1] = {
+ {
+ .integer = {
+ .type = ACPI_TYPE_INTEGER,
+ .value = reg,
+ },
+ },
+ };
+ struct uniwill_data *data = context;
+ struct acpi_object_list input = {
+ .count = ARRAY_SIZE(params),
+ .pointer = params,
+ };
+ unsigned long long output;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(data->handle, "ECRR", &input, &output);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ if (output > U8_MAX)
+ return -ENXIO;
+
+ usleep_range(UNIWILL_EC_DELAY_US, UNIWILL_EC_DELAY_US * 2);
+
+ *val = output;
+
+ return 0;
+}
+
+static const struct regmap_bus uniwill_ec_bus = {
+ .reg_write = uniwill_ec_reg_write,
+ .reg_read = uniwill_ec_reg_read,
+ .reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
+};
+
+static bool uniwill_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case EC_ADDR_AP_OEM:
+ case EC_ADDR_LIGHTBAR_AC_CTRL:
+ case EC_ADDR_LIGHTBAR_AC_RED:
+ case EC_ADDR_LIGHTBAR_AC_GREEN:
+ case EC_ADDR_LIGHTBAR_AC_BLUE:
+ case EC_ADDR_BIOS_OEM:
+ case EC_ADDR_TRIGGER:
+ case EC_ADDR_OEM_4:
+ case EC_ADDR_CHARGE_CTRL:
+ case EC_ADDR_LIGHTBAR_BAT_CTRL:
+ case EC_ADDR_LIGHTBAR_BAT_RED:
+ case EC_ADDR_LIGHTBAR_BAT_GREEN:
+ case EC_ADDR_LIGHTBAR_BAT_BLUE:
+ case EC_ADDR_CTGP_DB_CTRL:
+ case EC_ADDR_CTGP_DB_CTGP_OFFSET:
+ case EC_ADDR_CTGP_DB_TPP_OFFSET:
+ case EC_ADDR_CTGP_DB_DB_OFFSET:
+ case EC_ADDR_USB_C_POWER_PRIORITY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool uniwill_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case EC_ADDR_CPU_TEMP:
+ case EC_ADDR_GPU_TEMP:
+ case EC_ADDR_MAIN_FAN_RPM_1:
+ case EC_ADDR_MAIN_FAN_RPM_2:
+ case EC_ADDR_SECOND_FAN_RPM_1:
+ case EC_ADDR_SECOND_FAN_RPM_2:
+ case EC_ADDR_BAT_ALERT:
+ case EC_ADDR_PROJECT_ID:
+ case EC_ADDR_AP_OEM:
+ case EC_ADDR_LIGHTBAR_AC_CTRL:
+ case EC_ADDR_LIGHTBAR_AC_RED:
+ case EC_ADDR_LIGHTBAR_AC_GREEN:
+ case EC_ADDR_LIGHTBAR_AC_BLUE:
+ case EC_ADDR_BIOS_OEM:
+ case EC_ADDR_PWM_1:
+ case EC_ADDR_PWM_2:
+ case EC_ADDR_TRIGGER:
+ case EC_ADDR_SWITCH_STATUS:
+ case EC_ADDR_OEM_4:
+ case EC_ADDR_CHARGE_CTRL:
+ case EC_ADDR_LIGHTBAR_BAT_CTRL:
+ case EC_ADDR_LIGHTBAR_BAT_RED:
+ case EC_ADDR_LIGHTBAR_BAT_GREEN:
+ case EC_ADDR_LIGHTBAR_BAT_BLUE:
+ case EC_ADDR_SYSTEM_ID:
+ case EC_ADDR_CTGP_DB_CTRL:
+ case EC_ADDR_CTGP_DB_CTGP_OFFSET:
+ case EC_ADDR_CTGP_DB_TPP_OFFSET:
+ case EC_ADDR_CTGP_DB_DB_OFFSET:
+ case EC_ADDR_USB_C_POWER_PRIORITY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool uniwill_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case EC_ADDR_CPU_TEMP:
+ case EC_ADDR_GPU_TEMP:
+ case EC_ADDR_MAIN_FAN_RPM_1:
+ case EC_ADDR_MAIN_FAN_RPM_2:
+ case EC_ADDR_SECOND_FAN_RPM_1:
+ case EC_ADDR_SECOND_FAN_RPM_2:
+ case EC_ADDR_BAT_ALERT:
+ case EC_ADDR_BIOS_OEM:
+ case EC_ADDR_PWM_1:
+ case EC_ADDR_PWM_2:
+ case EC_ADDR_TRIGGER:
+ case EC_ADDR_SWITCH_STATUS:
+ case EC_ADDR_CHARGE_CTRL:
+ case EC_ADDR_USB_C_POWER_PRIORITY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config uniwill_ec_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .writeable_reg = uniwill_writeable_reg,
+ .readable_reg = uniwill_readable_reg,
+ .volatile_reg = uniwill_volatile_reg,
+ .can_sleep = true,
+ .max_register = 0xFFF,
+ .cache_type = REGCACHE_MAPLE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static ssize_t fn_lock_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ if (enable)
+ value = FN_LOCK_STATUS;
+ else
+ value = 0;
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_BIOS_OEM, &value);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", !!(value & FN_LOCK_STATUS));
+}
+
+static DEVICE_ATTR_RW(fn_lock);
+
+static ssize_t super_key_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ guard(mutex)(&data->super_key_lock);
+
+ ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * We can only toggle the super key lock, so we return early if the setting
+ * is already in the correct state.
+ */
+ if (enable == !(value & SUPER_KEY_LOCK_STATUS))
+ return count;
+
+ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK,
+ TRIGGER_SUPER_KEY_LOCK);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t super_key_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", !(value & SUPER_KEY_LOCK_STATUS));
+}
+
+static DEVICE_ATTR_RW(super_key_enable);
+
+static ssize_t touchpad_toggle_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ if (enable)
+ value = 0;
+ else
+ value = TOUCHPAD_TOGGLE_OFF;
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_4, TOUCHPAD_TOGGLE_OFF, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t touchpad_toggle_enable_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_OEM_4, &value);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", !(value & TOUCHPAD_TOGGLE_OFF));
+}
+
+static DEVICE_ATTR_RW(touchpad_toggle_enable);
+
+static ssize_t rainbow_animation_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ if (enable)
+ value = LIGHTBAR_WELCOME;
+ else
+ value = 0;
+
+ guard(mutex)(&data->led_lock);
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_WELCOME, value);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_WELCOME, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t rainbow_animation_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", !!(value & LIGHTBAR_WELCOME));
+}
+
+static DEVICE_ATTR_RW(rainbow_animation);
+
+static ssize_t breathing_in_suspend_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret < 0)
+ return ret;
+
+ if (enable)
+ value = 0;
+ else
+ value = LIGHTBAR_S3_OFF;
+
+ /* We only access a single register here, so we do not need to use data->led_lock */
+ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S3_OFF, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t breathing_in_suspend_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", !(value & LIGHTBAR_S3_OFF));
+}
+
+static DEVICE_ATTR_RW(breathing_in_suspend);
+
+static ssize_t ctgp_offset_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value > U8_MAX)
+ return -EINVAL;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ctgp_offset_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, &value);
+ if (ret < 0)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", value);
+}
+
+static DEVICE_ATTR_RW(ctgp_offset);
+
+static int uniwill_nvidia_ctgp_init(struct uniwill_data *data)
+{
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_CTGP_OFFSET, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_TPP_OFFSET, 255);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, EC_ADDR_CTGP_DB_DB_OFFSET, 25);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_GENERAL_ENABLE | CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const char * const usb_c_power_priority_text[] = {
+ [USB_C_POWER_PRIORITY_CHARGING] = "charging",
+ [USB_C_POWER_PRIORITY_PERFORMANCE] = "performance",
+};
+
+static const u8 usb_c_power_priority_value[] = {
+ [USB_C_POWER_PRIORITY_CHARGING] = 0,
+ [USB_C_POWER_PRIORITY_PERFORMANCE] = USB_C_POWER_PRIORITY,
+};
+
+static ssize_t usb_c_power_priority_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ enum usb_c_power_priority_options option;
+ unsigned int value;
+ int ret;
+
+ ret = sysfs_match_string(usb_c_power_priority_text, buf);
+ if (ret < 0)
+ return ret;
+
+ option = ret;
+ value = usb_c_power_priority_value[option];
+
+ guard(mutex)(&data->usb_c_power_priority_lock);
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_USB_C_POWER_PRIORITY,
+ USB_C_POWER_PRIORITY, value);
+ if (ret < 0)
+ return ret;
+
+ data->last_usb_c_power_priority_option = option;
+
+ return count;
+}
+
+static ssize_t usb_c_power_priority_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_USB_C_POWER_PRIORITY, &value);
+ if (ret < 0)
+ return ret;
+
+ value &= USB_C_POWER_PRIORITY;
+
+ if (usb_c_power_priority_value[USB_C_POWER_PRIORITY_PERFORMANCE] == value)
+ return sysfs_emit(buf, "%s\n",
+ usb_c_power_priority_text[USB_C_POWER_PRIORITY_PERFORMANCE]);
+
+ return sysfs_emit(buf, "%s\n", usb_c_power_priority_text[USB_C_POWER_PRIORITY_CHARGING]);
+}
+
+static DEVICE_ATTR_RW(usb_c_power_priority);
+
+static int usb_c_power_priority_restore(struct uniwill_data *data)
+{
+ unsigned int value;
+
+ value = usb_c_power_priority_value[data->last_usb_c_power_priority_option];
+
+ guard(mutex)(&data->usb_c_power_priority_lock);
+
+ return regmap_update_bits(data->regmap, EC_ADDR_USB_C_POWER_PRIORITY,
+ USB_C_POWER_PRIORITY, value);
+}
+
+static int usb_c_power_priority_init(struct uniwill_data *data)
+{
+ unsigned int value;
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
+ return 0;
+
+ ret = devm_mutex_init(data->dev, &data->usb_c_power_priority_lock);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_USB_C_POWER_PRIORITY, &value);
+ if (ret < 0)
+ return ret;
+
+ value &= USB_C_POWER_PRIORITY;
+
+ data->last_usb_c_power_priority_option =
+ usb_c_power_priority_value[USB_C_POWER_PRIORITY_PERFORMANCE] == value ?
+ USB_C_POWER_PRIORITY_PERFORMANCE :
+ USB_C_POWER_PRIORITY_CHARGING;
+
+ return 0;
+}
+
+static struct attribute *uniwill_attrs[] = {
+ /* Keyboard-related */
+ &dev_attr_fn_lock.attr,
+ &dev_attr_super_key_enable.attr,
+ &dev_attr_touchpad_toggle_enable.attr,
+ /* Lightbar-related */
+ &dev_attr_rainbow_animation.attr,
+ &dev_attr_breathing_in_suspend.attr,
+ /* Power-management-related */
+ &dev_attr_ctgp_offset.attr,
+ &dev_attr_usb_c_power_priority.attr,
+ NULL
+};
+
+static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct uniwill_data *data = dev_get_drvdata(dev);
+
+ if (attr == &dev_attr_fn_lock.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_super_key_enable.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_touchpad_toggle_enable.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_TOUCHPAD_TOGGLE))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_rainbow_animation.attr ||
+ attr == &dev_attr_breathing_in_suspend.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_ctgp_offset.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return attr->mode;
+ }
+
+ if (attr == &dev_attr_usb_c_power_priority.attr) {
+ if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
+ return attr->mode;
+ }
+
+ return 0;
+}
+
+static const struct attribute_group uniwill_group = {
+ .is_visible = uniwill_attr_is_visible,
+ .attrs = uniwill_attrs,
+};
+
+static const struct attribute_group *uniwill_groups[] = {
+ &uniwill_group,
+ NULL
+};
+
+static umode_t uniwill_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ const struct uniwill_data *data = drvdata;
+ unsigned int feature;
+
+ switch (type) {
+ case hwmon_temp:
+ switch (channel) {
+ case 0:
+ feature = UNIWILL_FEATURE_CPU_TEMP;
+ break;
+ case 1:
+ feature = UNIWILL_FEATURE_GPU_TEMP;
+ break;
+ default:
+ return 0;
+ }
+ break;
+ case hwmon_fan:
+ case hwmon_pwm:
+ switch (channel) {
+ case 0:
+ feature = UNIWILL_FEATURE_PRIMARY_FAN;
+ break;
+ case 1:
+ feature = UNIWILL_FEATURE_SECONDARY_FAN;
+ break;
+ default:
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ if (uniwill_device_supports(data, feature))
+ return 0444;
+
+ return 0;
+}
+
+static int uniwill_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long *val)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ unsigned int value;
+ __be16 rpm;
+ int ret;
+
+ switch (type) {
+ case hwmon_temp:
+ switch (channel) {
+ case 0:
+ ret = regmap_read(data->regmap, EC_ADDR_CPU_TEMP, &value);
+ break;
+ case 1:
+ ret = regmap_read(data->regmap, EC_ADDR_GPU_TEMP, &value);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ *val = value * MILLIDEGREE_PER_DEGREE;
+ return 0;
+ case hwmon_fan:
+ switch (channel) {
+ case 0:
+ ret = regmap_bulk_read(data->regmap, EC_ADDR_MAIN_FAN_RPM_1, &rpm,
+ sizeof(rpm));
+ break;
+ case 1:
+ ret = regmap_bulk_read(data->regmap, EC_ADDR_SECOND_FAN_RPM_1, &rpm,
+ sizeof(rpm));
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ *val = be16_to_cpu(rpm);
+ return 0;
+ case hwmon_pwm:
+ switch (channel) {
+ case 0:
+ ret = regmap_read(data->regmap, EC_ADDR_PWM_1, &value);
+ break;
+ case 1:
+ ret = regmap_read(data->regmap, EC_ADDR_PWM_2, &value);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ *val = fixp_linear_interpolate(0, 0, PWM_MAX, U8_MAX, value);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int uniwill_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_temp:
+ *str = uniwill_temp_labels[channel];
+ return 0;
+ case hwmon_fan:
+ *str = uniwill_fan_labels[channel];
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_ops uniwill_ops = {
+ .is_visible = uniwill_is_visible,
+ .read = uniwill_read,
+ .read_string = uniwill_read_string,
+};
+
+static const struct hwmon_channel_info * const uniwill_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_INPUT | HWMON_F_LABEL,
+ HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT,
+ HWMON_PWM_INPUT),
+ NULL
+};
+
+static const struct hwmon_chip_info uniwill_chip_info = {
+ .ops = &uniwill_ops,
+ .info = uniwill_info,
+};
+
+static int uniwill_hwmon_init(struct uniwill_data *data)
+{
+ struct device *hdev;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_CPU_TEMP) &&
+ !uniwill_device_supports(data, UNIWILL_FEATURE_GPU_TEMP) &&
+ !uniwill_device_supports(data, UNIWILL_FEATURE_PRIMARY_FAN) &&
+ !uniwill_device_supports(data, UNIWILL_FEATURE_SECONDARY_FAN))
+ return 0;
+
+ hdev = devm_hwmon_device_register_with_info(data->dev, "uniwill", data,
+ &uniwill_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(hdev);
+}
+
+static const unsigned int uniwill_led_channel_to_bat_reg[LED_CHANNELS] = {
+ EC_ADDR_LIGHTBAR_BAT_RED,
+ EC_ADDR_LIGHTBAR_BAT_GREEN,
+ EC_ADDR_LIGHTBAR_BAT_BLUE,
+};
+
+static const unsigned int uniwill_led_channel_to_ac_reg[LED_CHANNELS] = {
+ EC_ADDR_LIGHTBAR_AC_RED,
+ EC_ADDR_LIGHTBAR_AC_GREEN,
+ EC_ADDR_LIGHTBAR_AC_BLUE,
+};
+
+static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness)
+{
+ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, led_mc_cdev);
+ unsigned int value;
+ int ret;
+
+ ret = led_mc_calc_color_components(led_mc_cdev, brightness);
+ if (ret < 0)
+ return ret;
+
+ guard(mutex)(&data->led_lock);
+
+ for (int i = 0; i < LED_CHANNELS; i++) {
+ /* Prevent the brightness values from overflowing */
+ value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness);
+ ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (brightness)
+ value = 0;
+ else
+ value = LIGHTBAR_S0_OFF;
+
+ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, LIGHTBAR_S0_OFF, value);
+ if (ret < 0)
+ return ret;
+
+ return regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_S0_OFF, value);
+}
+
+#define LIGHTBAR_MASK (LIGHTBAR_APP_EXISTS | LIGHTBAR_S0_OFF | LIGHTBAR_S3_OFF | LIGHTBAR_WELCOME)
+
+static int uniwill_led_init(struct uniwill_data *data)
+{
+ struct led_init_data init_data = {
+ .devicename = DRIVER_NAME,
+ .default_label = "multicolor:" LED_FUNCTION_STATUS,
+ .devname_mandatory = true,
+ };
+ unsigned int color_indices[3] = {
+ LED_COLOR_ID_RED,
+ LED_COLOR_ID_GREEN,
+ LED_COLOR_ID_BLUE,
+ };
+ unsigned int value;
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_LIGHTBAR))
+ return 0;
+
+ ret = devm_mutex_init(data->dev, &data->led_lock);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The EC has separate lightbar settings for AC and battery mode,
+ * so we have to ensure that both settings are the same.
+ */
+ ret = regmap_read(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, &value);
+ if (ret < 0)
+ return ret;
+
+ value |= LIGHTBAR_APP_EXISTS;
+ ret = regmap_write(data->regmap, EC_ADDR_LIGHTBAR_AC_CTRL, value);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The breathing animation during suspend is not supported when
+ * running on battery power.
+ */
+ value |= LIGHTBAR_S3_OFF;
+ ret = regmap_update_bits(data->regmap, EC_ADDR_LIGHTBAR_BAT_CTRL, LIGHTBAR_MASK, value);
+ if (ret < 0)
+ return ret;
+
+ data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI;
+ data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS;
+ data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT;
+ data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set;
+
+ if (value & LIGHTBAR_S0_OFF)
+ data->led_mc_cdev.led_cdev.brightness = 0;
+ else
+ data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS;
+
+ for (int i = 0; i < LED_CHANNELS; i++) {
+ data->led_mc_subled_info[i].color_index = color_indices[i];
+
+ ret = regmap_read(data->regmap, uniwill_led_channel_to_ac_reg[i], &value);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Make sure that the initial intensity value is not greater than
+ * the maximum brightness.
+ */
+ value = min(LED_MAX_BRIGHTNESS, value);
+ ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(data->regmap, uniwill_led_channel_to_bat_reg[i], value);
+ if (ret < 0)
+ return ret;
+
+ data->led_mc_subled_info[i].intensity = value;
+ data->led_mc_subled_info[i].channel = i;
+ }
+
+ data->led_mc_cdev.subled_info = data->led_mc_subled_info;
+ data->led_mc_cdev.num_colors = LED_CHANNELS;
+
+ return devm_led_classdev_multicolor_register_ext(data->dev, &data->led_mc_cdev,
+ &init_data);
+}
+
+static unsigned int uniwill_sanitize_battery_threshold(unsigned int value)
+{
+ /* 0 means "charging threshold not active" */
+ if (!value)
+ return 100;
+
+ /* Guard against invalid values */
+ return min(value, 100);
+}
+
+static int uniwill_get_property(struct power_supply *psy, const struct power_supply_ext *ext,
+ void *drvdata, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct uniwill_data *data = drvdata;
+ union power_supply_propval prop;
+ unsigned int regval;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_PRESENT, &prop);
+ if (ret < 0)
+ return ret;
+
+ if (!prop.intval) {
+ val->intval = POWER_SUPPLY_HEALTH_NO_BATTERY;
+ return 0;
+ }
+
+ ret = power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_STATUS, &prop);
+ if (ret < 0)
+ return ret;
+
+ if (prop.intval == POWER_SUPPLY_STATUS_UNKNOWN) {
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ return 0;
+ }
+
+ ret = regmap_read(data->regmap, EC_ADDR_BAT_ALERT, &regval);
+ if (ret < 0)
+ return ret;
+
+ if (regval) {
+ /* Charging issue */
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval = FIELD_GET(CHARGE_CTRL_MASK, regval);
+ val->intval = uniwill_sanitize_battery_threshold(regval);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int uniwill_set_property(struct power_supply *psy, const struct power_supply_ext *ext,
+ void *drvdata, enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct uniwill_data *data = drvdata;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ if (val->intval < 0 || val->intval > 100)
+ return -EINVAL;
+
+ return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
+ max(val->intval, 1));
+ default:
+ return -EINVAL;
+ }
+}
+
+static int uniwill_property_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext, void *drvdata,
+ enum power_supply_property psp)
+{
+ if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD)
+ return true;
+
+ return false;
+}
+
+static const enum power_supply_property uniwill_properties[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_ext uniwill_extension = {
+ .name = DRIVER_NAME,
+ .properties = uniwill_properties,
+ .num_properties = ARRAY_SIZE(uniwill_properties),
+ .get_property = uniwill_get_property,
+ .set_property = uniwill_set_property,
+ .property_is_writeable = uniwill_property_is_writeable,
+};
+
+static int uniwill_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ struct uniwill_data *data = container_of(hook, struct uniwill_data, hook);
+ struct uniwill_battery_entry *entry;
+ int ret;
+
+ entry = kzalloc_obj(*entry);
+ if (!entry)
+ return -ENOMEM;
+
+ ret = power_supply_register_extension(battery, &uniwill_extension, data->dev, data);
+ if (ret < 0) {
+ kfree(entry);
+ return ret;
+ }
+
+ guard(mutex)(&data->battery_lock);
+
+ entry->battery = battery;
+ list_add(&entry->head, &data->batteries);
+
+ return 0;
+}
+
+static int uniwill_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ struct uniwill_data *data = container_of(hook, struct uniwill_data, hook);
+ struct uniwill_battery_entry *entry, *tmp;
+
+ scoped_guard(mutex, &data->battery_lock) {
+ list_for_each_entry_safe(entry, tmp, &data->batteries, head) {
+ if (entry->battery == battery) {
+ list_del(&entry->head);
+ kfree(entry);
+ break;
+ }
+ }
+ }
+
+ power_supply_unregister_extension(battery, &uniwill_extension);
+
+ return 0;
+}
+
+static int uniwill_battery_init(struct uniwill_data *data)
+{
+ unsigned int value, threshold, sanitized;
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
+ return 0;
+
+ ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The charge control threshold might be initialized with 0 by
+ * the EC to signal that said threshold is uninitialized. We thus
+ * need to replace this placeholder value with a valid one (100)
+ * to signal that we want to take control of battery charging.
+ * For the sake of completeness we also apply this to other
+ * invalid threshold values.
+ */
+ threshold = FIELD_GET(CHARGE_CTRL_MASK, value);
+ sanitized = uniwill_sanitize_battery_threshold(threshold);
+ if (threshold != sanitized) {
+ FIELD_MODIFY(CHARGE_CTRL_MASK, &value, sanitized);
+ ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL, value);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = devm_mutex_init(data->dev, &data->battery_lock);
+ if (ret < 0)
+ return ret;
+
+ INIT_LIST_HEAD(&data->batteries);
+ data->hook.name = "Uniwill Battery Extension";
+ data->hook.add_battery = uniwill_add_battery;
+ data->hook.remove_battery = uniwill_remove_battery;
+
+ return devm_battery_hook_register(data->dev, &data->hook);
+}
+
+static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy)
+{
+ struct uniwill_data *data = container_of(nb, struct uniwill_data, nb);
+ struct uniwill_battery_entry *entry;
+
+ switch (action) {
+ case UNIWILL_OSD_BATTERY_ALERT:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
+ return NOTIFY_DONE;
+
+ mutex_lock(&data->battery_lock);
+ list_for_each_entry(entry, &data->batteries, head) {
+ power_supply_changed(entry->battery);
+ }
+ mutex_unlock(&data->battery_lock);
+
+ return NOTIFY_OK;
+ case UNIWILL_OSD_DC_ADAPTER_CHANGED:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
+ return NOTIFY_DONE;
+
+ return notifier_from_errno(usb_c_power_priority_restore(data));
+ case UNIWILL_OSD_FN_LOCK:
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK))
+ return NOTIFY_DONE;
+
+ sysfs_notify(&data->dev->kobj, NULL, "fn_lock");
+
+ return NOTIFY_OK;
+ default:
+ mutex_lock(&data->input_lock);
+ sparse_keymap_report_event(data->input_device, action, 1, true);
+ mutex_unlock(&data->input_lock);
+
+ return NOTIFY_OK;
+ }
+}
+
+static int uniwill_input_init(struct uniwill_data *data)
+{
+ int ret;
+
+ ret = devm_mutex_init(data->dev, &data->input_lock);
+ if (ret < 0)
+ return ret;
+
+ data->input_device = devm_input_allocate_device(data->dev);
+ if (!data->input_device)
+ return -ENOMEM;
+
+ ret = sparse_keymap_setup(data->input_device, uniwill_keymap, NULL);
+ if (ret < 0)
+ return ret;
+
+ data->input_device->name = "Uniwill WMI hotkeys";
+ data->input_device->phys = "wmi/input0";
+ data->input_device->id.bustype = BUS_HOST;
+ ret = input_register_device(data->input_device);
+ if (ret < 0)
+ return ret;
+
+ data->nb.notifier_call = uniwill_notifier_call;
+
+ return devm_uniwill_wmi_register_notifier(data->dev, &data->nb);
+}
+
+static void uniwill_disable_manual_control(void *context)
+{
+ struct uniwill_data *data = context;
+
+ regmap_clear_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL);
+}
+
+static int uniwill_ec_init(struct uniwill_data *data)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_PROJECT_ID, &value);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(data->dev, "Project ID: %u\n", value);
+
+ ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(data->dev, uniwill_disable_manual_control, data);
+}
+
+static int uniwill_probe(struct platform_device *pdev)
+{
+ struct uniwill_data *data;
+ struct regmap *regmap;
+ acpi_handle handle;
+ int ret;
+
+ handle = ACPI_HANDLE(&pdev->dev);
+ if (!handle)
+ return -ENODEV;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = &pdev->dev;
+ data->handle = handle;
+ platform_set_drvdata(pdev, data);
+
+ regmap = devm_regmap_init(&pdev->dev, &uniwill_ec_bus, data, &uniwill_ec_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ data->regmap = regmap;
+
+ ret = devm_mutex_init(&pdev->dev, &data->super_key_lock);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_ec_init(data);
+ if (ret < 0)
+ return ret;
+
+ data->features = device_descriptor.features;
+
+ /*
+ * Some devices might need to perform some device-specific initialization steps
+ * before the supported features are initialized. Because of this we have to call
+ * this callback just after the EC itself was initialized.
+ */
+ if (device_descriptor.probe) {
+ ret = device_descriptor.probe(data);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = uniwill_battery_init(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_led_init(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_hwmon_init(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_nvidia_ctgp_init(data);
+ if (ret < 0)
+ return ret;
+
+ ret = usb_c_power_priority_init(data);
+ if (ret < 0)
+ return ret;
+
+ return uniwill_input_init(data);
+}
+
+static void uniwill_shutdown(struct platform_device *pdev)
+{
+ struct uniwill_data *data = platform_get_drvdata(pdev);
+
+ regmap_clear_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL);
+}
+
+static int uniwill_suspend_fn_lock(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK))
+ return 0;
+
+ /*
+ * The EC_ADDR_BIOS_OEM is marked as volatile, so we have to restore it
+ * ourselves.
+ */
+ return regmap_read(data->regmap, EC_ADDR_BIOS_OEM, &data->last_status);
+}
+
+static int uniwill_suspend_super_key(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY))
+ return 0;
+
+ /*
+ * The EC_ADDR_SWITCH_STATUS is marked as volatile, so we have to restore it
+ * ourselves.
+ */
+ return regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &data->last_switch_status);
+}
+
+static int uniwill_suspend_battery(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
+ return 0;
+
+ /*
+ * Save the current charge limit in order to restore it during resume.
+ * We cannot use the regmap code for that since this register needs to
+ * be declared as volatile due to CHARGE_CTRL_REACHED.
+ */
+ return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl);
+}
+
+static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ return regmap_clear_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+}
+
+static int uniwill_suspend(struct device *dev)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = uniwill_suspend_fn_lock(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_suspend_super_key(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_suspend_battery(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_suspend_nvidia_ctgp(data);
+ if (ret < 0)
+ return ret;
+
+ regcache_cache_only(data->regmap, true);
+ regcache_mark_dirty(data->regmap);
+
+ return 0;
+}
+
+static int uniwill_resume_fn_lock(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_FN_LOCK))
+ return 0;
+
+ return regmap_update_bits(data->regmap, EC_ADDR_BIOS_OEM, FN_LOCK_STATUS,
+ data->last_status);
+}
+
+static int uniwill_resume_super_key(struct uniwill_data *data)
+{
+ unsigned int value;
+ int ret;
+
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_SUPER_KEY))
+ return 0;
+
+ ret = regmap_read(data->regmap, EC_ADDR_SWITCH_STATUS, &value);
+ if (ret < 0)
+ return ret;
+
+ if ((data->last_switch_status & SUPER_KEY_LOCK_STATUS) == (value & SUPER_KEY_LOCK_STATUS))
+ return 0;
+
+ return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_SUPER_KEY_LOCK,
+ TRIGGER_SUPER_KEY_LOCK);
+}
+
+static int uniwill_resume_battery(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY))
+ return 0;
+
+ return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK,
+ data->last_charge_ctrl);
+}
+
+static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL))
+ return 0;
+
+ return regmap_set_bits(data->regmap, EC_ADDR_CTGP_DB_CTRL,
+ CTGP_DB_DB_ENABLE | CTGP_DB_CTGP_ENABLE);
+}
+
+static int uniwill_resume_usb_c_power_priority(struct uniwill_data *data)
+{
+ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_C_POWER_PRIORITY))
+ return 0;
+
+ return usb_c_power_priority_restore(data);
+}
+
+static int uniwill_resume(struct device *dev)
+{
+ struct uniwill_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ regcache_cache_only(data->regmap, false);
+
+ ret = regcache_sync(data->regmap);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_resume_fn_lock(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_resume_super_key(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_resume_battery(data);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_resume_nvidia_ctgp(data);
+ if (ret < 0)
+ return ret;
+
+ return uniwill_resume_usb_c_power_priority(data);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(uniwill_pm_ops, uniwill_suspend, uniwill_resume);
+
+/*
+ * We only use the DMI table for auoloading because the ACPI device itself
+ * does not guarantee that the underlying EC implementation is supported.
+ */
+static const struct acpi_device_id uniwill_id_table[] = {
+ { "INOU0000" },
+ { },
+};
+
+static struct platform_driver uniwill_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .dev_groups = uniwill_groups,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .acpi_match_table = uniwill_id_table,
+ .pm = pm_sleep_ptr(&uniwill_pm_ops),
+ },
+ .probe = uniwill_probe,
+ .shutdown = uniwill_shutdown,
+};
+
+static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_BATTERY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN,
+};
+
+static struct uniwill_device_descriptor lapac71h_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
+ UNIWILL_FEATURE_BATTERY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN,
+};
+
+static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_TOUCHPAD_TOGGLE |
+ UNIWILL_FEATURE_LIGHTBAR |
+ UNIWILL_FEATURE_BATTERY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN,
+};
+
+/*
+ * The featuresets below reflect somewhat chronological changes:
+ * 1 -> 2: UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL is added to the EC firmware.
+ * 2 -> 3: UNIWILL_FEATURE_USB_C_POWER_PRIORITY is removed from the EC firmware.
+ * Some devices might divert from this timeline.
+ */
+
+static struct uniwill_device_descriptor tux_featureset_1_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
+};
+
+static struct uniwill_device_descriptor tux_featureset_1_nvidia_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
+};
+
+static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
+};
+
+static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN,
+};
+
+static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL,
+};
+
+static int phxtxx1_probe(struct uniwill_data *data)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_PROJECT_ID, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value == PROJECT_ID_PH4TRX1 || value == PROJECT_ID_PH6TRX1)
+ data->features |= UNIWILL_FEATURE_SECONDARY_FAN;
+
+ return 0;
+};
+
+static struct uniwill_device_descriptor phxtxx1_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
+ .probe = phxtxx1_probe,
+};
+
+static int phxarx1_phxaqf1_probe(struct uniwill_data *data)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, EC_ADDR_SYSTEM_ID, &value);
+ if (ret < 0)
+ return ret;
+
+ if (value & HAS_GPU)
+ data->features |= UNIWILL_FEATURE_GPU_TEMP |
+ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL;
+
+ return 0;
+};
+
+static struct uniwill_device_descriptor phxarx1_phxaqf1_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN |
+ UNIWILL_FEATURE_SECONDARY_FAN |
+ UNIWILL_FEATURE_USB_C_POWER_PRIORITY,
+ .probe = phxarx1_phxaqf1_probe,
+};
+
+static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = {
+ .features = UNIWILL_FEATURE_FN_LOCK |
+ UNIWILL_FEATURE_SUPER_KEY |
+ UNIWILL_FEATURE_CPU_TEMP |
+ UNIWILL_FEATURE_PRIMARY_FAN,
+};
+
+static const struct dmi_system_id uniwill_dmi_table[] __initconst = {
+ {
+ .ident = "XMG FUSION 15 (L19)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"),
+ },
+ .driver_data = &lapqc71a_lapqc71b_descriptor,
+ },
+ {
+ .ident = "XMG FUSION 15 (L19)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"),
+ },
+ .driver_data = &lapqc71a_lapqc71b_descriptor,
+ },
+ {
+ .ident = "XMG FUSION 15 (L19)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71A"),
+ },
+ .driver_data = &lapqc71a_lapqc71b_descriptor,
+ },
+ {
+ .ident = "XMG FUSION 15 (L19)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "LAPQC71B"),
+ },
+ .driver_data = &lapqc71a_lapqc71b_descriptor,
+ },
+ {
+ .ident = "Intel NUC x15",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPAC71H"),
+ },
+ .driver_data = &lapac71h_descriptor,
+ },
+ {
+ .ident = "Intel NUC x15",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "LAPKC71F"),
+ },
+ .driver_data = &lapkc71f_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTxX1"),
+ },
+ .driver_data = &phxtxx1_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14 Gen6 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxTQx1"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14/16 Gen7 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PHxARX1_PHxAQF1"),
+ },
+ .driver_data = &phxarx1_phxaqf1_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 16 Gen7 Intel/Commodore Omnia-Book Pro Gen 7",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6AG01_PH6AQ71_PH6AQI1"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14/16 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PRX1_PH6PRX1"),
+ },
+ .driver_data = &tux_featureset_1_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14 Gen8 Intel/Commodore Omnia-Book Pro Gen 8",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH4PG31"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 16 Gen8 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PH6PG01_PH6PG71"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"),
+ },
+ .driver_data = &tux_featureset_3_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"),
+ },
+ .driver_data = &tux_featureset_3_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"),
+ },
+ .driver_data = &tux_featureset_3_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"),
+ },
+ .driver_data = &tux_featureset_3_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"),
+ },
+ .driver_data = &tux_featureset_3_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15 Gen1 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A1650TI"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15 Gen1 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501A2060"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 17 Gen1 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A1650TI"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 17 Gen1 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701A2060"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15 Gen1 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I1650TI"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15 Gen1 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1501I2060"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 17 Gen1 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I1650TI"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 17 Gen1 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "POLARIS1701I2060"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Trinity 15 Intel Gen1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1501I"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Trinity 17 Intel Gen1",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "TRINITY1701I"),
+ },
+ .driver_data = &tux_featureset_1_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15/17 Gen2 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxMGxx"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15/17 Gen2 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxNGxx"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxZGxx"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris/Polaris 15/17 Gen3 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxTGxx"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris/Polaris 15/17 Gen4 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxRGxx"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 15 Gen4 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxAGxx"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Polaris 15/17 Gen5 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxXGxx"),
+ },
+ .driver_data = &tux_featureset_2_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 16 Gen5 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6XGxX"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 16/17 Gen5 Intel/Commodore ORION Gen 5",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxPXxx"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris Slim 15 Gen6 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 16 Gen7 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 16 Gen7 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Stellaris 16 Gen7 Intel",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"),
+ },
+ .driver_data = &tux_featureset_3_nvidia_descriptor,
+ },
+ {
+ .ident = "TUXEDO Book BA15 Gen10 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5PU1G"),
+ },
+ .driver_data = &pf5pu1g_descriptor,
+ },
+ {
+ .ident = "TUXEDO Pulse 14 Gen1 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1401"),
+ },
+ .driver_data = &tux_featureset_1_descriptor,
+ },
+ {
+ .ident = "TUXEDO Pulse 15 Gen1 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PULSE1501"),
+ },
+ .driver_data = &tux_featureset_1_descriptor,
+ },
+ {
+ .ident = "TUXEDO Pulse 15 Gen2 AMD",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "PF5LUXG"),
+ },
+ .driver_data = &tux_featureset_1_descriptor,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(dmi, uniwill_dmi_table);
+
+static int __init uniwill_init(void)
+{
+ const struct uniwill_device_descriptor *descriptor;
+ const struct dmi_system_id *id;
+ int ret;
+
+ id = dmi_first_match(uniwill_dmi_table);
+ if (!id) {
+ if (!force)
+ return -ENODEV;
+
+ pr_warn("Loading on a potentially unsupported device\n");
+ } else {
+ /*
+ * Some devices might support additional features depending on
+ * the BIOS version/date, so we call this callback to let them
+ * modify their device descriptor accordingly.
+ */
+ if (id->callback) {
+ ret = id->callback(id);
+ if (ret < 0)
+ return ret;
+ }
+
+ descriptor = id->driver_data;
+ device_descriptor = *descriptor;
+ }
+
+ if (force) {
+ /* Assume that the device supports all features except the charge limit */
+ device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY;
+ pr_warn("Enabling potentially unsupported features\n");
+ }
+
+ ret = platform_driver_register(&uniwill_driver);
+ if (ret < 0)
+ return ret;
+
+ ret = uniwill_wmi_register_driver();
+ if (ret < 0) {
+ platform_driver_unregister(&uniwill_driver);
+ return ret;
+ }
+
+ return 0;
+}
+module_init(uniwill_init);
+
+static void __exit uniwill_exit(void)
+{
+ uniwill_wmi_unregister_driver();
+ platform_driver_unregister(&uniwill_driver);
+}
+module_exit(uniwill_exit);
+
+MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");
+MODULE_DESCRIPTION("Uniwill notebook driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.c b/drivers/platform/x86/uniwill/uniwill-wmi.c
new file mode 100644
index 000000000000..097882f10b1e
--- /dev/null
+++ b/drivers/platform/x86/uniwill/uniwill-wmi.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Linux hotkey driver for Uniwill notebooks.
+ *
+ * Special thanks go to Pőcze Barnabás, Christoffer Sandberg and Werner Sembach
+ * for supporting the development of this driver either through prior work or
+ * by answering questions regarding the underlying WMI interface.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/mod_devicetable.h>
+#include <linux/notifier.h>
+#include <linux/printk.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "uniwill-wmi.h"
+
+#define DRIVER_NAME "uniwill-wmi"
+#define UNIWILL_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000"
+
+static BLOCKING_NOTIFIER_HEAD(uniwill_wmi_chain_head);
+
+static void devm_uniwill_wmi_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ blocking_notifier_chain_unregister(&uniwill_wmi_chain_head, nb);
+}
+
+int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block *nb)
+{
+ int ret;
+
+ ret = blocking_notifier_chain_register(&uniwill_wmi_chain_head, nb);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_uniwill_wmi_unregister_notifier, nb);
+}
+
+static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
+{
+ u32 value;
+
+ if (obj->type != ACPI_TYPE_INTEGER)
+ return;
+
+ value = obj->integer.value;
+
+ dev_dbg(&wdev->dev, "Received WMI event %u\n", value);
+
+ blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL);
+}
+
+/*
+ * We cannot fully trust this GUID since Uniwill just copied the WMI GUID
+ * from the Windows driver example, and others probably did the same.
+ *
+ * Because of this we cannot use this WMI GUID for autoloading. Instead the
+ * associated driver will be registered manually after matching a DMI table.
+ */
+static const struct wmi_device_id uniwill_wmi_id_table[] = {
+ { UNIWILL_EVENT_GUID, NULL },
+ { }
+};
+
+static struct wmi_driver uniwill_wmi_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = uniwill_wmi_id_table,
+ .min_event_size = sizeof(u32),
+ .notify = uniwill_wmi_notify,
+ .no_singleton = true,
+};
+
+int __init uniwill_wmi_register_driver(void)
+{
+ return wmi_driver_register(&uniwill_wmi_driver);
+}
+
+void __exit uniwill_wmi_unregister_driver(void)
+{
+ wmi_driver_unregister(&uniwill_wmi_driver);
+}
diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h
new file mode 100644
index 000000000000..fb1910c0f741
--- /dev/null
+++ b/drivers/platform/x86/uniwill/uniwill-wmi.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Linux hotkey driver for Uniwill notebooks.
+ *
+ * Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
+ */
+
+#ifndef UNIWILL_WMI_H
+#define UNIWILL_WMI_H
+
+#include <linux/init.h>
+
+#define UNIWILL_OSD_CAPSLOCK 0x01
+#define UNIWILL_OSD_NUMLOCK 0x02
+#define UNIWILL_OSD_SCROLLLOCK 0x03
+
+#define UNIWILL_OSD_TOUCHPAD_ON 0x04
+#define UNIWILL_OSD_TOUCHPAD_OFF 0x05
+
+#define UNIWILL_OSD_SILENT_MODE_ON 0x06
+#define UNIWILL_OSD_SILENT_MODE_OFF 0x07
+
+#define UNIWILL_OSD_WLAN_ON 0x08
+#define UNIWILL_OSD_WLAN_OFF 0x09
+
+#define UNIWILL_OSD_WIMAX_ON 0x0A
+#define UNIWILL_OSD_WIMAX_OFF 0x0B
+
+#define UNIWILL_OSD_BLUETOOTH_ON 0x0C
+#define UNIWILL_OSD_BLUETOOTH_OFF 0x0D
+
+#define UNIWILL_OSD_RF_ON 0x0E
+#define UNIWILL_OSD_RF_OFF 0x0F
+
+#define UNIWILL_OSD_3G_ON 0x10
+#define UNIWILL_OSD_3G_OFF 0x11
+
+#define UNIWILL_OSD_WEBCAM_ON 0x12
+#define UNIWILL_OSD_WEBCAM_OFF 0x13
+
+#define UNIWILL_OSD_BRIGHTNESSUP 0x14
+#define UNIWILL_OSD_BRIGHTNESSDOWN 0x15
+
+#define UNIWILL_OSD_RADIOON 0x1A
+#define UNIWILL_OSD_RADIOOFF 0x1B
+
+#define UNIWILL_OSD_POWERSAVE_ON 0x31
+#define UNIWILL_OSD_POWERSAVE_OFF 0x32
+
+#define UNIWILL_OSD_MENU 0x34
+
+#define UNIWILL_OSD_MUTE 0x35
+#define UNIWILL_OSD_VOLUMEDOWN 0x36
+#define UNIWILL_OSD_VOLUMEUP 0x37
+
+#define UNIWILL_OSD_MENU_2 0x38
+
+#define UNIWILL_OSD_LIGHTBAR_ON 0x39
+#define UNIWILL_OSD_LIGHTBAR_OFF 0x3A
+
+#define UNIWILL_OSD_KB_LED_LEVEL0 0x3B
+#define UNIWILL_OSD_KB_LED_LEVEL1 0x3C
+#define UNIWILL_OSD_KB_LED_LEVEL2 0x3D
+#define UNIWILL_OSD_KB_LED_LEVEL3 0x3E
+#define UNIWILL_OSD_KB_LED_LEVEL4 0x3F
+
+#define UNIWILL_OSD_SUPER_KEY_DISABLE 0x40
+#define UNIWILL_OSD_SUPER_KEY_ENABLE 0x41
+
+#define UNIWILL_OSD_MENU_JP 0x42
+
+#define UNIWILL_OSD_CAMERA_ON 0x90
+#define UNIWILL_OSD_CAMERA_OFF 0x91
+
+#define UNIWILL_OSD_RFKILL 0xA4
+
+#define UNIWILL_OSD_SUPER_KEY_STATE_CHANGED 0xA5
+
+#define UNIWILL_OSD_LIGHTBAR_STATE_CHANGED 0xA6
+
+#define UNIWILL_OSD_FAN_BOOST_STATE_CHANGED 0xA7
+
+#define UNIWILL_OSD_LCD_SW 0xA9
+
+#define UNIWILL_OSD_FAN_OVERTEMP 0xAA
+
+#define UNIWILL_OSD_DC_ADAPTER_CHANGED 0xAB
+
+#define UNIWILL_OSD_BAT_HP_OFF 0xAC
+
+#define UNIWILL_OSD_FAN_DOWN_TEMP 0xAD
+
+#define UNIWILL_OSD_BATTERY_ALERT 0xAE
+
+#define UNIWILL_OSD_TIMAP_HAIERLB_SW 0xAF
+
+#define UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE 0xB0
+
+#define UNIWILL_OSD_KBDILLUMDOWN 0xB1
+#define UNIWILL_OSD_KBDILLUMUP 0xB2
+
+#define UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE 0xB3
+#define UNIWILL_OSD_BACKLIGHT_POWER_CHANGE 0xB4
+
+#define UNIWILL_OSD_MIC_MUTE 0xB7
+
+#define UNIWILL_OSD_FN_LOCK 0xB8
+#define UNIWILL_OSD_KBDILLUMTOGGLE 0xB9
+
+#define UNIWILL_OSD_BAT_CHARGE_FULL_24_H 0xBE
+
+#define UNIWILL_OSD_BAT_ERM_UPDATE 0xBF
+
+#define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0
+
+#define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF
+
+#define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0
+
+struct device;
+struct notifier_block;
+
+int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block *nb);
+
+int __init uniwill_wmi_register_driver(void);
+
+void __exit uniwill_wmi_unregister_driver(void);
+
+#endif /* UNIWILL_WMI_H */
diff --git a/drivers/platform/x86/uv_sysfs.c b/drivers/platform/x86/uv_sysfs.c
index f6a0627f36db..a8c50b323a9e 100644
--- a/drivers/platform/x86/uv_sysfs.c
+++ b/drivers/platform/x86/uv_sysfs.c
@@ -216,8 +216,7 @@ static int uv_hubs_init(void)
u64 sz;
int i, ret;
- prev_obj_to_cnode = kmalloc_array(uv_bios_obj_cnt, sizeof(*prev_obj_to_cnode),
- GFP_KERNEL);
+ prev_obj_to_cnode = kmalloc_objs(*prev_obj_to_cnode, uv_bios_obj_cnt);
if (!prev_obj_to_cnode)
return -ENOMEM;
@@ -242,14 +241,14 @@ static int uv_hubs_init(void)
goto err_enum_objs;
}
- uv_hubs = kcalloc(uv_bios_obj_cnt, sizeof(*uv_hubs), GFP_KERNEL);
+ uv_hubs = kzalloc_objs(*uv_hubs, uv_bios_obj_cnt);
if (!uv_hubs) {
ret = -ENOMEM;
goto err_enum_objs;
}
for (i = 0; i < uv_bios_obj_cnt; i++) {
- uv_hubs[i] = kzalloc(sizeof(*uv_hubs[i]), GFP_KERNEL);
+ uv_hubs[i] = kzalloc_obj(*uv_hubs[i]);
if (!uv_hubs[i]) {
i--;
ret = -ENOMEM;
@@ -368,7 +367,7 @@ static int uv_ports_init(void)
s64 biosr;
int j = 0, k = 0, ret, sz;
- port_buf = kcalloc(uv_bios_obj_cnt, sizeof(*port_buf), GFP_KERNEL);
+ port_buf = kzalloc_objs(*port_buf, uv_bios_obj_cnt);
if (!port_buf)
return -ENOMEM;
@@ -388,8 +387,8 @@ static int uv_ports_init(void)
}
}
for (j = 0; j < uv_bios_obj_cnt; j++) {
- uv_hubs[j]->ports = kcalloc(hub_buf[j].ports,
- sizeof(*uv_hubs[j]->ports), GFP_KERNEL);
+ uv_hubs[j]->ports = kzalloc_objs(*uv_hubs[j]->ports,
+ hub_buf[j].ports);
if (!uv_hubs[j]->ports) {
ret = -ENOMEM;
j--;
@@ -398,7 +397,7 @@ static int uv_ports_init(void)
}
for (j = 0; j < uv_bios_obj_cnt; j++) {
for (k = 0; k < hub_buf[j].ports; k++) {
- uv_hubs[j]->ports[k] = kzalloc(sizeof(*uv_hubs[j]->ports[k]), GFP_KERNEL);
+ uv_hubs[j]->ports[k] = kzalloc_obj(*uv_hubs[j]->ports[k]);
if (!uv_hubs[j]->ports[k]) {
ret = -ENOMEM;
k--;
@@ -674,8 +673,7 @@ static int pci_topology_init(void)
}
num_pci_lines = l;
- uv_pci_objs = kcalloc(num_pci_lines,
- sizeof(*uv_pci_objs), GFP_KERNEL);
+ uv_pci_objs = kzalloc_objs(*uv_pci_objs, num_pci_lines);
if (!uv_pci_objs) {
kfree(pci_top_str);
ret = -ENOMEM;
@@ -683,7 +681,7 @@ static int pci_topology_init(void)
}
start = pci_top_str;
while ((found = strsep(&start, "\n")) != NULL) {
- uv_pci_objs[k] = kzalloc(sizeof(*uv_pci_objs[k]), GFP_KERNEL);
+ uv_pci_objs[k] = kzalloc_obj(*uv_pci_objs[k]);
if (!uv_pci_objs[k]) {
ret = -ENOMEM;
goto err_pci_obj;
diff --git a/drivers/platform/x86/wireless-hotkey.c b/drivers/platform/x86/wireless-hotkey.c
index a220fe4f9ef8..3151844d1699 100644
--- a/drivers/platform/x86/wireless-hotkey.c
+++ b/drivers/platform/x86/wireless-hotkey.c
@@ -35,16 +35,17 @@ static const struct acpi_device_id wl_ids[] = {
{"", 0},
};
-static int wireless_input_setup(struct acpi_device *device)
+static int wireless_input_setup(struct device *dev)
{
- struct wl_button *button = acpi_driver_data(device);
+ struct wl_button *button = dev_get_drvdata(dev);
int err;
button->input_dev = input_allocate_device();
if (!button->input_dev)
return -ENOMEM;
- snprintf(button->phys, sizeof(button->phys), "%s/input0", acpi_device_hid(device));
+ snprintf(button->phys, sizeof(button->phys), "%s/input0",
+ acpi_device_hid(ACPI_COMPANION(dev)));
button->input_dev->name = "Wireless hotkeys";
button->input_dev->phys = button->phys;
@@ -63,17 +64,17 @@ err_free_dev:
return err;
}
-static void wireless_input_destroy(struct acpi_device *device)
+static void wireless_input_destroy(struct device *dev)
{
- struct wl_button *button = acpi_driver_data(device);
+ struct wl_button *button = dev_get_drvdata(dev);
input_unregister_device(button->input_dev);
kfree(button);
}
-static void wl_notify(struct acpi_device *acpi_dev, u32 event)
+static void wl_notify(acpi_handle handle, u32 event, void *data)
{
- struct wl_button *button = acpi_driver_data(acpi_dev);
+ struct wl_button *button = data;
if (event != 0x80) {
pr_info("Received unknown event (0x%x)\n", event);
@@ -86,39 +87,52 @@ static void wl_notify(struct acpi_device *acpi_dev, u32 event)
input_sync(button->input_dev);
}
-static int wl_add(struct acpi_device *device)
+static int wl_probe(struct platform_device *pdev)
{
+ struct acpi_device *adev;
struct wl_button *button;
int err;
- button = kzalloc(sizeof(struct wl_button), GFP_KERNEL);
+ adev = ACPI_COMPANION(&pdev->dev);
+ if (!adev)
+ return -ENODEV;
+
+ button = kzalloc_obj(struct wl_button);
if (!button)
return -ENOMEM;
- device->driver_data = button;
+ platform_set_drvdata(pdev, button);
- err = wireless_input_setup(device);
+ err = wireless_input_setup(&pdev->dev);
if (err) {
pr_err("Failed to setup wireless hotkeys\n");
kfree(button);
+ return err;
+ }
+ err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
+ wl_notify, button);
+ if (err) {
+ pr_err("Failed to install ACPI notify handler\n");
+ wireless_input_destroy(&pdev->dev);
}
return err;
}
-static void wl_remove(struct acpi_device *device)
+static void wl_remove(struct platform_device *pdev)
{
- wireless_input_destroy(device);
+ acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
+ ACPI_DEVICE_NOTIFY, wl_notify);
+ wireless_input_destroy(&pdev->dev);
}
-static struct acpi_driver wl_driver = {
- .name = "wireless-hotkey",
- .ids = wl_ids,
- .ops = {
- .add = wl_add,
- .remove = wl_remove,
- .notify = wl_notify,
+static struct platform_driver wl_driver = {
+ .probe = wl_probe,
+ .remove = wl_remove,
+ .driver = {
+ .name = "wireless-hotkey",
+ .acpi_match_table = wl_ids,
},
};
-module_acpi_driver(wl_driver);
+module_platform_driver(wl_driver);
diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c
index 3e33da36da8a..6623cf60e4b4 100644
--- a/drivers/platform/x86/wmi-bmof.c
+++ b/drivers/platform/x86/wmi-bmof.c
@@ -8,7 +8,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/kernel.h>
@@ -24,9 +23,9 @@ static ssize_t bmof_read(struct file *filp, struct kobject *kobj, const struct b
char *buf, loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
- union acpi_object *obj = dev_get_drvdata(dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(dev);
- return memory_read_from_buffer(buf, count, &off, obj->buffer.pointer, obj->buffer.length);
+ return memory_read_from_buffer(buf, count, &off, buffer->data, buffer->length);
}
static const BIN_ATTR_ADMIN_RO(bmof, 0);
@@ -39,14 +38,14 @@ static const struct bin_attribute * const bmof_attrs[] = {
static size_t bmof_bin_size(struct kobject *kobj, const struct bin_attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
- union acpi_object *obj = dev_get_drvdata(dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(dev);
- return obj->buffer.length;
+ return buffer->length;
}
static const struct attribute_group bmof_group = {
.bin_size = bmof_bin_size,
- .bin_attrs_new = bmof_attrs,
+ .bin_attrs = bmof_attrs,
};
static const struct attribute_group *bmof_groups[] = {
@@ -56,30 +55,27 @@ static const struct attribute_group *bmof_groups[] = {
static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
{
- union acpi_object *obj;
+ struct wmi_buffer *buffer;
+ int ret;
- obj = wmidev_block_query(wdev, 0);
- if (!obj) {
- dev_err(&wdev->dev, "failed to read Binary MOF\n");
- return -EIO;
- }
+ buffer = devm_kzalloc(&wdev->dev, sizeof(*buffer), GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
- if (obj->type != ACPI_TYPE_BUFFER) {
- dev_err(&wdev->dev, "Binary MOF is not a buffer\n");
- kfree(obj);
- return -EIO;
- }
+ ret = wmidev_query_block(wdev, 0, buffer, 0);
+ if (ret < 0)
+ return ret;
- dev_set_drvdata(&wdev->dev, obj);
+ dev_set_drvdata(&wdev->dev, buffer);
return 0;
}
static void wmi_bmof_remove(struct wmi_device *wdev)
{
- union acpi_object *obj = dev_get_drvdata(&wdev->dev);
+ struct wmi_buffer *buffer = dev_get_drvdata(&wdev->dev);
- kfree(obj);
+ kfree(buffer->data);
}
static const struct wmi_device_id wmi_bmof_id_table[] = {
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
deleted file mode 100644
index e46453750d5f..000000000000
--- a/drivers/platform/x86/wmi.c
+++ /dev/null
@@ -1,1390 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * ACPI-WMI mapping driver
- *
- * Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk>
- *
- * GUID parsing code from ldm.c is:
- * Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org>
- * Copyright (c) 2001-2007 Anton Altaparmakov
- * Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com>
- *
- * WMI bus infrastructure by Andrew Lutomirski and Darren Hart:
- * Copyright (C) 2015 Andrew Lutomirski
- * Copyright (C) 2017 VMware, Inc. All Rights Reserved.
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/acpi.h>
-#include <linux/bits.h>
-#include <linux/build_bug.h>
-#include <linux/device.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/rwsem.h>
-#include <linux/slab.h>
-#include <linux/sysfs.h>
-#include <linux/types.h>
-#include <linux/uuid.h>
-#include <linux/wmi.h>
-#include <linux/fs.h>
-
-MODULE_AUTHOR("Carlos Corbacho");
-MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
-MODULE_LICENSE("GPL");
-
-struct guid_block {
- guid_t guid;
- union {
- char object_id[2];
- struct {
- unsigned char notify_id;
- unsigned char reserved;
- };
- };
- u8 instance_count;
- u8 flags;
-} __packed;
-static_assert(sizeof(typeof_member(struct guid_block, guid)) == 16);
-static_assert(sizeof(struct guid_block) == 20);
-static_assert(__alignof__(struct guid_block) == 1);
-
-enum { /* wmi_block flags */
- WMI_READ_TAKES_NO_ARGS,
- WMI_GUID_DUPLICATED,
- WMI_NO_EVENT_DATA,
-};
-
-struct wmi_block {
- struct wmi_device dev;
- struct guid_block gblock;
- struct acpi_device *acpi_device;
- struct rw_semaphore notify_lock; /* Protects notify callback add/remove */
- wmi_notify_handler handler;
- void *handler_data;
- bool driver_ready;
- unsigned long flags;
-};
-
-struct wmi_guid_count_context {
- const guid_t *guid;
- int count;
-};
-
-/*
- * If the GUID data block is marked as expensive, we must enable and
- * explicitily disable data collection.
- */
-#define ACPI_WMI_EXPENSIVE BIT(0)
-#define ACPI_WMI_METHOD BIT(1) /* GUID is a method */
-#define ACPI_WMI_STRING BIT(2) /* GUID takes & returns a string */
-#define ACPI_WMI_EVENT BIT(3) /* GUID is an event */
-
-static const struct acpi_device_id wmi_device_ids[] = {
- {"PNP0C14", 0},
- {"pnp0c14", 0},
- { }
-};
-MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
-
-#define dev_to_wblock(__dev) container_of_const(__dev, struct wmi_block, dev.dev)
-
-/*
- * GUID parsing functions
- */
-
-static bool guid_parse_and_compare(const char *string, const guid_t *guid)
-{
- guid_t guid_input;
-
- if (guid_parse(string, &guid_input))
- return false;
-
- return guid_equal(&guid_input, guid);
-}
-
-static const void *find_guid_context(struct wmi_block *wblock,
- struct wmi_driver *wdriver)
-{
- const struct wmi_device_id *id;
-
- id = wdriver->id_table;
- if (!id)
- return NULL;
-
- while (*id->guid_string) {
- if (guid_parse_and_compare(id->guid_string, &wblock->gblock.guid))
- return id->context;
- id++;
- }
- return NULL;
-}
-
-#define WMI_ACPI_METHOD_NAME_SIZE 5
-
-static inline void get_acpi_method_name(const struct wmi_block *wblock,
- const char method,
- char buffer[static WMI_ACPI_METHOD_NAME_SIZE])
-{
- static_assert(ARRAY_SIZE(wblock->gblock.object_id) == 2);
- static_assert(WMI_ACPI_METHOD_NAME_SIZE >= 5);
-
- buffer[0] = 'W';
- buffer[1] = method;
- buffer[2] = wblock->gblock.object_id[0];
- buffer[3] = wblock->gblock.object_id[1];
- buffer[4] = '\0';
-}
-
-static inline acpi_object_type get_param_acpi_type(const struct wmi_block *wblock)
-{
- if (wblock->gblock.flags & ACPI_WMI_STRING)
- return ACPI_TYPE_STRING;
- else
- return ACPI_TYPE_BUFFER;
-}
-
-static int wmidev_match_guid(struct device *dev, const void *data)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
- const guid_t *guid = data;
-
- /* Legacy GUID-based functions are restricted to only see
- * a single WMI device for each GUID.
- */
- if (test_bit(WMI_GUID_DUPLICATED, &wblock->flags))
- return 0;
-
- if (guid_equal(guid, &wblock->gblock.guid))
- return 1;
-
- return 0;
-}
-
-static const struct bus_type wmi_bus_type;
-
-static const struct device_type wmi_type_event;
-
-static const struct device_type wmi_type_method;
-
-static int wmi_device_enable(struct wmi_device *wdev, bool enable)
-{
- struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
- char method[WMI_ACPI_METHOD_NAME_SIZE];
- acpi_handle handle;
- acpi_status status;
-
- if (!(wblock->gblock.flags & ACPI_WMI_EXPENSIVE))
- return 0;
-
- if (wblock->dev.dev.type == &wmi_type_method)
- return 0;
-
- if (wblock->dev.dev.type == &wmi_type_event)
- snprintf(method, sizeof(method), "WE%02X", wblock->gblock.notify_id);
- else
- get_acpi_method_name(wblock, 'C', method);
-
- /*
- * Not all WMI devices marked as expensive actually implement the
- * necessary ACPI method. Ignore this missing ACPI method to match
- * the behaviour of the Windows driver.
- */
- status = acpi_get_handle(wblock->acpi_device->handle, method, &handle);
- if (ACPI_FAILURE(status))
- return 0;
-
- status = acpi_execute_simple_method(handle, NULL, enable);
- if (ACPI_FAILURE(status))
- return -EIO;
-
- return 0;
-}
-
-static struct wmi_device *wmi_find_device_by_guid(const char *guid_string)
-{
- struct device *dev;
- guid_t guid;
- int ret;
-
- ret = guid_parse(guid_string, &guid);
- if (ret < 0)
- return ERR_PTR(ret);
-
- dev = bus_find_device(&wmi_bus_type, NULL, &guid, wmidev_match_guid);
- if (!dev)
- return ERR_PTR(-ENODEV);
-
- return to_wmi_device(dev);
-}
-
-static void wmi_device_put(struct wmi_device *wdev)
-{
- put_device(&wdev->dev);
-}
-
-/*
- * Exported WMI functions
- */
-
-/**
- * wmi_instance_count - Get number of WMI object instances
- * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
- *
- * Get the number of WMI object instances.
- *
- * Returns: Number of WMI object instances or negative error code.
- */
-int wmi_instance_count(const char *guid_string)
-{
- struct wmi_device *wdev;
- int ret;
-
- wdev = wmi_find_device_by_guid(guid_string);
- if (IS_ERR(wdev))
- return PTR_ERR(wdev);
-
- ret = wmidev_instance_count(wdev);
- wmi_device_put(wdev);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(wmi_instance_count);
-
-/**
- * wmidev_instance_count - Get number of WMI object instances
- * @wdev: A wmi bus device from a driver
- *
- * Get the number of WMI object instances.
- *
- * Returns: Number of WMI object instances.
- */
-u8 wmidev_instance_count(struct wmi_device *wdev)
-{
- struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
-
- return wblock->gblock.instance_count;
-}
-EXPORT_SYMBOL_GPL(wmidev_instance_count);
-
-/**
- * wmi_evaluate_method - Evaluate a WMI method (deprecated)
- * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
- * @instance: Instance index
- * @method_id: Method ID to call
- * @in: Mandatory buffer containing input for the method call
- * @out: Empty buffer to return the method results
- *
- * Call an ACPI-WMI method, the caller must free @out.
- *
- * Return: acpi_status signaling success or error.
- */
-acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method_id,
- const struct acpi_buffer *in, struct acpi_buffer *out)
-{
- struct wmi_device *wdev;
- acpi_status status;
-
- wdev = wmi_find_device_by_guid(guid_string);
- if (IS_ERR(wdev))
- return AE_ERROR;
-
- status = wmidev_evaluate_method(wdev, instance, method_id, in, out);
-
- wmi_device_put(wdev);
-
- return status;
-}
-EXPORT_SYMBOL_GPL(wmi_evaluate_method);
-
-/**
- * wmidev_evaluate_method - Evaluate a WMI method
- * @wdev: A wmi bus device from a driver
- * @instance: Instance index
- * @method_id: Method ID to call
- * @in: Mandatory buffer containing input for the method call
- * @out: Empty buffer to return the method results
- *
- * Call an ACPI-WMI method, the caller must free @out.
- *
- * Return: acpi_status signaling success or error.
- */
-acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id,
- const struct acpi_buffer *in, struct acpi_buffer *out)
-{
- struct guid_block *block;
- struct wmi_block *wblock;
- acpi_handle handle;
- struct acpi_object_list input;
- union acpi_object params[3];
- char method[WMI_ACPI_METHOD_NAME_SIZE];
-
- wblock = container_of(wdev, struct wmi_block, dev);
- block = &wblock->gblock;
- handle = wblock->acpi_device->handle;
-
- if (!in)
- return AE_BAD_DATA;
-
- if (!(block->flags & ACPI_WMI_METHOD))
- return AE_BAD_DATA;
-
- if (block->instance_count <= instance)
- return AE_BAD_PARAMETER;
-
- input.count = 3;
- input.pointer = params;
-
- params[0].type = ACPI_TYPE_INTEGER;
- params[0].integer.value = instance;
- params[1].type = ACPI_TYPE_INTEGER;
- params[1].integer.value = method_id;
- params[2].type = get_param_acpi_type(wblock);
- params[2].buffer.length = in->length;
- params[2].buffer.pointer = in->pointer;
-
- get_acpi_method_name(wblock, 'M', method);
-
- return acpi_evaluate_object(handle, method, &input, out);
-}
-EXPORT_SYMBOL_GPL(wmidev_evaluate_method);
-
-static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
- struct acpi_buffer *out)
-{
- struct guid_block *block;
- acpi_handle handle;
- struct acpi_object_list input;
- union acpi_object wq_params[1];
- char method[WMI_ACPI_METHOD_NAME_SIZE];
-
- if (!out)
- return AE_BAD_PARAMETER;
-
- block = &wblock->gblock;
- handle = wblock->acpi_device->handle;
-
- if (block->instance_count <= instance)
- return AE_BAD_PARAMETER;
-
- /* Check GUID is a data block */
- if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
- return AE_ERROR;
-
- input.count = 1;
- input.pointer = wq_params;
- wq_params[0].type = ACPI_TYPE_INTEGER;
- wq_params[0].integer.value = instance;
-
- if (instance == 0 && test_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags))
- input.count = 0;
-
- get_acpi_method_name(wblock, 'Q', method);
-
- return acpi_evaluate_object(handle, method, &input, out);
-}
-
-/**
- * wmi_query_block - Return contents of a WMI block (deprecated)
- * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
- * @instance: Instance index
- * @out: Empty buffer to return the contents of the data block to
- *
- * Query a ACPI-WMI block, the caller must free @out.
- *
- * Return: ACPI object containing the content of the WMI block.
- */
-acpi_status wmi_query_block(const char *guid_string, u8 instance,
- struct acpi_buffer *out)
-{
- struct wmi_block *wblock;
- struct wmi_device *wdev;
- acpi_status status;
-
- wdev = wmi_find_device_by_guid(guid_string);
- if (IS_ERR(wdev))
- return AE_ERROR;
-
- if (wmi_device_enable(wdev, true) < 0)
- dev_warn(&wdev->dev, "Failed to enable device\n");
-
- wblock = container_of(wdev, struct wmi_block, dev);
- status = __query_block(wblock, instance, out);
-
- if (wmi_device_enable(wdev, false) < 0)
- dev_warn(&wdev->dev, "Failed to disable device\n");
-
- wmi_device_put(wdev);
-
- return status;
-}
-EXPORT_SYMBOL_GPL(wmi_query_block);
-
-/**
- * wmidev_block_query - Return contents of a WMI block
- * @wdev: A wmi bus device from a driver
- * @instance: Instance index
- *
- * Query an ACPI-WMI block, the caller must free the result.
- *
- * Return: ACPI object containing the content of the WMI block.
- */
-union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
-{
- struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
- struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
-
- if (ACPI_FAILURE(__query_block(wblock, instance, &out)))
- return NULL;
-
- return out.pointer;
-}
-EXPORT_SYMBOL_GPL(wmidev_block_query);
-
-/**
- * wmi_set_block - Write to a WMI block (deprecated)
- * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
- * @instance: Instance index
- * @in: Buffer containing new values for the data block
- *
- * Write the contents of the input buffer to an ACPI-WMI data block.
- *
- * Return: acpi_status signaling success or error.
- */
-acpi_status wmi_set_block(const char *guid_string, u8 instance, const struct acpi_buffer *in)
-{
- struct wmi_device *wdev;
- acpi_status status;
-
- wdev = wmi_find_device_by_guid(guid_string);
- if (IS_ERR(wdev))
- return AE_ERROR;
-
- if (wmi_device_enable(wdev, true) < 0)
- dev_warn(&wdev->dev, "Failed to enable device\n");
-
- status = wmidev_block_set(wdev, instance, in);
-
- if (wmi_device_enable(wdev, false) < 0)
- dev_warn(&wdev->dev, "Failed to disable device\n");
-
- wmi_device_put(wdev);
-
- return status;
-}
-EXPORT_SYMBOL_GPL(wmi_set_block);
-
-/**
- * wmidev_block_set - Write to a WMI block
- * @wdev: A wmi bus device from a driver
- * @instance: Instance index
- * @in: Buffer containing new values for the data block
- *
- * Write contents of the input buffer to an ACPI-WMI data block.
- *
- * Return: acpi_status signaling success or error.
- */
-acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct acpi_buffer *in)
-{
- struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
- acpi_handle handle = wblock->acpi_device->handle;
- struct guid_block *block = &wblock->gblock;
- char method[WMI_ACPI_METHOD_NAME_SIZE];
- struct acpi_object_list input;
- union acpi_object params[2];
-
- if (!in)
- return AE_BAD_DATA;
-
- if (block->instance_count <= instance)
- return AE_BAD_PARAMETER;
-
- /* Check GUID is a data block */
- if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
- return AE_ERROR;
-
- input.count = 2;
- input.pointer = params;
- params[0].type = ACPI_TYPE_INTEGER;
- params[0].integer.value = instance;
- params[1].type = get_param_acpi_type(wblock);
- params[1].buffer.length = in->length;
- params[1].buffer.pointer = in->pointer;
-
- get_acpi_method_name(wblock, 'S', method);
-
- return acpi_evaluate_object(handle, method, &input, NULL);
-}
-EXPORT_SYMBOL_GPL(wmidev_block_set);
-
-/**
- * wmi_install_notify_handler - Register handler for WMI events (deprecated)
- * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
- * @handler: Function to handle notifications
- * @data: Data to be returned to handler when event is fired
- *
- * Register a handler for events sent to the ACPI-WMI mapper device.
- *
- * Return: acpi_status signaling success or error.
- */
-acpi_status wmi_install_notify_handler(const char *guid,
- wmi_notify_handler handler,
- void *data)
-{
- struct wmi_block *wblock;
- struct wmi_device *wdev;
- acpi_status status;
-
- wdev = wmi_find_device_by_guid(guid);
- if (IS_ERR(wdev))
- return AE_ERROR;
-
- wblock = container_of(wdev, struct wmi_block, dev);
-
- down_write(&wblock->notify_lock);
- if (wblock->handler) {
- status = AE_ALREADY_ACQUIRED;
- } else {
- wblock->handler = handler;
- wblock->handler_data = data;
-
- if (wmi_device_enable(wdev, true) < 0)
- dev_warn(&wblock->dev.dev, "Failed to enable device\n");
-
- status = AE_OK;
- }
- up_write(&wblock->notify_lock);
-
- wmi_device_put(wdev);
-
- return status;
-}
-EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
-
-/**
- * wmi_remove_notify_handler - Unregister handler for WMI events (deprecated)
- * @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
- *
- * Unregister handler for events sent to the ACPI-WMI mapper device.
- *
- * Return: acpi_status signaling success or error.
- */
-acpi_status wmi_remove_notify_handler(const char *guid)
-{
- struct wmi_block *wblock;
- struct wmi_device *wdev;
- acpi_status status;
-
- wdev = wmi_find_device_by_guid(guid);
- if (IS_ERR(wdev))
- return AE_ERROR;
-
- wblock = container_of(wdev, struct wmi_block, dev);
-
- down_write(&wblock->notify_lock);
- if (!wblock->handler) {
- status = AE_NULL_ENTRY;
- } else {
- if (wmi_device_enable(wdev, false) < 0)
- dev_warn(&wblock->dev.dev, "Failed to disable device\n");
-
- wblock->handler = NULL;
- wblock->handler_data = NULL;
-
- status = AE_OK;
- }
- up_write(&wblock->notify_lock);
-
- wmi_device_put(wdev);
-
- return status;
-}
-EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
-
-/**
- * wmi_has_guid - Check if a GUID is available
- * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
- *
- * Check if a given GUID is defined by _WDG.
- *
- * Return: True if GUID is available, false otherwise.
- */
-bool wmi_has_guid(const char *guid_string)
-{
- struct wmi_device *wdev;
-
- wdev = wmi_find_device_by_guid(guid_string);
- if (IS_ERR(wdev))
- return false;
-
- wmi_device_put(wdev);
-
- return true;
-}
-EXPORT_SYMBOL_GPL(wmi_has_guid);
-
-/**
- * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID (deprecated)
- * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
- *
- * Find the _UID of ACPI device associated with this WMI GUID.
- *
- * Return: The ACPI _UID field value or NULL if the WMI GUID was not found.
- */
-char *wmi_get_acpi_device_uid(const char *guid_string)
-{
- struct wmi_block *wblock;
- struct wmi_device *wdev;
- char *uid;
-
- wdev = wmi_find_device_by_guid(guid_string);
- if (IS_ERR(wdev))
- return NULL;
-
- wblock = container_of(wdev, struct wmi_block, dev);
- uid = acpi_device_uid(wblock->acpi_device);
-
- wmi_device_put(wdev);
-
- return uid;
-}
-EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid);
-
-/*
- * sysfs interface
- */
-static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
-
- return sysfs_emit(buf, "wmi:%pUL\n", &wblock->gblock.guid);
-}
-static DEVICE_ATTR_RO(modalias);
-
-static ssize_t guid_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
-
- return sysfs_emit(buf, "%pUL\n", &wblock->gblock.guid);
-}
-static DEVICE_ATTR_RO(guid);
-
-static ssize_t instance_count_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
-
- return sysfs_emit(buf, "%d\n", (int)wblock->gblock.instance_count);
-}
-static DEVICE_ATTR_RO(instance_count);
-
-static ssize_t expensive_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
-
- return sysfs_emit(buf, "%d\n",
- (wblock->gblock.flags & ACPI_WMI_EXPENSIVE) != 0);
-}
-static DEVICE_ATTR_RO(expensive);
-
-static ssize_t driver_override_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct wmi_device *wdev = to_wmi_device(dev);
- ssize_t ret;
-
- device_lock(dev);
- ret = sysfs_emit(buf, "%s\n", wdev->driver_override);
- device_unlock(dev);
-
- return ret;
-}
-
-static ssize_t driver_override_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct wmi_device *wdev = to_wmi_device(dev);
- int ret;
-
- ret = driver_set_override(dev, &wdev->driver_override, buf, count);
- if (ret < 0)
- return ret;
-
- return count;
-}
-static DEVICE_ATTR_RW(driver_override);
-
-static struct attribute *wmi_attrs[] = {
- &dev_attr_modalias.attr,
- &dev_attr_guid.attr,
- &dev_attr_instance_count.attr,
- &dev_attr_expensive.attr,
- &dev_attr_driver_override.attr,
- NULL
-};
-ATTRIBUTE_GROUPS(wmi);
-
-static ssize_t notify_id_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
-
- return sysfs_emit(buf, "%02X\n", (unsigned int)wblock->gblock.notify_id);
-}
-static DEVICE_ATTR_RO(notify_id);
-
-static struct attribute *wmi_event_attrs[] = {
- &dev_attr_notify_id.attr,
- NULL
-};
-ATTRIBUTE_GROUPS(wmi_event);
-
-static ssize_t object_id_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
-
- return sysfs_emit(buf, "%c%c\n", wblock->gblock.object_id[0],
- wblock->gblock.object_id[1]);
-}
-static DEVICE_ATTR_RO(object_id);
-
-static ssize_t setable_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct wmi_device *wdev = to_wmi_device(dev);
-
- return sysfs_emit(buf, "%d\n", (int)wdev->setable);
-}
-static DEVICE_ATTR_RO(setable);
-
-static struct attribute *wmi_data_attrs[] = {
- &dev_attr_object_id.attr,
- &dev_attr_setable.attr,
- NULL
-};
-ATTRIBUTE_GROUPS(wmi_data);
-
-static struct attribute *wmi_method_attrs[] = {
- &dev_attr_object_id.attr,
- NULL
-};
-ATTRIBUTE_GROUPS(wmi_method);
-
-static int wmi_dev_uevent(const struct device *dev, struct kobj_uevent_env *env)
-{
- const struct wmi_block *wblock = dev_to_wblock(dev);
-
- if (add_uevent_var(env, "MODALIAS=wmi:%pUL", &wblock->gblock.guid))
- return -ENOMEM;
-
- if (add_uevent_var(env, "WMI_GUID=%pUL", &wblock->gblock.guid))
- return -ENOMEM;
-
- return 0;
-}
-
-static void wmi_dev_release(struct device *dev)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
-
- kfree(wblock->dev.driver_override);
- kfree(wblock);
-}
-
-static int wmi_dev_match(struct device *dev, const struct device_driver *driver)
-{
- const struct wmi_driver *wmi_driver = to_wmi_driver(driver);
- struct wmi_block *wblock = dev_to_wblock(dev);
- const struct wmi_device_id *id = wmi_driver->id_table;
-
- /* When driver_override is set, only bind to the matching driver */
- if (wblock->dev.driver_override)
- return !strcmp(wblock->dev.driver_override, driver->name);
-
- if (id == NULL)
- return 0;
-
- while (*id->guid_string) {
- if (guid_parse_and_compare(id->guid_string, &wblock->gblock.guid))
- return 1;
-
- id++;
- }
-
- return 0;
-}
-
-static void wmi_dev_disable(void *data)
-{
- struct device *dev = data;
-
- if (wmi_device_enable(to_wmi_device(dev), false) < 0)
- dev_warn(dev, "Failed to disable device\n");
-}
-
-static int wmi_dev_probe(struct device *dev)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
- struct wmi_driver *wdriver = to_wmi_driver(dev->driver);
- int ret;
-
- /* Some older WMI drivers will break if instantiated multiple times,
- * so they are blocked from probing WMI devices with a duplicated GUID.
- *
- * New WMI drivers should support being instantiated multiple times.
- */
- if (test_bit(WMI_GUID_DUPLICATED, &wblock->flags) && !wdriver->no_singleton) {
- dev_warn(dev, "Legacy driver %s cannot be instantiated multiple times\n",
- dev->driver->name);
-
- return -ENODEV;
- }
-
- if (wdriver->notify) {
- if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data)
- return -ENODEV;
- }
-
- if (wmi_device_enable(to_wmi_device(dev), true) < 0)
- dev_warn(dev, "failed to enable device -- probing anyway\n");
-
- /*
- * We have to make sure that all devres-managed resources are released first because
- * some might still want to access the underlying WMI device.
- */
- ret = devm_add_action_or_reset(dev, wmi_dev_disable, dev);
- if (ret < 0)
- return ret;
-
- if (wdriver->probe) {
- ret = wdriver->probe(to_wmi_device(dev),
- find_guid_context(wblock, wdriver));
- if (ret)
- return ret;
- }
-
- down_write(&wblock->notify_lock);
- wblock->driver_ready = true;
- up_write(&wblock->notify_lock);
-
- return 0;
-}
-
-static void wmi_dev_remove(struct device *dev)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
- struct wmi_driver *wdriver = to_wmi_driver(dev->driver);
-
- down_write(&wblock->notify_lock);
- wblock->driver_ready = false;
- up_write(&wblock->notify_lock);
-
- if (wdriver->remove)
- wdriver->remove(to_wmi_device(dev));
-}
-
-static void wmi_dev_shutdown(struct device *dev)
-{
- struct wmi_driver *wdriver;
- struct wmi_block *wblock;
-
- if (dev->driver) {
- wdriver = to_wmi_driver(dev->driver);
- wblock = dev_to_wblock(dev);
-
- /*
- * Some machines return bogus WMI event data when disabling
- * the WMI event. Because of this we must prevent the associated
- * WMI driver from receiving new WMI events before disabling it.
- */
- down_write(&wblock->notify_lock);
- wblock->driver_ready = false;
- up_write(&wblock->notify_lock);
-
- if (wdriver->shutdown)
- wdriver->shutdown(to_wmi_device(dev));
-
- /*
- * We still need to disable the WMI device here since devres-managed resources
- * like wmi_dev_disable() will not be release during shutdown.
- */
- if (wmi_device_enable(to_wmi_device(dev), false) < 0)
- dev_warn(dev, "Failed to disable device\n");
- }
-}
-
-static struct class wmi_bus_class = {
- .name = "wmi_bus",
-};
-
-static const struct bus_type wmi_bus_type = {
- .name = "wmi",
- .dev_groups = wmi_groups,
- .match = wmi_dev_match,
- .uevent = wmi_dev_uevent,
- .probe = wmi_dev_probe,
- .remove = wmi_dev_remove,
- .shutdown = wmi_dev_shutdown,
-};
-
-static const struct device_type wmi_type_event = {
- .name = "event",
- .groups = wmi_event_groups,
- .release = wmi_dev_release,
-};
-
-static const struct device_type wmi_type_method = {
- .name = "method",
- .groups = wmi_method_groups,
- .release = wmi_dev_release,
-};
-
-static const struct device_type wmi_type_data = {
- .name = "data",
- .groups = wmi_data_groups,
- .release = wmi_dev_release,
-};
-
-static int wmi_count_guids(struct device *dev, void *data)
-{
- struct wmi_guid_count_context *context = data;
- struct wmi_block *wblock = dev_to_wblock(dev);
-
- if (guid_equal(&wblock->gblock.guid, context->guid))
- context->count++;
-
- return 0;
-}
-
-static int guid_count(const guid_t *guid)
-{
- struct wmi_guid_count_context context = {
- .guid = guid,
- .count = 0,
- };
- int ret;
-
- ret = bus_for_each_dev(&wmi_bus_type, NULL, &context, wmi_count_guids);
- if (ret < 0)
- return ret;
-
- return context.count;
-}
-
-static int wmi_create_device(struct device *wmi_bus_dev,
- struct wmi_block *wblock,
- struct acpi_device *device)
-{
- char method[WMI_ACPI_METHOD_NAME_SIZE];
- struct acpi_device_info *info;
- acpi_handle method_handle;
- acpi_status status;
- int count;
-
- if (wblock->gblock.flags & ACPI_WMI_EVENT) {
- wblock->dev.dev.type = &wmi_type_event;
- goto out_init;
- }
-
- if (wblock->gblock.flags & ACPI_WMI_METHOD) {
- get_acpi_method_name(wblock, 'M', method);
- if (!acpi_has_method(device->handle, method)) {
- dev_warn(wmi_bus_dev,
- FW_BUG "%s method block execution control method not found\n",
- method);
-
- return -ENXIO;
- }
-
- wblock->dev.dev.type = &wmi_type_method;
- goto out_init;
- }
-
- /*
- * Data Block Query Control Method (WQxx by convention) is
- * required per the WMI documentation. If it is not present,
- * we ignore this data block.
- */
- get_acpi_method_name(wblock, 'Q', method);
- status = acpi_get_handle(device->handle, method, &method_handle);
- if (ACPI_FAILURE(status)) {
- dev_warn(wmi_bus_dev,
- FW_BUG "%s data block query control method not found\n",
- method);
-
- return -ENXIO;
- }
-
- status = acpi_get_object_info(method_handle, &info);
- if (ACPI_FAILURE(status))
- return -EIO;
-
- wblock->dev.dev.type = &wmi_type_data;
-
- /*
- * The Microsoft documentation specifically states:
- *
- * Data blocks registered with only a single instance
- * can ignore the parameter.
- *
- * ACPICA will get mad at us if we call the method with the wrong number
- * of arguments, so check what our method expects. (On some Dell
- * laptops, WQxx may not be a method at all.)
- */
- if (info->type != ACPI_TYPE_METHOD || info->param_count == 0)
- set_bit(WMI_READ_TAKES_NO_ARGS, &wblock->flags);
-
- kfree(info);
-
- get_acpi_method_name(wblock, 'S', method);
- if (acpi_has_method(device->handle, method))
- wblock->dev.setable = true;
-
- out_init:
- init_rwsem(&wblock->notify_lock);
- wblock->driver_ready = false;
- wblock->dev.dev.bus = &wmi_bus_type;
- wblock->dev.dev.parent = wmi_bus_dev;
-
- count = guid_count(&wblock->gblock.guid);
- if (count < 0)
- return count;
-
- if (count) {
- dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, count);
- set_bit(WMI_GUID_DUPLICATED, &wblock->flags);
- } else {
- dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid);
- }
-
- device_initialize(&wblock->dev.dev);
-
- return 0;
-}
-
-static int wmi_add_device(struct platform_device *pdev, struct wmi_device *wdev)
-{
- struct device_link *link;
-
- /*
- * Many aggregate WMI drivers do not use -EPROBE_DEFER when they
- * are unable to find a WMI device during probe, instead they require
- * all WMI devices associated with an platform device to become available
- * at once. This device link thus prevents WMI drivers from probing until
- * the associated platform device has finished probing (and has registered
- * all discovered WMI devices).
- */
-
- link = device_link_add(&wdev->dev, &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER);
- if (!link)
- return -EINVAL;
-
- return device_add(&wdev->dev);
-}
-
-/*
- * Parse the _WDG method for the GUID data blocks
- */
-static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev)
-{
- struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
- struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
- const struct guid_block *gblock;
- bool event_data_available;
- struct wmi_block *wblock;
- union acpi_object *obj;
- acpi_status status;
- u32 i, total;
- int retval;
-
- status = acpi_evaluate_object(device->handle, "_WDG", NULL, &out);
- if (ACPI_FAILURE(status))
- return -ENXIO;
-
- obj = out.pointer;
- if (!obj)
- return -ENXIO;
-
- if (obj->type != ACPI_TYPE_BUFFER) {
- kfree(obj);
- return -ENXIO;
- }
-
- event_data_available = acpi_has_method(device->handle, "_WED");
- gblock = (const struct guid_block *)obj->buffer.pointer;
- total = obj->buffer.length / sizeof(struct guid_block);
-
- for (i = 0; i < total; i++) {
- if (!gblock[i].instance_count) {
- dev_info(wmi_bus_dev, FW_INFO "%pUL has zero instances\n", &gblock[i].guid);
- continue;
- }
-
- wblock = kzalloc(sizeof(*wblock), GFP_KERNEL);
- if (!wblock)
- continue;
-
- wblock->acpi_device = device;
- wblock->gblock = gblock[i];
- if (gblock[i].flags & ACPI_WMI_EVENT && !event_data_available)
- set_bit(WMI_NO_EVENT_DATA, &wblock->flags);
-
- retval = wmi_create_device(wmi_bus_dev, wblock, device);
- if (retval) {
- kfree(wblock);
- continue;
- }
-
- retval = wmi_add_device(pdev, &wblock->dev);
- if (retval) {
- dev_err(wmi_bus_dev, "failed to register %pUL\n",
- &wblock->gblock.guid);
-
- put_device(&wblock->dev.dev);
- }
- }
-
- kfree(obj);
-
- return 0;
-}
-
-static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj)
-{
- struct acpi_buffer data = { ACPI_ALLOCATE_BUFFER, NULL };
- union acpi_object param = {
- .integer = {
- .type = ACPI_TYPE_INTEGER,
- .value = wblock->gblock.notify_id,
- }
- };
- struct acpi_object_list input = {
- .count = 1,
- .pointer = &param,
- };
- acpi_status status;
-
- status = acpi_evaluate_object(wblock->acpi_device->handle, "_WED", &input, &data);
- if (ACPI_FAILURE(status)) {
- dev_warn(&wblock->dev.dev, "Failed to get event data\n");
- return -EIO;
- }
-
- *obj = data.pointer;
-
- return 0;
-}
-
-static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
-{
- struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver);
-
- if (!obj && !driver->no_notify_data) {
- dev_warn(&wblock->dev.dev, "Event contains no event data\n");
- return;
- }
-
- if (driver->notify)
- driver->notify(&wblock->dev, obj);
-}
-
-static int wmi_notify_device(struct device *dev, void *data)
-{
- struct wmi_block *wblock = dev_to_wblock(dev);
- union acpi_object *obj = NULL;
- u32 *event = data;
- int ret;
-
- if (!(wblock->gblock.flags & ACPI_WMI_EVENT && wblock->gblock.notify_id == *event))
- return 0;
-
- /* The ACPI WMI specification says that _WED should be
- * evaluated every time an notification is received, even
- * if no consumers are present.
- *
- * Some firmware implementations actually depend on this
- * by using a queue for events which will fill up if the
- * WMI driver core stops evaluating _WED due to missing
- * WMI event consumers.
- */
- if (!test_bit(WMI_NO_EVENT_DATA, &wblock->flags)) {
- ret = wmi_get_notify_data(wblock, &obj);
- if (ret < 0)
- return -EIO;
- }
-
- down_read(&wblock->notify_lock);
-
- if (wblock->dev.dev.driver && wblock->driver_ready)
- wmi_notify_driver(wblock, obj);
-
- if (wblock->handler)
- wblock->handler(obj, wblock->handler_data);
-
- up_read(&wblock->notify_lock);
-
- kfree(obj);
-
- acpi_bus_generate_netlink_event("wmi", acpi_dev_name(wblock->acpi_device), *event, 0);
-
- return -EBUSY;
-}
-
-static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, void *context)
-{
- struct device *wmi_bus_dev = context;
-
- device_for_each_child(wmi_bus_dev, &event, wmi_notify_device);
-}
-
-static int wmi_remove_device(struct device *dev, void *data)
-{
- device_unregister(dev);
-
- return 0;
-}
-
-static void acpi_wmi_remove(struct platform_device *device)
-{
- struct device *wmi_bus_device = dev_get_drvdata(&device->dev);
-
- device_for_each_child_reverse(wmi_bus_device, NULL, wmi_remove_device);
-}
-
-static void acpi_wmi_remove_notify_handler(void *data)
-{
- struct acpi_device *acpi_device = data;
-
- acpi_remove_notify_handler(acpi_device->handle, ACPI_ALL_NOTIFY, acpi_wmi_notify_handler);
-}
-
-static void acpi_wmi_remove_bus_device(void *data)
-{
- struct device *wmi_bus_dev = data;
-
- device_unregister(wmi_bus_dev);
-}
-
-static int acpi_wmi_probe(struct platform_device *device)
-{
- struct acpi_device *acpi_device;
- struct device *wmi_bus_dev;
- acpi_status status;
- int error;
-
- acpi_device = ACPI_COMPANION(&device->dev);
- if (!acpi_device) {
- dev_err(&device->dev, "ACPI companion is missing\n");
- return -ENODEV;
- }
-
- wmi_bus_dev = device_create(&wmi_bus_class, &device->dev, MKDEV(0, 0), NULL, "wmi_bus-%s",
- dev_name(&device->dev));
- if (IS_ERR(wmi_bus_dev))
- return PTR_ERR(wmi_bus_dev);
-
- error = devm_add_action_or_reset(&device->dev, acpi_wmi_remove_bus_device, wmi_bus_dev);
- if (error < 0)
- return error;
-
- dev_set_drvdata(&device->dev, wmi_bus_dev);
-
- status = acpi_install_notify_handler(acpi_device->handle, ACPI_ALL_NOTIFY,
- acpi_wmi_notify_handler, wmi_bus_dev);
- if (ACPI_FAILURE(status)) {
- dev_err(&device->dev, "Error installing notify handler\n");
- return -ENODEV;
- }
- error = devm_add_action_or_reset(&device->dev, acpi_wmi_remove_notify_handler,
- acpi_device);
- if (error < 0)
- return error;
-
- error = parse_wdg(wmi_bus_dev, device);
- if (error) {
- dev_err(&device->dev, "Failed to parse _WDG method\n");
- return error;
- }
-
- return 0;
-}
-
-int __must_check __wmi_driver_register(struct wmi_driver *driver,
- struct module *owner)
-{
- driver->driver.owner = owner;
- driver->driver.bus = &wmi_bus_type;
-
- return driver_register(&driver->driver);
-}
-EXPORT_SYMBOL(__wmi_driver_register);
-
-/**
- * wmi_driver_unregister() - Unregister a WMI driver
- * @driver: WMI driver to unregister
- *
- * Unregisters a WMI driver from the WMI bus.
- */
-void wmi_driver_unregister(struct wmi_driver *driver)
-{
- driver_unregister(&driver->driver);
-}
-EXPORT_SYMBOL(wmi_driver_unregister);
-
-static struct platform_driver acpi_wmi_driver = {
- .driver = {
- .name = "acpi-wmi",
- .acpi_match_table = wmi_device_ids,
- },
- .probe = acpi_wmi_probe,
- .remove = acpi_wmi_remove,
-};
-
-static int __init acpi_wmi_init(void)
-{
- int error;
-
- if (acpi_disabled)
- return -ENODEV;
-
- error = class_register(&wmi_bus_class);
- if (error)
- return error;
-
- error = bus_register(&wmi_bus_type);
- if (error)
- goto err_unreg_class;
-
- error = platform_driver_register(&acpi_wmi_driver);
- if (error) {
- pr_err("Error loading mapper\n");
- goto err_unreg_bus;
- }
-
- return 0;
-
-err_unreg_bus:
- bus_unregister(&wmi_bus_type);
-
-err_unreg_class:
- class_unregister(&wmi_bus_class);
-
- return error;
-}
-
-static void __exit acpi_wmi_exit(void)
-{
- platform_driver_unregister(&acpi_wmi_driver);
- bus_unregister(&wmi_bus_type);
- class_unregister(&wmi_bus_class);
-}
-
-subsys_initcall_sync(acpi_wmi_init);
-module_exit(acpi_wmi_exit);
diff --git a/drivers/platform/x86/x86-android-tablets/Makefile b/drivers/platform/x86/x86-android-tablets/Makefile
index 313be30548bc..a2cf8cbdb351 100644
--- a/drivers/platform/x86/x86-android-tablets/Makefile
+++ b/drivers/platform/x86/x86-android-tablets/Makefile
@@ -6,4 +6,4 @@
obj-$(CONFIG_X86_ANDROID_TABLETS) += vexia_atla10_ec.o
obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o
x86-android-tablets-y := core.o dmi.o shared-psy-info.o \
- asus.o lenovo.o other.o
+ acer.o asus.o lenovo.o other.o
diff --git a/drivers/platform/x86/x86-android-tablets/acer.c b/drivers/platform/x86/x86-android-tablets/acer.c
new file mode 100644
index 000000000000..d48c70ffd992
--- /dev/null
+++ b/drivers/platform/x86/x86-android-tablets/acer.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Board info for Acer X86 tablets which ship with Android as the factory image
+ * and which have broken DSDT tables. The factory kernels shipped on these
+ * devices typically have a bunch of things hardcoded, rather than specified
+ * in their DSDT.
+ *
+ * Copyright (C) 2021-2025 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/property.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#include "shared-psy-info.h"
+#include "x86-android-tablets.h"
+
+/* Acer Iconia One 8 A1-840 (non FHD version) */
+static const struct property_entry acer_a1_840_bq24190_props[] = {
+ PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
+ PROPERTY_ENTRY_BOOL("omit-battery-class"),
+ PROPERTY_ENTRY_BOOL("disable-reset"),
+ { }
+};
+
+static const struct software_node acer_a1_840_bq24190_node = {
+ .properties = acer_a1_840_bq24190_props,
+};
+
+static const struct property_entry acer_a1_840_touchscreen_props[] = {
+ PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
+ PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node acer_a1_840_touchscreen_node = {
+ .properties = acer_a1_840_touchscreen_props,
+};
+
+static const struct x86_i2c_client_info acer_a1_840_i2c_clients[] __initconst = {
+ {
+ /* BQ24297 charger IC */
+ .board_info = {
+ .type = "bq24297",
+ .addr = 0x6b,
+ .dev_name = "bq24297",
+ .swnode = &acer_a1_840_bq24190_node,
+ .platform_data = &bq24190_pdata,
+ },
+ .adapter_path = "\\_SB_.I2C1",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_GPIOINT,
+ .chip = "INT33FC:02",
+ .index = 2,
+ .trigger = ACPI_EDGE_SENSITIVE,
+ .polarity = ACPI_ACTIVE_LOW,
+ .con_id = "bq24297_irq",
+ },
+ }, {
+ /* MPU6515 sensors */
+ .board_info = {
+ .type = "mpu6515",
+ .addr = 0x69,
+ .dev_name = "mpu6515",
+ },
+ .adapter_path = "\\_SB_.I2C3",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_APIC,
+ .index = 0x47,
+ .trigger = ACPI_EDGE_SENSITIVE,
+ .polarity = ACPI_ACTIVE_HIGH,
+ },
+ }, {
+ /* FT5416 touchscreen controller */
+ .board_info = {
+ .type = "edt-ft5x06",
+ .addr = 0x38,
+ .dev_name = "ft5416",
+ .swnode = &acer_a1_840_touchscreen_node,
+ },
+ .adapter_path = "\\_SB_.I2C4",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_APIC,
+ .index = 0x45,
+ .trigger = ACPI_EDGE_SENSITIVE,
+ .polarity = ACPI_ACTIVE_HIGH,
+ },
+ }
+};
+
+static const struct property_entry acer_a1_840_int3496_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 18, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct platform_device_info acer_a1_840_pdevs[] __initconst = {
+ {
+ /* For micro USB ID pin handling */
+ .name = "intel-int3496",
+ .id = PLATFORM_DEVID_NONE,
+ .properties = acer_a1_840_int3496_props,
+ },
+};
+
+/* Properties for the Dollar Cove TI PMIC battery MFD child used as fuel-gauge */
+static const struct property_entry acer_a1_840_fg_props[] = {
+ PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
+ PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
+ PROPERTY_ENTRY_GPIO("charged-gpios", &baytrail_gpiochip_nodes[2], 10, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static struct device *acer_a1_840_fg_dev;
+static struct fwnode_handle *acer_a1_840_fg_node;
+
+static int __init acer_a1_840_init(struct device *dev)
+{
+ int ret;
+
+ acer_a1_840_fg_dev = bus_find_device_by_name(&platform_bus_type, NULL, "chtdc_ti_battery");
+ if (!acer_a1_840_fg_dev)
+ return dev_err_probe(dev, -EPROBE_DEFER, "getting chtdc_ti_battery dev\n");
+
+ acer_a1_840_fg_node = fwnode_create_software_node(acer_a1_840_fg_props, NULL);
+ if (IS_ERR(acer_a1_840_fg_node)) {
+ ret = PTR_ERR(acer_a1_840_fg_node);
+ goto err_put;
+ }
+
+ ret = device_add_software_node(acer_a1_840_fg_dev,
+ to_software_node(acer_a1_840_fg_node));
+ if (ret)
+ goto err_put;
+
+ return 0;
+
+err_put:
+ fwnode_handle_put(acer_a1_840_fg_node);
+ acer_a1_840_fg_node = NULL;
+ put_device(acer_a1_840_fg_dev);
+ acer_a1_840_fg_dev = NULL;
+ return ret;
+}
+
+static void acer_a1_840_exit(void)
+{
+ device_remove_software_node(acer_a1_840_fg_dev);
+ /*
+ * Skip fwnode_handle_put(acer_a1_840_fg_node), instead leak the node.
+ * The intel_dc_ti_battery driver may still reference the strdup-ed
+ * "supplied-from" string. This string will be free-ed if the node
+ * is released.
+ */
+ acer_a1_840_fg_node = NULL;
+ put_device(acer_a1_840_fg_dev);
+ acer_a1_840_fg_dev = NULL;
+}
+
+static const char * const acer_a1_840_modules[] __initconst = {
+ "bq24190_charger", /* For the Vbus regulator for intel-int3496 */
+ NULL
+};
+
+const struct x86_dev_info acer_a1_840_info __initconst = {
+ .i2c_client_info = acer_a1_840_i2c_clients,
+ .i2c_client_count = ARRAY_SIZE(acer_a1_840_i2c_clients),
+ .pdev_info = acer_a1_840_pdevs,
+ .pdev_count = ARRAY_SIZE(acer_a1_840_pdevs),
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
+ .swnode_group = generic_lipo_4v2_battery_swnodes,
+ .modules = acer_a1_840_modules,
+ .init = acer_a1_840_init,
+ .exit = acer_a1_840_exit,
+};
+
+/* Acer Iconia One 7 B1-750 has an Android factory image with everything hardcoded */
+static const char * const acer_b1_750_mount_matrix[] = {
+ "-1", "0", "0",
+ "0", "1", "0",
+ "0", "0", "1"
+};
+
+static const struct property_entry acer_b1_750_bma250e_props[] = {
+ PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", acer_b1_750_mount_matrix),
+ { }
+};
+
+static const struct software_node acer_b1_750_bma250e_node = {
+ .properties = acer_b1_750_bma250e_props,
+};
+
+static const struct property_entry acer_b1_750_novatek_props[] = {
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node acer_b1_750_novatek_node = {
+ .properties = acer_b1_750_novatek_props,
+};
+
+static const struct x86_i2c_client_info acer_b1_750_i2c_clients[] __initconst = {
+ {
+ /* Novatek NVT-ts touchscreen */
+ .board_info = {
+ .type = "nt11205-ts",
+ .addr = 0x34,
+ .dev_name = "NVT-ts",
+ .swnode = &acer_b1_750_novatek_node,
+ },
+ .adapter_path = "\\_SB_.I2C4",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_GPIOINT,
+ .chip = "INT33FC:02",
+ .index = 3,
+ .trigger = ACPI_EDGE_SENSITIVE,
+ .polarity = ACPI_ACTIVE_LOW,
+ .con_id = "NVT-ts_irq",
+ },
+ }, {
+ /* BMA250E accelerometer */
+ .board_info = {
+ .type = "bma250e",
+ .addr = 0x18,
+ .swnode = &acer_b1_750_bma250e_node,
+ },
+ .adapter_path = "\\_SB_.I2C3",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_GPIOINT,
+ .chip = "INT33FC:02",
+ .index = 25,
+ .trigger = ACPI_LEVEL_SENSITIVE,
+ .polarity = ACPI_ACTIVE_HIGH,
+ .con_id = "bma250e_irq",
+ },
+ },
+};
+
+const struct x86_dev_info acer_b1_750_info __initconst = {
+ .i2c_client_info = acer_b1_750_i2c_clients,
+ .i2c_client_count = ARRAY_SIZE(acer_b1_750_i2c_clients),
+ .pdev_info = int3496_pdevs,
+ .pdev_count = 1,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
+};
diff --git a/drivers/platform/x86/x86-android-tablets/asus.c b/drivers/platform/x86/x86-android-tablets/asus.c
index 7dde63b9943f..7d29c7654d21 100644
--- a/drivers/platform/x86/x86-android-tablets/asus.c
+++ b/drivers/platform/x86/x86-android-tablets/asus.c
@@ -5,36 +5,55 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/platform_device.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/* Asus ME176C and TF103C tablets shared data */
-static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = {
- .dev_id = "intel-int3496",
- .table = {
- GPIO_LOOKUP("INT33FC:02", 22, "id", GPIO_ACTIVE_HIGH),
- { }
- },
+static const struct property_entry asus_me176c_tf103c_int3496_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 22, GPIO_ACTIVE_HIGH),
+ { }
};
-static const struct x86_gpio_button asus_me176c_tf103c_lid __initconst = {
- .button = {
- .code = SW_LID,
- .active_low = true,
- .desc = "lid_sw",
- .type = EV_SW,
- .wakeup = true,
- .debounce_interval = 50,
+static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = {
+ {
+ /* For micro USB ID pin handling */
+ .name = "intel-int3496",
+ .id = PLATFORM_DEVID_NONE,
+ .properties = asus_me176c_tf103c_int3496_props,
},
- .chip = "INT33FC:02",
- .pin = 12,
+};
+
+static const struct software_node asus_me176c_tf103c_gpio_keys_node = {
+ .name = "lid_sw",
+};
+
+static const struct property_entry asus_me176c_tf103c_lid_props[] = {
+ PROPERTY_ENTRY_U32("linux,input-type", EV_SW),
+ PROPERTY_ENTRY_U32("linux,code", SW_LID),
+ PROPERTY_ENTRY_STRING("label", "lid_sw"),
+ PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[2], 12, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ PROPERTY_ENTRY_BOOL("wakeup-source"),
+ { }
+};
+
+static const struct software_node asus_me176c_tf103c_lid_node = {
+ .parent = &asus_me176c_tf103c_gpio_keys_node,
+ .properties = asus_me176c_tf103c_lid_props,
+};
+
+static const struct software_node *asus_me176c_tf103c_lid_swnodes[] = {
+ &asus_me176c_tf103c_gpio_keys_node,
+ &asus_me176c_tf103c_lid_node,
+ NULL
};
/* Asus ME176C tablets have an Android factory image with everything hardcoded */
@@ -77,6 +96,16 @@ static const struct software_node asus_me176c_ug3105_node = {
.properties = asus_me176c_ug3105_props,
};
+static const struct property_entry asus_me176c_touchscreen_props[] = {
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[0], 60, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 28, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node asus_me176c_touchscreen_node = {
+ .properties = asus_me176c_touchscreen_props,
+};
+
static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = {
{
/* bq24297 battery charger */
@@ -132,6 +161,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst =
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
+ .swnode = &asus_me176c_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C6",
.irq_data = {
@@ -152,33 +182,17 @@ static const struct x86_serdev_info asus_me176c_serdevs[] __initconst = {
},
};
-static struct gpiod_lookup_table asus_me176c_goodix_gpios = {
- .dev_id = "i2c-goodix_ts",
- .table = {
- GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 28, "irq", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const asus_me176c_gpios[] = {
- &int3496_gpo2_pin22_gpios,
- &asus_me176c_goodix_gpios,
- NULL
-};
-
const struct x86_dev_info asus_me176c_info __initconst = {
.i2c_client_info = asus_me176c_i2c_clients,
.i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients),
- .pdev_info = int3496_pdevs,
- .pdev_count = 1,
+ .pdev_info = asus_me176c_tf103c_pdevs,
+ .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
.serdev_info = asus_me176c_serdevs,
.serdev_count = ARRAY_SIZE(asus_me176c_serdevs),
- .gpio_button = &asus_me176c_tf103c_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = asus_me176c_gpios,
- .bat_swnode = &generic_lipo_hv_4v35_battery_node,
+ .gpio_button_swnodes = asus_me176c_tf103c_lid_swnodes,
+ .swnode_group = generic_lipo_hv_4v35_battery_swnodes,
.modules = bq24190_modules,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Asus TF103C tablets have an Android factory image with everything hardcoded */
@@ -206,24 +220,9 @@ static const struct software_node asus_tf103c_touchscreen_node = {
.properties = asus_tf103c_touchscreen_props,
};
-static const struct property_entry asus_tf103c_battery_props[] = {
- PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
- PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"),
- PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
- PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
- PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000),
- PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000),
- PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
- { }
-};
-
-static const struct software_node asus_tf103c_battery_node = {
- .properties = asus_tf103c_battery_props,
-};
-
static const struct property_entry asus_tf103c_bq24190_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", tusb1211_chg_det_psy, 1),
- PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
+ PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
@@ -236,7 +235,7 @@ static const struct software_node asus_tf103c_bq24190_node = {
static const struct property_entry asus_tf103c_ug3105_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
- PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node),
+ PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000),
{ }
};
@@ -308,19 +307,13 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst =
},
};
-static struct gpiod_lookup_table * const asus_tf103c_gpios[] = {
- &int3496_gpo2_pin22_gpios,
- NULL
-};
-
const struct x86_dev_info asus_tf103c_info __initconst = {
.i2c_client_info = asus_tf103c_i2c_clients,
.i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients),
- .pdev_info = int3496_pdevs,
- .pdev_count = 1,
- .gpio_button = &asus_me176c_tf103c_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = asus_tf103c_gpios,
- .bat_swnode = &asus_tf103c_battery_node,
+ .pdev_info = asus_me176c_tf103c_pdevs,
+ .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
+ .gpio_button_swnodes = asus_me176c_tf103c_lid_swnodes,
+ .swnode_group = generic_lipo_4v2_battery_swnodes,
.modules = bq24190_modules,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c
index 2a9c47178505..021009e9085b 100644
--- a/drivers/platform/x86/x86-android-tablets/core.c
+++ b/drivers/platform/x86/x86-android-tablets/core.c
@@ -5,7 +5,7 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -49,7 +49,7 @@ int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
struct gpiod_lookup_table *lookup;
struct gpio_desc *gpiod;
- lookup = kzalloc(struct_size(lookup, table, 2), GFP_KERNEL);
+ lookup = kzalloc_flex(*lookup, table, 2);
if (!lookup)
return -ENOMEM;
@@ -152,9 +152,9 @@ static struct i2c_client **i2c_clients;
static struct spi_device **spi_devs;
static struct platform_device **pdevs;
static struct serdev_device **serdevs;
-static struct gpio_keys_button *buttons;
-static struct gpiod_lookup_table * const *gpiod_lookup_tables;
-static const struct software_node *bat_swnode;
+static const struct software_node **gpio_button_swnodes;
+static const struct software_node **swnode_group;
+static const struct software_node **gpiochip_node_group;
static void (*exit_handler)(void);
static __init struct i2c_adapter *
@@ -265,8 +265,7 @@ static __init int x86_instantiate_spi_dev(const struct x86_dev_info *dev_info, i
spi_devs[idx] = spi_new_device(controller, &board_info);
put_device(&controller->dev);
if (!spi_devs[idx])
- return dev_err_probe(&controller->dev, -ENOMEM,
- "creating SPI-device %d\n", idx);
+ return -ENOMEM;
return 0;
}
@@ -277,8 +276,10 @@ get_serdev_controller_by_pci_parent(const struct x86_serdev_info *info)
struct pci_dev *pdev;
pdev = pci_get_domain_bus_and_slot(0, 0, info->ctrl.pci.devfn);
- if (!pdev)
- return ERR_PTR(-EPROBE_DEFER);
+ if (!pdev) {
+ pr_err("error could not get PCI serdev at devfn 0x%02x\n", info->ctrl.pci.devfn);
+ return ERR_PTR(-ENODEV);
+ }
/* This puts our reference on pdev and returns a ref on the ctrl */
return get_serdev_controller_from_parent(&pdev->dev, 0, info->ctrl_devname);
@@ -331,6 +332,34 @@ put_ctrl_dev:
return ret;
}
+const struct software_node baytrail_gpiochip_nodes[] = {
+ { .name = "INT33FC:00" },
+ { .name = "INT33FC:01" },
+ { .name = "INT33FC:02" },
+};
+
+static const struct software_node *baytrail_gpiochip_node_group[] = {
+ &baytrail_gpiochip_nodes[0],
+ &baytrail_gpiochip_nodes[1],
+ &baytrail_gpiochip_nodes[2],
+ NULL
+};
+
+const struct software_node cherryview_gpiochip_nodes[] = {
+ { .name = "INT33FF:00" },
+ { .name = "INT33FF:01" },
+ { .name = "INT33FF:02" },
+ { .name = "INT33FF:03" },
+};
+
+static const struct software_node *cherryview_gpiochip_node_group[] = {
+ &cherryview_gpiochip_nodes[0],
+ &cherryview_gpiochip_nodes[1],
+ &cherryview_gpiochip_nodes[2],
+ &cherryview_gpiochip_nodes[3],
+ NULL
+};
+
static void x86_android_tablet_remove(struct platform_device *pdev)
{
int i;
@@ -346,7 +375,6 @@ static void x86_android_tablet_remove(struct platform_device *pdev)
platform_device_unregister(pdevs[i]);
kfree(pdevs);
- kfree(buttons);
for (i = spi_dev_count - 1; i >= 0; i--)
spi_unregister_device(spi_devs[i]);
@@ -361,10 +389,9 @@ static void x86_android_tablet_remove(struct platform_device *pdev)
if (exit_handler)
exit_handler();
- for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
- gpiod_remove_lookup_table(gpiod_lookup_tables[i]);
-
- software_node_unregister(bat_swnode);
+ software_node_unregister_node_group(gpio_button_swnodes);
+ software_node_unregister_node_group(swnode_group);
+ software_node_unregister_node_group(gpiochip_node_group);
}
static __init int x86_android_tablet_probe(struct platform_device *pdev)
@@ -388,16 +415,28 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
for (i = 0; dev_info->modules && dev_info->modules[i]; i++)
request_module(dev_info->modules[i]);
- bat_swnode = dev_info->bat_swnode;
- if (bat_swnode) {
- ret = software_node_register(bat_swnode);
- if (ret)
- return ret;
+ switch (dev_info->gpiochip_type) {
+ case X86_GPIOCHIP_BAYTRAIL:
+ gpiochip_node_group = baytrail_gpiochip_node_group;
+ break;
+ case X86_GPIOCHIP_CHERRYVIEW:
+ gpiochip_node_group = cherryview_gpiochip_node_group;
+ break;
+ case X86_GPIOCHIP_UNSPECIFIED:
+ gpiochip_node_group = NULL;
+ break;
}
- gpiod_lookup_tables = dev_info->gpiod_lookup_tables;
- for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
- gpiod_add_lookup_table(gpiod_lookup_tables[i]);
+ ret = software_node_register_node_group(gpiochip_node_group);
+ if (ret)
+ return ret;
+
+ ret = software_node_register_node_group(dev_info->swnode_group);
+ if (ret) {
+ x86_android_tablet_remove(pdev);
+ return ret;
+ }
+ swnode_group = dev_info->swnode_group;
if (dev_info->init) {
ret = dev_info->init(&pdev->dev);
@@ -408,7 +447,7 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
exit_handler = dev_info->exit;
}
- i2c_clients = kcalloc(dev_info->i2c_client_count, sizeof(*i2c_clients), GFP_KERNEL);
+ i2c_clients = kzalloc_objs(*i2c_clients, dev_info->i2c_client_count);
if (!i2c_clients) {
x86_android_tablet_remove(pdev);
return -ENOMEM;
@@ -423,7 +462,7 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
}
}
- spi_devs = kcalloc(dev_info->spi_dev_count, sizeof(*spi_devs), GFP_KERNEL);
+ spi_devs = kzalloc_objs(*spi_devs, dev_info->spi_dev_count);
if (!spi_devs) {
x86_android_tablet_remove(pdev);
return -ENOMEM;
@@ -439,7 +478,7 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
}
/* + 1 to make space for the (optional) gpio_keys_button platform device */
- pdevs = kcalloc(dev_info->pdev_count + 1, sizeof(*pdevs), GFP_KERNEL);
+ pdevs = kzalloc_objs(*pdevs, dev_info->pdev_count + 1);
if (!pdevs) {
x86_android_tablet_remove(pdev);
return -ENOMEM;
@@ -455,7 +494,7 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
}
}
- serdevs = kcalloc(dev_info->serdev_count, sizeof(*serdevs), GFP_KERNEL);
+ serdevs = kzalloc_objs(*serdevs, dev_info->serdev_count);
if (!serdevs) {
x86_android_tablet_remove(pdev);
return -ENOMEM;
@@ -470,38 +509,22 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev)
}
}
- if (dev_info->gpio_button_count) {
- struct gpio_keys_platform_data pdata = { };
- struct gpio_desc *gpiod;
+ if (dev_info->gpio_button_swnodes) {
+ struct platform_device_info button_info = {
+ .name = "gpio-keys",
+ .id = PLATFORM_DEVID_AUTO,
+ };
- buttons = kcalloc(dev_info->gpio_button_count, sizeof(*buttons), GFP_KERNEL);
- if (!buttons) {
+ ret = software_node_register_node_group(dev_info->gpio_button_swnodes);
+ if (ret < 0) {
x86_android_tablet_remove(pdev);
- return -ENOMEM;
- }
-
- for (i = 0; i < dev_info->gpio_button_count; i++) {
- ret = x86_android_tablet_get_gpiod(dev_info->gpio_button[i].chip,
- dev_info->gpio_button[i].pin,
- dev_info->gpio_button[i].button.desc,
- false, GPIOD_IN, &gpiod);
- if (ret < 0) {
- x86_android_tablet_remove(pdev);
- return ret;
- }
-
- buttons[i] = dev_info->gpio_button[i].button;
- buttons[i].gpio = desc_to_gpio(gpiod);
- /* Release GPIO descriptor so that gpio-keys can request it */
- devm_gpiod_put(&x86_android_tablet_device->dev, gpiod);
+ return ret;
}
- pdata.buttons = buttons;
- pdata.nbuttons = dev_info->gpio_button_count;
+ gpio_button_swnodes = dev_info->gpio_button_swnodes;
- pdevs[pdev_count] = platform_device_register_data(&pdev->dev, "gpio-keys",
- PLATFORM_DEVID_AUTO,
- &pdata, sizeof(pdata));
+ button_info.fwnode = software_node_fwnode(dev_info->gpio_button_swnodes[0]);
+ pdevs[pdev_count] = platform_device_register_full(&button_info);
if (IS_ERR(pdevs[pdev_count])) {
ret = PTR_ERR(pdevs[pdev_count]);
x86_android_tablet_remove(pdev);
@@ -537,6 +560,6 @@ static void __exit x86_android_tablet_exit(void)
}
module_exit(x86_android_tablet_exit);
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/x86-android-tablets/dmi.c b/drivers/platform/x86/x86-android-tablets/dmi.c
index 3e5fa3b6e2fd..4a5720d6fc1d 100644
--- a/drivers/platform/x86/x86-android-tablets/dmi.c
+++ b/drivers/platform/x86/x86-android-tablets/dmi.c
@@ -5,7 +5,7 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/dmi.h>
@@ -17,6 +17,16 @@
const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
{
+ /* Acer Iconia One 8 A1-840 (non FHD version) */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "BayTrail"),
+ /* Above strings are too generic also match BIOS date */
+ DMI_MATCH(DMI_BIOS_DATE, "04/01/2014"),
+ },
+ .driver_data = (void *)&acer_a1_840_info,
+ },
+ {
/* Acer Iconia One 7 B1-750 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
@@ -180,6 +190,18 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
.driver_data = (void *)&peaq_c1010_info,
},
{
+ /* Vexia Edu Atla 10 tablet 5V version */
+ .matches = {
+ /* Having all 3 of these not set is somewhat unique */
+ DMI_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "To be filled by O.E.M."),
+ DMI_MATCH(DMI_BOARD_NAME, "To be filled by O.E.M."),
+ /* Above strings are too generic, also match on BIOS date */
+ DMI_MATCH(DMI_BIOS_DATE, "05/14/2015"),
+ },
+ .driver_data = (void *)&vexia_edu_atla10_5v_info,
+ },
+ {
/* Vexia Edu Atla 10 tablet 9V version */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
@@ -187,7 +209,7 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
/* Above strings are too generic, also match on BIOS date */
DMI_MATCH(DMI_BIOS_DATE, "08/25/2014"),
},
- .driver_data = (void *)&vexia_edu_atla10_info,
+ .driver_data = (void *)&vexia_edu_atla10_9v_info,
},
{
/* Whitelabel (sold as various brands) TM800A550L */
diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c
index 1241a97cda39..8d825e0b4661 100644
--- a/drivers/platform/x86/x86-android-tablets/lenovo.c
+++ b/drivers/platform/x86/x86-android-tablets/lenovo.c
@@ -5,13 +5,15 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/efi.h>
#include <linux/gpio/machine.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/mfd/arizona/pdata.h>
#include <linux/mfd/arizona/registers.h>
#include <linux/mfd/intel_soc_pmic.h>
@@ -59,11 +61,30 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = {
.initial_brightness = 128,
};
+static const struct software_node arizona_gpiochip_node = {
+ .name = "arizona",
+};
+
+static const struct software_node crystalcove_gpiochip_node = {
+ .name = "gpio_crystalcove",
+};
+
/* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */
+static const struct property_entry lenovo_yb1_x90_goodix_props[] = {
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[1], 53, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("irq-gpios", &cherryview_gpiochip_nodes[1], 56, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node lenovo_yb1_x90_goodix_node = {
+ .properties = lenovo_yb1_x90_goodix_props,
+};
+
static const struct property_entry lenovo_yb1_x90_wacom_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0001),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 150),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_LOW),
{ }
};
@@ -85,6 +106,7 @@ static const struct property_entry lenovo_yb1_x90_hideep_ts_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-y", 1920),
PROPERTY_ENTRY_U32("touchscreen-max-pressure", 16384),
PROPERTY_ENTRY_BOOL("hideep,force-native-protocol"),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 7, GPIO_ACTIVE_LOW),
{ }
};
@@ -108,6 +130,7 @@ static const struct x86_i2c_client_info lenovo_yb1_x90_i2c_clients[] __initconst
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
+ .swnode = &lenovo_yb1_x90_goodix_node,
},
.adapter_path = "\\_SB_.PCI0.I2C2",
.irq_data = {
@@ -185,48 +208,33 @@ static const struct x86_serdev_info lenovo_yb1_x90_serdevs[] __initconst = {
},
};
-static const struct x86_gpio_button lenovo_yb1_x90_lid __initconst = {
- .button = {
- .code = SW_LID,
- .active_low = true,
- .desc = "lid_sw",
- .type = EV_SW,
- .wakeup = true,
- .debounce_interval = 50,
- },
- .chip = "INT33FF:02",
- .pin = 19,
-};
-
-static struct gpiod_lookup_table lenovo_yb1_x90_goodix_gpios = {
- .dev_id = "i2c-goodix_ts",
- .table = {
- GPIO_LOOKUP("INT33FF:01", 53, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FF:01", 56, "irq", GPIO_ACTIVE_HIGH),
- { }
- },
+/*
+ * Software node attached to gpio-keys device representing the LID and
+ * serving as a parent to software nodes representing individual keys/buttons
+ * as required by the device tree binding.
+ */
+static const struct software_node lenovo_lid_gpio_keys_node = {
+ .name = "lid_sw",
};
-static struct gpiod_lookup_table lenovo_yb1_x90_hideep_gpios = {
- .dev_id = "i2c-hideep_ts",
- .table = {
- GPIO_LOOKUP("INT33FF:00", 7, "reset", GPIO_ACTIVE_LOW),
- { }
- },
+static const struct property_entry lenovo_yb1_x90_lid_props[] = {
+ PROPERTY_ENTRY_U32("linux,input-type", EV_SW),
+ PROPERTY_ENTRY_U32("linux,code", SW_LID),
+ PROPERTY_ENTRY_STRING("label", "lid_sw"),
+ PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[2], 19, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ PROPERTY_ENTRY_BOOL("wakeup-source"),
+ { }
};
-static struct gpiod_lookup_table lenovo_yb1_x90_wacom_gpios = {
- .dev_id = "i2c-wacom",
- .table = {
- GPIO_LOOKUP("INT33FF:00", 82, "reset", GPIO_ACTIVE_LOW),
- { }
- },
+static const struct software_node lenovo_yb1_x90_lid_node = {
+ .parent = &lenovo_lid_gpio_keys_node,
+ .properties = lenovo_yb1_x90_lid_props,
};
-static struct gpiod_lookup_table * const lenovo_yb1_x90_gpios[] = {
- &lenovo_yb1_x90_hideep_gpios,
- &lenovo_yb1_x90_goodix_gpios,
- &lenovo_yb1_x90_wacom_gpios,
+static const struct software_node *lenovo_yb1_x90_lid_swnodes[] = {
+ &lenovo_lid_gpio_keys_node,
+ &lenovo_yb1_x90_lid_node,
NULL
};
@@ -256,9 +264,8 @@ const struct x86_dev_info lenovo_yogabook_x90_info __initconst = {
.pdev_count = ARRAY_SIZE(lenovo_yb1_x90_pdevs),
.serdev_info = lenovo_yb1_x90_serdevs,
.serdev_count = ARRAY_SIZE(lenovo_yb1_x90_serdevs),
- .gpio_button = &lenovo_yb1_x90_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = lenovo_yb1_x90_gpios,
+ .gpio_button_swnodes = lenovo_yb1_x90_lid_swnodes,
+ .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
.init = lenovo_yb1_x90_init,
};
@@ -294,17 +301,25 @@ static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = {
.properties = lenovo_yoga_tab2_830_1050_bq24190_props,
};
-static const struct x86_gpio_button lenovo_yoga_tab2_830_1050_lid __initconst = {
- .button = {
- .code = SW_LID,
- .active_low = true,
- .desc = "lid_sw",
- .type = EV_SW,
- .wakeup = true,
- .debounce_interval = 50,
- },
- .chip = "INT33FC:02",
- .pin = 26,
+static const struct property_entry lenovo_yoga_tab2_830_1050_lid_props[] = {
+ PROPERTY_ENTRY_U32("linux,input-type", EV_SW),
+ PROPERTY_ENTRY_U32("linux,code", SW_LID),
+ PROPERTY_ENTRY_STRING("label", "lid_sw"),
+ PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[2], 26, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ PROPERTY_ENTRY_BOOL("wakeup-source"),
+ { }
+};
+
+static const struct software_node lenovo_yoga_tab2_830_1050_lid_node = {
+ .parent = &lenovo_lid_gpio_keys_node,
+ .properties = lenovo_yoga_tab2_830_1050_lid_props,
+};
+
+static const struct software_node *lenovo_yoga_tab2_830_1050_lid_swnodes[] = {
+ &lenovo_lid_gpio_keys_node,
+ &lenovo_yoga_tab2_830_1050_lid_node,
+ NULL
};
/* This gets filled by lenovo_yoga_tab2_830_1050_init() */
@@ -384,47 +399,65 @@ static struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __init
},
};
-static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = {
- .dev_id = "intel-int3496",
- .table = {
- GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW),
- GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH),
- { }
+static const struct property_entry lenovo_yoga_tab2_830_1050_int3496_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 24, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __initconst = {
+ {
+ /* For micro USB ID pin handling */
+ .name = "intel-int3496",
+ .id = PLATFORM_DEVID_NONE,
+ .properties = lenovo_yoga_tab2_830_1050_int3496_props,
},
};
#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00"
-static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = {
- .dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME,
- .table = {
- GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW),
- { }
- },
+static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = {
+ PROPERTY_ENTRY_GPIO("reset-gpios",
+ &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios",
+ &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios",
+ &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios",
+ &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = {
+ .properties = lenovo_yoga_tab2_830_1050_wm1502_props,
};
-static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = {
- &lenovo_yoga_tab2_830_1050_int3496_gpios,
- &lenovo_yoga_tab2_830_1050_codec_gpios,
+static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = {
+ &crystalcove_gpiochip_node,
+ &arizona_gpiochip_node,
+ &lenovo_yoga_tab2_830_1050_wm5102,
+ &generic_lipo_hv_4v35_battery_node,
NULL
};
static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev);
static void lenovo_yoga_tab2_830_1050_exit(void);
+static const char * const lenovo_yoga_tab2_modules[] __initconst = {
+ "spi_pxa2xx_platform", /* For the SPI codec device */
+ "bq24190_charger", /* For the Vbus regulator for int3496/lc824206xa */
+ NULL
+};
+
const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = {
.i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients,
.i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients),
- .pdev_info = int3496_pdevs,
- .pdev_count = 1,
- .gpio_button = &lenovo_yoga_tab2_830_1050_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios,
- .bat_swnode = &generic_lipo_hv_4v35_battery_node,
- .modules = bq24190_modules,
+ .pdev_info = lenovo_yoga_tab2_830_1050_pdevs,
+ .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs),
+ .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes,
+ .swnode_group = lenovo_yoga_tab2_830_1050_swnodes,
+ .modules = lenovo_yoga_tab2_modules,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
.init = lenovo_yoga_tab2_830_1050_init,
.exit = lenovo_yoga_tab2_830_1050_exit,
};
@@ -481,6 +514,7 @@ static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map =
PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk",
"INT33FC:02", "pmu_clk2_grp", "pmu_clk");
+static struct device *lenovo_yoga_tab2_830_1050_codec_dev;
static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl;
static struct sys_off_handler *lenovo_yoga_tab2_830_1050_sys_off_handler;
@@ -507,12 +541,18 @@ static int __init lenovo_yoga_tab2_830_1050_init_codec(void)
goto err_unregister_mappings;
}
- /* We're done with the codec_dev now */
- put_device(codec_dev);
+ ret = device_add_software_node(codec_dev, &lenovo_yoga_tab2_830_1050_wm5102);
+ if (ret) {
+ dev_err_probe(codec_dev, ret, "adding software node\n");
+ goto err_put_pinctrl;
+ }
+ lenovo_yoga_tab2_830_1050_codec_dev = codec_dev;
lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl;
return 0;
+err_put_pinctrl:
+ pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
err_unregister_mappings:
pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
err_put_device:
@@ -560,10 +600,10 @@ static void lenovo_yoga_tab2_830_1050_exit(void)
{
unregister_sys_off_handler(lenovo_yoga_tab2_830_1050_sys_off_handler);
- if (lenovo_yoga_tab2_830_1050_codec_pinctrl) {
- pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
- pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
- }
+ device_remove_software_node(lenovo_yoga_tab2_830_1050_codec_dev);
+ pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
+ pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
+ put_device(lenovo_yoga_tab2_830_1050_codec_dev);
}
/*
@@ -718,19 +758,21 @@ static const struct x86_i2c_client_info lenovo_yoga_tab2_1380_i2c_clients[] __in
}
};
+static const struct property_entry lenovo_yoga_tab2_1380_fc_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("uart3_txd-gpios", &baytrail_gpiochip_nodes[0], 57, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("uart3_rxd-gpios", &baytrail_gpiochip_nodes[0], 61, GPIO_ACTIVE_HIGH),
+ { }
+};
+
static const struct platform_device_info lenovo_yoga_tab2_1380_pdevs[] __initconst = {
{
/* For the Tablet 2 Pro 1380's custom fast charging driver */
.name = "lenovo-yoga-tab2-pro-1380-fastcharger",
.id = PLATFORM_DEVID_NONE,
+ .properties = lenovo_yoga_tab2_1380_fc_props,
},
};
-static const char * const lenovo_yoga_tab2_1380_modules[] __initconst = {
- "bq24190_charger", /* For the Vbus regulator for lc824206xa */
- NULL
-};
-
static int __init lenovo_yoga_tab2_1380_init(struct device *dev)
{
int ret;
@@ -752,31 +794,15 @@ static int __init lenovo_yoga_tab2_1380_init(struct device *dev)
return 0;
}
-static struct gpiod_lookup_table lenovo_yoga_tab2_1380_fc_gpios = {
- .dev_id = "serial0-0",
- .table = {
- GPIO_LOOKUP("INT33FC:00", 57, "uart3_txd", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:00", 61, "uart3_rxd", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const lenovo_yoga_tab2_1380_gpios[] = {
- &lenovo_yoga_tab2_830_1050_codec_gpios,
- &lenovo_yoga_tab2_1380_fc_gpios,
- NULL
-};
-
const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = {
.i2c_client_info = lenovo_yoga_tab2_1380_i2c_clients,
.i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_i2c_clients),
.pdev_info = lenovo_yoga_tab2_1380_pdevs,
.pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs),
- .gpio_button = &lenovo_yoga_tab2_830_1050_lid,
- .gpio_button_count = 1,
- .gpiod_lookup_tables = lenovo_yoga_tab2_1380_gpios,
- .bat_swnode = &generic_lipo_hv_4v35_battery_node,
- .modules = lenovo_yoga_tab2_1380_modules,
+ .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes,
+ .swnode_group = lenovo_yoga_tab2_830_1050_swnodes,
+ .modules = lenovo_yoga_tab2_modules,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
.init = lenovo_yoga_tab2_1380_init,
.exit = lenovo_yoga_tab2_830_1050_exit,
};
@@ -824,6 +850,7 @@ static const struct property_entry lenovo_yt3_hideep_ts_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1600),
PROPERTY_ENTRY_U32("touchscreen-size-y", 2560),
PROPERTY_ENTRY_U32("touchscreen-max-pressure", 255),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 7, GPIO_ACTIVE_LOW),
{ }
};
@@ -958,12 +985,34 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = {
},
};
+static const struct property_entry lenovo_yt3_wm1502_props[] = {
+ PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios",
+ &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios",
+ &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+static const struct software_node lenovo_yt3_wm5102 = {
+ .properties = lenovo_yt3_wm1502_props,
+ .name = "wm5102",
+};
+
+static const struct software_node *lenovo_yt3_swnodes[] = {
+ &arizona_gpiochip_node,
+ &lenovo_yt3_wm5102,
+ NULL
+};
+
static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = {
{
/* WM5102 codec */
.board_info = {
.modalias = "wm5102",
.platform_data = &lenovo_yt3_wm5102_pdata,
+ .swnode = &lenovo_yt3_wm5102,
.max_speed_hz = 5000000,
},
.ctrl_path = "\\_SB_.PCI0.SPI1",
@@ -1013,28 +1062,8 @@ static int __init lenovo_yt3_init(struct device *dev)
return 0;
}
-static struct gpiod_lookup_table lenovo_yt3_hideep_gpios = {
- .dev_id = "i2c-hideep_ts",
- .table = {
- GPIO_LOOKUP("INT33FF:00", 7, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table lenovo_yt3_wm5102_gpios = {
- .dev_id = "spi1.0",
- .table = {
- GPIO_LOOKUP("INT33FF:00", 75, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FF:00", 81, "wlf,ldoena", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FF:00", 82, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("arizona", 2, "wlf,micd-pol", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const lenovo_yt3_gpios[] = {
- &lenovo_yt3_hideep_gpios,
- &lenovo_yt3_wm5102_gpios,
+static const char * const lenovo_yt3_modules[] __initconst = {
+ "spi_pxa2xx_platform", /* For the SPI codec device */
NULL
};
@@ -1043,6 +1072,8 @@ const struct x86_dev_info lenovo_yt3_info __initconst = {
.i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients),
.spi_dev_info = lenovo_yt3_spi_devs,
.spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs),
- .gpiod_lookup_tables = lenovo_yt3_gpios,
+ .swnode_group = lenovo_yt3_swnodes,
+ .modules = lenovo_yt3_modules,
+ .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
.init = lenovo_yt3_init,
};
diff --git a/drivers/platform/x86/x86-android-tablets/other.c b/drivers/platform/x86/x86-android-tablets/other.c
index 1d93d9edb23f..7532af2d72d1 100644
--- a/drivers/platform/x86/x86-android-tablets/other.c
+++ b/drivers/platform/x86/x86-android-tablets/other.c
@@ -5,12 +5,13 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/acpi.h>
#include <linux/gpio/machine.h>
-#include <linux/input.h>
+#include <linux/gpio/property.h>
+#include <linux/input-event-codes.h>
#include <linux/leds.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
@@ -21,102 +22,38 @@
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
-/* Acer Iconia One 7 B1-750 has an Android factory image with everything hardcoded */
-static const char * const acer_b1_750_mount_matrix[] = {
- "-1", "0", "0",
- "0", "1", "0",
- "0", "0", "1"
+/*
+ * Advantech MICA-071
+ * This is a standard Windows tablet, but it has an extra "quick launch" button
+ * which is not described in the ACPI tables in anyway.
+ * Use the x86-android-tablets infra to create a gpio-keys device for this.
+ */
+static const struct software_node advantech_mica_071_gpio_keys_node = {
+ .name = "prog1_key",
};
-static const struct property_entry acer_b1_750_bma250e_props[] = {
- PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", acer_b1_750_mount_matrix),
+static const struct property_entry advantech_mica_071_prog1_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_PROG1),
+ PROPERTY_ENTRY_STRING("label", "prog1_key"),
+ PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[0], 2, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
{ }
};
-static const struct software_node acer_b1_750_bma250e_node = {
- .properties = acer_b1_750_bma250e_props,
+static const struct software_node advantech_mica_071_prog1_key_node = {
+ .parent = &advantech_mica_071_gpio_keys_node,
+ .properties = advantech_mica_071_prog1_key_props,
};
-static const struct x86_i2c_client_info acer_b1_750_i2c_clients[] __initconst = {
- {
- /* Novatek NVT-ts touchscreen */
- .board_info = {
- .type = "nt11205-ts",
- .addr = 0x34,
- .dev_name = "NVT-ts",
- },
- .adapter_path = "\\_SB_.I2C4",
- .irq_data = {
- .type = X86_ACPI_IRQ_TYPE_GPIOINT,
- .chip = "INT33FC:02",
- .index = 3,
- .trigger = ACPI_EDGE_SENSITIVE,
- .polarity = ACPI_ACTIVE_LOW,
- .con_id = "NVT-ts_irq",
- },
- }, {
- /* BMA250E accelerometer */
- .board_info = {
- .type = "bma250e",
- .addr = 0x18,
- .swnode = &acer_b1_750_bma250e_node,
- },
- .adapter_path = "\\_SB_.I2C3",
- .irq_data = {
- .type = X86_ACPI_IRQ_TYPE_GPIOINT,
- .chip = "INT33FC:02",
- .index = 25,
- .trigger = ACPI_LEVEL_SENSITIVE,
- .polarity = ACPI_ACTIVE_HIGH,
- .con_id = "bma250e_irq",
- },
- },
-};
-
-static struct gpiod_lookup_table acer_b1_750_nvt_ts_gpios = {
- .dev_id = "i2c-NVT-ts",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const acer_b1_750_gpios[] = {
- &acer_b1_750_nvt_ts_gpios,
- &int3496_reference_gpios,
+static const struct software_node *advantech_mica_071_button_swnodes[] = {
+ &advantech_mica_071_gpio_keys_node,
+ &advantech_mica_071_prog1_key_node,
NULL
};
-const struct x86_dev_info acer_b1_750_info __initconst = {
- .i2c_client_info = acer_b1_750_i2c_clients,
- .i2c_client_count = ARRAY_SIZE(acer_b1_750_i2c_clients),
- .pdev_info = int3496_pdevs,
- .pdev_count = 1,
- .gpiod_lookup_tables = acer_b1_750_gpios,
-};
-
-/*
- * Advantech MICA-071
- * This is a standard Windows tablet, but it has an extra "quick launch" button
- * which is not described in the ACPI tables in anyway.
- * Use the x86-android-tablets infra to create a gpio-keys device for this.
- */
-static const struct x86_gpio_button advantech_mica_071_button __initconst = {
- .button = {
- .code = KEY_PROG1,
- .active_low = true,
- .desc = "prog1_key",
- .type = EV_KEY,
- .wakeup = false,
- .debounce_interval = 50,
- },
- .chip = "INT33FC:00",
- .pin = 2,
-};
-
const struct x86_dev_info advantech_mica_071_info __initconst = {
- .gpio_button = &advantech_mica_071_button,
- .gpio_button_count = 1,
+ .gpio_button_swnodes = advantech_mica_071_button_swnodes,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
@@ -212,36 +149,46 @@ const struct x86_dev_info chuwi_hi8_info __initconst = {
* in the button row with the power + volume-buttons labeled P and F.
* Use the x86-android-tablets infra to create a gpio-keys device for these.
*/
-static const struct x86_gpio_button cyberbook_t116_buttons[] __initconst = {
- {
- .button = {
- .code = KEY_PROG1,
- .active_low = true,
- .desc = "prog1_key",
- .type = EV_KEY,
- .wakeup = false,
- .debounce_interval = 50,
- },
- .chip = "INT33FF:00",
- .pin = 30,
- },
- {
- .button = {
- .code = KEY_PROG2,
- .active_low = true,
- .desc = "prog2_key",
- .type = EV_KEY,
- .wakeup = false,
- .debounce_interval = 50,
- },
- .chip = "INT33FF:03",
- .pin = 48,
- },
+static const struct software_node cyberbook_t116_gpio_keys_node = {
+ .name = "prog_keys",
+};
+
+static const struct property_entry cyberbook_t116_prog1_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_PROG1),
+ PROPERTY_ENTRY_STRING("label", "prog1_key"),
+ PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[0], 30, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ { }
+};
+
+static const struct software_node cyberbook_t116_prog1_key_node = {
+ .parent = &cyberbook_t116_gpio_keys_node,
+ .properties = cyberbook_t116_prog1_key_props,
+};
+
+static const struct property_entry cyberbook_t116_prog2_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_PROG2),
+ PROPERTY_ENTRY_STRING("label", "prog2_key"),
+ PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[3], 48, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ { }
+};
+
+static const struct software_node cyberbook_t116_prog2_key_node = {
+ .parent = &cyberbook_t116_gpio_keys_node,
+ .properties = cyberbook_t116_prog2_key_props,
+};
+
+static const struct software_node *cyberbook_t116_buttons_swnodes[] = {
+ &cyberbook_t116_gpio_keys_node,
+ &cyberbook_t116_prog1_key_node,
+ &cyberbook_t116_prog2_key_node,
+ NULL
};
const struct x86_dev_info cyberbook_t116_info __initconst = {
- .gpio_button = cyberbook_t116_buttons,
- .gpio_button_count = ARRAY_SIZE(cyberbook_t116_buttons),
+ .gpio_button_swnodes = cyberbook_t116_buttons_swnodes,
+ .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
};
#define CZC_EC_EXTRA_PORT 0x68
@@ -297,6 +244,8 @@ static const struct software_node medion_lifetab_s10346_accel_node = {
static const struct property_entry medion_lifetab_s10346_touchscreen_props[] = {
PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 3, GPIO_ACTIVE_HIGH),
{ }
};
@@ -340,24 +289,10 @@ static const struct x86_i2c_client_info medion_lifetab_s10346_i2c_clients[] __in
},
};
-static struct gpiod_lookup_table medion_lifetab_s10346_goodix_gpios = {
- .dev_id = "i2c-goodix_ts",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const medion_lifetab_s10346_gpios[] = {
- &medion_lifetab_s10346_goodix_gpios,
- NULL
-};
-
const struct x86_dev_info medion_lifetab_s10346_info __initconst = {
.i2c_client_info = medion_lifetab_s10346_i2c_clients,
.i2c_client_count = ARRAY_SIZE(medion_lifetab_s10346_i2c_clients),
- .gpiod_lookup_tables = medion_lifetab_s10346_gpios,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Nextbook Ares 8 (BYT) tablets have an Android factory image with everything hardcoded */
@@ -416,17 +351,12 @@ static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst
},
};
-static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = {
- &int3496_reference_gpios,
- NULL
-};
-
const struct x86_dev_info nextbook_ares8_info __initconst = {
.i2c_client_info = nextbook_ares8_i2c_clients,
.i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
- .gpiod_lookup_tables = nextbook_ares8_gpios,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Nextbook Ares 8A (CHT) tablets have an Android factory image with everything hardcoded */
@@ -445,6 +375,17 @@ static const struct software_node nextbook_ares8a_accel_node = {
.properties = nextbook_ares8a_accel_props,
};
+static const struct property_entry nextbook_ares8a_ft5416_props[] = {
+ PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
+ PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[1], 25, GPIO_ACTIVE_LOW),
+ { }
+};
+
+static const struct software_node nextbook_ares8a_ft5416_node = {
+ .properties = nextbook_ares8a_ft5416_props,
+};
+
static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initconst = {
{
/* Freescale MMA8653FC accelerometer */
@@ -461,7 +402,7 @@ static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initcons
.type = "edt-ft5x06",
.addr = 0x38,
.dev_name = "ft5416",
- .swnode = &nextbook_ares8_touchscreen_node,
+ .swnode = &nextbook_ares8a_ft5416_node,
},
.adapter_path = "\\_SB_.PCI0.I2C6",
.irq_data = {
@@ -475,23 +416,10 @@ static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initcons
},
};
-static struct gpiod_lookup_table nextbook_ares8a_ft5416_gpios = {
- .dev_id = "i2c-ft5416",
- .table = {
- GPIO_LOOKUP("INT33FF:01", 25, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const nextbook_ares8a_gpios[] = {
- &nextbook_ares8a_ft5416_gpios,
- NULL
-};
-
const struct x86_dev_info nextbook_ares8a_info __initconst = {
.i2c_client_info = nextbook_ares8a_i2c_clients,
.i2c_client_count = ARRAY_SIZE(nextbook_ares8a_i2c_clients),
- .gpiod_lookup_tables = nextbook_ares8a_gpios,
+ .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
};
/*
@@ -500,22 +428,32 @@ const struct x86_dev_info nextbook_ares8a_info __initconst = {
* This button has a WMI interface, but that is broken. Instead of trying to
* use the broken WMI interface, instantiate a gpio-keys device for this.
*/
-static const struct x86_gpio_button peaq_c1010_button __initconst = {
- .button = {
- .code = KEY_SOUND,
- .active_low = true,
- .desc = "dolby_key",
- .type = EV_KEY,
- .wakeup = false,
- .debounce_interval = 50,
- },
- .chip = "INT33FC:00",
- .pin = 3,
+static const struct software_node peaq_c1010_gpio_keys_node = {
+ .name = "gpio_keys",
+};
+
+static const struct property_entry peaq_c1010_dolby_key_props[] = {
+ PROPERTY_ENTRY_U32("linux,code", KEY_SOUND),
+ PROPERTY_ENTRY_STRING("label", "dolby_key"),
+ PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[0], 3, GPIO_ACTIVE_LOW),
+ PROPERTY_ENTRY_U32("debounce-interval", 50),
+ { }
+};
+
+static const struct software_node peaq_c1010_dolby_key_node = {
+ .parent = &peaq_c1010_gpio_keys_node,
+ .properties = peaq_c1010_dolby_key_props,
+};
+
+static const struct software_node *peaq_c1010_button_swnodes[] = {
+ &peaq_c1010_gpio_keys_node,
+ &peaq_c1010_dolby_key_node,
+ NULL
};
const struct x86_dev_info peaq_c1010_info __initconst = {
- .gpio_button = &peaq_c1010_button,
- .gpio_button_count = 1,
+ .gpio_button_swnodes = peaq_c1010_button_swnodes,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
@@ -543,6 +481,8 @@ static const struct property_entry whitelabel_tm800a550l_goodix_props[] = {
PROPERTY_ENTRY_STRING("firmware-name", "gt912-tm800a550l.fw"),
PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-tm800a550l.cfg"),
PROPERTY_ENTRY_U32("goodix,main-clk", 54),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 3, GPIO_ACTIVE_HIGH),
{ }
};
@@ -578,83 +518,118 @@ static const struct x86_i2c_client_info whitelabel_tm800a550l_i2c_clients[] __in
},
};
-static struct gpiod_lookup_table whitelabel_tm800a550l_goodix_gpios = {
- .dev_id = "i2c-goodix_ts",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH),
- { }
- },
+const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
+ .i2c_client_info = whitelabel_tm800a550l_i2c_clients,
+ .i2c_client_count = ARRAY_SIZE(whitelabel_tm800a550l_i2c_clients),
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
-static struct gpiod_lookup_table * const whitelabel_tm800a550l_gpios[] = {
- &whitelabel_tm800a550l_goodix_gpios,
- NULL
+/*
+ * Vexia EDU ATLA 10 tablet 5V, Android 4.4 + Guadalinex Ubuntu tablet
+ * distributed to schools in the Spanish Andalucía region.
+ */
+static const struct property_entry vexia_edu_atla10_5v_touchscreen_props[] = {
+ PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000),
+ PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
+ { }
};
-const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
- .i2c_client_info = whitelabel_tm800a550l_i2c_clients,
- .i2c_client_count = ARRAY_SIZE(whitelabel_tm800a550l_i2c_clients),
- .gpiod_lookup_tables = whitelabel_tm800a550l_gpios,
+static const struct software_node vexia_edu_atla10_5v_touchscreen_node = {
+ .properties = vexia_edu_atla10_5v_touchscreen_props,
+};
+
+static const struct x86_i2c_client_info vexia_edu_atla10_5v_i2c_clients[] __initconst = {
+ {
+ /* kxcjk1013 accelerometer */
+ .board_info = {
+ .type = "kxcjk1013",
+ .addr = 0x0f,
+ .dev_name = "kxcjk1013",
+ },
+ .adapter_path = "\\_SB_.I2C3",
+ }, {
+ /* touchscreen controller */
+ .board_info = {
+ .type = "hid-over-i2c",
+ .addr = 0x38,
+ .dev_name = "FTSC1000",
+ .swnode = &vexia_edu_atla10_5v_touchscreen_node,
+ },
+ .adapter_path = "\\_SB_.I2C4",
+ .irq_data = {
+ .type = X86_ACPI_IRQ_TYPE_APIC,
+ .index = 0x44,
+ .trigger = ACPI_LEVEL_SENSITIVE,
+ .polarity = ACPI_ACTIVE_HIGH,
+ },
+ }
+};
+
+const struct x86_dev_info vexia_edu_atla10_5v_info __initconst = {
+ .i2c_client_info = vexia_edu_atla10_5v_i2c_clients,
+ .i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_5v_i2c_clients),
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
- * Vexia EDU ATLA 10 tablet, Android 4.2 / 4.4 + Guadalinex Ubuntu tablet
+ * Vexia EDU ATLA 10 tablet 9V, Android 4.2 + Guadalinex Ubuntu tablet
* distributed to schools in the Spanish Andalucía region.
*/
static const char * const crystal_cove_pwrsrc_psy[] = { "crystal_cove_pwrsrc" };
-static const struct property_entry vexia_edu_atla10_ulpmc_props[] = {
+static const struct property_entry vexia_edu_atla10_9v_ulpmc_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", crystal_cove_pwrsrc_psy),
{ }
};
-static const struct software_node vexia_edu_atla10_ulpmc_node = {
- .properties = vexia_edu_atla10_ulpmc_props,
+static const struct software_node vexia_edu_atla10_9v_ulpmc_node = {
+ .properties = vexia_edu_atla10_9v_ulpmc_props,
};
-static const char * const vexia_edu_atla10_accel_mount_matrix[] = {
+static const char * const vexia_edu_atla10_9v_accel_mount_matrix[] = {
"0", "-1", "0",
"1", "0", "0",
"0", "0", "1"
};
-static const struct property_entry vexia_edu_atla10_accel_props[] = {
- PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", vexia_edu_atla10_accel_mount_matrix),
+static const struct property_entry vexia_edu_atla10_9v_accel_props[] = {
+ PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", vexia_edu_atla10_9v_accel_mount_matrix),
{ }
};
-static const struct software_node vexia_edu_atla10_accel_node = {
- .properties = vexia_edu_atla10_accel_props,
+static const struct software_node vexia_edu_atla10_9v_accel_node = {
+ .properties = vexia_edu_atla10_9v_accel_props,
};
-static const struct property_entry vexia_edu_atla10_touchscreen_props[] = {
+static const struct property_entry vexia_edu_atla10_9v_touchscreen_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
+ PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[0], 60, GPIO_ACTIVE_LOW),
{ }
};
-static const struct software_node vexia_edu_atla10_touchscreen_node = {
- .properties = vexia_edu_atla10_touchscreen_props,
+static const struct software_node vexia_edu_atla10_9v_touchscreen_node = {
+ .properties = vexia_edu_atla10_9v_touchscreen_props,
};
-static const struct property_entry vexia_edu_atla10_pmic_props[] = {
+static const struct property_entry vexia_edu_atla10_9v_pmic_props[] = {
PROPERTY_ENTRY_BOOL("linux,register-pwrsrc-power_supply"),
{ }
};
-static const struct software_node vexia_edu_atla10_pmic_node = {
- .properties = vexia_edu_atla10_pmic_props,
+static const struct software_node vexia_edu_atla10_9v_pmic_node = {
+ .properties = vexia_edu_atla10_9v_pmic_props,
};
-static const struct x86_i2c_client_info vexia_edu_atla10_i2c_clients[] __initconst = {
+static const struct x86_i2c_client_info vexia_edu_atla10_9v_i2c_clients[] __initconst = {
{
/* I2C attached embedded controller, used to access fuel-gauge */
.board_info = {
.type = "vexia_atla10_ec",
.addr = 0x76,
.dev_name = "ulpmc",
- .swnode = &vexia_edu_atla10_ulpmc_node,
+ .swnode = &vexia_edu_atla10_9v_ulpmc_node,
},
.adapter_path = "0000:00:18.1",
}, {
@@ -679,7 +654,7 @@ static const struct x86_i2c_client_info vexia_edu_atla10_i2c_clients[] __initcon
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
- .swnode = &vexia_edu_atla10_accel_node,
+ .swnode = &vexia_edu_atla10_9v_accel_node,
},
.adapter_path = "0000:00:18.5",
}, {
@@ -688,7 +663,7 @@ static const struct x86_i2c_client_info vexia_edu_atla10_i2c_clients[] __initcon
.type = "hid-over-i2c",
.addr = 0x38,
.dev_name = "FTSC1000",
- .swnode = &vexia_edu_atla10_touchscreen_node,
+ .swnode = &vexia_edu_atla10_9v_touchscreen_node,
},
.adapter_path = "0000:00:18.6",
.irq_data = {
@@ -703,7 +678,7 @@ static const struct x86_i2c_client_info vexia_edu_atla10_i2c_clients[] __initcon
.type = "intel_soc_pmic_crc",
.addr = 0x6e,
.dev_name = "intel_soc_pmic_crc",
- .swnode = &vexia_edu_atla10_pmic_node,
+ .swnode = &vexia_edu_atla10_9v_pmic_node,
},
.adapter_path = "0000:00:18.7",
.irq_data = {
@@ -715,7 +690,7 @@ static const struct x86_i2c_client_info vexia_edu_atla10_i2c_clients[] __initcon
}
};
-static const struct x86_serdev_info vexia_edu_atla10_serdevs[] __initconst = {
+static const struct x86_serdev_info vexia_edu_atla10_9v_serdevs[] __initconst = {
{
.ctrl.pci.devfn = PCI_DEVFN(0x1e, 3),
.ctrl_devname = "serial0",
@@ -723,20 +698,7 @@ static const struct x86_serdev_info vexia_edu_atla10_serdevs[] __initconst = {
},
};
-static struct gpiod_lookup_table vexia_edu_atla10_ft5416_gpios = {
- .dev_id = "i2c-FTSC1000",
- .table = {
- GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_LOW),
- { }
- },
-};
-
-static struct gpiod_lookup_table * const vexia_edu_atla10_gpios[] = {
- &vexia_edu_atla10_ft5416_gpios,
- NULL
-};
-
-static int __init vexia_edu_atla10_init(struct device *dev)
+static int __init vexia_edu_atla10_9v_init(struct device *dev)
{
struct pci_dev *pdev;
int ret;
@@ -749,8 +711,10 @@ static int __init vexia_edu_atla10_init(struct device *dev)
/* Reprobe the SDIO controller to enumerate the now enabled Wifi module */
pdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x11, 0));
- if (!pdev)
- return -EPROBE_DEFER;
+ if (!pdev) {
+ pr_warn("Could not get PCI SDIO at devfn 0x%02x\n", PCI_DEVFN(0x11, 0));
+ return 0;
+ }
ret = device_reprobe(&pdev->dev);
if (ret)
@@ -760,14 +724,14 @@ static int __init vexia_edu_atla10_init(struct device *dev)
return 0;
}
-const struct x86_dev_info vexia_edu_atla10_info __initconst = {
- .i2c_client_info = vexia_edu_atla10_i2c_clients,
- .i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_i2c_clients),
- .serdev_info = vexia_edu_atla10_serdevs,
- .serdev_count = ARRAY_SIZE(vexia_edu_atla10_serdevs),
- .gpiod_lookup_tables = vexia_edu_atla10_gpios,
- .init = vexia_edu_atla10_init,
+const struct x86_dev_info vexia_edu_atla10_9v_info __initconst = {
+ .i2c_client_info = vexia_edu_atla10_9v_i2c_clients,
+ .i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_9v_i2c_clients),
+ .serdev_info = vexia_edu_atla10_9v_serdevs,
+ .serdev_count = ARRAY_SIZE(vexia_edu_atla10_9v_serdevs),
+ .init = vexia_edu_atla10_9v_init,
.use_pci = true,
+ .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
@@ -863,7 +827,6 @@ static int xiaomi_mipad2_brightness_set(struct led_classdev *led_cdev,
static int __init xiaomi_mipad2_init(struct device *dev)
{
struct led_classdev *led_cdev;
- int ret;
xiaomi_mipad2_led_pwm = devm_pwm_get(dev, "pwm_soc_lpss_2");
if (IS_ERR(xiaomi_mipad2_led_pwm))
@@ -880,16 +843,7 @@ static int __init xiaomi_mipad2_init(struct device *dev)
/* Turn LED off during suspend */
led_cdev->flags = LED_CORE_SUSPENDRESUME;
- ret = devm_led_classdev_register(dev, led_cdev);
- if (ret)
- return dev_err_probe(dev, ret, "registering LED\n");
-
- return software_node_register_node_group(ktd2026_node_group);
-}
-
-static void xiaomi_mipad2_exit(void)
-{
- software_node_unregister_node_group(ktd2026_node_group);
+ return devm_led_classdev_register(dev, led_cdev);
}
/*
@@ -924,6 +878,6 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst
const struct x86_dev_info xiaomi_mipad2_info __initconst = {
.i2c_client_info = xiaomi_mipad2_i2c_clients,
.i2c_client_count = ARRAY_SIZE(xiaomi_mipad2_i2c_clients),
+ .swnode_group = ktd2026_node_group,
.init = xiaomi_mipad2_init,
- .exit = xiaomi_mipad2_exit,
};
diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c
index a46fa15acfb1..29fc466f76fe 100644
--- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c
+++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c
@@ -5,16 +5,18 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/gpio/machine.h>
+#include <linux/gpio/property.h>
#include <linux/platform_device.h>
#include <linux/power/bq24190_charger.h>
#include <linux/property.h>
#include <linux/regulator/machine.h>
#include "shared-psy-info.h"
+#include "x86-android-tablets.h"
/* Generic / shared charger / battery settings */
const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" };
@@ -39,6 +41,83 @@ const struct software_node fg_bq25890_supply_node = {
.properties = fg_bq25890_supply_props,
};
+static const u32 generic_lipo_battery_ovc_cap_celcius[] = { 25 };
+
+static const u32 generic_lipo_4v2_battery_ovc_cap_table0[] = {
+ 4200000, 100,
+ 4150000, 95,
+ 4110000, 90,
+ 4075000, 85,
+ 4020000, 80,
+ 3982500, 75,
+ 3945000, 70,
+ 3907500, 65,
+ 3870000, 60,
+ 3853333, 55,
+ 3836667, 50,
+ 3820000, 45,
+ 3803333, 40,
+ 3786667, 35,
+ 3770000, 30,
+ 3750000, 25,
+ 3730000, 20,
+ 3710000, 15,
+ 3690000, 10,
+ 3610000, 5,
+ 3350000, 0
+};
+
+static const u32 generic_lipo_hv_4v35_battery_ovc_cap_table0[] = {
+ 4300000, 100,
+ 4250000, 96,
+ 4200000, 91,
+ 4150000, 86,
+ 4110000, 82,
+ 4075000, 77,
+ 4020000, 73,
+ 3982500, 68,
+ 3945000, 64,
+ 3907500, 59,
+ 3870000, 55,
+ 3853333, 50,
+ 3836667, 45,
+ 3820000, 41,
+ 3803333, 36,
+ 3786667, 32,
+ 3770000, 27,
+ 3750000, 23,
+ 3730000, 18,
+ 3710000, 14,
+ 3690000, 9,
+ 3610000, 5,
+ 3350000, 0
+};
+
+/* Standard LiPo (max 4.2V) settings used by most devs with a LiPo battery */
+static const struct property_entry generic_lipo_4v2_battery_props[] = {
+ PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
+ PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"),
+ PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
+ PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
+ PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000),
+ PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000),
+ PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
+ PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-celsius",
+ generic_lipo_battery_ovc_cap_celcius),
+ PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-table-0",
+ generic_lipo_4v2_battery_ovc_cap_table0),
+ { }
+};
+
+const struct software_node generic_lipo_4v2_battery_node = {
+ .properties = generic_lipo_4v2_battery_props,
+};
+
+const struct software_node *generic_lipo_4v2_battery_swnodes[] = {
+ &generic_lipo_4v2_battery_node,
+ NULL
+};
+
/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV battery */
static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
@@ -48,6 +127,10 @@ static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000),
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000),
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
+ PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-celsius",
+ generic_lipo_battery_ovc_cap_celcius),
+ PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-table-0",
+ generic_lipo_hv_4v35_battery_ovc_cap_table0),
{ }
};
@@ -55,6 +138,11 @@ const struct software_node generic_lipo_hv_4v35_battery_node = {
.properties = generic_lipo_hv_4v35_battery_props,
};
+const struct software_node *generic_lipo_hv_4v35_battery_swnodes[] = {
+ &generic_lipo_hv_4v35_battery_node,
+ NULL
+};
+
/* For enabling the bq24190 5V boost based on id-pin */
static struct regulator_consumer_supply intel_int3496_consumer = {
.supply = "vbus",
@@ -80,21 +168,19 @@ const char * const bq24190_modules[] __initconst = {
NULL
};
-/* Generic platform device array and GPIO lookup table for micro USB ID pin handling */
+static const struct property_entry int3496_reference_props[] __initconst = {
+ PROPERTY_ENTRY_GPIO("vbus-gpios", &baytrail_gpiochip_nodes[1], 15, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_HIGH),
+ PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 18, GPIO_ACTIVE_HIGH),
+ { }
+};
+
+/* Generic pdevs array and gpio-lookups for micro USB ID pin handling */
const struct platform_device_info int3496_pdevs[] __initconst = {
{
/* For micro USB ID pin handling */
.name = "intel-int3496",
.id = PLATFORM_DEVID_NONE,
- },
-};
-
-struct gpiod_lookup_table int3496_reference_gpios = {
- .dev_id = "intel-int3496",
- .table = {
- GPIO_LOOKUP("INT33FC:01", 15, "vbus", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH),
- { }
+ .properties = int3496_reference_props,
},
};
diff --git a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h
index c2d2968cddc2..149befba3330 100644
--- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h
+++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h
@@ -5,13 +5,12 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#ifndef __PDX86_SHARED_PSY_INFO_H
#define __PDX86_SHARED_PSY_INFO_H
struct bq24190_platform_data;
-struct gpiod_lookup_table;
struct platform_device_info;
struct software_node;
@@ -21,12 +20,16 @@ extern const char * const bq25890_psy[];
extern const struct software_node fg_bq24190_supply_node;
extern const struct software_node fg_bq25890_supply_node;
+
+extern const struct software_node generic_lipo_4v2_battery_node;
+extern const struct software_node *generic_lipo_4v2_battery_swnodes[];
+
extern const struct software_node generic_lipo_hv_4v35_battery_node;
+extern const struct software_node *generic_lipo_hv_4v35_battery_swnodes[];
extern struct bq24190_platform_data bq24190_pdata;
extern const char * const bq24190_modules[];
extern const struct platform_device_info int3496_pdevs[];
-extern struct gpiod_lookup_table int3496_reference_gpios;
#endif
diff --git a/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c b/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c
index 5d02af1c5aaa..ebbedfe5f4e8 100644
--- a/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c
+++ b/drivers/platform/x86/x86-android-tablets/vexia_atla10_ec.c
@@ -183,7 +183,7 @@ static void atla10_ec_external_power_changed(struct power_supply *psy)
struct atla10_ec_data *data = power_supply_get_drvdata(psy);
/* After charger plug in/out wait 0.5s for things to stabilize */
- mod_delayed_work(system_wq, &data->work, HZ / 2);
+ mod_delayed_work(system_percpu_wq, &data->work, HZ / 2);
}
static const enum power_supply_property atla10_ec_psy_props[] = {
@@ -256,6 +256,6 @@ static struct i2c_driver atla10_ec_driver = {
};
module_i2c_driver(atla10_ec_driver);
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("Battery driver for Vexia EDU ATLA 10 tablet EC");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h
index 63a38a0069ba..2498390958ad 100644
--- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h
+++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h
@@ -5,19 +5,17 @@
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
- * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
*/
#ifndef __PDX86_X86_ANDROID_TABLETS_H
#define __PDX86_X86_ANDROID_TABLETS_H
#include <linux/gpio/consumer.h>
-#include <linux/gpio_keys.h>
#include <linux/i2c.h>
#include <linux/irqdomain_defs.h>
#include <linux/spi/spi.h>
struct gpio_desc;
-struct gpiod_lookup_table;
struct platform_device_info;
struct software_node;
@@ -32,6 +30,12 @@ enum x86_acpi_irq_type {
X86_ACPI_IRQ_TYPE_PMIC,
};
+enum x86_gpiochip_type {
+ X86_GPIOCHIP_UNSPECIFIED = 0,
+ X86_GPIOCHIP_BAYTRAIL,
+ X86_GPIOCHIP_CHERRYVIEW,
+};
+
struct x86_acpi_irq_data {
char *chip; /* GPIO chip label (GPIOINT) or PMIC ACPI path (PMIC) */
enum x86_acpi_irq_type type;
@@ -76,29 +80,22 @@ struct x86_serdev_info {
const char *serdev_hid;
};
-struct x86_gpio_button {
- struct gpio_keys_button button;
- const char *chip;
- int pin;
-};
-
struct x86_dev_info {
const char * const *modules;
- const struct software_node *bat_swnode;
- struct gpiod_lookup_table * const *gpiod_lookup_tables;
+ const struct software_node **swnode_group;
const struct x86_i2c_client_info *i2c_client_info;
const struct x86_spi_dev_info *spi_dev_info;
const struct platform_device_info *pdev_info;
const struct x86_serdev_info *serdev_info;
- const struct x86_gpio_button *gpio_button;
+ const struct software_node **gpio_button_swnodes;
int i2c_client_count;
int spi_dev_count;
int pdev_count;
int serdev_count;
- int gpio_button_count;
int (*init)(struct device *dev);
void (*exit)(void);
bool use_pci;
+ enum x86_gpiochip_type gpiochip_type;
};
int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
@@ -106,10 +103,15 @@ int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
struct gpio_desc **desc);
int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data);
+/* Software nodes representing GPIO chips used by various tablets */
+extern const struct software_node baytrail_gpiochip_nodes[];
+extern const struct software_node cherryview_gpiochip_nodes[];
+
/*
* Extern declarations of x86_dev_info structs so there can be a single
* MODULE_DEVICE_TABLE(dmi, ...), while splitting the board descriptions.
*/
+extern const struct x86_dev_info acer_a1_840_info;
extern const struct x86_dev_info acer_b1_750_info;
extern const struct x86_dev_info advantech_mica_071_info;
extern const struct x86_dev_info asus_me176c_info;
@@ -127,7 +129,8 @@ extern const struct x86_dev_info nextbook_ares8_info;
extern const struct x86_dev_info nextbook_ares8a_info;
extern const struct x86_dev_info peaq_c1010_info;
extern const struct x86_dev_info whitelabel_tm800a550l_info;
-extern const struct x86_dev_info vexia_edu_atla10_info;
+extern const struct x86_dev_info vexia_edu_atla10_5v_info;
+extern const struct x86_dev_info vexia_edu_atla10_9v_info;
extern const struct x86_dev_info xiaomi_mipad2_info;
extern const struct dmi_system_id x86_android_tablet_ids[];
diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c
index cbed29ca502a..3874f3336a0d 100644
--- a/drivers/platform/x86/xiaomi-wmi.c
+++ b/drivers/platform/x86/xiaomi-wmi.c
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/* WMI driver for Xiaomi Laptops */
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/input.h>
#include <linux/module.h>
@@ -26,13 +25,6 @@ struct xiaomi_wmi {
unsigned int key_code;
};
-static void xiaomi_mutex_destroy(void *data)
-{
- struct mutex *lock = data;
-
- mutex_destroy(lock);
-}
-
static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
{
struct xiaomi_wmi *data;
@@ -46,8 +38,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
return -ENOMEM;
dev_set_drvdata(&wdev->dev, data);
- mutex_init(&data->key_lock);
- ret = devm_add_action_or_reset(&wdev->dev, xiaomi_mutex_destroy, &data->key_lock);
+ ret = devm_mutex_init(&wdev->dev, &data->key_lock);
if (ret < 0)
return ret;
@@ -64,7 +55,7 @@ static int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context)
return input_register_device(data->input_dev);
}
-static void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
+static void xiaomi_wmi_notify(struct wmi_device *wdev, const struct wmi_buffer *dummy)
{
struct xiaomi_wmi *data = dev_get_drvdata(&wdev->dev);
@@ -92,8 +83,9 @@ static struct wmi_driver xiaomi_wmi_driver = {
.name = "xiaomi-wmi",
},
.id_table = xiaomi_wmi_id_table,
+ .min_event_size = 0,
.probe = xiaomi_wmi_probe,
- .notify = xiaomi_wmi_notify,
+ .notify_new = xiaomi_wmi_notify,
.no_singleton = true,
};
module_wmi_driver(xiaomi_wmi_driver);
diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c
index df2bf1c58523..4d1b1b310cc5 100644
--- a/drivers/platform/x86/xo15-ebook.c
+++ b/drivers/platform/x86/xo15-ebook.c
@@ -84,10 +84,9 @@ static int ebook_switch_add(struct acpi_device *device)
const struct acpi_device_id *id;
struct ebook_switch *button;
struct input_dev *input;
- char *name, *class;
int error;
- button = kzalloc(sizeof(struct ebook_switch), GFP_KERNEL);
+ button = kzalloc_obj(struct ebook_switch);
if (!button)
return -ENOMEM;
@@ -99,9 +98,6 @@ static int ebook_switch_add(struct acpi_device *device)
goto err_free_button;
}
- name = acpi_device_name(device);
- class = acpi_device_class(device);
-
id = acpi_match_acpi_device(ebook_device_ids, device);
if (!id) {
dev_err(&device->dev, "Unsupported hid\n");
@@ -109,12 +105,12 @@ static int ebook_switch_add(struct acpi_device *device)
goto err_free_input;
}
- strcpy(name, XO15_EBOOK_DEVICE_NAME);
- sprintf(class, "%s/%s", XO15_EBOOK_CLASS, XO15_EBOOK_SUBCLASS);
+ strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME);
+ strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS);
snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id);
- input->name = name;
+ input->name = acpi_device_name(device);
input->phys = button->phys;
input->id.bustype = BUS_HOST;
input->dev.parent = &device->dev;