platform-drivers-x86 for v6.5-1

Highlights:
  -  AMD PMC and PMF drivers:
     - Various bugfixes
     - Improved debugging support
  -  Intel PMC:
     - Refactor to support hw with multiple PMCs
     - Various other improvements / new hw support
  -  Intel Speed Select Technology (ISST):
     - TPMI Uncore Frequency + Cluster Level Power Controls
     - Various bugfixes
     - tools/intel-speed-select: Misc. improvements
  -  Dell-DDV: Add documentation
  -  INT3472 ACPI camera sensor glue code:
     - Evaluate device's _DSM method to control imaging clock
     - Drop the need to have a table with per sensor-model info
  -  Lenovo Yogabook: Refactor / rework to also support Android models
  -  Think-LMI: Multiple improvements and fixes
  -  WMI: Add proper API documentation for the WMI bus
  -  x86-android-tablets: Misc. new hw support
  -  Miscellaneous other cleanups / fixes
 
 The following is an automated git shortlog grouped by driver:
 
 ISST:
  -  Fix usage counter
  -  Reset default callback on unregister
 
 Merge remote-tracking branch 'intel-speed-select/intel-sst' into review-hans:
  - Merge remote-tracking branch 'intel-speed-select/intel-sst' into review-hans
 
 apple-gmux:
  -  don't use be32_to_cpu and cpu_to_be32
 
 asus-tf103c-dock:
  -  Switch back to use struct i2c_driver's .probe()
 
 dell-ddv:
  -  Update ABI documentation
  -  Add documentation
 
 dell-sysman:
  -  Improve instance detection
 
 gigabyte:
  -  constify pointers to hwmon_channel_info
 
 gigabyte-wmi:
  -  remove allowlist
 
 hp:
  -  constify pointers to hwmon_channel_info
 
 hp-wmi:
  -  Add thermal profile for Victus 16-d1xxx
  -  Add HP Envy special key support
  -  Add HP WMI camera switch
 
 int3472:
  -  discrete: Log a warning if the pin-numbers don't match
  -  discrete: Use FIELD_GET() on the GPIO _DSM return value
  -  discrete: Add alternative "AVDD" regulator supply name
  -  discrete: Add support for 1 GPIO regulator shared between 2 sensors
  -  discrete: Remove sensor_config-s
  -  discrete: Drop GPIO remapping support
  -  Switch back to use struct i2c_driver's .probe()
  -  Evaluate device's _DSM method to control imaging clock
 
 intel/pmc:
  -  Add Meteor Lake IOE-M PMC related maps
  -  Add Meteor Lake IOE-P PMC related maps
  -  Use SSRAM to discover pwrm base address of primary PMC
  -  Discover PMC devices
  -  Enable debugfs multiple PMC support
  -  Add support to handle multiple PMCs
  -  Combine core_init() and core_configure()
  -  Update maps for Meteor Lake P/M platforms
  -  Remove Meteor Lake S platform support
 
 lenovo-yogabook:
  -  add I2C dependency
  -  Rename lenovo-yogabook-wmi to lenovo-yogabook
  -  Add keyboard backlight control to platform driver
  -  Add platform driver support
  -  Add YB_KBD_BL_MAX define
  -  Group WMI specific code together
  -  Drop _wmi_ from remaining generic symbols
  -  Add a yogabook_toggle_digitizer_mode() helper function
  -  Abstract kbd backlight setting
  -  Stop checking adev->power.state
  -  Split probe() into generic and WMI specific parts
  -  Use PMIC LED driver for pen icon LED control
  -  Add dev local variable to probe()
  -  Store dev instead of wdev in drvdata struct
  -  Switch to DEFINE_SIMPLE_DEV_PM_OPS()
  -  Simplify gpio lookup table cleanup
  -  Set default keyboard backligh brightness on probe()
  -  Reprobe devices on remove()
  -  Fix work race on remove()
 
 mlxbf-bootctl:
  -  Add sysfs file for BlueField boot log
 
 platform/surface:
  -  surface3_power: Switch back to use struct i2c_driver's .probe()
 
 platform/x86/amd:
  -  pmc: Update metrics table info for Pink Sardine
  -  pmc: Add helper function to check the cpu id
  -  pmc: Get STB DRAM size from PMFW
  -  pmc: Pass true/false to bool argument
 
 platform/x86/amd/pmf:
  -  Fix compiler warnings in static slider
  -  Add PMF debug facilities
  -  Add PMF acpi debug support
 
 platform/x86/dell/dell-rbtn:
  -  Fix resources leaking on error path
 
 platform/x86/intel:
  -  tpmi: Remove hardcoded unit and offset
 
 platform/x86/intel-uncore-freq:
  -  tpmi: Provide cluster level control
  -  Support for cluster level controls
  -  Uncore frequency control via TPMI
 
 platform/x86/intel/pmc:
  -  Add resume callback
 
 platform/x86/intel/pmc/mtl:
  -  Put devices in D3 during resume
 
 system76:
  -  constify pointers to hwmon_channel_info
 
 think-lmi:
  -  Don't display unnecessary authentication settings
  -  Correct NVME index default
  -  Correct NVME password handling
  -  Update password fields to use BIT
  -  Update password attribute comments
  -  Correct System password interface
  -  Enable opcode support on BIOS settings
  -  mutex protection around multiple WMI calls
 
 thinkpad_acpi:
  -  Fix lkp-tests warnings for platform profiles
 
 tools/power/x86/intel-speed-select:
  -  v1.16 release
  -  Fix json formatting issue
  -  Adjust scope of core-power config
 
 toshiba:
  -  constify pointers to hwmon_channel_info
 
 wmi:
  -  Allow retrieving the number of WMI object instances
  -  Add device specific documentation
  -  Add documentation
  -  Mark GUID-based WMI interface as deprecated
  -  Add kernel doc comments
 
 x86-android-tablets:
  -  Add support for extra buttons on Cyberbook T116
  -  Add support for more then 1 gpio_key
  -  Add Lenovo Yoga Book lid switch
  -  Fix Bluetooth on Lenovo Yoga Book
  -  Add Nextbook Ares 8A data
  -  Remove unnecessary invalid_aei_gpiochip settings
  -  Add ALS sensor support for Yoga Tablet 2 1050/830 series
 -----BEGIN PGP SIGNATURE-----
 
 iQFIBAABCAAyFiEEuvA7XScYQRpenhd+kuxHeUQDJ9wFAmSdSpoUHGhkZWdvZWRl
 QHJlZGhhdC5jb20ACgkQkuxHeUQDJ9xd8gf/eJ9BFzUfA8otMPcjEHZWOr6TkL3u
 P0C3ITatLhLk4Y5BkQ3iQlelJlcEflYF8VDT9Xfrm086aJLI57Dv5NWvyzsVBb20
 CD+5ojdASAsgeoNYO4dIEB7G8gIibvGeH7xGb+oZBl8xaVLWbBHfl4pXPSi/iGzn
 33aB5yaL0v0Uveb2kQrMmQ1P4APKNCvYuoi6u6zUmansLFWpQ3NbdME/bCyqW+qx
 fTNfjjuNcRBlbK4Pvf5x9tT8xzPFz0YATOe/TTyL4K490aShEnXHBio2F9FAuzib
 7FeStlhUGzQuF2LabgLiBw16j/Kr5XR37eukSwY8jVvZCQCqvTRBfbgM2g==
 =H1S7
 -----END PGP SIGNATURE-----

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

Pull x86 platform driver updates from Hans de Goede:
 "AMD PMC and PMF drivers:
   - Various bugfixes
   - Improved debugging support

  Intel PMC:
   - Refactor to support hw with multiple PMCs
   - Various other improvements / new hw support

  Intel Speed Select Technology (ISST):
   - TPMI Uncore Frequency + Cluster Level Power Controls
   - Various bugfixes
   - tools/intel-speed-select: Misc improvements

  Dell-DDV: Add documentation

  INT3472 ACPI camera sensor glue code:
   - Evaluate device's _DSM method to control imaging clock
   - Drop the need to have a table with per sensor-model info

  Lenovo Yogabook:
   - Refactor / rework to also support Android models

  Think-LMI:
   - Multiple improvements and fixes

  WMI:
   - Add proper API documentation for the WMI bus

  x86-android-tablets:
   - Misc new hw support

  Miscellaneous other cleanups / fixes"

* tag 'platform-drivers-x86-v6.5-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (91 commits)
  platform/x86:intel/pmc: Add Meteor Lake IOE-M PMC related maps
  platform/x86:intel/pmc: Add Meteor Lake IOE-P PMC related maps
  platform/x86:intel/pmc: Use SSRAM to discover pwrm base address of primary PMC
  platform/x86:intel/pmc: Discover PMC devices
  platform/x86:intel/pmc: Enable debugfs multiple PMC support
  platform/x86:intel/pmc: Add support to handle multiple PMCs
  platform/x86:intel/pmc: Combine core_init() and core_configure()
  platform/x86:intel/pmc: Update maps for Meteor Lake P/M platforms
  platform/x86/intel: tpmi: Remove hardcoded unit and offset
  platform/x86: int3472: discrete: Log a warning if the pin-numbers don't match
  platform/x86: int3472: discrete: Use FIELD_GET() on the GPIO _DSM return value
  platform/x86: int3472: discrete: Add alternative "AVDD" regulator supply name
  platform/x86: int3472: discrete: Add support for 1 GPIO regulator shared between 2 sensors
  platform/x86: int3472: discrete: Remove sensor_config-s
  platform/x86: int3472: discrete: Drop GPIO remapping support
  platform/x86: apple-gmux: don't use be32_to_cpu and cpu_to_be32
  platform/x86/dell/dell-rbtn: Fix resources leaking on error path
  platform/x86: ISST: Fix usage counter
  platform/x86: ISST: Reset default callback on unregister
  platform/x86: int3472: Switch back to use struct i2c_driver's .probe()
  ...
This commit is contained in:
Linus Torvalds 2023-06-30 14:50:00 -07:00
commit 9c3255a8f3
70 changed files with 4517 additions and 955 deletions

View File

@ -0,0 +1,7 @@
What: /sys/bus/wmi/devices/05901221-D566-11D1-B2F0-00A0C9062910[-X]/bmof
Date: Jun 2017
KernelVersion: 4.13
Description:
Binary MOF metadata used to decribe the details of available ACPI WMI interfaces.
See Documentation/wmi/devices/wmi-bmof.rst for details.

View File

@ -3,19 +3,32 @@ Date: September 2022
KernelVersion: 6.1
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
This file contains the contents of the fan sensor information buffer,
which contains fan sensor entries and a terminating character (0xFF).
This file contains the contents of the fan sensor information
buffer, which contains fan sensor entries and a terminating
character (0xFF).
Each fan sensor entry consists of three bytes with an unknown meaning,
interested people may use this file for reverse-engineering.
Each fan sensor entry contains:
- fan type (single byte)
- fan speed in RPM (two bytes, little endian)
See Documentation/wmi/devices/dell-wmi-ddv.rst for details.
What: /sys/kernel/debug/dell-wmi-ddv-<wmi_device_name>/thermal_sensor_information
Date: September 2022
KernelVersion: 6.1
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
This file contains the contents of the thermal sensor information buffer,
which contains thermal sensor entries and a terminating character (0xFF).
This file contains the contents of the thermal sensor information
buffer, which contains thermal sensor entries and a terminating
character (0xFF).
Each thermal sensor entry consists of five bytes with an unknown meaning,
interested people may use this file for reverse-engineering.
Each thermal sensor entry contains:
- thermal type (single byte)
- current temperature (single byte)
- min. temperature (single byte)
- max. temperature (single byte)
- unknown field (single byte)
See Documentation/wmi/devices/dell-wmi-ddv.rst for details.

View File

@ -243,8 +243,8 @@ Description:
index:
Used with HDD and NVME authentication to set the drive index
that is being referenced (e.g hdd0, hdd1 etc)
This attribute defaults to device 0.
that is being referenced (e.g hdd1, hdd2 etc)
This attribute defaults to device 1.
certificate, signature, save_signature:
These attributes are used for certificate based authentication. This is

View File

@ -3,5 +3,7 @@ Date: September 2022
KernelVersion: 6.1
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
Reports the Dell ePPID (electronic Dell Piece Part Identification)
Reports the Dell ePPID (electronic Piece Part Identification)
of the ACPI battery.
See Documentation/wmi/devices/dell-wmi-ddv.rst for details.

View File

@ -75,3 +75,12 @@ KernelVersion: 6.4
Contact: "Liming Sun <limings@nvidia.com>"
Description:
The file used to access the BlueField boot fifo.
What: /sys/bus/platform/devices/MLNXBF04:00/rsh_log
Date: May 2023
KernelVersion: 6.4
Contact: "Liming Sun <limings@nvidia.com>"
Description:
The file used to write BlueField boot log with the format
"[INFO|WARN|ERR|ASSERT ]<msg>". Log level 'INFO' is used by
default if not specified.

View File

@ -5,7 +5,7 @@
Intel Uncore Frequency Scaling
==============================
:Copyright: |copy| 2022 Intel Corporation
:Copyright: |copy| 2022-2023 Intel Corporation
:Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
@ -58,3 +58,58 @@ Each package_*_die_* contains the following attributes:
``current_freq_khz``
This attribute is used to get the current uncore frequency.
SoCs with TPMI (Topology Aware Register and PM Capsule Interface)
-----------------------------------------------------------------
An SoC can contain multiple power domains with individual or collection
of mesh partitions. This partition is called fabric cluster.
Certain type of meshes will need to run at the same frequency, they will
be placed in the same fabric cluster. Benefit of fabric cluster is that it
offers a scalable mechanism to deal with partitioned fabrics in a SoC.
The current sysfs interface supports controls at package and die level.
This interface is not enough to support more granular control at
fabric cluster level.
SoCs with the support of TPMI (Topology Aware Register and PM Capsule
Interface), can have multiple power domains. Each power domain can
contain one or more fabric clusters.
To represent controls at fabric cluster level in addition to the
controls at package and die level (like systems without TPMI
support), sysfs is enhanced. This granular interface is presented in the
sysfs with directories names prefixed with "uncore". For example:
uncore00, uncore01 etc.
The scope of control is specified by attributes "package_id", "domain_id"
and "fabric_cluster_id" in the directory.
Attributes in each directory:
``domain_id``
This attribute is used to get the power domain id of this instance.
``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.
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"
is updated at "package_*_die_*" level. This model will be still supported
with the following approach:
When user uses controls at "package_*_die_*" level, then every fabric
cluster is affected in that package and die. For example: user changes
"max_freq_khz" in the package_00_die_00, then "max_freq_khz" for uncore*
directory with the same package id will be updated. In this case user can
still update "max_freq_khz" at each uncore* level, which is more restrictive.
Similarly, user can update "min_freq_khz" at "package_*_die_*" level
to apply at each uncore* level.
Support for "current_freq_khz" is available only at each fabric cluster
level (i.e., in uncore* directory).

View File

@ -113,6 +113,7 @@ available subsections can be seen below.
xillybus
zorro
hte/index
wmi
.. only:: subproject and html

View File

@ -0,0 +1,21 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
==============
WMI Driver API
==============
The WMI driver core supports a more modern bus-based interface for interacting
with WMI devices, and an older GUID-based interface. The latter interface is
considered to be deprecated, so new WMI drivers should generally avoid it since
it has some issues with multiple WMI devices and events sharing the same GUIDs
and/or notification IDs. The modern bus-based interface instead maps each
WMI device to a :c:type:`struct wmi_device <wmi_device>`, so it supports
WMI devices sharing GUIDs and/or notification IDs. Drivers can then register
a :c:type:`struct wmi_driver <wmi_driver>`, which will be bound to compatible
WMI devices by the driver core.
.. kernel-doc:: include/linux/wmi.h
:internal:
.. kernel-doc:: drivers/platform/x86/wmi.c
:export:

View File

@ -71,3 +71,4 @@ Storage interfaces
scheduler/index
mhi/index
peci/index
wmi/index

View File

@ -0,0 +1,96 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
==================
ACPI WMI interface
==================
The ACPI WMI interface is a proprietary extension of the ACPI specification made
by Microsoft to allow hardware vendors to embed WMI (Windows Management Instrumentation)
objects inside their ACPI firmware. Typical functions implemented over ACPI WMI
are hotkey events on modern notebooks and configuration of BIOS options.
PNP0C14 ACPI device
-------------------
Discovery of WMI objects is handled by defining ACPI devices with a PNP ID
of ``PNP0C14``. These devices will contain a set of ACPI buffers and methods
used for mapping and execution of WMI methods and/or queries. If there exist
multiple of such devices, then each device is required to have a
unique ACPI UID.
_WDG buffer
-----------
The ``_WDG`` buffer is used to discover WMI objects and is required to be
static. Its internal structure consists of data blocks with a size of 20 bytes,
containing the following data:
======= =============== =====================================================
Offset Size (in bytes) Content
======= =============== =====================================================
0x00 16 128 bit Variant 2 object GUID.
0x10 2 2 character method ID or single byte notification ID.
0x12 1 Object instance count.
0x13 1 Object flags.
======= =============== =====================================================
The WMI object flags control whether the method or notification ID is used:
- 0x1: Data block usage is expensive and must be explicitly enabled/disabled.
- 0x2: Data block contains WMI methods.
- 0x4: Data block contains ASCIZ string.
- 0x8: Data block describes a WMI event, use notification ID instead
of method ID.
Each WMI object GUID can appear multiple times inside a system.
The method/notification ID is used to construct the ACPI method names used for
interacting with the WMI object.
WQxx ACPI methods
-----------------
If a data block does not contain WMI methods, then its content can be retrieved
by this required ACPI method. The last two characters of the ACPI method name
are the method ID of the data block to query. Their single parameter is an
integer describing the instance which should be queried. This parameter can be
omitted if the data block contains only a single instance.
WSxx ACPI methods
-----------------
Similar to the ``WQxx`` ACPI methods, except that it is optional and takes an
additional buffer as its second argument. The instance argument also cannot
be omitted.
WMxx ACPI methods
-----------------
Used for executing WMI methods associated with a data block. The last two
characters of the ACPI method name are the method ID of the data block
containing the WMI methods. Their first parameter is a integer describing the
instance which methods should be executed. The second parameter is an integer
describing the WMI method ID to execute, and the third parameter is a buffer
containing the WMI method parameters. If the data block is marked as containing
an ASCIZ string, then this buffer should contain an ASCIZ string. The ACPI
method will return the result of the executed WMI method.
WExx ACPI methods
-----------------
Used for optionally enabling/disabling WMI events, the last two characters of
the ACPI method are the notification ID of the data block describing the WMI
event as hexadecimal value. Their first parameter is an integer with a value
of 0 if the WMI event should be disabled, other values will enable
the WMI event.
WCxx ACPI methods
-----------------
Similar to the ``WExx`` ACPI methods, except that it controls data collection
instead of events and thus the last two characters of the ACPI method name are
the method ID of the data block to enable/disable.
_WED ACPI method
----------------
Used to retrieve additional WMI event data, its single parameter is a integer
holding the notification ID of the event.

View File

