1
0

Merge tag 'platform-drivers-x86-v6.16-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86

Pull x86 platform drivers updates from Ilpo Järvinen:
 "The changes are mostly business as usual. Besides pdx86 changes, there
  are a few power supply changes needed for related pdx86 features, move
  of oxpec driver from hwmon (oxp-sensors) to pdx86, and one FW version
  warning to hid-asus.

  Highlights:

   - alienware-wmi-wmax:
       - Add HWMON support
       - Add ABI and admin-guide documentation
       - Expose GPIO debug methods through debug FS
       - Support manual fan control and "custom" thermal profile

   - amd/hsmp:
       - Add sysfs files to show HSMP telemetry
       - Report power readings and limits via hwmon

   - amd/isp4: Add AMD ISP platform config for OV05C10

   - asus-wmi:
       - Refactor Ally suspend/resume to work better with older FW
       - hid-asus: check ROG Ally MCU version and warn about old FW versions

   - dasharo-acpi:
       - Add driver for Dasharo devices supporting fans and temperatures
         monitoring

   - dell-ddv:
       - Expose the battery health and manufacture date to userspace
         using power supply extensions
       - Implement the battery matching algorithm

   - dell-pc:
       - Improve error propagation
       - Use faux device

   - int3472:
       - Add delays to avoid GPIO regulator spikes
       - Add handshake pin support
       - Make regulator supply name configurable and allow registering
         more than 1 GPIO regulator
       - Map mt9m114 powerdown pin to powerenable

   - intel/pmc: Add separate SSRAM Telemetry driver

   - intel-uncore-freq: Add attributes to show agent types and die ID

   - ISST:
       - Support SST-TF revision 2 (allows more cores per bucket)
       - Support SST-PP revision 2 (fabric 1 frequencies)
       - Remove unnecessary SST MSRs restore (the package retains MSRs
         despite CPU offlining)

   - mellanox: Add support for SN2201, SN4280, SN5610, and SN5640

   - mellanox: mlxbf-pmc: Support additional PMC blocks

   - oxpec:
       - Add OneXFly variants
       - Add support for charge limit, charge thresholds, and turbo LED
       - Distinguish current X1 variants to avoid unwanted matching to
         new variants
       - Follow hwmon conventions
       - Move from hwmon/oxp-sensors to platform/x86 to match the
         enlarged scope

   - power supply:
       - Add inhibit-charge-awake (needed by oxpec)
       - Add additional battery health status values ("blown fuse" and
         "cell imbalance") (needed by dell-ddv)

   - powerwell-ec: Add driver for Portwell EC supporting GPIO and watchdog

   - thinkpad-acpi: Support camera shutter switch hotkey

   - tuxedo: Add virtual LampArray for TUXEDO NB04 devices

   - tools/power/x86/intel-speed-select:
       - Support displaying SST-PP revision 2 fields
       - Skip uncore frequency update on newer generations of CPUs

   - Miscellaneous cleanups / refactoring / improvements"

* tag 'platform-drivers-x86-v6.16-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (112 commits)
  thermal/drivers/acerhdf: Constify struct thermal_zone_device_ops
  platform/x86/amd/hsmp: fix building with CONFIG_HWMON=m
  platform/x86: asus-wmi: fix build without CONFIG_SUSPEND
  docs: ABI: Fix "aassociated" to "associated"
  platform/x86: Add AMD ISP platform config for OV05C10
  Documentation: admin-guide: pm: Add documentation for die_id
  platform/x86/intel-uncore-freq: Add attributes to show die_id
  platform/x86/intel: power-domains: Add interface to get Linux die ID
  Documentation: admin-guide: pm: Add documentation for agent_types
  platform/x86/intel-uncore-freq: Add attributes to show agent types
  platform/x86/tuxedo: Prevent invalid Kconfig state
  platform/x86: dell-ddv: Expose the battery health to userspace
  platform/x86: dell-ddv: Expose the battery manufacture date to userspace
  platform/x86: dell-ddv: Implement the battery matching algorithm
  power: supply: core: Add additional health status values
  platform/x86/amd/hsmp: acpi: Add sysfs files to display HSMP telemetry
  platform/x86/amd/hsmp: Report power via hwmon sensors
  platform/x86/amd/hsmp: Use a single DRIVER_VERSION for all hsmp modules
  platform/mellanox: mlxreg-dpu: Fix smatch warnings
  platform: mellanox: nvsw-sn2200: Fix .items in nvsw_sn2201_busbar_hotplug
  ...
This commit is contained in:
Linus Torvalds
2025-05-29 10:19:22 -07:00
103 changed files with 9273 additions and 2167 deletions

View File

@@ -715,3 +715,101 @@ Description: This file shows 1 in case the system reset happened due to the
switch board.
The file is read only.
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/global_wp_request
Date: May 2025
KernelVersion: 6.16
Contact: Vadim Pasternak <vadimp@nvidia.com>
Description: This file when written 1 activates request to allow access to
the write protected flashes. Such request can be performed only
for system equipped with BMC (Board Management Controller),
which can grant access to protected flashes. In case BMC allows
access - it will respond with "global_wp_response". BMC decides
regarding time window of granted access. After granted window is
expired, BMC will change value back to 0.
Default value is 0.
The file is read/write.
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/global_wp_response
Date: May 2025
KernelVersion: 6.16
Contact: Vadim Pasternak <vadimp@nvidia.com>
Description: This file, when set 1, indicates that access to protected
flashes have been granted to host CPU by BMC.
Default value is 0.
The file is read only.
What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/shutdown_unlock
Date: May 2025
KernelVersion: 6.16
Contact: Vadim Pasternak <vadimp@nvidia.com>
Description: When ASICs are getting overheated, system protection
hardware mechanism enforces system reboot. After system
reboot ASICs come up in locked state. To unlock ASICs,
this file should be written 1
Default value is 0.
The file is read/write.
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/boot_progress
Date: May 2025
KernelVersion: 6.16
Contact: Vadim Pasternak <vadimp@nvidia.com>
Description: These files show the Data Process Unit board boot progress
state. Valid states are:
- 4 : OS starting.
- 5 : OS running.
- 6 : Low-Power Standby.
The file is read only.
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/dpu_id
Date: May 2025
KernelVersion: 6.16
Contact: Vadim Pasternak <vadimp@nvidia.com>
Description: This file shows hardware Id of Data Process Unit board.
The file is read only.
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/reset_aux_pwr_or_reload
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/reset_dpu_thermal
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/reset_from_main_board
Date: May 2025
KernelVersion: 6.16
Contact: Vadim Pasternak <vadimp@nvidia.com>
Description: These files expose the cause of the most recent reset of the Data
Processing Unit (DPU) board. The possible causes are:
- Power auxiliary outage or power reload.
- Thermal shutdown.
- Reset request from the main board.
Value 1 in file means this is reset cause, 0 - otherwise. Only one of
the above causes could be 1 at the same time, representing only last
reset cause.
The files are read only.
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/perst_rst
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/phy_rst
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/tpm_rst
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/usbphy_rst
Date: May 2025
KernelVersion: 6.16
Contact: Vadim Pasternak <vadimp@nvidia.com>
Description: These files allow to reset hardware components of Data Process
Unit board. Respectively PCI, Ethernet PHY, TPM and USB PHY
resets.
Default values for all the attributes is 1. Writing 0 will
cause reset of the related component.
The files are read/write.
What: /sys/devices/platform/mlxplat/i2c_mlxcpld.*/i2c-*/i2c-*/*-00**/mlxreg-io.*/hwmon/hwmon*/ufm_upgrade
Date: May 2025
KernelVersion: 6.16
Contact: Vadim Pasternak <vadimp@nvidia.com>
Description: These files show status of Unified Fabric Manager upgrade.
state. 0 - means upgrade is done, 1 - otherwise.
The file is read only.

View File

@@ -0,0 +1,64 @@
What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/system_description
Date: March 2025
KernelVersion: 6.15
Contact: Kurt Borja <kuurtb@gmail.com>
Description:
This file exposes the raw ``system_description`` number reported
by the WMAX device.
Only present on devices with the AWCC interface.
See Documentation/admin-guide/laptops/alienware-wmi.rst for
details.
RO
What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/hwmon_data
Date: March 2025
KernelVersion: 6.15
Contact: Kurt Borja <kuurtb@gmail.com>
Description:
This file exposes HWMON private data.
Includes fan sensor count, temperature sensor count, internal
fan IDs and internal temp IDs.
See Documentation/admin-guide/laptops/alienware-wmi.rst for
details.
RO
What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/pprof_data
Date: March 2025
KernelVersion: 6.15
Contact: Kurt Borja <kuurtb@gmail.com>
Description:
This file exposes Platform Profile private data.
Includes internal mapping to platform profiles and thermal
profile IDs.
See Documentation/admin-guide/laptops/alienware-wmi.rst for
details.
RO
What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/gpio_ctl/total_gpios
Date: May 2025
KernelVersion: 6.16
Contact: Kurt Borja <kuurtb@gmail.com>
Description:
Total number of GPIO pins reported by the device.
RO
What: /sys/kernel/debug/alienware-wmi-<wmi_device_name>/gpio_ctl/pinX
Date: May 2025
KernelVersion: 6.16
Contact: Kurt Borja <kuurtb@gmail.com>
Description:
This file controls GPIO pin X status.
See Documentation/wmi/devices/alienware-wmi.rst for details.
RW

View File

@@ -76,6 +76,6 @@ Date: May 2017
Contact: Darren Hart (VMware) <dvhart@infradead.org>
Description:
This file contains a boolean flags signaling the data block
aassociated with the given WMI device is writable. If the
associated with the given WMI device is writable. If the
given WMI device is not associated with a data block, then
this file will not exist.

View File

@@ -456,7 +456,7 @@ Description:
"Over voltage", "Under voltage", "Unspecified failure", "Cold",
"Watchdog timer expire", "Safety timer expire",
"Over current", "Calibration required", "Warm",
"Cool", "Hot", "No battery"
"Cool", "Hot", "No battery", "Blown fuse", "Cell imbalance"
What: /sys/class/power_supply/<supply_name>/precharge_current
Date: June 2017
@@ -508,11 +508,12 @@ Description:
Access: Read, Write
Valid values:
================ ====================================
auto: Charge normally, respect thresholds
inhibit-charge: Do not charge while AC is attached
force-discharge: Force discharge while AC is attached
================ ====================================
===================== ========================================
auto: Charge normally, respect thresholds
inhibit-charge: Do not charge while AC is attached
inhibit-charge-awake: inhibit-charge only when device is awake
force-discharge: Force discharge while AC is attached
===================== ========================================
What: /sys/class/power_supply/<supply_name>/technology
Date: May 2007

View File

@@ -0,0 +1,14 @@
What: /sys/class/hwmon/hwmonX/fanY_boost
Date: March 2025
KernelVersion: 6.15
Contact: Kurt Borja <kuurtb@gmail.com>
Description:
This file exposes fan boost control for Dell gaming laptops with
the AWCC WMI interface.
See Documentation/admin-guide/laptops/alienware-wmi.rst for
details.
Integer value in the range 0 to 255
RW

View File

@@ -0,0 +1,25 @@
What: /sys/devices/platform/<platform>/tt_toggle
Date: Jun 2023
KernelVersion: 6.5
Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
Description:
Takeover TDP controls from the device. OneXPlayer devices have a
turbo button that can be used to switch between two TDP modes
(usually 15W and 25W). By setting this attribute to 1, this
functionality is disabled, handing TDP control over to (Windows)
userspace software and the Turbo button turns into a keyboard
shortcut over the AT keyboard of the device. In addition,
using this setting is a prerequisite for PWM control for most
newer models (otherwise it NOOPs).
What: /sys/devices/platform/<platform>/tt_led
Date: April 2025
KernelVersion: 6.16
Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
Description:
Some OneXPlayer devices (e.g., X1 series) feature a little LED
nested in the Turbo button. This LED is illuminated when the
device is in the higher TDP mode (e.g., 25W). Once tt_toggle
is engaged, this LED is left dangling to its last state. This
attribute allows userspace to control the LED state manually
(either with 1 or 0). Only a subset of devices contain this LED.

View File

@@ -0,0 +1,127 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
====================
Alienware WMI Driver
====================
Kurt Borja <kuurtb@gmail.com>
This is a driver for the "WMAX" WMI device, which is found in most Dell gaming
laptops and controls various special features.
Before the launch of M-Series laptops (~2018), the "WMAX" device controlled
basic RGB lighting, deep sleep mode, HDMI mode and amplifier status.
Later, this device was completely repurpused. Now it mostly deals with thermal
profiles, sensor monitoring and overclocking. This interface is named "AWCC" and
is known to be used by the AWCC OEM application to control these features.
The alienware-wmi driver controls both interfaces.
AWCC Interface
==============
WMI device documentation: Documentation/wmi/devices/alienware-wmi.rst
Supported devices
-----------------
- Alienware M-Series laptops
- Alienware X-Series laptops
- Alienware Aurora Desktops
- Dell G-Series laptops
If you believe your device supports the AWCC interface and you don't have any of
the features described in this document, try the following alienware-wmi module
parameters:
- ``force_platform_profile=1``: Forces probing for platform profile support
- ``force_hwmon=1``: Forces probing for HWMON support
If the module loads successfully with these parameters, consider submitting a
patch adding your model to the ``awcc_dmi_table`` located in
``drivers/platform/x86/dell/alienware-wmi-wmax.c`` or contacting the maintainer
for further guidance.
Status
------
The following features are currently supported:
- :ref:`Platform Profile <platform-profile>`:
- Thermal profile control
- G-Mode toggling
- :ref:`HWMON <hwmon>`:
- Sensor monitoring
- Manual fan control
.. _platform-profile:
Platform Profile
----------------
The AWCC interface exposes various firmware defined thermal profiles. These are
exposed to user-space through the Platform Profile class interface. Refer to
:ref:`sysfs-class-platform-profile <abi_file_testing_sysfs_class_platform_profile>`
for more information.
The name of the platform-profile class device exported by this driver is
"alienware-wmi" and it's path can be found with:
::
grep -l "alienware-wmi" /sys/class/platform-profile/platform-profile-*/name | sed 's|/[^/]*$||'
If the device supports G-Mode, it is also toggled when selecting the
``performance`` profile.
.. note::
You may set the ``force_gmode`` module parameter to always try to toggle this
feature, without checking if your model supports it.
.. _hwmon:
HWMON
-----
The AWCC interface also supports sensor monitoring and manual fan control. Both
of these features are exposed to user-space through the HWMON interface.
The name of the hwmon class device exported by this driver is "alienware_wmi"
and it's path can be found with:
::
grep -l "alienware_wmi" /sys/class/hwmon/hwmon*/name | sed 's|/[^/]*$||'
Sensor monitoring is done through the standard HWMON interface. Refer to
:ref:`sysfs-class-hwmon <abi_file_testing_sysfs_class_hwmon>` for more
information.
Manual fan control on the other hand, is not exposed directly by the AWCC
interface. Instead it let's us control a fan `boost` value. This `boost` value
has the following aproximate behavior over the fan pwm:
::
pwm = pwm_base + (fan_boost / 255) * (pwm_max - pwm_base)
Due to the above behavior, the fan `boost` control is exposed to user-space
through the following, custom hwmon sysfs attribute:
=============================== ======= =======================================
Name Perm Description
=============================== ======= =======================================
fan[1-4]_boost RW Fan boost value.
Integer value between 0 and 255
=============================== ======= =======================================
.. note::
In some devices, manual fan control only works reliably if the ``custom``
platform profile is selected.

