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:
@@ -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.
|
||||
|
||||
64
Documentation/ABI/testing/debugfs-alienware-wmi
Normal file
64
Documentation/ABI/testing/debugfs-alienware-wmi
Normal 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
14
Documentation/ABI/testing/sysfs-platform-alienware-wmi
Normal file
14
Documentation/ABI/testing/sysfs-platform-alienware-wmi
Normal 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
|
||||
25
Documentation/ABI/testing/sysfs-platform-oxp
Normal file
25
Documentation/ABI/testing/sysfs-platform-oxp
Normal 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.
|
||||
127
Documentation/admin-guide/laptops/alienware-wmi.rst
Normal file
127
Documentation/admin-guide/laptops/alienware-wmi.rst
Normal 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.
|
||||
@@ -7,6 +7,7 @@ Laptop Drivers
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
alienware-wmi
|
||||
asus-laptop
|
||||
disk-shock-protection
|
||||
laptop-mode
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
==========
|
||||
|
||||
@@ -189,7 +189,6 @@ Hardware Monitoring Kernel Drivers
|
||||
nzxt-kraken3
|
||||
nzxt-smart2
|
||||
occ
|
||||
oxp-sensors
|
||||
pc87360
|
||||
pc87427
|
||||
pcf8591
|
||||
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
=========================================
|
||||
|
||||
|
||||
30
MAINTAINERS
30
MAINTAINERS
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
@@ -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.
|
||||
|
||||
@@ -651,7 +651,7 @@ static int gaokun_ec_resume(struct device *dev)
|
||||
break;
|
||||
|
||||
msleep(100); /* EC need time to resume */
|
||||
};
|
||||
}
|
||||
|
||||
ec->suspended = false;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
613
drivers/platform/mellanox/mlxreg-dpu.c
Normal file
613
drivers/platform/mellanox/mlxreg-dpu.c
Normal 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, ®val);
|
||||
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");
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
311
drivers/platform/x86/amd/amd_isp4.c
Normal file
311
drivers/platform/x86/amd/amd_isp4.c
Normal 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");
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[] = {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 */
|
||||
|
||||
121
drivers/platform/x86/amd/hsmp/hwmon.c
Normal file
121
drivers/platform/x86/amd/hsmp/hwmon.c
Normal 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");
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
360
drivers/platform/x86/dasharo-acpi.c
Normal file
360
drivers/platform/x86/dasharo-acpi.c
Normal 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");
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ®ulator->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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>");
|
||||
|
||||
@@ -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");
|
||||
|
||||
21
drivers/platform/x86/intel/int3472/discrete_quirks.c
Normal file
21
drivers/platform/x86/intel/int3472/discrete_quirks.c
Normal 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,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
@@ -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
|
||||
},
|
||||
{}
|
||||
|
||||
204
drivers/platform/x86/intel/pmc/ssram_telemetry.c
Normal file
204
drivers/platform/x86/intel/pmc/ssram_telemetry.c
Normal 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");
|
||||
24
drivers/platform/x86/intel/pmc/ssram_telemetry.h
Normal file
24
drivers/platform/x86/intel/pmc/ssram_telemetry.h
Normal 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 */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
1054
drivers/platform/x86/oxpec.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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) {
|
||||
|
||||
291
drivers/platform/x86/portwell-ec.c
Normal file
291
drivers/platform/x86/portwell-ec.c
Normal 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");
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
8
drivers/platform/x86/tuxedo/Kconfig
Normal file
8
drivers/platform/x86/tuxedo/Kconfig
Normal 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"
|
||||
8
drivers/platform/x86/tuxedo/Makefile
Normal file
8
drivers/platform/x86/tuxedo/Makefile
Normal 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/
|
||||
17
drivers/platform/x86/tuxedo/nb04/Kconfig
Normal file
17
drivers/platform/x86/tuxedo/nb04/Kconfig
Normal 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.
|
||||
10
drivers/platform/x86/tuxedo/nb04/Makefile
Normal file
10
drivers/platform/x86/tuxedo/nb04/Makefile
Normal 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
|
||||
923
drivers/platform/x86/tuxedo/nb04/wmi_ab.c
Normal file
923
drivers/platform/x86/tuxedo/nb04/wmi_ab.c
Normal 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");
|
||||
91
drivers/platform/x86/tuxedo/nb04/wmi_util.c
Normal file
91
drivers/platform/x86/tuxedo/nb04/wmi_util.c
Normal 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);
|
||||
}
|
||||
109
drivers/platform/x86/tuxedo/nb04/wmi_util.h
Normal file
109
drivers/platform/x86/tuxedo/nb04/wmi_util.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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_ */
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user