summaryrefslogtreecommitdiff
path: root/drivers/platform/x86
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86')
-rw-r--r--drivers/platform/x86/Kconfig273
-rw-r--r--drivers/platform/x86/Makefile28
-rw-r--r--drivers/platform/x86/acerhdf.c4
-rw-r--r--drivers/platform/x86/amd/Kconfig11
-rw-r--r--drivers/platform/x86/amd/Makefile1
-rw-r--r--drivers/platform/x86/amd/amd_isp4.c419
-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.c276
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.c39
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.h10
-rw-r--r--drivers/platform/x86/amd/hsmp/hwmon.c121
-rw-r--r--drivers/platform/x86/amd/hsmp/plat.c34
-rw-r--r--drivers/platform/x86/amd/pmc/pmc-quirks.c20
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.c2
-rw-r--r--drivers/platform/x86/amd/pmf/core.c3
-rw-r--r--drivers/platform/x86/amd/pmf/tee-if.c108
-rw-r--r--drivers/platform/x86/asus-nb-wmi.c9
-rw-r--r--drivers/platform/x86/asus-wmi.c148
-rw-r--r--drivers/platform/x86/barco-p50-gpio.c10
-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.c1142
-rw-r--r--drivers/platform/x86/dell/dell-lis3lv02d.c2
-rw-r--r--drivers/platform/x86/dell/dell-pc.c67
-rw-r--r--drivers/platform/x86/dell/dell-uart-backlight.c2
-rw-r--r--drivers/platform/x86/dell/dell-wmi-ddv.c250
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h5
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c5
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c5
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c5
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c5
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/sysman.c12
-rw-r--r--drivers/platform/x86/dell/dell_rbu.c16
-rw-r--r--drivers/platform/x86/eeepc-laptop.c4
-rw-r--r--drivers/platform/x86/fujitsu-laptop.c8
-rw-r--r--drivers/platform/x86/hp/hp-bioscfg/bioscfg.c4
-rw-r--r--drivers/platform/x86/intel/hid.c1
-rw-r--r--drivers/platform/x86/intel/int0002_vgpio.c9
-rw-r--r--drivers/platform/x86/intel/int3472/Makefile3
-rw-r--r--drivers/platform/x86/intel/int3472/clk_and_regulator.c173
-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.c69
-rw-r--r--drivers/platform/x86/intel/int3472/discrete_quirks.c21
-rw-r--r--drivers/platform/x86/intel/int3472/led.c3
-rw-r--r--drivers/platform/x86/intel/int3472/tps68470.c3
-rw-r--r--drivers/platform/x86/intel/plr_tpmi.c3
-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.c13
-rw-r--r--drivers/platform/x86/intel/pmc/core.c240
-rw-r--r--drivers/platform/x86/intel/pmc/core.h29
-rw-r--r--drivers/platform/x86/intel/pmc/core_ssram.c332
-rw-r--r--drivers/platform/x86/intel/pmc/mtl.c10
-rw-r--r--drivers/platform/x86/intel/pmc/ssram_telemetry.c207
-rw-r--r--drivers/platform/x86/intel/pmc/ssram_telemetry.h24
-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.c50
-rw-r--r--drivers/platform/x86/intel/pmt/class.h12
-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.c635
-rw-r--r--drivers/platform/x86/intel/pmt/features.c205
-rw-r--r--drivers/platform/x86/intel/pmt/telemetry.c94
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_common.c21
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c112
-rw-r--r--drivers/platform/x86/intel/telemetry/core.c177
-rw-r--r--drivers/platform/x86/intel/telemetry/pltdrv.c231
-rw-r--r--drivers/platform/x86/intel/tpmi_power_domains.c36
-rw-r--r--drivers/platform/x86/intel/tpmi_power_domains.h1
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c34
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h20
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c63
-rw-r--r--drivers/platform/x86/intel/vsec.c381
-rw-r--r--drivers/platform/x86/intel/vsec_tpmi.c8
-rw-r--r--drivers/platform/x86/intel_ips.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)133
-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)104
-rw-r--r--drivers/platform/x86/lenovo/think-lmi.h (renamed from drivers/platform/x86/think-lmi.h)0
-rw-r--r--drivers/platform/x86/lenovo/thinkpad_acpi.c (renamed from drivers/platform/x86/thinkpad_acpi.c)56
-rw-r--r--drivers/platform/x86/lenovo/wmi-camera.c (renamed from drivers/platform/x86/lenovo-wmi-camera.c)0
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata01.c302
-rw-r--r--drivers/platform/x86/lenovo/wmi-capdata01.h25
-rw-r--r--drivers/platform/x86/lenovo/wmi-events.c196
-rw-r--r--drivers/platform/x86/lenovo/wmi-events.h20
-rw-r--r--drivers/platform/x86/lenovo/wmi-gamezone.c407
-rw-r--r--drivers/platform/x86/lenovo/wmi-gamezone.h20
-rw-r--r--drivers/platform/x86/lenovo/wmi-helpers.c74
-rw-r--r--drivers/platform/x86/lenovo/wmi-helpers.h20
-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.c665
-rw-r--r--drivers/platform/x86/lenovo/wmi-other.h16
-rw-r--r--drivers/platform/x86/lenovo/ymc.c (renamed from drivers/platform/x86/lenovo-ymc.c)0
-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)35
-rw-r--r--drivers/platform/x86/lenovo/yogabook.c (renamed from drivers/platform/x86/lenovo-yogabook.c)0
-rw-r--r--drivers/platform/x86/oxpec.c1072
-rw-r--r--drivers/platform/x86/panasonic-laptop.c4
-rw-r--r--drivers/platform/x86/portwell-ec.c292
-rw-r--r--drivers/platform/x86/samsung-galaxybook.c1
-rw-r--r--drivers/platform/x86/samsung-laptop.c110
-rw-r--r--drivers/platform/x86/silicom-platform.c13
-rw-r--r--drivers/platform/x86/sony-laptop.c175
-rw-r--r--drivers/platform/x86/topstar-laptop.c4
-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/wmi.c53
-rw-r--r--drivers/platform/x86/x86-android-tablets/asus.c21
-rw-r--r--drivers/platform/x86/x86-android-tablets/shared-psy-info.c76
-rw-r--r--drivers/platform/x86/x86-android-tablets/shared-psy-info.h1
-rw-r--r--drivers/platform/x86/xo15-ebook.c10
120 files changed, 10672 insertions, 2128 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 43407e76476b..6d238e120dce 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -37,6 +37,15 @@ config ACPI_WMI
It is safe to enable this driver even if your DSDT doesn't define
any ACPI-WMI devices.
+config ACPI_WMI_LEGACY_DEVICE_NAMES
+ bool "Use legacy WMI device naming scheme"
+ depends on ACPI_WMI
+ help
+ Say Y here to force the WMI driver core to use the old WMI device naming
+ scheme when creating WMI devices. Doing so might be necessary for some
+ userspace applications but will cause the registration of WMI devices with
+ the same GUID to fail in some corner cases.
+
config WMI_BMOF
tristate "WMI embedded Binary MOF driver"
depends on ACPI_WMI
@@ -120,32 +129,6 @@ 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"
- 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 ACERHDF
tristate "Acer Aspire One temperature and fan driver"
depends on ACPI_EC && THERMAL
@@ -459,43 +442,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 +460,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"
@@ -779,6 +573,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 +619,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 +873,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 +1009,21 @@ 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. Due to legacy
+ reasons, this driver also provides hwmon functionality to Ayaneo
+ devices and the OrangePi Neo.
+
+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..a0c5848513e3 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -58,17 +58,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 +89,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
@@ -112,6 +112,9 @@ obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
# 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 +125,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 +151,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/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/amd/Kconfig b/drivers/platform/x86/amd/Kconfig
index c3e086ea64fc..63e4bd985699 100644
--- a/drivers/platform/x86/amd/Kconfig
+++ b/drivers/platform/x86/amd/Kconfig
@@ -32,3 +32,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..b0e284b5d497 100644
--- a/drivers/platform/x86/amd/Makefile
+++ b/drivers/platform/x86/amd/Makefile
@@ -10,3 +10,4 @@ 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
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/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 73ca3f48c5cf..d974c2289f5a 100644
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -12,6 +12,9 @@
#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>
@@ -28,7 +31,6 @@
#include "hsmp.h"
#define DRIVER_NAME "hsmp_acpi"
-#define DRIVER_VERSION "2.3"
/* These are the strings specified in ACPI table */
#define MSG_IDOFF_STR "MsgIdOffset"
@@ -37,6 +39,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)
{
@@ -244,6 +251,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;
@@ -282,6 +498,12 @@ static int init_acpi(struct device *dev)
dev_err(dev, "Failed to init metric table\n");
}
+ ret = hsmp_create_sensor(dev, sock_ind);
+ if (ret)
+ dev_err(dev, "Failed to register HSMP sensors with hwmon\n");
+
+ dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]);
+
return ret;
}
@@ -296,9 +518,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,
+ .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,8 +587,10 @@ static int hsmp_acpi_probe(struct platform_device *pdev)
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)
+ if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES) {
+ dev_err(&pdev->dev, "Wrong number of sockets\n");
return -ENODEV;
+ }
hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
sizeof(*hsmp_pdev->sock),
@@ -340,9 +607,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 e262e8a97b45..885e2f8136fd 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -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
@@ -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 };
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h
index d58d4f0c20d5..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>
@@ -25,6 +27,8 @@
#define HSMP_DEVNODE_NAME "hsmp"
#define ACPI_HSMP_DEVICE_HID "AMDI0097"
+#define DRIVER_VERSION "2.5"
+
struct hsmp_mbaddr_info {
u32 base_addr;
u32 msg_id_off;
@@ -61,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 62bf9547631e..f8aa844d33e4 100644
--- a/drivers/platform/x86/amd/hsmp/plat.c
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -14,6 +14,8 @@
#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>
@@ -24,7 +26,6 @@
#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
@@ -190,6 +191,11 @@ static int init_platform_device(struct device *dev)
if (ret)
dev_err(dev, "Failed to init metric table\n");
}
+
+ /* Register with hwmon interface for reporting power */
+ ret = hsmp_create_sensor(dev, i);
+ if (ret)
+ dev_err(dev, "Failed to register HSMP sensors with hwmon\n");
}
return 0;
@@ -211,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)
@@ -283,15 +296,20 @@ 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;
}
- if (acpi_dev_present(ACPI_HSMP_DEVICE_HID, NULL, -1))
- return -ENODEV;
-
hsmp_pdev = get_hsmp_pdev();
if (!hsmp_pdev)
return -ENOMEM;
@@ -301,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/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c
index 5c7c01f66cde..ded4c84f5ed1 100644
--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c
+++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
@@ -11,7 +11,7 @@
#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/ioport.h>
-#include <asm/amd/fch.h>
+#include <linux/platform_data/x86/amd-fch.h>
#include "pmc.h"
@@ -190,6 +190,15 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "82XQ"),
}
},
+ /* https://gitlab.freedesktop.org/drm/amd/-/issues/4434 */
+ {
+ .ident = "Lenovo Yoga 6 13ALC6",
+ .driver_data = &quirk_s2idle_bug,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "82ND"),
+ }
+ },
/* https://gitlab.freedesktop.org/drm/amd/-/issues/2684 */
{
.ident = "HP Laptop 15s-eq2xxx",
@@ -225,6 +234,15 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_BOARD_NAME, "WUJIE14-GX4HRXL"),
}
},
+ /* 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"),
+ }
+ },
{}
};
diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index 37c7a57afee5..0b9b23eb7c2c 100644
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -157,6 +157,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);
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index 76910601cac8..ef988605c4da 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -280,7 +280,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;
}
@@ -493,7 +493,6 @@ static void amd_pmf_remove(struct platform_device *pdev)
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/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c
index d3bd12ad036a..4f626ebcb619 100644
--- a/drivers/platform/x86/amd/pmf/tee-if.c
+++ b/drivers/platform/x86/amd/pmf/tee-if.c
@@ -358,30 +358,28 @@ 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;
+
+ if (copy_from_user(new_policy_buf, buf, length)) {
+ devm_kfree(dev->dev, new_policy_buf);
+ return -EFAULT;
+ }
- kfree(dev->policy_buf);
+ devm_kfree(dev->dev, dev->policy_buf);
dev->policy_buf = new_policy_buf;
dev->policy_sz = length;
- if (!amd_pmf_pb_valid(dev)) {
- ret = -EINVAL;
- goto cleanup;
- }
+ if (!amd_pmf_pb_valid(dev))
+ return -EINVAL;
amd_pmf_hex_dump_pb(dev);
ret = amd_pmf_start_policy_engine(dev);
if (ret < 0)
- goto cleanup;
+ return ret;
return length;
-
-cleanup:
- kfree(dev->policy_buf);
- dev->policy_buf = NULL;
- return ret;
}
static const struct file_operations pb_fops = {
@@ -422,12 +420,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)
@@ -462,7 +460,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);
@@ -502,9 +502,12 @@ out_ctx:
static 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)
@@ -527,64 +530,45 @@ 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");
- ret = -EINVAL;
- goto err_free_policy;
+ 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;
- 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;
- }
-
+ dev_dbg(dev->dev, "start policy engine ret: %d\n", ret);
+ status = ret == TA_PMF_TYPE_SUCCESS;
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)
@@ -592,22 +576,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;
}
@@ -621,11 +595,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/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
index 3f8b2a324efd..f84c3d03c1de 100644
--- a/drivers/platform/x86/asus-nb-wmi.c
+++ b/drivers/platform/x86/asus-nb-wmi.c
@@ -530,6 +530,15 @@ 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,
+ },
{},
};
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 47cc766624d7..f7191fdded14 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -142,16 +142,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"),
@@ -274,9 +278,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;
@@ -336,6 +337,9 @@ 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;
+
/* WMI ************************************************************************/
static int asus_wmi_evaluate_method3(u32 method_id,
@@ -550,7 +554,7 @@ 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,
+int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param,
u32 *retval)
{
return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id,
@@ -1344,6 +1348,44 @@ static ssize_t nv_temp_target_show(struct device *dev,
static DEVICE_ATTR_RW(nv_temp_target);
/* 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");
+
static ssize_t mcu_powersave_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -4712,6 +4754,21 @@ 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 */
asus->ppt_pl2_sppt = 5;
asus->ppt_pl1_spl = 5;
@@ -4725,8 +4782,6 @@ static int asus_wmi_add(struct platform_device *pdev)
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->oobe_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_OOBE);
- 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;
@@ -4913,34 +4968,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);
@@ -4988,11 +5015,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,
};
@@ -5020,6 +5086,8 @@ static int asus_wmi_probe(struct platform_device *pdev)
return ret;
}
+ asus_s2idle_check_register();
+
return asus_wmi_add(pdev);
}
@@ -5052,6 +5120,8 @@ EXPORT_SYMBOL_GPL(asus_wmi_register_driver);
void asus_wmi_unregister_driver(struct asus_wmi_driver *driver)
{
+ asus_s2idle_check_unregister();
+
platform_device_unregister(driver->platform_device);
platform_driver_unregister(&driver->platform_driver);
used = false;
diff --git a/drivers/platform/x86/barco-p50-gpio.c b/drivers/platform/x86/barco-p50-gpio.c
index 143d14548565..bb3393bbfb89 100644
--- a/drivers/platform/x86/barco-p50-gpio.c
+++ b/drivers/platform/x86/barco-p50-gpio.c
@@ -268,15 +268,19 @@ static int p50_gpio_get(struct gpio_chip *gc, unsigned int offset)
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);
+ int ret;
mutex_lock(&p50->lock);
- p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO, gpio_params[offset], value);
+ ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO,
+ gpio_params[offset], value);
mutex_unlock(&p50->lock);
+
+ return ret;
}
static int p50_gpio_probe(struct platform_device *pdev)
@@ -312,7 +316,7 @@ static int p50_gpio_probe(struct platform_device *pdev)
p50->gc.base = -1;
p50->gc.get_direction = p50_gpio_get_direction;
p50->gc.get = p50_gpio_get;
- p50->gc.set = p50_gpio_set;
+ p50->gc.set_rv = p50_gpio_set;
/* reset mbox */
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 08b82c151e07..31f9643a6a3b 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,6 +90,14 @@ static struct awcc_quirks empty_quirks;
static const struct dmi_system_id awcc_dmi_table[] __initconst = {
{
+ .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 Area-51m R2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
@@ -70,6 +106,14 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
.driver_data = &generic_quirks,
},
{
+ .ident = "Alienware m15 R5",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m15 R5"),
+ },
+ .driver_data = &generic_quirks,
+ },
+ {
.ident = "Alienware m15 R7",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
@@ -91,7 +135,7 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
DMI_MATCH(DMI_SYS_VENDOR, "Alienware"),
DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m16 R1 AMD"),
},
- .driver_data = &g_series_quirks,
+ .driver_data = &generic_quirks,
},
{
.ident = "Alienware m16 R2",
@@ -205,40 +249,82 @@ static const struct dmi_system_id awcc_dmi_table[] __initconst = {
},
.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_SPECIAL_THERMAL_CODES {
+ AWCC_SPECIAL_PROFILE_CUSTOM = 0x00,
+ AWCC_SPECIAL_PROFILE_GMODE = 0xAB,
};
-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_TEMP_SENSOR_TYPES {
+ AWCC_TEMP_SENSOR_CPU = 0x01,
+ AWCC_TEMP_SENSOR_FRONT = 0x03,
+ AWCC_TEMP_SENSOR_GPU = 0x06,
+};
+
+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_USTT_BALANCED,
+ AWCC_PROFILE_USTT_BALANCED_PERFORMANCE,
+ AWCC_PROFILE_USTT_COOL,
+ AWCC_PROFILE_USTT_QUIET,
+ AWCC_PROFILE_USTT_PERFORMANCE,
+ AWCC_PROFILE_USTT_LOW_POWER,
+ AWCC_PROFILE_LEGACY_QUIET,
+ AWCC_PROFILE_LEGACY_BALANCED,
+ AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE,
+ AWCC_PROFILE_LEGACY_PERFORMANCE,
+ AWCC_PROFILE_LAST,
};
struct wmax_led_args {
@@ -263,23 +349,48 @@ 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];
+
+ u32 gpio_count;
};
-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,
+static const enum platform_profile_option awcc_mode_to_platform_profile[AWCC_PROFILE_LAST] = {
+ [AWCC_PROFILE_USTT_BALANCED] = PLATFORM_PROFILE_BALANCED,
+ [AWCC_PROFILE_USTT_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
+ [AWCC_PROFILE_USTT_COOL] = PLATFORM_PROFILE_COOL,
+ [AWCC_PROFILE_USTT_QUIET] = PLATFORM_PROFILE_QUIET,
+ [AWCC_PROFILE_USTT_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
+ [AWCC_PROFILE_USTT_LOW_POWER] = PLATFORM_PROFILE_LOW_POWER,
+ [AWCC_PROFILE_LEGACY_QUIET] = PLATFORM_PROFILE_QUIET,
+ [AWCC_PROFILE_LEGACY_BALANCED] = PLATFORM_PROFILE_BALANCED,
+ [AWCC_PROFILE_LEGACY_BALANCED_PERFORMANCE] = PLATFORM_PROFILE_BALANCED_PERFORMANCE,
+ [AWCC_PROFILE_LEGACY_PERFORMANCE] = PLATFORM_PROFILE_PERFORMANCE,
};
static struct awcc_quirks *awcc;
@@ -497,123 +608,693 @@ 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 bool is_awcc_thermal_profile_id(u8 code)
{
- if (code & WMAX_SENSOR_ID_MASK)
- return false;
+ u8 table = FIELD_GET(AWCC_THERMAL_TABLE_MASK, code);
+ u8 mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, code);
- if ((code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_LAST)
+ if (mode >= AWCC_PROFILE_LAST)
return false;
- if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_BASIC &&
- (code & WMAX_THERMAL_MODE_MASK) >= THERMAL_MODE_BASIC_QUIET)
+ if (table == AWCC_THERMAL_TABLE_LEGACY && mode >= AWCC_PROFILE_LEGACY_QUIET)
return true;
- if ((code & WMAX_THERMAL_TABLE_MASK) == WMAX_THERMAL_TABLE_USTT &&
- (code & WMAX_THERMAL_MODE_MASK) <= THERMAL_MODE_USTT_LOW_POWER)
+ if (table == AWCC_THERMAL_TABLE_USTT && mode <= AWCC_PROFILE_USTT_LOW_POWER)
return true;
return false;
}
-static int wmax_thermal_information(struct wmi_device *wdev, u8 operation,
- u8 arg, u32 *out_data)
+static int awcc_wmi_command(struct wmi_device *wdev, u32 method_id,
+ struct wmax_u32_args *args, u32 *out)
+{
+ int ret;
+
+ 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;
+}
+
+static int awcc_get_fan_sensors(struct wmi_device *wdev, u8 operation,
+ u8 fan_id, u8 index, u32 *out)
{
- struct wmax_u32_args in_args = {
+ 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 awcc_thermal_information(struct wmi_device *wdev, u8 operation, u8 arg,
+ u32 *out)
+{
+ 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;
+ }
- if (*out_data == WMAX_FAILURE_CODE)
+ 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;
+ }
+
+ 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;
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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 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;
- if (out_data == WMAX_THERMAL_MODE_GMODE) {
+ 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;
+
+ switch (out_data) {
+ case AWCC_SPECIAL_PROFILE_CUSTOM:
+ *profile = PLATFORM_PROFILE_CUSTOM;
+ return 0;
+ case AWCC_SPECIAL_PROFILE_GMODE:
*profile = PLATFORM_PROFILE_PERFORMANCE;
return 0;
+ default:
+ break;
}
- if (!is_wmax_thermal_code(out_data))
+ if (!is_awcc_thermal_profile_id(out_data))
return -ENODATA;
- out_data &= WMAX_THERMAL_MODE_MASK;
- *profile = wmax_mode_to_platform_profile[out_data];
+ out_data = FIELD_GET(AWCC_THERMAL_MODE_MASK, out_data);
+ *profile = awcc_mode_to_platform_profile[out_data];
return 0;
}
-static int thermal_profile_set(struct device *dev,
- enum platform_profile_option profile)
+static int awcc_platform_profile_set(struct device *dev,
+ enum platform_profile_option profile)
{
struct awcc_priv *priv = dev_get_drvdata(dev);
@@ -621,8 +1302,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)
@@ -630,8 +1311,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)
@@ -639,62 +1320,70 @@ 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;
+ enum awcc_thermal_profile mode;
+ 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);
+ /*
+ * 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))
+ if (!is_awcc_thermal_profile_id(id)) {
+ dev_dbg(&priv->wdev->dev, "Unmapped thermal profile ID 0x%02x\n", id);
continue;
+ }
- mode = out_data & WMAX_THERMAL_MODE_MASK;
- profile = wmax_mode_to_platform_profile[mode];
- priv->supported_thermal_profiles[profile] = out_data;
+ mode = FIELD_GET(AWCC_THERMAL_MODE_MASK, id);
+ profile = awcc_mode_to_platform_profile[mode];
+ priv->supported_profiles[profile] = id;
- set_bit(profile, choices);
+ __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_SPECIAL_PROFILE_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_SPECIAL_PROFILE_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)
@@ -707,6 +1396,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;
@@ -716,15 +1556,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;
}
@@ -775,6 +1637,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->hwmon)
+ awcc_hwmon_suspend(dev);
+
+ return 0;
+}
+
+static int wmax_wmi_resume(struct device *dev)
+{
+ if (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 },
{ },
@@ -785,6 +1665,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,
@@ -799,6 +1680,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/dell-lis3lv02d.c b/drivers/platform/x86/dell/dell-lis3lv02d.c
index efe26d667973..732de5f556f8 100644
--- a/drivers/platform/x86/dell/dell-lis3lv02d.c
+++ b/drivers/platform/x86/dell/dell-lis3lv02d.c
@@ -45,9 +45,11 @@ static const struct dmi_system_id lis3lv02d_devices[] __initconst = {
* Additional individual entries were added after verification.
*/
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("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..48cc7511905a 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]);
@@ -233,13 +229,13 @@ static int thermal_platform_profile_get(struct device *dev,
static int thermal_platform_profile_probe(void *drvdata, unsigned long *choices)
{
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);
return 0;
}
@@ -250,68 +246,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;
+ ppdev = devm_platform_profile_register(&fdev->dev, "dell-pc", NULL,
+ &dell_pc_platform_profile_ops);
-cleanup_platform_device:
- platform_device_unregister(platform_device);
-
- 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-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-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-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h
index 3ad33a094588..817ee7ba07ca 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,
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..fc2f58b4cbc6 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c
@@ -23,9 +23,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);
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..735248064239 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);
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 d8f1bf5e58a0..3167e06d416e 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);
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..0d2c74f8d1aa 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);
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
index d00389b860e4..f5402b714657 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
@@ -407,10 +407,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;
@@ -597,7 +597,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 +611,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..45c0a72e494a 100644
--- a/drivers/platform/x86/dell/dell_rbu.c
+++ b/drivers/platform/x86/dell/dell_rbu.c
@@ -45,7 +45,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 +77,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;
@@ -183,7 +183,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);
@@ -292,7 +292,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 +315,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,
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
index f52fbc4924d4..d1908815f5a2 100644
--- a/drivers/platform/x86/eeepc-laptop.c
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -1370,8 +1370,8 @@ static int eeepc_acpi_add(struct acpi_device *device)
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);
+ strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME);
+ strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS);
device->driver_data = eeepc;
eeepc->device = device;
diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c
index 162809140f68..931fbcdd21b8 100644
--- a/drivers/platform/x86/fujitsu-laptop.c
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@ -180,15 +180,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);
diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
index 13237890fc92..5bfa7159f5bc 100644
--- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
+++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c
@@ -1034,7 +1034,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 +1045,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/intel/hid.c b/drivers/platform/x86/intel/hid.c
index 0b5e43444ed6..f25a427cccda 100644
--- a/drivers/platform/x86/intel/hid.c
+++ b/drivers/platform/x86/intel/hid.c
@@ -54,6 +54,7 @@ static const struct acpi_device_id intel_hid_ids[] = {
{ "INTC107B" },
{ "INTC10CB" },
{ "INTC10CC" },
+ { "INTC10F1" },
{ }
};
MODULE_DEVICE_TABLE(acpi, intel_hid_ids);
diff --git a/drivers/platform/x86/intel/int0002_vgpio.c b/drivers/platform/x86/intel/int0002_vgpio.c
index 3b48cd7a4075..9bc24ed19c64 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,
@@ -192,7 +193,7 @@ static int int0002_probe(struct platform_device *pdev)
chip->parent = dev;
chip->owner = THIS_MODULE;
chip->get = int0002_gpio_get;
- chip->set = int0002_gpio_set;
+ chip->set_rv = int0002_gpio_set;
chip->direction_input = int0002_gpio_get;
chip->direction_output = int0002_gpio_direction_output;
chip->base = -1;
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..476ec24d3702 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,78 @@ 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);
+
+ /* 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 < 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;
+ 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");
-
- int3472->regulator.rdesc = INT3472_REGULATOR(
- int3472->regulator.regulator_name,
- int3472->regulator.supply_name,
- &int3472_gpio_regulator_ops);
+ snprintf(regulator->regulator_name, sizeof(regulator->regulator_name), "%s-%s",
+ acpi_dev_name(int3472->adev), supply_name);
- 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->regulators[int3472->n_regulator_gpios].ena_gpio = gpio;
+ 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);
+ gpiod_put(int3472->regulators[i].ena_gpio);
+ }
}
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..4c0aed6e626f 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
@@ -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;
@@ -142,12 +142,16 @@ struct int3472_gpio_map {
};
static const struct int3472_gpio_map int3472_gpio_map[] = {
+ /* mt9m114 designs declare a powerdown pin which controls the regulators */
+ { "INT33F0", INT3472_GPIO_TYPE_POWERDOWN, INT3472_GPIO_TYPE_POWER_ENABLE, false, "vdd" },
+ /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */
{ "INT347E", INT3472_GPIO_TYPE_RESET, INT3472_GPIO_TYPE_RESET, false, "enable" },
};
-static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
+static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type,
const char **con_id, unsigned long *gpio_flags)
{
+ struct acpi_device *adev = int3472->sensor;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(int3472_gpio_map); i++) {
@@ -162,6 +166,9 @@ 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;
@@ -187,7 +194,11 @@ static void int3472_get_con_id_and_polarity(struct acpi_device *adev, u8 *type,
*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_HANDSHAKE:
+ *con_id = "dvdd";
*gpio_flags = GPIO_ACTIVE_HIGH;
break;
default:
@@ -262,7 +273,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);
pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value);
/* Pin field is not really used under Windows and wraps around at 8 bits */
@@ -289,6 +300,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
case INT3472_GPIO_TYPE_CLK_ENABLE:
case INT3472_GPIO_TYPE_PRIVACY_LED:
case INT3472_GPIO_TYPE_POWER_ENABLE:
+ 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);
@@ -310,15 +322,31 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
break;
case INT3472_GPIO_TYPE_POWER_ENABLE:
- ret = skl_int3472_register_regulator(int3472, gpio);
+ ret = skl_int3472_register_regulator(int3472, gpio,
+ GPIO_REGULATOR_ENABLE_TIME,
+ con_id,
+ int3472->quirks.avdd_second_sensor);
if (ret)
- err_msg = "Failed to map regulator to sensor\n";
+ err_msg = "Failed to map power-enable to sensor\n";
+
+ break;
+ case INT3472_GPIO_TYPE_HANDSHAKE:
+ /* Setups using a handshake pin need 25 ms enable delay */
+ ret = skl_int3472_register_regulator(int3472, gpio,
+ 25 * USEC_PER_MSEC,
+ con_id, NULL);
+ if (ret)
+ err_msg = "Failed to map handshake to sensor\n";
break;
default: /* Never reached */
ret = -EINVAL;
break;
}
+
+ if (ret)
+ gpiod_put(gpio);
+
break;
default:
dev_warn(int3472->dev,
@@ -338,7 +366,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 +391,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_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 +447,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 +461,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 +490,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..f1d6d7b0cb75 100644
--- a/drivers/platform/x86/intel/int3472/led.c
+++ b/drivers/platform/x86/intel/int3472/led.c
@@ -4,7 +4,7 @@
#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)
@@ -56,4 +56,5 @@ void skl_int3472_unregister_pled(struct int3472_discrete_device *int3472)
led_remove_lookup(&int3472->pled.lookup);
led_classdev_unregister(&int3472->pled.classdev);
+ gpiod_put(int3472->pled.gpio);
}
diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c
index 81ac4c691963..0133405697dc 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
@@ -261,4 +261,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/plr_tpmi.c b/drivers/platform/x86/intel/plr_tpmi.c
index 2b55347a5a93..58132da47745 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>
@@ -256,7 +257,7 @@ DEFINE_SHOW_STORE_ATTRIBUTE(plr_status);
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;
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..5f68c8503a56 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
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 f9c48738b853..9d66d65e7577 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
@@ -651,29 +650,25 @@ static const struct pmc_reg_map arl_pchs_reg_map = {
.etr3_offset = ETR3_OFFSET,
};
-#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,
},
{}
diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c
index 9f678c753dfa..540cd2fb0673 100644
--- a/drivers/platform/x86/intel/pmc/core.c
+++ b/drivers/platform/x86/intel/pmc/core.c
@@ -29,6 +29,7 @@
#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 */
@@ -1345,6 +1346,198 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
}
}
+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;
+}
+
+/*
+ * 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 |
+ * +----+---------------------------------------------------------+
+ *
+ */
+static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct pci_dev *pcidev)
+{
+ 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(pcidev, guid, 0);
+ if (IS_ERR(ep)) {
+ dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %pe", 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;
+ }
+
+ 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);
+ goto unregister_ep;
+ }
+ ++req_offset;
+ }
+ mode_offset += LPM_REG_COUNT + LPM_MODE_OFFSET;
+ }
+
+unregister_ep:
+ pmt_telem_unregister_endpoint(ep);
+
+ return ret;
+}
+
+static int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev, int func)
+{
+ struct pci_dev *pcidev __free(pci_dev_put) = NULL;
+ unsigned int i;
+ int ret;
+
+ pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, func));
+ if (!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], pcidev);
+ 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_index)
+
+{
+ 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_index, &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_index];
+ /* 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_index] = 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
@@ -1362,10 +1555,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;
}
}
@@ -1381,10 +1582,26 @@ int generic_core_init(struct pmc_dev *pmcdev, struct pmc_dev_info *pmc_dev_info)
if (pmc_dev_info->dmu_guid)
pmc_core_punit_pmt_init(pmcdev, pmc_dev_info->dmu_guid);
- if (ssram)
- return pmc_core_ssram_get_lpm_reqs(pmcdev);
+ if (ssram) {
+ ret = pmc_core_ssram_get_lpm_reqs(pmcdev, pmc_dev_info->pci_func);
+ if (ret)
+ goto unmap_regbase;
+ }
return 0;
+
+unmap_regbase:
+ for (unsigned int i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
+ struct pmc *pmc = pmcdev->pmcs[i];
+
+ 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[] = {
@@ -1471,20 +1688,14 @@ static void pmc_core_clean_structure(struct platform_device *pdev)
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
struct pmc *pmc = pmcdev->pmcs[i];
- 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 +1740,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,7 +1750,7 @@ 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;
}
@@ -1719,5 +1932,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..4a94a4ee031e 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
@@ -293,6 +298,25 @@ enum ppfear_regs {
#define PTL_PMC_LTR_CUR_PLT 0x1C2C
#define PTL_PCD_PMC_MMIO_REG_LEN 0x31A8
+/* SSRAM PMC Device ID */
+/* LNL */
+#define PMC_DEVID_LNL_SOCM 0xa87f
+
+/* PTL */
+#define PMC_DEVID_PTL_PCDH 0xe37f
+#define PMC_DEVID_PTL_PCDP 0xe47f
+
+/* 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[];
struct pmc_bit_map {
@@ -396,7 +420,6 @@ struct pmc {
* 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
@@ -416,7 +439,6 @@ 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 */
@@ -485,7 +507,6 @@ extern const struct pmc_reg_map mtl_socm_reg_map;
extern const struct pmc_reg_map mtl_ioep_reg_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);
@@ -494,8 +515,6 @@ 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_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;
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/mtl.c b/drivers/platform/x86/intel/pmc/mtl.c
index 8862829694a7..faa13a7ee688 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
@@ -947,23 +946,20 @@ static const struct pmc_reg_map mtl_ioem_reg_map = {
.lpm_reg_index = MTL_LPM_REG_INDEX,
};
-#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
},
{}
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..93579152188e
--- /dev/null
+++ b/drivers/platform/x86/intel/pmc/ssram_telemetry.c
@@ -0,0 +1,207 @@
+// 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, &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) },
+ { }
+};
+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/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..edcce340ea67 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"
@@ -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->pcidev, entry->cb, entry->header.guid, buf,
entry->base, off, count);
return count;
@@ -166,12 +168,41 @@ 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,
@@ -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;
}
@@ -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..3c5ad5f52bca 100644
--- a/drivers/platform/x86/intel/pmt/class.h
+++ b/drivers/platform/x86/intel/pmt/class.h
@@ -20,6 +20,7 @@
#define GET_ADDRESS(v) ((v) & GENMASK(31, 3))
struct pci_dev;
+extern struct class intel_pmt_class;
struct telem_endpoint {
struct pci_dev *pcidev;
@@ -39,22 +40,25 @@ 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,
@@ -69,4 +73,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..32713a194a55
--- /dev/null
+++ b/drivers/platform/x86/intel/pmt/discovery.c
@@ -0,0 +1,635 @@
+// 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)
+ 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->pcidev->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(priv->dev, PTR_ERR(priv->dev),
+ "Could not create %s-%s device node\n",
+ "features", dev_name(priv->dev));
+
+ /* 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->pcidev->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..a4dfca6cac19 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"
@@ -206,6 +214,87 @@ 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 oobmsm_plat_info *plat_info;
+
+ plat_info = intel_vsec_get_mapping(entry->ep->pcidev);
+ 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;
@@ -311,6 +400,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 +439,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/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
index 8a5713593811..71e104a068e9 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
@@ -199,25 +199,6 @@ void isst_resume_common(void)
}
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)
- wrmsrq_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.
@@ -435,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;
}
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 4d30d5360c8f..34bff2f65a83 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,6 +22,7 @@
#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>
@@ -35,7 +36,7 @@
/* Supported SST hardware version by this driver */
#define ISST_MAJOR_VERSION 0
-#define ISST_MINOR_VERSION 1
+#define ISST_MINOR_VERSION 2
/*
* Used to indicate if value read from MMIO needs to get multiplied
@@ -381,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));
@@ -1017,6 +1018,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
@@ -1069,6 +1071,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;
@@ -1168,6 +1179,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
@@ -1329,9 +1393,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
@@ -1341,10 +1410,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)))
@@ -1361,6 +1434,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,
@@ -1377,12 +1454,32 @@ static int isst_if_get_turbo_freq_info(void __user *argp)
SST_MUL_FACTOR_FREQ)
}
+ 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;
@@ -1421,6 +1518,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;
@@ -1447,7 +1547,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;
@@ -1599,7 +1699,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)
@@ -1621,7 +1721,7 @@ 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 oobmsm_plat_info *plat_info;
void __iomem *cp_base;
plat_info = tpmi_get_platform_data(auxdev);
@@ -1649,7 +1749,7 @@ 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 oobmsm_plat_info *plat_info;
void __iomem *cp_base;
plat_info = tpmi_get_platform_data(auxdev);
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/pltdrv.c b/drivers/platform/x86/intel/telemetry/pltdrv.c
index 9a499efa1e4d..f23c170a55dc 100644
--- a/drivers/platform/x86/intel/telemetry/pltdrv.c
+++ b/drivers/platform/x86/intel/telemetry/pltdrv.c
@@ -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 c21b3cb99b7c..9d8247bb9cfa 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);
@@ -152,6 +154,15 @@ 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;
@@ -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;
}
@@ -212,17 +226,30 @@ static int __init tpmi_init(void)
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/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
index 4e2c6a2d7e6e..65897fae17df 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
@@ -43,6 +43,29 @@ static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr
return sprintf(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)
{
unsigned int value;
@@ -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;
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..70ae11519837 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,6 +37,7 @@
* @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
@@ -41,6 +54,7 @@
* @elc_high_threshold_enable_kobj_attr:
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
* @uncore_attrs: Attribute storage for group creation
*
* This structure is used to encapsulate all data related to uncore sysfs
@@ -58,6 +72,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 +87,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 +102,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..6df55c8e16b7 100644
--- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
@@ -22,10 +22,12 @@
#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
@@ -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;
};
@@ -347,9 +350,31 @@ 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);
+}
+
/* 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 +389,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,6 +448,16 @@ 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)
@@ -421,7 +466,7 @@ static void remove_cluster_entries(struct tpmi_uncore_struct *tpmi_uncore)
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,10 +512,13 @@ 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;
@@ -552,12 +600,16 @@ 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);
+
cluster_info->uncore_root = tpmi_uncore;
if (TPMI_MINOR_VERSION(pd_info->ufs_header_ver) >= UNCORE_ELC_SUPPORTED_VERSION)
@@ -631,5 +683,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/vsec.c b/drivers/platform/x86/intel/vsec.c
index db3c031d1757..f66f0ce8559b 100644
--- a/drivers/platform/x86/intel/vsec.c
+++ b/drivers/platform/x86/intel/vsec.c
@@ -15,9 +15,12 @@
#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>
@@ -32,6 +35,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 {
+ 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 +67,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 +88,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;
}
@@ -91,6 +113,97 @@ static void intel_vsec_dev_release(struct device *dev)
kfree(intel_vsec_dev);
}
+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 pci_dev *pdev, struct device *dev,
+ int consumer_id)
+{
+ const struct vsec_feature_dependency *deps;
+ enum vsec_device_state *state;
+ struct device **suppliers;
+ struct vsec_priv *priv;
+ int supplier_id;
+
+ if (!consumer_id)
+ return 0;
+
+ 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 pci_dev *pdev, struct device *parent,
struct intel_vsec_device *intel_vsec_dev,
const char *name)
@@ -128,19 +241,37 @@ 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(pdev, &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)
+ struct intel_vsec_platform_info *info,
+ unsigned long cap_id)
{
struct intel_vsec_device __free(kfree) *intel_vsec_dev = NULL;
struct resource __free(kfree) *res = NULL;
@@ -207,6 +338,7 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
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;
if (header->id == VSEC_ID_SDSI)
intel_vsec_dev->ida = &intel_vsec_sdsi_ida;
@@ -221,6 +353,109 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
intel_vsec_name(header->id));
}
+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 pci_dev *pdev,
+ struct intel_vsec_header *header,
+ struct intel_vsec_platform_info *info)
+{
+ const struct vsec_feature_dependency *consumer_deps;
+ struct vsec_priv *priv;
+ 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 (!pci_match_id(intel_vsec_pci_ids, pdev))
+ return intel_vsec_add_dev(pdev, header, info, cap_id);
+
+ 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(pdev, header, info, cap_id);
+ if (ret)
+ priv->state[cap_id] = STATE_SKIP;
+ else
+ priv->state[cap_id] = STATE_REGISTERED;
+
+ return ret;
+ }
+
+ return -EAGAIN;
+}
+
static bool intel_vsec_walk_header(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
@@ -229,7 +464,7 @@ static bool intel_vsec_walk_header(struct pci_dev *pdev,
int ret;
for ( ; *header; header++) {
- ret = intel_vsec_add_dev(pdev, *header, info);
+ ret = intel_vsec_register_device(pdev, *header, info);
if (!ret)
have_devices = true;
}
@@ -277,7 +512,7 @@ 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, &header, info);
if (ret)
continue;
@@ -322,7 +557,7 @@ 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, &header, info);
if (ret)
continue;
@@ -332,21 +567,69 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev,
return have_devices;
}
-void intel_vsec_register(struct pci_dev *pdev,
+int intel_vsec_register(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
if (!pdev || !info || !info->headers)
- return;
+ return -EINVAL;
- intel_vsec_walk_header(pdev, info);
+ if (!intel_vsec_walk_header(pdev, info))
+ return -ENODEV;
+ else
+ return 0;
}
EXPORT_SYMBOL_NS_GPL(intel_vsec_register, "INTEL_VSEC");
+static bool intel_vsec_get_features(struct pci_dev *pdev,
+ 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, 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_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct intel_vsec_platform_info *info;
- bool have_devices = false;
- int ret;
+ struct vsec_priv *priv;
+ int num_caps, ret;
+ int run_once = 0;
+ bool found_any = false;
ret = pcim_enable_device(pdev);
if (ret)
@@ -357,22 +640,62 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id
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;
+ 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 (!have_devices)
+ if (!run_once) {
+ intel_vsec_skip_missing_dependencies(pdev);
+ run_once = 1;
+ }
+ }
+
+ if (!found_any)
return -ENODEV;
return 0;
}
+int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info,
+ struct intel_vsec_device *vsec_dev)
+{
+ struct vsec_priv *priv;
+
+ priv = pci_get_drvdata(vsec_dev->pcidev);
+ 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 = {
.length = 0x10,
@@ -399,14 +722,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 */
diff --git a/drivers/platform/x86/intel/vsec_tpmi.c b/drivers/platform/x86/intel/vsec_tpmi.c
index 5c383a27bbe8..7748b5557a18 100644
--- a/drivers/platform/x86/intel/vsec_tpmi.c
+++ b/drivers/platform/x86/intel/vsec_tpmi.c
@@ -116,7 +116,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 +187,7 @@ 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)
+struct oobmsm_plat_info *tpmi_get_platform_data(struct auxiliary_device *auxdev)
{
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
@@ -799,6 +799,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)
diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c
index 9506f28fb7d8..b1b2d9caba7b 100644
--- a/drivers/platform/x86/intel_ips.c
+++ b/drivers/platform/x86/intel_ips.c
@@ -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);
}
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
new file mode 100644
index 000000000000..d22b774e0236
--- /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_DATA01
+ 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
+ select LENOVO_WMI_TUNING
+ 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 FW_ATTR_CLASS
+ select LENOVO_WMI_DATA01
+ 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..7b2128e3a214
--- /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_DATA01) += wmi-capdata01.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 ede483573fe0..fcebfbaf0460 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,6 +28,7 @@
#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/sysfs.h>
@@ -34,6 +36,7 @@
#include <linux/wmi.h>
#include "ideapad-laptop.h"
+#include <acpi/battery.h>
#include <acpi/video.h>
#include <dt-bindings/leds/common.h>
@@ -162,6 +165,7 @@ struct ideapad_private {
struct backlight_device *blightdev;
struct ideapad_dytc_priv *dytc;
struct dentry *debug;
+ struct acpi_battery_hook battery_hook;
unsigned long cfg;
unsigned long r_touchpad_val;
struct {
@@ -267,6 +271,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 +401,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 +432,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)
@@ -589,6 +607,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,6 +620,8 @@ static ssize_t conservation_mode_show(struct device *dev,
unsigned long result;
int err;
+ show_conservation_mode_deprecation_warning(dev);
+
err = eval_gbmd(priv->adev->handle, &result);
if (err)
return err;
@@ -612,6 +637,8 @@ 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;
@@ -1654,7 +1681,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)
@@ -1713,7 +1740,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)
@@ -1973,10 +2000,90 @@ 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;
+
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
+ return exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_ON);
+ case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
+ return exec_sbmc(priv->adev->handle, SBMC_CONSERVATION_OFF);
+ default:
+ return -EINVAL;
+ }
+}
+
+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;
+ unsigned long result;
+ int err;
+
+ err = eval_gbmd(priv->adev->handle, &result);
+ if (err)
+ return err;
+
+ if (test_bit(GBMD_CONSERVATION_STATE_BIT, &result))
+ 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,
+};
+
+static const struct power_supply_ext ideapad_battery_ext = {
+ .name = "ideapad_laptop",
+ .properties = ideapad_power_supply_props,
+ .num_properties = ARRAY_SIZE(ideapad_power_supply_props),
+ .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
+ .get_property = ideapad_psy_ext_get_prop,
+ .set_property = ideapad_psy_ext_set_prop,
+ .property_is_writeable = ideapad_psy_prop_is_writeable,
+};
+
+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, &ideapad_battery_ext,
+ &priv->platform_device->dev, priv);
+}
+
+static int ideapad_battery_remove(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &ideapad_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);
@@ -1991,8 +2098,16 @@ 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"))
+ if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
priv->features.conservation_mode = true;
+ 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;
@@ -2027,6 +2142,8 @@ static void ideapad_check_features(struct ideapad_private *priv)
}
}
}
+
+ return 0;
}
#if IS_ENABLED(CONFIG_ACPI_WMI)
@@ -2175,7 +2292,9 @@ static int ideapad_acpi_add(struct platform_device *pdev)
if (err)
return err;
- ideapad_check_features(priv);
+ 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 00b1e7c79a3d..0992b41b6221 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;
@@ -772,6 +772,7 @@ 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;
int ret;
@@ -789,9 +790,10 @@ 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;
@@ -973,6 +975,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,
@@ -1188,6 +1191,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 +1211,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,
@@ -1380,21 +1386,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 +1405,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 +1447,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 +1471,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 +1492,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 +1529,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;
}
@@ -1577,8 +1552,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;
}
@@ -1683,7 +1656,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);
}
@@ -1781,7 +1753,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 9b014644d316..9b014644d316 100644
--- a/drivers/platform/x86/think-lmi.h
+++ b/drivers/platform/x86/lenovo/think-lmi.h
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index 657625dd60a0..cc19fe520ea9 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -81,7 +81,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 +182,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 */
@@ -558,12 +559,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;
}
@@ -837,9 +838,9 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm)
}
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);
@@ -2251,6 +2252,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;
@@ -3275,6 +3295,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 }
};
@@ -3304,7 +3325,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");
@@ -3468,6 +3489,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,
@@ -11166,6 +11193,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:
@@ -11242,6 +11271,19 @@ 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;
return true;
diff --git a/drivers/platform/x86/lenovo-wmi-camera.c b/drivers/platform/x86/lenovo/wmi-camera.c
index eb60fb9a5b3f..eb60fb9a5b3f 100644
--- a/drivers/platform/x86/lenovo-wmi-camera.c
+++ b/drivers/platform/x86/lenovo/wmi-camera.c
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform/x86/lenovo/wmi-capdata01.c
new file mode 100644
index 000000000000..c922680b3cba
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-capdata01.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Capability Data 01 WMI Data Block driver.
+ *
+ * Lenovo Capability Data 01 provides information on tunable attributes used by
+ * the "Other Mode" WMI interface. The data includes if the attribute is
+ * supported by the hardware, 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.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.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/module.h>
+#include <linux/mutex.h>
+#include <linux/mutex_types.h>
+#include <linux/notifier.h>
+#include <linux/overflow.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-capdata01.h"
+
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+#define ACPI_AC_CLASS "ac_adapter"
+#define ACPI_AC_NOTIFY_STATUS 0x80
+
+struct lwmi_cd01_priv {
+ struct notifier_block acpi_nb; /* ACPI events */
+ struct wmi_device *wdev;
+ struct cd01_list *list;
+};
+
+struct cd01_list {
+ struct mutex list_mutex; /* list R/W mutex */
+ u8 count;
+ struct capdata01 data[];
+};
+
+/**
+ * lwmi_cd01_component_bind() - Bind component to master device.
+ * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: capdata01_list object pointer used to return the capability data.
+ *
+ * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
+ * list. This is used to call lwmi_cd01_get_data to look up attribute data
+ * from the lenovo-wmi-other driver.
+ *
+ * Return: 0
+ */
+static int lwmi_cd01_component_bind(struct device *cd01_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
+ struct cd01_list **cd01_list = data;
+
+ *cd01_list = priv->list;
+
+ return 0;
+}
+
+static const struct component_ops lwmi_cd01_component_ops = {
+ .bind = lwmi_cd01_component_bind,
+};
+
+/**
+ * lwmi_cd01_get_data - Get the data of the specified attribute
+ * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct.
+ * @attribute_id: The capdata attribute ID to be found.
+ * @output: Pointer to a capdata01 struct to return the data.
+ *
+ * Retrieves the capability data 01 struct pointer for the given
+ * attribute for its specified thermal mode.
+ *
+ * Return: 0 on success, or -EINVAL.
+ */
+int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
+{
+ u8 idx;
+
+ guard(mutex)(&list->list_mutex);
+ for (idx = 0; idx < list->count; idx++) {
+ if (list->data[idx].id != attribute_id)
+ continue;
+ memcpy(output, &list->data[idx], sizeof(list->data[idx]));
+ return 0;
+ };
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
+
+/**
+ * lwmi_cd01_cache() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Loop through each WMI data block and cache the data.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
+{
+ int idx;
+
+ guard(mutex)(&priv->list->list_mutex);
+ for (idx = 0; idx < priv->list->count; idx++) {
+ 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 < sizeof(priv->list->data[idx]))
+ continue;
+
+ memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
+ ret_obj->buffer.length);
+ }
+
+ return 0;
+}
+
+/**
+ * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Allocate a cd01_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_cd01_alloc(struct lwmi_cd01_priv *priv)
+{
+ struct cd01_list *list;
+ size_t list_size;
+ int count, ret;
+
+ count = wmidev_instance_count(priv->wdev);
+ list_size = struct_size(list, data, count);
+
+ list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
+ if (ret)
+ return ret;
+
+ list->count = count;
+ priv->list = list;
+
+ return 0;
+}
+
+/**
+ * lwmi_cd01_setup() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Allocate a cd01_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_cd01_setup(struct lwmi_cd01_priv *priv)
+{
+ int ret;
+
+ ret = lwmi_cd01_alloc(priv);
+ if (ret)
+ return ret;
+
+ return lwmi_cd01_cache(priv);
+}
+
+/**
+ * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver 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_cd01_priv *priv;
+ int ret;
+
+ if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
+ return NOTIFY_DONE;
+
+ priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
+
+ switch (event->type) {
+ case ACPI_AC_NOTIFY_STATUS:
+ ret = lwmi_cd01_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_cd01_probe(struct wmi_device *wdev, const void *context)
+
+{
+ struct lwmi_cd01_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);
+
+ ret = lwmi_cd01_setup(priv);
+ if (ret)
+ return ret;
+
+ priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
+
+ ret = register_acpi_notifier(&priv->acpi_nb);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
+ if (ret)
+ return ret;
+
+ return component_add(&wdev->dev, &lwmi_cd01_component_ops);
+}
+
+static void lwmi_cd01_remove(struct wmi_device *wdev)
+{
+ component_del(&wdev->dev, &lwmi_cd01_component_ops);
+}
+
+static const struct wmi_device_id lwmi_cd01_id_table[] = {
+ { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
+ {}
+};
+
+static struct wmi_driver lwmi_cd01_driver = {
+ .driver = {
+ .name = "lenovo_wmi_cd01",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .id_table = lwmi_cd01_id_table,
+ .probe = lwmi_cd01_probe,
+ .remove = lwmi_cd01_remove,
+ .no_singleton = true,
+};
+
+/**
+ * lwmi_cd01_match() - Match rule for the master driver.
+ * @dev: Pointer to the capability data 01 parent device.
+ * @data: Unused void pointer for passing match criteria.
+ *
+ * Return: int.
+ */
+int lwmi_cd01_match(struct device *dev, void *data)
+{
+ return dev->driver == &lwmi_cd01_driver.driver;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
+
+module_wmi_driver(lwmi_cd01_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.h b/drivers/platform/x86/lenovo/wmi-capdata01.h
new file mode 100644
index 000000000000..bd06c5751f68
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-capdata01.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_CAPDATA01_H_
+#define _LENOVO_WMI_CAPDATA01_H_
+
+#include <linux/types.h>
+
+struct device;
+struct cd01_list;
+
+struct capdata01 {
+ u32 id;
+ u32 supported;
+ u32 default_value;
+ u32 step;
+ u32 min_value;
+ u32 max_value;
+};
+
+int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
+int lwmi_cd01_match(struct device *dev, void *data);
+
+#endif /* !_LENOVO_WMI_CAPDATA01_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..0994cd7dd504
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-events.c
@@ -0,0 +1,196 @@
+// 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-gamezone.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,
+ .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..0eb7fe8222f4
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-gamezone.c
@@ -0,0 +1,407 @@
+// 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-gamezone.h"
+#include "wmi-helpers.h"
+#include "wmi-other.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
+
+static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
+
+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:
+ if (priv->extreme_supported) {
+ *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+ break;
+ }
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case LWMI_GZ_THERMAL_MODE_EXTREME:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ 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_BALANCED_PERFORMANCE:
+ mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_PERFORMANCE:
+ if (priv->extreme_supported) {
+ mode = LWMI_GZ_THERMAL_MODE_EXTREME;
+ break;
+ }
+ mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
+ 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,
+ },
+ {},
+
+};
+
+/**
+ * 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_BALANCED_PERFORMANCE, 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_om_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_IMPORT_NS("LENOVO_WMI_OTHER");
+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-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h
new file mode 100644
index 000000000000..6b163a5eeb95
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-gamezone.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_GAMEZONE_H_
+#define _LENOVO_WMI_GAMEZONE_H_
+
+enum gamezone_events_type {
+ LWMI_GZ_GET_THERMAL_MODE = 1,
+};
+
+enum thermal_mode {
+ 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,
+};
+
+#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
new file mode 100644
index 000000000000..f6fef6296251
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -0,0 +1,74 @@
+// 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/wmi.h>
+
+#include "wmi-helpers.h"
+
+/**
+ * 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 };
+ union acpi_object *ret_obj __free(kfree) = 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;
+
+ if (retval) {
+ ret_obj = output.pointer;
+ if (!ret_obj)
+ return -ENODATA;
+
+ if (ret_obj->type != ACPI_TYPE_INTEGER)
+ return -ENXIO;
+
+ *retval = (u32)ret_obj->integer.value;
+ }
+
+ return 0;
+};
+EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_int, "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..20fd21749803
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-helpers.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_HELPERS_H_
+#define _LENOVO_WMI_HELPERS_H_
+
+#include <linux/types.h>
+
+struct wmi_device;
+
+struct wmi_method_args_32 {
+ u32 arg0;
+ u32 arg1;
+};
+
+int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
+ unsigned char *buf, size_t size, u32 *retval);
+
+#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..2a960b278f11
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -0,0 +1,665 @@
+// 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.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#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/idr.h>
+#include <linux/kdev_t.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-capdata01.h"
+#include "wmi-events.h"
+#include "wmi-gamezone.h"
+#include "wmi-helpers.h"
+#include "wmi-other.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_TYPE_ID_NONE 0x00
+
+#define LWMI_FEATURE_VALUE_GET 17
+#define LWMI_FEATURE_VALUE_SET 18
+
+#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_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
+
+static BLOCKING_NOTIFIER_HEAD(om_chain_head);
+static DEFINE_IDA(lwmi_om_ida);
+
+enum attribute_property {
+ DEFAULT_VAL,
+ MAX_VAL,
+ MIN_VAL,
+ STEP_VAL,
+ SUPPORTED,
+};
+
+struct lwmi_om_priv {
+ struct component_master_ops *ops;
+ struct cd01_list *cd01_list; /* only valid after capdata01 bind */
+ struct device *fw_attr_dev;
+ struct kset *fw_attr_kset;
+ struct notifier_block nb;
+ struct wmi_device *wdev;
+ int ida_id;
+};
+
+struct tunable_attr_01 {
+ struct capdata01 *capdata;
+ struct device *dev;
+ u32 feature_id;
+ u32 device_id;
+ u32 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;
+};
+
+/**
+ * lwmi_om_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
+ * lenovo-wmi-other driver notifier chain.
+ *
+ * Return: 0 on success, %-EEXIST on error.
+ */
+int lwmi_om_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&om_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
+
+/**
+ * lwmi_om_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
+ * lenovo-wmi-other driver notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+int lwmi_om_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&om_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
+
+/**
+ * devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking
+ * notifier chain.
+ * @data: Void pointer to the notifier_block struct to register.
+ *
+ * Call lwmi_om_unregister_notifier to unregister the notifier block from the
+ * lenovo-wmi-other driver notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+static void devm_lwmi_om_unregister_notifier(void *data)
+{
+ struct notifier_block *nb = data;
+
+ lwmi_om_unregister_notifier(nb);
+}
+
+/**
+ * devm_lwmi_om_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_om_register_notifier to register the notifier block to the
+ * lenovo-wmi-other driver notifier chain. Then add devm_lwmi_om_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_om_register_notifier(struct device *dev,
+ struct notifier_block *nb)
+{
+ int ret;
+
+ ret = lwmi_om_register_notifier(nb);
+ if (ret < 0)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
+ nb);
+}
+EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
+
+/**
+ * lwmi_om_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.
+ */
+static int lwmi_om_notifier_call(enum thermal_mode *mode)
+{
+ int ret;
+
+ ret = blocking_notifier_call_chain(&om_chain_head,
+ LWMI_GZ_GET_THERMAL_MODE, &mode);
+ if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK)
+ return -EINVAL;
+
+ return 0;
+}
+
+/* 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 =
+ FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
+ LWMI_GZ_THERMAL_MODE_CUSTOM) |
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_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 attribute_id;
+ u32 value;
+ int ret;
+
+ ret = lwmi_om_notifier_call(&mode);
+ if (ret)
+ return ret;
+
+ if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
+ return -EBUSY;
+
+ attribute_id =
+ FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+ ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &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 = attribute_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;
+ u32 attribute_id;
+ int retval;
+ int ret;
+
+ ret = lwmi_om_notifier_call(&mode);
+ if (ret)
+ return ret;
+
+ attribute_id =
+ FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+ args.arg0 = attribute_id;
+
+ 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);
+}
+
+/* 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.
+ *
+ * Return: Either 0, or an error code.
+ */
+static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
+{
+ unsigned int i;
+ int err;
+
+ priv->ida_id = ida_alloc(&lwmi_om_ida, GFP_KERNEL);
+ if (priv->ida_id < 0)
+ return priv->ida_id;
+
+ 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++) {
+ err = sysfs_create_group(&priv->fw_attr_kset->kobj,
+ cd01_attr_groups[i].attr_group);
+ if (err)
+ goto err_remove_groups;
+
+ cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
+ }
+ return 0;
+
+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);
+ return 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)
+{
+ 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);
+}
+
+/**
+ * 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-capdata01 driver to the
+ * lenovo-wmi-other master driver. On success, assign the capability data 01
+ * list pointer to the driver data struct for later access. This pointer
+ * is only valid while the capdata01 interface exists. Finally, register all
+ * firmware attribute groups.
+ *
+ * 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 cd01_list *tmp_list;
+ int ret;
+
+ ret = component_bind_all(dev, &tmp_list);
+ if (ret)
+ return ret;
+
+ priv->cd01_list = tmp_list;
+ if (!priv->cd01_list)
+ return -ENODEV;
+
+ return lwmi_om_fw_attr_add(priv);
+}
+
+/**
+ * lwmi_om_master_unbind() - Unbind all components of the other mode driver
+ * @dev: The lenovo-wmi-other driver basic device
+ *
+ * Unregister all capability data attribute groups. Then call
+ * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
+ * lenovo-wmi-other master driver. Finally, free the IDA for this device.
+ */
+static void lwmi_om_master_unbind(struct device *dev)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+
+ lwmi_om_fw_attr_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;
+
+ priv->wdev = wdev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
+ 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)
+{
+ struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
+
+ component_master_del(&wdev->dev, &lwmi_om_master_ops);
+ ida_free(&lwmi_om_ida, priv->ida_id);
+}
+
+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_CD01");
+MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
+MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-other.h b/drivers/platform/x86/lenovo/wmi-other.h
new file mode 100644
index 000000000000..8ebf5602bb99
--- /dev/null
+++ b/drivers/platform/x86/lenovo/wmi-other.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_OTHER_H_
+#define _LENOVO_WMI_OTHER_H_
+
+struct device;
+struct notifier_block;
+
+int lwmi_om_register_notifier(struct notifier_block *nb);
+int lwmi_om_unregister_notifier(struct notifier_block *nb);
+int devm_lwmi_om_register_notifier(struct device *dev,
+ struct notifier_block *nb);
+
+#endif /* !_LENOVO_WMI_OTHER_H_ */
diff --git a/drivers/platform/x86/lenovo-ymc.c b/drivers/platform/x86/lenovo/ymc.c
index 470d53e3c9d2..470d53e3c9d2 100644
--- a/drivers/platform/x86/lenovo-ymc.c
+++ b/drivers/platform/x86/lenovo/ymc.c
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..1b33c977f6d7 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,25 @@ 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;
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 +268,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 +284,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..31b298dc5046 100644
--- a/drivers/platform/x86/lenovo-yogabook.c
+++ b/drivers/platform/x86/lenovo/yogabook.c
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
new file mode 100644
index 000000000000..eb076bb4099b
--- /dev/null
+++ b/drivers/platform/x86/oxpec.c
@@ -0,0 +1,1072 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Platform driver for OneXPlayer and AOKZOE devices. For the time being,
+ * it also exposes fan controls for AYANEO, and OrangePi Handhelds via
+ * hwmon sysfs.
+ *
+ * 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 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,
+ aya_neo_2,
+ aya_neo_air,
+ aya_neo_air_1s,
+ aya_neo_air_plus_mendo,
+ aya_neo_air_pro,
+ aya_neo_flip,
+ aya_neo_geek,
+ aya_neo_kun,
+ 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, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
+ },
+ .driver_data = (void *)aya_neo_2,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
+ },
+ .driver_data = (void *)aya_neo_air,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),
+ },
+ .driver_data = (void *)aya_neo_air_1s,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
+ },
+ .driver_data = (void *)aya_neo_air_plus_mendo,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
+ },
+ .driver_data = (void *)aya_neo_air_pro,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "FLIP"),
+ },
+ .driver_data = (void *)aya_neo_flip,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "GEEK"),
+ },
+ .driver_data = (void *)aya_neo_geek,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),
+ },
+ .driver_data = (void *)aya_neo_kun,
+ },
+ {
+ .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 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 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 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,
+ },
+ {},
+};
+
+/* 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 aya_neo_2:
+ case aya_neo_air:
+ case aya_neo_air_plus_mendo:
+ case aya_neo_air_pro:
+ case aya_neo_flip:
+ case aya_neo_geek:
+ case aya_neo_kun:
+ 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 aya_neo_2:
+ case aya_neo_air:
+ case aya_neo_air_1s:
+ case aya_neo_air_plus_mendo:
+ case aya_neo_air_pro:
+ case aya_neo_flip:
+ case aya_neo_geek:
+ case aya_neo_kun:
+ 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 aya_neo_2:
+ case aya_neo_air:
+ case aya_neo_air_1s:
+ case aya_neo_air_plus_mendo:
+ case aya_neo_air_pro:
+ case aya_neo_flip:
+ case aya_neo_geek:
+ case aya_neo_kun:
+ 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 aya_neo_2:
+ case aya_neo_air:
+ case aya_neo_air_1s:
+ case aya_neo_air_plus_mendo:
+ case aya_neo_air_pro:
+ case aya_neo_flip:
+ case aya_neo_geek:
+ case aya_neo_kun:
+ 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 aya_neo_2:
+ case aya_neo_air:
+ case aya_neo_air_1s:
+ case aya_neo_air_plus_mendo:
+ case aya_neo_air_pro:
+ case aya_neo_flip:
+ case aya_neo_geek:
+ case aya_neo_kun:
+ 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 aya_neo_2:
+ case aya_neo_air:
+ case aya_neo_air_1s:
+ case aya_neo_air_plus_mendo:
+ case aya_neo_air_pro:
+ case aya_neo_flip:
+ case aya_neo_geek:
+ case aya_neo_kun:
+ 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..255317e6fec8 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -1033,8 +1033,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) {
diff --git a/drivers/platform/x86/portwell-ec.c b/drivers/platform/x86/portwell-ec.c
new file mode 100644
index 000000000000..3e019c51913e
--- /dev/null
+++ b/drivers/platform/x86/portwell-ec.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * portwell-ec.c: Portwell embedded controller driver.
+ *
+ * Tested on:
+ * - Portwell NANO-6064
+ *
+ * This driver provides support for GPIO and Watchdog Timer
+ * functionalities of the Portwell boards with ITE embedded controller (EC).
+ * The EC is accessed through I/O ports and provides:
+ * - 8 GPIO pins for control and monitoring
+ * - Hardware watchdog with 1-15300 second timeout range
+ *
+ * It integrates with the Linux GPIO and Watchdog subsystems, allowing
+ * userspace interaction with EC GPIO pins and watchdog control,
+ * ensuring system stability and configurability.
+ *
+ * (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/bitfield.h>
+#include <linux/dmi.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sizes.h>
+#include <linux/string.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_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"
+
+static bool force;
+module_param(force, bool, 0444);
+MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname");
+
+static const struct dmi_system_id pwec_dmi_table[] = {
+ {
+ .ident = "NANO-6064 series",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"),
+ },
+ },
+ { }
+};
+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);
+}
+
+/* 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_rv(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_rv = pwec_gpio_set_rv,
+ .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,
+};
+
+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)
+{
+ 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;
+ }
+
+ ec_wdt_dev.parent = &pdev->dev;
+ ret = devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register Portwell EC Watchdog\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver pwec_driver = {
+ .driver = {
+ .name = "portwell-ec",
+ },
+ .probe = pwec_probe,
+};
+
+static struct platform_device *pwec_dev;
+
+static int __init pwec_init(void)
+{
+ int ret;
+
+ if (!dmi_check_system(pwec_dmi_table)) {
+ if (!force)
+ return -ENODEV;
+ pr_warn("force load portwell-ec without DMI check\n");
+ }
+
+ ret = platform_driver_register(&pwec_driver);
+ if (ret)
+ return ret;
+
+ pwec_dev = platform_device_register_simple("portwell-ec", -1, NULL, 0);
+ 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/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c
index 5878a351993e..3c13e13d4885 100644
--- a/drivers/platform/x86/samsung-galaxybook.c
+++ b/drivers/platform/x86/samsung-galaxybook.c
@@ -1403,6 +1403,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..9d43a12db73c 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;
@@ -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/silicom-platform.c b/drivers/platform/x86/silicom-platform.c
index c0910af16a3a..63b5da410ed5 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,
@@ -469,7 +466,7 @@ static struct gpio_chip silicom_gpio_chip = {
.direction_input = silicom_gpio_direction_input,
.direction_output = silicom_gpio_direction_output,
.get = silicom_gpio_get,
- .set = silicom_gpio_set,
+ .set_rv = silicom_gpio_set,
.base = -1,
.ngpio = ARRAY_SIZE(plat_0222_gpio_channels),
.names = plat_0222_gpio_names,
diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c
index b52390fbd743..56beebc38850 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>
@@ -3157,7 +3156,7 @@ static int sony_nc_add(struct acpi_device *device)
struct sony_nc_value *item;
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;
@@ -3327,8 +3326,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 +3620,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 +3707,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)
{
@@ -4677,7 +4512,7 @@ static int sony_pic_add(struct acpi_device *device)
struct sony_pic_irq *irq, *tmp_irq;
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);
diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c
index 20df1ebefc30..53fc2b364552 100644
--- a/drivers/platform/x86/topstar-laptop.c
+++ b/drivers/platform/x86/topstar-laptop.c
@@ -296,8 +296,8 @@ static int topstar_acpi_add(struct acpi_device *device)
if (!topstar)
return -ENOMEM;
- strcpy(acpi_device_name(device), "Topstar TPSACPI");
- strcpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS);
+ strscpy(acpi_device_name(device), "Topstar TPSACPI");
+ strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS);
device->driver_data = topstar;
topstar->device = device;
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/wmi.c b/drivers/platform/x86/wmi.c
index e46453750d5f..4e86a422f05f 100644
--- a/drivers/platform/x86/wmi.c
+++ b/drivers/platform/x86/wmi.c
@@ -20,6 +20,7 @@
#include <linux/bits.h>
#include <linux/build_bug.h>
#include <linux/device.h>
+#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -74,6 +75,8 @@ struct wmi_guid_count_context {
int count;
};
+static DEFINE_IDA(wmi_ida);
+
/*
* If the GUID data block is marked as expensive, we must enable and
* explicitily disable data collection.
@@ -177,16 +180,22 @@ static int wmi_device_enable(struct wmi_device *wdev, bool enable)
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)
+ if (wblock->dev.dev.type == &wmi_type_event) {
+ /*
+ * Windows always enables/disables WMI events, even when they are
+ * not marked as being expensive. We follow this behavior for
+ * compatibility reasons.
+ */
snprintf(method, sizeof(method), "WE%02X", wblock->gblock.notify_id);
- else
+ } else {
+ if (!(wblock->gblock.flags & ACPI_WMI_EXPENSIVE))
+ return 0;
+
get_acpi_method_name(wblock, 'C', method);
+ }
/*
* Not all WMI devices marked as expensive actually implement the
@@ -978,6 +987,19 @@ static int guid_count(const guid_t *guid)
return context.count;
}
+static int wmi_dev_set_name(struct wmi_block *wblock, int count)
+{
+ if (IS_ENABLED(CONFIG_ACPI_WMI_LEGACY_DEVICE_NAMES)) {
+ if (count)
+ return dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid,
+ count);
+ else
+ return dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid);
+ }
+
+ return dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, wblock->dev.dev.id);
+}
+
static int wmi_create_device(struct device *wmi_bus_dev,
struct wmi_block *wblock,
struct acpi_device *device)
@@ -986,7 +1008,7 @@ static int wmi_create_device(struct device *wmi_bus_dev,
struct acpi_device_info *info;
acpi_handle method_handle;
acpi_status status;
- int count;
+ int count, ret;
if (wblock->gblock.flags & ACPI_WMI_EVENT) {
wblock->dev.dev.type = &wmi_type_event;
@@ -1057,11 +1079,18 @@ static int wmi_create_device(struct device *wmi_bus_dev,
if (count < 0)
return count;
- if (count) {
- dev_set_name(&wblock->dev.dev, "%pUL-%d", &wblock->gblock.guid, count);
+ if (count)
set_bit(WMI_GUID_DUPLICATED, &wblock->flags);
- } else {
- dev_set_name(&wblock->dev.dev, "%pUL", &wblock->gblock.guid);
+
+ ret = ida_alloc(&wmi_ida, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ wblock->dev.dev.id = ret;
+ ret = wmi_dev_set_name(wblock, count);
+ if (ret < 0) {
+ ida_free(&wmi_ida, wblock->dev.dev.id);
+ return ret;
}
device_initialize(&wblock->dev.dev);
@@ -1147,6 +1176,7 @@ static int parse_wdg(struct device *wmi_bus_dev, struct platform_device *pdev)
dev_err(wmi_bus_dev, "failed to register %pUL\n",
&wblock->gblock.guid);
+ ida_free(&wmi_ida, wblock->dev.dev.id);
put_device(&wblock->dev.dev);
}
}
@@ -1246,7 +1276,10 @@ static void acpi_wmi_notify_handler(acpi_handle handle, u32 event, void *context
static int wmi_remove_device(struct device *dev, void *data)
{
+ int id = dev->id;
+
device_unregister(dev);
+ ida_free(&wmi_ida, id);
return 0;
}
diff --git a/drivers/platform/x86/x86-android-tablets/asus.c b/drivers/platform/x86/x86-android-tablets/asus.c
index 7dde63b9943f..97cd14c1fd23 100644
--- a/drivers/platform/x86/x86-android-tablets/asus.c
+++ b/drivers/platform/x86/x86-android-tablets/asus.c
@@ -206,24 +206,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 +221,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),
{ }
};
@@ -321,6 +306,6 @@ const struct x86_dev_info asus_tf103c_info __initconst = {
.gpio_button = &asus_me176c_tf103c_lid,
.gpio_button_count = 1,
.gpiod_lookup_tables = asus_tf103c_gpios,
- .bat_swnode = &asus_tf103c_battery_node,
+ .bat_swnode = &generic_lipo_4v2_battery_node,
.modules = bq24190_modules,
};
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..fe34cedb6257 100644
--- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.c
+++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.c
@@ -39,6 +39,78 @@ 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,
+};
+
/* 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 +120,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),
{ }
};
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..bcf9845ad275 100644
--- a/drivers/platform/x86/x86-android-tablets/shared-psy-info.h
+++ b/drivers/platform/x86/x86-android-tablets/shared-psy-info.h
@@ -21,6 +21,7 @@ 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_hv_4v35_battery_node;
extern struct bq24190_platform_data bq24190_pdata;
diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c
index df2bf1c58523..cb02222c978c 100644
--- a/drivers/platform/x86/xo15-ebook.c
+++ b/drivers/platform/x86/xo15-ebook.c
@@ -84,7 +84,6 @@ 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);
@@ -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;