View File

@@ -7,6 +7,7 @@ Laptop Drivers
.. toctree::
:maxdepth: 1
alienware-wmi
asus-laptop
disk-shock-protection
laptop-mode

View File

@@ -91,12 +91,22 @@ Attributes in each directory:
``domain_id``
This attribute is used to get the power domain id of this instance.
``die_id``
This attribute is used to get the Linux die id of this instance.
This attribute is only present for domains with core agents and
when the CPUID leaf 0x1f presents die ID.
``fabric_cluster_id``
This attribute is used to get the fabric cluster id of this instance.
``package_id``
This attribute is used to get the package id of this instance.
``agent_types``
This attribute displays all the hardware agents present within the
domain. Each agent has the capability to control one or more hardware
subsystems, which include: core, cache, memory, and I/O.
The other attributes are same as presented at package_*_die_* level.
In most of current use cases, the "max_freq_khz" and "min_freq_khz"

View File

@@ -71,6 +71,28 @@ Note: lseek() is not supported as entire metrics table is read.
Metrics table definitions will be documented as part of Public PPR.
The same is defined in the amd_hsmp.h header.
2. HSMP telemetry sysfs files
Following sysfs files are available at /sys/devices/platform/AMDI0097:0X/.
* c0_residency_input: Percentage of cores in C0 state.
* prochot_status: Reports 1 if the processor is at thermal threshold value,
0 otherwise.
* smu_fw_version: SMU firmware version.
* protocol_version: HSMP interface version.
* ddr_max_bw: Theoretical maximum DDR bandwidth in GB/s.
* ddr_utilised_bw_input: Current utilized DDR bandwidth in GB/s.
* ddr_utilised_bw_perc_input(%): Percentage of current utilized DDR bandwidth.
* mclk_input: Memory clock in MHz.
* fclk_input: Fabric clock in MHz.
* clk_fmax: Maximum frequency of socket in MHz.
* clk_fmin: Minimum frequency of socket in MHz.
* cclk_freq_limit_input: Core clock frequency limit per socket in MHz.
* pwr_current_active_freq_limit: Current active frequency limit of socket
in MHz.
* pwr_current_active_freq_limit_source: Source of current active frequency
limit.
ACPI device object format
=========================
The ACPI object format expected from the amd_hsmp driver
@@ -116,6 +138,14 @@ for socket with ID00 is given below::
})
}
HSMP HWMON interface
====================
HSMP power sensors are registered with the hwmon interface. A separate hwmon
directory is created for each socket and the following files are generated
within the hwmon directory.
- power1_input (read only)
- power1_cap_max (read only)
- power1_cap (read, write)
An example
==========

View File

@@ -189,7 +189,6 @@ Hardware Monitoring Kernel Drivers
nzxt-kraken3
nzxt-smart2
occ
oxp-sensors
pc87360
pc87427
pcf8591

View File

@@ -1,89 +0,0 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver oxp-sensors
=========================
Authors:
- Derek John Clark <derekjohn.clark@gmail.com>
- Joaquín Ignacio Aramendía <samsagax@gmail.com>
Description:
------------
Handheld devices from OneNetbook, AOKZOE, AYANEO, And OrangePi provide fan
readings and fan control through their embedded controllers.
Currently supports OneXPlayer devices, AOKZOE, AYANEO, and OrangePi
handheld devices. AYANEO devices preceding the AIR and OneXPlayer devices
preceding the Mini A07 are not supportable as the EC model is different
and do not have manual control capabilities.
Some OneXPlayer and AOKZOE models have a toggle for changing the behaviour
of the "Turbo/Silent" button of the device. It will change the key event
that it triggers with a flip of the `tt_toggle` attribute. See below for
boards that support this function.
Supported devices
-----------------
Currently the driver supports the following handhelds:
- AOKZOE A1
- AOKZOE A1 PRO
- AYANEO 2
- AYANEO 2S
- AYANEO AIR
- AYANEO AIR 1S
- AYANEO AIR Plus (Mendocino)
- AYANEO AIR Pro
- AYANEO Flip DS
- AYANEO Flip KB
- AYANEO Geek
- AYANEO Geek 1S
- AYANEO KUN
- OneXPlayer 2
- OneXPlayer 2 Pro
- OneXPlayer AMD
- OneXPlayer mini AMD
- OneXPlayer mini AMD PRO
- OneXPlayer OneXFly
- OneXPlayer X1 A
- OneXPlayer X1 i
- OneXPlayer X1 mini
- OrangePi NEO-01
"Turbo/Silent" button behaviour toggle is only supported on:
- AOK ZOE A1
- AOK ZOE A1 PRO
- OneXPlayer 2
- OneXPlayer 2 Pro
- OneXPlayer mini AMD (only with updated alpha BIOS)
- OneXPlayer mini AMD PRO
- OneXPlayer OneXFly
- OneXPlayer X1 A
- OneXPlayer X1 i
- OneXPlayer X1 mini
Sysfs entries
-------------
The following attributes are supported:
fan1_input
Read Only. Reads current fan RPM.
pwm1_enable
Read Write. Enable manual fan control. Write "1" to set to manual, write "0"
to let the EC control de fan speed. Read this attribute to see current status.
pwm1
Read Write. Read this attribute to see current duty cycle in the range [0-255].
When pwm1_enable is set to "1" (manual) write any value in the range [0-255]
to set fan speed.
tt_toggle
Read Write. Read this attribute to check the status of the turbo/silent
button behaviour function. Write "1" to activate the switch and "0" to
deactivate it. The specific keycodes and behaviour is specific to the device
both with this function on and off. This attribute is attached to the platform
driver and not to the hwmon driver (/sys/devices/platform/oxp-platform/tt_toggle)

View File