@ -0,0 +1,296 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
============================================
Dell DDV WMI interface driver (dell-wmi-ddv)
============================================
Introduction
============
Many Dell notebooks made after ~2020 support a WMI-based interface for
retrieving various system data like battery temperature, ePPID, diagostic data
and fan/thermal sensor data.
This interface is likely used by the `Dell Data Vault` software on Windows,
so it was called `DDV`. Currently the ``dell-wmi-ddv`` driver supports
version 2 and 3 of the interface, with support for new interface versions
easily added.
.. warning:: The interface is regarded as internal by Dell, so no vendor
documentation is available. All knowledge was thus obtained by
trial-and-error, please keep that in mind.
Dell ePPID (electronic Piece Part Identification)
=================================================
The Dell ePPID is used to uniquely identify components in Dell machines,
including batteries. It has a form similar to `CC-PPPPPP-MMMMM-YMD-SSSS-FFF`
and contains the following information:
* Country code of origin (CC).
* Part number with the first character being a filling number (PPPPPP).
* Manufacture Identification (MMMMM).
* Manufacturing Year/Month/Date (YMD) in base 36, with Y being the last digit
of the year.
* Manufacture Sequence Number (SSSS).
* Optional Firmware Version/Revision (FFF).
The `eppidtool <https://pypi.org/project/eppidtool>`_ python utility can be used
to decode and display this information.
All information regarding the Dell ePPID was gathered using Dell support
documentation and `this website <https://telcontar.net/KBK/Dell/date_codes>`_.
WMI interface description
=========================
The WMI interface description can be decoded from the embedded binary MOF (bmof)
data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
::
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("WMI Function"), guid("{8A42EA14-4F2A-FD45-6422-0087F7A7E608}")]
class DDVWmiMethodFunction {
[key, read] string InstanceName;
[read] boolean Active;
[WmiMethodId(1), Implemented, read, write, Description("Return Battery Design Capacity.")] void BatteryDesignCapacity([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(2), Implemented, read, write, Description("Return Battery Full Charge Capacity.")] void BatteryFullChargeCapacity([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(3), Implemented, read, write, Description("Return Battery Manufacture Name.")] void BatteryManufactureName([in] uint32 arg2, [out] string argr);
[WmiMethodId(4), Implemented, read, write, Description("Return Battery Manufacture Date.")] void BatteryManufactureDate([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(5), Implemented, read, write, Description("Return Battery Serial Number.")] void BatterySerialNumber([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(6), Implemented, read, write, Description("Return Battery Chemistry Value.")] void BatteryChemistryValue([in] uint32 arg2, [out] string argr);
[WmiMethodId(7), Implemented, read, write, Description("Return Battery Temperature.")] void BatteryTemperature([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(8), Implemented, read, write, Description("Return Battery Current.")] void BatteryCurrent([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(9), Implemented, read, write, Description("Return Battery Voltage.")] void BatteryVoltage([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(10), Implemented, read, write, Description("Return Battery Manufacture Access(MA code).")] void BatteryManufactureAceess([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(11), Implemented, read, write, Description("Return Battery Relative State-Of-Charge.")] void BatteryRelativeStateOfCharge([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(12), Implemented, read, write, Description("Return Battery Cycle Count")] void BatteryCycleCount([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(13), Implemented, read, write, Description("Return Battery ePPID")] void BatteryePPID([in] uint32 arg2, [out] string argr);
[WmiMethodId(14), Implemented, read, write, Description("Return Battery Raw Analytics Start")] void BatteryeRawAnalyticsStart([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(15), Implemented, read, write, Description("Return Battery Raw Analytics")] void BatteryeRawAnalytics([in] uint32 arg2, [out] uint32 RawSize, [out, WmiSizeIs("RawSize") : ToInstance] uint8 RawData[]);
[WmiMethodId(16), Implemented, read, write, Description("Return Battery Design Voltage.")] void BatteryDesignVoltage([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(17), Implemented, read, write, Description("Return Battery Raw Analytics A Block")] void BatteryeRawAnalyticsABlock([in] uint32 arg2, [out] uint32 RawSize, [out, WmiSizeIs("RawSize") : ToInstance] uint8 RawData[]);
[WmiMethodId(18), Implemented, read, write, Description("Return Version.")] void ReturnVersion([in] uint32 arg2, [out] uint32 argr);
[WmiMethodId(32), Implemented, read, write, Description("Return Fan Sensor Information")] void FanSensorInformation([in] uint32 arg2, [out] uint32 RawSize, [out, WmiSizeIs("RawSize") : ToInstance] uint8 RawData[]);
[WmiMethodId(34), Implemented, read, write, Description("Return Thermal Sensor Information")] void ThermalSensorInformation([in] uint32 arg2, [out] uint32 RawSize, [out, WmiSizeIs("RawSize") : ToInstance] uint8 RawData[]);
};
Each WMI method takes an ACPI buffer containing a 32-bit index as input argument,
with the first 8 bit being used to specify the battery when using battery-related
WMI methods. Other WMI methods may ignore this argument or interpret it
differently. The WMI method output format varies:
* if the function has only a single output, then an ACPI object
of the corresponding type is returned
* if the function has multiple outputs, when an ACPI package
containing the outputs in the same order is returned
The format of the output should be thoroughly checked, since many methods can
return malformed data in case of an error.
The data format of many battery-related methods seems to be based on the
`Smart Battery Data Specification`, so unknown battery-related methods are
likely to follow this standard in some way.
WMI method GetBatteryDesignCapacity()
-------------------------------------
Returns the design capacity of the battery in mAh as an u16.
WMI method BatteryFullCharge()
------------------------------
Returns the full charge capacity of the battery in mAh as an u16.
WMI method BatteryManufactureName()
-----------------------------------
Returns the manufacture name of the battery as an ASCII string.
WMI method BatteryManufactureDate()
-----------------------------------
Returns the manufacture date of the battery as an u16.
The date is encoded in the following manner:
- bits 0 to 4 contain the manufacture day.
- 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()
--------------------------------
Returns the serial number of the battery as an u16.
WMI method BatteryChemistryValue()
----------------------------------
Returns the chemistry of the battery as an ASCII string.
Known values are:
- "Li-I" for Li-Ion
WMI method BatteryTemperature()
-------------------------------
Returns the temperature of the battery in tenth degree kelvin as an u16.
WMI method BatteryCurrent()
---------------------------
Returns the current flow of the battery in mA as an s16.
Negative values indicate discharging.
WMI method BatteryVoltage()
---------------------------
Returns the voltage flow of the battery in mV as an u16.
WMI method BatteryManufactureAccess()
-------------------------------------
Returns a manufacture-defined value as an u16.
WMI method BatteryRelativeStateOfCharge()
-----------------------------------------
Returns the capacity of the battery in percent as an u16.
WMI method BatteryCycleCount()
------------------------------
Returns the cycle count of the battery as an u16.
WMI method BatteryePPID()
-------------------------
Returns the ePPID of the battery as an ASCII string.
WMI method BatteryeRawAnalyticsStart()
--------------------------------------
Performs an analysis of the battery and returns a status code:
- ``0x0``: Success
- ``0x1``: Interface not supported
- ``0xfffffffe``: Error/Timeout
.. note::
The meaning of this method is still largely unknown.
WMI method BatteryeRawAnalytics()
---------------------------------
Returns a buffer usually containg 12 blocks of analytics data.
Those blocks contain:
- block number starting with 0 (u8)
- 31 bytes of unknown data
.. note::
The meaning of this method is still largely unknown.
WMI method BatteryDesignVoltage()
---------------------------------
Returns the design voltage of the battery in mV as an u16.
WMI method BatteryeRawAnalyticsABlock()
---------------------------------------
Returns a single block of analytics data, with the second byte
of the index being used for selecting the block number.
*Supported since WMI interface version 3!*
.. note::
The meaning of this method is still largely unknown.
WMI method ReturnVersion()
--------------------------
Returns the WMI interface version as an u32.
WMI method FanSensorInformation()
---------------------------------
Returns a buffer containg fan sensor entries, terminated
with a single ``0xff``.
Those entries contain:
- fan type (u8)
- fan speed in RPM (little endian u16)
WMI method ThermalSensorInformation()
-------------------------------------
Returns a buffer containing thermal sensor entries, terminated
with a single ``0xff``.
Those entries contain:
- thermal type (u8)
- current temperature (s8)
- min. temperature (s8)
- max. temperature (s8)
- unknown field (u8)
.. note::
TODO: Find out what the meaning of the last byte is.
ACPI battery matching algorithm
===============================
The algorithm used to match ACPI batteries to indices is based on information
which was found inside the logging messages of the OEM software.
Basically for each new ACPI battery, the serial numbers of the batteries behind
indices 1 till 3 are compared with the serial number of the ACPI battery.
Since the serial number of the ACPI battery can either be encoded as a normal
integer or as a hexadecimal value, both cases need to be checked. The first
index with a matching serial number is then selected.
A serial number of 0 indicates that the corresponding index is not associated
with an actual battery, or that the associated battery is not present.
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
=========================================
1. Find a supported Dell notebook, usually made after ~2020.
2. Dump the ACPI tables and search for the WMI device (usually called "ADDV").
3. Decode the corresponding bmof data and look at the ASL code.
4. Try to deduce the meaning of a certain WMI method by comparing the control
flow with other ACPI methods (_BIX or _BIF for battery related methods
for example).
5. Use the built-in UEFI diagostics to view sensor types/values for fan/thermal
related methods (sometimes overwriting static ACPI data fields can be used
to test different sensor type values, since on some machines this data is
not reinitialized upon a warm reset).
Alternatively:
1. Load the ``dell-wmi-ddv`` driver, use the ``force`` module param
if necessary.
2. Use the debugfs interface to access the raw fan/thermal sensor buffer data.
3. Compare the data with the built-in UEFI diagnostics.
In case the DDV WMI interface version available on your Dell notebook is not
supported or you are seeing unknown fan/thermal sensors, please submit a
bugreport on `bugzilla <https://bugzilla.kernel.org>`_ so they can be added
to the ``dell-wmi-ddv`` driver.
See Documentation/admin-guide/reporting-issues.rst for further information.

View File

@ -0,0 +1,22 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
=============================
Driver-specific Documentation
=============================
This section provides information about various devices supported by
the Linux kernel, their protocols and driver details.
.. toctree::
:maxdepth: 1
:numbered:
:glob:
*
.. only:: subproject and html
Indices
=======
* :ref:`genindex`

View File

@ -0,0 +1,25 @@
.. SPDX-License-Identifier: GPL-2.0-only
==============================
WMI embedded Binary MOF driver
==============================
Introduction
============
Many machines embed WMI Binary MOF (Managed Object Format) metadata used to
describe the details of their ACPI WMI interfaces. The data can be decoded
with tools like `bmfdec <https://github.com/pali/bmfdec>`_ to obtain a
human readable WMI interface description, which is useful for developing
new WMI drivers.
The Binary MOF data can be retrieved from the ``bmof`` sysfs attribute of the
associated WMI device. Please note that multiple WMI devices containing Binary
MOF data can exist on a given system.
WMI interface
=============
The Binary MOF WMI device is identified by the WMI GUID ``05901221-D566-11D1-B2F0-00A0C9062910``.
The Binary MOF can be obtained by doing a WMI data block query. The result is
then returned as an ACPI buffer with a variable size.

View File

@ -0,0 +1,19 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
=============
WMI Subsystem
=============
.. toctree::
:maxdepth: 1
acpi-interface
devices/index
.. only:: subproject and html
Indices
=======
* :ref:`genindex`

View File

@ -456,6 +456,8 @@ F: include/linux/acpi_viot.h
ACPI WMI DRIVER
L: platform-driver-x86@vger.kernel.org
S: Orphan
F: Documentation/driver-api/wmi.rst
F: Documentation/wmi/
F: drivers/platform/x86/wmi.c
F: include/uapi/linux/wmi.h
@ -5842,6 +5844,7 @@ M: Armin Wolf <W_Armin@gmx.de>
S: Maintained
F: Documentation/ABI/testing/debugfs-dell-wmi-ddv
F: Documentation/ABI/testing/sysfs-platform-dell-wmi-ddv
F: Documentation/wmi/devices/dell-wmi-ddv.rst
F: drivers/platform/x86/dell/dell-wmi-ddv.c
DELL WMI DESCRIPTOR DRIVER
@ -22910,6 +22913,13 @@ L: linux-wireless@vger.kernel.org
S: Odd fixes
F: drivers/net/wireless/legacy/wl3501*
WMI BINARY MOF DRIVER
L: platform-drivers-x86@vger.kernel.org
S: Orphan
F: Documentation/ABI/stable/sysfs-platform-wmi-bmof
F: Documentation/wmi/devices/wmi-bmof.rst
F: drivers/platform/x86/wmi-bmof.c
WOLFSON MICROELECTRONICS DRIVERS
L: patches@opensource.cirrus.com
S: Supported

View File

@ -11,6 +11,7 @@
#include <linux/acpi.h>
#include <linux/arm-smccc.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@ -45,10 +46,39 @@ static const char * const mlxbf_bootctl_lifecycle_states[] = {
[3] = "RMA",
};
/* Log header format. */
#define MLXBF_RSH_LOG_TYPE_MASK GENMASK_ULL(59, 56)
#define MLXBF_RSH_LOG_LEN_MASK GENMASK_ULL(54, 48)
#define MLXBF_RSH_LOG_LEVEL_MASK GENMASK_ULL(7, 0)
/* Log module ID and type (only MSG type in Linux driver for now). */
#define MLXBF_RSH_LOG_TYPE_MSG 0x04ULL
/* Log ctl/data register offset. */
#define MLXBF_RSH_SCRATCH_BUF_CTL_OFF 0
#define MLXBF_RSH_SCRATCH_BUF_DATA_OFF 0x10
/* Log message levels. */
enum {
MLXBF_RSH_LOG_INFO,
MLXBF_RSH_LOG_WARN,
MLXBF_RSH_LOG_ERR,
MLXBF_RSH_LOG_ASSERT
};
/* Mapped pointer for RSH_BOOT_FIFO_DATA and RSH_BOOT_FIFO_COUNT register. */
static void __iomem *mlxbf_rsh_boot_data;
static void __iomem *mlxbf_rsh_boot_cnt;
/* Mapped pointer for rsh log semaphore/ctrl/data register. */
static void __iomem *mlxbf_rsh_semaphore;
static void __iomem *mlxbf_rsh_scratch_buf_ctl;
static void __iomem *mlxbf_rsh_scratch_buf_data;
/* Rsh log levels. */
static const char * const mlxbf_rsh_log_level[] = {
"INFO", "WARN", "ERR", "ASSERT"};
/* ARM SMC call which is atomic and no need for lock. */
static int mlxbf_bootctl_smc(unsigned int smc_op, int smc_arg)
{
@ -266,12 +296,108 @@ static ssize_t fw_reset_store(struct device *dev,
return count;
}
/* Size(8-byte words) of the log buffer. */
#define RSH_SCRATCH_BUF_CTL_IDX_MASK 0x7f
/* 100ms timeout */
#define RSH_SCRATCH_BUF_POLL_TIMEOUT 100000
static int mlxbf_rsh_log_sem_lock(void)
{
unsigned long reg;
return readq_poll_timeout(mlxbf_rsh_semaphore, reg, !reg, 0,
RSH_SCRATCH_BUF_POLL_TIMEOUT);
}
static void mlxbf_rsh_log_sem_unlock(void)
{
writeq(0, mlxbf_rsh_semaphore);
}
static ssize_t rsh_log_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int rc, idx, num, len, level = MLXBF_RSH_LOG_INFO;
size_t size = count;
u64 data;
if (!size)
return -EINVAL;
if (!mlxbf_rsh_semaphore || !mlxbf_rsh_scratch_buf_ctl)
return -EOPNOTSUPP;
/* Ignore line break at the end. */
if (buf[size - 1] == '\n')
size--;
/* Check the message prefix. */
for (idx = 0; idx < ARRAY_SIZE(mlxbf_rsh_log_level); idx++) {
len = strlen(mlxbf_rsh_log_level[idx]);
if (len + 1 < size &&
!strncmp(buf, mlxbf_rsh_log_level[idx], len)) {
buf += len;
size -= len;
level = idx;
break;
}
}
/* Ignore leading spaces. */
while (size > 0 && buf[0] == ' ') {
size--;
buf++;
}
/* Take the semaphore. */
rc = mlxbf_rsh_log_sem_lock();
if (rc)
return rc;
/* Calculate how many words are available. */
idx = readq(mlxbf_rsh_scratch_buf_ctl);
num = min((int)DIV_ROUND_UP(size, sizeof(u64)),
RSH_SCRATCH_BUF_CTL_IDX_MASK - idx - 1);
if (num <= 0)
goto done;
/* Write Header. */
data = FIELD_PREP(MLXBF_RSH_LOG_TYPE_MASK, MLXBF_RSH_LOG_TYPE_MSG);
data |= FIELD_PREP(MLXBF_RSH_LOG_LEN_MASK, num);
data |= FIELD_PREP(MLXBF_RSH_LOG_LEVEL_MASK, level);
writeq(data, mlxbf_rsh_scratch_buf_data);
/* Write message. */
for (idx = 0; idx < num && size > 0; idx++) {
if (size < sizeof(u64)) {
data = 0;
memcpy(&data, buf, size);
size = 0;
} else {
memcpy(&data, buf, sizeof(u64));
size -= sizeof(u64);
buf += sizeof(u64);
}
writeq(data, mlxbf_rsh_scratch_buf_data);
}
done:
/* Release the semaphore. */
mlxbf_rsh_log_sem_unlock();
/* Ignore the rest if no more space. */
return count;
}
static DEVICE_ATTR_RW(post_reset_wdog);
static DEVICE_ATTR_RW(reset_action);
static DEVICE_ATTR_RW(second_reset_action);
static DEVICE_ATTR_RO(lifecycle_state);
static DEVICE_ATTR_RO(secure_boot_fuse_state);
static DEVICE_ATTR_WO(fw_reset);
static DEVICE_ATTR_WO(rsh_log);
static struct attribute *mlxbf_bootctl_attrs[] = {
&dev_attr_post_reset_wdog.attr,
@ -280,6 +406,7 @@ static struct attribute *mlxbf_bootctl_attrs[] = {
&dev_attr_lifecycle_state.attr,
&dev_attr_secure_boot_fuse_state.attr,
&dev_attr_fw_reset.attr,
&dev_attr_rsh_log.attr,
NULL
};
@ -345,19 +472,32 @@ static bool mlxbf_bootctl_guid_match(const guid_t *guid,
static int mlxbf_bootctl_probe(struct platform_device *pdev)
{
struct arm_smccc_res res = { 0 };
void __iomem *reg;
guid_t guid;
int ret;
/* Get the resource of the bootfifo data register. */
/* Map the resource of the bootfifo data register. */
mlxbf_rsh_boot_data = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mlxbf_rsh_boot_data))
return PTR_ERR(mlxbf_rsh_boot_data);
/* Get the resource of the bootfifo counter register. */
/* Map the resource of the bootfifo counter register. */
mlxbf_rsh_boot_cnt = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(mlxbf_rsh_boot_cnt))
return PTR_ERR(mlxbf_rsh_boot_cnt);
/* Map the resource of the rshim semaphore register. */
mlxbf_rsh_semaphore = devm_platform_ioremap_resource(pdev, 2);
if (IS_ERR(mlxbf_rsh_semaphore))
return PTR_ERR(mlxbf_rsh_semaphore);
/* Map the resource of the scratch buffer (log) registers. */
reg = devm_platform_ioremap_resource(pdev, 3);
if (IS_ERR(reg))
return PTR_ERR(reg);
mlxbf_rsh_scratch_buf_ctl = reg + MLXBF_RSH_SCRATCH_BUF_CTL_OFF;
mlxbf_rsh_scratch_buf_data = reg + MLXBF_RSH_SCRATCH_BUF_DATA_OFF;
/* Ensure we have the UUID we expect for this service. */
arm_smccc_smc(MLXBF_BOOTCTL_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res);
guid_parse(mlxbf_bootctl_svc_uuid_str, &guid);

View File

@ -573,7 +573,7 @@ static const struct acpi_device_id mshw0011_acpi_match[] = {
MODULE_DEVICE_TABLE(acpi, mshw0011_acpi_match);
static struct i2c_driver mshw0011_driver = {
.probe_new = mshw0011_probe,
.probe = mshw0011_probe,
.remove = mshw0011_remove,
.driver = {
.name = "mshw0011",

View File

@ -43,8 +43,8 @@ config WMI_BMOF
default ACPI_WMI
help
Say Y here if you want to be able to read a firmware-embedded
WMI Binary MOF data. Using this requires userspace tools and may be
rather tedious.
WMI Binary MOF (Managed Object Format) data. Using this requires
userspace tools and may be rather tedious.
To compile this driver as a module, choose M here: the module will
be called wmi-bmof.
@ -121,10 +121,11 @@ config GIGABYTE_WMI
To compile this driver as a module, choose M here: the module will
be called gigabyte-wmi.
config YOGABOOK_WMI
tristate "Lenovo Yoga Book tablet WMI key driver"
config YOGABOOK
tristate "Lenovo Yoga Book tablet key driver"
depends on ACPI_WMI
depends on INPUT
depends on I2C
select LEDS_CLASS
select NEW_LEDS
help
@ -132,7 +133,7 @@ config YOGABOOK_WMI
control on the Lenovo Yoga Book tablets.
To compile this driver as a module, choose M here: the module will
be called lenovo-yogabook-wmi.
be called lenovo-yogabook.
config ACERHDF
tristate "Acer Aspire One temperature and fan driver"

View File

@ -14,7 +14,6 @@ obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
obj-$(CONFIG_NVIDIA_WMI_EC_BACKLIGHT) += nvidia-wmi-ec-backlight.o
obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o
obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o
obj-$(CONFIG_YOGABOOK_WMI) += lenovo-yogabook-wmi.o
# Acer
obj-$(CONFIG_ACERHDF) += acerhdf.o
@ -66,6 +65,7 @@ obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
# Intel
obj-y += intel/

View File

@ -45,7 +45,6 @@
#define AMD_PMC_STB_DUMMY_PC 0xC6000007
/* STB S2D(Spill to DRAM) has different message port offset */
#define STB_SPILL_TO_DRAM 0xBE
#define AMD_S2D_REGISTER_MESSAGE 0xA20
#define AMD_S2D_REGISTER_RESPONSE 0xA80
#define AMD_S2D_REGISTER_ARGUMENT 0xA88
@ -99,7 +98,6 @@
#define PMC_MSG_DELAY_MIN_US 50
#define RESPONSE_REGISTER_LOOP_MAX 20000
#define SOC_SUBSYSTEM_IP_MAX 12
#define DELAY_MIN_US 2000
#define DELAY_MAX_US 3000
#define FIFO_SIZE 4096
@ -115,6 +113,7 @@ enum s2d_arg {
S2D_PHYS_ADDR_LOW,
S2D_PHYS_ADDR_HIGH,
S2D_NUM_SAMPLES,
S2D_DRAM_SIZE,
};
struct amd_pmc_bit_map {
@ -132,9 +131,18 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = {
{"ISP", BIT(6)},
{"NBIO", BIT(7)},
{"DF", BIT(8)},
{"USB0", BIT(9)},
{"USB1", BIT(10)},
{"USB3_0", BIT(9)},
{"USB3_1", BIT(10)},
{"LAPIC", BIT(11)},
{"USB3_2", BIT(12)},
{"USB3_3", BIT(13)},
{"USB3_4", BIT(14)},
{"USB4_0", BIT(15)},
{"USB4_1", BIT(16)},
{"MPM", BIT(17)},
{"JPEG", BIT(18)},
{"IPU", BIT(19)},
{"UMSCH", BIT(20)},
{}
};
@ -147,6 +155,9 @@ struct amd_pmc_dev {
u32 base_addr;
u32 cpu_id;
u32 active_ips;
u32 dram_size;
u32 num_ips;
u32 s2d_msg_id;
/* SMU version information */
u8 smu_program;
u8 major;
@ -194,8 +205,8 @@ struct smu_metrics {
u64 timein_s0i3_totaltime;
u64 timein_swdrips_lastcapture;
u64 timein_swdrips_totaltime;
u64 timecondition_notmet_lastcapture[SOC_SUBSYSTEM_IP_MAX];
u64 timecondition_notmet_totaltime[SOC_SUBSYSTEM_IP_MAX];
u64 timecondition_notmet_lastcapture[32];
u64 timecondition_notmet_totaltime[32];
} __packed;
static int amd_pmc_stb_debugfs_open(struct inode *inode, struct file *filp)
@ -261,7 +272,7 @@ static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
dev->msg_port = 1;
/* Get the num_samples to calculate the last push location */
ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, STB_SPILL_TO_DRAM, 1);
ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->s2d_msg_id, true);
/* Clear msg_port for other SMU operation */
dev->msg_port = 0;
if (ret) {
@ -308,6 +319,23 @@ static const struct file_operations amd_pmc_stb_debugfs_fops_v2 = {
.release = amd_pmc_stb_debugfs_release_v2,
};
static void amd_pmc_get_ip_info(struct amd_pmc_dev *dev)
{
switch (dev->cpu_id) {
case AMD_CPU_ID_PCO:
case AMD_CPU_ID_RN:
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
dev->num_ips = 12;
dev->s2d_msg_id = 0xBE;
break;
case AMD_CPU_ID_PS:
dev->num_ips = 21;
dev->s2d_msg_id = 0x85;
break;
}
}
static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev)
{
if (dev->cpu_id == AMD_CPU_ID_PCO) {
@ -317,15 +345,15 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev)
/* Get Active devices list from SMU */
if (!dev->active_ips)
amd_pmc_send_cmd(dev, 0, &dev->active_ips, SMU_MSG_GET_SUP_CONSTRAINTS, 1);
amd_pmc_send_cmd(dev, 0, &dev->active_ips, SMU_MSG_GET_SUP_CONSTRAINTS, true);
/* Get dram address */
if (!dev->smu_virt_addr) {
u32 phys_addr_low, phys_addr_hi;
u64 smu_phys_addr;
amd_pmc_send_cmd(dev, 0, &phys_addr_low, SMU_MSG_LOG_GETDRAM_ADDR_LO, 1);
amd_pmc_send_cmd(dev, 0, &phys_addr_hi, SMU_MSG_LOG_GETDRAM_ADDR_HI, 1);
amd_pmc_send_cmd(dev, 0, &phys_addr_low, SMU_MSG_LOG_GETDRAM_ADDR_LO, true);
amd_pmc_send_cmd(dev, 0, &phys_addr_hi, SMU_MSG_LOG_GETDRAM_ADDR_HI, true);
smu_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
dev->smu_virt_addr = devm_ioremap(dev->dev, smu_phys_addr,
@ -335,8 +363,8 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev)
}
/* Start the logging */
amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_RESET, 0);
amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_START, 0);
amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_RESET, false);
amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_START, false);
return 0;
}
@ -377,7 +405,7 @@ static int amd_pmc_get_smu_version(struct amd_pmc_dev *dev)
if (dev->cpu_id == AMD_CPU_ID_PCO)
return -ENODEV;
rc = amd_pmc_send_cmd(dev, 0, &val, SMU_MSG_GETSMUVERSION, 1);
rc = amd_pmc_send_cmd(dev, 0, &val, SMU_MSG_GETSMUVERSION, true);
if (rc)
return rc;
@ -469,7 +497,7 @@ static int smu_fw_info_show(struct seq_file *s, void *unused)
table.timeto_resume_to_os_lastcapture);
seq_puts(s, "\n=== Active time (in us) ===\n");
for (idx = 0 ; idx < SOC_SUBSYSTEM_IP_MAX ; idx++) {
for (idx = 0 ; idx < dev->num_ips ; idx++) {
if (soc15_ip_blk[idx].bit_mask & dev->active_ips)
seq_printf(s, "%-8s : %lld\n", soc15_ip_blk[idx].name,
table.timecondition_notmet_lastcapture[idx]);
@ -562,6 +590,18 @@ static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev)
debugfs_remove_recursive(dev->dbgfs_dir);
}
static bool amd_pmc_is_stb_supported(struct amd_pmc_dev *dev)
{
switch (dev->cpu_id) {
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
case AMD_CPU_ID_PS:
return true;
default:
return false;
}
}
static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
{
dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL);
@ -573,8 +613,7 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
&amd_pmc_idlemask_fops);
/* Enable STB only when the module_param is set */
if (enable_stb) {
if (dev->cpu_id == AMD_CPU_ID_YC || dev->cpu_id == AMD_CPU_ID_CB ||
dev->cpu_id == AMD_CPU_ID_PS)
if (amd_pmc_is_stb_supported(dev))
debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
&amd_pmc_stb_debugfs_fops_v2);
else
@ -794,7 +833,7 @@ static void amd_pmc_s2idle_prepare(void)
}
msg = amd_pmc_get_os_hint(pdev);
rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0);
rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, false);
if (rc) {
dev_err(pdev->dev, "suspend failed: %d\n", rc);
return;
@ -829,7 +868,7 @@ static int amd_pmc_dump_data(struct amd_pmc_dev *pdev)
if (pdev->cpu_id == AMD_CPU_ID_PCO)
return -ENODEV;
return amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0);
return amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, false);
}
static void amd_pmc_s2idle_restore(void)
@ -839,7 +878,7 @@ static void amd_pmc_s2idle_restore(void)
u8 msg;
msg = amd_pmc_get_os_hint(pdev);
rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0);
rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, false);
if (rc)
dev_err(pdev->dev, "resume failed: %d\n", rc);
@ -890,29 +929,65 @@ static const struct pci_device_id pmc_pci_ids[] = {
{ }
};
static int amd_pmc_get_dram_size(struct amd_pmc_dev *dev)
{
int ret;
switch (dev->cpu_id) {
case AMD_CPU_ID_YC:
if (!(dev->major > 90 || (dev->major == 90 && dev->minor > 39))) {
ret = -EINVAL;
goto err_dram_size;
}
break;
default:
ret = -EINVAL;
goto err_dram_size;
}
ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->s2d_msg_id, true);
if (ret || !dev->dram_size)
goto err_dram_size;
return 0;
err_dram_size:
dev_err(dev->dev, "DRAM size command not supported for this platform\n");
return ret;
}
static int amd_pmc_s2d_init(struct amd_pmc_dev *dev)
{
u32 phys_addr_low, phys_addr_hi;
u64 stb_phys_addr;
u32 size = 0;
int ret;
/* Spill to DRAM feature uses separate SMU message port */
dev->msg_port = 1;
amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, STB_SPILL_TO_DRAM, 1);
/* Get num of IP blocks within the SoC */
amd_pmc_get_ip_info(dev);
amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->s2d_msg_id, true);
if (size != S2D_TELEMETRY_BYTES_MAX)
return -EIO;
/* Get DRAM size */
ret = amd_pmc_get_dram_size(dev);
if (ret)
dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX;
/* Get STB DRAM address */
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, STB_SPILL_TO_DRAM, 1);
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, STB_SPILL_TO_DRAM, 1);
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->s2d_msg_id, true);
amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->s2d_msg_id, true);
stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
/* Clear msg_port for other SMU operation */
dev->msg_port = 0;
dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, S2D_TELEMETRY_DRAMBYTES_MAX);
dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size);
if (!dev->stb_virt_addr)
return -ENOMEM;
@ -1001,7 +1076,7 @@ static int amd_pmc_probe(struct platform_device *pdev)
mutex_init(&dev->lock);
if (enable_stb && (dev->cpu_id == AMD_CPU_ID_YC || dev->cpu_id == AMD_CPU_ID_CB)) {
if (enable_stb && amd_pmc_is_stb_supported(dev)) {
err = amd_pmc_s2d_init(dev);
if (err)
goto err_pci_dev_put;

View File

@ -16,3 +16,14 @@ config AMD_PMF
To compile this driver as a module, choose M here: the module will
be called amd_pmf.
config AMD_PMF_DEBUG
bool "PMF debug information"
depends on AMD_PMF
help
Enabling this option would give more debug information on the OEM fed
power setting values for each of the PMF feature. PMF driver gets this
information after evaluating a ACPI method and the information is stored
in the PMF config store.
Say Y here to enable more debug logs and Say N here if you are not sure.

View File

@ -15,6 +15,100 @@
static struct auto_mode_mode_config config_store;
static const char *state_as_str(unsigned int state);
#ifdef CONFIG_AMD_PMF_DEBUG
static void amd_pmf_dump_auto_mode_defaults(struct auto_mode_mode_config *data)
{
struct auto_mode_mode_settings *its_mode;
pr_debug("Auto Mode Data - BEGIN\n");
/* time constant */
pr_debug("balanced_to_perf: %u ms\n",
data->transition[AUTO_TRANSITION_TO_PERFORMANCE].time_constant);
pr_debug("perf_to_balanced: %u ms\n",
data->transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].time_constant);
pr_debug("quiet_to_balanced: %u ms\n",
data->transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].time_constant);
pr_debug("balanced_to_quiet: %u ms\n",
data->transition[AUTO_TRANSITION_TO_QUIET].time_constant);
/* power floor */
pr_debug("pfloor_perf: %u mW\n", data->mode_set[AUTO_PERFORMANCE].power_floor);
pr_debug("pfloor_balanced: %u mW\n", data->mode_set[AUTO_BALANCE].power_floor);
pr_debug("pfloor_quiet: %u mW\n", data->mode_set[AUTO_QUIET].power_floor);
/* Power delta for mode change */
pr_debug("pd_balanced_to_perf: %u mW\n",
data->transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta);
pr_debug("pd_perf_to_balanced: %u mW\n",
data->transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta);
pr_debug("pd_quiet_to_balanced: %u mW\n",
data->transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta);
pr_debug("pd_balanced_to_quiet: %u mW\n",
data->transition[AUTO_TRANSITION_TO_QUIET].power_delta);
/* skin temperature limits */
its_mode = &data->mode_set[AUTO_PERFORMANCE_ON_LAP];
pr_debug("stt_apu_perf_on_lap: %u C\n",
its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
pr_debug("stt_hs2_perf_on_lap: %u C\n",
its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
pr_debug("stt_min_limit_perf_on_lap: %u mW\n", its_mode->power_control.stt_min);
its_mode = &data->mode_set[AUTO_PERFORMANCE];
pr_debug("stt_apu_perf: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
pr_debug("stt_hs2_perf: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
pr_debug("stt_min_limit_perf: %u mW\n", its_mode->power_control.stt_min);
its_mode = &data->mode_set[AUTO_BALANCE];
pr_debug("stt_apu_balanced: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
pr_debug("stt_hs2_balanced: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
pr_debug("stt_min_limit_balanced: %u mW\n", its_mode->power_control.stt_min);
its_mode = &data->mode_set[AUTO_QUIET];
pr_debug("stt_apu_quiet: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
pr_debug("stt_hs2_quiet: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
pr_debug("stt_min_limit_quiet: %u mW\n", its_mode->power_control.stt_min);
/* SPL based power limits */
its_mode = &data->mode_set[AUTO_PERFORMANCE_ON_LAP];
pr_debug("fppt_perf_on_lap: %u mW\n", its_mode->power_control.fppt);
pr_debug("sppt_perf_on_lap: %u mW\n", its_mode->power_control.sppt);
pr_debug("spl_perf_on_lap: %u mW\n", its_mode->power_control.spl);
pr_debug("sppt_apu_only_perf_on_lap: %u mW\n", its_mode->power_control.sppt_apu_only);
its_mode = &data->mode_set[AUTO_PERFORMANCE];
pr_debug("fppt_perf: %u mW\n", its_mode->power_control.fppt);
pr_debug("sppt_perf: %u mW\n", its_mode->power_control.sppt);
pr_debug("spl_perf: %u mW\n", its_mode->power_control.spl);
pr_debug("sppt_apu_only_perf: %u mW\n", its_mode->power_control.sppt_apu_only);
its_mode = &data->mode_set[AUTO_BALANCE];
pr_debug("fppt_balanced: %u mW\n", its_mode->power_control.fppt);
pr_debug("sppt_balanced: %u mW\n", its_mode->power_control.sppt);
pr_debug("spl_balanced: %u mW\n", its_mode->power_control.spl);
pr_debug("sppt_apu_only_balanced: %u mW\n", its_mode->power_control.sppt_apu_only);
its_mode = &data->mode_set[AUTO_QUIET];
pr_debug("fppt_quiet: %u mW\n", its_mode->power_control.fppt);
pr_debug("sppt_quiet: %u mW\n", its_mode->power_control.sppt);
pr_debug("spl_quiet: %u mW\n", its_mode->power_control.spl);
pr_debug("sppt_apu_only_quiet: %u mW\n", its_mode->power_control.sppt_apu_only);
/* Fan ID */
pr_debug("fan_id_perf: %lu\n",
data->mode_set[AUTO_PERFORMANCE].fan_control.fan_id);
pr_debug("fan_id_balanced: %lu\n",
data->mode_set[AUTO_BALANCE].fan_control.fan_id);
pr_debug("fan_id_quiet: %lu\n",
data->mode_set[AUTO_QUIET].fan_control.fan_id);
pr_debug("Auto Mode Data - END\n");
}
#else
static void amd_pmf_dump_auto_mode_defaults(struct auto_mode_mode_config *data) {}
#endif
static void amd_pmf_set_automode(struct amd_pmf_dev *dev, int idx,
struct auto_mode_mode_config *table)
{
@ -85,11 +179,34 @@ void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t t
config_store.transition[i].applied = false;
update = true;
}
#ifdef CONFIG_AMD_PMF_DEBUG
dev_dbg(dev->dev, "[AUTO MODE] average_power : %d mW mode: %s\n", avg_power,
state_as_str(config_store.current_mode));
dev_dbg(dev->dev, "[AUTO MODE] time: %lld ms timer: %u ms tc: %u ms\n",
time_elapsed_ms, config_store.transition[i].timer,
config_store.transition[i].time_constant);
dev_dbg(dev->dev, "[AUTO MODE] shiftup: %u pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[i].shifting_up,
config_store.transition[i].power_threshold,
config_store.mode_set[i].power_floor,
config_store.transition[i].power_delta);
#endif
}
dev_dbg(dev->dev, "[AUTO_MODE] avg power: %u mW mode: %s\n", avg_power,
state_as_str(config_store.current_mode));
#ifdef CONFIG_AMD_PMF_DEBUG
dev_dbg(dev->dev, "[AUTO MODE] priority1: %u priority2: %u priority3: %u priority4: %u\n",
config_store.transition[0].applied,
config_store.transition[1].applied,
config_store.transition[2].applied,
config_store.transition[3].applied);
#endif
if (update) {
for (j = 0; j < AUTO_TRANSITION_MAX; j++) {
/* Apply the mode with highest priority indentified */
@ -140,6 +257,30 @@ static void amd_pmf_get_power_threshold(void)
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_threshold =
config_store.mode_set[AUTO_PERFORMANCE].power_floor -
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta;
#ifdef CONFIG_AMD_PMF_DEBUG
pr_debug("[AUTO MODE TO_QUIET] pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[AUTO_TRANSITION_TO_QUIET].power_threshold,
config_store.mode_set[AUTO_BALANCE].power_floor,
config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta);
pr_debug("[AUTO MODE TO_PERFORMANCE] pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_threshold,
config_store.mode_set[AUTO_BALANCE].power_floor,
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta);
pr_debug("[AUTO MODE QUIET_TO_BALANCE] pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE]
.power_threshold,
config_store.mode_set[AUTO_QUIET].power_floor,
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta);
pr_debug("[AUTO MODE PERFORMANCE_TO_BALANCE] pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE]
.power_threshold,
config_store.mode_set[AUTO_PERFORMANCE].power_floor,
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta);
#endif
}
static const char *state_as_str(unsigned int state)
@ -262,6 +403,8 @@ static void amd_pmf_load_defaults_auto_mode(struct amd_pmf_dev *dev)
/* set to initial default values */
config_store.current_mode = AUTO_BALANCE;
dev->socket_power_history_idx = -1;
amd_pmf_dump_auto_mode_defaults(&config_store);
}
int amd_pmf_reset_amt(struct amd_pmf_dev *dev)

View File

@ -13,6 +13,61 @@
static struct cnqf_config config_store;
#ifdef CONFIG_AMD_PMF_DEBUG
static const char *state_as_str_cnqf(unsigned int state)
{
switch (state) {
case APMF_CNQF_TURBO:
return "turbo";
case APMF_CNQF_PERFORMANCE:
return "performance";
case APMF_CNQF_BALANCE:
return "balance";
case APMF_CNQF_QUIET:
return "quiet";
default:
return "Unknown CnQF State";
}
}
static void amd_pmf_cnqf_dump_defaults(struct apmf_dyn_slider_output *data, int idx)
{
int i;
pr_debug("Dynamic Slider %s Defaults - BEGIN\n", idx ? "DC" : "AC");
pr_debug("size: %u\n", data->size);
pr_debug("flags: 0x%x\n", data->flags);
/* Time constants */
pr_debug("t_perf_to_turbo: %u ms\n", data->t_perf_to_turbo);
pr_debug("t_balanced_to_perf: %u ms\n", data->t_balanced_to_perf);
pr_debug("t_quiet_to_balanced: %u ms\n", data->t_quiet_to_balanced);
pr_debug("t_balanced_to_quiet: %u ms\n", data->t_balanced_to_quiet);
pr_debug("t_perf_to_balanced: %u ms\n", data->t_perf_to_balanced);
pr_debug("t_turbo_to_perf: %u ms\n", data->t_turbo_to_perf);
for (i = 0 ; i < CNQF_MODE_MAX ; i++) {
pr_debug("pfloor_%s: %u mW\n", state_as_str_cnqf(i), data->ps[i].pfloor);
pr_debug("fppt_%s: %u mW\n", state_as_str_cnqf(i), data->ps[i].fppt);
pr_debug("sppt_%s: %u mW\n", state_as_str_cnqf(i), data->ps[i].sppt);
pr_debug("sppt_apuonly_%s: %u mW\n",
state_as_str_cnqf(i), data->ps[i].sppt_apu_only);
pr_debug("spl_%s: %u mW\n", state_as_str_cnqf(i), data->ps[i].spl);
pr_debug("stt_minlimit_%s: %u mW\n",
state_as_str_cnqf(i), data->ps[i].stt_min_limit);
pr_debug("stt_skintemp_apu_%s: %u C\n", state_as_str_cnqf(i),
data->ps[i].stt_skintemp[STT_TEMP_APU]);
pr_debug("stt_skintemp_hs2_%s: %u C\n", state_as_str_cnqf(i),
data->ps[i].stt_skintemp[STT_TEMP_HS2]);
pr_debug("fan_id_%s: %u\n", state_as_str_cnqf(i), data->ps[i].fan_id);
}
pr_debug("Dynamic Slider %s Defaults - END\n", idx ? "DC" : "AC");
}
#else
static void amd_pmf_cnqf_dump_defaults(struct apmf_dyn_slider_output *data, int idx) {}
#endif
static int amd_pmf_set_cnqf(struct amd_pmf_dev *dev, int src, int idx,
struct cnqf_config *table)
{
@ -120,6 +175,13 @@ int amd_pmf_trans_cnqf(struct amd_pmf_dev *dev, int socket_power, ktime_t time_l
config_store.trans_param[src][i].count++;
tp = &config_store.trans_param[src][i];
#ifdef CONFIG_AMD_PMF_DEBUG
dev_dbg(dev->dev, "avg_power: %u mW total_power: %u mW count: %u timer: %u ms\n",
avg_power, config_store.trans_param[src][i].total_power,
config_store.trans_param[src][i].count,
config_store.trans_param[src][i].timer);
#endif
if (tp->timer >= tp->time_constant && tp->count) {
avg_power = tp->total_power / tp->count;
@ -140,6 +202,18 @@ int amd_pmf_trans_cnqf(struct amd_pmf_dev *dev, int socket_power, ktime_t time_l
dev_dbg(dev->dev, "[CNQF] Avg power: %u mW socket power: %u mW mode:%s\n",
avg_power, socket_power, state_as_str(config_store.current_mode));
#ifdef CONFIG_AMD_PMF_DEBUG
dev_dbg(dev->dev, "[CNQF] priority1: %u priority2: %u priority3: %u\n",
config_store.trans_param[src][0].priority,
config_store.trans_param[src][1].priority,
config_store.trans_param[src][2].priority);
dev_dbg(dev->dev, "[CNQF] priority4: %u priority5: %u priority6: %u\n",
config_store.trans_param[src][3].priority,
config_store.trans_param[src][4].priority,
config_store.trans_param[src][5].priority);
#endif
for (j = 0; j < CNQF_TRANSITION_MAX; j++) {
/* apply the highest priority */
if (config_store.trans_param[src][j].priority) {
@ -284,6 +358,7 @@ static int amd_pmf_load_defaults_cnqf(struct amd_pmf_dev *dev)
return ret;
}
amd_pmf_cnqf_dump_defaults(&out, i);
amd_pmf_update_mode_set(i, &out);
amd_pmf_update_trans_data(i, &out);
amd_pmf_update_power_threshold(i);

View File

@ -12,6 +12,60 @@
static struct amd_pmf_static_slider_granular config_store;
#ifdef CONFIG_AMD_PMF_DEBUG
static const char *slider_as_str(unsigned int state)
{
switch (state) {
case POWER_MODE_PERFORMANCE:
return "PERFORMANCE";
case POWER_MODE_BALANCED_POWER:
return "BALANCED_POWER";
case POWER_MODE_POWER_SAVER:
return "POWER_SAVER";
default:
return "Unknown Slider State";
}
}
static const char *source_as_str(unsigned int state)
{
switch (state) {
case POWER_SOURCE_AC:
return "AC";
case POWER_SOURCE_DC:
return "DC";
default:
return "Unknown Power State";
}
}
static void amd_pmf_dump_sps_defaults(struct amd_pmf_static_slider_granular *data)
{
int i, j;
pr_debug("Static Slider Data - BEGIN\n");
for (i = 0; i < POWER_SOURCE_MAX; i++) {
for (j = 0; j < POWER_MODE_MAX; j++) {
pr_debug("--- Source:%s Mode:%s ---\n", source_as_str(i), slider_as_str(j));
pr_debug("SPL: %u mW\n", data->prop[i][j].spl);
pr_debug("SPPT: %u mW\n", data->prop[i][j].sppt);
pr_debug("SPPT_ApuOnly: %u mW\n", data->prop[i][j].sppt_apu_only);
pr_debug("FPPT: %u mW\n", data->prop[i][j].fppt);
pr_debug("STTMinLimit: %u mW\n", data->prop[i][j].stt_min);
pr_debug("STT_SkinTempLimit_APU: %u C\n",
data->prop[i][j].stt_skin_temp[STT_TEMP_APU]);
pr_debug("STT_SkinTempLimit_HS2: %u C\n",
data->prop[i][j].stt_skin_temp[STT_TEMP_HS2]);
}
}
pr_debug("Static Slider Data - END\n");
}
#else
static void amd_pmf_dump_sps_defaults(struct amd_pmf_static_slider_granular *data) {}
#endif
static void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev)
{
struct apmf_static_slider_granular_output output;
@ -36,6 +90,7 @@ static void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev)
idx++;
}
}
amd_pmf_dump_sps_defaults(&config_store);
}
void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,

View File

@ -278,7 +278,7 @@ static u32 gmux_mmio_read32(struct apple_gmux_data *gmux_data, int port)
iowrite8(GMUX_MMIO_READ | sizeof(val),
gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
gmux_mmio_wait(gmux_data);
val = be32_to_cpu(ioread32(gmux_data->iomem_base));
val = ioread32be(gmux_data->iomem_base);
mutex_unlock(&gmux_data->index_lock);
return val;
@ -288,7 +288,7 @@ static void gmux_mmio_write32(struct apple_gmux_data *gmux_data, int port,
u32 val)
{
mutex_lock(&gmux_data->index_lock);
iowrite32(cpu_to_be32(val), gmux_data->iomem_base);
iowrite32be(val, gmux_data->iomem_base);
iowrite8(port & 0xff, gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
iowrite8(GMUX_MMIO_WRITE | sizeof(val),
gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);

View File

@ -933,7 +933,7 @@ static struct i2c_driver tf103c_dock_driver = {
.pm = &tf103c_dock_pm_ops,
.acpi_match_table = tf103c_dock_acpi_match,
},
.probe_new = tf103c_dock_probe,
.probe = tf103c_dock_probe,
.remove = tf103c_dock_remove,
};
module_i2c_driver(tf103c_dock_driver);

View File

@ -395,16 +395,16 @@ static int rbtn_add(struct acpi_device *device)
return -EINVAL;
}
rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
if (!rbtn_data)
return -ENOMEM;
ret = rbtn_acquire(device, true);
if (ret < 0) {
dev_err(&device->dev, "Cannot enable device\n");
return ret;
}
rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
if (!rbtn_data)
return -ENOMEM;
rbtn_data->type = type;
device->driver_data = rbtn_data;
@ -420,10 +420,12 @@ static int rbtn_add(struct acpi_device *device)
break;
default:
ret = -EINVAL;
break;
}
if (ret)
rbtn_acquire(device, false);
return ret;
}
static void rbtn_remove(struct acpi_device *device)
@ -442,7 +444,6 @@ static void rbtn_remove(struct acpi_device *device)
}
rbtn_acquire(device, false);
device->driver_data = NULL;
}
static void rbtn_notify(struct acpi_device *device, u32 event)

View File

@ -303,16 +303,13 @@ union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string)
*/
int get_instance_count(const char *guid_string)
{
union acpi_object *wmi_obj = NULL;
int i = 0;
int ret;
do {
kfree(wmi_obj);
wmi_obj = get_wmiobj_pointer(i, guid_string);
i++;
} while (wmi_obj);
ret = wmi_instance_count(guid_string);
if (ret < 0)
return 0;
return (i-1);
return ret;
}
/**

View File

@ -5,7 +5,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/wmi.h>
@ -13,10 +12,6 @@
#define GIGABYTE_WMI_GUID "DEADBEEF-2001-0000-00A0-C90629100000"
#define NUM_TEMPERATURE_SENSORS 6
static bool force_load;
module_param(force_load, bool, 0444);
MODULE_PARM_DESC(force_load, "Force loading on unknown platform");
static u8 usable_sensors_mask;
enum gigabyte_wmi_commandtype {
@ -99,7 +94,7 @@ static umode_t gigabyte_wmi_hwmon_is_visible(const void *data, enum hwmon_sensor
return usable_sensors_mask & BIT(channel) ? 0444 : 0;
}
static const struct hwmon_channel_info *gigabyte_wmi_hwmon_info[] = {
static const struct hwmon_channel_info * const gigabyte_wmi_hwmon_info[] = {
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT,
HWMON_T_INPUT,
@ -133,49 +128,10 @@ static u8 gigabyte_wmi_detect_sensor_usability(struct wmi_device *wdev)
return r;
}
#define DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME(name) \
{ .matches = { \
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."), \
DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \
}}
static const struct dmi_system_id gigabyte_wmi_known_working_platforms[] = {
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("A320M-S2H V2-CF"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B450M DS3H-CF"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B450M DS3H WIFI-CF"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B450M S2H V2"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550 AORUS ELITE AX V2"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550 AORUS ELITE"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550 AORUS ELITE V2"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550 GAMING X V2"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550I AORUS PRO AX"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550M AORUS PRO-P"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B550M DS3H"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B650 AORUS ELITE AX"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B660 GAMING X DDR4"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("B660I AORUS PRO DDR4"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("Z390 I AORUS PRO WIFI-CF"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("Z490 AORUS ELITE AC"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 AORUS ELITE"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 AORUS ELITE WIFI"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 GAMING X"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 I AORUS PRO WIFI"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570 UD"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("X570S AORUS ELITE"),
DMI_EXACT_MATCH_GIGABYTE_BOARD_NAME("Z690M AORUS ELITE AX DDR4"),
{ }
};
static int gigabyte_wmi_probe(struct wmi_device *wdev, const void *context)
{
struct device *hwmon_dev;
if (!dmi_check_system(gigabyte_wmi_known_working_platforms)) {
if (!force_load)
return -ENODEV;
dev_warn(&wdev->dev, "Forcing load on unknown platform");
}
usable_sensors_mask = gigabyte_wmi_detect_sensor_usability(wdev);
if (!usable_sensors_mask) {
dev_info(&wdev->dev, "No temperature sensors usable");

View File

@ -66,6 +66,11 @@ static const char *const omen_thermal_profile_force_v0_boards[] = {
"8607", "8746", "8747", "8749", "874A", "8748"
};
/* DMI Board names of Victus laptops */
static const char * const victus_thermal_profile_boards[] = {
"8A25"
};
enum hp_wmi_radio {
HPWMI_WIFI = 0x0,
HPWMI_BLUETOOTH = 0x1,
@ -90,6 +95,7 @@ enum hp_wmi_event_ids {
HPWMI_PEAKSHIFT_PERIOD = 0x0F,
HPWMI_BATTERY_CHARGE_PERIOD = 0x10,
HPWMI_SANITIZATION_MODE = 0x17,
HPWMI_CAMERA_TOGGLE = 0x1A,
HPWMI_OMEN_KEY = 0x1D,
HPWMI_SMART_EXPERIENCE_APP = 0x21,
};
@ -176,6 +182,12 @@ enum hp_thermal_profile_omen_v1 {
HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50,
};
enum hp_thermal_profile_victus {
HP_VICTUS_THERMAL_PROFILE_DEFAULT = 0x00,
HP_VICTUS_THERMAL_PROFILE_PERFORMANCE = 0x01,
HP_VICTUS_THERMAL_PROFILE_QUIET = 0x03,
};
enum hp_thermal_profile {
HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
HP_THERMAL_PROFILE_DEFAULT = 0x01,
@ -222,6 +234,7 @@ static const struct key_entry hp_wmi_keymap[] = {
{ KE_IGNORE, 0x121a4, }, /* Win Lock Off */
{ KE_KEY, 0x21a5, { KEY_PROG2 } }, /* HP Omen Key */
{ KE_KEY, 0x21a7, { KEY_FN_ESC } },
{ KE_KEY, 0x21a8, { KEY_PROG2 } }, /* HP Envy x360 programmable key */
{ KE_KEY, 0x21a9, { KEY_TOUCHPAD_OFF } },
{ KE_KEY, 0x121a9, { KEY_TOUCHPAD_ON } },
{ KE_KEY, 0x231b, { KEY_HELP } },
@ -229,6 +242,7 @@ static const struct key_entry hp_wmi_keymap[] = {
};
static struct input_dev *hp_wmi_input_dev;
static struct input_dev *camera_shutter_input_dev;
static struct platform_device *hp_wmi_platform_dev;
static struct platform_profile_handler platform_profile_handler;
static bool platform_profile_support;
@ -740,6 +754,33 @@ static ssize_t postcode_store(struct device *dev, struct device_attribute *attr,
return count;
}
static int camera_shutter_input_setup(void)
{
int err;
camera_shutter_input_dev = input_allocate_device();
if (!camera_shutter_input_dev)
return -ENOMEM;
camera_shutter_input_dev->name = "HP WMI camera shutter";
camera_shutter_input_dev->phys = "wmi/input1";
camera_shutter_input_dev->id.bustype = BUS_HOST;
__set_bit(EV_SW, camera_shutter_input_dev->evbit);
__set_bit(SW_CAMERA_LENS_COVER, camera_shutter_input_dev->swbit);
err = input_register_device(camera_shutter_input_dev);
if (err)
goto err_free_dev;
return 0;
err_free_dev:
input_free_device(camera_shutter_input_dev);
camera_shutter_input_dev = NULL;
return err;
}
static DEVICE_ATTR_RO(display);
static DEVICE_ATTR_RO(hddtemp);
static DEVICE_ATTR_RW(als);
@ -816,11 +857,20 @@ static void hp_wmi_notify(u32 value, void *context)
case HPWMI_SMART_ADAPTER:
break;
case HPWMI_BEZEL_BUTTON:
case HPWMI_OMEN_KEY:
key_code = hp_wmi_read_int(HPWMI_HOTKEY_QUERY);
if (key_code < 0)
break;
if (!sparse_keymap_report_event(hp_wmi_input_dev,
key_code, 1, true))
pr_info("Unknown key code - 0x%x\n", key_code);
break;
case HPWMI_OMEN_KEY:
if (event_data) /* Only should be true for HP Omen */
key_code = event_data;
else
key_code = hp_wmi_read_int(HPWMI_HOTKEY_QUERY);
if (!sparse_keymap_report_event(hp_wmi_input_dev,
key_code, 1, true))
pr_info("Unknown key code - 0x%x\n", key_code);
@ -867,6 +917,20 @@ static void hp_wmi_notify(u32 value, void *context)
break;
case HPWMI_SANITIZATION_MODE:
break;
case HPWMI_CAMERA_TOGGLE:
if (!camera_shutter_input_dev)
if (camera_shutter_input_setup()) {
pr_err("Failed to setup camera shutter input device\n");
break;
}
if (event_data == 0xff)
input_report_switch(camera_shutter_input_dev, SW_CAMERA_LENS_COVER, 1);
else if (event_data == 0xfe)
input_report_switch(camera_shutter_input_dev, SW_CAMERA_LENS_COVER, 0);
else
pr_warn("Unknown camera shutter state - 0x%x\n", event_data);
input_sync(camera_shutter_input_dev);
break;
case HPWMI_SMART_EXPERIENCE_APP:
break;
default:
@ -1246,6 +1310,70 @@ static int hp_wmi_platform_profile_set(struct platform_profile_handler *pprof,
return 0;
}
static bool is_victus_thermal_profile(void)
{
const char *board_name = dmi_get_system_info(DMI_BOARD_NAME);
if (!board_name)
return false;
return match_string(victus_thermal_profile_boards,
ARRAY_SIZE(victus_thermal_profile_boards),
board_name) >= 0;
}
static int platform_profile_victus_get(struct platform_profile_handler *pprof,
enum platform_profile_option *profile)
{
int tp;
tp = omen_thermal_profile_get();
if (tp < 0)
return tp;
switch (tp) {
case HP_VICTUS_THERMAL_PROFILE_PERFORMANCE:
*profile = PLATFORM_PROFILE_PERFORMANCE;
break;
case HP_VICTUS_THERMAL_PROFILE_DEFAULT:
*profile = PLATFORM_PROFILE_BALANCED;
break;
case HP_VICTUS_THERMAL_PROFILE_QUIET:
*profile = PLATFORM_PROFILE_QUIET;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int platform_profile_victus_set(struct platform_profile_handler *pprof,
enum platform_profile_option profile)
{
int err, tp;
switch (profile) {
case PLATFORM_PROFILE_PERFORMANCE:
tp = HP_VICTUS_THERMAL_PROFILE_PERFORMANCE;
break;
case PLATFORM_PROFILE_BALANCED:
tp = HP_VICTUS_THERMAL_PROFILE_DEFAULT;
break;
case PLATFORM_PROFILE_QUIET:
tp = HP_VICTUS_THERMAL_PROFILE_QUIET;
break;
default:
return -EOPNOTSUPP;
}
err = omen_thermal_profile_set(tp);
if (err < 0)
return err;
return 0;
}
static int thermal_profile_setup(void)
{
int err, tp;
@ -1266,6 +1394,25 @@ static int thermal_profile_setup(void)
platform_profile_handler.profile_get = platform_profile_omen_get;
platform_profile_handler.profile_set = platform_profile_omen_set;
set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices);
} else if (is_victus_thermal_profile()) {
tp = omen_thermal_profile_get();
if (tp < 0)
return tp;
/*
* call thermal profile write command to ensure that the
* firmware correctly sets the OEM variables
*/
err = omen_thermal_profile_set(tp);
if (err < 0)
return err;
platform_profile_handler.profile_get = platform_profile_victus_get;
platform_profile_handler.profile_set = platform_profile_victus_set;
set_bit(PLATFORM_PROFILE_QUIET, platform_profile_handler.choices);
} else {
tp = thermal_profile_get();
@ -1284,9 +1431,9 @@ static int thermal_profile_setup(void)
platform_profile_handler.profile_set = hp_wmi_platform_profile_set;
set_bit(PLATFORM_PROFILE_QUIET, platform_profile_handler.choices);
set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices);
}
set_bit(PLATFORM_PROFILE_COOL, platform_profile_handler.choices);
set_bit(PLATFORM_PROFILE_BALANCED, platform_profile_handler.choices);
set_bit(PLATFORM_PROFILE_PERFORMANCE, platform_profile_handler.choices);
@ -1483,7 +1630,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
}
}
static const struct hwmon_channel_info *info[] = {
static const struct hwmon_channel_info * const info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT),
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
NULL
@ -1565,6 +1712,9 @@ static void __exit hp_wmi_exit(void)
if (wmi_has_guid(HPWMI_EVENT_GUID))
hp_wmi_input_destroy();
if (camera_shutter_input_dev)
input_unregister_device(camera_shutter_input_dev);
if (hp_wmi_platform_dev) {
platform_device_unregister(hp_wmi_platform_dev);
platform_driver_unregister(&hp_wmi_driver);

View File

@ -5,12 +5,48 @@
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/gpio/consumer.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
* through a GPIO.
*/
static const guid_t img_clk_guid =
GUID_INIT(0x82c0d13a, 0x78c5, 0x4244,
0x9b, 0xb1, 0xeb, 0x8b, 0x53, 0x9a, 0x8d, 0x11);
static void skl_int3472_enable_clk(struct int3472_clock *clk, int enable)
{
struct int3472_discrete_device *int3472 = to_int3472_device(clk);
union acpi_object args[3];
union acpi_object argv4;
if (clk->ena_gpio) {
gpiod_set_value_cansleep(clk->ena_gpio, enable);
return;
}
args[0].integer.type = ACPI_TYPE_INTEGER;
args[0].integer.value = clk->imgclk_index;
args[1].integer.type = ACPI_TYPE_INTEGER;
args[1].integer.value = enable;
args[2].integer.type = ACPI_TYPE_INTEGER;
args[2].integer.value = 1;
argv4.type = ACPI_TYPE_PACKAGE;
argv4.package.count = 3;
argv4.package.elements = args;
acpi_evaluate_dsm(acpi_device_handle(int3472->adev), &img_clk_guid,
0, 1, &argv4);
}
/*
* The regulators have to have .ops to be valid, but the only ops we actually
* support are .enable and .disable which are handled via .ena_gpiod. Pass an
@ -20,17 +56,13 @@ static const struct regulator_ops int3472_gpio_regulator_ops;
static int skl_int3472_clk_prepare(struct clk_hw *hw)
{
struct int3472_gpio_clock *clk = to_int3472_clk(hw);
gpiod_set_value_cansleep(clk->ena_gpio, 1);
skl_int3472_enable_clk(to_int3472_clk(hw), 1);
return 0;
}
static void skl_int3472_clk_unprepare(struct clk_hw *hw)
{
struct int3472_gpio_clock *clk = to_int3472_clk(hw);
gpiod_set_value_cansleep(clk->ena_gpio, 0);
skl_int3472_enable_clk(to_int3472_clk(hw), 0);
}
static int skl_int3472_clk_enable(struct clk_hw *hw)
@ -73,7 +105,7 @@ static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device
static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct int3472_gpio_clock *clk = to_int3472_clk(hw);
struct int3472_clock *clk = to_int3472_clk(hw);
return clk->frequency;
}
@ -86,8 +118,51 @@ static const struct clk_ops skl_int3472_clock_ops = {
.recalc_rate = skl_int3472_clk_recalc_rate,
};
int skl_int3472_register_clock(struct int3472_discrete_device *int3472,
struct acpi_resource_gpio *agpio, u32 polarity)
int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472)
{
struct acpi_device *adev = int3472->adev;
struct clk_init_data init = {
.ops = &skl_int3472_clock_ops,
.flags = CLK_GET_RATE_NOCACHE,
};
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;
int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472);
int3472->clock.clk_hw.init = &init;
int3472->clock.clk = clk_register(&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;
}
int skl_int3472_register_gpio_clock(struct int3472_discrete_device *int3472,
struct acpi_resource_gpio *agpio, u32 polarity)
{
char *path = agpio->resource_source.string_ptr;
struct clk_init_data init = {
@ -160,32 +235,73 @@ void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472)
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.
*/
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 acpi_resource_gpio *agpio)
{
const struct int3472_sensor_config *sensor_config;
char *path = agpio->resource_source.string_ptr;
struct regulator_consumer_supply supply_map;
struct regulator_init_data init_data = { };
struct regulator_config cfg = { };
int ret;
const char *second_sensor = NULL;
const struct dmi_system_id *id;
int i, j, ret;
sensor_config = int3472->sensor_config;
if (IS_ERR(sensor_config)) {
dev_err(int3472->dev, "No sensor module config\n");
return PTR_ERR(sensor_config);
}
id = dmi_first_match(skl_int3472_regulator_second_sensor);
if (id)
second_sensor = id->driver_data;
if (!sensor_config->supply_map.supply) {
dev_err(int3472->dev, "No supply name defined\n");
return -ENODEV;
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;
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;
j++;
}
}
init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS;
init_data.num_consumer_supplies = 1;
supply_map = sensor_config->supply_map;
supply_map.dev_name = int3472->sensor_name;
init_data.consumer_supplies = &supply_map;
init_data.consumer_supplies = int3472->regulator.supply_map;
init_data.num_consumer_supplies = j;
snprintf(int3472->regulator.regulator_name,
sizeof(int3472->regulator.regulator_name), "%s-regulator",

View File

@ -28,6 +28,7 @@
#define GPIO_REGULATOR_NAME_LENGTH 21
#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9
#define GPIO_REGULATOR_SUPPLY_MAP_COUNT 2
#define INT3472_LED_MAX_NAME_LEN 32
@ -43,7 +44,7 @@
}
#define to_int3472_clk(hw) \
container_of(hw, struct int3472_gpio_clock, clk_hw)
container_of(hw, struct int3472_clock, clk_hw)
#define to_int3472_device(clk) \
container_of(clk, struct int3472_discrete_device, clock)
@ -64,18 +65,9 @@ struct int3472_cldb {
u8 control_logic_type;
u8 control_logic_id;
u8 sensor_card_sku;
u8 reserved[28];
};
struct int3472_gpio_function_remap {
const char *documented;
const char *actual;
};
struct int3472_sensor_config {
const char *sensor_module_name;
struct regulator_consumer_supply supply_map;
const struct int3472_gpio_function_remap *function_maps;
u8 reserved[10];
u8 clock_source;
u8 reserved2[17];
};
struct int3472_discrete_device {
@ -87,6 +79,8 @@ struct int3472_discrete_device {
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;
@ -94,12 +88,13 @@ struct int3472_discrete_device {
struct regulator_desc rdesc;
} regulator;
struct int3472_gpio_clock {
struct int3472_clock {
struct clk *clk;
struct clk_hw clk_hw;
struct clk_lookup *cl;
struct gpio_desc *ena_gpio;
u32 frequency;
u8 imgclk_index;
} clock;
struct int3472_pled {
@ -121,8 +116,9 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev,
struct acpi_device **sensor_adev_ret,
const char **name_ret);
int skl_int3472_register_clock(struct int3472_discrete_device *int3472,
struct acpi_resource_gpio *agpio, u32 polarity);
int skl_int3472_register_gpio_clock(struct int3472_discrete_device *int3472,
struct acpi_resource_gpio *agpio, u32 polarity);
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,

View File

@ -2,6 +2,7 @@
/* Author: Dan Scally <djrscally@gmail.com> */
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
@ -25,6 +26,10 @@ static const guid_t int3472_gpio_guid =
GUID_INIT(0x79234640, 0x9e10, 0x4fea,
0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f);
#define INT3472_GPIO_DSM_TYPE GENMASK(7, 0)
#define INT3472_GPIO_DSM_PIN GENMASK(15, 8)
#define INT3472_GPIO_DSM_SENSOR_ON_VAL GENMASK(31, 24)
/*
* 822ace8f-2814-4174-a56b-5f029fe079ee
* This _DSM GUID returns a string from the sensor device, which acts as a
@ -34,69 +39,23 @@ static const guid_t cio2_sensor_module_guid =
GUID_INIT(0x822ace8f, 0x2814, 0x4174,
0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee);
/*
* Here follows platform specific mapping information that we can pass to
* the functions mapping resources to the sensors. Where the sensors have
* a power enable pin defined in DSDT we need to provide a supply name so
* the sensor drivers can find the regulator. The device name will be derived
* from the sensor's ACPI device within the code. Optionally, we can provide a
* NULL terminated array of function name mappings to deal with any platform
* specific deviations from the documented behaviour of GPIOs.
*
* Map a GPIO function name to NULL to prevent the driver from mapping that
* GPIO at all.
*/
static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = {
{ "reset", NULL },
{ "powerdown", "reset" },
{ }
};
static const struct int3472_sensor_config int3472_sensor_configs[] = {
/* Lenovo Miix 510-12ISK - OV2680, Front */
{ "GNDF140809R", { 0 }, ov2680_gpio_function_remaps },
/* Lenovo Miix 510-12ISK - OV5648, Rear */
{ "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL },
/* Surface Go 1&2 - OV5693, Front */
{ "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL },
};
static const struct int3472_sensor_config *
skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472)
static void skl_int3472_log_sensor_module_name(struct int3472_discrete_device *int3472)
{
union acpi_object *obj;
unsigned int i;
obj = acpi_evaluate_dsm_typed(int3472->sensor->handle,
&cio2_sensor_module_guid, 0x00,
0x01, NULL, ACPI_TYPE_STRING);
if (!obj) {
dev_err(int3472->dev,
"Failed to get sensor module string from _DSM\n");
return ERR_PTR(-ENODEV);
if (obj) {
dev_dbg(int3472->dev, "Sensor module id: '%s'\n", obj->string.pointer);
ACPI_FREE(obj);
}
for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) {
if (!strcmp(int3472_sensor_configs[i].sensor_module_name,
obj->string.pointer))
break;
}
ACPI_FREE(obj);
if (i >= ARRAY_SIZE(int3472_sensor_configs))
return ERR_PTR(-EINVAL);
return &int3472_sensor_configs[i];
}
static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472,
struct acpi_resource_gpio *agpio,
const char *func, u32 polarity)
{
const struct int3472_sensor_config *sensor_config;
char *path = agpio->resource_source.string_ptr;
struct gpiod_lookup *table_entry;
struct acpi_device *adev;
@ -108,22 +67,6 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347
return -EINVAL;
}
sensor_config = int3472->sensor_config;
if (!IS_ERR(sensor_config) && sensor_config->function_maps) {
const struct int3472_gpio_function_remap *remap;
for (remap = sensor_config->function_maps; remap->documented; remap++) {
if (!strcmp(func, remap->documented)) {
func = remap->actual;
break;
}
}
}
/* Functions mapped to NULL should not be mapped to the sensor */
if (!func)
return 0;
status = acpi_get_handle(NULL, path, &handle);
if (ACPI_FAILURE(status))
return -EINVAL;
@ -211,8 +154,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
{
struct int3472_discrete_device *int3472 = data;
struct acpi_resource_gpio *agpio;
u8 active_value, pin, type;
union acpi_object *obj;
u8 active_value, type;
const char *err_msg;
const char *func;
u32 polarity;
@ -236,12 +179,17 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
return 1;
}
type = obj->integer.value & 0xff;
type = FIELD_GET(INT3472_GPIO_DSM_TYPE, obj->integer.value);
int3472_get_func_and_polarity(type, &func, &polarity);
/* If bits 31-24 of the _DSM entry are all 0 then the signal is inverted */
active_value = obj->integer.value >> 24;
pin = FIELD_GET(INT3472_GPIO_DSM_PIN, obj->integer.value);
if (pin != agpio->pin_table[0])
dev_warn(int3472->dev, "%s %s pin number mismatch _DSM %d resource %d\n",
func, agpio->resource_source.string_ptr, pin,
agpio->pin_table[0]);
active_value = FIELD_GET(INT3472_GPIO_DSM_SENSOR_ON_VAL, obj->integer.value);
if (!active_value)
polarity ^= GPIO_ACTIVE_LOW;
@ -258,7 +206,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
break;
case INT3472_GPIO_TYPE_CLK_ENABLE:
ret = skl_int3472_register_clock(int3472, agpio, polarity);
ret = skl_int3472_register_gpio_clock(int3472, agpio, polarity);
if (ret)
err_msg = "Failed to register clock\n";
@ -297,11 +245,7 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472)
LIST_HEAD(resource_list);
int ret;
/*
* No error check, because not having a sensor config is not necessarily
* a failure mode.
*/
int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472);
skl_int3472_log_sensor_module_name(int3472);
ret = acpi_dev_get_resources(int3472->adev, &resource_list,
skl_int3472_handle_gpio_resources,
@ -311,6 +255,11 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472)
acpi_dev_free_resource_list(&resource_list);
/* Register _DSM based clock (no-op if a GPIO clock was already registered) */
ret = skl_int3472_register_dsm_clock(int3472);
if (ret < 0)
return ret;
int3472->gpios.dev_id = int3472->sensor_name;
gpiod_add_lookup_table(&int3472->gpios);
@ -356,6 +305,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev)
int3472->adev = adev;
int3472->dev = &pdev->dev;
platform_set_drvdata(pdev, int3472);
int3472->clock.imgclk_index = cldb.clock_source;
ret = skl_int3472_get_sensor_adev_and_name(&pdev->dev, &int3472->sensor,
&int3472->sensor_name);

View File

@ -250,7 +250,7 @@ static struct i2c_driver int3472_tps68470 = {
.name = "int3472-tps68470",
.acpi_match_table = int3472_device_id,
},
.probe_new = skl_int3472_tps68470_probe,
.probe = skl_int3472_tps68470_probe,
.remove = skl_int3472_tps68470_remove,
};
module_i2c_driver(int3472_tps68470);

View File

@ -3,8 +3,8 @@
# Intel x86 Platform-Specific Drivers
#
intel_pmc_core-y := core.o spt.o cnp.o icl.o tgl.o \
adl.o mtl.o
intel_pmc_core-y := core.o core_ssram.o spt.o cnp.o \
icl.o tgl.o adl.o mtl.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

View File

@ -309,17 +309,21 @@ const struct pmc_reg_map adl_reg_map = {
.lpm_live_status_offset = ADL_LPM_LIVE_STATUS_OFFSET,
};
void adl_core_configure(struct pmc_dev *pmcdev)
int adl_core_init(struct pmc_dev *pmcdev)
{
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
int ret;
pmc->map = &adl_reg_map;
ret = get_primary_reg_base(pmc);
if (ret)
return ret;
/* Due to a hardware limitation, the GBE LTR blocks PC10
* when a cable is attached. Tell the PMC to ignore it.
*/
dev_dbg(&pmcdev->pdev->dev, "ignoring GBE LTR\n");
pmc_core_send_ltr_ignore(pmcdev, 3);
}
void adl_core_init(struct pmc_dev *pmcdev)
{
pmcdev->map = &adl_reg_map;
pmcdev->core_configure = adl_core_configure;
return 0;
}

View File

@ -204,7 +204,21 @@ const struct pmc_reg_map cnp_reg_map = {
.etr3_offset = ETR3_OFFSET,
};
void cnp_core_init(struct pmc_dev *pmcdev)
int cnp_core_init(struct pmc_dev *pmcdev)
{
pmcdev->map = &cnp_reg_map;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
int ret;
pmc->map = &cnp_reg_map;
ret = get_primary_reg_base(pmc);
if (ret)
return ret;
/* Due to a hardware limitation, the GBE LTR blocks PC10
* when a cable is attached. Tell the PMC to ignore it.
*/
dev_dbg(&pmcdev->pdev->dev, "ignoring GBE LTR\n");
pmc_core_send_ltr_ignore(pmcdev, 3);
return 0;
}

View File

@ -53,18 +53,18 @@ const struct pmc_bit_map msr_map[] = {
{}
};
static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset)
static inline u32 pmc_core_reg_read(struct pmc *pmc, int reg_offset)
{
return readl(pmcdev->regbase + reg_offset);
return readl(pmc->regbase + reg_offset);
}
static inline void pmc_core_reg_write(struct pmc_dev *pmcdev, int reg_offset,
static inline void pmc_core_reg_write(struct pmc *pmc, int reg_offset,
u32 val)
{
writel(val, pmcdev->regbase + reg_offset);
writel(val, pmc->regbase + reg_offset);
}
static inline u64 pmc_core_adjust_slp_s0_step(struct pmc_dev *pmcdev, u32 value)
static inline u64 pmc_core_adjust_slp_s0_step(struct pmc *pmc, u32 value)
{
/*
* ADL PCH does not have the SLP_S0 counter and LPM Residency counters are
@ -72,17 +72,18 @@ static inline u64 pmc_core_adjust_slp_s0_step(struct pmc_dev *pmcdev, u32 value)
* programs have the legacy SLP_S0 residency counter that is using the 122
* usec tick.
*/
const int lpm_adj_x2 = pmcdev->map->lpm_res_counter_step_x2;
const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
if (pmcdev->map == &adl_reg_map)
if (pmc->map == &adl_reg_map)
return (u64)value * GET_X2_COUNTER((u64)lpm_adj_x2);
else
return (u64)value * pmcdev->map->slp_s0_res_counter_step;
return (u64)value * pmc->map->slp_s0_res_counter_step;
}
static int set_etr3(struct pmc_dev *pmcdev)
{
const struct pmc_reg_map *map = pmcdev->map;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_reg_map *map = pmc->map;
u32 reg;
int err;
@ -92,7 +93,7 @@ static int set_etr3(struct pmc_dev *pmcdev)
mutex_lock(&pmcdev->lock);
/* check if CF9 is locked */
reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
reg = pmc_core_reg_read(pmc, map->etr3_offset);
if (reg & ETR3_CF9LOCK) {
err = -EACCES;
goto out_unlock;
@ -100,9 +101,9 @@ static int set_etr3(struct pmc_dev *pmcdev)
/* write CF9 global reset bit */
reg |= ETR3_CF9GR;
pmc_core_reg_write(pmcdev, map->etr3_offset, reg);
pmc_core_reg_write(pmc, map->etr3_offset, reg);
reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
reg = pmc_core_reg_read(pmc, map->etr3_offset);
if (!(reg & ETR3_CF9GR)) {
err = -EIO;
goto out_unlock;
@ -120,11 +121,12 @@ static umode_t etr3_is_visible(struct kobject *kobj,
{
struct device *dev = kobj_to_dev(kobj);
struct pmc_dev *pmcdev = dev_get_drvdata(dev);
const struct pmc_reg_map *map = pmcdev->map;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_reg_map *map = pmc->map;
u32 reg;
mutex_lock(&pmcdev->lock);
reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
reg = pmc_core_reg_read(pmc, map->etr3_offset);
mutex_unlock(&pmcdev->lock);
return reg & ETR3_CF9LOCK ? attr->mode & (SYSFS_PREALLOC | 0444) : attr->mode;
@ -134,7 +136,8 @@ static ssize_t etr3_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pmc_dev *pmcdev = dev_get_drvdata(dev);
const struct pmc_reg_map *map = pmcdev->map;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_reg_map *map = pmc->map;
u32 reg;
if (!map->etr3_offset)
@ -142,7 +145,7 @@ static ssize_t etr3_show(struct device *dev,
mutex_lock(&pmcdev->lock);
reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
reg = pmc_core_reg_read(pmc, map->etr3_offset);
reg &= ETR3_CF9GR | ETR3_CF9LOCK;
mutex_unlock(&pmcdev->lock);
@ -191,37 +194,37 @@ static const struct attribute_group *pmc_dev_groups[] = {
static int pmc_core_dev_state_get(void *data, u64 *val)
{
struct pmc_dev *pmcdev = data;
const struct pmc_reg_map *map = pmcdev->map;
struct pmc *pmc = data;
const struct pmc_reg_map *map = pmc->map;
u32 value;
value = pmc_core_reg_read(pmcdev, map->slp_s0_offset);
*val = pmc_core_adjust_slp_s0_step(pmcdev, value);
value = pmc_core_reg_read(pmc, map->slp_s0_offset);
*val = pmc_core_adjust_slp_s0_step(pmc, value);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_dev_state, pmc_core_dev_state_get, NULL, "%llu\n");
static int pmc_core_check_read_lock_bit(struct pmc_dev *pmcdev)
static int pmc_core_check_read_lock_bit(struct pmc *pmc)
{
u32 value;
value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_cfg_offset);
return value & BIT(pmcdev->map->pm_read_disable_bit);
value = pmc_core_reg_read(pmc, pmc->map->pm_cfg_offset);
return value & BIT(pmc->map->pm_read_disable_bit);
}
static void pmc_core_slps0_display(struct pmc_dev *pmcdev, struct device *dev,
static void pmc_core_slps0_display(struct pmc *pmc, struct device *dev,
struct seq_file *s)
{
const struct pmc_bit_map **maps = pmcdev->map->slps0_dbg_maps;
const struct pmc_bit_map **maps = pmc->map->slps0_dbg_maps;
const struct pmc_bit_map *map;
int offset = pmcdev->map->slps0_dbg_offset;
int offset = pmc->map->slps0_dbg_offset;
u32 data;
while (*maps) {
map = *maps;
data = pmc_core_reg_read(pmcdev, offset);
data = pmc_core_reg_read(pmc, offset);
offset += 4;
while (map->name) {
if (dev)
@ -248,8 +251,8 @@ static int pmc_core_lpm_get_arr_size(const struct pmc_bit_map **maps)
return idx;
}
static void pmc_core_lpm_display(struct pmc_dev *pmcdev, struct device *dev,
struct seq_file *s, u32 offset,
static void pmc_core_lpm_display(struct pmc *pmc, struct device *dev,
struct seq_file *s, u32 offset, int pmc_index,
const char *str,
const struct pmc_bit_map **maps)
{
@ -262,25 +265,25 @@ static void pmc_core_lpm_display(struct pmc_dev *pmcdev, struct device *dev,
return;
for (index = 0; index < arr_size; index++) {
lpm_regs[index] = pmc_core_reg_read(pmcdev, offset);
lpm_regs[index] = pmc_core_reg_read(pmc, offset);
offset += 4;
}
for (idx = 0; idx < arr_size; idx++) {
if (dev)
dev_info(dev, "\nLPM_%s_%d:\t0x%x\n", str, idx,
dev_info(dev, "\nPMC%d:LPM_%s_%d:\t0x%x\n", pmc_index, str, idx,
lpm_regs[idx]);
if (s)
seq_printf(s, "\nLPM_%s_%d:\t0x%x\n", str, idx,
seq_printf(s, "\nPMC%d:LPM_%s_%d:\t0x%x\n", pmc_index, str, idx,
lpm_regs[idx]);
for (index = 0; maps[idx][index].name && index < len; index++) {
bit_mask = maps[idx][index].bit_mask;
if (dev)
dev_info(dev, "%-30s %-30d\n",
dev_info(dev, "PMC%d:%-30s %-30d\n", pmc_index,
maps[idx][index].name,
lpm_regs[idx] & bit_mask ? 1 : 0);
if (s)
seq_printf(s, "%-30s %-30d\n",
seq_printf(s, "PMC%d:%-30s %-30d\n", pmc_index,
maps[idx][index].name,
lpm_regs[idx] & bit_mask ? 1 : 0);
}
@ -291,37 +294,46 @@ static void pmc_core_lpm_display(struct pmc_dev *pmcdev, struct device *dev,
static bool slps0_dbg_latch;
static inline u8 pmc_core_reg_read_byte(struct pmc_dev *pmcdev, int offset)
static inline u8 pmc_core_reg_read_byte(struct pmc *pmc, int offset)
{
return readb(pmcdev->regbase + offset);
return readb(pmc->regbase + offset);
}
static void pmc_core_display_map(struct seq_file *s, int index, int idx, int ip,
u8 pf_reg, const struct pmc_bit_map **pf_map)
int pmc_index, u8 pf_reg, const struct pmc_bit_map **pf_map)
{
seq_printf(s, "PCH IP: %-2d - %-32s\tState: %s\n",
ip, pf_map[idx][index].name,
seq_printf(s, "PMC%d:PCH IP: %-2d - %-32s\tState: %s\n",
pmc_index, ip, pf_map[idx][index].name,
pf_map[idx][index].bit_mask & pf_reg ? "Off" : "On");
}
static int pmc_core_ppfear_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map **maps = pmcdev->map->pfear_sts;
u8 pf_regs[PPFEAR_MAX_NUM_ENTRIES];
int index, iter, idx, ip = 0;
int i;
iter = pmcdev->map->ppfear0_offset;
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
struct pmc *pmc = pmcdev->pmcs[i];
const struct pmc_bit_map **maps;
u8 pf_regs[PPFEAR_MAX_NUM_ENTRIES];
int index, iter, idx, ip = 0;
for (index = 0; index < pmcdev->map->ppfear_buckets &&
index < PPFEAR_MAX_NUM_ENTRIES; index++, iter++)
pf_regs[index] = pmc_core_reg_read_byte(pmcdev, iter);
if (!pmc)
continue;
for (idx = 0; maps[idx]; idx++) {
for (index = 0; maps[idx][index].name &&
index < pmcdev->map->ppfear_buckets * 8; ip++, index++)
pmc_core_display_map(s, index, idx, ip,
pf_regs[index / 8], maps);
maps = pmc->map->pfear_sts;
iter = pmc->map->ppfear0_offset;
for (index = 0; index < pmc->map->ppfear_buckets &&
index < PPFEAR_MAX_NUM_ENTRIES; index++, iter++)
pf_regs[index] = pmc_core_reg_read_byte(pmc, iter);
for (idx = 0; maps[idx]; idx++) {
for (index = 0; maps[idx][index].name &&
index < pmc->map->ppfear_buckets * 8; ip++, index++)
pmc_core_display_map(s, index, idx, ip, i,
pf_regs[index / 8], maps);
}
}
return 0;
@ -329,37 +341,38 @@ static int pmc_core_ppfear_show(struct seq_file *s, void *unused)
DEFINE_SHOW_ATTRIBUTE(pmc_core_ppfear);
/* This function should return link status, 0 means ready */
static int pmc_core_mtpmc_link_status(struct pmc_dev *pmcdev)
static int pmc_core_mtpmc_link_status(struct pmc *pmc)
{
u32 value;
value = pmc_core_reg_read(pmcdev, SPT_PMC_PM_STS_OFFSET);
value = pmc_core_reg_read(pmc, SPT_PMC_PM_STS_OFFSET);
return value & BIT(SPT_PMC_MSG_FULL_STS_BIT);
}
static int pmc_core_send_msg(struct pmc_dev *pmcdev, u32 *addr_xram)
static int pmc_core_send_msg(struct pmc *pmc, u32 *addr_xram)
{
u32 dest;
int timeout;
for (timeout = NUM_RETRIES; timeout > 0; timeout--) {
if (pmc_core_mtpmc_link_status(pmcdev) == 0)
if (pmc_core_mtpmc_link_status(pmc) == 0)
break;
msleep(5);
}
if (timeout <= 0 && pmc_core_mtpmc_link_status(pmcdev))
if (timeout <= 0 && pmc_core_mtpmc_link_status(pmc))
return -EBUSY;
dest = (*addr_xram & MTPMC_MASK) | (1U << 1);
pmc_core_reg_write(pmcdev, SPT_PMC_MTPMC_OFFSET, dest);
pmc_core_reg_write(pmc, SPT_PMC_MTPMC_OFFSET, dest);
return 0;
}
static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map *map = pmcdev->map->mphy_sts;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_bit_map *map = pmc->map->mphy_sts;
u32 mphy_core_reg_low, mphy_core_reg_high;
u32 val_low, val_high;
int index, err = 0;
@ -374,21 +387,21 @@ static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused)
mutex_lock(&pmcdev->lock);
if (pmc_core_send_msg(pmcdev, &mphy_core_reg_low) != 0) {
if (pmc_core_send_msg(pmc, &mphy_core_reg_low) != 0) {
err = -EBUSY;
goto out_unlock;
}
msleep(10);
val_low = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET);
val_low = pmc_core_reg_read(pmc, SPT_PMC_MFPMC_OFFSET);
if (pmc_core_send_msg(pmcdev, &mphy_core_reg_high) != 0) {
if (pmc_core_send_msg(pmc, &mphy_core_reg_high) != 0) {
err = -EBUSY;
goto out_unlock;
}
msleep(10);
val_high = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET);
val_high = pmc_core_reg_read(pmc, SPT_PMC_MFPMC_OFFSET);
for (index = 0; index < 8 && map[index].name; index++) {
seq_printf(s, "%-32s\tState: %s\n",
@ -413,7 +426,8 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_mphy_pg);
static int pmc_core_pll_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map *map = pmcdev->map->pll_sts;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_bit_map *map = pmc->map->pll_sts;
u32 mphy_common_reg, val;
int index, err = 0;
@ -425,14 +439,14 @@ static int pmc_core_pll_show(struct seq_file *s, void *unused)
mphy_common_reg = (SPT_PMC_MPHY_COM_STS_0 << 16);
mutex_lock(&pmcdev->lock);
if (pmc_core_send_msg(pmcdev, &mphy_common_reg) != 0) {
if (pmc_core_send_msg(pmc, &mphy_common_reg) != 0) {
err = -EBUSY;
goto out_unlock;
}
/* Observed PMC HW response latency for MTPMC-MFPMC is ~10 ms */
msleep(10);
val = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET);
val = pmc_core_reg_read(pmc, SPT_PMC_MFPMC_OFFSET);
for (index = 0; map[index].name ; index++) {
seq_printf(s, "%-32s\tState: %s\n",
@ -448,25 +462,48 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_pll);
int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value)
{
const struct pmc_reg_map *map = pmcdev->map;
struct pmc *pmc;
const struct pmc_reg_map *map;
u32 reg;
int err = 0;
int pmc_index, ltr_index;
ltr_index = value;
/* For platforms with multiple pmcs, ltr index value given by user
* is based on the contiguous indexes from ltr_show output.
* pmc index and ltr index needs to be calculated from it.
*/
for (pmc_index = 0; pmc_index < ARRAY_SIZE(pmcdev->pmcs) && ltr_index > 0; pmc_index++) {
pmc = pmcdev->pmcs[pmc_index];
if (!pmc)
continue;
map = pmc->map;
if (ltr_index <= map->ltr_ignore_max)
break;
/* Along with IP names, ltr_show map includes CURRENT_PLATFORM
* and AGGREGATED_SYSTEM values per PMC. Take these two index
* values into account in ltr_index calculation. Also, to start
* ltr index from zero for next pmc, subtract it by 1.
*/
ltr_index = ltr_index - (map->ltr_ignore_max + 2) - 1;
}
if (pmc_index >= ARRAY_SIZE(pmcdev->pmcs) || ltr_index < 0)
return -EINVAL;
pr_debug("ltr_ignore for pmc%d: ltr_index:%d\n", pmc_index, ltr_index);
mutex_lock(&pmcdev->lock);
if (value > map->ltr_ignore_max) {
err = -EINVAL;
goto out_unlock;
}
reg = pmc_core_reg_read(pmc, map->ltr_ignore_offset);
reg |= BIT(ltr_index);
pmc_core_reg_write(pmc, map->ltr_ignore_offset, reg);
reg = pmc_core_reg_read(pmcdev, map->ltr_ignore_offset);
reg |= BIT(value);
pmc_core_reg_write(pmcdev, map->ltr_ignore_offset, reg);
out_unlock:
mutex_unlock(&pmcdev->lock);
return err;
return 0;
}
static ssize_t pmc_core_ltr_ignore_write(struct file *file,
@ -509,7 +546,8 @@ static const struct file_operations pmc_core_ltr_ignore_ops = {
static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset)
{
const struct pmc_reg_map *map = pmcdev->map;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_reg_map *map = pmc->map;
u32 fd;
mutex_lock(&pmcdev->lock);
@ -517,12 +555,12 @@ static void pmc_core_slps0_dbg_latch(struct pmc_dev *pmcdev, bool reset)
if (!reset && !slps0_dbg_latch)
goto out_unlock;
fd = pmc_core_reg_read(pmcdev, map->slps0_dbg_offset);
fd = pmc_core_reg_read(pmc, map->slps0_dbg_offset);
if (reset)
fd &= ~CNP_PMC_LATCH_SLPS0_EVENTS;
else
fd |= CNP_PMC_LATCH_SLPS0_EVENTS;
pmc_core_reg_write(pmcdev, map->slps0_dbg_offset, fd);
pmc_core_reg_write(pmc, map->slps0_dbg_offset, fd);
slps0_dbg_latch = false;
@ -535,7 +573,7 @@ static int pmc_core_slps0_dbg_show(struct seq_file *s, void *unused)
struct pmc_dev *pmcdev = s->private;
pmc_core_slps0_dbg_latch(pmcdev, false);
pmc_core_slps0_display(pmcdev, NULL, s);
pmc_core_slps0_display(pmcdev->pmcs[PMC_IDX_MAIN], NULL, s);
pmc_core_slps0_dbg_latch(pmcdev, true);
return 0;
@ -579,44 +617,52 @@ static u32 convert_ltr_scale(u32 val)
static int pmc_core_ltr_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map *map = pmcdev->map->ltr_show_sts;
u64 decoded_snoop_ltr, decoded_non_snoop_ltr;
u32 ltr_raw_data, scale, val;
u16 snoop_ltr, nonsnoop_ltr;
int index;
int i, index, ltr_index = 0;
for (index = 0; map[index].name ; index++) {
decoded_snoop_ltr = decoded_non_snoop_ltr = 0;
ltr_raw_data = pmc_core_reg_read(pmcdev,
map[index].bit_mask);
snoop_ltr = ltr_raw_data & ~MTPMC_MASK;
nonsnoop_ltr = (ltr_raw_data >> 0x10) & ~MTPMC_MASK;
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
struct pmc *pmc = pmcdev->pmcs[i];
const struct pmc_bit_map *map;
if (FIELD_GET(LTR_REQ_NONSNOOP, ltr_raw_data)) {
scale = FIELD_GET(LTR_DECODED_SCALE, nonsnoop_ltr);
val = FIELD_GET(LTR_DECODED_VAL, nonsnoop_ltr);
decoded_non_snoop_ltr = val * convert_ltr_scale(scale);
if (!pmc)
continue;
map = pmc->map->ltr_show_sts;
for (index = 0; map[index].name; index++) {
decoded_snoop_ltr = decoded_non_snoop_ltr = 0;
ltr_raw_data = pmc_core_reg_read(pmc,
map[index].bit_mask);
snoop_ltr = ltr_raw_data & ~MTPMC_MASK;
nonsnoop_ltr = (ltr_raw_data >> 0x10) & ~MTPMC_MASK;
if (FIELD_GET(LTR_REQ_NONSNOOP, ltr_raw_data)) {
scale = FIELD_GET(LTR_DECODED_SCALE, nonsnoop_ltr);
val = FIELD_GET(LTR_DECODED_VAL, nonsnoop_ltr);
decoded_non_snoop_ltr = val * convert_ltr_scale(scale);
}
if (FIELD_GET(LTR_REQ_SNOOP, ltr_raw_data)) {
scale = FIELD_GET(LTR_DECODED_SCALE, snoop_ltr);
val = FIELD_GET(LTR_DECODED_VAL, snoop_ltr);
decoded_snoop_ltr = val * convert_ltr_scale(scale);
}
seq_printf(s, "%d\tPMC%d:%-32s\tLTR: RAW: 0x%-16x\tNon-Snoop(ns): %-16llu\tSnoop(ns): %-16llu\n",
ltr_index, i, map[index].name, ltr_raw_data,
decoded_non_snoop_ltr,
decoded_snoop_ltr);
ltr_index++;
}
if (FIELD_GET(LTR_REQ_SNOOP, ltr_raw_data)) {
scale = FIELD_GET(LTR_DECODED_SCALE, snoop_ltr);
val = FIELD_GET(LTR_DECODED_VAL, snoop_ltr);
decoded_snoop_ltr = val * convert_ltr_scale(scale);
}
seq_printf(s, "%-32s\tLTR: RAW: 0x%-16x\tNon-Snoop(ns): %-16llu\tSnoop(ns): %-16llu\n",
map[index].name, ltr_raw_data,
decoded_non_snoop_ltr,
decoded_snoop_ltr);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_ltr);
static inline u64 adjust_lpm_residency(struct pmc_dev *pmcdev, u32 offset,
static inline u64 adjust_lpm_residency(struct pmc *pmc, u32 offset,
const int lpm_adj_x2)
{
u64 lpm_res = pmc_core_reg_read(pmcdev, offset);
u64 lpm_res = pmc_core_reg_read(pmc, offset);
return GET_X2_COUNTER((u64)lpm_adj_x2 * lpm_res);
}
@ -624,15 +670,16 @@ static inline u64 adjust_lpm_residency(struct pmc_dev *pmcdev, u32 offset,
static int pmc_core_substate_res_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const int lpm_adj_x2 = pmcdev->map->lpm_res_counter_step_x2;
u32 offset = pmcdev->map->lpm_residency_offset;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const int lpm_adj_x2 = pmc->map->lpm_res_counter_step_x2;
u32 offset = pmc->map->lpm_residency_offset;
int i, mode;
seq_printf(s, "%-10s %-15s\n", "Substate", "Residency");
pmc_for_each_mode(i, mode, pmcdev) {
seq_printf(s, "%-10s %-15llu\n", pmc_lpm_modes[mode],
adjust_lpm_residency(pmcdev, offset + (4 * mode), lpm_adj_x2));
adjust_lpm_residency(pmc, offset + (4 * mode), lpm_adj_x2));
}
return 0;
@ -642,10 +689,19 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_res);
static int pmc_core_substate_sts_regs_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map **maps = pmcdev->map->lpm_sts;
u32 offset = pmcdev->map->lpm_status_offset;
int i;
pmc_core_lpm_display(pmcdev, NULL, s, offset, "STATUS", maps);
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
struct pmc *pmc = pmcdev->pmcs[i];
const struct pmc_bit_map **maps;
u32 offset;
if (!pmc)
continue;
maps = pmc->map->lpm_sts;
offset = pmc->map->lpm_status_offset;
pmc_core_lpm_display(pmc, NULL, s, offset, i, "STATUS", maps);
}
return 0;
}
@ -654,10 +710,19 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_sts_regs);
static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map **maps = pmcdev->map->lpm_sts;
u32 offset = pmcdev->map->lpm_live_status_offset;
int i;
pmc_core_lpm_display(pmcdev, NULL, s, offset, "LIVE_STATUS", maps);
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
struct pmc *pmc = pmcdev->pmcs[i];
const struct pmc_bit_map **maps;
u32 offset;
if (!pmc)
continue;
maps = pmc->map->lpm_sts;
offset = pmc->map->lpm_live_status_offset;
pmc_core_lpm_display(pmc, NULL, s, offset, i, "LIVE_STATUS", maps);
}
return 0;
}
@ -678,11 +743,12 @@ static void pmc_core_substate_req_header_show(struct seq_file *s)
static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map **maps = pmcdev->map->lpm_sts;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_bit_map **maps = pmc->map->lpm_sts;
const struct pmc_bit_map *map;
const int num_maps = pmcdev->map->lpm_num_maps;
u32 sts_offset = pmcdev->map->lpm_status_offset;
u32 *lpm_req_regs = pmcdev->lpm_req_regs;
const int num_maps = pmc->map->lpm_num_maps;
u32 sts_offset = pmc->map->lpm_status_offset;
u32 *lpm_req_regs = pmc->lpm_req_regs;
int mp;
/* Display the header */
@ -703,7 +769,7 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused)
req_mask |= lpm_req_regs[mp + (mode * num_maps)];
/* Get the last latched status for this map */
lpm_status = pmc_core_reg_read(pmcdev, sts_offset + (mp * 4));
lpm_status = pmc_core_reg_read(pmc, sts_offset + (mp * 4));
/* Loop over elements in this map */
map = maps[mp];
@ -746,11 +812,12 @@ DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs);
static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
bool c10;
u32 reg;
int idx, mode;
reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset);
reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset);
if (reg & LPM_STS_LATCH_MODE) {
seq_puts(s, "c10");
c10 = false;
@ -777,6 +844,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
{
struct seq_file *s = file->private_data;
struct pmc_dev *pmcdev = s->private;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
bool clear = false, c10 = false;
unsigned char buf[8];
int idx, m, mode;
@ -813,9 +881,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
if (clear) {
mutex_lock(&pmcdev->lock);
reg = pmc_core_reg_read(pmcdev, pmcdev->map->etr3_offset);
reg = pmc_core_reg_read(pmc, pmc->map->etr3_offset);
reg |= ETR3_CLEAR_LPM_EVENTS;
pmc_core_reg_write(pmcdev, pmcdev->map->etr3_offset, reg);
pmc_core_reg_write(pmc, pmc->map->etr3_offset, reg);
mutex_unlock(&pmcdev->lock);
@ -825,9 +893,9 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
if (c10) {
mutex_lock(&pmcdev->lock);
reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset);
reg = pmc_core_reg_read(pmc, pmc->map->lpm_sts_latch_en_offset);
reg &= ~LPM_STS_LATCH_MODE;
pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg);
pmc_core_reg_write(pmc, pmc->map->lpm_sts_latch_en_offset, reg);
mutex_unlock(&pmcdev->lock);
@ -840,7 +908,7 @@ static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
*/
reg = LPM_STS_LATCH_MODE | BIT(mode);
mutex_lock(&pmcdev->lock);
pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg);
pmc_core_reg_write(pmc, pmc->map->lpm_sts_latch_en_offset, reg);
mutex_unlock(&pmcdev->lock);
return count;
@ -849,8 +917,8 @@ DEFINE_PMC_CORE_ATTR_WRITE(pmc_core_lpm_latch_mode);
static int pmc_core_pkgc_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
const struct pmc_bit_map *map = pmcdev->map->msr_sts;
struct pmc *pmc = s->private;
const struct pmc_bit_map *map = pmc->map->msr_sts;
u64 pcstate_count;
int index;
@ -901,6 +969,7 @@ static bool pmc_core_pri_verify(u32 lpm_pri, u8 *mode_order)
static void pmc_core_get_low_power_modes(struct platform_device *pdev)
{
struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
u8 pri_order[LPM_MAX_NUM_MODES] = LPM_DEFAULT_PRI;
u8 mode_order[LPM_MAX_NUM_MODES];
u32 lpm_pri;
@ -908,10 +977,10 @@ static void pmc_core_get_low_power_modes(struct platform_device *pdev)
int mode, i, p;
/* Use LPM Maps to indicate support for substates */
if (!pmcdev->map->lpm_num_maps)
if (!pmc->map->lpm_num_maps)
return;
lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset);
lpm_en = pmc_core_reg_read(pmc, pmc->map->lpm_en_offset);
/* For MTL, BIT 31 is not an lpm mode but a enable bit.
* Lower byte is enough to cover the number of lpm modes for all
* platforms and hence mask the upper 3 bytes.
@ -919,7 +988,7 @@ static void pmc_core_get_low_power_modes(struct platform_device *pdev)
pmcdev->num_lpm_modes = hweight32(lpm_en & 0xFF);
/* Read 32 bit LPM_PRI register */
lpm_pri = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_priority_offset);
lpm_pri = pmc_core_reg_read(pmc, pmc->map->lpm_priority_offset);
/*
@ -948,6 +1017,25 @@ static void pmc_core_get_low_power_modes(struct platform_device *pdev)
}
}
int get_primary_reg_base(struct pmc *pmc)
{
u64 slp_s0_addr;
if (lpit_read_residency_count_address(&slp_s0_addr)) {
pmc->base_addr = PMC_BASE_ADDR_DEFAULT;
if (page_is_ram(PHYS_PFN(pmc->base_addr)))
return -ENODEV;
} else {
pmc->base_addr = slp_s0_addr - pmc->map->slp_s0_offset;
}
pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length);
if (!pmc->regbase)
return -ENOMEM;
return 0;
}
static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
{
debugfs_remove_recursive(pmcdev->dbgfs_dir);
@ -955,15 +1043,16 @@ static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
{
struct pmc *primary_pmc = pmcdev->pmcs[PMC_IDX_MAIN];
struct dentry *dir;
dir = debugfs_create_dir("pmc_core", NULL);
pmcdev->dbgfs_dir = dir;
debugfs_create_file("slp_s0_residency_usec", 0444, dir, pmcdev,
debugfs_create_file("slp_s0_residency_usec", 0444, dir, primary_pmc,
&pmc_core_dev_state);
if (pmcdev->map->pfear_sts)
if (primary_pmc->map->pfear_sts)
debugfs_create_file("pch_ip_power_gating_status", 0444, dir,
pmcdev, &pmc_core_ppfear_fops);
@ -972,19 +1061,19 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
debugfs_create_file("ltr_show", 0444, dir, pmcdev, &pmc_core_ltr_fops);
debugfs_create_file("package_cstate_show", 0444, dir, pmcdev,
debugfs_create_file("package_cstate_show", 0444, dir, primary_pmc,
&pmc_core_pkgc_fops);
if (pmcdev->map->pll_sts)
if (primary_pmc->map->pll_sts)
debugfs_create_file("pll_status", 0444, dir, pmcdev,
&pmc_core_pll_fops);
if (pmcdev->map->mphy_sts)
if (primary_pmc->map->mphy_sts)
debugfs_create_file("mphy_core_lanes_power_gating_status",
0444, dir, pmcdev,
&pmc_core_mphy_pg_fops);
if (pmcdev->map->slps0_dbg_maps) {
if (primary_pmc->map->slps0_dbg_maps) {
debugfs_create_file("slp_s0_debug_status", 0444,
dir, pmcdev,
&pmc_core_slps0_dbg_fops);
@ -993,13 +1082,13 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
dir, &slps0_dbg_latch);
}
if (pmcdev->map->lpm_en_offset) {
if (primary_pmc->map->lpm_en_offset) {
debugfs_create_file("substate_residencies", 0444,
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_substate_res_fops);
}
if (pmcdev->map->lpm_status_offset) {
if (primary_pmc->map->lpm_status_offset) {
debugfs_create_file("substate_status_registers", 0444,
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_substate_sts_regs_fops);
@ -1011,7 +1100,7 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
&pmc_core_lpm_latch_mode_fops);
}
if (pmcdev->lpm_req_regs) {
if (primary_pmc->lpm_req_regs) {
debugfs_create_file("substate_requirements", 0444,
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_substate_req_regs_fops);
@ -1039,7 +1128,6 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_P, tgl_core_init),
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE, adl_core_init),
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_S, adl_core_init),
X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE, mtl_core_init),
X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE_L, mtl_core_init),
{}
};
@ -1063,16 +1151,16 @@ static int quirk_xtal_ignore(const struct dmi_system_id *id)
return 0;
}
static void pmc_core_xtal_ignore(struct pmc_dev *pmcdev)
static void pmc_core_xtal_ignore(struct pmc *pmc)
{
u32 value;
value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_vric1_offset);
value = pmc_core_reg_read(pmc, pmc->map->pm_vric1_offset);
/* 24MHz Crystal Shutdown Qualification Disable */
value |= SPT_PMC_VRIC1_XTALSDQDIS;
/* Low Voltage Mode Enable */
value &= ~SPT_PMC_VRIC1_SLPS0LVEN;
pmc_core_reg_write(pmcdev, pmcdev->map->pm_vric1_offset, value);
pmc_core_reg_write(pmc, pmc->map->pm_vric1_offset, value);
}
static const struct dmi_system_id pmc_core_dmi_table[] = {
@ -1087,12 +1175,32 @@ static const struct dmi_system_id pmc_core_dmi_table[] = {
{}
};
static void pmc_core_do_dmi_quirks(struct pmc_dev *pmcdev)
static void pmc_core_do_dmi_quirks(struct pmc *pmc)
{
dmi_check_system(pmc_core_dmi_table);
if (xtal_ignore)
pmc_core_xtal_ignore(pmcdev);
pmc_core_xtal_ignore(pmc);
}
static void pmc_core_clean_structure(struct platform_device *pdev)
{
struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
int i;
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
struct pmc *pmc = pmcdev->pmcs[i];
if (pmc)
iounmap(pmc->regbase);
}
if (pmcdev->ssram_pcidev) {
pci_dev_put(pmcdev->ssram_pcidev);
pci_disable_device(pmcdev->ssram_pcidev);
}
platform_set_drvdata(pdev, NULL);
mutex_destroy(&pmcdev->lock);
}
static int pmc_core_probe(struct platform_device *pdev)
@ -1100,8 +1208,9 @@ static int pmc_core_probe(struct platform_device *pdev)
static bool device_initialized;
struct pmc_dev *pmcdev;
const struct x86_cpu_id *cpu_id;
void (*core_init)(struct pmc_dev *pmcdev);
u64 slp_s0_addr;
int (*core_init)(struct pmc_dev *pmcdev);
struct pmc *primary_pmc;
int ret;
if (device_initialized)
return -ENODEV;
@ -1117,7 +1226,13 @@ static int pmc_core_probe(struct platform_device *pdev)
if (!cpu_id)
return -ENODEV;
core_init = (void (*)(struct pmc_dev *))cpu_id->driver_data;
core_init = (int (*)(struct pmc_dev *))cpu_id->driver_data;
/* Primary PMC */
primary_pmc = devm_kzalloc(&pdev->dev, sizeof(*primary_pmc), GFP_KERNEL);
if (!primary_pmc)
return -ENOMEM;
pmcdev->pmcs[PMC_IDX_MAIN] = primary_pmc;
/*
* Coffee Lake has CPU ID of Kaby Lake and Cannon Lake PCH. So here
@ -1128,33 +1243,19 @@ static int pmc_core_probe(struct platform_device *pdev)
core_init = cnp_core_init;
mutex_init(&pmcdev->lock);
core_init(pmcdev);
if (lpit_read_residency_count_address(&slp_s0_addr)) {
pmcdev->base_addr = PMC_BASE_ADDR_DEFAULT;
if (page_is_ram(PHYS_PFN(pmcdev->base_addr)))
return -ENODEV;
} else {
pmcdev->base_addr = slp_s0_addr - pmcdev->map->slp_s0_offset;
ret = core_init(pmcdev);
if (ret) {
pmc_core_clean_structure(pdev);
return ret;
}
pmcdev->regbase = ioremap(pmcdev->base_addr,
pmcdev->map->regmap_length);
if (!pmcdev->regbase)
return -ENOMEM;
if (pmcdev->core_configure)
pmcdev->core_configure(pmcdev);
pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(pmcdev);
pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(primary_pmc);
pmc_core_get_low_power_modes(pdev);
pmc_core_do_dmi_quirks(pmcdev);
pmc_core_do_dmi_quirks(primary_pmc);
pmc_core_dbgfs_register(pmcdev);
pm_report_max_hw_sleep(FIELD_MAX(SLP_S0_RES_COUNTER_MASK) *
pmc_core_adjust_slp_s0_step(pmcdev, 1));
pmc_core_adjust_slp_s0_step(primary_pmc, 1));
device_initialized = true;
dev_info(&pdev->dev, " initialized\n");
@ -1165,11 +1266,8 @@ static int pmc_core_probe(struct platform_device *pdev)
static void pmc_core_remove(struct platform_device *pdev)
{
struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
pmc_core_dbgfs_unregister(pmcdev);
platform_set_drvdata(pdev, NULL);
mutex_destroy(&pmcdev->lock);
iounmap(pmcdev->regbase);
pmc_core_clean_structure(pdev);
}
static bool warn_on_s0ix_failures;
@ -1179,6 +1277,7 @@ MODULE_PARM_DESC(warn_on_s0ix_failures, "Check and warn for S0ix failures");
static __maybe_unused int pmc_core_suspend(struct device *dev)
{
struct pmc_dev *pmcdev = dev_get_drvdata(dev);
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
/* Check if the syspend will actually use S0ix */
if (pm_suspend_via_firmware())
@ -1189,7 +1288,7 @@ static __maybe_unused int pmc_core_suspend(struct device *dev)
return -EIO;
/* Save S0ix residency for checking later */
if (pmc_core_dev_state_get(pmcdev, &pmcdev->s0ix_counter))
if (pmc_core_dev_state_get(pmc, &pmcdev->s0ix_counter))
return -EIO;
return 0;
@ -1212,7 +1311,7 @@ static inline bool pmc_core_is_s0ix_failed(struct pmc_dev *pmcdev)
{
u64 s0ix_counter;
if (pmc_core_dev_state_get(pmcdev, &s0ix_counter))
if (pmc_core_dev_state_get(pmcdev->pmcs[PMC_IDX_MAIN], &s0ix_counter))
return false;
pm_report_hw_sleep_time((u32)(s0ix_counter - pmcdev->s0ix_counter));
@ -1223,11 +1322,13 @@ static inline bool pmc_core_is_s0ix_failed(struct pmc_dev *pmcdev)
return false;
}
static __maybe_unused int pmc_core_resume(struct device *dev)
int pmc_core_resume_common(struct pmc_dev *pmcdev)
{
struct pmc_dev *pmcdev = dev_get_drvdata(dev);
const struct pmc_bit_map **maps = pmcdev->map->lpm_sts;
int offset = pmcdev->map->lpm_status_offset;
struct device *dev = &pmcdev->pdev->dev;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const struct pmc_bit_map **maps = pmc->map->lpm_sts;
int offset = pmc->map->lpm_status_offset;
int i;
/* Check if the syspend used S0ix */
if (pm_suspend_via_firmware())
@ -1249,14 +1350,32 @@ static __maybe_unused int pmc_core_resume(struct device *dev)
/* The real interesting case - S0ix failed - lets ask PMC why. */
dev_warn(dev, "CPU did not enter SLP_S0!!! (S0ix cnt=%llu)\n",
pmcdev->s0ix_counter);
if (pmcdev->map->slps0_dbg_maps)
pmc_core_slps0_display(pmcdev, dev, NULL);
if (pmcdev->map->lpm_sts)
pmc_core_lpm_display(pmcdev, dev, NULL, offset, "STATUS", maps);
if (pmc->map->slps0_dbg_maps)
pmc_core_slps0_display(pmc, dev, NULL);
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
struct pmc *pmc = pmcdev->pmcs[i];
if (!pmc)
continue;
if (pmc->map->lpm_sts)
pmc_core_lpm_display(pmc, dev, NULL, offset, i, "STATUS", maps);
}
return 0;
}
static __maybe_unused int pmc_core_resume(struct device *dev)
{
struct pmc_dev *pmcdev = dev_get_drvdata(dev);
if (pmcdev->resume)
return pmcdev->resume(pmcdev);
return pmc_core_resume_common(pmcdev);
}
static const struct dev_pm_ops pmc_core_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(pmc_core_suspend, pmc_core_resume)
};

View File

@ -19,6 +19,7 @@
#define SLP_S0_RES_COUNTER_MASK GENMASK(31, 0)
#define PMC_BASE_ADDR_DEFAULT 0xFE000000
#define MAX_NUM_PMC 3
/* Sunrise Point Power Management Controller PCI Device ID */
#define SPT_PMC_PCI_DEVICE_ID 0x9d21
@ -249,6 +250,17 @@ enum ppfear_regs {
#define MTL_LPM_STATUS_LATCH_EN_OFFSET 0x16F8
#define MTL_LPM_STATUS_OFFSET 0x1700
#define MTL_LPM_LIVE_STATUS_OFFSET 0x175C
#define MTL_PMC_LTR_IOE_PMC 0x1C0C
#define MTL_PMC_LTR_ESE 0x1BAC
#define MTL_PMC_LTR_RESERVED 0x1BA4
#define MTL_IOE_PMC_MMIO_REG_LEN 0x23A4
#define MTL_SOCM_NUM_IP_IGN_ALLOWED 25
#define MTL_SOC_PMC_MMIO_REG_LEN 0x2708
#define MTL_PMC_LTR_SPG 0x1B74
/* Meteor Lake PGD PFET Enable Ack Status */
#define MTL_SOCM_PPFEAR_NUM_ENTRIES 8
#define MTL_IOE_PPFEAR_NUM_ENTRIES 10
extern const char *pmc_lpm_modes[];
@ -311,12 +323,38 @@ struct pmc_reg_map {
};
/**
* struct pmc_dev - pmc device structure
* struct pmc_info - Structure to keep pmc info
* @devid: device id of the pmc device
* @map: pointer to a pmc_reg_map struct that contains platform
* specific attributes
*/
struct pmc_info {
u16 devid;
const struct pmc_reg_map *map;
};
/**
* struct pmc - pmc private info structure
* @base_addr: contains pmc base address
* @regbase: pointer to io-remapped memory location
* @map: pointer to pmc_reg_map struct that contains platform
* specific attributes
* @lpm_req_regs: List of substate requirements
*
* pmc contains info about one power management controller device.
*/
struct pmc {
u64 base_addr;
void __iomem *regbase;
const struct pmc_reg_map *map;
u32 *lpm_req_regs;
};
/**
* 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
* @dbgfs_dir: path to debugfs interface
* @pmc_xram_read_bit: flag to indicate whether PMC XRAM shadow registers
* used to read MPHY PG and PLL status are available
@ -325,17 +363,15 @@ struct pmc_reg_map {
* @s0ix_counter: S0ix residency (step adjusted)
* @num_lpm_modes: Count of enabled modes
* @lpm_en_modes: Array of enabled modes from lowest to highest priority
* @lpm_req_regs: List of substate requirements
* @core_configure: Function pointer to configure the platform
* @resume: Function to perform platform specific resume
*
* pmc_dev contains info about power management controller device.
*/
struct pmc_dev {
u32 base_addr;
void __iomem *regbase;
const struct pmc_reg_map *map;
struct pmc *pmcs[MAX_NUM_PMC];
struct dentry *dbgfs_dir;
struct platform_device *pdev;
struct pci_dev *ssram_pcidev;
int pmc_xram_read_bit;
struct mutex lock; /* generic mutex lock for PMC Core */
@ -343,8 +379,20 @@ struct pmc_dev {
u64 s0ix_counter;
int num_lpm_modes;
int lpm_en_modes[LPM_MAX_NUM_MODES];
u32 *lpm_req_regs;
void (*core_configure)(struct pmc_dev *pmcdev);
int (*resume)(struct pmc_dev *pmcdev);
bool has_die_c6;
u32 die_c6_offset;
struct telem_endpoint *punit_ep;
struct pmc_info *regmap_list;
};
enum pmc_index {
PMC_IDX_MAIN,
PMC_IDX_SOC = PMC_IDX_MAIN,
PMC_IDX_IOE,
PMC_IDX_PCH,
PMC_IDX_MAX
};
extern const struct pmc_bit_map msr_map[];
@ -393,20 +441,64 @@ extern const struct pmc_bit_map adl_vnn_req_status_3_map[];
extern const struct pmc_bit_map adl_vnn_misc_status_map[];
extern const struct pmc_bit_map *adl_lpm_maps[];
extern const struct pmc_reg_map adl_reg_map;
extern const struct pmc_reg_map mtl_reg_map;
extern const struct pmc_bit_map mtl_socm_pfear_map[];
extern const struct pmc_bit_map *ext_mtl_socm_pfear_map[];
extern const struct pmc_bit_map mtl_socm_ltr_show_map[];
extern const struct pmc_bit_map mtl_socm_clocksource_status_map[];
extern const struct pmc_bit_map mtl_socm_power_gating_status_0_map[];
extern const struct pmc_bit_map mtl_socm_power_gating_status_1_map[];
extern const struct pmc_bit_map mtl_socm_power_gating_status_2_map[];
extern const struct pmc_bit_map mtl_socm_d3_status_0_map[];
extern const struct pmc_bit_map mtl_socm_d3_status_1_map[];
extern const struct pmc_bit_map mtl_socm_d3_status_2_map[];
extern const struct pmc_bit_map mtl_socm_d3_status_3_map[];
extern const struct pmc_bit_map mtl_socm_vnn_req_status_0_map[];
extern const struct pmc_bit_map mtl_socm_vnn_req_status_1_map[];
extern const struct pmc_bit_map mtl_socm_vnn_req_status_2_map[];
extern const struct pmc_bit_map mtl_socm_vnn_req_status_3_map[];
extern const struct pmc_bit_map mtl_socm_vnn_misc_status_map[];
extern const struct pmc_bit_map mtl_socm_signal_status_map[];
extern const struct pmc_bit_map *mtl_socm_lpm_maps[];
extern const struct pmc_reg_map mtl_socm_reg_map;
extern const struct pmc_bit_map mtl_ioep_pfear_map[];
extern const struct pmc_bit_map *ext_mtl_ioep_pfear_map[];
extern const struct pmc_bit_map mtl_ioep_ltr_show_map[];
extern const struct pmc_bit_map mtl_ioep_clocksource_status_map[];
extern const struct pmc_bit_map mtl_ioep_power_gating_status_0_map[];
extern const struct pmc_bit_map mtl_ioep_power_gating_status_1_map[];
extern const struct pmc_bit_map mtl_ioep_power_gating_status_2_map[];
extern const struct pmc_bit_map mtl_ioep_d3_status_0_map[];
extern const struct pmc_bit_map mtl_ioep_d3_status_1_map[];
extern const struct pmc_bit_map mtl_ioep_d3_status_2_map[];
extern const struct pmc_bit_map mtl_ioep_d3_status_3_map[];
extern const struct pmc_bit_map mtl_ioep_vnn_req_status_0_map[];
extern const struct pmc_bit_map mtl_ioep_vnn_req_status_1_map[];
extern const struct pmc_bit_map mtl_ioep_vnn_req_status_2_map[];
extern const struct pmc_bit_map mtl_ioep_vnn_req_status_3_map[];
extern const struct pmc_bit_map mtl_ioep_vnn_misc_status_map[];
extern const struct pmc_bit_map *mtl_ioep_lpm_maps[];
extern const struct pmc_reg_map mtl_ioep_reg_map;
extern const struct pmc_bit_map mtl_ioem_pfear_map[];
extern const struct pmc_bit_map *ext_mtl_ioem_pfear_map[];
extern const struct pmc_bit_map mtl_ioem_power_gating_status_1_map[];
extern const struct pmc_bit_map mtl_ioem_vnn_req_status_1_map[];
extern const struct pmc_bit_map *mtl_ioem_lpm_maps[];
extern const struct pmc_reg_map mtl_ioem_reg_map;
extern void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev);
extern int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value);
void spt_core_init(struct pmc_dev *pmcdev);
void cnp_core_init(struct pmc_dev *pmcdev);
void icl_core_init(struct pmc_dev *pmcdev);
void tgl_core_init(struct pmc_dev *pmcdev);
void adl_core_init(struct pmc_dev *pmcdev);
void mtl_core_init(struct pmc_dev *pmcdev);
void tgl_core_configure(struct pmc_dev *pmcdev);
void adl_core_configure(struct pmc_dev *pmcdev);
void mtl_core_configure(struct pmc_dev *pmcdev);
int pmc_core_resume_common(struct pmc_dev *pmcdev);
int get_primary_reg_base(struct pmc *pmc);
extern void pmc_core_ssram_init(struct pmc_dev *pmcdev);
int spt_core_init(struct pmc_dev *pmcdev);
int cnp_core_init(struct pmc_dev *pmcdev);
int icl_core_init(struct pmc_dev *pmcdev);
int tgl_core_init(struct pmc_dev *pmcdev);
int adl_core_init(struct pmc_dev *pmcdev);
int mtl_core_init(struct pmc_dev *pmcdev);
#define pmc_for_each_mode(i, mode, pmcdev) \
for (i = 0, mode = pmcdev->lpm_en_modes[i]; \

View File

@ -0,0 +1,133 @@
// 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/pci.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include "core.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
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 void
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;
/* 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;
}
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;
}
pmcdev->pmcs[pmc_index] = pmc;
}
static void
pmc_core_ssram_get_pmc(struct pmc_dev *pmcdev, void __iomem *ssram, u32 offset,
int pmc_idx)
{
u64 pwrm_base;
u16 devid;
if (pmc_idx != PMC_IDX_SOC) {
u64 ssram_base = get_base(ssram, offset);
if (!ssram_base)
return;
ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
if (!ssram)
return;
}
pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
devid = readw(ssram + SSRAM_DEVID_OFFSET);
if (pmcdev->regmap_list) {
const struct pmc_reg_map *map;
map = pmc_core_find_regmap(pmcdev->regmap_list, devid);
if (map)
pmc_core_pmc_add(pmcdev, pwrm_base, map, pmc_idx);
}
if (pmc_idx != PMC_IDX_SOC)
iounmap(ssram);
}
void pmc_core_ssram_init(struct pmc_dev *pmcdev)
{
void __iomem *ssram;
struct pci_dev *pcidev;
u64 ssram_base;
int ret;
pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, 2));
if (!pcidev)
goto out;
ret = pcim_enable_device(pcidev);
if (ret)
goto release_dev;
ssram_base = pcidev->resource[0].start;
ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
if (!ssram)
goto disable_dev;
pmcdev->ssram_pcidev = pcidev;
pmc_core_ssram_get_pmc(pmcdev, ssram, 0, PMC_IDX_SOC);
pmc_core_ssram_get_pmc(pmcdev, ssram, SSRAM_IOE_OFFSET, PMC_IDX_IOE);
pmc_core_ssram_get_pmc(pmcdev, ssram, SSRAM_PCH_OFFSET, PMC_IDX_PCH);
iounmap(ssram);
out:
return;
disable_dev:
pci_disable_device(pcidev);
release_dev:
pci_dev_put(pcidev);
}

View File

@ -50,7 +50,10 @@ const struct pmc_reg_map icl_reg_map = {
.etr3_offset = ETR3_OFFSET,
};
void icl_core_init(struct pmc_dev *pmcdev)
int icl_core_init(struct pmc_dev *pmcdev)
{
pmcdev->map = &icl_reg_map;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
pmc->map = &icl_reg_map;
return get_primary_reg_base(pmc);
}

File diff suppressed because it is too large Load Diff

View File

@ -134,7 +134,10 @@ const struct pmc_reg_map spt_reg_map = {
.pm_vric1_offset = SPT_PMC_VRIC1_OFFSET,
};
void spt_core_init(struct pmc_dev *pmcdev)
int spt_core_init(struct pmc_dev *pmcdev)
{
pmcdev->map = &spt_reg_map;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
pmc->map = &spt_reg_map;
return get_primary_reg_base(pmc);
}

View File

@ -208,7 +208,8 @@ const struct pmc_reg_map tgl_reg_map = {
void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev)
{
struct pmc_dev *pmcdev = platform_get_drvdata(pdev);
const int num_maps = pmcdev->map->lpm_num_maps;
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
const int num_maps = pmc->map->lpm_num_maps;
u32 lpm_size = LPM_MAX_NUM_MODES * num_maps * 4;
union acpi_object *out_obj;
struct acpi_device *adev;
@ -246,24 +247,28 @@ void pmc_core_get_tgl_lpm_reqs(struct platform_device *pdev)
goto free_acpi_obj;
memcpy(lpm_req_regs, addr, lpm_size);
pmcdev->lpm_req_regs = lpm_req_regs;
pmc->lpm_req_regs = lpm_req_regs;
free_acpi_obj:
ACPI_FREE(out_obj);
}
void tgl_core_configure(struct pmc_dev *pmcdev)
int tgl_core_init(struct pmc_dev *pmcdev)
{
struct pmc *pmc = pmcdev->pmcs[PMC_IDX_MAIN];
int ret;
pmc->map = &tgl_reg_map;
ret = get_primary_reg_base(pmc);
if (ret)
return ret;
pmc_core_get_tgl_lpm_reqs(pmcdev->pdev);
/* Due to a hardware limitation, the GBE LTR blocks PC10
* when a cable is attached. Tell the PMC to ignore it.
*/
dev_dbg(&pmcdev->pdev->dev, "ignoring GBE LTR\n");
pmc_core_send_ltr_ignore(pmcdev, 3);
}
void tgl_core_init(struct pmc_dev *pmcdev)
{
pmcdev->map = &tgl_reg_map;
pmcdev->core_configure = tgl_core_configure;
return 0;
}

View File

@ -829,6 +829,7 @@ void isst_if_cdev_unregister(int device_type)
{
isst_misc_unreg();
mutex_lock(&punit_misc_dev_open_lock);
punit_callbacks[device_type].def_ioctl = NULL;
punit_callbacks[device_type].registered = 0;
if (device_type == ISST_IF_DEV_MBOX)
isst_delete_hash();

View File

@ -1414,6 +1414,8 @@ int tpmi_sst_init(void)
ret = isst_if_cdev_register(ISST_IF_DEV_TPMI, &cb);
if (ret)
kfree(isst_common.sst_inst);
else
++isst_core_usage_count;
init_done:
mutex_unlock(&isst_tpmi_dev_lock);
return ret;

View File

@ -222,7 +222,7 @@ static int tpmi_create_device(struct intel_tpmi_info *tpmi_info,
snprintf(feature_id_name, sizeof(feature_id_name), "tpmi-%s", name);
for (i = 0, tmp = res; i < pfs->pfs_header.num_entries; i++, tmp++) {
u64 entry_size_bytes = pfs->pfs_header.entry_size * 4;
u64 entry_size_bytes = pfs->pfs_header.entry_size * sizeof(u32);
tmp->start = pfs->vsec_offset + entry_size_bytes * i;
tmp->end = tmp->start + entry_size_bytes - 1;
@ -277,7 +277,7 @@ static int tpmi_process_info(struct intel_tpmi_info *tpmi_info,
void __iomem *info_mem;
info_mem = ioremap(pfs->vsec_offset + TPMI_INFO_BUS_INFO_OFFSET,
pfs->pfs_header.entry_size * 4 - TPMI_INFO_BUS_INFO_OFFSET);
pfs->pfs_header.entry_size * sizeof(u32) - TPMI_INFO_BUS_INFO_OFFSET);
if (!info_mem)
return -ENOMEM;
@ -308,6 +308,8 @@ static int tpmi_fetch_pfs_header(struct intel_tpmi_pm_feature *pfs, u64 start, i
return 0;
}
#define TPMI_CAP_OFFSET_UNIT 1024
static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
{
struct intel_vsec_device *vsec_dev = auxdev_to_ivdev(auxdev);
@ -354,7 +356,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)
if (!pfs_start)
pfs_start = res_start;
pfs->pfs_header.cap_offset *= 1024;
pfs->pfs_header.cap_offset *= TPMI_CAP_OFFSET_UNIT;
pfs->vsec_offset = pfs_start + pfs->pfs_header.cap_offset;

View File

@ -6,9 +6,13 @@
menu "Intel Uncore Frequency Control"
depends on X86_64 || COMPILE_TEST
config INTEL_UNCORE_FREQ_CONTROL_TPMI
tristate
config INTEL_UNCORE_FREQ_CONTROL
tristate "Intel Uncore frequency control driver"
depends on X86_64
select INTEL_UNCORE_FREQ_CONTROL_TPMI if INTEL_TPMI
help
This driver allows control of Uncore frequency limits on
supported server platforms.

View File

@ -7,3 +7,5 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o
intel-uncore-frequency-y := uncore-frequency.o
obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency-common.o
intel-uncore-frequency-common-y := uncore-frequency-common.o
obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL_TPMI) += intel-uncore-frequency-tpmi.o
intel-uncore-frequency-tpmi-y := uncore-frequency-tpmi.o

View File

@ -16,11 +16,34 @@ static struct kobject *uncore_root_kobj;
/* uncore instance count */
static int uncore_instance_count;
static DEFINE_IDA(intel_uncore_ida);
/* callbacks for actual HW read/write */
static int (*uncore_read)(struct uncore_data *data, unsigned int *min, unsigned int *max);
static int (*uncore_write)(struct uncore_data *data, unsigned int input, unsigned int min_max);
static int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq);
static ssize_t show_domain_id(struct device *dev, struct device_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_dev_attr);
return sprintf(buf, "%u\n", data->domain_id);
}
static ssize_t show_fabric_cluster_id(struct device *dev, struct device_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_dev_attr);
return sprintf(buf, "%u\n", data->cluster_id);
}
static ssize_t show_package_id(struct device *dev, struct device_attribute *attr, char *buf)
{
struct uncore_data *data = container_of(attr, struct uncore_data, package_id_dev_attr);
return sprintf(buf, "%u\n", data->package_id);
}
static ssize_t show_min_max_freq_khz(struct uncore_data *data,
char *buf, int min_max)
{
@ -161,6 +184,15 @@ static int create_attr_group(struct uncore_data *data, char *name)
init_attribute_ro(initial_max_freq_khz);
init_attribute_root_ro(current_freq_khz);
if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) {
init_attribute_root_ro(domain_id);
data->uncore_attrs[index++] = &data->domain_id_dev_attr.attr;
init_attribute_root_ro(fabric_cluster_id);
data->uncore_attrs[index++] = &data->fabric_cluster_id_dev_attr.attr;
init_attribute_root_ro(package_id);
data->uncore_attrs[index++] = &data->package_id_dev_attr.attr;
}
data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr;
data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr;
data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr;
@ -191,12 +223,24 @@ int uncore_freq_add_entry(struct uncore_data *data, int cpu)
goto uncore_unlock;
}
sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) {
ret = ida_alloc(&intel_uncore_ida, GFP_KERNEL);
if (ret < 0)
goto uncore_unlock;
data->instance_id = ret;
sprintf(data->name, "uncore%02d", ret);
} else {
sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
}
uncore_read(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz);
ret = create_attr_group(data, data->name);
if (!ret) {
if (ret) {
if (data->domain_id != UNCORE_DOMAIN_ID_INVALID)
ida_free(&intel_uncore_ida, data->instance_id);
} else {
data->control_cpu = cpu;
data->valid = true;
}
@ -214,6 +258,9 @@ void uncore_freq_remove_die_entry(struct uncore_data *data)
delete_attr_group(data, data->name);
data->control_cpu = -1;
data->valid = false;
if (data->domain_id != UNCORE_DOMAIN_ID_INVALID)
ida_free(&intel_uncore_ida, data->instance_id);
mutex_unlock(&uncore_lock);
}
EXPORT_SYMBOL_NS_GPL(uncore_freq_remove_die_entry, INTEL_UNCORE_FREQUENCY);

View File

@ -21,6 +21,9 @@
* @valid: Mark the data valid/invalid
* @package_id: Package id for this instance
* @die_id: Die id for this instance
* @domain_id: Power domain id for this instance
* @cluster_id: cluster id in a domain
* @instance_id: Unique instance id to append to directory name
* @name: Sysfs entry name for this instance
* @uncore_attr_group: Attribute group storage
* @max_freq_khz_dev_attr: Storage for device attribute max_freq_khz
@ -28,6 +31,9 @@
* @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz
* @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz
* @current_freq_khz_dev_attr: Storage for device attribute current_freq_khz
* @domain_id_dev_attr: Storage for device attribute domain_id
* @fabric_cluster_id_dev_attr: Storage for device attribute fabric_cluster_id
* @package_id_dev_attr: Storage for device attribute package_id
* @uncore_attrs: Attribute storage for group creation
*
* This structure is used to encapsulate all data related to uncore sysfs
@ -41,6 +47,9 @@ struct uncore_data {
bool valid;
int package_id;
int die_id;
int domain_id;
int cluster_id;
int instance_id;
char name[32];
struct attribute_group uncore_attr_group;
@ -49,9 +58,14 @@ struct uncore_data {
struct device_attribute initial_max_freq_khz_dev_attr;
struct device_attribute initial_min_freq_khz_dev_attr;
struct device_attribute current_freq_khz_dev_attr;
struct attribute *uncore_attrs[6];
struct device_attribute domain_id_dev_attr;
struct device_attribute fabric_cluster_id_dev_attr;
struct device_attribute package_id_dev_attr;
struct attribute *uncore_attrs[9];
};
#define UNCORE_DOMAIN_ID_INVALID -1
int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max),
int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int min_max),
int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq));

View File

@ -0,0 +1,413 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* uncore-frquency-tpmi: Uncore frequency scaling using TPMI
*
* Copyright (c) 2023, Intel Corporation.
* All Rights Reserved.
*
* The hardware interface to read/write is basically substitution of
* MSR 0x620 and 0x621.
* There are specific MMIO offset and bits to get/set minimum and
* maximum uncore ratio, similar to MSRs.
* The scope of the uncore MSRs was package scope. But TPMI allows
* new gen CPUs to have multiple uncore controls at uncore-cluster
* level. Each package can have multiple power domains which further
* can have multiple clusters.
* Here number of power domains = number of resources in this aux
* device. There are offsets and bits to discover number of clusters
* and offset for each cluster level controls.
*
*/
#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/intel_tpmi.h>
#include "uncore-frequency-common.h"
#define UNCORE_HEADER_VERSION 1
#define UNCORE_HEADER_INDEX 0
#define UNCORE_FABRIC_CLUSTER_OFFSET 8
/* status + control + adv_ctl1 + adv_ctl2 */
#define UNCORE_FABRIC_CLUSTER_SIZE (4 * 8)
#define UNCORE_STATUS_INDEX 0
#define UNCORE_CONTROL_INDEX 8
#define UNCORE_FREQ_KHZ_MULTIPLIER 100000
struct tpmi_uncore_struct;
/* Information for each cluster */
struct tpmi_uncore_cluster_info {
bool root_domain;
u8 __iomem *cluster_base;
struct uncore_data uncore_data;
struct tpmi_uncore_struct *uncore_root;
};
/* Information for each power domain */
struct tpmi_uncore_power_domain_info {
u8 __iomem *uncore_base;
int ufs_header_ver;
int cluster_count;
struct tpmi_uncore_cluster_info *cluster_infos;
};
/* Information for all power domains in a package */
struct tpmi_uncore_struct {
int power_domain_count;
int max_ratio;
int min_ratio;
struct tpmi_uncore_power_domain_info *pd_info;
struct tpmi_uncore_cluster_info root_cluster;
};
#define UNCORE_GENMASK_MIN_RATIO GENMASK_ULL(21, 15)
#define UNCORE_GENMASK_MAX_RATIO GENMASK_ULL(14, 8)
#define UNCORE_GENMASK_CURRENT_RATIO GENMASK_ULL(6, 0)
/* Helper function to read MMIO offset for max/min control frequency */
static void read_control_freq(struct tpmi_uncore_cluster_info *cluster_info,
unsigned int *min, unsigned int *max)
{
u64 control;
control = readq(cluster_info->cluster_base + UNCORE_CONTROL_INDEX);
*max = FIELD_GET(UNCORE_GENMASK_MAX_RATIO, control) * UNCORE_FREQ_KHZ_MULTIPLIER;
*min = FIELD_GET(UNCORE_GENMASK_MIN_RATIO, control) * UNCORE_FREQ_KHZ_MULTIPLIER;
}
#define UNCORE_MAX_RATIO FIELD_MAX(UNCORE_GENMASK_MAX_RATIO)
/* Callback for sysfs read for max/min frequencies. Called under mutex locks */
static int uncore_read_control_freq(struct uncore_data *data, unsigned int *min,
unsigned int *max)
{
struct tpmi_uncore_cluster_info *cluster_info;
cluster_info = container_of(data, struct tpmi_uncore_cluster_info, uncore_data);
if (cluster_info->root_domain) {
struct tpmi_uncore_struct *uncore_root = cluster_info->uncore_root;
int i, _min = 0, _max = 0;
*min = UNCORE_MAX_RATIO * UNCORE_FREQ_KHZ_MULTIPLIER;
*max = 0;
/*
* Get the max/min by looking at each cluster. Get the lowest
* min and highest max.
*/
for (i = 0; i < uncore_root->power_domain_count; ++i) {
int j;
for (j = 0; j < uncore_root->pd_info[i].cluster_count; ++j) {
read_control_freq(&uncore_root->pd_info[i].cluster_infos[j],
&_min, &_max);
if (*min > _min)
*min = _min;
if (*max < _max)
*max = _max;
}
}
return 0;
}
read_control_freq(cluster_info, min, max);
return 0;
}
/* Helper function to write MMIO offset for max/min control frequency */
static void write_control_freq(struct tpmi_uncore_cluster_info *cluster_info, unsigned int input,
unsigned int min_max)
{
u64 control;
control = readq(cluster_info->cluster_base + UNCORE_CONTROL_INDEX);
if (min_max) {
control &= ~UNCORE_GENMASK_MAX_RATIO;
control |= FIELD_PREP(UNCORE_GENMASK_MAX_RATIO, input);
} else {
control &= ~UNCORE_GENMASK_MIN_RATIO;
control |= FIELD_PREP(UNCORE_GENMASK_MIN_RATIO, input);
}
writeq(control, (cluster_info->cluster_base + UNCORE_CONTROL_INDEX));
}
/* Callback for sysfs write for max/min frequencies. Called under mutex locks */
static int uncore_write_control_freq(struct uncore_data *data, unsigned int input,
unsigned int min_max)
{
struct tpmi_uncore_cluster_info *cluster_info;
struct tpmi_uncore_struct *uncore_root;
input /= UNCORE_FREQ_KHZ_MULTIPLIER;
if (!input || input > UNCORE_MAX_RATIO)
return -EINVAL;
cluster_info = container_of(data, struct tpmi_uncore_cluster_info, uncore_data);
uncore_root = cluster_info->uncore_root;
/* Update each cluster in a package */
if (cluster_info->root_domain) {
struct tpmi_uncore_struct *uncore_root = cluster_info->uncore_root;
int i;
for (i = 0; i < uncore_root->power_domain_count; ++i) {
int j;
for (j = 0; j < uncore_root->pd_info[i].cluster_count; ++j)
write_control_freq(&uncore_root->pd_info[i].cluster_infos[j],
input, min_max);
}
if (min_max)
uncore_root->max_ratio = input;
else
uncore_root->min_ratio = input;
return 0;
}
if (min_max && uncore_root->max_ratio && uncore_root->max_ratio < input)
return -EINVAL;
if (!min_max && uncore_root->min_ratio && uncore_root->min_ratio > input)
return -EINVAL;
write_control_freq(cluster_info, input, min_max);
return 0;
}
/* Callback for sysfs read for the current uncore frequency. Called under mutex locks */
static int uncore_read_freq(struct uncore_data *data, unsigned int *freq)
{
struct tpmi_uncore_cluster_info *cluster_info;
u64 status;
cluster_info = container_of(data, struct tpmi_uncore_cluster_info, uncore_data);
if (cluster_info->root_domain)
return -ENODATA;
status = readq((u8 __iomem *)cluster_info->cluster_base + UNCORE_STATUS_INDEX);
*freq = FIELD_GET(UNCORE_GENMASK_CURRENT_RATIO, status) * UNCORE_FREQ_KHZ_MULTIPLIER;
return 0;
}
static void remove_cluster_entries(struct tpmi_uncore_struct *tpmi_uncore)
{
int i;
for (i = 0; i < tpmi_uncore->power_domain_count; ++i) {
struct tpmi_uncore_power_domain_info *pd_info;
int j;
pd_info = &tpmi_uncore->pd_info[i];
if (!pd_info->uncore_base)
continue;
for (j = 0; j < pd_info->cluster_count; ++j) {
struct tpmi_uncore_cluster_info *cluster_info;
cluster_info = &pd_info->cluster_infos[j];
uncore_freq_remove_die_entry(&cluster_info->uncore_data);
}
}
}
#define UNCORE_VERSION_MASK GENMASK_ULL(7, 0)
#define UNCORE_LOCAL_FABRIC_CLUSTER_ID_MASK GENMASK_ULL(15, 8)
#define UNCORE_CLUSTER_OFF_MASK GENMASK_ULL(7, 0)
#define UNCORE_MAX_CLUSTER_PER_DOMAIN 8
static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
{
struct intel_tpmi_plat_info *plat_info;
struct tpmi_uncore_struct *tpmi_uncore;
int ret, i, pkg = 0;
int num_resources;
/* Get number of power domains, which is equal to number of resources */
num_resources = tpmi_get_resource_count(auxdev);
if (!num_resources)
return -EINVAL;
/* Register callbacks to uncore core */
ret = uncore_freq_common_init(uncore_read_control_freq, uncore_write_control_freq,
uncore_read_freq);
if (ret)
return ret;
/* Allocate uncore instance per package */
tpmi_uncore = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_uncore), GFP_KERNEL);
if (!tpmi_uncore) {
ret = -ENOMEM;
goto err_rem_common;
}
/* Allocate memory for all power domains in a package */
tpmi_uncore->pd_info = devm_kcalloc(&auxdev->dev, num_resources,
sizeof(*tpmi_uncore->pd_info),
GFP_KERNEL);
if (!tpmi_uncore->pd_info) {
ret = -ENOMEM;
goto err_rem_common;
}
tpmi_uncore->power_domain_count = num_resources;
/* Get the package ID from the TPMI core */
plat_info = tpmi_get_platform_data(auxdev);
if (plat_info)
pkg = plat_info->package_id;
else
dev_info(&auxdev->dev, "Platform information is NULL\n");
for (i = 0; i < num_resources; ++i) {
struct tpmi_uncore_power_domain_info *pd_info;
struct resource *res;
u64 cluster_offset;
u8 cluster_mask;
int mask, j;
u64 header;
res = tpmi_get_resource_at_index(auxdev, i);
if (!res)
continue;
pd_info = &tpmi_uncore->pd_info[i];
pd_info->uncore_base = devm_ioremap_resource(&auxdev->dev, res);
if (IS_ERR(pd_info->uncore_base)) {
ret = PTR_ERR(pd_info->uncore_base);
/*
* Set to NULL so that clean up can still remove other
* entries already created if any by
* remove_cluster_entries()
*/
pd_info->uncore_base = NULL;
goto remove_clusters;
}
/* Check for version and skip this resource if there is mismatch */
header = readq(pd_info->uncore_base);
pd_info->ufs_header_ver = header & UNCORE_VERSION_MASK;
if (pd_info->ufs_header_ver != UNCORE_HEADER_VERSION) {
dev_info(&auxdev->dev, "Uncore: Unsupported version:%d\n",
pd_info->ufs_header_ver);
continue;
}
/* Get Cluster ID Mask */
cluster_mask = FIELD_GET(UNCORE_LOCAL_FABRIC_CLUSTER_ID_MASK, header);
if (!cluster_mask) {
dev_info(&auxdev->dev, "Uncore: Invalid cluster mask:%x\n", cluster_mask);
continue;
}
/* Find out number of clusters in this resource */
pd_info->cluster_count = hweight8(cluster_mask);
pd_info->cluster_infos = devm_kcalloc(&auxdev->dev, pd_info->cluster_count,
sizeof(struct tpmi_uncore_cluster_info),
GFP_KERNEL);
if (!pd_info->cluster_infos) {
ret = -ENOMEM;
goto remove_clusters;
}
/*
* Each byte in the register point to status and control
* registers belonging to cluster id 0-8.
*/
cluster_offset = readq(pd_info->uncore_base +
UNCORE_FABRIC_CLUSTER_OFFSET);
for (j = 0; j < pd_info->cluster_count; ++j) {
struct tpmi_uncore_cluster_info *cluster_info;
/* Get the offset for this cluster */
mask = (cluster_offset & UNCORE_CLUSTER_OFF_MASK);
/* Offset in QWORD, so change to bytes */
mask <<= 3;
cluster_info = &pd_info->cluster_infos[j];
cluster_info->cluster_base = pd_info->uncore_base + mask;
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;
cluster_info->uncore_root = tpmi_uncore;
ret = uncore_freq_add_entry(&cluster_info->uncore_data, 0);
if (ret) {
cluster_info->cluster_base = NULL;
goto remove_clusters;
}
/* Point to next cluster offset */
cluster_offset >>= UNCORE_MAX_CLUSTER_PER_DOMAIN;
}
}
auxiliary_set_drvdata(auxdev, tpmi_uncore);
tpmi_uncore->root_cluster.root_domain = true;
tpmi_uncore->root_cluster.uncore_root = tpmi_uncore;
tpmi_uncore->root_cluster.uncore_data.package_id = pkg;
tpmi_uncore->root_cluster.uncore_data.domain_id = UNCORE_DOMAIN_ID_INVALID;
ret = uncore_freq_add_entry(&tpmi_uncore->root_cluster.uncore_data, 0);
if (ret)
goto remove_clusters;
return 0;
remove_clusters:
remove_cluster_entries(tpmi_uncore);
err_rem_common:
uncore_freq_common_exit();
return ret;
}
static void uncore_remove(struct auxiliary_device *auxdev)
{
struct tpmi_uncore_struct *tpmi_uncore = auxiliary_get_drvdata(auxdev);
uncore_freq_remove_die_entry(&tpmi_uncore->root_cluster.uncore_data);
remove_cluster_entries(tpmi_uncore);
uncore_freq_common_exit();
}
static const struct auxiliary_device_id intel_uncore_id_table[] = {
{ .name = "intel_vsec.tpmi-uncore" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, intel_uncore_id_table);
static struct auxiliary_driver intel_uncore_aux_driver = {
.id_table = intel_uncore_id_table,
.remove = uncore_remove,
.probe = uncore_probe,
};
module_auxiliary_driver(intel_uncore_aux_driver);
MODULE_IMPORT_NS(INTEL_TPMI);
MODULE_IMPORT_NS(INTEL_UNCORE_FREQUENCY);
MODULE_DESCRIPTION("Intel TPMI UFS Driver");
MODULE_LICENSE("GPL");

View File

@ -136,6 +136,7 @@ static int uncore_event_cpu_online(unsigned int cpu)
data->package_id = topology_physical_package_id(cpu);
data->die_id = topology_die_id(cpu);
data->domain_id = UNCORE_DOMAIN_ID_INVALID;
return uncore_freq_add_entry(data, cpu);
}

View File

@ -1,408 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/* WMI driver for Lenovo Yoga Book YB1-X90* / -X91* tablets */
#include <linux/acpi.h>
#include <linux/devm-helpers.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/leds.h>
#include <linux/wmi.h>
#include <linux/workqueue.h>
#define YB_MBTN_EVENT_GUID "243FEC1D-1963-41C1-8100-06A9D82A94B4"
#define YB_MBTN_METHOD_GUID "742B0CA1-0B20-404B-9CAA-AEFCABF30CE0"
#define YB_PAD_ENABLE 1
#define YB_PAD_DISABLE 2
#define YB_LIGHTUP_BTN 3
#define YB_KBD_BL_DEFAULT 128
/* flags */
enum {
YB_KBD_IS_ON,
YB_DIGITIZER_IS_ON,
YB_DIGITIZER_MODE,
YB_TABLET_MODE,
YB_SUSPENDED,
};
struct yogabook_wmi {
struct wmi_device *wdev;
struct acpi_device *kbd_adev;
struct acpi_device *dig_adev;
struct device *kbd_dev;
struct device *dig_dev;
struct gpio_desc *backside_hall_gpio;
int backside_hall_irq;
struct work_struct work;
struct led_classdev kbd_bl_led;
unsigned long flags;
uint8_t brightness;
};
static int yogabook_wmi_do_action(struct wmi_device *wdev, int action)
{
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer input;
acpi_status status;
u32 dummy_arg = 0;
dev_dbg(&wdev->dev, "Do action: %d\n", action);
input.pointer = &dummy_arg;
input.length = sizeof(dummy_arg);
status = wmi_evaluate_method(YB_MBTN_METHOD_GUID, 0, action, &input,
&output);
if (ACPI_FAILURE(status)) {
dev_err(&wdev->dev, "Calling WMI method failure: 0x%x\n",
status);
return status;
}
kfree(output.pointer);
return 0;
}
/*
* To control keyboard backlight, call the method KBLC() of the TCS1 ACPI
* device (Goodix touchpad acts as virtual sensor keyboard).
*/
static int yogabook_wmi_set_kbd_backlight(struct wmi_device *wdev,
uint8_t level)
{
struct yogabook_wmi *data = dev_get_drvdata(&wdev->dev);
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_object_list input;
union acpi_object param;
acpi_status status;
if (data->kbd_adev->power.state != ACPI_STATE_D0) {
dev_warn(&wdev->dev, "keyboard touchscreen not in D0, cannot set brightness\n");
return -ENXIO;
}
dev_dbg(&wdev->dev, "Set KBLC level to %u\n", level);
input.count = 1;
input.pointer = &param;
param.type = ACPI_TYPE_INTEGER;
param.integer.value = 255 - level;
status = acpi_evaluate_object(acpi_device_handle(data->kbd_adev), "KBLC",
&input, &output);
if (ACPI_FAILURE(status)) {
dev_err(&wdev->dev, "Failed to call KBLC method: 0x%x\n", status);
return status;
}
kfree(output.pointer);
return 0;
}
static void yogabook_wmi_work(struct work_struct *work)
{
struct yogabook_wmi *data = container_of(work, struct yogabook_wmi, work);
struct device *dev = &data->wdev->dev;
bool kbd_on, digitizer_on;
int r;
if (test_bit(YB_SUSPENDED, &data->flags))
return;
if (test_bit(YB_TABLET_MODE, &data->flags)) {
kbd_on = false;
digitizer_on = false;
} else if (test_bit(YB_DIGITIZER_MODE, &data->flags)) {
digitizer_on = true;
kbd_on = false;
} else {
kbd_on = true;
digitizer_on = false;
}
if (!kbd_on && test_bit(YB_KBD_IS_ON, &data->flags)) {
/*
* Must be done before releasing the keyboard touchscreen driver,
* so that the keyboard touchscreen dev is still in D0.
*/
yogabook_wmi_set_kbd_backlight(data->wdev, 0);
device_release_driver(data->kbd_dev);
clear_bit(YB_KBD_IS_ON, &data->flags);
}
if (!digitizer_on && test_bit(YB_DIGITIZER_IS_ON, &data->flags)) {
yogabook_wmi_do_action(data->wdev, YB_PAD_DISABLE);
device_release_driver(data->dig_dev);
clear_bit(YB_DIGITIZER_IS_ON, &data->flags);
}
if (kbd_on && !test_bit(YB_KBD_IS_ON, &data->flags)) {
r = device_reprobe(data->kbd_dev);
if (r)
dev_warn(dev, "Reprobe of keyboard touchscreen failed: %d\n", r);
yogabook_wmi_set_kbd_backlight(data->wdev, data->brightness);
set_bit(YB_KBD_IS_ON, &data->flags);
}
if (digitizer_on && !test_bit(YB_DIGITIZER_IS_ON, &data->flags)) {
r = device_reprobe(data->dig_dev);
if (r)
dev_warn(dev, "Reprobe of digitizer failed: %d\n", r);
yogabook_wmi_do_action(data->wdev, YB_PAD_ENABLE);
set_bit(YB_DIGITIZER_IS_ON, &data->flags);
}
}
static void yogabook_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
{
struct yogabook_wmi *data = dev_get_drvdata(&wdev->dev);
if (test_bit(YB_SUSPENDED, &data->flags))
return;
if (test_bit(YB_DIGITIZER_MODE, &data->flags))
clear_bit(YB_DIGITIZER_MODE, &data->flags);
else
set_bit(YB_DIGITIZER_MODE, &data->flags);
/*
* We are called from the ACPI core and the driver [un]binding which is
* done also needs ACPI functions, use a workqueue to avoid deadlocking.
*/
schedule_work(&data->work);
}
static irqreturn_t yogabook_backside_hall_irq(int irq, void *_data)
{
struct yogabook_wmi *data = _data;
if (gpiod_get_value(data->backside_hall_gpio))
set_bit(YB_TABLET_MODE, &data->flags);
else
clear_bit(YB_TABLET_MODE, &data->flags);
schedule_work(&data->work);
return IRQ_HANDLED;
}
static enum led_brightness kbd_brightness_get(struct led_classdev *cdev)
{
struct yogabook_wmi *data =
container_of(cdev, struct yogabook_wmi, kbd_bl_led);
return data->brightness;
}
static int kbd_brightness_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct yogabook_wmi *data =
container_of(cdev, struct yogabook_wmi, kbd_bl_led);
struct wmi_device *wdev = data->wdev;
if ((value < 0) || (value > 255))
return -EINVAL;
data->brightness = value;
if (data->kbd_adev->power.state != ACPI_STATE_D0)
return 0;
return yogabook_wmi_set_kbd_backlight(wdev, data->brightness);
}
static struct gpiod_lookup_table yogabook_wmi_gpios = {
.dev_id = "243FEC1D-1963-41C1-8100-06A9D82A94B4",
.table = {
GPIO_LOOKUP("INT33FF:02", 18, "backside_hall_sw", GPIO_ACTIVE_LOW),
{}
},
};
static void yogabook_wmi_rm_gpio_lookup(void *unused)
{
gpiod_remove_lookup_table(&yogabook_wmi_gpios);
}
static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context)
{
struct yogabook_wmi *data;
int r;
data = devm_kzalloc(&wdev->dev, sizeof(struct yogabook_wmi), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
dev_set_drvdata(&wdev->dev, data);
data->wdev = wdev;
data->brightness = YB_KBD_BL_DEFAULT;
set_bit(YB_KBD_IS_ON, &data->flags);
set_bit(YB_DIGITIZER_IS_ON, &data->flags);
r = devm_work_autocancel(&wdev->dev, &data->work, yogabook_wmi_work);
if (r)
return r;
data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1);
if (!data->kbd_adev) {
dev_err(&wdev->dev, "Cannot find the touchpad device in ACPI tables\n");
return -ENODEV;
}
data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1);
if (!data->dig_adev) {
dev_err(&wdev->dev, "Cannot find the digitizer device in ACPI tables\n");
r = -ENODEV;
goto error_put_devs;
}
data->kbd_dev = get_device(acpi_get_first_physical_node(data->kbd_adev));
if (!data->kbd_dev || !data->kbd_dev->driver) {
r = -EPROBE_DEFER;
goto error_put_devs;
}
data->dig_dev = get_device(acpi_get_first_physical_node(data->dig_adev));
if (!data->dig_dev || !data->dig_dev->driver) {
r = -EPROBE_DEFER;
goto error_put_devs;
}
gpiod_add_lookup_table(&yogabook_wmi_gpios);
r = devm_add_action_or_reset(&wdev->dev, yogabook_wmi_rm_gpio_lookup, NULL);
if (r)
goto error_put_devs;
data->backside_hall_gpio =
devm_gpiod_get(&wdev->dev, "backside_hall_sw", GPIOD_IN);
if (IS_ERR(data->backside_hall_gpio)) {
r = PTR_ERR(data->backside_hall_gpio);
dev_err_probe(&wdev->dev, r, "Getting backside_hall_sw GPIO\n");
goto error_put_devs;
}
r = gpiod_to_irq(data->backside_hall_gpio);
if (r < 0) {
dev_err_probe(&wdev->dev, r, "Getting backside_hall_sw IRQ\n");
goto error_put_devs;
}
data->backside_hall_irq = r;
r = devm_request_irq(&wdev->dev, data->backside_hall_irq,
yogabook_backside_hall_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"backside_hall_sw", data);
if (r) {
dev_err_probe(&wdev->dev, r, "Requesting backside_hall_sw IRQ\n");
goto error_put_devs;
}
schedule_work(&data->work);
data->kbd_bl_led.name = "ybwmi::kbd_backlight";
data->kbd_bl_led.brightness_set_blocking = kbd_brightness_set;
data->kbd_bl_led.brightness_get = kbd_brightness_get;
data->kbd_bl_led.max_brightness = 255;
r = devm_led_classdev_register(&wdev->dev, &data->kbd_bl_led);
if (r < 0) {
dev_err_probe(&wdev->dev, r, "Registering backlight LED device\n");
goto error_put_devs;
}
return 0;
error_put_devs:
put_device(data->dig_dev);
put_device(data->kbd_dev);
acpi_dev_put(data->dig_adev);
acpi_dev_put(data->kbd_adev);
return r;
}
static void yogabook_wmi_remove(struct wmi_device *wdev)
{
struct yogabook_wmi *data = dev_get_drvdata(&wdev->dev);
put_device(data->dig_dev);
put_device(data->kbd_dev);
acpi_dev_put(data->dig_adev);
acpi_dev_put(data->kbd_adev);
}
static int __maybe_unused yogabook_wmi_suspend(struct device *dev)
{
struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
struct yogabook_wmi *data = dev_get_drvdata(dev);
set_bit(YB_SUSPENDED, &data->flags);
flush_work(&data->work);
/* Turn off the pen button at sleep */
if (test_bit(YB_DIGITIZER_IS_ON, &data->flags))
yogabook_wmi_do_action(wdev, YB_PAD_DISABLE);
return 0;
}
static int __maybe_unused yogabook_wmi_resume(struct device *dev)
{
struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
struct yogabook_wmi *data = dev_get_drvdata(dev);
if (test_bit(YB_KBD_IS_ON, &data->flags)) {
/* Ensure keyboard touchpad is on before we call KBLC() */
acpi_device_set_power(data->kbd_adev, ACPI_STATE_D0);
yogabook_wmi_set_kbd_backlight(wdev, data->brightness);
}
if (test_bit(YB_DIGITIZER_IS_ON, &data->flags))
yogabook_wmi_do_action(wdev, YB_PAD_ENABLE);
clear_bit(YB_SUSPENDED, &data->flags);
/* Check for YB_TABLET_MODE changes made during suspend */
schedule_work(&data->work);
return 0;
}
static const struct wmi_device_id yogabook_wmi_id_table[] = {
{
.guid_string = YB_MBTN_EVENT_GUID,
},
{ } /* Terminating entry */
};
static SIMPLE_DEV_PM_OPS(yogabook_wmi_pm_ops,
yogabook_wmi_suspend, yogabook_wmi_resume);
static struct wmi_driver yogabook_wmi_driver = {
.driver = {
.name = "yogabook-wmi",
.pm = &yogabook_wmi_pm_ops,
},
.no_notify_data = true,
.id_table = yogabook_wmi_id_table,
.probe = yogabook_wmi_probe,
.remove = yogabook_wmi_remove,
.notify = yogabook_wmi_notify,
};
module_wmi_driver(yogabook_wmi_driver);
MODULE_DEVICE_TABLE(wmi, yogabook_wmi_id_table);
MODULE_AUTHOR("Yauhen Kharuzhy");
MODULE_DESCRIPTION("Lenovo Yoga Book WMI driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,573 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Platform driver for Lenovo Yoga Book YB1-X90F/L tablets (Android model)
* WMI driver for Lenovo Yoga Book YB1-X91F/L tablets (Windows model)
*
* The keyboard half of the YB1 models can function as both a capacitive
* touch keyboard or as a Wacom digitizer, but not at the same time.
*
* This driver takes care of switching between the 2 functions.
*
* Copyright 2023 Hans de Goede <hansg@kernel.org>
*/
#include <linux/acpi.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/wmi.h>
#include <linux/workqueue.h>
#define YB_MBTN_EVENT_GUID "243FEC1D-1963-41C1-8100-06A9D82A94B4"
#define YB_KBD_BL_DEFAULT 128
#define YB_KBD_BL_MAX 255
#define YB_KBD_BL_PWM_PERIOD 13333
#define YB_PDEV_NAME "yogabook-touch-kbd-digitizer-switch"
/* flags */
enum {
YB_KBD_IS_ON,
YB_DIGITIZER_IS_ON,
YB_DIGITIZER_MODE,
YB_TABLET_MODE,
YB_SUSPENDED,
};
struct yogabook_data {
struct device *dev;
struct acpi_device *kbd_adev;
struct acpi_device *dig_adev;
struct device *kbd_dev;
struct device *dig_dev;
struct led_classdev *pen_led;
struct gpio_desc *pen_touch_event;
struct gpio_desc *kbd_bl_led_enable;
struct gpio_desc *backside_hall_gpio;
struct pwm_device *kbd_bl_pwm;
int (*set_kbd_backlight)(struct yogabook_data *data, uint8_t level);
int pen_touch_irq;
int backside_hall_irq;
struct work_struct work;
struct led_classdev kbd_bl_led;
unsigned long flags;
uint8_t brightness;
};
static void yogabook_work(struct work_struct *work)
{
struct yogabook_data *data = container_of(work, struct yogabook_data, work);
bool kbd_on, digitizer_on;
int r;
if (test_bit(YB_SUSPENDED, &data->flags))
return;
if (test_bit(YB_TABLET_MODE, &data->flags)) {
kbd_on = false;
digitizer_on = false;
} else if (test_bit(YB_DIGITIZER_MODE, &data->flags)) {
digitizer_on = true;
kbd_on = false;
} else {
kbd_on = true;
digitizer_on = false;
}
if (!kbd_on && test_bit(YB_KBD_IS_ON, &data->flags)) {
/*
* Must be done before releasing the keyboard touchscreen driver,
* so that the keyboard touchscreen dev is still in D0.
*/
data->set_kbd_backlight(data, 0);
device_release_driver(data->kbd_dev);
clear_bit(YB_KBD_IS_ON, &data->flags);
}
if (!digitizer_on && test_bit(YB_DIGITIZER_IS_ON, &data->flags)) {
led_set_brightness(data->pen_led, LED_OFF);
device_release_driver(data->dig_dev);
clear_bit(YB_DIGITIZER_IS_ON, &data->flags);
}
if (kbd_on && !test_bit(YB_KBD_IS_ON, &data->flags)) {
r = device_reprobe(data->kbd_dev);
if (r)
dev_warn(data->dev, "Reprobe of keyboard touchscreen failed: %d\n", r);
data->set_kbd_backlight(data, data->brightness);
set_bit(YB_KBD_IS_ON, &data->flags);
}
if (digitizer_on && !test_bit(YB_DIGITIZER_IS_ON, &data->flags)) {
r = device_reprobe(data->dig_dev);
if (r)
dev_warn(data->dev, "Reprobe of digitizer failed: %d\n", r);
led_set_brightness(data->pen_led, LED_FULL);
set_bit(YB_DIGITIZER_IS_ON, &data->flags);
}
}
static void yogabook_toggle_digitizer_mode(struct yogabook_data *data)
{
if (test_bit(YB_SUSPENDED, &data->flags))
return;
if (test_bit(YB_DIGITIZER_MODE, &data->flags))
clear_bit(YB_DIGITIZER_MODE, &data->flags);
else
set_bit(YB_DIGITIZER_MODE, &data->flags);
/*
* We are called from the ACPI core and the driver [un]binding which is
* done also needs ACPI functions, use a workqueue to avoid deadlocking.
*/
schedule_work(&data->work);
}
static irqreturn_t yogabook_backside_hall_irq(int irq, void *_data)
{
struct yogabook_data *data = _data;
if (gpiod_get_value(data->backside_hall_gpio))
set_bit(YB_TABLET_MODE, &data->flags);
else
clear_bit(YB_TABLET_MODE, &data->flags);
schedule_work(&data->work);
return IRQ_HANDLED;
}
#define kbd_led_to_yogabook(cdev) container_of(cdev, struct yogabook_data, kbd_bl_led)
static enum led_brightness kbd_brightness_get(struct led_classdev *cdev)
{
struct yogabook_data *data = kbd_led_to_yogabook(cdev);
return data->brightness;
}
static int kbd_brightness_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct yogabook_data *data = kbd_led_to_yogabook(cdev);
if ((value < 0) || (value > YB_KBD_BL_MAX))
return -EINVAL;
data->brightness = value;
if (!test_bit(YB_KBD_IS_ON, &data->flags))
return 0;
return data->set_kbd_backlight(data, data->brightness);
}
static struct gpiod_lookup_table yogabook_gpios = {
.table = {
GPIO_LOOKUP("INT33FF:02", 18, "backside_hall_sw", GPIO_ACTIVE_LOW),
{}
},
};
static struct led_lookup_data yogabook_pen_led = {
.provider = "platform::indicator",
.con_id = "pen-icon-led",
};
static int yogabook_probe(struct device *dev, struct yogabook_data *data,
const char *kbd_bl_led_name)
{
int r;
data->dev = dev;
data->brightness = YB_KBD_BL_DEFAULT;
set_bit(YB_KBD_IS_ON, &data->flags);
set_bit(YB_DIGITIZER_IS_ON, &data->flags);
INIT_WORK(&data->work, yogabook_work);
yogabook_pen_led.dev_id = dev_name(dev);
led_add_lookup(&yogabook_pen_led);
data->pen_led = devm_led_get(dev, "pen-icon-led");
led_remove_lookup(&yogabook_pen_led);
if (IS_ERR(data->pen_led))
return dev_err_probe(dev, PTR_ERR(data->pen_led), "Getting pen icon LED\n");
yogabook_gpios.dev_id = dev_name(dev);
gpiod_add_lookup_table(&yogabook_gpios);
data->backside_hall_gpio = devm_gpiod_get(dev, "backside_hall_sw", GPIOD_IN);
gpiod_remove_lookup_table(&yogabook_gpios);
if (IS_ERR(data->backside_hall_gpio))
return dev_err_probe(dev, PTR_ERR(data->backside_hall_gpio),
"Getting backside_hall_sw GPIO\n");
r = gpiod_to_irq(data->backside_hall_gpio);
if (r < 0)
return dev_err_probe(dev, r, "Getting backside_hall_sw IRQ\n");
data->backside_hall_irq = r;
/* Set default brightness before enabling the IRQ */
data->set_kbd_backlight(data, YB_KBD_BL_DEFAULT);
r = request_irq(data->backside_hall_irq, yogabook_backside_hall_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"backside_hall_sw", data);
if (r)
return dev_err_probe(dev, r, "Requesting backside_hall_sw IRQ\n");
schedule_work(&data->work);
data->kbd_bl_led.name = kbd_bl_led_name;
data->kbd_bl_led.brightness_set_blocking = kbd_brightness_set;
data->kbd_bl_led.brightness_get = kbd_brightness_get;
data->kbd_bl_led.max_brightness = YB_KBD_BL_MAX;
r = devm_led_classdev_register(dev, &data->kbd_bl_led);
if (r < 0) {
dev_err_probe(dev, r, "Registering backlight LED device\n");
goto error_free_irq;
}
dev_set_drvdata(dev, data);
return 0;
error_free_irq:
free_irq(data->backside_hall_irq, data);
cancel_work_sync(&data->work);
return r;
}
static void yogabook_remove(struct yogabook_data *data)
{
int r = 0;
free_irq(data->backside_hall_irq, data);
cancel_work_sync(&data->work);
if (!test_bit(YB_KBD_IS_ON, &data->flags))
r |= device_reprobe(data->kbd_dev);
if (!test_bit(YB_DIGITIZER_IS_ON, &data->flags))
r |= device_reprobe(data->dig_dev);
if (r)
dev_warn(data->dev, "Reprobe of devices failed\n");
}
static int yogabook_suspend(struct device *dev)
{
struct yogabook_data *data = dev_get_drvdata(dev);
set_bit(YB_SUSPENDED, &data->flags);
flush_work(&data->work);
if (test_bit(YB_KBD_IS_ON, &data->flags))
data->set_kbd_backlight(data, 0);
return 0;
}
static int yogabook_resume(struct device *dev)
{
struct yogabook_data *data = dev_get_drvdata(dev);
if (test_bit(YB_KBD_IS_ON, &data->flags))
data->set_kbd_backlight(data, data->brightness);
clear_bit(YB_SUSPENDED, &data->flags);
/* Check for YB_TABLET_MODE changes made during suspend */
schedule_work(&data->work);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(yogabook_pm_ops, yogabook_suspend, yogabook_resume);
/********** WMI driver code **********/
/*
* To control keyboard backlight, call the method KBLC() of the TCS1 ACPI
* device (Goodix touchpad acts as virtual sensor keyboard).
*/
static int yogabook_wmi_set_kbd_backlight(struct yogabook_data *data,
uint8_t level)
{
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_object_list input;
union acpi_object param;
acpi_status status;
dev_dbg(data->dev, "Set KBLC level to %u\n", level);
/* Ensure keyboard touchpad is on before we call KBLC() */
acpi_device_set_power(data->kbd_adev, ACPI_STATE_D0);
input.count = 1;
input.pointer = &param;
param.type = ACPI_TYPE_INTEGER;
param.integer.value = YB_KBD_BL_MAX - level;
status = acpi_evaluate_object(acpi_device_handle(data->kbd_adev), "KBLC",
&input, &output);
if (ACPI_FAILURE(status)) {
dev_err(data->dev, "Failed to call KBLC method: 0x%x\n", status);
return status;
}
kfree(output.pointer);
return 0;
}
static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context)
{
struct device *dev = &wdev->dev;
struct yogabook_data *data;
int r;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
data->kbd_adev = acpi_dev_get_first_match_dev("GDIX1001", NULL, -1);
if (!data->kbd_adev)
return dev_err_probe(dev, -ENODEV, "Cannot find the touchpad device in ACPI tables\n");
data->dig_adev = acpi_dev_get_first_match_dev("WCOM0019", NULL, -1);
if (!data->dig_adev) {
r = dev_err_probe(dev, -ENODEV, "Cannot find the digitizer device in ACPI tables\n");
goto error_put_devs;
}
data->kbd_dev = get_device(acpi_get_first_physical_node(data->kbd_adev));
if (!data->kbd_dev || !data->kbd_dev->driver) {
r = -EPROBE_DEFER;
goto error_put_devs;
}
data->dig_dev = get_device(acpi_get_first_physical_node(data->dig_adev));
if (!data->dig_dev || !data->dig_dev->driver) {
r = -EPROBE_DEFER;
goto error_put_devs;
}
data->set_kbd_backlight = yogabook_wmi_set_kbd_backlight;
r = yogabook_probe(dev, data, "ybwmi::kbd_backlight");
if (r)
goto error_put_devs;
return 0;
error_put_devs:
put_device(data->dig_dev);
put_device(data->kbd_dev);
acpi_dev_put(data->dig_adev);
acpi_dev_put(data->kbd_adev);
return r;
}
static void yogabook_wmi_remove(struct wmi_device *wdev)
{
struct yogabook_data *data = dev_get_drvdata(&wdev->dev);
yogabook_remove(data);
put_device(data->dig_dev);
put_device(data->kbd_dev);
acpi_dev_put(data->dig_adev);
acpi_dev_put(data->kbd_adev);
}
static void yogabook_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy)
{
yogabook_toggle_digitizer_mode(dev_get_drvdata(&wdev->dev));
}
static const struct wmi_device_id yogabook_wmi_id_table[] = {
{
.guid_string = YB_MBTN_EVENT_GUID,
},
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(wmi, yogabook_wmi_id_table);
static struct wmi_driver yogabook_wmi_driver = {
.driver = {
.name = "yogabook-wmi",
.pm = pm_sleep_ptr(&yogabook_pm_ops),
},
.no_notify_data = true,
.id_table = yogabook_wmi_id_table,
.probe = yogabook_wmi_probe,
.remove = yogabook_wmi_remove,
.notify = yogabook_wmi_notify,
};
/********** platform driver code **********/
static struct gpiod_lookup_table yogabook_pdev_gpios = {
.dev_id = YB_PDEV_NAME,
.table = {
GPIO_LOOKUP("INT33FF:00", 95, "pen_touch_event", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FF:03", 52, "enable_keyboard_led", GPIO_ACTIVE_HIGH),
{}
},
};
static int yogabook_pdev_set_kbd_backlight(struct yogabook_data *data, u8 level)
{
struct pwm_state state = {
.period = YB_KBD_BL_PWM_PERIOD,
.duty_cycle = YB_KBD_BL_PWM_PERIOD * level / YB_KBD_BL_MAX,
.enabled = level,
};
pwm_apply_state(data->kbd_bl_pwm, &state);
gpiod_set_value(data->kbd_bl_led_enable, level ? 1 : 0);
return 0;
}
static irqreturn_t yogabook_pen_touch_irq(int irq, void *data)
{
yogabook_toggle_digitizer_mode(data);
return IRQ_HANDLED;
}
static int yogabook_pdev_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct yogabook_data *data;
int r;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
data->kbd_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-goodix_ts");
if (!data->kbd_dev || !data->kbd_dev->driver) {
r = -EPROBE_DEFER;
goto error_put_devs;
}
data->dig_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "i2c-wacom");
if (!data->dig_dev || !data->dig_dev->driver) {
r = -EPROBE_DEFER;
goto error_put_devs;
}
gpiod_add_lookup_table(&yogabook_pdev_gpios);
data->pen_touch_event = devm_gpiod_get(dev, "pen_touch_event", GPIOD_IN);
data->kbd_bl_led_enable = devm_gpiod_get(dev, "enable_keyboard_led", GPIOD_OUT_HIGH);
gpiod_remove_lookup_table(&yogabook_pdev_gpios);
if (IS_ERR(data->pen_touch_event)) {
r = dev_err_probe(dev, PTR_ERR(data->pen_touch_event),
"Getting pen_touch_event GPIO\n");
goto error_put_devs;
}
if (IS_ERR(data->kbd_bl_led_enable)) {
r = dev_err_probe(dev, PTR_ERR(data->kbd_bl_led_enable),
"Getting enable_keyboard_led GPIO\n");
goto error_put_devs;
}
data->kbd_bl_pwm = devm_pwm_get(dev, "pwm_soc_lpss_2");
if (IS_ERR(data->kbd_bl_pwm)) {
r = dev_err_probe(dev, PTR_ERR(data->kbd_bl_pwm),
"Getting keyboard backlight PWM\n");
goto error_put_devs;
}
r = gpiod_to_irq(data->pen_touch_event);
if (r < 0) {
dev_err_probe(dev, r, "Getting pen_touch_event IRQ\n");
goto error_put_devs;
}
data->pen_touch_irq = r;
r = request_irq(data->pen_touch_irq, yogabook_pen_touch_irq, IRQF_TRIGGER_FALLING,
"pen_touch_event", data);
if (r) {
dev_err_probe(dev, r, "Requesting pen_touch_event IRQ\n");
goto error_put_devs;
}
data->set_kbd_backlight = yogabook_pdev_set_kbd_backlight;
r = yogabook_probe(dev, data, "yogabook::kbd_backlight");
if (r)
goto error_free_irq;
return 0;
error_free_irq:
free_irq(data->pen_touch_irq, data);
cancel_work_sync(&data->work);
error_put_devs:
put_device(data->dig_dev);
put_device(data->kbd_dev);
return r;
}
static void yogabook_pdev_remove(struct platform_device *pdev)
{
struct yogabook_data *data = platform_get_drvdata(pdev);
yogabook_remove(data);
free_irq(data->pen_touch_irq, data);
cancel_work_sync(&data->work);
put_device(data->dig_dev);
put_device(data->kbd_dev);
}
static struct platform_driver yogabook_pdev_driver = {
.probe = yogabook_pdev_probe,
.remove_new = yogabook_pdev_remove,
.driver = {
.name = YB_PDEV_NAME,
.pm = pm_sleep_ptr(&yogabook_pm_ops),
},
};
static int __init yogabook_module_init(void)
{
int r;
r = wmi_driver_register(&yogabook_wmi_driver);
if (r)
return r;
r = platform_driver_register(&yogabook_pdev_driver);
if (r)
wmi_driver_unregister(&yogabook_wmi_driver);
return r;
}
static void __exit yogabook_module_exit(void)
{
platform_driver_unregister(&yogabook_pdev_driver);
wmi_driver_unregister(&yogabook_wmi_driver);
}
module_init(yogabook_module_init);
module_exit(yogabook_module_exit);
MODULE_ALIAS("platform:" YB_PDEV_NAME);
MODULE_AUTHOR("Yauhen Kharuzhy");
MODULE_DESCRIPTION("Lenovo Yoga Book driver");
MODULE_LICENSE("GPL v2");

View File

@ -581,7 +581,7 @@ static const struct hwmon_ops thermal_ops = {
};
// Allocate up to 8 fans and temperatures
static const struct hwmon_channel_info *thermal_channel_info[] = {
static const struct hwmon_channel_info * const thermal_channel_info[] = {
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_LABEL,
HWMON_F_INPUT | HWMON_F_LABEL,

View File

@ -14,6 +14,7 @@
#include <linux/acpi.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/dmi.h>
@ -168,11 +169,11 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support");
*/
#define LENOVO_CERT_THUMBPRINT_GUID "C59119ED-1C0D-4806-A8E9-59AA318176C4"
#define TLMI_POP_PWD (1 << 0)
#define TLMI_PAP_PWD (1 << 1)
#define TLMI_HDD_PWD (1 << 2)
#define TLMI_SYS_PWD (1 << 3)
#define TLMI_CERT (1 << 7)
#define TLMI_POP_PWD BIT(0) /* Supervisor */
#define TLMI_PAP_PWD BIT(1) /* Power-on */
#define TLMI_HDD_PWD BIT(2) /* HDD/NVME */
#define TLMI_SMP_PWD BIT(6) /* System Management */
#define TLMI_CERT BIT(7) /* Certificate Based */
#define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj)
#define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj)
@ -195,6 +196,7 @@ static const char * const level_options[] = {
};
static struct think_lmi tlmi_priv;
static struct class *fw_attr_class;
static DEFINE_MUTEX(tlmi_mutex);
/* ------ Utility functions ------------*/
/* Strip out CR if one is present */
@ -437,6 +439,9 @@ static ssize_t new_password_store(struct kobject *kobj,
/* Strip out CR if one is present, setting password won't work if it is present */
strip_cr(new_pwd);
/* Use lock in case multiple WMI operations needed */
mutex_lock(&tlmi_mutex);
pwdlen = strlen(new_pwd);
/* pwdlen == 0 is allowed to clear the password */
if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen))) {
@ -456,9 +461,9 @@ static ssize_t new_password_store(struct kobject *kobj,
sprintf(pwd_type, "mhdp%d", setting->index);
} else if (setting == tlmi_priv.pwd_nvme) {
if (setting->level == TLMI_LEVEL_USER)
sprintf(pwd_type, "unvp%d", setting->index);
sprintf(pwd_type, "udrp%d", setting->index);
else
sprintf(pwd_type, "mnvp%d", setting->index);
sprintf(pwd_type, "adrp%d", setting->index);
} else {
sprintf(pwd_type, "%s", setting->pwd_type);
}
@ -493,6 +498,7 @@ static ssize_t new_password_store(struct kobject *kobj,
kfree(auth_str);
}
out:
mutex_unlock(&tlmi_mutex);
kfree(new_pwd);
return ret ?: count;
}
@ -879,6 +885,11 @@ static umode_t auth_attr_is_visible(struct kobject *kobj,
return 0;
}
/* Don't display un-needed settings if opcode available */
if ((attr == &auth_encoding.attr || attr == &auth_kbdlang.attr) &&
tlmi_priv.opcode_support)
return 0;
return attr->mode;
}
@ -981,6 +992,9 @@ static ssize_t current_value_store(struct kobject *kobj,
/* Strip out CR if one is present */
strip_cr(new_setting);
/* Use lock in case multiple WMI operations needed */
mutex_lock(&tlmi_mutex);
/* Check if certificate authentication is enabled and active */
if (tlmi_priv.certificate_support && tlmi_priv.pwd_admin->cert_installed) {
if (!tlmi_priv.pwd_admin->signature || !tlmi_priv.pwd_admin->save_signature) {
@ -1001,7 +1015,33 @@ static ssize_t current_value_store(struct kobject *kobj,
tlmi_priv.pwd_admin->save_signature);
if (ret)
goto out;
} else { /* Non certiifcate based authentication */
} else if (tlmi_priv.opcode_support) {
/*
* If opcode support is present use that interface.
* Note - this sets the variable and then the password as separate
* WMI calls. Function tlmi_save_bios_settings will error if the
* password is incorrect.
*/
set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name,
new_setting);
if (!set_str) {
ret = -ENOMEM;
goto out;
}
ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str);
if (ret)
goto out;
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
ret = tlmi_opcode_setting("WmiOpcodePasswordAdmin",
tlmi_priv.pwd_admin->password);
if (ret)
goto out;
}
ret = tlmi_save_bios_settings("");
} else { /* old non-opcode based authentication method (deprecated) */
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
tlmi_priv.pwd_admin->password,
@ -1039,6 +1079,7 @@ static ssize_t current_value_store(struct kobject *kobj,
kobject_uevent(&tlmi_priv.class_dev->kobj, KOBJ_CHANGE);
}
out:
mutex_unlock(&tlmi_mutex);
kfree(auth_str);
kfree(set_str);
kfree(new_setting);
@ -1483,11 +1524,11 @@ static int tlmi_analyze(void)
tlmi_priv.pwd_power->valid = true;
if (tlmi_priv.opcode_support) {
tlmi_priv.pwd_system = tlmi_create_auth("sys", "system");
tlmi_priv.pwd_system = tlmi_create_auth("smp", "system");
if (!tlmi_priv.pwd_system)
goto fail_clear_attr;
if (tlmi_priv.pwdcfg.core.password_state & TLMI_SYS_PWD)
if (tlmi_priv.pwdcfg.core.password_state & TLMI_SMP_PWD)
tlmi_priv.pwd_system->valid = true;
tlmi_priv.pwd_hdd = tlmi_create_auth("hdd", "hdd");
@ -1498,6 +1539,10 @@ static int tlmi_analyze(void)
if (!tlmi_priv.pwd_nvme)
goto fail_clear_attr;
/* Set default hdd/nvme index to 1 as there is no device 0 */
tlmi_priv.pwd_hdd->index = 1;
tlmi_priv.pwd_nvme->index = 1;
if (tlmi_priv.pwdcfg.core.password_state & TLMI_HDD_PWD) {
/* Check if PWD is configured and set index to first drive found */
if (tlmi_priv.pwdcfg.ext.hdd_user_password ||

View File

@ -10524,8 +10524,8 @@ unlock:
static void dytc_profile_refresh(void)
{
enum platform_profile_option profile;
int output, err = 0;
int perfmode, funcmode;
int output = 0, err = 0;
int perfmode, funcmode = 0;
mutex_lock(&dytc_mutex);
if (dytc_capabilities & BIT(DYTC_FC_MMC)) {
@ -10538,6 +10538,8 @@ static void dytc_profile_refresh(void)
err = dytc_command(DYTC_CMD_GET, &output);
/* Check if we are PSC mode, or have AMT enabled */
funcmode = (output >> DYTC_GET_FUNCTION_BIT) & 0xF;
} else { /* Unknown profile mode */
err = -ENODEV;
}
mutex_unlock(&dytc_mutex);
if (err)

View File

@ -3037,7 +3037,7 @@ static int toshiba_acpi_hwmon_read(struct device *dev, enum hwmon_sensor_types t
return -EOPNOTSUPP;
}
static const struct hwmon_channel_info *toshiba_acpi_hwmon_info[] = {
static const struct hwmon_channel_info * const toshiba_acpi_hwmon_info[] = {
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
NULL
};

View File

@ -248,7 +248,9 @@ static acpi_status get_event_data(const struct wmi_block *wblock, struct acpi_bu
* @wdev: A wmi bus device from a driver
* @length: Required buffer size
*
* Allocates memory needed for buffer, stores the buffer size in that memory
* Allocates memory needed for buffer, stores the buffer size in that memory.
*
* Return: 0 on success or a negative error code for failure.
*/
int set_required_buffer_size(struct wmi_device *wdev, u64 length)
{
@ -262,14 +264,57 @@ int set_required_buffer_size(struct wmi_device *wdev, u64 length)
EXPORT_SYMBOL_GPL(set_required_buffer_size);
/**
* wmi_evaluate_method - Evaluate a WMI method
* wmi_instance_count - Get number of WMI object instances
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
*
* Get the number of WMI object instances.
*
* Returns: Number of WMI object instances or negative error code.
*/
int wmi_instance_count(const char *guid_string)
{
struct wmi_block *wblock;
acpi_status status;
status = find_guid(guid_string, &wblock);
if (ACPI_FAILURE(status)) {
if (status == AE_BAD_PARAMETER)
return -EINVAL;
return -ENODEV;
}
return wmidev_instance_count(&wblock->dev);
}
EXPORT_SYMBOL_GPL(wmi_instance_count);
/**
* wmidev_instance_count - Get number of WMI object instances
* @wdev: A wmi bus device from a driver
*
* Get the number of WMI object instances.
*
* Returns: Number of WMI object instances.
*/
u8 wmidev_instance_count(struct wmi_device *wdev)
{
struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
return wblock->gblock.instance_count;
}
EXPORT_SYMBOL_GPL(wmidev_instance_count);
/**
* wmi_evaluate_method - Evaluate a WMI method (deprecated)
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
* @instance: Instance index
* @method_id: Method ID to call
* @in: Buffer containing input for the method call
* @out: Empty buffer to return the method results
*
* Call an ACPI-WMI method
* Call an ACPI-WMI method, the caller must free @out.
*
* Return: acpi_status signaling success or error.
*/
acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method_id,
const struct acpi_buffer *in, struct acpi_buffer *out)
@ -294,7 +339,9 @@ EXPORT_SYMBOL_GPL(wmi_evaluate_method);
* @in: Buffer containing input for the method call
* @out: Empty buffer to return the method results
*
* Call an ACPI-WMI method
* Call an ACPI-WMI method, the caller must free @out.
*
* Return: acpi_status signaling success or error.
*/
acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 method_id,
const struct acpi_buffer *in, struct acpi_buffer *out)
@ -411,7 +458,9 @@ static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
* @instance: Instance index
* @out: Empty buffer to return the contents of the data block to
*
* Return the contents of an ACPI-WMI data block to a buffer
* Query a ACPI-WMI block, the caller must free @out.
*
* Return: ACPI object containing the content of the WMI block.
*/
acpi_status wmi_query_block(const char *guid_string, u8 instance,
struct acpi_buffer *out)
@ -427,6 +476,15 @@ acpi_status wmi_query_block(const char *guid_string, u8 instance,
}
EXPORT_SYMBOL_GPL(wmi_query_block);
/**
* wmidev_block_query - Return contents of a WMI block
* @wdev: A wmi bus device from a driver
* @instance: Instance index
*
* Query an ACPI-WMI block, the caller must free the result.
*
* Return: ACPI object containing the content of the WMI block.
*/
union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
{
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
@ -440,12 +498,14 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
EXPORT_SYMBOL_GPL(wmidev_block_query);
/**
* wmi_set_block - Write to a WMI block
* wmi_set_block - Write to a WMI block (deprecated)
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
* @instance: Instance index
* @in: Buffer containing new values for the data block
*
* Write the contents of the input buffer to an ACPI-WMI data block
* Write the contents of the input buffer to an ACPI-WMI data block.
*
* Return: acpi_status signaling success or error.
*/
acpi_status wmi_set_block(const char *guid_string, u8 instance,
const struct acpi_buffer *in)
@ -549,12 +609,14 @@ static void wmi_notify_debug(u32 value, void *context)
}
/**
* wmi_install_notify_handler - Register handler for WMI events
* wmi_install_notify_handler - Register handler for WMI events (deprecated)
* @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
* @handler: Function to handle notifications
* @data: Data to be returned to handler when event is fired
*
* Register a handler for events sent to the ACPI-WMI mapper device.
*
* Return: acpi_status signaling success or error.
*/
acpi_status wmi_install_notify_handler(const char *guid,
wmi_notify_handler handler,
@ -593,10 +655,12 @@ acpi_status wmi_install_notify_handler(const char *guid,
EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
/**
* wmi_remove_notify_handler - Unregister handler for WMI events
* wmi_remove_notify_handler - Unregister handler for WMI events (deprecated)
* @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
*
* Unregister handler for events sent to the ACPI-WMI mapper device.
*
* Return: acpi_status signaling success or error.
*/
acpi_status wmi_remove_notify_handler(const char *guid)
{
@ -638,12 +702,14 @@ acpi_status wmi_remove_notify_handler(const char *guid)
EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
/**
* wmi_get_event_data - Get WMI data associated with an event
* wmi_get_event_data - Get WMI data associated with an event (deprecated)
*
* @event: Event to find
* @out: Buffer to hold event data. out->pointer should be freed with kfree()
* @out: Buffer to hold event data
*
* Returns extra data associated with an event in WMI.
* Get extra data associated with an WMI event, the caller needs to free @out.
*
* Return: acpi_status signaling success or error.
*/
acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out)
{
@ -664,7 +730,9 @@ EXPORT_SYMBOL_GPL(wmi_get_event_data);
* wmi_has_guid - Check if a GUID is available
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
*
* Check if a given GUID is defined by _WDG
* Check if a given GUID is defined by _WDG.
*
* Return: True if GUID is available, false otherwise.
*/
bool wmi_has_guid(const char *guid_string)
{
@ -673,12 +741,12 @@ bool wmi_has_guid(const char *guid_string)
EXPORT_SYMBOL_GPL(wmi_has_guid);
/**
* wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID
* wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID (deprecated)
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
*
* Find the _UID of ACPI device associated with this WMI GUID.
*
* Return: The ACPI _UID field value or NULL if the WMI GUID was not found
* Return: The ACPI _UID field value or NULL if the WMI GUID was not found.
*/
char *wmi_get_acpi_device_uid(const char *guid_string)
{
@ -1454,6 +1522,12 @@ int __must_check __wmi_driver_register(struct wmi_driver *driver,
}
EXPORT_SYMBOL(__wmi_driver_register);
/**
* wmi_driver_unregister() - Unregister a WMI driver
* @driver: WMI driver to unregister
*
* Unregisters a WMI driver from the WMI bus.
*/
void wmi_driver_unregister(struct wmi_driver *driver)
{
driver_unregister(&driver->driver);

View File

@ -24,7 +24,7 @@ static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = {
},
};
static struct x86_gpio_button asus_me176c_tf103c_lid = {
static const struct x86_gpio_button asus_me176c_tf103c_lid __initconst = {
.button = {
.code = SW_LID,
.active_low = true,
@ -175,10 +175,10 @@ const struct x86_dev_info asus_me176c_info __initconst = {
.serdev_info = asus_me176c_serdevs,
.serdev_count = ARRAY_SIZE(asus_me176c_serdevs),
.gpio_button = &asus_me176c_tf103c_lid,
.gpio_button_count = 1,
.gpiod_lookup_tables = asus_me176c_gpios,
.bat_swnode = &generic_lipo_hv_4v35_battery_node,
.modules = bq24190_modules,
.invalid_aei_gpiochip = "INT33FC:02",
};
/* Asus TF103C tablets have an Android factory img with everything hardcoded */
@ -318,8 +318,8 @@ const struct x86_dev_info asus_tf103c_info __initconst = {
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpio_button = &asus_me176c_tf103c_lid,
.gpio_button_count = 1,
.gpiod_lookup_tables = asus_tf103c_gpios,
.bat_swnode = &asus_tf103c_battery_node,
.modules = bq24190_modules,
.invalid_aei_gpiochip = "INT33FC:02",
};

View File

@ -124,6 +124,7 @@ static int serdev_count;
static struct i2c_client **i2c_clients;
static struct platform_device **pdevs;
static struct serdev_device **serdevs;
static struct gpio_keys_button *buttons;
static struct gpiod_lookup_table * const *gpiod_lookup_tables;
static const struct software_node *bat_swnode;
static void (*exit_handler)(void);
@ -238,6 +239,7 @@ static void x86_android_tablet_cleanup(void)
platform_device_unregister(pdevs[i]);
kfree(pdevs);
kfree(buttons);
for (i = 0; i < i2c_client_count; i++)
i2c_unregister_device(i2c_clients[i]);
@ -353,22 +355,30 @@ static __init int x86_android_tablet_init(void)
}
}
if (dev_info->gpio_button) {
struct gpio_keys_platform_data pdata = {
.buttons = &dev_info->gpio_button->button,
.nbuttons = 1,
};
if (dev_info->gpio_button_count) {
struct gpio_keys_platform_data pdata = { };
struct gpio_desc *gpiod;
/* Get GPIO for the gpio-button */
ret = x86_android_tablet_get_gpiod(dev_info->gpio_button->chip,
dev_info->gpio_button->pin, &gpiod);
if (ret < 0) {
buttons = kcalloc(dev_info->gpio_button_count, sizeof(*buttons), GFP_KERNEL);
if (!buttons) {
x86_android_tablet_cleanup();
return ret;
return -ENOMEM;
}
dev_info->gpio_button->button.gpio = desc_to_gpio(gpiod);
for (i = 0; i < dev_info->gpio_button_count; i++) {
ret = x86_android_tablet_get_gpiod(dev_info->gpio_button[i].chip,
dev_info->gpio_button[i].pin, &gpiod);
if (ret < 0) {
x86_android_tablet_cleanup();
return ret;
}
buttons[i] = dev_info->gpio_button[i].button;
buttons[i].gpio = desc_to_gpio(gpiod);
}
pdata.buttons = buttons;
pdata.nbuttons = dev_info->gpio_button_count;
pdevs[pdev_count] = platform_device_register_data(NULL, "gpio-keys",
PLATFORM_DEVID_AUTO,

View File

@ -58,6 +58,17 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
},
.driver_data = (void *)&chuwi_hi8_info,
},
{
/* Cyberbook T116 Android version */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Default string"),
DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
/* Above strings are much too generic, also match on SKU + BIOS date */
DMI_MATCH(DMI_PRODUCT_SKU, "20170531"),
DMI_MATCH(DMI_BIOS_DATE, "07/12/2017"),
},
.driver_data = (void *)&cyberbook_t116_info,
},
{
/* CZC P10T */
.ident = "CZC ODEON TPC-10 (\"P10T\")",
@ -127,13 +138,22 @@ const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
.driver_data = (void *)&medion_lifetab_s10346_info,
},
{
/* Nextbook Ares 8 */
/* Nextbook Ares 8 (BYT version) */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"),
},
.driver_data = (void *)&nextbook_ares8_info,
},
{
/* Nextbook Ares 8A (CHT version)*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "CherryTrail"),
DMI_MATCH(DMI_BIOS_VERSION, "M882"),
},
.driver_data = (void *)&nextbook_ares8a_info,
},
{
/* Peaq C1010 */
.matches = {

View File

@ -147,6 +147,32 @@ static const struct platform_device_info lenovo_yb1_x90_pdevs[] __initconst = {
},
};
/*
* DSDT says UART path is "\\_SB.PCIO.URT1" with a letter 'O' instead of
* the number '0' add the link manually.
*/
static const struct x86_serdev_info lenovo_yb1_x90_serdevs[] __initconst = {
{
.ctrl_hid = "8086228A",
.ctrl_uid = "1",
.ctrl_devname = "serial0",
.serdev_hid = "BCM2E1A",
},
};
static const struct x86_gpio_button lenovo_yb1_x90_lid __initconst = {
.button = {
.code = SW_LID,
.active_low = true,
.desc = "lid_sw",
.type = EV_SW,
.wakeup = true,
.debounce_interval = 50,
},
.chip = "INT33FF:02",
.pin = 19,
};
static struct gpiod_lookup_table lenovo_yb1_x90_goodix_gpios = {
.dev_id = "i2c-goodix_ts",
.table = {
@ -203,6 +229,10 @@ const struct x86_dev_info lenovo_yogabook_x90_info __initconst = {
.i2c_client_count = ARRAY_SIZE(lenovo_yb1_x90_i2c_clients),
.pdev_info = lenovo_yb1_x90_pdevs,
.pdev_count = ARRAY_SIZE(lenovo_yb1_x90_pdevs),
.serdev_info = lenovo_yb1_x90_serdevs,
.serdev_count = ARRAY_SIZE(lenovo_yb1_x90_serdevs),
.gpio_button = &lenovo_yb1_x90_lid,
.gpio_button_count = 1,
.gpiod_lookup_tables = lenovo_yb1_x90_gpios,
.init = lenovo_yb1_x90_init,
};
@ -239,7 +269,7 @@ static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = {
.properties = lenovo_yoga_tab2_830_1050_bq24190_props,
};
static struct x86_gpio_button lenovo_yoga_tab2_830_1050_lid = {
static const struct x86_gpio_button lenovo_yoga_tab2_830_1050_lid __initconst = {
.button = {
.code = SW_LID,
.active_low = true,
@ -267,6 +297,14 @@ static struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __init
.dev_name = "lsm303d",
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* AL3320A ambient light sensor */
.board_info = {
.type = "al3320a",
.addr = 0x1c,
.dev_name = "al3320a",
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* bq24292i battery charger */
.board_info = {
@ -357,6 +395,7 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = {
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpio_button = &lenovo_yoga_tab2_830_1050_lid,
.gpio_button_count = 1,
.gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios,
.bat_swnode = &generic_lipo_hv_4v35_battery_node,
.modules = bq24190_modules,

View File

@ -94,7 +94,7 @@ const struct x86_dev_info acer_b1_750_info __initconst = {
* which is not described in the ACPI tables in anyway.
* Use the x86-android-tablets infra to create a gpio-button device for this.
*/
static struct x86_gpio_button advantech_mica_071_button = {
static const struct x86_gpio_button advantech_mica_071_button __initconst = {
.button = {
.code = KEY_PROG1,
.active_low = true,
@ -109,6 +109,7 @@ static struct x86_gpio_button advantech_mica_071_button = {
const struct x86_dev_info advantech_mica_071_info __initconst = {
.gpio_button = &advantech_mica_071_button,
.gpio_button_count = 1,
};
/*
@ -196,6 +197,45 @@ const struct x86_dev_info chuwi_hi8_info __initconst = {
.init = chuwi_hi8_init,
};
/*
* Cyberbook T116 Android version
* This comes in both Windows and Android versions and even on Android
* the DSDT is mostly sane. This tablet has 2 extra general purpose buttons
* in the button row with the power + volume-buttons labeled P and F.
* Use the x86-android-tablets infra to create a gpio-button device for these.
*/
static const struct x86_gpio_button cyberbook_t116_buttons[] __initconst = {
{
.button = {
.code = KEY_PROG1,
.active_low = true,
.desc = "prog1_key",
.type = EV_KEY,
.wakeup = false,
.debounce_interval = 50,
},
.chip = "INT33FF:00",
.pin = 30,
},
{
.button = {
.code = KEY_PROG2,
.active_low = true,
.desc = "prog2_key",
.type = EV_KEY,
.wakeup = false,
.debounce_interval = 50,
},
.chip = "INT33FF:03",
.pin = 48,
},
};
const struct x86_dev_info cyberbook_t116_info __initconst = {
.gpio_button = cyberbook_t116_buttons,
.gpio_button_count = ARRAY_SIZE(cyberbook_t116_buttons),
};
#define CZC_EC_EXTRA_PORT 0x68
#define CZC_EC_ANDROID_KEYS 0x63
@ -311,7 +351,7 @@ const struct x86_dev_info medion_lifetab_s10346_info __initconst = {
.gpiod_lookup_tables = medion_lifetab_s10346_gpios,
};
/* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */
/* Nextbook Ares 8 (BYT) tablets have an Android factory img with everything hardcoded */
static const char * const nextbook_ares8_accel_mount_matrix[] = {
"0", "-1", "0",
"-1", "0", "0",
@ -377,7 +417,70 @@ const struct x86_dev_info nextbook_ares8_info __initconst = {
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpiod_lookup_tables = nextbook_ares8_gpios,
.invalid_aei_gpiochip = "INT33FC:02",
};
/* Nextbook Ares 8A (CHT) tablets have an Android factory img with everything hardcoded */
static const char * const nextbook_ares8a_accel_mount_matrix[] = {
"1", "0", "0",
"0", "-1", "0",
"0", "0", "1"
};
static const struct property_entry nextbook_ares8a_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8a_accel_mount_matrix),
{ }
};
static const struct software_node nextbook_ares8a_accel_node = {
.properties = nextbook_ares8a_accel_props,
};
static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initconst = {
{
/* Freescale MMA8653FC accel */
.board_info = {
.type = "mma8653",
.addr = 0x1d,
.dev_name = "mma8653",
.swnode = &nextbook_ares8a_accel_node,
},
.adapter_path = "\\_SB_.PCI0.I2C3",
}, {
/* FT5416DQ9 touchscreen controller */
.board_info = {
.type = "edt-ft5x06",
.addr = 0x38,
.dev_name = "ft5416",
.swnode = &nextbook_ares8_touchscreen_node,
},
.adapter_path = "\\_SB_.PCI0.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FF:01",
.index = 17,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
},
};
static struct gpiod_lookup_table nextbook_ares8a_ft5416_gpios = {
.dev_id = "i2c-ft5416",
.table = {
GPIO_LOOKUP("INT33FF:01", 25, "reset", GPIO_ACTIVE_LOW),
{ }
},
};
static struct gpiod_lookup_table * const nextbook_ares8a_gpios[] = {
&nextbook_ares8a_ft5416_gpios,
NULL
};
const struct x86_dev_info nextbook_ares8a_info __initconst = {
.i2c_client_info = nextbook_ares8a_i2c_clients,
.i2c_client_count = ARRAY_SIZE(nextbook_ares8a_i2c_clients),
.gpiod_lookup_tables = nextbook_ares8a_gpios,
};
/*
@ -386,7 +489,7 @@ const struct x86_dev_info nextbook_ares8_info __initconst = {
* This button has a WMI interface, but that is broken. Instead of trying to
* use the broken WMI interface, instantiate a gpio_keys device for this.
*/
static struct x86_gpio_button peaq_c1010_button = {
static const struct x86_gpio_button peaq_c1010_button __initconst = {
.button = {
.code = KEY_SOUND,
.active_low = true,
@ -401,6 +504,7 @@ static struct x86_gpio_button peaq_c1010_button = {
const struct x86_dev_info peaq_c1010_info __initconst = {
.gpio_button = &peaq_c1010_button,
.gpio_button_count = 1,
/*
* Move the ACPI event handler used by the broken WMI interface out of
* the way. This is the only event handler on INT33FC:00.

View File

@ -73,10 +73,11 @@ struct x86_dev_info {
const struct x86_i2c_client_info *i2c_client_info;
const struct platform_device_info *pdev_info;
const struct x86_serdev_info *serdev_info;
struct x86_gpio_button *gpio_button;
const struct x86_gpio_button *gpio_button;
int i2c_client_count;
int pdev_count;
int serdev_count;
int gpio_button_count;
int (*init)(void);
void (*exit)(void);
};
@ -93,6 +94,7 @@ extern const struct x86_dev_info advantech_mica_071_info;
extern const struct x86_dev_info asus_me176c_info;
extern const struct x86_dev_info asus_tf103c_info;
extern const struct x86_dev_info chuwi_hi8_info;
extern const struct x86_dev_info cyberbook_t116_info;
extern const struct x86_dev_info czc_p10t;
extern const struct x86_dev_info lenovo_yogabook_x90_info;
extern const struct x86_dev_info lenovo_yogabook_x91_info;
@ -100,6 +102,7 @@ extern const struct x86_dev_info lenovo_yoga_tab2_830_1050_info;
extern const struct x86_dev_info lenovo_yt3_info;
extern const struct x86_dev_info medion_lifetab_s10346_info;
extern const struct x86_dev_info nextbook_ares8_info;
extern const struct x86_dev_info nextbook_ares8a_info;
extern const struct x86_dev_info peaq_c1010_info;
extern const struct x86_dev_info whitelabel_tm800a550l_info;
extern const struct x86_dev_info xiaomi_mipad2_info;

View File

@ -414,6 +414,8 @@ extern bool acpi_is_pnp_device(struct acpi_device *);
typedef void (*wmi_notify_handler) (u32 value, void *context);
int wmi_instance_count(const char *guid);
extern acpi_status wmi_evaluate_method(const char *guid, u8 instance,
u32 method_id,
const struct acpi_buffer *in,

View File

@ -13,25 +13,46 @@
#include <linux/mod_devicetable.h>
#include <uapi/linux/wmi.h>
/**
* struct wmi_device - WMI device structure
* @dev: Device associated with this WMI device
* @setable: True for devices implementing the Set Control Method
*
* This represents WMI devices discovered by the WMI driver core.
*/
struct wmi_device {
struct device dev;
/* True for data blocks implementing the Set Control Method */
/* private: used by the WMI driver core */
bool setable;
};
/* evaluate the ACPI method associated with this device */
extern acpi_status wmidev_evaluate_method(struct wmi_device *wdev,
u8 instance, u32 method_id,
const struct acpi_buffer *in,
struct acpi_buffer *out);
/* Caller must kfree the result. */
extern union acpi_object *wmidev_block_query(struct wmi_device *wdev,
u8 instance);
u8 wmidev_instance_count(struct wmi_device *wdev);
extern int set_required_buffer_size(struct wmi_device *wdev, u64 length);
/**
* struct wmi_driver - WMI driver structure
* @driver: Driver model structure
* @id_table: List of WMI GUIDs supported by this driver
* @no_notify_data: WMI events provide no event data
* @probe: Callback for device binding
* @remove: Callback for device unbinding
* @notify: Callback for receiving WMI events
* @filter_callback: Callback for filtering device IOCTLs
*
* This represents WMI drivers which handle WMI devices.
* @filter_callback is only necessary for drivers which
* want to set up a WMI IOCTL interface.
*/
struct wmi_driver {
struct device_driver driver;
const struct wmi_device_id *id_table;
@ -47,8 +68,24 @@ struct wmi_driver {
extern int __must_check __wmi_driver_register(struct wmi_driver *driver,
struct module *owner);
extern void wmi_driver_unregister(struct wmi_driver *driver);
/**
* wmi_driver_register() - Helper macro to register a WMI driver
* @driver: wmi_driver struct
*
* Helper macro for registering a WMI driver. It automatically passes
* THIS_MODULE to the underlying function.
*/
#define wmi_driver_register(driver) __wmi_driver_register((driver), THIS_MODULE)
/**
* module_wmi_driver() - Helper macro to register/unregister a WMI driver
* @__wmi_driver: wmi_driver struct
*
* Helper macro for WMI drivers which do not do anything special in module
* init/exit. This eliminates a lot of boilerplate. Each module may only
* use this macro once, and calling it replaces module_init() and module_exit().
*/
#define module_wmi_driver(__wmi_driver) \
module_driver(__wmi_driver, wmi_driver_register, \
wmi_driver_unregister)

View File

@ -15,7 +15,7 @@ struct process_cmd_struct {
int arg;
};
static const char *version_str = "v1.15";
static const char *version_str = "v1.16";
static const int supported_api_ver = 2;
static struct isst_if_platform_info isst_platform_info;
@ -2113,7 +2113,6 @@ static void set_fact_enable(int arg)
else
for_each_online_power_domain_in_set(set_fact_for_cpu, NULL, NULL,
NULL, &enable);
isst_ctdp_display_information_end(outf);
if (!fact_enable_fail && enable && auto_mode) {
/*
@ -2192,10 +2191,13 @@ static void set_fact_enable(int arg)
isst_display_result(&id, outf, "turbo-freq --auto", "enable", 0);
}
isst_ctdp_display_information_end(outf);
return;
error_disp:
isst_display_result(&id, outf, "turbo-freq --auto", "enable", ret);
isst_ctdp_display_information_end(outf);
}
@ -2261,9 +2263,6 @@ static void dump_clos_config_for_cpu(struct isst_id *id, void *arg1, void *arg2,
struct isst_clos_config clos_config;
int ret;
if (id->cpu < 0)
return;
ret = isst_pm_get_clos(id, current_clos, &clos_config);
if (ret)
isst_display_error_info_message(1, "isst_pm_get_clos failed", 0, 0);
@ -2437,12 +2436,16 @@ static void set_clos_assoc(int arg)
isst_display_error_info_message(1, "Invalid clos id\n", 0, 0);
exit(0);
}
isst_ctdp_display_information_start(outf);
if (max_target_cpus)
for_each_online_target_cpu_in_set(set_clos_assoc_for_cpu, NULL,
NULL, NULL, NULL);
else {
isst_display_error_info_message(1, "Invalid target cpu. Specify with [-c|--cpu]", 0, 0);
}
isst_ctdp_display_information_end(outf);
}
static void get_clos_assoc_for_cpu(struct isst_id *id, void *arg1, void *arg2, void *arg3,

View File

@ -641,16 +641,30 @@ static int tpmi_pm_qos_config(struct isst_id *id, int enable_clos,
int priority_type)
{
struct isst_core_power info;
int ret;
int i, ret, saved_punit;
info.get_set = 1;
info.socket_id = id->pkg;
info.power_domain_id = id->punit;
info.enable = enable_clos;
info.priority_type = priority_type;
ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info);
if (ret == -1)
return ret;
saved_punit = id->punit;
/* Set for all other dies also. This is per package setting */
for (i = 0; i < MAX_PUNIT_PER_DIE; i++) {
id->punit = i;
if (isst_is_punit_valid(id)) {
info.power_domain_id = i;
ret = tpmi_process_ioctl(ISST_IF_CORE_POWER_STATE, &info);
if (ret == -1) {
id->punit = saved_punit;
return ret;
}
}
}
id->punit = saved_punit;
return 0;
}
@ -686,7 +700,7 @@ int tpmi_set_clos(struct isst_id *id, int clos,
struct isst_clos_config *clos_config)
{
struct isst_clos_param info;
int ret;
int i, ret, saved_punit;
info.get_set = 1;
info.socket_id = id->pkg;
@ -702,9 +716,22 @@ int tpmi_set_clos(struct isst_id *id, int clos,
if (info.max_freq_mhz <= 0xff)
info.max_freq_mhz *= 100;
ret = tpmi_process_ioctl(ISST_IF_CLOS_PARAM, &info);
if (ret == -1)
return ret;
saved_punit = id->punit;
/* Set for all other dies also. This is per package setting */
for (i = 0; i < MAX_PUNIT_PER_DIE; i++) {
id->punit = i;
if (isst_is_punit_valid(id)) {
info.power_domain_id = i;
ret = tpmi_process_ioctl(ISST_IF_CLOS_PARAM, &info);
if (ret == -1) {
id->punit = saved_punit;
return ret;
}
}
}
id->punit = saved_punit;
debug_printf("set cpu:%d clos:%d min:%d max:%d\n", id->cpu, clos,
clos_config->clos_min, clos_config->clos_max);