@@ -11,7 +11,7 @@ The WMI device WMAX has been implemented for many Alienware and Dell's G-Series
models. Throughout these models, two implementations have been identified. The
first one, used by older systems, deals with HDMI, brightness, RGB, amplifier
and deep sleep control. The second one used by newer systems deals primarily
with thermal, overclocking, and GPIO control.
with thermal control and overclocking.
It is suspected that the latter is used by Alienware Command Center (AWCC) to
manage manufacturer predefined thermal profiles. The alienware-wmi driver
@@ -69,9 +69,6 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
[WmiMethodId(164), Implemented, read, write, Description("Tobii Camera Power Off.")] void TobiiCameraPowerOff([out] uint32 argr);
};
Some of these methods get quite intricate so we will describe them using
pseudo-code that vaguely resembles the original ASL code.
Methods not described in the following document have unknown behavior.
Argument Structure
@@ -87,175 +84,133 @@ ID 0xA0, the argument you would pass to the method is 0xA001.
Thermal Methods
===============
WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------
+--------------------+------------------------------------+--------------------+
| Operation (Byte 0) | Description | Arguments |
+====================+====================================+====================+
| 0x01 | Get the number of temperature | - Byte 1: Fan ID |
| | sensors related with a fan ID | |
+--------------------+------------------------------------+--------------------+
| 0x02 | Get the temperature sensor IDs | - Byte 1: Fan ID |
| | related to a fan sensor ID | - Byte 2: Index |
+--------------------+------------------------------------+--------------------+
WMI method Thermal_Information([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------------
::
if BYTE_0(arg2) == 0x01:
argr = 1
if BYTE_0(arg2) == 0x02:
argr = SYSTEM_DESCRIPTION
if BYTE_0(arg2) == 0x03:
if BYTE_1(arg2) == 0x00:
argr = FAN_ID_0
if BYTE_1(arg2) == 0x01:
argr = FAN_ID_1
if BYTE_1(arg2) == 0x02:
argr = FAN_ID_2
if BYTE_1(arg2) == 0x03:
argr = FAN_ID_3
if BYTE_1(arg2) == 0x04:
argr = SENSOR_ID_CPU | 0x0100
if BYTE_1(arg2) == 0x05:
argr = SENSOR_ID_GPU | 0x0100
if BYTE_1(arg2) == 0x06:
argr = THERMAL_MODE_QUIET_ID
if BYTE_1(arg2) == 0x07:
argr = THERMAL_MODE_BALANCED_ID
if BYTE_1(arg2) == 0x08:
argr = THERMAL_MODE_BALANCED_PERFORMANCE_ID
if BYTE_1(arg2) == 0x09:
argr = THERMAL_MODE_PERFORMANCE_ID
if BYTE_1(arg2) == 0x0A:
argr = THERMAL_MODE_LOW_POWER_ID
if BYTE_1(arg2) == 0x0B:
argr = THERMAL_MODE_GMODE_ID
else:
argr = 0xFFFFFFFF
if BYTE_0(arg2) == 0x04:
if is_valid_sensor(BYTE_1(arg2)):
argr = SENSOR_TEMP_C
else:
argr = 0xFFFFFFFF
if BYTE_0(arg2) == 0x05:
if is_valid_fan(BYTE_1(arg2)):
argr = FAN_RPM()
if BYTE_0(arg2) == 0x06:
skip
if BYTE_0(arg2) == 0x07:
argr = 0
If BYTE_0(arg2) == 0x08:
if is_valid_fan(BYTE_1(arg2)):
argr = 0
else:
argr = 0xFFFFFFFF
if BYTE_0(arg2) == 0x09:
if is_valid_fan(BYTE_1(arg2)):
argr = FAN_UNKNOWN_STAT_0()
else:
argr = 0xFFFFFFFF
if BYTE_0(arg2) == 0x0A:
argr = THERMAL_MODE_BALANCED_ID
if BYTE_0(arg2) == 0x0B:
argr = CURRENT_THERMAL_MODE()
if BYTE_0(arg2) == 0x0C:
if is_valid_fan(BYTE_1(arg2)):
argr = FAN_UNKNOWN_STAT_1()
else:
argr = 0xFFFFFFFF
Operation 0x02 returns a *system description* buffer with the following
structure:
::
out[0] -> Number of fans
out[1] -> Number of sensors
out[2] -> 0x00
out[3] -> Number of thermal modes
Operation 0x03 list all available fan IDs, sensor IDs and thermal profile
codes in order, but different models may have different number of fans and
thermal profiles. These are the known ranges:
* Fan IDs: from 2 up to 4
* Sensor IDs: 2
* Thermal profile codes: from 1 up to 7
In total BYTE_1(ARG2) may range from 0x5 up to 0xD depending on the model.
+--------------------+------------------------------------+--------------------+
| Operation (Byte 0) | Description | Arguments |
+====================+====================================+====================+
| 0x01 | Unknown. | - None |
+--------------------+------------------------------------+--------------------+
| 0x02 | Get system description number with | - None |
| | the following structure: | |
| | | |
| | - Byte 0: Number of fans | |
| | - Byte 1: Number of temperature | |
| | sensors | |
| | - Byte 2: Unknown | |
| | - Byte 3: Number of thermal | |
| | profiles | |
+--------------------+------------------------------------+--------------------+
| 0x03 | List an ID or resource at a given | - Byte 1: Index |
| | index. Fan IDs, temperature IDs, | |
| | unknown IDs and thermal profile | |
| | IDs are listed in that exact | |
| | order. | |
| | | |
| | Operation 0x02 is used to know | |
| | which indexes map to which | |
| | resources. | |
| | | |
| | **Returns:** ID at a given index | |
+--------------------+------------------------------------+--------------------+
| 0x04 | Get the current temperature for a | - Byte 1: Sensor |
| | given temperature sensor. | ID |
+--------------------+------------------------------------+--------------------+
| 0x05 | Get the current RPM for a given | - Byte 1: Fan ID |
| | fan. | |
+--------------------+------------------------------------+--------------------+
| 0x06 | Get fan speed percentage. (not | - Byte 1: Fan ID |
| | implemented in every model) | |
+--------------------+------------------------------------+--------------------+
| 0x07 | Unknown. | - Unknown |
+--------------------+------------------------------------+--------------------+
| 0x08 | Get minimum RPM for a given FAN | - Byte 1: Fan ID |
| | ID. | |
+--------------------+------------------------------------+--------------------+
| 0x09 | Get maximum RPM for a given FAN | - Byte 1: Fan ID |
| | ID. | |
+--------------------+------------------------------------+--------------------+
| 0x0A | Get balanced thermal profile ID. | - None |
+--------------------+------------------------------------+--------------------+
| 0x0B | Get current thermal profile ID. | - None |
+--------------------+------------------------------------+--------------------+
| 0x0C | Get current `boost` value for a | - Byte 1: Fan ID |
| | given fan ID. | |
+--------------------+------------------------------------+--------------------+
WMI method Thermal_Control([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
::
if BYTE_0(arg2) == 0x01:
if is_valid_thermal_profile(BYTE_1(arg2)):
SET_THERMAL_PROFILE(BYTE_1(arg2))
argr = 0
if BYTE_0(arg2) == 0x02:
if is_valid_fan(BYTE_1(arg2)):
SET_FAN_SPEED_MULTIPLIER(BYTE_2(arg2))
argr = 0
else:
argr = 0xFFFFFFFF
.. note::
While you can manually change the fan speed multiplier with this method,
Dell's BIOS tends to overwrite this changes anyway.
+--------------------+------------------------------------+--------------------+
| Operation (Byte 0) | Description | Arguments |
+====================+====================================+====================+
| 0x01 | Activate a given thermal profile. | - Byte 1: Thermal |
| | | profile ID |
+--------------------+------------------------------------+--------------------+
| 0x02 | Set a `boost` value for a given | - Byte 1: Fan ID |
| | fan ID. | - Byte 2: Boost |
+--------------------+------------------------------------+--------------------+
These are the known thermal profile codes:
::
+------------------------------+----------+------+
| Thermal Profile | Type | ID |
+==============================+==========+======+
| Custom | Special | 0x00 |
+------------------------------+----------+------+
| G-Mode | Special | 0xAB |
+------------------------------+----------+------+
| Quiet | Legacy | 0x96 |
+------------------------------+----------+------+
| Balanced | Legacy | 0x97 |
+------------------------------+----------+------+
| Balanced Performance | Legacy | 0x98 |
+------------------------------+----------+------+
| Performance | Legacy | 0x99 |
+------------------------------+----------+------+
| Balanced | USTT | 0xA0 |
+------------------------------+----------+------+
| Balanced Performance | USTT | 0xA1 |
+------------------------------+----------+------+
| Cool | USTT | 0xA2 |
+------------------------------+----------+------+
| Quiet | USTT | 0xA3 |
+------------------------------+----------+------+
| Performance | USTT | 0xA4 |
+------------------------------+----------+------+
| Low Power | USTT | 0xA5 |
+------------------------------+----------+------+
CUSTOM 0x00
If a model supports the User Selectable Thermal Tables (USTT) profiles, it will
not support the Legacy profiles and vice-versa.
BALANCED_USTT 0xA0
BALANCED_PERFORMANCE_USTT 0xA1
COOL_USTT 0xA2
QUIET_USTT 0xA3
PERFORMANCE_USTT 0xA4
LOW_POWER_USTT 0xA5
QUIET 0x96
BALANCED 0x97
BALANCED_PERFORMANCE 0x98
PERFORMANCE 0x99
GMODE 0xAB
Usually if a model doesn't support the first four profiles they will support
the User Selectable Thermal Tables (USTT) profiles and vice-versa.
GMODE replaces PERFORMANCE in G-Series laptops.
Every model supports the CUSTOM (0x00) thermal profile. GMODE replaces
PERFORMANCE in G-Series laptops.
WMI method GameShiftStatus([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
::
if BYTE_0(arg2) == 0x1:
TOGGLE_GAME_SHIFT()
argr = GET_GAME_SHIFT_STATUS()
if BYTE_0(arg2) == 0x2:
argr = GET_GAME_SHIFT_STATUS()
+--------------------+------------------------------------+--------------------+
| Operation (Byte 0) | Description | Arguments |
+====================+====================================+====================+
| 0x01 | Toggle *Game Shift*. | - None |
+--------------------+------------------------------------+--------------------+
| 0x02 | Get *Game Shift* status. | - None |
+--------------------+------------------------------------+--------------------+
Game Shift Status does not change the fan speed profile but it could be some
sort of CPU/GPU power profile. Benchmarks have not been done.
@@ -267,118 +222,82 @@ Thermal_Information does not list it.
G-key on Dell's G-Series laptops also changes Game Shift status, so both are
directly related.
WMI method GetFanSensors([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------
::
if BYTE_0(arg2) == 0x1:
if is_valid_fan(BYTE_1(arg2)):
argr = 1
else:
argr = 0
if BYTE_0(arg2) == 0x2:
if is_valid_fan(BYTE_1(arg2)):
if BYTE_2(arg2) == 0:
argr == SENSOR_ID
else
argr == 0xFFFFFFFF
else:
argr = 0
Overclocking Methods
====================
.. warning::
These methods have not been tested and are only partially reverse
engineered.
WMI method Return_OverclockingReport([out] uint32 argr)
-------------------------------------------------------
::
CSMI (0xE3, 0x99)
argr = 0
CSMI is an unknown operation.
WMI method Set_OCUIBIOSControl([in] uint32 arg2, [out] uint32 argr)
-------------------------------------------------------------------
::
CSMI (0xE3, 0x99)
argr = 0
CSMI is an unknown operation.
WMI method Clear_OCFailSafeFlag([out] uint32 argr)
--------------------------------------------------
::
CSMI (0xE3, 0x99)
argr = 0
CSMI is an unknown operation.
WMI method MemoryOCControl([in] uint32 arg2, [out] uint32 argr)
---------------------------------------------------------------
AWCC supports memory overclocking, but this method is very intricate and has
not been deciphered yet.
GPIO methods
============
GPIO control Methods
====================
These methods are probably related to some kind of firmware update system,
through a GPIO device.
Alienware and Dell G Series devices with the AWCC interface usually have an
embedded STM32 RGB lighting controller with USB/HID capabilities. It's vendor ID
is ``187c`` while it's product ID may vary from model to model.
.. warning::
These methods have not been tested and are only partially reverse
engineered.
The control of two GPIO pins of this MCU is exposed as WMI methods for debugging
purposes.
+--------------+--------------------------------------------------------------+
| Pin | Description |
+==============+===============================+==============================+
| 0 | Device Firmware Update (DFU) | **HIGH**: Enables DFU mode |
| | mode pin. | on next MCU boot. |
| | +------------------------------+
| | | **LOW**: Disables DFU mode |
| | | on next MCU boot. |
+--------------+-------------------------------+------------------------------+
| 1 | Negative Reset (NRST) pin. | **HIGH**: MCU is ON. |
| | | |
| | +------------------------------+
| | | **LOW**: MCU is OFF. |
| | | |
+--------------+-------------------------------+------------------------------+
See :ref:`acknowledgements` for more information on this MCU.
.. note::
Some GPIO control methods break the usual argument structure and take a
**Pin number** instead of an operation on the first byte.
WMI method FWUpdateGPIOtoggle([in] uint32 arg2, [out] uint32 argr)
------------------------------------------------------------------
::
if BYTE_0(arg2) == 0:
if BYTE_1(arg2) == 1:
SET_PIN_A_HIGH()
else:
SET_PIN_A_LOW()
if BYTE_0(arg2) == 1:
if BYTE_1(arg2) == 1:
SET_PIN_B_HIGH()
else:
SET_PIN_B_LOW()
else:
argr = 1
+--------------------+------------------------------------+--------------------+
| Operation (Byte 0) | Description | Arguments |
+====================+====================================+====================+
| Pin number | Set the pin status | - Byte 1: Pin |
| | | status |
+--------------------+------------------------------------+--------------------+
WMI method ReadTotalofGPIOs([out] uint32 argr)
----------------------------------------------
::
+--------------------+------------------------------------+--------------------+
| Operation (Byte 0) | Description | Arguments |
+====================+====================================+====================+
| N/A | Get the total number of GPIOs | - None |
+--------------------+------------------------------------+--------------------+
argr = 0x02
.. note::
Due to how WMI methods are implemented on the firmware level, this method
requires a dummy uint32 input argument when invoked.
WMI method ReadGPIOpPinStatus([in] uint32 arg2, [out] uint32 argr)
------------------------------------------------------------------
::
+--------------------+------------------------------------+--------------------+
| Operation (Byte 0) | Description | Arguments |
+====================+====================================+====================+
| Pin number | Get the pin status | - None |
+--------------------+------------------------------------+--------------------+
if BYTE_0(arg2) == 0:
argr = PIN_A_STATUS
if BYTE_0(arg2) == 1:
argr = PIN_B_STATUS
.. note::
There known firmware bug in some laptops where reading the status of a pin
also flips it.
Other information Methods
=========================
@@ -386,12 +305,18 @@ Other information Methods
WMI method ReadChassisColor([out] uint32 argr)
----------------------------------------------
::
Returns the chassis color internal ID.
argr = CHASSIS_COLOR_ID
.. _acknowledgements:
Acknowledgements
================
Kudos to `AlexIII <https://github.com/AlexIII/tcc-g15>`_ for documenting
and testing available thermal profile codes.
Kudos to
* `AlexIII <https://github.com/AlexIII/tcc-g15>`_
* `T-Troll <https://github.com/T-Troll/alienfx-tools/>`_
* `Gabriel Marcano <https://gabriel.marcanobrady.family/blog/2024/12/16/dell-g5-5505-se-acpi-or-figuring-out-how-to-reset-the-rgb-controller/>`_
for documenting and testing some of this device's functionality, making it
possible to generalize this driver.

View File

@@ -118,9 +118,6 @@ The date is encoded in the following manner:
- bits 5 to 8 contain the manufacture month.
- bits 9 to 15 contain the manufacture year biased by 1980.
.. note::
The data format needs to be verified on more machines.
WMI method BatterySerialNumber()
--------------------------------
@@ -153,7 +150,40 @@ Returns the voltage flow of the battery in mV as an u16.
WMI method BatteryManufactureAccess()
-------------------------------------
Returns a manufacture-defined value as an u16.
Returns the health status of the battery as a u16.
The health status encoded in the following manner:
- the third nibble contains the general failure mode
- the fourth nibble contains the specific failure code
Valid failure modes are:
- permanent failure (``0x9``)
- overheat failure (``0xa``)
- overcurrent failure (``0xb``)
All other failure modes are to be considered normal.
The following failure codes are valid for a permanent failure:
- fuse blown (``0x0``)
- cell imbalance (``0x1``)
- overvoltage (``0x2``)
- fet failure (``0x3``)
The last two bits of the failure code are to be ignored when the battery
signals a permanent failure.
The following failure codes a valid for a overheat failure:
- overheat at start of charging (``0x5``)
- overheat during charging (``0x7``)
- overheat during discharging (``0x8``)
The following failure codes are valid for a overcurrent failure:
- overcurrent during charging (``0x6``)
- overcurrent during discharging (``0xb``)
WMI method BatteryRelativeStateOfCharge()
-----------------------------------------
@@ -260,14 +290,6 @@ Some machines like the Dell Inspiron 3505 only support a single battery and thus
ignore the battery index. Because of this the driver depends on the ACPI battery
hook mechanism to discover batteries.
.. note::
The ACPI battery matching algorithm currently used inside the driver is
outdated and does not match the algorithm described above. The reasons for
this are differences in the handling of the ToHexString() ACPI opcode between
Linux and Windows, which distorts the serial number of ACPI batteries on many
machines. Until this issue is resolved, the driver cannot use the above
algorithm.
Reverse-Engineering the DDV WMI interface
=========================================

View File

@@ -805,6 +805,9 @@ M: Kurt Borja <kuurtb@gmail.com>
L: platform-driver-x86@vger.kernel.org
L: Dell.Client.Kernel@dell.com
S: Maintained
F: Documentation/ABI/testing/debugfs-alienware-wmi
F: Documentation/ABI/testing/sysfs-platform-alienware-wmi
F: Documentation/admin-guide/laptops/alienware-wmi.rst
F: Documentation/wmi/devices/alienware-wmi.rst
F: drivers/platform/x86/dell/alienware-wmi*
@@ -6578,6 +6581,12 @@ F: net/ax25/ax25_out.c
F: net/ax25/ax25_timer.c
F: net/ax25/sysctl_net_ax25.c
DASHARO ACPI PLATFORM DRIVER
M: Michał Kopeć <michal.kopec@3mdeb.com>
S: Maintained
W: https://docs.dasharo.com/
F: drivers/platform/x86/dasharo-acpi.c
DATA ACCESS MONITOR
M: SeongJae Park <sj@kernel.org>
L: damon@lists.linux.dev
@@ -12355,6 +12364,7 @@ INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER
M: Daniel Scally <djrscally@gmail.com>
S: Maintained
F: drivers/platform/x86/intel/int3472/
F: include/linux/platform_data/x86/int3472.h
INTEL SPEED SELECT TECHNOLOGY
M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
@@ -18347,12 +18357,13 @@ S: Maintained
F: drivers/mtd/nand/onenand/
F: include/linux/mtd/onenand*.h
ONEXPLAYER FAN DRIVER
ONEXPLAYER PLATFORM EC DRIVER
M: Antheas Kapenekakis <lkml@antheas.dev>
M: Derek John Clark <derekjohn.clark@gmail.com>
M: Joaquín Ignacio Aramendía <samsagax@gmail.com>
L: linux-hwmon@vger.kernel.org
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/hwmon/oxp-sensors.c
F: drivers/platform/x86/oxpec.c
ONIE TLV NVMEM LAYOUT DRIVER
M: Miquel Raynal <miquel.raynal@bootlin.com>
@@ -19564,6 +19575,12 @@ S: Maintained
F: drivers/pnp/
F: include/linux/pnp.h
PORTWELL EC DRIVER
M: Yen-Chi Huang <jesse.huang@portwell.com.tw>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/portwell-ec.c
POSIX CLOCKS and TIMERS
M: Anna-Maria Behnsen <anna-maria@linutronix.de>
M: Frederic Weisbecker <frederic@kernel.org>
@@ -22971,7 +22988,6 @@ W: http://www.linux.it/~malattia/wiki/index.php/Sony_drivers
F: Documentation/admin-guide/laptops/sony-laptop.rst
F: drivers/char/sonypi.c
F: drivers/platform/x86/sony-laptop.c
F: include/linux/sony-laptop.h
SOPHGO DEVICETREES and DRIVERS
M: Chen Wang <unicorn_wang@outlook.com>
@@ -25044,6 +25060,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat
F: tools/power/x86/turbostat/
F: tools/testing/selftests/turbostat/
TUXEDO DRIVERS
M: Werner Sembach <wse@tuxedocomputers.com>
L: platform-driver-x86@vger.kernel.org
S: Supported
F: drivers/platform/x86/tuxedo/
TW5864 VIDEO4LINUX DRIVER
M: Bluecherry Maintainers <maintainers@bluecherrydvr.com>
M: Andrey Utkin <andrey.utkin@corp.bluecherry.net>

View File

@@ -52,6 +52,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
#define ROG_ALLY_REPORT_SIZE 64
#define ROG_ALLY_X_MIN_MCU 313
#define ROG_ALLY_MIN_MCU 319
#define SUPPORT_KBD_BACKLIGHT BIT(0)
#define MAX_TOUCH_MAJOR 8
@@ -84,6 +88,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_MEDION_E1239T BIT(10)
#define QUIRK_ROG_NKEY_KEYBOARD BIT(11)
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
#define QUIRK_ROG_ALLY_XPAD BIT(13)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@@ -534,9 +539,102 @@ static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
}
/*
* We don't care about any other part of the string except the version section.
* Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
* The bytes "5a 05 03 31 00 1a 13" and possibly more come before the version
* string, and there may be additional bytes after the version string such as
* "75 00 74 00 65 00" or a postfix such as "_T01"
*/
static int mcu_parse_version_string(const u8 *response, size_t response_size)
{
const u8 *end = response + response_size;
const u8 *p = response;
int dots, err, version;
char buf[4];
dots = 0;
while (p < end && dots < 2) {
if (*p++ == '.')
dots++;
}
if (dots != 2 || p >= end || (p + 3) >= end)
return -EINVAL;
memcpy(buf, p, 3);
buf[3] = '\0';
err = kstrtoint(buf, 10, &version);
if (err || version < 0)
return -EINVAL;
return version;
}
static int mcu_request_version(struct hid_device *hdev)
{
u8 *response __free(kfree) = kzalloc(ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
const u8 request[] = { 0x5a, 0x05, 0x03, 0x31, 0x00, 0x20 };
int ret;
if (!response)
return -ENOMEM;
ret = asus_kbd_set_report(hdev, request, sizeof(request));
if (ret < 0)
return ret;
ret = hid_hw_raw_request(hdev, FEATURE_REPORT_ID, response,
ROG_ALLY_REPORT_SIZE, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret < 0)
return ret;
ret = mcu_parse_version_string(response, ROG_ALLY_REPORT_SIZE);
if (ret < 0) {
pr_err("Failed to parse MCU version: %d\n", ret);
print_hex_dump(KERN_ERR, "MCU: ", DUMP_PREFIX_NONE,
16, 1, response, ROG_ALLY_REPORT_SIZE, false);
}
return ret;
}
static void validate_mcu_fw_version(struct hid_device *hdev, int idProduct)
{
int min_version, version;
version = mcu_request_version(hdev);
if (version < 0)
return;
switch (idProduct) {
case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY:
min_version = ROG_ALLY_MIN_MCU;
break;
case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X:
min_version = ROG_ALLY_X_MIN_MCU;
break;
default:
min_version = 0;
}
if (version < min_version) {
hid_warn(hdev,
"The MCU firmware version must be %d or greater to avoid issues with suspend.\n",
min_version);
} else {
set_ally_mcu_hack(ASUS_WMI_ALLY_MCU_HACK_DISABLED);
set_ally_mcu_powersave(true);
}
}
static int asus_kbd_register_leds(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
struct usb_interface *intf;
struct usb_device *udev;
unsigned char kbd_func;
int ret;
@@ -560,6 +658,14 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
if (ret < 0)
return ret;
}
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
intf = to_usb_interface(hdev->dev.parent);
udev = interface_to_usbdev(intf);
validate_mcu_fw_version(hdev,
le16_to_cpu(udev->descriptor.idProduct));
}
} else {
/* Initialize keyboard */
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
@@ -1280,10 +1386,10 @@ static const struct hid_device_id asus_devices[] = {
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD},
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
QUIRK_ROG_CLAYMORE_II_KEYBOARD },
@@ -1327,4 +1433,5 @@ static struct hid_driver asus_driver = {
};
module_hid_driver(asus_driver);
MODULE_IMPORT_NS("ASUS_WMI");
MODULE_LICENSE("GPL");

View File

@@ -1795,17 +1795,6 @@ config SENSORS_NZXT_SMART2
source "drivers/hwmon/occ/Kconfig"
config SENSORS_OXP
tristate "OneXPlayer EC fan control"
depends on ACPI_EC
depends on X86
help
If you say yes here you get support for fan readings and control over
OneXPlayer handheld devices. Only OneXPlayer mini AMD handheld variant
boards are supported.
Can also be built as a module. In that case it will be called oxp-sensors.
config SENSORS_PCF8591
tristate "Philips PCF8591 ADC/DAC"
depends on I2C

View File

@@ -183,7 +183,6 @@ obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o
obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o
obj-$(CONFIG_SENSORS_NZXT_KRAKEN3) += nzxt-kraken3.o
obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o
obj-$(CONFIG_SENSORS_OXP) += oxp-sensors.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o

View File

@@ -1,716 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Platform driver for OneXPlayer, AOKZOE, AYANEO, and OrangePi Handhelds
* that expose fan reading and control via hwmon sysfs.
*
* Old OXP boards have the same DMI strings and they are told apart by
* the boot cpu vendor (Intel/AMD). Of these older models only AMD is
* supported.
*
* 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>
*/
#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>
/* 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,
};
static enum oxp_board board;
/* 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 */
#define OXP_TURBO_RETURN_VAL 0x00 /* Common return val */
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 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_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X1"),
},
.driver_data = (void *)oxp_x1,
},
{},
};
/* Helper functions to handle EC read/write */
static int read_from_ec(u8 reg, int size, long *val)
{
int i;
int ret;
u8 buffer;
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;
}
/* Turbo button toggle functions */
static int tt_toggle_enable(void)
{
u8 reg;
u8 val;
switch (board) {
case oxp_mini_amd_a07:
reg = OXP_MINI_TURBO_SWITCH_REG;
val = OXP_MINI_TURBO_TAKE_VAL;
break;
case aok_zoe_a1:
case oxp_fly:
case oxp_mini_amd_pro:
reg = OXP_TURBO_SWITCH_REG;
val = OXP_TURBO_TAKE_VAL;
break;
case oxp_2:
case oxp_x1:
reg = OXP_2_TURBO_SWITCH_REG;
val = OXP_TURBO_TAKE_VAL;
break;
default:
return -EINVAL;
}
return write_to_ec(reg, val);
}
static int tt_toggle_disable(void)
{
u8 reg;
u8 val;
switch (board) {
case oxp_mini_amd_a07:
reg = OXP_MINI_TURBO_SWITCH_REG;
val = OXP_TURBO_RETURN_VAL;
break;
case aok_zoe_a1:
case oxp_fly:
case oxp_mini_amd_pro:
reg = OXP_TURBO_SWITCH_REG;
val = OXP_TURBO_RETURN_VAL;
break;
case oxp_2:
case oxp_x1:
reg = OXP_2_TURBO_SWITCH_REG;
val = OXP_TURBO_RETURN_VAL;
break;
default:
return -EINVAL;
}
return write_to_ec(reg, val);
}
/* 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:
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)
{
int rval;
bool value;
rval = kstrtobool(buf, &value);
if (rval)
return rval;
if (value) {
rval = tt_toggle_enable();
} else {
rval = tt_toggle_disable();
}
if (rval)
return rval;
return count;
}
static ssize_t tt_toggle_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int retval;
u8 reg;
long val;
switch (board) {
case oxp_mini_amd_a07:
reg = OXP_MINI_TURBO_SWITCH_REG;
break;
case aok_zoe_a1:
case oxp_fly:
case oxp_mini_amd_pro:
reg = OXP_TURBO_SWITCH_REG;
break;
case oxp_2:
case oxp_x1:
reg = OXP_2_TURBO_SWITCH_REG;
break;
default:
return -EINVAL;
}
retval = read_from_ec(reg, 1, &val);
if (retval)
return retval;
return sysfs_emit(buf, "%d\n", !!val);
}
static DEVICE_ATTR_RW(tt_toggle);
/* 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:
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:
return write_to_ec(OXP_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
default:
return -EINVAL;
}
}
/* 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;
}
}
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:
switch (board) {
case orange_pi_neo:
return read_from_ec(ORANGEPI_SENSOR_FAN_REG, 2, val);
case oxp_2:
case oxp_x1:
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:
return read_from_ec(OXP_SENSOR_FAN_REG, 2, val);
default:
break;
}
break;
default:
break;
}
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
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:
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:
default:
ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
if (ret)
return ret;
break;
}
return 0;
case hwmon_pwm_enable:
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:
return read_from_ec(OXP_SENSOR_PWM_ENABLE_REG, 1, val);
default:
break;
}
break;
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)
{
switch (type) {
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_enable:
if (val == 1)
return oxp_pwm_enable();
else if (val == 0)
return oxp_pwm_disable();
return -EINVAL;
case hwmon_pwm_input:
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:
/* 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:
return write_to_ec(OXP_SENSOR_PWM_REG, val);
default:
break;
}
break;
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_ec_attrs[] = {
&dev_attr_tt_toggle.attr,
NULL
};
static struct attribute_group oxp_ec_attribute_group = {
.is_visible = tt_toggle_is_visible,
.attrs = oxp_ec_attrs,
};
static const struct attribute_group *oxp_ec_groups[] = {
&oxp_ec_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;
hwdev = devm_hwmon_device_register_with_info(dev, "oxpec", NULL,
&oxp_ec_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwdev);
}
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");

View File

@@ -6,7 +6,7 @@
menuconfig ARM64_PLATFORM_DEVICES
bool "ARM64 Platform-Specific Device Drivers"
depends on ARM64 || COMPILE_TEST
default y
default ARM64
help
Say Y here to get to see options for platform-specific device drivers
for arm64 based devices, primarily EC-like device drivers.

View File

@@ -651,7 +651,7 @@ static int gaokun_ec_resume(struct device *dev)
break;
msleep(100); /* EC need time to resume */
};
}
ec->suspended = false;

View File

@@ -27,6 +27,19 @@ config MLX_PLATFORM
If you have a Mellanox system, say Y or M here.
config MLXREG_DPU
tristate "Nvidia Data Processor Unit platform driver support"
depends on I2C
select REGMAP_I2C
help
This driver provides support for the Nvidia BF3 Data Processor Units,
which are the part of SN4280 Ethernet smart switch systems
providing a high performance switching solution for Enterprise Data
Centers (EDC) for building Ethernet based clusters, High-Performance
Computing (HPC) and embedded environments.
If you have a Nvidia smart switch system, say Y or M here.
config MLXREG_HOTPLUG
tristate "Mellanox platform hotplug driver support"
depends on HWMON

View File

@@ -7,6 +7,7 @@ obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o
obj-$(CONFIG_MLXBF_BOOTCTL) += mlxbf-bootctl.o
obj-$(CONFIG_MLXBF_PMC) += mlxbf-pmc.o
obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o
obj-$(CONFIG_MLXREG_DPU) += mlxreg-dpu.o
obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o
obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o
obj-$(CONFIG_MLXREG_LC) += mlxreg-lc.o

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@
#define MLXBF_PMC_EVENT_SET_BF3 2
#define MLXBF_PMC_EVENT_INFO_LEN 100
#define MLXBF_PMC_MAX_BLOCKS 30
#define MLXBF_PMC_MAX_BLOCKS 40
#define MLXBF_PMC_MAX_ATTRS 70
#define MLXBF_PMC_INFO_SZ 4
#define MLXBF_PMC_REG_SIZE 8
@@ -139,6 +139,7 @@ struct mlxbf_pmc_block_info {
* @pdev: The kernel structure representing the device
* @total_blocks: Total number of blocks
* @tile_count: Number of tiles in the system
* @apt_enable: Info on enabled APTs
* @llt_enable: Info on enabled LLTs
* @mss_enable: Info on enabled MSSs
* @group_num: Group number assigned to each valid block
@@ -154,6 +155,7 @@ struct mlxbf_pmc_context {
struct platform_device *pdev;
u32 total_blocks;
u32 tile_count;
u8 apt_enable;
u8 llt_enable;
u8 mss_enable;
u32 group_num;
@@ -893,6 +895,107 @@ static const struct mlxbf_pmc_events mlxbf_pmc_clock_events[] = {
{ 0x6c, "REFERENCE_WINDOW_WIDTH_REF_156" },
};
static const struct mlxbf_pmc_events mlxbf_pmc_gga_events[] = {
{ 0, "GGA_PERF_DESC_WQE_STRB" },
{ 5, "GGA_PERF_DESC_CQE_STRB" },
{ 8, "GGA_PERF_DESC_TPT_REQUEST_STRB" },
{ 17, "GGA_PERF_DESC_TPT_RESPONSESTRB" },
{ 120, "GGA_PERF_DESC_ENGINE0_IN_DATA_STRB" },
{ 121, "GGA_PERF_DESC_ENGINE1_IN_DATA_STRB" },
{ 122, "GGA_PERF_DESC_ENGINE2_IN_DATA_STRB" },
{ 123, "GGA_PERF_DESC_ENGINE3_IN_DATA_STRB" },
{ 124, "GGA_PERF_DESC_ENGINE4_IN_DATA_STRB" },
{ 125, "GGA_PERF_DESC_ENGINE5_IN_DATA_STRB" },
{ 126, "GGA_PERF_DESC_ENGINE6_IN_DATA_STRB" },
{ 127, "GGA_PERF_DESC_ENGINE7_IN_DATA_STRB" },
{ 128, "GGA_PERF_DESC_ENGINE8_IN_DATA_STRB" },
{ 129, "GGA_PERF_DESC_ENGINE9_IN_DATA_STRB" },
{ 130, "GGA_PERF_DESC_ENGINE10_IN_DATA_STRB" },
{ 131, "GGA_PERF_DESC_ENGINE11_IN_DATA_STRB" },
{ 132, "GGA_PERF_DESC_ENGINE12_IN_DATA_STRB" },
{ 133, "GGA_PERF_DESC_ENGINE13_IN_DATA_STRB" },
{ 134, "GGA_PERF_DESC_ENGINE14_IN_DATA_STRB" },
{ 195, "GGA_PERF_DESC_ENGINE0_OUT_DATA_STRB" },
{ 196, "GGA_PERF_DESC_ENGINE1_OUT_DATA_STRB" },
{ 197, "GGA_PERF_DESC_ENGINE2_OUT_DATA_STRB" },
{ 198, "GGA_PERF_DESC_ENGINE3_OUT_DATA_STRB" },
{ 199, "GGA_PERF_DESC_ENGINE4_OUT_DATA_STRB" },
{ 200, "GGA_PERF_DESC_ENGINE5_OUT_DATA_STRB" },
{ 201, "GGA_PERF_DESC_ENGINE6_OUT_DATA_STRB" },
{ 202, "GGA_PERF_DESC_ENGINE7_OUT_DATA_STRB" },
{ 203, "GGA_PERF_DESC_ENGINE8_OUT_DATA_STRB" },
{ 204, "GGA_PERF_DESC_ENGINE9_OUT_DATA_STRB" },
{ 205, "GGA_PERF_DESC_ENGINE10_OUT_DATA_STRB" },
{ 206, "GGA_PERF_DESC_ENGINE11_OUT_DATA_STRB" },
{ 207, "GGA_PERF_DESC_ENGINE12_OUT_DATA_STRB" },
{ 208, "GGA_PERF_DESC_ENGINE13_OUT_DATA_STRB" },
{ 209, "GGA_PERF_DESC_ENGINE14_OUT_DATA_STRB" },
};
static const struct mlxbf_pmc_events mlxbf_pmc_apt_events[] = {
{ 0, "APT_DATA_0" },
{ 1, "APT_DATA_1" },
{ 2, "APT_DATA_2" },
{ 3, "APT_DATA_3" },
{ 4, "APT_DATA_4" },
{ 5, "APT_DATA_5" },
{ 6, "APT_DATA_6" },
{ 7, "APT_DATA_7" },
{ 8, "APT_DATA_8" },
{ 9, "APT_DATA_9" },
{ 10, "APT_DATA_10" },
{ 11, "APT_DATA_11" },
{ 12, "APT_DATA_12" },
{ 13, "APT_DATA_13" },
{ 14, "APT_DATA_14" },
{ 15, "APT_DATA_15" },
{ 16, "APT_DATA_16" },
{ 17, "APT_DATA_17" },
{ 18, "APT_DATA_18" },
{ 19, "APT_DATA_19" },
{ 20, "APT_DATA_20" },
{ 21, "APT_DATA_21" },
};
static const struct mlxbf_pmc_events mlxbf_pmc_emi_events[] = {
{ 0, "MCH_WR_IN_MCH_REQ_IN_STRB" },
{ 10, "MCH_RD_IN_MCH_REQ_IN_STRB" },
{ 20, "MCH_RD_RESP_DATA_MCH_RESP_OUT_STRB" },
{ 98, "EMI_ARBITER_EARB2CTRL_STRB" },
{ 99, "EMI_ARBITER_EARB2CTRL_RAS_STRB" },
{ 100, "EMI_ARBITER_EARB2CTRL_CAS_STRB" },
};
static const struct mlxbf_pmc_events mlxbf_pmc_prnf_events[] = {
{ 0, "PRNF_DMA_RD_TLP_REQ" },
{ 1, "PRNF_DMA_RD_ICMC_BYPASS_REQ" },
{ 8, "PRNF_DMA_RD_TLP_SENT_TO_CHI" },
{ 11, "PRNF_DMA_RD_CHI_RES" },
{ 17, "PRNF_DMA_RD_TLP_RES_SENT" },
{ 18, "PRNF_DMA_WR_WR0_SLICE_ALLOC_RO" },
{ 19, "PRNF_DMA_WR_WR0_SLICE_ALLOC_NRO" },
{ 24, "PRNF_DMA_WR_WR1_SLICE_ALLOC_RO" },
{ 25, "PRNF_DMA_WR_WR1_SLICE_ALLOC_NRO" },
{ 30, "PRNF_PIO_POSTED_REQ_PUSH" },
{ 31, "PRNF_PIO_POSTED_REQ_POP" },
{ 32, "PRNF_PIO_NP_REQ_PUSH" },
{ 33, "PRNF_PIO_NP_REQ_POP" },
{ 34, "PRNF_PIO_COMP_RO_PUSH" },
{ 35, "PRNF_PIO_COMP_RO_POP" },
{ 36, "PRNF_PIO_COMP_NRO_PUSH" },
{ 37, "PRNF_PIO_COMP_NRO_POP" },
};
static const struct mlxbf_pmc_events mlxbf_pmc_msn_events[] = {
{ 46, "MSN_CORE_MMA_WQE_DONE_PUSH_STRB" },
{ 116, "MSN_CORE_MSN2MMA_WQE_STRB" },
{ 164, "MSN_CORE_WQE_TOP_TILE_WQE_STRB" },
{ 168, "MSN_CORE_TPT_TOP_GGA_REQ_STRB" },
{ 171, "MSN_CORE_TPT_TOP_MMA_REQ_STRB" },
{ 174, "MSN_CORE_TPT_TOP_GGA_RES_STRB" },
{ 177, "MSN_CORE_TPT_TOP_MMA_RES_STRB" },
};
static struct mlxbf_pmc_context *pmc;
/* UUID used to probe ATF service. */
@@ -1069,6 +1172,21 @@ static const struct mlxbf_pmc_events *mlxbf_pmc_event_list(const char *blk, size
} else if (strstr(blk, "clock_measure")) {
events = mlxbf_pmc_clock_events;
size = ARRAY_SIZE(mlxbf_pmc_clock_events);
} else if (strstr(blk, "gga")) {
events = mlxbf_pmc_gga_events;
size = ARRAY_SIZE(mlxbf_pmc_gga_events);
} else if (strstr(blk, "apt")) {
events = mlxbf_pmc_apt_events;
size = ARRAY_SIZE(mlxbf_pmc_apt_events);
} else if (strstr(blk, "emi")) {
events = mlxbf_pmc_emi_events;
size = ARRAY_SIZE(mlxbf_pmc_emi_events);
} else if (strstr(blk, "prnf")) {
events = mlxbf_pmc_prnf_events;
size = ARRAY_SIZE(mlxbf_pmc_prnf_events);
} else if (strstr(blk, "msn")) {
events = mlxbf_pmc_msn_events;
size = ARRAY_SIZE(mlxbf_pmc_msn_events);
} else {
events = NULL;
size = 0;
@@ -2056,6 +2174,18 @@ static int mlxbf_pmc_map_counters(struct device *dev)
continue;
}
/* Create sysfs only for enabled EMI blocks */
if (strstr(pmc->block_name[i], "emi") &&
pmc->event_set == MLXBF_PMC_EVENT_SET_BF3) {
unsigned int emi_num;
if (sscanf(pmc->block_name[i], "emi%u", &emi_num) != 1)
continue;
if (!((pmc->mss_enable >> (emi_num / 2)) & 0x1))
continue;
}
/* Create sysfs only for enabled LLT blocks */
if (strstr(pmc->block_name[i], "llt_miss")) {
unsigned int llt_num;
@@ -2075,6 +2205,17 @@ static int mlxbf_pmc_map_counters(struct device *dev)
continue;
}
/* Create sysfs only for enabled APT blocks */
if (strstr(pmc->block_name[i], "apt")) {
unsigned int apt_num;
if (sscanf(pmc->block_name[i], "apt%u", &apt_num) != 1)
continue;
if (!((pmc->apt_enable >> apt_num) & 0x1))
continue;
}
ret = device_property_read_u64_array(dev, pmc->block_name[i],
info, MLXBF_PMC_INFO_SZ);
if (ret)
@@ -2171,13 +2312,17 @@ static int mlxbf_pmc_probe(struct platform_device *pdev)
return -EFAULT;
if (device_property_read_u32(dev, "tile_num", &pmc->tile_count)) {
if (device_property_read_u8(dev, "apt_enable", &pmc->apt_enable)) {
dev_warn(dev, "Number of APTs undefined, ignoring blocks\n");
pmc->apt_enable = 0;
}
if (device_property_read_u8(dev, "llt_enable", &pmc->llt_enable)) {
dev_err(dev, "Number of tiles/LLTs undefined\n");
return -EINVAL;
dev_warn(dev, "Number of LLTs undefined, ignoring blocks\n");
pmc->llt_enable = 0;
}
if (device_property_read_u8(dev, "mss_enable", &pmc->mss_enable)) {
dev_err(dev, "Number of tiles/MSSs undefined\n");
return -EINVAL;
dev_warn(dev, "Number of MSSs undefined, ignoring blocks\n");
pmc->mss_enable = 0;
}
}

View File

@@ -0,0 +1,613 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Nvidia Data Processor Unit platform driver
*
* Copyright (C) 2025 Nvidia Technologies Ltd.
*/
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/platform_data/mlxcpld.h>
#include <linux/platform_data/mlxreg.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
/* I2C bus IO offsets */
#define MLXREG_DPU_REG_FPGA1_VER_OFFSET 0x2400
#define MLXREG_DPU_REG_FPGA1_PN_OFFSET 0x2404
#define MLXREG_DPU_REG_FPGA1_PN1_OFFSET 0x2405
#define MLXREG_DPU_REG_PG_OFFSET 0x2414
#define MLXREG_DPU_REG_PG_EVENT_OFFSET 0x2415
#define MLXREG_DPU_REG_PG_MASK_OFFSET 0x2416
#define MLXREG_DPU_REG_RESET_GP1_OFFSET 0x2417
#define MLXREG_DPU_REG_RST_CAUSE1_OFFSET 0x241e
#define MLXREG_DPU_REG_GP0_RO_OFFSET 0x242b
#define MLXREG_DPU_REG_GP0_OFFSET 0x242e
#define MLXREG_DPU_REG_GP1_OFFSET 0x242c
#define MLXREG_DPU_REG_GP4_OFFSET 0x2438
#define MLXREG_DPU_REG_AGGRCO_OFFSET 0x2442
#define MLXREG_DPU_REG_AGGRCO_MASK_OFFSET 0x2443
#define MLXREG_DPU_REG_HEALTH_OFFSET 0x244d
#define MLXREG_DPU_REG_HEALTH_EVENT_OFFSET 0x244e
#define MLXREG_DPU_REG_HEALTH_MASK_OFFSET 0x244f
#define MLXREG_DPU_REG_FPGA1_MVER_OFFSET 0x24de
#define MLXREG_DPU_REG_CONFIG3_OFFSET 0x24fd
#define MLXREG_DPU_REG_MAX 0x3fff
/* Power Good event masks. */
#define MLXREG_DPU_PG_VDDIO_MASK BIT(0)
#define MLXREG_DPU_PG_VDD_CPU_MASK BIT(1)
#define MLXREG_DPU_PG_VDD_MASK BIT(2)
#define MLXREG_DPU_PG_1V8_MASK BIT(3)
#define MLXREG_DPU_PG_COMPARATOR_MASK BIT(4)
#define MLXREG_DPU_PG_VDDQ_MASK BIT(5)
#define MLXREG_DPU_PG_HVDD_MASK BIT(6)
#define MLXREG_DPU_PG_DVDD_MASK BIT(7)
#define MLXREG_DPU_PG_MASK (MLXREG_DPU_PG_DVDD_MASK | \
MLXREG_DPU_PG_HVDD_MASK | \
MLXREG_DPU_PG_VDDQ_MASK | \
MLXREG_DPU_PG_COMPARATOR_MASK | \
MLXREG_DPU_PG_1V8_MASK | \
MLXREG_DPU_PG_VDD_CPU_MASK | \
MLXREG_DPU_PG_VDD_MASK | \
MLXREG_DPU_PG_VDDIO_MASK)
/* Health event masks. */
#define MLXREG_DPU_HLTH_THERMAL_TRIP_MASK BIT(0)
#define MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK BIT(1)
#define MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK BIT(2)
#define MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK BIT(3)
#define MLXREG_DPU_HLTH_VDDQ_ALERT_MASK BIT(4)
#define MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK BIT(5)
#define MLXREG_DPU_HEALTH_MASK (MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK | \
MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK | \
MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK | \
MLXREG_DPU_HLTH_VDDQ_ALERT_MASK | \
MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK | \
MLXREG_DPU_HLTH_THERMAL_TRIP_MASK)
/* Hotplug aggregation masks. */
#define MLXREG_DPU_HEALTH_AGGR_MASK BIT(0)
#define MLXREG_DPU_PG_AGGR_MASK BIT(1)
#define MLXREG_DPU_AGGR_MASK (MLXREG_DPU_HEALTH_AGGR_MASK | \
MLXREG_DPU_PG_AGGR_MASK)
/* Voltage regulator firmware update status mask. */
#define MLXREG_DPU_VOLTREG_UPD_MASK GENMASK(5, 4)
#define MLXREG_DPU_NR_NONE (-1)
/*
* enum mlxreg_dpu_type - Data Processor Unit types
*
* @MLXREG_DPU_BF3: DPU equipped with BF3 SoC;
*/
enum mlxreg_dpu_type {
MLXREG_DPU_BF3 = 0x0050,
};
/* Default register access data. */
static struct mlxreg_core_data mlxreg_dpu_io_data[] = {
{
.label = "fpga1_version",
.reg = MLXREG_DPU_REG_FPGA1_VER_OFFSET,
.bit = GENMASK(7, 0),
.mode = 0444,
},
{
.label = "fpga1_pn",
.reg = MLXREG_DPU_REG_FPGA1_PN_OFFSET,
.bit = GENMASK(15, 0),
.mode = 0444,
.regnum = 2,
},
{
.label = "fpga1_version_min",
.reg = MLXREG_DPU_REG_FPGA1_MVER_OFFSET,
.bit = GENMASK(7, 0),
.mode = 0444,
},
{
.label = "perst_rst",
.reg = MLXREG_DPU_REG_RESET_GP1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(0),
.mode = 0644,
},
{
.label = "usbphy_rst",
.reg = MLXREG_DPU_REG_RESET_GP1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(1),
.mode = 0644,
},
{
.label = "phy_rst",
.reg = MLXREG_DPU_REG_RESET_GP1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(2),
.mode = 0644,
},
{
.label = "tpm_rst",
.reg = MLXREG_DPU_REG_RESET_GP1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(6),
.mode = 0644,
},
{
.label = "reset_from_main_board",
.reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(1),
.mode = 0444,
},
{
.label = "reset_aux_pwr_or_reload",
.reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(2),
.mode = 0444,
},
{
.label = "reset_comex_pwr_fail",
.reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(3),
.mode = 0444,
},
{
.label = "reset_dpu_thermal",
.reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(6),
.mode = 0444,
},
{
.label = "reset_pwr_off",
.reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(7),
.mode = 0444,
},
{
.label = "dpu_id",
.reg = MLXREG_DPU_REG_GP0_RO_OFFSET,
.bit = GENMASK(3, 0),
.mode = 0444,
},
{
.label = "voltreg_update_status",
.reg = MLXREG_DPU_REG_GP0_RO_OFFSET,
.mask = MLXREG_DPU_VOLTREG_UPD_MASK,
.bit = 5,
.mode = 0444,
},
{
.label = "boot_progress",
.reg = MLXREG_DPU_REG_GP1_OFFSET,
.mask = GENMASK(3, 0),
.mode = 0444,
},
{
.label = "ufm_upgrade",
.reg = MLXREG_DPU_REG_GP4_OFFSET,
.mask = GENMASK(7, 0) & ~BIT(1),
.mode = 0644,
},
};
static struct mlxreg_core_platform_data mlxreg_dpu_default_regs_io_data = {
.data = mlxreg_dpu_io_data,
.counter = ARRAY_SIZE(mlxreg_dpu_io_data),
};
/* Default hotplug data. */
static struct mlxreg_core_data mlxreg_dpu_power_events_items_data[] = {
{
.label = "pg_vddio",
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_VDDIO_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "pg_vdd_cpu",
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_VDD_CPU_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "pg_vdd",
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_VDD_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "pg_1v8",
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_1V8_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "pg_comparator",
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_COMPARATOR_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "pg_vddq",
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_VDDQ_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "pg_hvdd",
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_HVDD_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "pg_dvdd",
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_DVDD_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
};
static struct mlxreg_core_data mlxreg_dpu_health_events_items_data[] = {
{
.label = "thermal_trip",
.reg = MLXREG_DPU_REG_HEALTH_OFFSET,
.mask = MLXREG_DPU_HLTH_THERMAL_TRIP_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "ufm_upgrade_done",
.reg = MLXREG_DPU_REG_HEALTH_OFFSET,
.mask = MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "vddq_hot_alert",
.reg = MLXREG_DPU_REG_HEALTH_OFFSET,
.mask = MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "vdd_cpu_hot_alert",
.reg = MLXREG_DPU_REG_HEALTH_OFFSET,
.mask = MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "vddq_alert",
.reg = MLXREG_DPU_REG_HEALTH_OFFSET,
.mask = MLXREG_DPU_HLTH_VDDQ_ALERT_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
{
.label = "vdd_cpu_alert",
.reg = MLXREG_DPU_REG_HEALTH_OFFSET,
.mask = MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK,
.hpdev.nr = MLXREG_DPU_NR_NONE,
},
};
static struct mlxreg_core_item mlxreg_dpu_hotplug_items[] = {
{
.data = mlxreg_dpu_power_events_items_data,
.aggr_mask = MLXREG_DPU_PG_AGGR_MASK,
.reg = MLXREG_DPU_REG_PG_OFFSET,
.mask = MLXREG_DPU_PG_MASK,
.count = ARRAY_SIZE(mlxreg_dpu_power_events_items_data),
.health = false,
.inversed = 0,
},
{
.data = mlxreg_dpu_health_events_items_data,
.aggr_mask = MLXREG_DPU_HEALTH_AGGR_MASK,
.reg = MLXREG_DPU_REG_HEALTH_OFFSET,
.mask = MLXREG_DPU_HEALTH_MASK,
.count = ARRAY_SIZE(mlxreg_dpu_health_events_items_data),
.health = false,
.inversed = 0,
},
};
static
struct mlxreg_core_hotplug_platform_data mlxreg_dpu_default_hotplug_data = {
.items = mlxreg_dpu_hotplug_items,
.count = ARRAY_SIZE(mlxreg_dpu_hotplug_items),
.cell = MLXREG_DPU_REG_AGGRCO_OFFSET,
.mask = MLXREG_DPU_AGGR_MASK,
};
/**
* struct mlxreg_dpu - device private data
* @dev: platform device
* @data: platform core data
* @io_data: register access platform data
* @io_regs: register access device
* @hotplug_data: hotplug platform data
* @hotplug: hotplug device
*/
struct mlxreg_dpu {
struct device *dev;
struct mlxreg_core_data *data;
struct mlxreg_core_platform_data *io_data;
struct platform_device *io_regs;
struct mlxreg_core_hotplug_platform_data *hotplug_data;
struct platform_device *hotplug;
};
static bool mlxreg_dpu_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case MLXREG_DPU_REG_PG_EVENT_OFFSET:
case MLXREG_DPU_REG_PG_MASK_OFFSET:
case MLXREG_DPU_REG_RESET_GP1_OFFSET:
case MLXREG_DPU_REG_GP0_OFFSET:
case MLXREG_DPU_REG_GP1_OFFSET:
case MLXREG_DPU_REG_GP4_OFFSET:
case MLXREG_DPU_REG_AGGRCO_OFFSET:
case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET:
case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET:
case MLXREG_DPU_REG_HEALTH_MASK_OFFSET:
return true;
}
return false;
}
static bool mlxreg_dpu_readable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case MLXREG_DPU_REG_FPGA1_VER_OFFSET:
case MLXREG_DPU_REG_FPGA1_PN_OFFSET:
case MLXREG_DPU_REG_FPGA1_PN1_OFFSET:
case MLXREG_DPU_REG_PG_OFFSET:
case MLXREG_DPU_REG_PG_EVENT_OFFSET:
case MLXREG_DPU_REG_PG_MASK_OFFSET:
case MLXREG_DPU_REG_RESET_GP1_OFFSET:
case MLXREG_DPU_REG_RST_CAUSE1_OFFSET:
case MLXREG_DPU_REG_GP0_RO_OFFSET:
case MLXREG_DPU_REG_GP0_OFFSET:
case MLXREG_DPU_REG_GP1_OFFSET:
case MLXREG_DPU_REG_GP4_OFFSET:
case MLXREG_DPU_REG_AGGRCO_OFFSET:
case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET:
case MLXREG_DPU_REG_HEALTH_OFFSET:
case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET:
case MLXREG_DPU_REG_HEALTH_MASK_OFFSET:
case MLXREG_DPU_REG_FPGA1_MVER_OFFSET:
case MLXREG_DPU_REG_CONFIG3_OFFSET:
return true;
}
return false;
}
static bool mlxreg_dpu_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case MLXREG_DPU_REG_FPGA1_VER_OFFSET:
case MLXREG_DPU_REG_FPGA1_PN_OFFSET:
case MLXREG_DPU_REG_FPGA1_PN1_OFFSET:
case MLXREG_DPU_REG_PG_OFFSET:
case MLXREG_DPU_REG_PG_EVENT_OFFSET:
case MLXREG_DPU_REG_PG_MASK_OFFSET:
case MLXREG_DPU_REG_RESET_GP1_OFFSET:
case MLXREG_DPU_REG_RST_CAUSE1_OFFSET:
case MLXREG_DPU_REG_GP0_RO_OFFSET:
case MLXREG_DPU_REG_GP0_OFFSET:
case MLXREG_DPU_REG_GP1_OFFSET:
case MLXREG_DPU_REG_GP4_OFFSET:
case MLXREG_DPU_REG_AGGRCO_OFFSET:
case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET:
case MLXREG_DPU_REG_HEALTH_OFFSET:
case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET:
case MLXREG_DPU_REG_HEALTH_MASK_OFFSET:
case MLXREG_DPU_REG_FPGA1_MVER_OFFSET:
case MLXREG_DPU_REG_CONFIG3_OFFSET:
return true;
}
return false;
}
/* Configuration for the register map of a device with 2 bytes address space. */
static const struct regmap_config mlxreg_dpu_regmap_conf = {
.reg_bits = 16,
.val_bits = 8,
.max_register = MLXREG_DPU_REG_MAX,
.cache_type = REGCACHE_FLAT,
.writeable_reg = mlxreg_dpu_writeable_reg,
.readable_reg = mlxreg_dpu_readable_reg,
.volatile_reg = mlxreg_dpu_volatile_reg,
};
static int
mlxreg_dpu_copy_hotplug_data(struct device *dev, struct mlxreg_dpu *mlxreg_dpu,
const struct mlxreg_core_hotplug_platform_data *hotplug_data)
{
struct mlxreg_core_item *item;
int i;
mlxreg_dpu->hotplug_data = devm_kmemdup(dev, hotplug_data,
sizeof(*mlxreg_dpu->hotplug_data), GFP_KERNEL);
if (!mlxreg_dpu->hotplug_data)
return -ENOMEM;
mlxreg_dpu->hotplug_data->items = devm_kmemdup(dev, hotplug_data->items,
mlxreg_dpu->hotplug_data->count *
sizeof(*mlxreg_dpu->hotplug_data->items),
GFP_KERNEL);
if (!mlxreg_dpu->hotplug_data->items)
return -ENOMEM;
item = mlxreg_dpu->hotplug_data->items;
for (i = 0; i < hotplug_data->count; i++, item++) {
item->data = devm_kmemdup(dev, hotplug_data->items[i].data,
hotplug_data->items[i].count * sizeof(*item->data),
GFP_KERNEL);
if (!item->data)
return -ENOMEM;
}
return 0;
}
static int mlxreg_dpu_config_init(struct mlxreg_dpu *mlxreg_dpu, void *regmap,
struct mlxreg_core_data *data, int irq)
{
struct device *dev = &data->hpdev.client->dev;
u32 regval;
int err;
/* Validate DPU type. */
err = regmap_read(regmap, MLXREG_DPU_REG_CONFIG3_OFFSET, &regval);
if (err)
return err;
switch (regval) {
case MLXREG_DPU_BF3:
/* Copy platform specific hotplug data. */
err = mlxreg_dpu_copy_hotplug_data(dev, mlxreg_dpu,
&mlxreg_dpu_default_hotplug_data);
if (err)
return err;
mlxreg_dpu->io_data = &mlxreg_dpu_default_regs_io_data;
break;
default:
return -ENODEV;
}
/* Register IO access driver. */
if (mlxreg_dpu->io_data) {
mlxreg_dpu->io_data->regmap = regmap;
mlxreg_dpu->io_regs =
platform_device_register_resndata(dev, "mlxreg-io",
data->slot, NULL, 0,
mlxreg_dpu->io_data,
sizeof(*mlxreg_dpu->io_data));
if (IS_ERR(mlxreg_dpu->io_regs)) {
dev_err(dev, "Failed to create regio for client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr,
data->hpdev.brdinfo->addr);
return PTR_ERR(mlxreg_dpu->io_regs);
}
}
/* Register hotplug driver. */
if (mlxreg_dpu->hotplug_data && irq) {
mlxreg_dpu->hotplug_data->regmap = regmap;
mlxreg_dpu->hotplug_data->irq = irq;
mlxreg_dpu->hotplug =
platform_device_register_resndata(dev, "mlxreg-hotplug",
data->slot, NULL, 0,
mlxreg_dpu->hotplug_data,
sizeof(*mlxreg_dpu->hotplug_data));
if (IS_ERR(mlxreg_dpu->hotplug)) {
err = PTR_ERR(mlxreg_dpu->hotplug);
goto fail_register_hotplug;
}
}
return 0;
fail_register_hotplug:
platform_device_unregister(mlxreg_dpu->io_regs);
return err;
}
static void mlxreg_dpu_config_exit(struct mlxreg_dpu *mlxreg_dpu)
{
platform_device_unregister(mlxreg_dpu->hotplug);
platform_device_unregister(mlxreg_dpu->io_regs);
}
static int mlxreg_dpu_probe(struct platform_device *pdev)
{
struct mlxreg_core_data *data;
struct mlxreg_dpu *mlxreg_dpu;
void *regmap;
int err;
data = dev_get_platdata(&pdev->dev);
if (!data || !data->hpdev.brdinfo)
return -EINVAL;
data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr);
if (!data->hpdev.adapter)
return -EPROBE_DEFER;
mlxreg_dpu = devm_kzalloc(&pdev->dev, sizeof(*mlxreg_dpu), GFP_KERNEL);
if (!mlxreg_dpu) {
err = -ENOMEM;
goto alloc_fail;
}
/* Create device at the top of DPU I2C tree. */
data->hpdev.client = i2c_new_client_device(data->hpdev.adapter,
data->hpdev.brdinfo);
if (IS_ERR(data->hpdev.client)) {
dev_err(&pdev->dev, "Failed to create client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
err = PTR_ERR(data->hpdev.client);
goto i2c_new_device_fail;
}
regmap = devm_regmap_init_i2c(data->hpdev.client, &mlxreg_dpu_regmap_conf);
if (IS_ERR(regmap)) {
dev_err(&pdev->dev, "Failed to create regmap for client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
err = PTR_ERR(regmap);
goto devm_regmap_init_i2c_fail;
}
/* Sync registers with hardware. */
regcache_mark_dirty(regmap);
err = regcache_sync(regmap);
if (err) {
dev_err(&pdev->dev, "Failed to sync regmap for client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
goto regcache_sync_fail;
}
mlxreg_dpu->data = data;
mlxreg_dpu->dev = &pdev->dev;
platform_set_drvdata(pdev, mlxreg_dpu);
err = mlxreg_dpu_config_init(mlxreg_dpu, regmap, data, data->hpdev.brdinfo->irq);
if (err)
goto mlxreg_dpu_config_init_fail;
return err;
mlxreg_dpu_config_init_fail:
regcache_sync_fail:
devm_regmap_init_i2c_fail:
i2c_unregister_device(data->hpdev.client);
i2c_new_device_fail:
alloc_fail:
i2c_put_adapter(data->hpdev.adapter);
return err;
}
static void mlxreg_dpu_remove(struct platform_device *pdev)
{
struct mlxreg_core_data *data = dev_get_platdata(&pdev->dev);
struct mlxreg_dpu *mlxreg_dpu = platform_get_drvdata(pdev);
mlxreg_dpu_config_exit(mlxreg_dpu);
i2c_unregister_device(data->hpdev.client);
i2c_put_adapter(data->hpdev.adapter);
}
static struct platform_driver mlxreg_dpu_driver = {
.probe = mlxreg_dpu_probe,
.remove = mlxreg_dpu_remove,
.driver = {
.name = "mlxreg-dpu",
},
};
module_platform_driver(mlxreg_dpu_driver);
MODULE_AUTHOR("Vadim Pasternak <vadimp@nvidia.com>");
MODULE_DESCRIPTION("Nvidia Data Processor Unit platform driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS("platform:mlxreg-dpu");

View File

@@ -262,7 +262,7 @@ static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv)
item = pdata->items;
/* Go over all kinds of items - psu, pwr, fan. */
for (i = 0; i < pdata->counter; i++, item++) {
for (i = 0; i < pdata->count; i++, item++) {
if (item->capability) {
/*
* Read group capability register to get actual number
@@ -541,7 +541,7 @@ static void mlxreg_hotplug_work_handler(struct work_struct *work)
goto unmask_event;
/* Handle topology and health configuration changes. */
for (i = 0; i < pdata->counter; i++, item++) {
for (i = 0; i < pdata->count; i++, item++) {
if (aggr_asserted & item->aggr_mask) {
if (item->health)
mlxreg_hotplug_health_work_helper(priv, item);
@@ -590,7 +590,7 @@ static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv)
pdata = dev_get_platdata(&priv->pdev->dev);
item = pdata->items;
for (i = 0; i < pdata->counter; i++, item++) {
for (i = 0; i < pdata->count; i++, item++) {
/* Clear group presense event. */
ret = regmap_write(priv->regmap, item->reg +
MLXREG_HOTPLUG_EVENT_OFF, 0);
@@ -674,7 +674,7 @@ static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv)
0);
/* Clear topology configurations. */
for (i = 0; i < pdata->counter; i++, item++) {
for (i = 0; i < pdata->count; i++, item++) {
data = item->data;
/* Mask group presense event. */
regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF,

View File

@@ -6,6 +6,7 @@
*/
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@@ -104,6 +105,9 @@
| NVSW_SN2201_CPLD_AGGR_PSU_MASK_DEF \
| NVSW_SN2201_CPLD_AGGR_PWR_MASK_DEF \
| NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF)
#define NVSW_SN2201_CPLD_AGGR_BUSBAR_MASK_DEF \
(NVSW_SN2201_CPLD_AGGR_ASIC_MASK_DEF \
| NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF)
#define NVSW_SN2201_CPLD_ASIC_MASK GENMASK(3, 1)
#define NVSW_SN2201_CPLD_PSU_MASK GENMASK(1, 0)
@@ -132,6 +136,7 @@
* @cpld_devs: I2C devices for cpld;
* @cpld_devs_num: number of I2C devices for cpld;
* @main_mux_deferred_nr: I2C adapter number must be exist prior creating devices execution;
* @ext_pwr_source: true if system powered by external power supply; false - by internal;
*/
struct nvsw_sn2201 {
struct device *dev;
@@ -152,6 +157,7 @@ struct nvsw_sn2201 {
struct mlxreg_hotplug_device *cpld_devs;
int cpld_devs_num;
int main_mux_deferred_nr;
bool ext_pwr_source;
};
static bool nvsw_sn2201_writeable_reg(struct device *dev, unsigned int reg)
@@ -517,11 +523,40 @@ static struct mlxreg_core_item nvsw_sn2201_items[] = {
static
struct mlxreg_core_hotplug_platform_data nvsw_sn2201_hotplug = {
.items = nvsw_sn2201_items,
.counter = ARRAY_SIZE(nvsw_sn2201_items),
.count = ARRAY_SIZE(nvsw_sn2201_items),
.cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET,
.mask = NVSW_SN2201_CPLD_AGGR_MASK_DEF,
};
static struct mlxreg_core_item nvsw_sn2201_busbar_items[] = {
{
.data = nvsw_sn2201_fan_items_data,
.aggr_mask = NVSW_SN2201_CPLD_AGGR_FAN_MASK_DEF,
.reg = NVSW_SN2201_FAN_PRSNT_STATUS_OFFSET,
.mask = NVSW_SN2201_CPLD_FAN_MASK,
.count = ARRAY_SIZE(nvsw_sn2201_fan_items_data),
.inversed = 1,
.health = false,
},
{
.data = nvsw_sn2201_sys_items_data,
.aggr_mask = NVSW_SN2201_CPLD_AGGR_ASIC_MASK_DEF,
.reg = NVSW_SN2201_ASIC_STATUS_OFFSET,
.mask = NVSW_SN2201_CPLD_ASIC_MASK,
.count = ARRAY_SIZE(nvsw_sn2201_sys_items_data),
.inversed = 1,
.health = false,
},
};
static
struct mlxreg_core_hotplug_platform_data nvsw_sn2201_busbar_hotplug = {
.items = nvsw_sn2201_busbar_items,
.count = ARRAY_SIZE(nvsw_sn2201_busbar_items),
.cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET,
.mask = NVSW_SN2201_CPLD_AGGR_BUSBAR_MASK_DEF,
};
/* SN2201 static devices. */
static struct i2c_board_info nvsw_sn2201_static_devices[] = {
{
@@ -557,6 +592,9 @@ static struct i2c_board_info nvsw_sn2201_static_devices[] = {
{
I2C_BOARD_INFO("pmbus", 0x40),
},
{
I2C_BOARD_INFO("lm5066i", 0x15),
},
};
/* SN2201 default static board info. */
@@ -607,6 +645,58 @@ static struct mlxreg_hotplug_device nvsw_sn2201_static_brdinfo[] = {
},
};
/* SN2201 default busbar static board info. */
static struct mlxreg_hotplug_device nvsw_sn2201_busbar_static_brdinfo[] = {
{
.brdinfo = &nvsw_sn2201_static_devices[0],
.nr = NVSW_SN2201_MAIN_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[1],
.nr = NVSW_SN2201_MAIN_MUX_CH0_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[2],
.nr = NVSW_SN2201_MAIN_MUX_CH0_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[3],
.nr = NVSW_SN2201_MAIN_MUX_CH0_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[4],
.nr = NVSW_SN2201_MAIN_MUX_CH3_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[5],
.nr = NVSW_SN2201_MAIN_MUX_CH5_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[6],
.nr = NVSW_SN2201_MAIN_MUX_CH5_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[7],
.nr = NVSW_SN2201_MAIN_MUX_CH5_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[8],
.nr = NVSW_SN2201_MAIN_MUX_CH6_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[9],
.nr = NVSW_SN2201_MAIN_MUX_CH6_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[10],
.nr = NVSW_SN2201_MAIN_MUX_CH7_NR,
},
{
.brdinfo = &nvsw_sn2201_static_devices[11],
.nr = NVSW_SN2201_MAIN_MUX_CH1_NR,
},
};
/* LED default data. */
static struct mlxreg_core_data nvsw_sn2201_led_data[] = {
{
@@ -981,7 +1071,10 @@ static int nvsw_sn2201_config_init(struct nvsw_sn2201 *nvsw_sn2201, void *regmap
nvsw_sn2201->io_data = &nvsw_sn2201_regs_io;
nvsw_sn2201->led_data = &nvsw_sn2201_led;
nvsw_sn2201->wd_data = &nvsw_sn2201_wd;
nvsw_sn2201->hotplug_data = &nvsw_sn2201_hotplug;
if (nvsw_sn2201->ext_pwr_source)
nvsw_sn2201->hotplug_data = &nvsw_sn2201_busbar_hotplug;
else
nvsw_sn2201->hotplug_data = &nvsw_sn2201_hotplug;
/* Register IO access driver. */
if (nvsw_sn2201->io_data) {
@@ -1198,12 +1291,18 @@ static int nvsw_sn2201_config_pre_init(struct nvsw_sn2201 *nvsw_sn2201)
static int nvsw_sn2201_probe(struct platform_device *pdev)
{
struct nvsw_sn2201 *nvsw_sn2201;
const char *sku;
int ret;
nvsw_sn2201 = devm_kzalloc(&pdev->dev, sizeof(*nvsw_sn2201), GFP_KERNEL);
if (!nvsw_sn2201)
return -ENOMEM;
/* Validate system powering type - only HI168 SKU supports external power. */
sku = dmi_get_system_info(DMI_PRODUCT_SKU);
if (sku && !strcmp(sku, "HI168"))
nvsw_sn2201->ext_pwr_source = true;
nvsw_sn2201->dev = &pdev->dev;
platform_set_drvdata(pdev, nvsw_sn2201);
ret = platform_device_add_resources(pdev, nvsw_sn2201_lpc_io_resources,
@@ -1214,8 +1313,13 @@ static int nvsw_sn2201_probe(struct platform_device *pdev)
nvsw_sn2201->main_mux_deferred_nr = NVSW_SN2201_MAIN_MUX_DEFER_NR;
nvsw_sn2201->main_mux_devs = nvsw_sn2201_main_mux_brdinfo;
nvsw_sn2201->cpld_devs = nvsw_sn2201_cpld_brdinfo;
nvsw_sn2201->sn2201_devs = nvsw_sn2201_static_brdinfo;
nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_static_brdinfo);
if (nvsw_sn2201->ext_pwr_source) {
nvsw_sn2201->sn2201_devs = nvsw_sn2201_busbar_static_brdinfo;
nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_busbar_static_brdinfo);
} else {
nvsw_sn2201->sn2201_devs = nvsw_sn2201_static_brdinfo;
nvsw_sn2201->sn2201_devs_num = ARRAY_SIZE(nvsw_sn2201_static_brdinfo);
}
return nvsw_sn2201_config_pre_init(nvsw_sn2201);
}

View File

@@ -6,7 +6,7 @@
menuconfig SURFACE_PLATFORMS
bool "Microsoft Surface Platform-Specific Device Drivers"
depends on ARM64 || X86 || COMPILE_TEST
default y
default y if ARM64 || X86
help
Say Y here to get to see options for platform-specific device drivers
for Microsoft Surface devices. This option alone does not add any

View File

@@ -779,6 +779,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
@@ -1075,6 +1090,16 @@ config LENOVO_WMI_CAMERA
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
config DASHARO_ACPI
tristate "Dasharo ACPI Platform Driver"
depends on ACPI
depends on HWMON
help
This driver provides HWMON support for devices running Dasharo
firmware.
If you have a device with Dasharo firmware, choose Y or M here.
source "drivers/platform/x86/x86-android-tablets/Kconfig"
config FW_ATTR_CLASS
@@ -1201,6 +1226,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

View File

@@ -92,6 +92,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 +115,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
@@ -149,8 +155,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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,311 @@
// 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/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"
/*
* 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 */
};
/* Top-level OV05C10 camera node property table */
static const struct property_entry ov05c10_camera_props[] = {
PROPERTY_ENTRY_U32("clock-frequency", 24 * HZ_PER_MHZ),
{ }
};
/* Root AMD ISP OV05C10 camera node definition */
static const struct software_node camera_node = {
.name = AMDISP_OV05C10_HID,
.properties = ov05c10_camera_props,
};
/*
* AMD ISP OV05C10 Ports node definition. No properties defined for
* ports node for OV05C10.
*/
static const struct software_node ports = {
.name = "ports",
.parent = &camera_node,
};
/*
* AMD ISP OV05C10 Port node definition. No properties defined for
* port node for OV05C10.
*/
static const struct software_node port_node = {
.name = "port@",
.parent = &ports,
};
/*
* Remote endpoint AMD ISP node definition. No properties defined for
* remote endpoint node for OV05C10.
*/
static const struct software_node remote_ep_isp_node = {
.name = AMDISP_OV05C10_REMOTE_EP_NAME,
};
/*
* Remote endpoint reference for isp node included in the
* OV05C10 endpoint.
*/
static const struct software_node_ref_args ov05c10_refs[] = {
SOFTWARE_NODE_REFERENCE(&remote_ep_isp_node),
};
/* OV05C10 supports one single link frequency */
static const u64 ov05c10_link_freqs[] = {
925 * 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),
{ }
};
/* AMD ISP endpoint node definition */
static const struct software_node endpoint_node = {
.name = "endpoint",
.parent = &port_node,
.properties = ov05c10_endpoint_props,
};
/*
* AMD ISP swnode graph uses 5 nodes and also its relationship is
* fixed to align with the structure that v4l2 expects for successful
* endpoint fwnode parsing.
*
* It is only the node property_entries that will vary for each platform
* supporting different sensor modules.
*/
static const struct software_node *ov05c10_nodes[] = {
&camera_node,
&ports,
&port_node,
&endpoint_node,
&remote_ep_isp_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 = ov05c10_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->owner->name, "i2c_designware_amdisp");
}
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);
isp4_platform->board_info.swnode = src->swnodes[0];
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;
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;
/* 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");

View File

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

View File

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

View File

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

View File

@@ -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.
@@ -230,6 +228,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 };

View File

@@ -12,6 +12,7 @@
#include <linux/compiler_types.h>
#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
#include <linux/semaphore.h>
@@ -25,6 +26,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 +64,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_REACHABLE(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 */

View File

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

View File

@@ -24,7 +24,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 +189,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;

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -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(&fdev->dev, "dell-pc", NULL,
&dell_pc_platform_profile_ops);
ppdev = devm_platform_profile_register(&platform_device->dev, "dell-pc",
NULL, &dell_pc_platform_profile_ops);
if (IS_ERR(ppdev)) {
ret = PTR_ERR(ppdev);
goto cleanup_platform_device;
}
return 0;
cleanup_platform_device:
platform_device_unregister(platform_device);
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);

View File

@@ -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,78 @@ 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;
uid_str = acpi_device_uid(acpi_dev);
if (!uid_str)
return -ENODEV;
guard(mutex)(&data->translation_cache_lock);
return kstrtou32(uid_str, 10, index);
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 */
ret = power_supply_get_property(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;
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 -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 +750,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 +769,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 +887,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 +905,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 +932,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 +961,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;

View File

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

View File

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

View File

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

View File

@@ -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;
}
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;
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 < GPIO_REGULATOR_SUPPLY_MAP_COUNT; i++) {
const char *supply = i ? regulator->supply_name_upper : supply_name;
regulator->supply_map[j].supply = supply;
regulator->supply_map[j].dev_name = int3472->sensor_name;
j++;
if (second_sensor) {
int3472->regulator.supply_map[j].supply =
skl_int3472_regulator_map_supplies[i];
int3472->regulator.supply_map[j].dev_name = second_sensor;
regulator->supply_map[j].supply = supply;
regulator->supply_map[j].dev_name = second_sensor;
j++;
}
}
init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS;
init_data.consumer_supplies = int3472->regulator.supply_map;
init_data.consumer_supplies = regulator->supply_map;
init_data.num_consumer_supplies = j;
snprintf(int3472->regulator.regulator_name,
sizeof(int3472->regulator.regulator_name), "%s-regulator",
acpi_dev_name(int3472->adev));
snprintf(int3472->regulator.supply_name,
GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0");
snprintf(regulator->regulator_name, sizeof(regulator->regulator_name), "%s-%s",
acpi_dev_name(int3472->adev), supply_name);
int3472->regulator.rdesc = INT3472_REGULATOR(
int3472->regulator.regulator_name,
int3472->regulator.supply_name,
&int3472_gpio_regulator_ops);
int3472->regulator.gpio = gpio;
regulator->rdesc = INT3472_REGULATOR(regulator->regulator_name,
&int3472_gpio_regulator_ops,
enable_time, GPIO_REGULATOR_OFF_ON_DELAY);
cfg.dev = &int3472->adev->dev;
cfg.init_data = &init_data;
cfg.ena_gpiod = int3472->regulator.gpio;
cfg.ena_gpiod = gpio;
int3472->regulator.rdev = regulator_register(int3472->dev,
&int3472->regulator.rdesc,
&cfg);
regulator->rdev = regulator_register(int3472->dev, &regulator->rdesc, &cfg);
if (IS_ERR(regulator->rdev))
return PTR_ERR(regulator->rdev);
return PTR_ERR_OR_ZERO(int3472->regulator.rdev);
int3472->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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,18 @@ enum ppfear_regs {
#define PTL_PMC_LTR_CUR_PLT 0x1C2C
#define PTL_PCD_PMC_MMIO_REG_LEN 0x31A8
/* SSRAM PMC Device ID */
/* 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 +413,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 +432,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 +500,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 +508,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;

View File

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

View File

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

View File

@@ -0,0 +1,204 @@
// 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) },
{ }
};
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");

View File

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

View File

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

View File

@@ -35,7 +35,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 +381,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 +1017,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 +1070,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 +1178,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 +1392,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 +1409,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 +1433,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 +1453,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 +1517,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;

View File

@@ -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,28 @@ 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)
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 +255,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)

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@
#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 +50,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 +349,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 +388,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 +447,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 intel_tpmi_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)
@@ -552,12 +596,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 +679,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");

View File

@@ -332,13 +332,16 @@ 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");

1054
drivers/platform/x86/oxpec.c Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,291 @@
// 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;
}
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");

View File

@@ -245,18 +245,19 @@ 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;
return -EPERM;
silicom_mec_port_set(channel, !value);
return 0;
}
static int silicom_gpio_direction_output(struct gpio_chip *gc,
@@ -469,7 +470,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,

View File

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

View File

@@ -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 */
@@ -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;
@@ -3304,7 +3324,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 +3488,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 +11192,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:
@@ -11241,6 +11269,19 @@ static bool tpacpi_driver_event(const unsigned int hkey_event)
else
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -110,6 +110,8 @@ static const char * const POWER_SUPPLY_HEALTH_TEXT[] = {
[POWER_SUPPLY_HEALTH_COOL] = "Cool",
[POWER_SUPPLY_HEALTH_HOT] = "Hot",
[POWER_SUPPLY_HEALTH_NO_BATTERY] = "No battery",
[POWER_SUPPLY_HEALTH_BLOWN_FUSE] = "Blown fuse",
[POWER_SUPPLY_HEALTH_CELL_IMBALANCE] = "Cell imbalance",
};
static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = {
@@ -138,9 +140,10 @@ static const char * const POWER_SUPPLY_SCOPE_TEXT[] = {
};
static const char * const POWER_SUPPLY_CHARGE_BEHAVIOUR_TEXT[] = {
[POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto",
[POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge",
[POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge",
[POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO] = "auto",
[POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE] = "inhibit-charge",
[POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE] = "inhibit-charge-awake",
[POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE] = "force-discharge",
};
static struct power_supply_attr power_supply_attrs[] __ro_after_init = {

View File

@@ -228,6 +228,7 @@ static const struct power_supply_desc test_power_desc[] = {
.property_is_writeable = test_power_battery_property_is_writeable,
.charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO)
| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE)
| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE)
| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE),
.charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD)
| BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)

View File

@@ -139,12 +139,13 @@ static inline struct intel_vsec_device *auxdev_to_ivdev(struct auxiliary_device
}
#if IS_ENABLED(CONFIG_INTEL_VSEC)
void intel_vsec_register(struct pci_dev *pdev,
int intel_vsec_register(struct pci_dev *pdev,
struct intel_vsec_platform_info *info);
#else
static inline void intel_vsec_register(struct pci_dev *pdev,
static inline int intel_vsec_register(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
return -ENODEV;
}
#endif
#endif

View File

@@ -209,7 +209,7 @@ struct mlxreg_core_platform_data {
* @items: same type components with the hotplug capability;
* @irq: platform interrupt number;
* @regmap: register map of parent device;
* @counter: number of the components with the hotplug capability;
* @count: number of the components with the hotplug capability;
* @cell: location of top aggregation interrupt register;
* @mask: top aggregation interrupt common mask;
* @cell_low: location of low aggregation interrupt register;
@@ -224,7 +224,7 @@ struct mlxreg_core_hotplug_platform_data {
struct mlxreg_core_item *items;
int irq;
void *regmap;
int counter;
int count;
u32 cell;
u32 mask;
u32 cell_low;

View File

@@ -157,9 +157,28 @@
#define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00
#define ASUS_WMI_DSTS_LIGHTBAR_MASK 0x0000000F
enum asus_ally_mcu_hack {
ASUS_WMI_ALLY_MCU_HACK_INIT,
ASUS_WMI_ALLY_MCU_HACK_ENABLED,
ASUS_WMI_ALLY_MCU_HACK_DISABLED,
};
#if IS_REACHABLE(CONFIG_ASUS_WMI)
void set_ally_mcu_hack(enum asus_ally_mcu_hack status);
void set_ally_mcu_powersave(bool enabled);
int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval);
int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
#else
static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
{
}
static inline void set_ally_mcu_powersave(bool enabled)
{
}
static inline int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval)
{
return -ENODEV;
}
static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
u32 *retval)
{

View File

@@ -1,8 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Author: Dan Scally <djrscally@gmail.com> */
/*
* Intel INT3472 ACPI camera sensor power-management support
*
* Author: Dan Scally <djrscally@gmail.com>
*/
#ifndef _INTEL_SKL_INT3472_H
#define _INTEL_SKL_INT3472_H
#ifndef __PLATFORM_DATA_X86_INT3472_H
#define __PLATFORM_DATA_X86_INT3472_H
#include <linux/clk-provider.h>
#include <linux/gpio/machine.h>
@@ -22,25 +26,39 @@
#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b
#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c
#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d
#define INT3472_GPIO_TYPE_HANDSHAKE 0x12
#define INT3472_PDEV_MAX_NAME_LEN 23
#define INT3472_MAX_SENSOR_GPIOS 3
#define INT3472_MAX_REGULATORS 3
#define GPIO_REGULATOR_NAME_LENGTH 21
#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9
/* E.g. "avdd\0" */
#define GPIO_SUPPLY_NAME_LENGTH 5
/* 12 chars for acpi_dev_name() + "-", e.g. "ABCD1234:00-" */
#define GPIO_REGULATOR_NAME_LENGTH (12 + GPIO_SUPPLY_NAME_LENGTH)
/* lower- and upper-case mapping */
#define GPIO_REGULATOR_SUPPLY_MAP_COUNT 2
/*
* Ensure the GPIO is driven low/high for at least 2 ms before changing.
*
* 2 ms has been chosen because it is the minimum time ovXXXX sensors need to
* have their reset line driven logical high to properly register a reset.
*/
#define GPIO_REGULATOR_ENABLE_TIME (2 * USEC_PER_MSEC)
#define GPIO_REGULATOR_OFF_ON_DELAY (2 * USEC_PER_MSEC)
#define INT3472_LED_MAX_NAME_LEN 32
#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86
#define INT3472_REGULATOR(_name, _supply, _ops) \
#define INT3472_REGULATOR(_name, _ops, _enable_time, _off_on_delay) \
(const struct regulator_desc) { \
.name = _name, \
.supply_name = _supply, \
.type = REGULATOR_VOLTAGE, \
.ops = _ops, \
.owner = THIS_MODULE, \
.enable_time = _enable_time, \
.off_on_delay = _off_on_delay, \
}
#define to_int3472_clk(hw) \
@@ -50,6 +68,7 @@
container_of(clk, struct int3472_discrete_device, clock)
struct acpi_device;
struct dmi_system_id;
struct i2c_client;
struct platform_device;
@@ -70,23 +89,28 @@ struct int3472_cldb {
u8 reserved2[17];
};
struct int3472_discrete_quirks {
/* For models where AVDD GPIO is shared between sensors */
const char *avdd_second_sensor;
};
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 supply_name_upper[GPIO_SUPPLY_NAME_LENGTH];
char regulator_name[GPIO_REGULATOR_NAME_LENGTH];
struct gpio_desc *ena_gpio;
struct regulator_dev *rdev;
struct regulator_desc rdesc;
};
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_gpio_regulator regulators[INT3472_MAX_REGULATORS];
struct int3472_clock {
struct clk *clk;
@@ -104,11 +128,16 @@ struct int3472_discrete_device {
struct gpio_desc *gpio;
} pled;
struct int3472_discrete_quirks quirks;
unsigned int ngpios; /* how many GPIOs have we seen */
unsigned int n_sensor_gpios; /* how many have we mapped to sensor */
unsigned int n_regulator_gpios; /* how many have we mapped to a regulator */
struct gpiod_lookup_table gpios;
};
extern const struct dmi_system_id skl_int3472_discrete_quirks[];
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);
@@ -116,13 +145,19 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev,
struct acpi_device **sensor_adev_ret,
const char **name_ret);
int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472);
void int3472_discrete_cleanup(struct int3472_discrete_device *int3472);
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);
struct gpio_desc *gpio,
unsigned int enable_time,
const char *supply_name,
const char *second_sensor);
void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472);
int skl_int3472_register_pled(struct int3472_discrete_device *int3472, struct gpio_desc *gpio);

View File

@@ -71,6 +71,8 @@ enum {
POWER_SUPPLY_HEALTH_COOL,
POWER_SUPPLY_HEALTH_HOT,
POWER_SUPPLY_HEALTH_NO_BATTERY,
POWER_SUPPLY_HEALTH_BLOWN_FUSE,
POWER_SUPPLY_HEALTH_CELL_IMBALANCE,
};
enum {
@@ -212,6 +214,7 @@ enum power_supply_usb_type {
enum power_supply_charge_behaviour {
POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO = 0,
POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE,
POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE,
POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE,
};

View File

@@ -1,39 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _SONYLAPTOP_H_
#define _SONYLAPTOP_H_
#include <linux/types.h>
#ifdef __KERNEL__
/* used only for communication between v4l and sony-laptop */
#define SONY_PIC_COMMAND_GETCAMERA 1 /* obsolete */
#define SONY_PIC_COMMAND_SETCAMERA 2
#define SONY_PIC_COMMAND_GETCAMERABRIGHTNESS 3 /* obsolete */
#define SONY_PIC_COMMAND_SETCAMERABRIGHTNESS 4
#define SONY_PIC_COMMAND_GETCAMERACONTRAST 5 /* obsolete */
#define SONY_PIC_COMMAND_SETCAMERACONTRAST 6
#define SONY_PIC_COMMAND_GETCAMERAHUE 7 /* obsolete */
#define SONY_PIC_COMMAND_SETCAMERAHUE 8
#define SONY_PIC_COMMAND_GETCAMERACOLOR 9 /* obsolete */
#define SONY_PIC_COMMAND_SETCAMERACOLOR 10
#define SONY_PIC_COMMAND_GETCAMERASHARPNESS 11 /* obsolete */
#define SONY_PIC_COMMAND_SETCAMERASHARPNESS 12
#define SONY_PIC_COMMAND_GETCAMERAPICTURE 13 /* obsolete */
#define SONY_PIC_COMMAND_SETCAMERAPICTURE 14
#define SONY_PIC_COMMAND_GETCAMERAAGC 15 /* obsolete */
#define SONY_PIC_COMMAND_SETCAMERAAGC 16
#define SONY_PIC_COMMAND_GETCAMERADIRECTION 17 /* obsolete */
#define SONY_PIC_COMMAND_GETCAMERAROMVERSION 18 /* obsolete */
#define SONY_PIC_COMMAND_GETCAMERAREVISION 19 /* obsolete */
#if IS_ENABLED(CONFIG_SONY_LAPTOP)
int sony_pic_camera_command(int command, u8 value);
#else
static inline int sony_pic_camera_command(int command, u8 value) { return 0; }
#endif
#endif /* __KERNEL__ */
#endif /* _SONYLAPTOP_H_ */

View File

@@ -375,6 +375,30 @@ struct isst_perf_level_data_info {
__u16 trl_freq_mhz[TRL_MAX_LEVELS][TRL_MAX_BUCKETS];
};
#define MAX_FABRIC_COUNT 8
/**
* struct isst_perf_level_fabric_info - Structure to get SST-PP fabric details
* @socket_id: Socket/package id
* @power_domain_id: Power Domain id
* @level: SST-PP level for which caller wants to get information
* @max_fabrics: Count of fabrics in resonse
* @p0_fabric_freq_mhz: Fabric (Uncore) maximum frequency
* @p1_fabric_freq_mhz: Fabric (Uncore) TDP frequency
* @pm_fabric_freq_mhz: Fabric (Uncore) minimum frequency
*
* Structure used to get information on frequencies for fabrics.
*/
struct isst_perf_level_fabric_info {
__u8 socket_id;
__u8 power_domain_id;
__u16 level;
__u16 max_fabrics;
__u16 p0_fabric_freq_mhz[MAX_FABRIC_COUNT];
__u16 p1_fabric_freq_mhz[MAX_FABRIC_COUNT];
__u16 pm_fabric_freq_mhz[MAX_FABRIC_COUNT];
};
/**
* struct isst_perf_level_cpu_mask - Structure to get SST-PP level CPU mask
* @socket_id: Socket/package id
@@ -471,5 +495,7 @@ struct isst_turbo_freq_info {
#define ISST_IF_GET_BASE_FREQ_INFO _IOR(ISST_IF_MAGIC, 14, struct isst_base_freq_info *)
#define ISST_IF_GET_BASE_FREQ_CPU_MASK _IOR(ISST_IF_MAGIC, 15, struct isst_perf_level_cpu_mask *)
#define ISST_IF_GET_TURBO_FREQ_INFO _IOR(ISST_IF_MAGIC, 16, struct isst_turbo_freq_info *)
#define ISST_IF_GET_PERF_LEVEL_FABRIC_INFO _IOR(ISST_IF_MAGIC, 17,\
struct isst_perf_level_fabric_info *)
#endif

View File

@@ -16,7 +16,7 @@ struct process_cmd_struct {
int arg;
};
static const char *version_str = "v1.22";
static const char *version_str = "v1.23";
static const int supported_api_ver = 3;
static struct isst_if_platform_info isst_platform_info;
@@ -26,6 +26,7 @@ static FILE *outf;
static int cpu_model;
static int cpu_stepping;
static int extended_family;
#define MAX_CPUS_IN_ONE_REQ 512
static short max_target_cpus;
@@ -143,6 +144,14 @@ int is_icx_platform(void)
return 0;
}
static int is_dmr_plus_platform(void)
{
if (extended_family == 0x04)
return 1;
return 0;
}
static int update_cpu_model(void)
{
unsigned int ebx, ecx, edx;
@@ -150,6 +159,7 @@ static int update_cpu_model(void)
__cpuid(1, fms, ebx, ecx, edx);
family = (fms >> 8) & 0xf;
extended_family = (fms >> 20) & 0x0f;
cpu_model = (fms >> 4) & 0xf;
if (family == 6 || family == 0xf)
cpu_model += ((fms >> 16) & 0xf) << 4;
@@ -1517,7 +1527,8 @@ display_result:
usleep(2000);
/* Adjusting uncore freq */
isst_adjust_uncore_freq(id, tdp_level, &ctdp_level);
if (!is_dmr_plus_platform())
isst_adjust_uncore_freq(id, tdp_level, &ctdp_level);
fprintf(stderr, "Option is set to online/offline\n");
ctdp_level.core_cpumask_size =

Some files were not shown because too many files have changed in this diff Show More