summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/sysfs-platform-intel-pmc20
-rw-r--r--Documentation/admin-guide/laptops/thinkpad-acpi.rst20
-rw-r--r--Documentation/driver-api/surface_aggregator/client.rst4
-rw-r--r--Documentation/driver-api/surface_aggregator/clients/dtx.rst718
-rw-r--r--Documentation/driver-api/surface_aggregator/clients/index.rst1
-rw-r--r--Documentation/userspace-api/ioctl/ioctl-number.rst2
-rw-r--r--MAINTAINERS49
-rw-r--r--drivers/mfd/intel_pmt.c112
-rw-r--r--drivers/platform/mellanox/mlxbf-bootctl.c2
-rw-r--r--drivers/platform/mellanox/mlxreg-hotplug.c4
-rw-r--r--drivers/platform/surface/Kconfig69
-rw-r--r--drivers/platform/surface/Makefile3
-rw-r--r--drivers/platform/surface/aggregator/controller.c16
-rw-r--r--drivers/platform/surface/surface_aggregator_registry.c626
-rw-r--r--drivers/platform/surface/surface_dtx.c1289
-rw-r--r--drivers/platform/surface/surface_platform_profile.c190
-rw-r--r--drivers/platform/surface/surfacepro3_button.c2
-rw-r--r--drivers/platform/x86/Kconfig26
-rw-r--r--drivers/platform/x86/Makefile4
-rw-r--r--drivers/platform/x86/adv_swbutton.c121
-rw-r--r--drivers/platform/x86/asus-laptop.c2
-rw-r--r--drivers/platform/x86/asus-wmi.c5
-rw-r--r--drivers/platform/x86/classmate-laptop.c2
-rw-r--r--drivers/platform/x86/dell/alienware-wmi.c4
-rw-r--r--drivers/platform/x86/dell/dell-smbios-base.c2
-rw-r--r--drivers/platform/x86/dell/dell-smbios-wmi.c3
-rw-r--r--drivers/platform/x86/dell/dell-wmi-descriptor.c5
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c3
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c3
-rw-r--r--drivers/platform/x86/dell/dell-wmi-sysman/sysman.c34
-rw-r--r--drivers/platform/x86/dell/dell-wmi.c3
-rw-r--r--drivers/platform/x86/gigabyte-wmi.c203
-rw-r--r--drivers/platform/x86/hp-wmi.c101
-rw-r--r--drivers/platform/x86/intel-vbtn.c3
-rw-r--r--drivers/platform/x86/intel-wmi-sbl-fw-update.c3
-rw-r--r--drivers/platform/x86/intel-wmi-thunderbolt.c3
-rw-r--r--drivers/platform/x86/intel_chtdc_ti_pwrbtn.c2
-rw-r--r--drivers/platform/x86/intel_pmc_core.c493
-rw-r--r--drivers/platform/x86/intel_pmc_core.h53
-rw-r--r--drivers/platform/x86/intel_pmt_class.c46
-rw-r--r--drivers/platform/x86/intel_pmt_class.h1
-rw-r--r--drivers/platform/x86/intel_pmt_telemetry.c20
-rw-r--r--drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c33
-rw-r--r--drivers/platform/x86/lg-laptop.c2
-rw-r--r--drivers/platform/x86/panasonic-laptop.c2
-rw-r--r--drivers/platform/x86/pmc_atom.c28
-rw-r--r--drivers/platform/x86/thinkpad_acpi.c256
-rw-r--r--drivers/platform/x86/touchscreen_dmi.c37
-rw-r--r--drivers/platform/x86/wmi-bmof.c3
-rw-r--r--drivers/platform/x86/wmi.c6
-rw-r--r--drivers/platform/x86/xo15-ebook.c6
-rw-r--r--include/linux/surface_aggregator/controller.h74
-rw-r--r--include/linux/surface_aggregator/device.h31
-rw-r--r--include/linux/wmi.h2
-rw-r--r--include/uapi/linux/surface_aggregator/dtx.h146
-rw-r--r--tools/power/x86/intel-speed-select/isst-config.c30
-rw-r--r--tools/power/x86/intel-speed-select/isst-display.c12
57 files changed, 4643 insertions, 297 deletions
diff --git a/Documentation/ABI/testing/sysfs-platform-intel-pmc b/Documentation/ABI/testing/sysfs-platform-intel-pmc
new file mode 100644
index 000000000000..ef199af75ab0
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-intel-pmc
@@ -0,0 +1,20 @@
+What: /sys/devices/platform/<platform>/etr3
+Date: Apr 2021
+KernelVersion: 5.13
+Contact: "Tomas Winkler" <tomas.winkler@intel.com>
+Description:
+ The file exposes "Extended Test Mode Register 3" global
+ reset bits. The bits are used during an Intel platform
+ manufacturing process to indicate that consequent reset
+ of the platform is a "global reset". This type of reset
+ is required in order for manufacturing configurations
+ to take effect.
+
+ Display global reset setting bits for PMC.
+ * bit 31 - global reset is locked
+ * bit 20 - global reset is set
+ Writing bit 20 value to the etr3 will induce
+ a platform "global reset" upon consequent platform reset,
+ in case the register is not locked.
+ The "global reset bit" should be locked on a production
+ system and the file is in read-only mode.
diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
index 91fd6846ce17..6721a80a2d4f 100644
--- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst
+++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst
@@ -52,6 +52,7 @@ detailed description):
- LCD Shadow (PrivacyGuard) enable and disable
- Lap mode sensor
- Setting keyboard language
+ - WWAN Antenna type
A compatibility table by model and feature is maintained on the web
site, http://ibm-acpi.sf.net/. I appreciate any success or failure
@@ -1490,6 +1491,25 @@ fr(French), fr-ch(French(Switzerland)), hu(Hungarian), it(Italy), jp (Japan),
nl(Dutch), nn(Norway), pl(Polish), pt(portugese), sl(Slovenian), sv(Sweden),
tr(Turkey)
+WWAN Antenna type
+-----------------
+
+sysfs: wwan_antenna_type
+
+On some newer Thinkpads we need to set SAR value based on the antenna
+type. This interface will be used by userspace to get the antenna type
+and set the corresponding SAR value, as is required for FCC certification.
+
+The available commands are::
+
+ cat /sys/devices/platform/thinkpad_acpi/wwan_antenna_type
+
+Currently 2 antenna types are supported as mentioned below:
+- type a
+- type b
+
+The property is read-only. If the platform doesn't have support the sysfs
+class is not created.
Adaptive keyboard
-----------------
diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst
index 26d13085a117..e519d374c378 100644
--- a/Documentation/driver-api/surface_aggregator/client.rst
+++ b/Documentation/driver-api/surface_aggregator/client.rst
@@ -248,7 +248,7 @@ This example defines a function
.. code-block:: c
- int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
+ static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
executing the specified request, with the controller passed in when calling
said function. In this example, the argument is provided via the ``arg``
@@ -296,7 +296,7 @@ This invocation of the macro defines a function
.. code-block:: c
- int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
+ static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
executing the specified request, using the device IDs and controller given
in the client device. The full list of such macros for client devices is:
diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst
new file mode 100644
index 000000000000..e7e7c20007f0
--- /dev/null
+++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst
@@ -0,0 +1,718 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+.. |__u16| replace:: :c:type:`__u16 <__u16>`
+.. |sdtx_event| replace:: :c:type:`struct sdtx_event <sdtx_event>`
+.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code <sdtx_event_code>`
+.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info <sdtx_base_info>`
+.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode <sdtx_device_mode>`
+
+======================================================
+User-Space DTX (Clipboard Detachment System) Interface
+======================================================
+
+The ``surface_dtx`` driver is responsible for proper clipboard detachment
+and re-attachment handling. To this end, it provides the ``/dev/surface/dtx``
+device file, through which it can interface with a user-space daemon. This
+daemon is then ultimately responsible for determining and taking necessary
+actions, such as unmounting devices attached to the base,
+unloading/reloading the graphics-driver, user-notifications, etc.
+
+There are two basic communication principles used in this driver: Commands
+(in other parts of the documentation also referred to as requests) and
+events. Commands are sent to the EC and may have a different implications in
+different contexts. Events are sent by the EC upon some internal state
+change. Commands are always driver-initiated, whereas events are always
+initiated by the EC.
+
+.. contents::
+
+Nomenclature
+============
+
+* **Clipboard:**
+ The detachable upper part of the Surface Book, housing the screen and CPU.
+
+* **Base:**
+ The lower part of the Surface Book from which the clipboard can be
+ detached, optionally (model dependent) housing the discrete GPU (dGPU).
+
+* **Latch:**
+ The mechanism keeping the clipboard attached to the base in normal
+ operation and allowing it to be detached when requested.
+
+* **Silently ignored commands:**
+ The command is accepted by the EC as a valid command and acknowledged
+ (following the standard communication protocol), but the EC does not act
+ upon it, i.e. ignores it.e upper part of the
+
+
+Detachment Process
+==================
+
+Warning: This part of the documentation is based on reverse engineering and
+testing and thus may contain errors or be incomplete.
+
+Latch States
+------------
+
+The latch mechanism has two major states: *open* and *closed*. In the
+*closed* state (default), the clipboard is secured to the base, whereas in
+the *open* state, the clipboard can be removed by a user.
+
+The latch can additionally be locked and, correspondingly, unlocked, which
+can influence the detachment procedure. Specifically, this locking mechanism
+is intended to prevent the dGPU, positioned in the base of the device, from
+being hot-unplugged while in use. More details can be found in the
+documentation for the detachment procedure below. By default, the latch is
+unlocked.
+
+Detachment Procedure
+--------------------
+
+Note that the detachment process is governed fully by the EC. The
+``surface_dtx`` driver only relays events from the EC to user-space and
+commands from user-space to the EC, i.e. it does not influence this process.
+
+The detachment process is started with the user pressing the *detach* button
+on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL.
+Following that:
+
+1. The EC turns on the indicator led on the detach-button, sends a
+ *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further
+ instructions/commands. In case the latch is unlocked, the led will flash
+ green. If the latch has been locked, the led will be solid red
+
+2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where
+ an appropriate user-space daemon can handle it and send instructions back
+ to the EC via IOCTLs provided by this driver.
+
+3. The EC waits for instructions from user-space and acts according to them.
+ If the EC does not receive any instructions in a given period, it will
+ time out and continue as follows:
+
+ - If the latch is unlocked, the EC will open the latch and the clipboard
+ can be detached from the base. This is the exact behavior as without
+ this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM``
+ description below for more details on the follow-up behavior of the EC.
+
+ - If the latch is locked, the EC will *not* open the latch, meaning the
+ clipboard cannot be detached from the base. Furthermore, the EC sends
+ an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel
+ reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details).
+
+Valid responses by a user-space daemon to a detachment request event are:
+
+- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the
+ detachment process. Furthermore, the EC will send a detach-request event,
+ similar to the user pressing the detach-button to cancel said process (see
+ below).
+
+- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the
+ latch, after which the user can separate clipboard and base.
+
+ As this changes the latch state, a *latch-status* event
+ (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened
+ successfully. If the EC fails to open the latch, e.g. due to hardware
+ error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be
+ sent with the cancel reason indicating the specific failure.
+
+ If the latch is currently locked, the latch will automatically be
+ unlocked before it is opened.
+
+- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout.
+ No other actions will be performed, i.e. the detachment process will neither
+ be completed nor canceled, and the EC will still be waiting for further
+ responses.
+
+- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process,
+ similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button
+ press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``)
+ is send in response to this. In contrast to those, however, this command
+ does not trigger a new detachment process if none is currently in
+ progress.
+
+- Do nothing. The detachment process eventually times out as described in
+ point 3.
+
+See :ref:`ioctls` for more details on these responses.
+
+It is important to note that, if the user presses the detach button at any
+point when a detachment operation is in progress (i.e. after the EC has sent
+the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before it
+received the corresponding response concluding the process), the detachment
+process is canceled on the EC-level and an identical event is being sent.
+Thus a *detach-request* event, by itself, does not signal the start of the
+detachment process.
+
+The detachment process may further be canceled by the EC due to hardware
+failures or a low clipboard battery. This is done via a cancel event
+(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason.
+
+
+User-Space Interface Documentation
+==================================
+
+Error Codes and Status Values
+-----------------------------
+
+Error and status codes are divided into different categories, which can be
+used to determine if the status code is an error, and, if it is, the
+severity and type of that error. The current categories are:
+
+.. flat-table:: Overview of Status/Error Categories.
+ :widths: 2 1 3
+ :header-rows: 1
+
+ * - Name
+ - Value
+ - Short Description
+
+ * - ``STATUS``
+ - ``0x0000``
+ - Non-error status codes.
+
+ * - ``RUNTIME_ERROR``
+ - ``0x1000``
+ - Non-critical runtime errors.
+
+ * - ``HARDWARE_ERROR``
+ - ``0x2000``
+ - Critical hardware failures.
+
+ * - ``UNKNOWN``
+ - ``0xF000``
+ - Unknown error codes.
+
+Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro
+can be used to determine the category of any status value. The
+``SDTX_SUCCESS()`` macro can be used to check if the status value is a
+success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure.
+
+Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN``
+category by the driver and may be implemented via their own code in the
+future.
+
+Currently used error codes are:
+
+.. flat-table:: Overview of Error Codes.
+ :widths: 2 1 1 3
+ :header-rows: 1
+
+ * - Name
+ - Category
+ - Value
+ - Short Description
+
+ * - ``SDTX_DETACH_NOT_FEASIBLE``
+ - ``RUNTIME``
+ - ``0x1001``
+ - Detachment not feasible due to low clipboard battery.
+
+ * - ``SDTX_DETACH_TIMEDOUT``
+ - ``RUNTIME``
+ - ``0x1002``
+ - Detachment process timed out while the latch was locked.
+
+ * - ``SDTX_ERR_FAILED_TO_OPEN``
+ - ``HARDWARE``
+ - ``0x2001``
+ - Failed to open latch.
+
+ * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``
+ - ``HARDWARE``
+ - ``0x2002``
+ - Failed to keep latch open.
+
+ * - ``SDTX_ERR_FAILED_TO_CLOSE``
+ - ``HARDWARE``
+ - ``0x2003``
+ - Failed to close latch.
+
+Other error codes are reserved for future use. Non-error status codes may
+overlap and are generally only unique within their use-case:
+
+.. flat-table:: Latch Status Codes.
+ :widths: 2 1 1 3
+ :header-rows: 1
+
+ * - Name
+ - Category
+ - Value
+ - Short Description
+
+ * - ``SDTX_LATCH_CLOSED``
+ - ``STATUS``
+ - ``0x0000``
+ - Latch is closed/has been closed.
+
+ * - ``SDTX_LATCH_OPENED``
+ - ``STATUS``
+ - ``0x0001``
+ - Latch is open/has been opened.
+
+.. flat-table:: Base State Codes.
+ :widths: 2 1 1 3
+ :header-rows: 1
+
+ * - Name
+ - Category
+ - Value
+ - Short Description
+
+ * - ``SDTX_BASE_DETACHED``
+ - ``STATUS``
+ - ``0x0000``
+ - Base has been detached/is not present.
+
+ * - ``SDTX_BASE_ATTACHED``
+ - ``STATUS``
+ - ``0x0001``
+ - Base has been attached/is present.
+
+Again, other codes are reserved for future use.
+
+.. _events:
+
+Events
+------
+
+Events can be received by reading from the device file. They are disabled by
+default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE``
+first. All events follow the layout prescribed by |sdtx_event|. Specific
+event types can be identified by their event code, described in
+|sdtx_event_code|. Note that other event codes are reserved for future use,
+thus an event parser must be able to handle any unknown/unsupported event
+types gracefully, by relying on the payload length given in the event header.
+
+Currently provided event types are:
+
+.. flat-table:: Overview of DTX events.
+ :widths: 2 1 1 3
+ :header-rows: 1
+
+ * - Name
+ - Code
+ - Payload
+ - Short Description
+
+ * - ``SDTX_EVENT_REQUEST``
+ - ``1``
+ - ``0`` bytes
+ - Detachment process initiated/aborted.
+
+ * - ``SDTX_EVENT_CANCEL``
+ - ``2``
+ - ``2`` bytes
+ - EC canceled detachment process.
+
+ * - ``SDTX_EVENT_BASE_CONNECTION``
+ - ``3``
+ - ``4`` bytes
+ - Base connection state changed.
+
+ * - ``SDTX_EVENT_LATCH_STATUS``
+ - ``4``
+ - ``2`` bytes
+ - Latch status changed.
+
+ * - ``SDTX_EVENT_DEVICE_MODE``
+ - ``5``
+ - ``2`` bytes
+ - Device mode changed.
+
+Individual events in more detail:
+
+``SDTX_EVENT_REQUEST``
+^^^^^^^^^^^^^^^^^^^^^^
+
+Sent when a detachment process is started or, if in progress, aborted by the
+user, either via a detach button press or a detach request
+(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space.
+
+Does not have any payload.
+
+``SDTX_EVENT_CANCEL``
+^^^^^^^^^^^^^^^^^^^^^
+
+Sent when a detachment process is canceled by the EC due to unfulfilled
+preconditions (e.g. clipboard battery too low to detach) or hardware
+failure. The reason for cancellation is given in the event payload detailed
+below and can be one of
+
+* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked.
+ The latch has neither been opened nor unlocked.
+
+* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard
+ battery.
+
+* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure).
+
+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware
+ failure).
+
+* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure).
+
+Other error codes in this context are reserved for future use.
+
+These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern
+between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or
+runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may
+happen during normal operation if certain preconditions for detachment are
+not given.
+
+.. flat-table:: Detachment Cancel Event Payload
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Field
+ - Type
+ - Description
+
+ * - ``reason``
+ - |__u16|
+ - Reason for cancellation.
+
+``SDTX_EVENT_BASE_CONNECTION``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Sent when the base connection state has changed, i.e. when the base has been
+attached, detached, or detachment has become infeasible due to low clipboard
+battery. The new state and, if a base is connected, ID of the base is
+provided as payload of type |sdtx_base_info| with its layout presented
+below:
+
+.. flat-table:: Base-Connection-Change Event Payload
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Field
+ - Type
+ - Description
+
+ * - ``state``
+ - |__u16|
+ - Base connection state.
+
+ * - ``base_id``
+ - |__u16|
+ - Type of base connected (zero if none).
+
+Possible values for ``state`` are:
+
+* ``SDTX_BASE_DETACHED``,
+* ``SDTX_BASE_ATTACHED``, and
+* ``SDTX_DETACH_NOT_FEASIBLE``.
+
+Other values are reserved for future use.
+
+``SDTX_EVENT_LATCH_STATUS``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Sent when the latch status has changed, i.e. when the latch has been opened,
+closed, or an error occurred. The current status is provided as payload:
+
+.. flat-table:: Latch-Status-Change Event Payload
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Field
+ - Type
+ - Description
+
+ * - ``status``
+ - |__u16|
+ - Latch status.
+
+Possible values for ``status`` are:
+
+* ``SDTX_LATCH_CLOSED``,
+* ``SDTX_LATCH_OPENED``,
+* ``SDTX_ERR_FAILED_TO_OPEN``,
+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and
+* ``SDTX_ERR_FAILED_TO_CLOSE``.
+
+Other values are reserved for future use.
+
+``SDTX_EVENT_DEVICE_MODE``
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Sent when the device mode has changed. The new device mode is provided as
+payload:
+
+.. flat-table:: Device-Mode-Change Event Payload
+ :widths: 1 1 4
+ :header-rows: 1
+
+ * - Field
+ - Type
+ - Description
+
+ * - ``mode``
+ - |__u16|
+ - Device operation mode.
+
+Possible values for ``mode`` are:
+
+* ``SDTX_DEVICE_MODE_TABLET``,
+* ``SDTX_DEVICE_MODE_LAPTOP``, and
+* ``SDTX_DEVICE_MODE_STUDIO``.
+
+Other values are reserved for future use.
+
+.. _ioctls:
+
+IOCTLs
+------
+
+The following IOCTLs are provided:
+
+.. flat-table:: Overview of DTX IOCTLs
+ :widths: 1 1 1 1 4
+ :header-rows: 1
+
+ * - Type
+ - Number
+ - Direction
+ - Name
+ - Description
+
+ * - ``0xA5``
+ - ``0x21``
+ - ``-``
+ - ``EVENTS_ENABLE``
+ - Enable events for the current file descriptor.
+
+ * - ``0xA5``
+ - ``0x22``
+ - ``-``
+ - ``EVENTS_DISABLE``
+ - Disable events for the current file descriptor.
+
+ * - ``0xA5``
+ - ``0x23``
+ - ``-``
+ - ``LATCH_LOCK``
+ - Lock the latch.
+
+ * - ``0xA5``
+ - ``0x24``
+ - ``-``
+ - ``LATCH_UNLOCK``
+ - Unlock the latch.
+
+ * - ``0xA5``
+ - ``0x25``
+ - ``-``
+ - ``LATCH_REQUEST``
+ - Request clipboard detachment.
+
+ * - ``0xA5``
+ - ``0x26``
+ - ``-``
+ - ``LATCH_CONFIRM``
+ - Confirm clipboard detachment request.
+
+ * - ``0xA5``
+ - ``0x27``
+ - ``-``
+ - ``LATCH_HEARTBEAT``
+ - Send heartbeat signal to EC.
+
+ * - ``0xA5``
+ - ``0x28``
+ - ``-``
+ - ``LATCH_CANCEL``
+ - Cancel detachment process.
+
+ * - ``0xA5``
+ - ``0x29``
+ - ``R``
+ - ``GET_BASE_INFO``
+ - Get current base/connection information.
+
+ * - ``0xA5``
+ - ``0x2A``
+ - ``R``
+ - ``GET_DEVICE_MODE``
+ - Get current device operation mode.
+
+ * - ``0xA5``
+ - ``0x2B``
+ - ``R``
+ - ``GET_LATCH_STATUS``
+ - Get current device latch status.
+
+``SDTX_IOCTL_EVENTS_ENABLE``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IO(0xA5, 0x22)``.
+
+Enable events for the current file descriptor. Events can be obtained by
+reading from the device, if enabled. Events are disabled by default.
+
+``SDTX_IOCTL_EVENTS_DISABLE``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IO(0xA5, 0x22)``.
+
+Disable events for the current file descriptor. Events can be obtained by
+reading from the device, if enabled. Events are disabled by default.
+
+``SDTX_IOCTL_LATCH_LOCK``
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IO(0xA5, 0x23)``.
+
+Locks the latch, causing the detachment procedure to abort without opening
+the latch on timeout. The latch is unlocked by default. This command will be
+silently ignored if the latch is already locked.
+
+``SDTX_IOCTL_LATCH_UNLOCK``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IO(0xA5, 0x24)``.
+
+Unlocks the latch, causing the detachment procedure to open the latch on
+timeout. The latch is unlocked by default. This command will not open the
+latch when sent during an ongoing detachment process. It will be silently
+ignored if the latch is already unlocked.
+
+``SDTX_IOCTL_LATCH_REQUEST``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IO(0xA5, 0x25)``.
+
+Generic latch request. Behavior depends on the context: If no
+detachment-process is active, detachment is requested. Otherwise the
+currently active detachment-process will be aborted.
+
+If a detachment process is canceled by this operation, a generic detachment
+request event (``SDTX_EVENT_REQUEST``) will be sent.
+
+This essentially behaves the same as a detachment button press.
+
+``SDTX_IOCTL_LATCH_CONFIRM``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IO(0xA5, 0x26)``.
+
+Acknowledges and confirms a latch request. If sent during an ongoing
+detachment process, this command causes the latch to be opened immediately.
+The latch will also be opened if it has been locked. In this case, the latch
+lock is reset to the unlocked state.
+
+This command will be silently ignored if there is currently no detachment
+procedure in progress.
+
+``SDTX_IOCTL_LATCH_HEARTBEAT``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IO(0xA5, 0x27)``.
+
+Sends a heartbeat, essentially resetting the detachment timeout. This
+command can be used to keep the detachment process alive while work required
+for the detachment to succeed is still in progress.
+
+This command will be silently ignored if there is currently no detachment
+procedure in progress.
+
+``SDTX_IOCTL_LATCH_CANCEL``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IO(0xA5, 0x28)``.
+
+Cancels detachment in progress (if any). If a detachment process is canceled
+by this operation, a generic detachment request event
+(``SDTX_EVENT_REQUEST``) will be sent.
+
+This command will be silently ignored if there is currently no detachment
+procedure in progress.
+
+``SDTX_IOCTL_GET_BASE_INFO``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``.
+
+Get the current base connection state (i.e. attached/detached) and the type
+of the base connected to the clipboard. This is command essentially provides
+a way to query the information provided by the base connection change event
+(``SDTX_EVENT_BASE_CONNECTION``).
+
+Possible values for ``struct sdtx_base_info.state`` are:
+
+* ``SDTX_BASE_DETACHED``,
+* ``SDTX_BASE_ATTACHED``, and
+* ``SDTX_DETACH_NOT_FEASIBLE``.
+
+Other values are reserved for future use.
+
+``SDTX_IOCTL_GET_DEVICE_MODE``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IOR(0xA5, 0x2A, __u16)``.
+
+Returns the device operation mode, indicating if and how the base is
+attached to the clipboard. This is command essentially provides a way to
+query the information provided by the device mode change event
+(``SDTX_EVENT_DEVICE_MODE``).
+
+Returned values are:
+
+* ``SDTX_DEVICE_MODE_LAPTOP``
+* ``SDTX_DEVICE_MODE_TABLET``
+* ``SDTX_DEVICE_MODE_STUDIO``
+
+See |sdtx_device_mode| for details. Other values are reserved for future
+use.
+
+
+``SDTX_IOCTL_GET_LATCH_STATUS``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Defined as ``_IOR(0xA5, 0x2B, __u16)``.
+
+Get the current latch status or (presumably) the last error encountered when
+trying to open/close the latch. This is command essentially provides a way
+to query the information provided by the latch status change event
+(``SDTX_EVENT_LATCH_STATUS``).
+
+Returned values are:
+
+* ``SDTX_LATCH_CLOSED``,
+* ``SDTX_LATCH_OPENED``,
+* ``SDTX_ERR_FAILED_TO_OPEN``,
+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and
+* ``SDTX_ERR_FAILED_TO_CLOSE``.
+
+Other values are reserved for future use.
+
+A Note on Base IDs
+------------------
+
+Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or
+``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from the EC in the lower
+byte of the combined |__u16| value, with the driver storing the EC type from
+which this ID comes in the high byte (without this, base IDs over different
+types of ECs may be overlapping).
+
+The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device
+type. This can be one of
+
+* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and
+
+* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial
+ Hub.
+
+Note that currently only the ``SSH`` type EC is supported, however ``HID``
+type is reserved for future use.
+
+Structures and Enums
+--------------------
+
+.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h
+
+API Users
+=========
+
+A user-space daemon utilizing this API can be found at
+https://github.com/linux-surface/surface-dtx-daemon.
diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst
index 3ccabce23271..98ea9946b8a2 100644
--- a/Documentation/driver-api/surface_aggregator/clients/index.rst
+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst
@@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to
:maxdepth: 1
cdev
+ dtx
san
.. only:: subproject and html
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 599bd4493944..1c28b8ef6677 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -327,6 +327,8 @@ Code Seq# Include File Comments
0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org>
0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
<mailto:luzmaximilian@gmail.com>
+0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
+ <mailto:luzmaximilian@gmail.com>
0xAA 00-3F linux/uapi/linux/userfaultfd.h
0xAB 00-1F linux/nbd.h
0xAC 00-1F linux/raw.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 837457cf70a6..03f2d6c0fc11 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -573,6 +573,12 @@ S: Maintained
F: Documentation/scsi/advansys.rst
F: drivers/scsi/advansys.c
+ADVANTECH SWBTN DRIVER
+M: Andrea Ho <Andrea.Ho@advantech.com.tw>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/x86/adv_swbutton.c
+
ADXL34X THREE-AXIS DIGITAL ACCELEROMETER DRIVER (ADXL345/ADXL346)
M: Michael Hennerich <michael.hennerich@analog.com>
S: Supported
@@ -697,6 +703,11 @@ S: Maintained
F: Documentation/i2c/busses/i2c-ali1563.rst
F: drivers/i2c/busses/i2c-ali1563.c
+ALIENWARE WMI DRIVER
+L: Dell.Client.Kernel@dell.com
+S: Maintained
+F: drivers/platform/x86/dell/alienware-wmi.c
+
ALL SENSORS DLH SERIES PRESSURE SENSORS DRIVER
M: Tomislav Denis <tomislav.denis@avl.com>
L: linux-iio@vger.kernel.org
@@ -5043,19 +5054,19 @@ F: drivers/platform/x86/dell/dell_rbu.c
DELL SMBIOS DRIVER
M: Pali Rohár <pali@kernel.org>
-M: Mario Limonciello <mario.limonciello@dell.com>
+L: Dell.Client.Kernel@dell.com
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/dell/dell-smbios.*
DELL SMBIOS SMM DRIVER
-M: Mario Limonciello <mario.limonciello@dell.com>
+L: Dell.Client.Kernel@dell.com
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/dell/dell-smbios-smm.c
DELL SMBIOS WMI DRIVER
-M: Mario Limonciello <mario.limonciello@dell.com>
+L: Dell.Client.Kernel@dell.com
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/dell/dell-smbios-wmi.c
@@ -5069,14 +5080,14 @@ F: Documentation/driver-api/dcdbas.rst
F: drivers/platform/x86/dell/dcdbas.*
DELL WMI DESCRIPTOR DRIVER
-M: Mario Limonciello <mario.limonciello@dell.com>
+L: Dell.Client.Kernel@dell.com
S: Maintained
F: drivers/platform/x86/dell/dell-wmi-descriptor.c
DELL WMI SYSMAN DRIVER
M: Divya Bharathi <divya.bharathi@dell.com>
-M: Mario Limonciello <mario.limonciello@dell.com>
M: Prasanth Ksr <prasanth.ksr@dell.com>
+L: Dell.Client.Kernel@dell.com
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-class-firmware-attributes
@@ -7551,6 +7562,12 @@ F: Documentation/filesystems/gfs2*
F: fs/gfs2/
F: include/uapi/linux/gfs2_ondisk.h
+GIGABYTE WMI DRIVER
+M: Thomas Weißschuh <thomas@weissschuh.net>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/x86/gigabyte-wmi.c
+
GNSS SUBSYSTEM
M: Johan Hovold <johan@kernel.org>
S: Maintained
@@ -9141,6 +9158,7 @@ M: Rajneesh Bhardwaj <irenic.rajneesh@gmail.com>
M: David E Box <david.e.box@intel.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
+F: Documentation/ABI/testing/sysfs-platform-intel-pmc
F: drivers/platform/x86/intel_pmc_core*
INTEL PMIC GPIO DRIVERS
@@ -9251,7 +9269,7 @@ W: https://slimbootloader.github.io/security/firmware-update.html
F: drivers/platform/x86/intel-wmi-sbl-fw-update.c
INTEL WMI THUNDERBOLT FORCE POWER DRIVER
-M: Mario Limonciello <mario.limonciello@dell.com>
+L: Dell.Client.Kernel@dell.com
S: Maintained
F: drivers/platform/x86/intel-wmi-thunderbolt.c
@@ -11452,8 +11470,8 @@ Q: https://patchwork.kernel.org/project/netdevbpf/list/
F: drivers/net/ethernet/mellanox/mlxfw/
MELLANOX HARDWARE PLATFORM SUPPORT
-M: Andy Shevchenko <andy@infradead.org>
-M: Darren Hart <dvhart@infradead.org>
+M: Hans de Goede <hdegoede@redhat.com>
+M: Mark Gross <mgross@linux.intel.com>
M: Vadim Pasternak <vadimp@nvidia.com>
L: platform-driver-x86@vger.kernel.org
S: Supported
@@ -11876,6 +11894,14 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
F: include/linux/cciss*.h
F: include/uapi/linux/cciss*.h
+MICROSOFT SURFACE DTX DRIVER
+M: Maximilian Luz <luzmaximilian@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: Documentation/driver-api/surface_aggregator/clients/dtx.rst
+F: drivers/platform/surface/surface_dtx.c
+F: include/uapi/linux/surface_aggregator/dtx.h
+
MICROSOFT SURFACE GPE LID SUPPORT DRIVER
M: Maximilian Luz <luzmaximilian@gmail.com>
L: platform-driver-x86@vger.kernel.org
@@ -11897,6 +11923,12 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/surface/surface_hotplug.c
+MICROSOFT SURFACE PLATFORM PROFILE DRIVER
+M: Maximilian Luz <luzmaximilian@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/surface/surface_platform_profile.c
+
MICROSOFT SURFACE PRO 3 BUTTON DRIVER
M: Chen Yu <yu.c.chen@intel.com>
L: platform-driver-x86@vger.kernel.org
@@ -11912,6 +11944,7 @@ F: Documentation/driver-api/surface_aggregator/
F: drivers/platform/surface/aggregator/
F: drivers/platform/surface/surface_acpi_notify.c
F: drivers/platform/surface/surface_aggregator_cdev.c
+F: drivers/platform/surface/surface_aggregator_registry.c
F: include/linux/surface_acpi_notify.h
F: include/linux/surface_aggregator/
F: include/uapi/linux/surface_aggregator/
diff --git a/drivers/mfd/intel_pmt.c b/drivers/mfd/intel_pmt.c
index 744b230cdcca..dd7eb614c28e 100644
--- a/drivers/mfd/intel_pmt.c
+++ b/drivers/mfd/intel_pmt.c
@@ -49,10 +49,14 @@ enum pmt_quirks {
/* Use shift instead of mask to read discovery table offset */
PMT_QUIRK_TABLE_SHIFT = BIT(2),
+
+ /* DVSEC not present (provided in driver data) */
+ PMT_QUIRK_NO_DVSEC = BIT(3),
};
struct pmt_platform_info {
unsigned long quirks;
+ struct intel_dvsec_header **capabilities;
};
static const struct pmt_platform_info tgl_info = {
@@ -60,6 +64,26 @@ static const struct pmt_platform_info tgl_info = {
PMT_QUIRK_TABLE_SHIFT,
};
+/* DG1 Platform with DVSEC quirk*/
+static struct intel_dvsec_header dg1_telemetry = {
+ .length = 0x10,
+ .id = 2,
+ .num_entries = 1,
+ .entry_size = 3,
+ .tbir = 0,
+ .offset = 0x466000,
+};
+
+static struct intel_dvsec_header *dg1_capabilities[] = {
+ &dg1_telemetry,
+ NULL
+};
+
+static const struct pmt_platform_info dg1_info = {
+ .quirks = PMT_QUIRK_NO_DVSEC,
+ .capabilities = dg1_capabilities,
+};
+
static int pmt_add_dev(struct pci_dev *pdev, struct intel_dvsec_header *header,
unsigned long quirks)
{
@@ -79,19 +103,18 @@ static int pmt_add_dev(struct pci_dev *pdev, struct intel_dvsec_header *header,
case DVSEC_INTEL_ID_WATCHER:
if (quirks & PMT_QUIRK_NO_WATCHER) {
dev_info(dev, "Watcher not supported\n");
- return 0;
+ return -EINVAL;
}
name = "pmt_watcher";
break;
case DVSEC_INTEL_ID_CRASHLOG:
if (quirks & PMT_QUIRK_NO_CRASHLOG) {
dev_info(dev, "Crashlog not supported\n");
- return 0;
+ return -EINVAL;
}
name = "pmt_crashlog";
break;
default:
- dev_err(dev, "Unrecognized PMT capability: %d\n", id);
return -EINVAL;
}
@@ -148,41 +171,54 @@ static int pmt_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (info)
quirks = info->quirks;
- do {
- struct intel_dvsec_header header;
- u32 table;
- u16 vid;
-
- pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC);
- if (!pos)
- break;
-
- pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER1, &vid);
- if (vid != PCI_VENDOR_ID_INTEL)
- continue;
-
- pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER2,
- &header.id);
- pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES,
- &header.num_entries);
- pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE,
- &header.entry_size);
- pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE,
- &table);
-
- header.tbir = INTEL_DVSEC_TABLE_BAR(table);
- header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
-
- ret = pmt_add_dev(pdev, &header, quirks);
- if (ret) {
- dev_warn(&pdev->dev,
- "Failed to add device for DVSEC id %d\n",
- header.id);
- continue;
- }
+ if (info && (info->quirks & PMT_QUIRK_NO_DVSEC)) {
+ struct intel_dvsec_header **header;
+
+ header = info->capabilities;
+ while (*header) {
+ ret = pmt_add_dev(pdev, *header, quirks);
+ if (ret)
+ dev_warn(&pdev->dev,
+ "Failed to add device for DVSEC id %d\n",
+ (*header)->id);
+ else
+ found_devices = true;
- found_devices = true;
- } while (true);
+ ++header;
+ }
+ } else {
+ do {
+ struct intel_dvsec_header header;
+ u32 table;
+ u16 vid;
+
+ pos = pci_find_next_ext_capability(pdev, pos, PCI_EXT_CAP_ID_DVSEC);
+ if (!pos)
+ break;
+
+ pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER1, &vid);
+ if (vid != PCI_VENDOR_ID_INTEL)
+ continue;
+
+ pci_read_config_word(pdev, pos + PCI_DVSEC_HEADER2,
+ &header.id);
+ pci_read_config_byte(pdev, pos + INTEL_DVSEC_ENTRIES,
+ &header.num_entries);
+ pci_read_config_byte(pdev, pos + INTEL_DVSEC_SIZE,
+ &header.entry_size);
+ pci_read_config_dword(pdev, pos + INTEL_DVSEC_TABLE,
+ &table);
+
+ header.tbir = INTEL_DVSEC_TABLE_BAR(table);
+ header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
+
+ ret = pmt_add_dev(pdev, &header, quirks);
+ if (ret)
+ continue;
+
+ found_devices = true;
+ } while (true);
+ }
if (!found_devices)
return -ENODEV;
@@ -200,10 +236,12 @@ static void pmt_pci_remove(struct pci_dev *pdev)
}
#define PCI_DEVICE_ID_INTEL_PMT_ADL 0x467d
+#define PCI_DEVICE_ID_INTEL_PMT_DG1 0x490e
#define PCI_DEVICE_ID_INTEL_PMT_OOBMSM 0x09a7
#define PCI_DEVICE_ID_INTEL_PMT_TGL 0x9a0d
static const struct pci_device_id pmt_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, PMT_ADL, &tgl_info) },
+ { PCI_DEVICE_DATA(INTEL, PMT_DG1, &dg1_info) },
{ PCI_DEVICE_DATA(INTEL, PMT_OOBMSM, NULL) },
{ PCI_DEVICE_DATA(INTEL, PMT_TGL, &tgl_info) },
{ }
diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c
index 5d21c6adf1ab..1c7a288b59a5 100644
--- a/drivers/platform/mellanox/mlxbf-bootctl.c
+++ b/drivers/platform/mellanox/mlxbf-bootctl.c
@@ -208,7 +208,7 @@ static ssize_t secure_boot_fuse_state_show(struct device *dev,
* 0011 = version 1, 0111 = version 2, 1111 = version 3). Upper 4 bits
* are a thermometer code indicating key programming has completed for
* key n (same encodings as the start bits). This allows for detection
- * of an interruption in the progamming process which has left the key
+ * of an interruption in the programming process which has left the key
* partially programmed (and thus invalid). The process is to burn the
* eFuse for the new key start bit, burn the key eFuses, then burn the
* eFuse for the new key complete bit.
diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c
index b013445147dd..a9db2f32658f 100644
--- a/drivers/platform/mellanox/mlxreg-hotplug.c
+++ b/drivers/platform/mellanox/mlxreg-hotplug.c
@@ -683,13 +683,13 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev)
err = devm_request_irq(&pdev->dev, priv->irq,
mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING
- | IRQF_SHARED, "mlxreg-hotplug", priv);
+ | IRQF_SHARED | IRQF_NO_AUTOEN,
+ "mlxreg-hotplug", priv);
if (err) {
dev_err(&pdev->dev, "Failed to request irq: %d\n", err);
return err;
}
- disable_irq(priv->irq);
spin_lock_init(&priv->lock);
INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler);
dev_set_drvdata(&pdev->dev, priv);
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
index 0847b2dc97bf..3105f651614f 100644
--- a/drivers/platform/surface/Kconfig
+++ b/drivers/platform/surface/Kconfig
@@ -77,6 +77,53 @@ config SURFACE_AGGREGATOR_CDEV
The provided interface is intended for debugging and development only,
and should not be used otherwise.
+config SURFACE_AGGREGATOR_REGISTRY
+ tristate "Surface System Aggregator Module Device Registry"
+ depends on SURFACE_AGGREGATOR
+ depends on SURFACE_AGGREGATOR_BUS
+ help
+ Device-registry and device-hubs for Surface System Aggregator Module
+ (SSAM) devices.
+
+ Provides a module and driver which act as a device-registry for SSAM
+ client devices that cannot be detected automatically, e.g. via ACPI.
+ Such devices are instead provided via this registry and attached via
+ device hubs, also provided in this module.
+
+ Devices provided via this registry are:
+ - Platform profile (performance-/cooling-mode) device (5th- and later
+ generations).
+ - Battery/AC devices (7th-generation).
+ - HID input devices (7th-generation).
+
+ Select M (recommended) or Y here if you want support for the above
+ mentioned devices on the corresponding Surface models. Without this
+ module, the respective devices will not be instantiated and thus any
+ functionality provided by them will be missing, even when drivers for
+ these devices are present. In other words, this module only provides
+ the respective client devices. Drivers for these devices still need to
+ be selected via the other options.
+
+config SURFACE_DTX
+ tristate "Surface DTX (Detachment System) Driver"
+ depends on SURFACE_AGGREGATOR
+ depends on INPUT
+ help
+ Driver for the Surface Book clipboard detachment system (DTX).
+
+ On the Surface Book series devices, the display part containing the
+ CPU (called the clipboard) can be detached from the base (containing a
+ battery, the keyboard, and, optionally, a discrete GPU) by (if
+ necessary) unlocking and opening the latch connecting both parts.
+
+ This driver provides a user-space interface that can influence the
+ behavior of this process, which includes the option to abort it in
+ case the base is still in use or speed it up in case it is not.
+
+ Note that this module can be built without support for the Surface
+ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case,
+ some devices, specifically the Surface Book 3, will not be supported.
+
config SURFACE_GPE
tristate "Surface GPE/Lid Support Driver"
depends on DMI
@@ -105,6 +152,28 @@ config SURFACE_HOTPLUG
Select M or Y here, if you want to (fully) support hot-plugging of
dGPU devices on the Surface Book 2 and/or 3 during D3cold.
+config SURFACE_PLATFORM_PROFILE
+ tristate "Surface Platform Profile Driver"
+ depends on SURFACE_AGGREGATOR_REGISTRY
+ select ACPI_PLATFORM_PROFILE
+ help
+ Provides support for the ACPI platform profile on 5th- and later
+ generation Microsoft Surface devices.
+
+ More specifically, this driver provides ACPI platform profile support
+ on Microsoft Surface devices with a Surface System Aggregator Module
+ (SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In
+ other words, this driver provides platform profile support on the
+ Surface Pro 5, Surface Book 2, Surface Laptop, Surface Laptop Go and
+ later. On those devices, the platform profile can significantly
+ influence cooling behavior, e.g. setting it to 'quiet' (default) or
+ 'low-power' can significantly limit performance of the discrete GPU on
+ Surface Books, while in turn leading to lower power consumption and/or
+ less fan noise.
+
+ Select M or Y here, if you want to include ACPI platform profile
+ support on the above mentioned devices.
+
config SURFACE_PRO3_BUTTON
tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"
depends on INPUT
diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
index 990424c5f0c9..32889482de55 100644
--- a/drivers/platform/surface/Makefile
+++ b/drivers/platform/surface/Makefile
@@ -10,6 +10,9 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o
obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o
+obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
+obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
+obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
index 5bcb59ed579d..69e86cd599d3 100644
--- a/drivers/platform/surface/aggregator/controller.c
+++ b/drivers/platform/surface/aggregator/controller.c
@@ -1040,7 +1040,7 @@ static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret)
union acpi_object *obj;
u64 val;
- if (!(funcs & BIT(func)))
+ if (!(funcs & BIT_ULL(func)))
return 0; /* Not supported, leave *ret at its default value */
obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID,
@@ -1750,35 +1750,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer);
/* -- Internal SAM requests. ------------------------------------------------ */
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
.target_category = SSAM_SSH_TC_SAM,
.target_id = 0x01,
.command_id = 0x13,
.instance_id = 0x00,
});
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
.target_category = SSAM_SSH_TC_SAM,
.target_id = 0x01,
.command_id = 0x15,
.instance_id = 0x00,
});
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
.target_category = SSAM_SSH_TC_SAM,
.target_id = 0x01,
.command_id = 0x16,
.instance_id = 0x00,
});
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
.target_category = SSAM_SSH_TC_SAM,
.target_id = 0x01,
.command_id = 0x33,
.instance_id = 0x00,
});
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
.target_category = SSAM_SSH_TC_SAM,
.target_id = 0x01,
.command_id = 0x34,
@@ -2483,7 +2483,8 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
* interrupt, and let the SAM resume callback during the controller
* resume process clear it.
*/
- const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING;
+ const int irqf = IRQF_SHARED | IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
if (IS_ERR(gpiod))
@@ -2501,7 +2502,6 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
return status;
ctrl->irq.num = irq;
- disable_irq(ctrl->irq.num);
return 0;
}
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
new file mode 100644
index 000000000000..685d37a7add1
--- /dev/null
+++ b/drivers/platform/surface/surface_aggregator_registry.c
@@ -0,0 +1,626 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Surface System Aggregator Module (SSAM) client device registry.
+ *
+ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that
+ * cannot be auto-detected. Provides device-hubs and performs instantiation
+ * for these devices.
+ *
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <linux/surface_aggregator/controller.h>
+#include <linux/surface_aggregator/device.h>
+
+
+/* -- Device registry. ------------------------------------------------------ */
+
+/*
+ * SSAM device names follow the SSAM module alias, meaning they are prefixed
+ * with 'ssam:', followed by domain, category, target ID, instance ID, and
+ * function, each encoded as two-digit hexadecimal, separated by ':'. In other
+ * words, it follows the scheme
+ *
+ * ssam:dd:cc:tt:ii:ff
+ *
+ * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal
+ * values mentioned above, respectively.
+ */
+
+/* Root node. */
+static const struct software_node ssam_node_root = {
+ .name = "ssam_platform_hub",
+};
+
+/* Base device hub (devices attached to Surface Book 3 base). */
+static const struct software_node ssam_node_hub_base = {
+ .name = "ssam:00:00:02:00:00",
+ .parent = &ssam_node_root,
+};
+
+/* AC adapter. */
+static const struct software_node ssam_node_bat_ac = {
+ .name = "ssam:01:02:01:01:01",
+ .parent = &ssam_node_root,
+};
+
+/* Primary battery. */
+static const struct software_node ssam_node_bat_main = {
+ .name = "ssam:01:02:01:01:00",
+ .parent = &ssam_node_root,
+};
+
+/* Secondary battery (Surface Book 3). */
+static const struct software_node ssam_node_bat_sb3base = {
+ .name = "ssam:01:02:02:01:00",
+ .parent = &ssam_node_hub_base,
+};
+
+/* Platform profile / performance-mode device. */
+static const struct software_node ssam_node_tmp_pprof = {
+ .name = "ssam:01:03:01:00:01",
+ .parent = &ssam_node_root,
+};
+
+/* DTX / detachment-system device (Surface Book 3). */
+static const struct software_node ssam_node_bas_dtx = {
+ .name = "ssam:01:11:01:00:00",
+ .parent = &ssam_node_root,
+};
+
+/* HID keyboard. */
+static const struct software_node ssam_node_hid_main_keyboard = {
+ .name = "ssam:01:15:02:01:00",
+ .parent = &ssam_node_root,
+};
+
+/* HID touchpad. */
+static const struct software_node ssam_node_hid_main_touchpad = {
+ .name = "ssam:01:15:02:03:00",
+ .parent = &ssam_node_root,
+};
+
+/* HID device instance 5 (unknown HID device). */
+static const struct software_node ssam_node_hid_main_iid5 = {
+ .name = "ssam:01:15:02:05:00",
+ .parent = &ssam_node_root,
+};
+
+/* HID keyboard (base hub). */
+static const struct software_node ssam_node_hid_base_keyboard = {
+ .name = "ssam:01:15:02:01:00",
+ .parent = &ssam_node_hub_base,
+};
+
+/* HID touchpad (base hub). */
+static const struct software_node ssam_node_hid_base_touchpad = {
+ .name = "ssam:01:15:02:03:00",
+ .parent = &ssam_node_hub_base,
+};
+
+/* HID device instance 5 (unknown HID device, base hub). */
+static const struct software_node ssam_node_hid_base_iid5 = {
+ .name = "ssam:01:15:02:05:00",
+ .parent = &ssam_node_hub_base,
+};
+
+/* HID device instance 6 (unknown HID device, base hub). */
+static const struct software_node ssam_node_hid_base_iid6 = {
+ .name = "ssam:01:15:02:06:00",
+ .parent = &ssam_node_hub_base,
+};
+
+/* Devices for Surface Book 2. */
+static const struct software_node *ssam_node_group_sb2[] = {
+ &ssam_node_root,
+ &ssam_node_tmp_pprof,
+ NULL,
+};
+
+/* Devices for Surface Book 3. */
+static const struct software_node *ssam_node_group_sb3[] = {
+ &ssam_node_root,
+ &ssam_node_hub_base,
+ &ssam_node_bat_ac,
+ &ssam_node_bat_main,
+ &ssam_node_bat_sb3base,
+ &ssam_node_tmp_pprof,
+ &ssam_node_bas_dtx,
+ &ssam_node_hid_base_keyboard,
+ &ssam_node_hid_base_touchpad,
+ &ssam_node_hid_base_iid5,
+ &ssam_node_hid_base_iid6,
+ NULL,
+};
+
+/* Devices for Surface Laptop 1. */
+static const struct software_node *ssam_node_group_sl1[] = {
+ &ssam_node_root,
+ &ssam_node_tmp_pprof,
+ NULL,
+};
+
+/* Devices for Surface Laptop 2. */
+static const struct software_node *ssam_node_group_sl2[] = {
+ &ssam_node_root,
+ &ssam_node_tmp_pprof,
+ NULL,
+};
+
+/* Devices for Surface Laptop 3. */
+static const struct software_node *ssam_node_group_sl3[] = {
+ &ssam_node_root,
+ &ssam_node_bat_ac,
+ &ssam_node_bat_main,
+ &ssam_node_tmp_pprof,
+ &ssam_node_hid_main_keyboard,
+ &ssam_node_hid_main_touchpad,
+ &ssam_node_hid_main_iid5,
+ NULL,
+};
+
+/* Devices for Surface Laptop Go. */
+static const struct software_node *ssam_node_group_slg1[] = {
+ &ssam_node_root,
+ &ssam_node_bat_ac,
+ &ssam_node_bat_main,
+ &ssam_node_tmp_pprof,
+ NULL,
+};
+
+/* Devices for Surface Pro 5. */
+static const struct software_node *ssam_node_group_sp5[] = {
+ &ssam_node_root,
+ &ssam_node_tmp_pprof,
+ NULL,
+};
+
+/* Devices for Surface Pro 6. */
+static const struct software_node *ssam_node_group_sp6[] = {
+ &ssam_node_root,
+ &ssam_node_tmp_pprof,
+ NULL,
+};
+
+/* Devices for Surface Pro 7 and Surface Pro 7+. */
+static const struct software_node *ssam_node_group_sp7[] = {
+ &ssam_node_root,
+ &ssam_node_bat_ac,
+ &ssam_node_bat_main,
+ &ssam_node_tmp_pprof,
+ NULL,
+};
+
+
+/* -- Device registry helper functions. ------------------------------------- */
+
+static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
+{
+ u8 d, tc, tid, iid, fn;
+ int n;
+
+ n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
+ if (n != 5)
+ return -EINVAL;
+
+ uid->domain = d;
+ uid->category = tc;
+ uid->target = tid;
+ uid->instance = iid;
+ uid->function = fn;
+
+ return 0;
+}
+
+static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
+{
+ if (!is_ssam_device(dev))
+ return 0;
+
+ ssam_device_remove(to_ssam_device(dev));
+ return 0;
+}
+
+static void ssam_hub_remove_devices(struct device *parent)
+{
+ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
+}
+
+static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
+ struct fwnode_handle *node)
+{
+ struct ssam_device_uid uid;
+ struct ssam_device *sdev;
+ int status;
+
+ status = ssam_uid_from_string(fwnode_get_name(node), &uid);
+ if (status)
+ return status;
+
+ sdev = ssam_device_alloc(ctrl, uid);
+ if (!sdev)
+ return -ENOMEM;
+
+ sdev->dev.parent = parent;
+ sdev->dev.fwnode = node;
+
+ status = ssam_device_add(sdev);
+ if (status)
+ ssam_device_put(sdev);
+
+ return status;
+}
+
+static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
+ struct fwnode_handle *node)
+{
+ struct fwnode_handle *child;
+ int status;
+
+ fwnode_for_each_child_node(node, child) {
+ /*
+ * Try to add the device specified in the firmware node. If
+ * this fails with -EINVAL, the node does not specify any SSAM
+ * device, so ignore it and continue with the next one.
+ */
+
+ status = ssam_hub_add_device(parent, ctrl, child);
+ if (status && status != -EINVAL)
+ goto err;
+ }
+
+ return 0;
+err:
+ ssam_hub_remove_devices(parent);
+ return status;
+}
+
+
+/* -- SSAM base-hub driver. ------------------------------------------------- */
+
+/*
+ * Some devices (especially battery) may need a bit of time to be fully usable
+ * after being (re-)connected. This delay has been determined via
+ * experimentation.
+ */
+#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500)
+
+enum ssam_base_hub_state {
+ SSAM_BASE_HUB_UNINITIALIZED,
+ SSAM_BASE_HUB_CONNECTED,
+ SSAM_BASE_HUB_DISCONNECTED,
+};
+
+struct ssam_base_hub {
+ struct ssam_device *sdev;
+
+ enum ssam_base_hub_state state;
+ struct delayed_work update_work;
+
+ struct ssam_event_notifier notif;
+};
+
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x0d,
+ .instance_id = 0x00,
+});
+
+#define SSAM_BAS_OPMODE_TABLET 0x00
+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c
+
+static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state)
+{
+ u8 opmode;
+ int status;
+
+ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
+ if (status < 0) {
+ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
+ return status;
+ }
+
+ if (opmode != SSAM_BAS_OPMODE_TABLET)
+ *state = SSAM_BASE_HUB_CONNECTED;
+ else
+ *state = SSAM_BASE_HUB_DISCONNECTED;
+
+ return 0;
+}
+
+static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct ssam_base_hub *hub = dev_get_drvdata(dev);
+ bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
+
+ return sysfs_emit(buf, "%d\n", connected);
+}
+
+static struct device_attribute ssam_base_hub_attr_state =
+ __ATTR(state, 0444, ssam_base_hub_state_show, NULL);
+
+static struct attribute *ssam_base_hub_attrs[] = {
+ &ssam_base_hub_attr_state.attr,
+ NULL,
+};
+
+static const struct attribute_group ssam_base_hub_group = {
+ .attrs = ssam_base_hub_attrs,
+};
+
+static void ssam_base_hub_update_workfn(struct work_struct *work)
+{
+ struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work);
+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
+ enum ssam_base_hub_state state;
+ int status = 0;
+
+ status = ssam_base_hub_query_state(hub, &state);
+ if (status)
+ return;
+
+ if (hub->state == state)
+ return;
+ hub->state = state;
+
+ if (hub->state == SSAM_BASE_HUB_CONNECTED)
+ status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
+ else
+ ssam_hub_remove_devices(&hub->sdev->dev);
+
+ if (status)
+ dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
+}
+
+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
+{
+ struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif);
+ unsigned long delay;
+
+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
+ return 0;
+
+ if (event->length < 1) {
+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
+ return 0;
+ }
+
+ /*
+ * Delay update when the base is being connected to give devices/EC
+ * some time to set up.
+ */
+ delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0;
+
+ schedule_delayed_work(&hub->update_work, delay);
+
+ /*
+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
+ * consumed by the detachment system driver. We're just a (more or less)
+ * silent observer.
+ */
+ return 0;
+}
+
+static int __maybe_unused ssam_base_hub_resume(struct device *dev)
+{
+ struct ssam_base_hub *hub = dev_get_drvdata(dev);
+
+ schedule_delayed_work(&hub->update_work, 0);
+ return 0;
+}
+static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
+
+static int ssam_base_hub_probe(struct ssam_device *sdev)
+{
+ struct ssam_base_hub *hub;
+ int status;
+
+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+
+ hub->sdev = sdev;
+ hub->state = SSAM_BASE_HUB_UNINITIALIZED;
+
+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */
+ hub->notif.base.fn = ssam_base_hub_notif;
+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
+ hub->notif.event.id.target_category = SSAM_SSH_TC_BAS,
+ hub->notif.event.id.instance = 0,
+ hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
+
+ INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn);
+
+ ssam_device_set_drvdata(sdev, hub);
+
+ status = ssam_notifier_register(sdev->ctrl, &hub->notif);
+ if (status)
+ return status;
+
+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
+ if (status)
+ goto err;
+
+ schedule_delayed_work(&hub->update_work, 0);
+ return 0;
+
+err:
+ ssam_notifier_unregister(sdev->ctrl, &hub->notif);
+ cancel_delayed_work_sync(&hub->update_work);
+ ssam_hub_remove_devices(&sdev->dev);
+ return status;
+}
+
+static void ssam_base_hub_remove(struct ssam_device *sdev)
+{
+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev);
+
+ sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
+
+ ssam_notifier_unregister(sdev->ctrl, &hub->notif);
+ cancel_delayed_work_sync(&hub->update_work);
+ ssam_hub_remove_devices(&sdev->dev);
+}
+
+static const struct ssam_device_id ssam_base_hub_match[] = {
+ { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
+ { },
+};
+
+static struct ssam_device_driver ssam_base_hub_driver = {
+ .probe = ssam_base_hub_probe,
+ .remove = ssam_base_hub_remove,
+ .match_table = ssam_base_hub_match,
+ .driver = {
+ .name = "surface_aggregator_base_hub",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .pm = &ssam_base_hub_pm_ops,
+ },
+};
+
+
+/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
+
+static const struct acpi_device_id ssam_platform_hub_match[] = {
+ /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
+ { "MSHW0081", (unsigned long)ssam_node_group_sp5 },
+
+ /* Surface Pro 6 (OMBR >= 0x10) */
+ { "MSHW0111", (unsigned long)ssam_node_group_sp6 },
+
+ /* Surface Pro 7 */
+ { "MSHW0116", (unsigned long)ssam_node_group_sp7 },
+
+ /* Surface Pro 7+ */
+ { "MSHW0119", (unsigned long)ssam_node_group_sp7 },
+
+ /* Surface Book 2 */
+ { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
+
+ /* Surface Book 3 */
+ { "MSHW0117", (unsigned long)ssam_node_group_sb3 },
+
+ /* Surface Laptop 1 */
+ { "MSHW0086", (unsigned long)ssam_node_group_sl1 },
+
+ /* Surface Laptop 2 */
+ { "MSHW0112", (unsigned long)ssam_node_group_sl2 },
+
+ /* Surface Laptop 3 (13", Intel) */
+ { "MSHW0114", (unsigned long)ssam_node_group_sl3 },
+
+ /* Surface Laptop 3 (15", AMD) */
+ { "MSHW0110", (unsigned long)ssam_node_group_sl3 },
+
+ /* Surface Laptop Go 1 */
+ { "MSHW0118", (unsigned long)ssam_node_group_slg1 },
+
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match);
+
+static int ssam_platform_hub_probe(struct platform_device *pdev)
+{
+ const struct software_node **nodes;
+ struct ssam_controller *ctrl;
+ struct fwnode_handle *root;
+ int status;
+
+ nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
+ if (!nodes)
+ return -ENODEV;
+
+ /*
+ * As we're adding the SSAM client devices as children under this device
+ * and not the SSAM controller, we need to add a device link to the
+ * controller to ensure that we remove all of our devices before the
+ * controller is removed. This also guarantees proper ordering for
+ * suspend/resume of the devices on this hub.
+ */
+ ctrl = ssam_client_bind(&pdev->dev);
+ if (IS_ERR(ctrl))
+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
+
+ status = software_node_register_node_group(nodes);
+ if (status)
+ return status;
+
+ root = software_node_fwnode(&ssam_node_root);
+ if (!root) {
+ software_node_unregister_node_group(nodes);
+ return -ENOENT;
+ }
+
+ set_secondary_fwnode(&pdev->dev, root);
+
+ status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
+ if (status) {
+ set_secondary_fwnode(&pdev->dev, NULL);
+ software_node_unregister_node_group(nodes);
+ }
+
+ platform_set_drvdata(pdev, nodes);
+ return status;
+}
+
+static int ssam_platform_hub_remove(struct platform_device *pdev)
+{
+ const struct software_node **nodes = platform_get_drvdata(pdev);
+
+ ssam_hub_remove_devices(&pdev->dev);
+ set_secondary_fwnode(&pdev->dev, NULL);
+ software_node_unregister_node_group(nodes);
+ return 0;
+}
+
+static struct platform_driver ssam_platform_hub_driver = {
+ .probe = ssam_platform_hub_probe,
+ .remove = ssam_platform_hub_remove,
+ .driver = {
+ .name = "surface_aggregator_platform_hub",
+ .acpi_match_table = ssam_platform_hub_match,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+
+
+/* -- Module initialization. ------------------------------------------------ */
+
+static int __init ssam_device_hub_init(void)
+{
+ int status;
+
+ status = platform_driver_register(&ssam_platform_hub_driver);
+ if (status)
+ return status;
+
+ status = ssam_device_driver_register(&ssam_base_hub_driver);
+ if (status)
+ platform_driver_unregister(&ssam_platform_hub_driver);
+
+ return status;
+}
+module_init(ssam_device_hub_init);
+
+static void __exit ssam_device_hub_exit(void)
+{
+ ssam_device_driver_unregister(&ssam_base_hub_driver);
+ platform_driver_unregister(&ssam_platform_hub_driver);
+}
+module_exit(ssam_device_hub_exit);
+
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
new file mode 100644
index 000000000000..63ce587e79e3
--- /dev/null
+++ b/drivers/platform/surface/surface_dtx.c
@@ -0,0 +1,1289 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Surface Book (gen. 2 and later) detachment system (DTX) driver.
+ *
+ * Provides a user-space interface to properly handle clipboard/tablet
+ * (containing screen and processor) detachment from the base of the device
+ * (containing the keyboard and optionally a discrete GPU). Allows to
+ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in
+ * use), or request detachment via user-space.
+ *
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#include <linux/fs.h>
+#include <linux/input.h>
+#include <linux/ioctl.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/kref.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/surface_aggregator/controller.h>
+#include <linux/surface_aggregator/device.h>
+#include <linux/surface_aggregator/dtx.h>
+
+
+/* -- SSAM interface. ------------------------------------------------------- */
+
+enum sam_event_cid_bas {
+ SAM_EVENT_CID_DTX_CONNECTION = 0x0c,
+ SAM_EVENT_CID_DTX_REQUEST = 0x0e,
+ SAM_EVENT_CID_DTX_CANCEL = 0x0f,
+ SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11,
+};
+
+enum ssam_bas_base_state {
+ SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00,
+ SSAM_BAS_BASE_STATE_ATTACHED = 0x01,
+ SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02,
+};
+
+enum ssam_bas_latch_status {
+ SSAM_BAS_LATCH_STATUS_CLOSED = 0x00,
+ SSAM_BAS_LATCH_STATUS_OPENED = 0x01,
+ SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02,
+ SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03,
+ SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04,
+};
+
+enum ssam_bas_cancel_reason {
+ SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */
+ SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02,
+ SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03,
+ SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04,
+ SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05,
+};
+
+struct ssam_bas_base_info {
+ u8 state;
+ u8 base_id;
+} __packed;
+
+static_assert(sizeof(struct ssam_bas_base_info) == 2);
+
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x06,
+ .instance_id = 0x00,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x07,
+ .instance_id = 0x00,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x08,
+ .instance_id = 0x00,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x09,
+ .instance_id = 0x00,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x0a,
+ .instance_id = 0x00,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x0b,
+ .instance_id = 0x00,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x0c,
+ .instance_id = 0x00,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x0d,
+ .instance_id = 0x00,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, {
+ .target_category = SSAM_SSH_TC_BAS,
+ .target_id = 0x01,
+ .command_id = 0x11,
+ .instance_id = 0x00,
+});
+
+
+/* -- Main structures. ------------------------------------------------------ */
+
+enum sdtx_device_state {
+ SDTX_DEVICE_SHUTDOWN_BIT = BIT(0),
+ SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1),
+ SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2),
+ SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3),
+};
+
+struct sdtx_device {
+ struct kref kref;
+ struct rw_semaphore lock; /* Guards device and controller reference. */
+
+ struct device *dev;
+ struct ssam_controller *ctrl;
+ unsigned long flags;
+
+ struct miscdevice mdev;
+ wait_queue_head_t waitq;
+ struct mutex write_lock; /* Guards order of events/notifications. */
+ struct rw_semaphore client_lock; /* Guards client list. */
+ struct list_head client_list;
+
+ struct delayed_work state_work;
+ struct {
+ struct ssam_bas_base_info base;
+ u8 device_mode;
+ u8 latch_status;
+ } state;
+
+ struct delayed_work mode_work;
+ struct input_dev *mode_switch;
+
+ struct ssam_event_notifier notif;
+};
+
+enum sdtx_client_state {
+ SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0),
+};
+
+struct sdtx_client {
+ struct sdtx_device *ddev;
+ struct list_head node;
+ unsigned long flags;
+
+ struct fasync_struct *fasync;
+
+ struct mutex read_lock; /* Guards FIFO buffer read access. */
+ DECLARE_KFIFO(buffer, u8, 512);
+};
+
+static void __sdtx_device_release(struct kref *kref)
+{
+ struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref);
+
+ mutex_destroy(&ddev->write_lock);
+ kfree(ddev);
+}
+
+static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev)
+{
+ if (ddev)
+ kref_get(&ddev->kref);
+
+ return ddev;
+}
+
+static void sdtx_device_put(struct sdtx_device *ddev)
+{
+ if (ddev)
+ kref_put(&ddev->kref, __sdtx_device_release);
+}
+
+
+/* -- Firmware value translations. ------------------------------------------ */
+
+static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state)
+{
+ switch (state) {
+ case SSAM_BAS_BASE_STATE_ATTACHED:
+ return SDTX_BASE_ATTACHED;
+
+ case SSAM_BAS_BASE_STATE_DETACH_SUCCESS:
+ return SDTX_BASE_DETACHED;
+
+ case SSAM_BAS_BASE_STATE_NOT_FEASIBLE:
+ return SDTX_DETACH_NOT_FEASIBLE;
+
+ default:
+ dev_err(ddev->dev, "unknown base state: %#04x\n", state);
+ return SDTX_UNKNOWN(state);
+ }
+}
+
+static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status)
+{
+ switch (status) {
+ case SSAM_BAS_LATCH_STATUS_CLOSED:
+ return SDTX_LATCH_CLOSED;
+
+ case SSAM_BAS_LATCH_STATUS_OPENED:
+ return SDTX_LATCH_OPENED;
+
+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN:
+ return SDTX_ERR_FAILED_TO_OPEN;
+
+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN:
+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
+
+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE:
+ return SDTX_ERR_FAILED_TO_CLOSE;
+
+ default:
+ dev_err(ddev->dev, "unknown latch status: %#04x\n", status);
+ return SDTX_UNKNOWN(status);
+ }
+}
+
+static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason)
+{
+ switch (reason) {
+ case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE:
+ return SDTX_DETACH_NOT_FEASIBLE;
+
+ case SSAM_BAS_CANCEL_REASON_TIMEOUT:
+ return SDTX_DETACH_TIMEDOUT;
+
+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN:
+ return SDTX_ERR_FAILED_TO_OPEN;
+
+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN:
+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
+
+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE:
+ return SDTX_ERR_FAILED_TO_CLOSE;
+
+ default:
+ dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason);
+ return SDTX_UNKNOWN(reason);
+ }
+}
+
+
+/* -- IOCTLs. --------------------------------------------------------------- */
+
+static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev,
+ struct sdtx_base_info __user *buf)
+{
+ struct ssam_bas_base_info raw;
+ struct sdtx_base_info info;
+ int status;
+
+ lockdep_assert_held_read(&ddev->lock);
+
+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw);
+ if (status < 0)
+ return status;
+
+ info.state = sdtx_translate_base_state(ddev, raw.state);
+ info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id);
+
+ if (copy_to_user(buf, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf)
+{
+ u8 mode;
+ int status;
+
+ lockdep_assert_held_read(&ddev->lock);
+
+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
+ if (status < 0)
+ return status;
+
+ return put_user(mode, buf);
+}
+
+static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf)
+{
+ u8 latch;
+ int status;
+
+ lockdep_assert_held_read(&ddev->lock);
+
+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
+ if (status < 0)
+ return status;
+
+ return put_user(sdtx_translate_latch_status(ddev, latch), buf);
+}
+
+static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg)
+{
+ struct sdtx_device *ddev = client->ddev;
+
+ lockdep_assert_held_read(&ddev->lock);
+
+ switch (cmd) {
+ case SDTX_IOCTL_EVENTS_ENABLE:
+ set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
+ return 0;
+
+ case SDTX_IOCTL_EVENTS_DISABLE:
+ clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
+ return 0;
+
+ case SDTX_IOCTL_LATCH_LOCK:
+ return ssam_retry(ssam_bas_latch_lock, ddev->ctrl);
+
+ case SDTX_IOCTL_LATCH_UNLOCK:
+ return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl);
+
+ case SDTX_IOCTL_LATCH_REQUEST:
+ return ssam_retry(ssam_bas_latch_request, ddev->ctrl);
+
+ case SDTX_IOCTL_LATCH_CONFIRM:
+ return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl);
+
+ case SDTX_IOCTL_LATCH_HEARTBEAT:
+ return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl);
+
+ case SDTX_IOCTL_LATCH_CANCEL:
+ return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl);
+
+ case SDTX_IOCTL_GET_BASE_INFO:
+ return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg);
+
+ case SDTX_IOCTL_GET_DEVICE_MODE:
+ return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg);
+
+ case SDTX_IOCTL_GET_LATCH_STATUS:
+ return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct sdtx_client *client = file->private_data;
+ long status;
+
+ if (down_read_killable(&client->ddev->lock))
+ return -ERESTARTSYS;
+
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
+ up_read(&client->ddev->lock);
+ return -ENODEV;
+ }
+
+ status = __surface_dtx_ioctl(client, cmd, arg);
+
+ up_read(&client->ddev->lock);
+ return status;
+}
+
+
+/* -- File operations. ------------------------------------------------------ */
+
+static int surface_dtx_open(struct inode *inode, struct file *file)
+{
+ struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev);
+ struct sdtx_client *client;
+
+ /* Initialize client. */
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ client->ddev = sdtx_device_get(ddev);
+
+ INIT_LIST_HEAD(&client->node);
+
+ mutex_init(&client->read_lock);
+ INIT_KFIFO(client->buffer);
+
+ file->private_data = client;
+
+ /* Attach client. */
+ down_write(&ddev->client_lock);
+
+ /*
+ * Do not add a new client if the device has been shut down. Note that
+ * it's enough to hold the client_lock here as, during shutdown, we
+ * only acquire that lock and remove clients after marking the device
+ * as shut down.
+ */
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
+ up_write(&ddev->client_lock);
+ sdtx_device_put(client->ddev);
+ kfree(client);
+ return -ENODEV;
+ }
+
+ list_add_tail(&client->node, &ddev->client_list);
+ up_write(&ddev->client_lock);
+
+ stream_open(inode, file);
+ return 0;
+}
+
+static int surface_dtx_release(struct inode *inode, struct file *file)
+{
+ struct sdtx_client *client = file->private_data;
+
+ /* Detach client. */
+ down_write(&client->ddev->client_lock);
+ list_del(&client->node);
+ up_write(&client->ddev->client_lock);
+
+ /* Free client. */
+ sdtx_device_put(client->ddev);
+ mutex_destroy(&client->read_lock);
+ kfree(client);
+
+ return 0;
+}
+
+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
+{
+ struct sdtx_client *client = file->private_data;
+ struct sdtx_device *ddev = client->ddev;
+ unsigned int copied;
+ int status = 0;
+
+ if (down_read_killable(&ddev->lock))
+ return -ERESTARTSYS;
+
+ /* Make sure we're not shut down. */
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
+ up_read(&ddev->lock);
+ return -ENODEV;
+ }
+
+ do {
+ /* Check availability, wait if necessary. */
+ if (kfifo_is_empty(&client->buffer)) {
+ up_read(&ddev->lock);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ status = wait_event_interruptible(ddev->waitq,
+ !kfifo_is_empty(&client->buffer) ||
+ test_bit(SDTX_DEVICE_SHUTDOWN_BIT,
+ &ddev->flags));
+ if (status < 0)
+ return status;
+
+ if (down_read_killable(&ddev->lock))
+ return -ERESTARTSYS;
+
+ /* Need to check that we're not shut down again. */
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
+ up_read(&ddev->lock);
+ return -ENODEV;
+ }
+ }
+
+ /* Try to read from FIFO. */
+ if (mutex_lock_interruptible(&client->read_lock)) {
+ up_read(&ddev->lock);
+ return -ERESTARTSYS;
+ }
+
+ status = kfifo_to_user(&client->buffer, buf, count, &copied);
+ mutex_unlock(&client->read_lock);
+
+ if (status < 0) {
+ up_read(&ddev->lock);
+ return status;
+ }
+
+ /* We might not have gotten anything, check this here. */
+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
+ up_read(&ddev->lock);
+ return -EAGAIN;
+ }
+ } while (copied == 0);
+
+ up_read(&ddev->lock);
+ return copied;
+}
+
+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
+{
+ struct sdtx_client *client = file->private_data;
+ __poll_t events = 0;
+
+ if (down_read_killable(&client->ddev->lock))
+ return -ERESTARTSYS;
+
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
+ up_read(&client->ddev->lock);
+ return EPOLLHUP | EPOLLERR;
+ }
+
+ poll_wait(file, &client->ddev->waitq, pt);
+
+ if (!kfifo_is_empty(&client->buffer))
+ events |= EPOLLIN | EPOLLRDNORM;
+
+ up_read(&client->ddev->lock);
+ return events;
+}
+
+static int surface_dtx_fasync(int fd, struct file *file, int on)
+{
+ struct sdtx_client *client = file->private_data;
+
+ return fasync_helper(fd, file, on, &client->fasync);
+}
+
+static const struct file_operations surface_dtx_fops = {
+ .owner = THIS_MODULE,
+ .open = surface_dtx_open,
+ .release = surface_dtx_release,
+ .read = surface_dtx_read,
+ .poll = surface_dtx_poll,
+ .fasync = surface_dtx_fasync,
+ .unlocked_ioctl = surface_dtx_ioctl,
+ .compat_ioctl = surface_dtx_ioctl,
+ .llseek = no_llseek,
+};
+
+
+/* -- Event handling/forwarding. -------------------------------------------- */
+
+/*
+ * The device operation mode is not immediately updated on the EC when the
+ * base has been connected, i.e. querying the device mode inside the
+ * connection event callback yields an outdated value. Thus, we can only
+ * determine the new tablet-mode switch and device mode values after some
+ * time.
+ *
+ * These delays have been chosen by experimenting. We first delay on connect
+ * events, then check and validate the device mode against the base state and
+ * if invalid delay again by the "recheck" delay.
+ */
+#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100)
+#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100)
+
+struct sdtx_status_event {
+ struct sdtx_event e;
+ __u16 v;
+} __packed;
+
+struct sdtx_base_info_event {
+ struct sdtx_event e;
+ struct sdtx_base_info v;
+} __packed;
+
+union sdtx_generic_event {
+ struct sdtx_event common;
+ struct sdtx_status_event status;
+ struct sdtx_base_info_event base;
+};
+
+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay);
+
+/* Must be executed with ddev->write_lock held. */
+static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt)
+{
+ const size_t len = sizeof(struct sdtx_event) + evt->length;
+ struct sdtx_client *client;
+
+ lockdep_assert_held(&ddev->write_lock);
+
+ down_read(&ddev->client_lock);
+ list_for_each_entry(client, &ddev->client_list, node) {
+ if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags))
+ continue;
+
+ if (likely(kfifo_avail(&client->buffer) >= len))
+ kfifo_in(&client->buffer, (const u8 *)evt, len);
+ else
+ dev_warn(ddev->dev, "event buffer overrun\n");
+
+ kill_fasync(&client->fasync, SIGIO, POLL_IN);
+ }
+ up_read(&ddev->client_lock);
+
+ wake_up_interruptible(&ddev->waitq);
+}
+
+static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
+{
+ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif);
+ union sdtx_generic_event event;
+ size_t len;
+
+ /* Validate event payload length. */
+ switch (in->command_id) {
+ case SAM_EVENT_CID_DTX_CONNECTION:
+ len = 2 * sizeof(u8);
+ break;
+
+ case SAM_EVENT_CID_DTX_REQUEST:
+ len = 0;
+ break;
+
+ case SAM_EVENT_CID_DTX_CANCEL:
+ len = sizeof(u8);
+ break;
+
+ case SAM_EVENT_CID_DTX_LATCH_STATUS:
+ len = sizeof(u8);
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (in->length != len) {
+ dev_err(ddev->dev,
+ "unexpected payload size for event %#04x: got %u, expected %zu\n",
+ in->command_id, in->length, len);
+ return 0;
+ }
+
+ mutex_lock(&ddev->write_lock);
+
+ /* Translate event. */
+ switch (in->command_id) {
+ case SAM_EVENT_CID_DTX_CONNECTION:
+ clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
+
+ /* If state has not changed: do not send new event. */
+ if (ddev->state.base.state == in->data[0] &&
+ ddev->state.base.base_id == in->data[1])
+ goto out;
+
+ ddev->state.base.state = in->data[0];
+ ddev->state.base.base_id = in->data[1];
+
+ event.base.e.length = sizeof(struct sdtx_base_info);
+ event.base.e.code = SDTX_EVENT_BASE_CONNECTION;
+ event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]);
+ event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]);
+ break;
+
+ case SAM_EVENT_CID_DTX_REQUEST:
+ event.common.code = SDTX_EVENT_REQUEST;
+ event.common.length = 0;
+ break;
+
+ case SAM_EVENT_CID_DTX_CANCEL:
+ event.status.e.length = sizeof(u16);
+ event.status.e.code = SDTX_EVENT_CANCEL;
+ event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]);
+ break;
+
+ case SAM_EVENT_CID_DTX_LATCH_STATUS:
+ clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
+
+ /* If state has not changed: do not send new event. */
+ if (ddev->state.latch_status == in->data[0])
+ goto out;
+
+ ddev->state.latch_status = in->data[0];
+
+ event.status.e.length = sizeof(u16);
+ event.status.e.code = SDTX_EVENT_LATCH_STATUS;
+ event.status.v = sdtx_translate_latch_status(ddev, in->data[0]);
+ break;
+ }
+
+ sdtx_push_event(ddev, &event.common);
+
+ /* Update device mode on base connection change. */
+ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) {
+ unsigned long delay;
+
+ delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0;
+ sdtx_update_device_mode(ddev, delay);
+ }
+
+out:
+ mutex_unlock(&ddev->write_lock);
+ return SSAM_NOTIF_HANDLED;
+}
+
+
+/* -- State update functions. ----------------------------------------------- */
+
+static bool sdtx_device_mode_invalid(u8 mode, u8 base_state)
+{
+ return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) &&
+ (mode == SDTX_DEVICE_MODE_TABLET)) ||
+ ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) &&
+ (mode != SDTX_DEVICE_MODE_TABLET));
+}
+
+static void sdtx_device_mode_workfn(struct work_struct *work)
+{
+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work);
+ struct sdtx_status_event event;
+ struct ssam_bas_base_info base;
+ int status, tablet;
+ u8 mode;
+
+ /* Get operation mode. */
+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
+ if (status) {
+ dev_err(ddev->dev, "failed to get device mode: %d\n", status);
+ return;
+ }
+
+ /* Get base info. */
+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
+ if (status) {
+ dev_err(ddev->dev, "failed to get base info: %d\n", status);
+ return;
+ }
+
+ /*
+ * In some cases (specifically when attaching the base), the device
+ * mode isn't updated right away. Thus we check if the device mode
+ * makes sense for the given base state and try again later if it
+ * doesn't.
+ */
+ if (sdtx_device_mode_invalid(mode, base.state)) {
+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
+ return;
+ }
+
+ mutex_lock(&ddev->write_lock);
+ clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
+
+ /* Avoid sending duplicate device-mode events. */
+ if (ddev->state.device_mode == mode) {
+ mutex_unlock(&ddev->write_lock);
+ return;
+ }
+
+ ddev->state.device_mode = mode;
+
+ event.e.length = sizeof(u16);
+ event.e.code = SDTX_EVENT_DEVICE_MODE;
+ event.v = mode;
+
+ sdtx_push_event(ddev, &event.e);
+
+ /* Send SW_TABLET_MODE event. */
+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
+ input_sync(ddev->mode_switch);
+
+ mutex_unlock(&ddev->write_lock);
+}
+
+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay)
+{
+ schedule_delayed_work(&ddev->mode_work, delay);
+}
+
+/* Must be executed with ddev->write_lock held. */
+static void __sdtx_device_state_update_base(struct sdtx_device *ddev,
+ struct ssam_bas_base_info info)
+{
+ struct sdtx_base_info_event event;
+
+ lockdep_assert_held(&ddev->write_lock);
+
+ /* Prevent duplicate events. */
+ if (ddev->state.base.state == info.state &&
+ ddev->state.base.base_id == info.base_id)
+ return;
+
+ ddev->state.base = info;
+
+ event.e.length = sizeof(struct sdtx_base_info);
+ event.e.code = SDTX_EVENT_BASE_CONNECTION;
+ event.v.state = sdtx_translate_base_state(ddev, info.state);
+ event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id);
+
+ sdtx_push_event(ddev, &event.e);
+}
+
+/* Must be executed with ddev->write_lock held. */
+static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode)
+{
+ struct sdtx_status_event event;
+ int tablet;
+
+ /*
+ * Note: This function must be called after updating the base state
+ * via __sdtx_device_state_update_base(), as we rely on the updated
+ * base state value in the validity check below.
+ */
+
+ lockdep_assert_held(&ddev->write_lock);
+
+ if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) {
+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
+ return;
+ }
+
+ /* Prevent duplicate events. */
+ if (ddev->state.device_mode == mode)
+ return;
+
+ ddev->state.device_mode = mode;
+
+ /* Send event. */
+ event.e.length = sizeof(u16);
+ event.e.code = SDTX_EVENT_DEVICE_MODE;
+ event.v = mode;
+
+ sdtx_push_event(ddev, &event.e);
+
+ /* Send SW_TABLET_MODE event. */
+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
+ input_sync(ddev->mode_switch);
+}
+
+/* Must be executed with ddev->write_lock held. */
+static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status)
+{
+ struct sdtx_status_event event;
+
+ lockdep_assert_held(&ddev->write_lock);
+
+ /* Prevent duplicate events. */
+ if (ddev->state.latch_status == status)
+ return;
+
+ ddev->state.latch_status = status;
+
+ event.e.length = sizeof(struct sdtx_base_info);
+ event.e.code = SDTX_EVENT_BASE_CONNECTION;
+ event.v = sdtx_translate_latch_status(ddev, status);
+
+ sdtx_push_event(ddev, &event.e);
+}
+
+static void sdtx_device_state_workfn(struct work_struct *work)
+{
+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work);
+ struct ssam_bas_base_info base;
+ u8 mode, latch;
+ int status;
+
+ /* Mark everything as dirty. */
+ set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
+ set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
+ set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
+
+ /*
+ * Ensure that the state gets marked as dirty before continuing to
+ * query it. Necessary to ensure that clear_bit() calls in
+ * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these
+ * bits if an event is received while updating the state here.
+ */
+ smp_mb__after_atomic();
+
+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
+ if (status) {
+ dev_err(ddev->dev, "failed to get base state: %d\n", status);
+ return;
+ }
+
+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
+ if (status) {
+ dev_err(ddev->dev, "failed to get device mode: %d\n", status);
+ return;
+ }
+
+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
+ if (status) {
+ dev_err(ddev->dev, "failed to get latch status: %d\n", status);
+ return;
+ }
+
+ mutex_lock(&ddev->write_lock);
+
+ /*
+ * If the respective dirty-bit has been cleared, an event has been
+ * received, updating this state. The queried state may thus be out of
+ * date. At this point, we can safely assume that the state provided
+ * by the event is either up to date, or we're about to receive
+ * another event updating it.
+ */
+
+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags))
+ __sdtx_device_state_update_base(ddev, base);
+
+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags))
+ __sdtx_device_state_update_mode(ddev, mode);
+
+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags))
+ __sdtx_device_state_update_latch(ddev, latch);
+
+ mutex_unlock(&ddev->write_lock);
+}
+
+static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay)
+{
+ schedule_delayed_work(&ddev->state_work, delay);
+}
+
+
+/* -- Common device initialization. ----------------------------------------- */
+
+static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev,
+ struct ssam_controller *ctrl)
+{
+ int status, tablet_mode;
+
+ /* Basic initialization. */
+ kref_init(&ddev->kref);
+ init_rwsem(&ddev->lock);
+ ddev->dev = dev;
+ ddev->ctrl = ctrl;
+
+ ddev->mdev.minor = MISC_DYNAMIC_MINOR;
+ ddev->mdev.name = "surface_dtx";
+ ddev->mdev.nodename = "surface/dtx";
+ ddev->mdev.fops = &surface_dtx_fops;
+
+ ddev->notif.base.priority = 1;
+ ddev->notif.base.fn = sdtx_notifier;
+ ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
+ ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS;
+ ddev->notif.event.id.instance = 0;
+ ddev->notif.event.mask = SSAM_EVENT_MASK_NONE;
+ ddev->notif.event.flags = SSAM_EVENT_SEQUENCED;
+
+ init_waitqueue_head(&ddev->waitq);
+ mutex_init(&ddev->write_lock);
+ init_rwsem(&ddev->client_lock);
+ INIT_LIST_HEAD(&ddev->client_list);
+
+ INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn);
+ INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn);
+
+ /*
+ * Get current device state. We want to guarantee that events are only
+ * sent when state actually changes. Thus we cannot use special
+ * "uninitialized" values, as that would cause problems when manually
+ * querying the state in surface_dtx_pm_complete(). I.e. we would not
+ * be able to detect state changes there if no change event has been
+ * received between driver initialization and first device suspension.
+ *
+ * Note that we also need to do this before registering the event
+ * notifier, as that may access the state values.
+ */
+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base);
+ if (status)
+ return status;
+
+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode);
+ if (status)
+ return status;
+
+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status);
+ if (status)
+ return status;
+
+ /* Set up tablet mode switch. */
+ ddev->mode_switch = input_allocate_device();
+ if (!ddev->mode_switch)
+ return -ENOMEM;
+
+ ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch";
+ ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0";
+ ddev->mode_switch->id.bustype = BUS_HOST;
+ ddev->mode_switch->dev.parent = ddev->dev;
+
+ tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP);
+ input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE);
+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode);
+
+ status = input_register_device(ddev->mode_switch);
+ if (status) {
+ input_free_device(ddev->mode_switch);
+ return status;
+ }
+
+ /* Set up event notifier. */
+ status = ssam_notifier_register(ddev->ctrl, &ddev->notif);
+ if (status)
+ goto err_notif;
+
+ /* Register miscdevice. */
+ status = misc_register(&ddev->mdev);
+ if (status)
+ goto err_mdev;
+
+ /*
+ * Update device state in case it has changed between getting the
+ * initial mode and registering the event notifier.
+ */
+ sdtx_update_device_state(ddev, 0);
+ return 0;
+
+err_notif:
+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
+ cancel_delayed_work_sync(&ddev->mode_work);
+err_mdev:
+ input_unregister_device(ddev->mode_switch);
+ return status;
+}
+
+static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl)
+{
+ struct sdtx_device *ddev;
+ int status;
+
+ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
+ if (!ddev)
+ return ERR_PTR(-ENOMEM);
+
+ status = sdtx_device_init(ddev, dev, ctrl);
+ if (status) {
+ sdtx_device_put(ddev);
+ return ERR_PTR(status);
+ }
+
+ return ddev;
+}
+
+static void sdtx_device_destroy(struct sdtx_device *ddev)
+{
+ struct sdtx_client *client;
+
+ /*
+ * Mark device as shut-down. Prevent new clients from being added and
+ * new operations from being executed.
+ */
+ set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags);
+
+ /* Disable notifiers, prevent new events from arriving. */
+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
+
+ /* Stop mode_work, prevent access to mode_switch. */
+ cancel_delayed_work_sync(&ddev->mode_work);
+
+ /* Stop state_work. */
+ cancel_delayed_work_sync(&ddev->state_work);
+
+ /* With mode_work canceled, we can unregister the mode_switch. */
+ input_unregister_device(ddev->mode_switch);
+
+ /* Wake up async clients. */
+ down_write(&ddev->client_lock);
+ list_for_each_entry(client, &ddev->client_list, node) {
+ kill_fasync(&client->fasync, SIGIO, POLL_HUP);
+ }
+ up_write(&ddev->client_lock);
+
+ /* Wake up blocking clients. */
+ wake_up_interruptible(&ddev->waitq);
+
+ /*
+ * Wait for clients to finish their current operation. After this, the
+ * controller and device references are guaranteed to be no longer in
+ * use.
+ */
+ down_write(&ddev->lock);
+ ddev->dev = NULL;
+ ddev->ctrl = NULL;
+ up_write(&ddev->lock);
+
+ /* Finally remove the misc-device. */
+ misc_deregister(&ddev->mdev);
+
+ /*
+ * We're now guaranteed that sdtx_device_open() won't be called any
+ * more, so we can now drop out reference.
+ */
+ sdtx_device_put(ddev);
+}
+
+
+/* -- PM ops. --------------------------------------------------------------- */
+
+#ifdef CONFIG_PM_SLEEP
+
+static void surface_dtx_pm_complete(struct device *dev)
+{
+ struct sdtx_device *ddev = dev_get_drvdata(dev);
+
+ /*
+ * Normally, the EC will store events while suspended (i.e. in
+ * display-off state) and release them when resumed (i.e. transitioned
+ * to display-on state). During hibernation, however, the EC will be
+ * shut down and does not store events. Furthermore, events might be
+ * dropped during prolonged suspension (it is currently unknown how
+ * big this event buffer is and how it behaves on overruns).
+ *
+ * To prevent any problems, we update the device state here. We do
+ * this delayed to ensure that any events sent by the EC directly
+ * after resuming will be handled first. The delay below has been
+ * chosen (experimentally), so that there should be ample time for
+ * these events to be handled, before we check and, if necessary,
+ * update the state.
+ */
+ sdtx_update_device_state(ddev, msecs_to_jiffies(1000));
+}
+
+static const struct dev_pm_ops surface_dtx_pm_ops = {
+ .complete = surface_dtx_pm_complete,
+};
+
+#else /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops surface_dtx_pm_ops = {};
+
+#endif /* CONFIG_PM_SLEEP */
+
+
+/* -- Platform driver. ------------------------------------------------------ */
+
+static int surface_dtx_platform_probe(struct platform_device *pdev)
+{
+ struct ssam_controller *ctrl;
+ struct sdtx_device *ddev;
+
+ /* Link to EC. */
+ ctrl = ssam_client_bind(&pdev->dev);
+ if (IS_ERR(ctrl))
+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
+
+ ddev = sdtx_device_create(&pdev->dev, ctrl);
+ if (IS_ERR(ddev))
+ return PTR_ERR(ddev);
+
+ platform_set_drvdata(pdev, ddev);
+ return 0;
+}
+
+static int surface_dtx_platform_remove(struct platform_device *pdev)
+{
+ sdtx_device_destroy(platform_get_drvdata(pdev));
+ return 0;
+}
+
+static const struct acpi_device_id surface_dtx_acpi_match[] = {
+ { "MSHW0133", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match);
+
+static struct platform_driver surface_dtx_platform_driver = {
+ .probe = surface_dtx_platform_probe,
+ .remove = surface_dtx_platform_remove,
+ .driver = {
+ .name = "surface_dtx_pltf",
+ .acpi_match_table = surface_dtx_acpi_match,
+ .pm = &surface_dtx_pm_ops,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+
+
+/* -- SSAM device driver. --------------------------------------------------- */
+
+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
+
+static int surface_dtx_ssam_probe(struct ssam_device *sdev)
+{
+ struct sdtx_device *ddev;
+
+ ddev = sdtx_device_create(&sdev->dev, sdev->ctrl);
+ if (IS_ERR(ddev))
+ return PTR_ERR(ddev);
+
+ ssam_device_set_drvdata(sdev, ddev);
+ return 0;
+}
+
+static void surface_dtx_ssam_remove(struct ssam_device *sdev)
+{
+ sdtx_device_destroy(ssam_device_get_drvdata(sdev));
+}
+
+static const struct ssam_device_id surface_dtx_ssam_match[] = {
+ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) },
+ { },
+};
+MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match);
+
+static struct ssam_device_driver surface_dtx_ssam_driver = {
+ .probe = surface_dtx_ssam_probe,
+ .remove = surface_dtx_ssam_remove,
+ .match_table = surface_dtx_ssam_match,
+ .driver = {
+ .name = "surface_dtx",
+ .pm = &surface_dtx_pm_ops,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+
+static int ssam_dtx_driver_register(void)
+{
+ return ssam_device_driver_register(&surface_dtx_ssam_driver);
+}
+
+static void ssam_dtx_driver_unregister(void)
+{
+ ssam_device_driver_unregister(&surface_dtx_ssam_driver);
+}
+
+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
+
+static int ssam_dtx_driver_register(void)
+{
+ return 0;
+}
+
+static void ssam_dtx_driver_unregister(void)
+{
+}
+
+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
+
+
+/* -- Module setup. --------------------------------------------------------- */
+
+static int __init surface_dtx_init(void)
+{
+ int status;
+
+ status = ssam_dtx_driver_register();
+ if (status)
+ return status;
+
+ status = platform_driver_register(&surface_dtx_platform_driver);
+ if (status)
+ ssam_dtx_driver_unregister();
+
+ return status;
+}
+module_init(surface_dtx_init);
+
+static void __exit surface_dtx_exit(void)
+{
+ platform_driver_unregister(&surface_dtx_platform_driver);
+ ssam_dtx_driver_unregister();
+}
+module_exit(surface_dtx_exit);
+
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
new file mode 100644
index 000000000000..6373d3b5eb7f
--- /dev/null
+++ b/drivers/platform/surface/surface_platform_profile.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Surface Platform Profile / Performance Mode driver for Surface System
+ * Aggregator Module (thermal subsystem).
+ *
+ * Copyright (C) 2021 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_profile.h>
+#include <linux/types.h>
+
+#include <linux/surface_aggregator/device.h>
+
+enum ssam_tmp_profile {
+ SSAM_TMP_PROFILE_NORMAL = 1,
+ SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
+ SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
+ SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
+};
+
+struct ssam_tmp_profile_info {
+ __le32 profile;
+ __le16 unknown1;
+ __le16 unknown2;
+} __packed;
+
+struct ssam_tmp_profile_device {
+ struct ssam_device *sdev;
+ struct platform_profile_handler handler;
+};
+
+SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
+ .target_category = SSAM_SSH_TC_TMP,
+ .command_id = 0x02,
+});
+
+SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
+ .target_category = SSAM_SSH_TC_TMP,
+ .command_id = 0x03,
+});
+
+static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
+{
+ struct ssam_tmp_profile_info info;
+ int status;
+
+ status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
+ if (status < 0)
+ return status;
+
+ *p = le32_to_cpu(info.profile);
+ return 0;
+}
+
+static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
+{
+ __le32 profile_le = cpu_to_le32(p);
+
+ return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
+}
+
+static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
+{
+ switch (p) {
+ case SSAM_TMP_PROFILE_NORMAL:
+ return PLATFORM_PROFILE_BALANCED;
+
+ case SSAM_TMP_PROFILE_BATTERY_SAVER:
+ return PLATFORM_PROFILE_LOW_POWER;
+
+ case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
+ return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+
+ case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
+ return PLATFORM_PROFILE_PERFORMANCE;
+
+ default:
+ dev_err(&sdev->dev, "invalid performance profile: %d", p);
+ return -EINVAL;
+ }
+}
+
+static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
+{
+ switch (p) {
+ case PLATFORM_PROFILE_LOW_POWER:
+ return SSAM_TMP_PROFILE_BATTERY_SAVER;
+
+ case PLATFORM_PROFILE_BALANCED:
+ return SSAM_TMP_PROFILE_NORMAL;
+
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+ return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
+
+ case PLATFORM_PROFILE_PERFORMANCE:
+ return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
+
+ default:
+ /* This should have already been caught by platform_profile_store(). */
+ WARN(true, "unsupported platform profile");
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
+ enum platform_profile_option *profile)
+{
+ struct ssam_tmp_profile_device *tpd;
+ enum ssam_tmp_profile tp;
+ int status;
+
+ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
+
+ status = ssam_tmp_profile_get(tpd->sdev, &tp);
+ if (status)
+ return status;
+
+ status = convert_ssam_to_profile(tpd->sdev, tp);
+ if (status < 0)
+ return status;
+
+ *profile = status;
+ return 0;
+}
+
+static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
+ enum platform_profile_option profile)
+{
+ struct ssam_tmp_profile_device *tpd;
+ int tp;
+
+ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
+
+ tp = convert_profile_to_ssam(tpd->sdev, profile);
+ if (tp < 0)
+ return tp;
+
+ return ssam_tmp_profile_set(tpd->sdev, tp);
+}
+
+static int surface_platform_profile_probe(struct ssam_device *sdev)
+{
+ struct ssam_tmp_profile_device *tpd;
+
+ tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
+ if (!tpd)
+ return -ENOMEM;
+
+ tpd->sdev = sdev;
+
+ tpd->handler.profile_get = ssam_platform_profile_get;
+ tpd->handler.profile_set = ssam_platform_profile_set;
+
+ set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices);
+
+ platform_profile_register(&tpd->handler);
+ return 0;
+}
+
+static void surface_platform_profile_remove(struct ssam_device *sdev)
+{
+ platform_profile_remove();
+}
+
+static const struct ssam_device_id ssam_platform_profile_match[] = {
+ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) },
+ { },
+};
+MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
+
+static struct ssam_device_driver surface_platform_profile = {
+ .probe = surface_platform_profile_probe,
+ .remove = surface_platform_profile_remove,
+ .match_table = ssam_platform_profile_match,
+ .driver = {
+ .name = "surface_platform_profile",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_ssam_device_driver(surface_platform_profile);
+
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c
index d8afed5db94c..242fb690dcaf 100644
--- a/drivers/platform/surface/surfacepro3_button.c
+++ b/drivers/platform/surface/surfacepro3_button.c
@@ -40,8 +40,6 @@ static const guid_t MSHW0040_DSM_UUID =
#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN 0xc2
#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN 0xc3
-ACPI_MODULE_NAME("surface pro 3 button");
-
MODULE_AUTHOR("Chen Yu");
MODULE_DESCRIPTION("Surface Pro3 Button Driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 461ec61530eb..2714f7c3843e 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -123,6 +123,17 @@ config XIAOMI_WMI
To compile this driver as a module, choose M here: the module will
be called xiaomi-wmi.
+config GIGABYTE_WMI
+ tristate "Gigabyte WMI temperature driver"
+ depends on ACPI_WMI
+ depends on HWMON
+ help
+ Say Y here if you want to support WMI-based temperature reporting on
+ Gigabyte mainboards.
+
+ To compile this driver as a module, choose M here: the module will
+ be called gigabyte-wmi.
+
config ACERHDF
tristate "Acer Aspire One temperature and fan driver"
depends on ACPI && THERMAL
@@ -193,6 +204,17 @@ config AMD_PMC
If you choose to compile this driver as a module the module will be
called amd-pmc.
+config ADV_SWBUTTON
+ tristate "Advantech ACPI Software Button Driver"
+ depends on ACPI && INPUT
+ help
+ Say Y here to enable support for Advantech software defined
+ button feature. More information can be found at
+ <http://www.advantech.com.tw/products/>
+
+ To compile this driver as a module, choose M here. The module will
+ be called adv_swbutton.
+
config APPLE_GMUX
tristate "Apple Gmux Driver"
depends on ACPI && PCI
@@ -410,6 +432,7 @@ config HP_WMI
depends on INPUT
depends on RFKILL || RFKILL = n
select INPUT_SPARSEKMAP
+ select ACPI_PLATFORM_PROFILE
help
Say Y here if you want to support WMI-based hotkeys on HP laptops and
to read data from WMI such as docking or ambient light sensor state.
@@ -1171,6 +1194,7 @@ config INTEL_MRFLD_PWRBTN
config INTEL_PMC_CORE
tristate "Intel PMC Core driver"
depends on PCI
+ depends on ACPI
help
The Intel Platform Controller Hub for Intel Core SoCs provides access
to Power Management Controller registers via various interfaces. This
@@ -1192,7 +1216,7 @@ config INTEL_PMT_CLASS
tristate
help
The Intel Platform Monitoring Technology (PMT) class driver provides
- the basic sysfs interface and file hierarchy uses by PMT devices.
+ the basic sysfs interface and file hierarchy used by PMT devices.
For more information, see:
<file:Documentation/ABI/testing/sysfs-class-intel_pmt>
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 60d554073749..dcc8cdb95b4d 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o
obj-$(CONFIG_MXM_WMI) += mxm-wmi.o
obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o
obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o
+obj-$(CONFIG_GIGABYTE_WMI) += gigabyte-wmi.o
# Acer
obj-$(CONFIG_ACERHDF) += acerhdf.o
@@ -24,6 +25,9 @@ obj-$(CONFIG_ACER_WMI) += acer-wmi.o
# AMD
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
+# Advantech
+obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o
+
# Apple
obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
diff --git a/drivers/platform/x86/adv_swbutton.c b/drivers/platform/x86/adv_swbutton.c
new file mode 100644
index 000000000000..38693b735c87
--- /dev/null
+++ b/drivers/platform/x86/adv_swbutton.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * adv_swbutton.c - Software Button Interface Driver.
+ *
+ * (C) Copyright 2020 Advantech Corporation, Inc
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+
+#define ACPI_BUTTON_HID_SWBTN "AHC0310"
+
+#define ACPI_BUTTON_NOTIFY_SWBTN_RELEASE 0x86
+#define ACPI_BUTTON_NOTIFY_SWBTN_PRESSED 0x85
+
+struct adv_swbutton {
+ struct input_dev *input;
+ char phys[32];
+};
+
+/*-------------------------------------------------------------------------
+ * Driver Interface
+ *--------------------------------------------------------------------------
+ */
+static void adv_swbutton_notify(acpi_handle handle, u32 event, void *context)
+{
+ struct platform_device *device = context;
+ struct adv_swbutton *button = dev_get_drvdata(&device->dev);
+
+ switch (event) {
+ case ACPI_BUTTON_NOTIFY_SWBTN_RELEASE:
+ input_report_key(button->input, KEY_PROG1, 0);
+ input_sync(button->input);
+ break;
+ case ACPI_BUTTON_NOTIFY_SWBTN_PRESSED:
+ input_report_key(button->input, KEY_PROG1, 1);
+ input_sync(button->input);
+ break;
+ default:
+ dev_dbg(&device->dev, "Unsupported event [0x%x]\n", event);
+ }
+}
+
+static int adv_swbutton_probe(struct platform_device *device)
+{
+ struct adv_swbutton *button;
+ struct input_dev *input;
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+ acpi_status status;
+ int error;
+
+ button = devm_kzalloc(&device->dev, sizeof(*button), GFP_KERNEL);
+ if (!button)
+ return -ENOMEM;
+
+ dev_set_drvdata(&device->dev, button);
+
+ input = devm_input_allocate_device(&device->dev);
+ if (!input)
+ return -ENOMEM;
+
+ button->input = input;
+ snprintf(button->phys, sizeof(button->phys), "%s/button/input0", ACPI_BUTTON_HID_SWBTN);
+
+ input->name = "Advantech Software Button";
+ input->phys = button->phys;
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = &device->dev;
+ set_bit(EV_REP, input->evbit);
+ input_set_capability(input, EV_KEY, KEY_PROG1);
+
+ error = input_register_device(input);
+ if (error)
+ return error;
+
+ device_init_wakeup(&device->dev, true);
+
+ status = acpi_install_notify_handler(handle,
+ ACPI_DEVICE_NOTIFY,
+ adv_swbutton_notify,
+ device);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&device->dev, "Error installing notify handler\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int adv_swbutton_remove(struct platform_device *device)
+{
+ acpi_handle handle = ACPI_HANDLE(&device->dev);
+
+ acpi_remove_notify_handler(handle, ACPI_DEVICE_NOTIFY,
+ adv_swbutton_notify);
+
+ return 0;
+}
+
+static const struct acpi_device_id button_device_ids[] = {
+ {ACPI_BUTTON_HID_SWBTN, 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, button_device_ids);
+
+static struct platform_driver adv_swbutton_driver = {
+ .driver = {
+ .name = "adv_swbutton",
+ .acpi_match_table = button_device_ids,
+ },
+ .probe = adv_swbutton_probe,
+ .remove = adv_swbutton_remove,
+};
+module_platform_driver(adv_swbutton_driver);
+
+MODULE_AUTHOR("Andrea Ho");
+MODULE_DESCRIPTION("Advantech ACPI SW Button Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c
index bfea656e910c..4d2d32bfbe2a 100644
--- a/drivers/platform/x86/asus-laptop.c
+++ b/drivers/platform/x86/asus-laptop.c
@@ -1569,7 +1569,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
struct attribute *attr,
int idx)
{
- struct device *dev = container_of(kobj, struct device, kobj);
+ struct device *dev = kobj_to_dev(kobj);
struct asus_laptop *asus = dev_get_drvdata(dev);
acpi_handle handle = asus->handle;
bool supported;
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 9ca15f724343..ebaeb7bb80f5 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -47,6 +47,9 @@ MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>, "
MODULE_DESCRIPTION("Asus Generic WMI Driver");
MODULE_LICENSE("GPL");
+static bool fnlock_default = true;
+module_param(fnlock_default, bool, 0444);
+
#define to_asus_wmi_driver(pdrv) \
(container_of((pdrv), struct asus_wmi_driver, platform_driver))
@@ -2673,7 +2676,7 @@ static int asus_wmi_add(struct platform_device *pdev)
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, 2, NULL);
if (asus_wmi_has_fnlock_key(asus)) {
- asus->fnlock_locked = true;
+ asus->fnlock_locked = fnlock_default;
asus_wmi_fnlock_update(asus);
}
diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c
index 3e03e8d3a07f..9309ab5792cb 100644
--- a/drivers/platform/x86/classmate-laptop.c
+++ b/drivers/platform/x86/classmate-laptop.c
@@ -956,7 +956,7 @@ static int cmpc_ipml_add(struct acpi_device *acpi)
/*
* If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV).
* This is OK, however, since all other uses of the device will not
- * derefence it.
+ * dereference it.
*/
if (ipml->rf) {
retval = rfkill_register(ipml->rf);
diff --git a/drivers/platform/x86/dell/alienware-wmi.c b/drivers/platform/x86/dell/alienware-wmi.c
index 5bb2859c8285..f21248255529 100644
--- a/drivers/platform/x86/dell/alienware-wmi.c
+++ b/drivers/platform/x86/dell/alienware-wmi.c
@@ -2,7 +2,7 @@
/*
* Alienware AlienFX control
*
- * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com>
+ * Copyright (C) 2014 Dell Inc <Dell.Client.Kernel@dell.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -26,7 +26,7 @@
#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B
#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C
-MODULE_AUTHOR("Mario Limonciello <mario_limonciello@dell.com>");
+MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
MODULE_DESCRIPTION("Alienware special feature control");
MODULE_LICENSE("GPL");
MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID);
diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c
index 3a1dbf199441..fc086b66f70b 100644
--- a/drivers/platform/x86/dell/dell-smbios-base.c
+++ b/drivers/platform/x86/dell/dell-smbios-base.c
@@ -647,6 +647,6 @@ module_exit(dell_smbios_exit);
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");
MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
-MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
+MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c
index 27a298b7c541..a1753485159c 100644
--- a/drivers/platform/x86/dell/dell-smbios-wmi.c
+++ b/drivers/platform/x86/dell/dell-smbios-wmi.c
@@ -205,7 +205,7 @@ fail_register:
return ret;
}
-static int dell_smbios_wmi_remove(struct wmi_device *wdev)
+static void dell_smbios_wmi_remove(struct wmi_device *wdev)
{
struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev);
int count;
@@ -218,7 +218,6 @@ static int dell_smbios_wmi_remove(struct wmi_device *wdev)
count = get_order(priv->req_buf_size);
free_pages((unsigned long)priv->buf, count);
mutex_unlock(&call_mutex);
- return 0;
}
static const struct wmi_device_id dell_smbios_wmi_id_table[] = {
diff --git a/drivers/platform/x86/dell/dell-wmi-descriptor.c b/drivers/platform/x86/dell/dell-wmi-descriptor.c
index a068900ae8a1..c2a180202719 100644
--- a/drivers/platform/x86/dell/dell-wmi-descriptor.c
+++ b/drivers/platform/x86/dell/dell-wmi-descriptor.c
@@ -174,14 +174,13 @@ out:
return ret;
}
-static int dell_wmi_descriptor_remove(struct wmi_device *wdev)
+static void dell_wmi_descriptor_remove(struct wmi_device *wdev)
{
struct descriptor_priv *priv = dev_get_drvdata(&wdev->dev);
mutex_lock(&list_mutex);
list_del(&priv->list);
mutex_unlock(&list_mutex);
- return 0;
}
static const struct wmi_device_id dell_wmi_descriptor_id_table[] = {
@@ -201,6 +200,6 @@ static struct wmi_driver dell_wmi_descriptor_driver = {
module_wmi_driver(dell_wmi_descriptor_driver);
MODULE_DEVICE_TABLE(wmi, dell_wmi_descriptor_id_table);
-MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
+MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
MODULE_DESCRIPTION("Dell WMI descriptor driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c
index f95d8ddace5a..c2dd2de6bc20 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c
@@ -152,12 +152,11 @@ static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *co
return 0;
}
-static int bios_attr_set_interface_remove(struct wmi_device *wdev)
+static void bios_attr_set_interface_remove(struct wmi_device *wdev)
{
mutex_lock(&wmi_priv.mutex);
wmi_priv.bios_attr_wdev = NULL;
mutex_unlock(&wmi_priv.mutex);
- return 0;
}
static const struct wmi_device_id bios_attr_set_interface_id_table[] = {
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c
index 5780b4d94759..339a082d6c18 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c
@@ -119,12 +119,11 @@ static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *c
return 0;
}
-static int bios_attr_pass_interface_remove(struct wmi_device *wdev)
+static void bios_attr_pass_interface_remove(struct wmi_device *wdev)
{
mutex_lock(&wmi_priv.mutex);
wmi_priv.password_attr_wdev = NULL;
mutex_unlock(&wmi_priv.mutex);
- return 0;
}
static const struct wmi_device_id bios_attr_pass_interface_id_table[] = {
diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
index 7410ccae650c..c8d276d78e92 100644
--- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
+++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c
@@ -399,6 +399,7 @@ static int init_bios_attributes(int attr_type, const char *guid)
union acpi_object *obj = NULL;
union acpi_object *elements;
struct kset *tmp_set;
+ int min_elements;
/* instance_id needs to be reset for each type GUID
* also, instance IDs are unique within GUID but not across
@@ -409,14 +410,38 @@ static int init_bios_attributes(int attr_type, const char *guid)
retval = alloc_attributes_data(attr_type);
if (retval)
return retval;
+
+ switch (attr_type) {
+ case ENUM: min_elements = 8; break;
+ case INT: min_elements = 9; break;
+ case STR: min_elements = 8; break;
+ case PO: min_elements = 4; break;
+ default:
+ pr_err("Error: Unknown attr_type: %d\n", attr_type);
+ return -EINVAL;
+ }
+
/* need to use specific instance_id and guid combination to get right data */
obj = get_wmiobj_pointer(instance_id, guid);
- if (!obj || obj->type != ACPI_TYPE_PACKAGE)
+ if (!obj)
return -ENODEV;
- elements = obj->package.elements;
mutex_lock(&wmi_priv.mutex);
- while (elements) {
+ while (obj) {
+ if (obj->type != ACPI_TYPE_PACKAGE) {
+ pr_err("Error: Expected ACPI-package type, got: %d\n", obj->type);
+ retval = -EIO;
+ goto err_attr_init;
+ }
+
+ if (obj->package.count < min_elements) {
+ pr_err("Error: ACPI-package does not have enough elements: %d < %d\n",
+ obj->package.count, min_elements);
+ goto nextobj;
+ }
+
+ elements = obj->package.elements;
+
/* sanity checking */
if (elements[ATTR_NAME].type != ACPI_TYPE_STRING) {
pr_debug("incorrect element type\n");
@@ -481,7 +506,6 @@ nextobj:
kfree(obj);
instance_id++;
obj = get_wmiobj_pointer(instance_id, guid);
- elements = obj ? obj->package.elements : NULL;
}
mutex_unlock(&wmi_priv.mutex);
@@ -604,7 +628,7 @@ static void __exit sysman_exit(void)
module_init(sysman_init);
module_exit(sysman_exit);
-MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
+MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
MODULE_AUTHOR("Prasanth Ksr <prasanth.ksr@dell.com>");
MODULE_AUTHOR("Divya Bharathi <divya.bharathi@dell.com>");
MODULE_DESCRIPTION("Dell platform setting control interface");
diff --git a/drivers/platform/x86/dell/dell-wmi.c b/drivers/platform/x86/dell/dell-wmi.c
index bbdb3e860892..5e1b7f897df5 100644
--- a/drivers/platform/x86/dell/dell-wmi.c
+++ b/drivers/platform/x86/dell/dell-wmi.c
@@ -714,10 +714,9 @@ static int dell_wmi_probe(struct wmi_device *wdev, const void *context)
return dell_wmi_input_setup(wdev);
}
-static int dell_wmi_remove(struct wmi_device *wdev)
+static void dell_wmi_remove(struct wmi_device *wdev)
{
dell_wmi_input_destroy(wdev);
- return 0;
}
static const struct wmi_device_id dell_wmi_id_table[] = {
{ .guid_string = DELL_EVENT_GUID },
diff --git a/drivers/platform/x86/gigabyte-wmi.c b/drivers/platform/x86/gigabyte-wmi.c
new file mode 100644
index 000000000000..13d57434e60f
--- /dev/null
+++ b/drivers/platform/x86/gigabyte-wmi.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Thomas Weißschuh <thomas@weissschuh.net>
+ */
+#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>
+
+#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 {
+ GIGABYTE_WMI_BUILD_DATE_QUERY = 0x1,
+ GIGABYTE_WMI_MAINBOARD_TYPE_QUERY = 0x2,
+ GIGABYTE_WMI_FIRMWARE_VERSION_QUERY = 0x4,
+ GIGABYTE_WMI_MAINBOARD_NAME_QUERY = 0x5,
+ GIGABYTE_WMI_TEMPERATURE_QUERY = 0x125,
+};
+
+struct gigabyte_wmi_args {
+ u32 arg1;
+};
+
+static int gigabyte_wmi_perform_query(struct wmi_device *wdev,
+ enum gigabyte_wmi_commandtype command,
+ struct gigabyte_wmi_args *args, struct acpi_buffer *out)
+{
+ const struct acpi_buffer in = {
+ .length = sizeof(*args),
+ .pointer = args,
+ };
+
+ acpi_status ret = wmidev_evaluate_method(wdev, 0x0, command, &in, out);
+
+ if (ACPI_FAILURE(ret))
+ return -EIO;
+
+ return 0;
+}
+
+static int gigabyte_wmi_query_integer(struct wmi_device *wdev,
+ enum gigabyte_wmi_commandtype command,
+ struct gigabyte_wmi_args *args, u64 *res)
+{
+ union acpi_object *obj;
+ struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+ int ret;
+
+ ret = gigabyte_wmi_perform_query(wdev, command, args, &result);
+ if (ret)
+ return ret;
+ obj = result.pointer;
+ if (obj && obj->type == ACPI_TYPE_INTEGER)
+ *res = obj->integer.value;
+ else
+ ret = -EIO;
+ kfree(result.pointer);
+ return ret;
+}
+
+static int gigabyte_wmi_temperature(struct wmi_device *wdev, u8 sensor, long *res)
+{
+ struct gigabyte_wmi_args args = {
+ .arg1 = sensor,
+ };
+ u64 temp;
+ acpi_status ret;
+
+ ret = gigabyte_wmi_query_integer(wdev, GIGABYTE_WMI_TEMPERATURE_QUERY, &args, &temp);
+ if (ret == 0) {
+ if (temp == 0)
+ return -ENODEV;
+ *res = (s8)temp * 1000; // value is a signed 8-bit integer
+ }
+ return ret;
+}
+
+static int gigabyte_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct wmi_device *wdev = dev_get_drvdata(dev);
+
+ return gigabyte_wmi_temperature(wdev, channel, val);
+}
+
+static umode_t gigabyte_wmi_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ return usable_sensors_mask & BIT(channel) ? 0444 : 0;
+}
+
+static const struct hwmon_channel_info *gigabyte_wmi_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT,
+ HWMON_T_INPUT),
+ NULL
+};
+
+static const struct hwmon_ops gigabyte_wmi_hwmon_ops = {
+ .read = gigabyte_wmi_hwmon_read,
+ .is_visible = gigabyte_wmi_hwmon_is_visible,
+};
+
+static const struct hwmon_chip_info gigabyte_wmi_hwmon_chip_info = {
+ .ops = &gigabyte_wmi_hwmon_ops,
+ .info = gigabyte_wmi_hwmon_info,
+};
+
+static u8 gigabyte_wmi_detect_sensor_usability(struct wmi_device *wdev)
+{
+ int i;
+ long temp;
+ u8 r = 0;
+
+ for (i = 0; i < NUM_TEMPERATURE_SENSORS; i++) {
+ if (!gigabyte_wmi_temperature(wdev, i, &temp))
+ r |= BIT(i);
+ }
+ return r;
+}
+
+static const struct dmi_system_id gigabyte_wmi_known_working_platforms[] = {
+ { .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550 GAMING X V2"),
+ }},
+ { .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550M AORUS PRO-P"),
+ }},
+ { .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "B550M DS3H"),
+ }},
+ { .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Z390 I AORUS PRO WIFI-CF"),
+ }},
+ { .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X570 AORUS ELITE"),
+ }},
+ { .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X570 I AORUS PRO WIFI"),
+ }},
+ { }
+};
+
+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");
+ return -ENODEV;
+ }
+
+ hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev, "gigabyte_wmi", wdev,
+ &gigabyte_wmi_hwmon_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct wmi_device_id gigabyte_wmi_id_table[] = {
+ { GIGABYTE_WMI_GUID, NULL },
+ { }
+};
+
+static struct wmi_driver gigabyte_wmi_driver = {
+ .driver = {
+ .name = "gigabyte-wmi",
+ },
+ .id_table = gigabyte_wmi_id_table,
+ .probe = gigabyte_wmi_probe,
+};
+module_wmi_driver(gigabyte_wmi_driver);
+
+MODULE_DEVICE_TABLE(wmi, gigabyte_wmi_id_table);
+MODULE_AUTHOR("Thomas Weißschuh <thomas@weissschuh.net>");
+MODULE_DESCRIPTION("Gigabyte WMI temperature driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
index e94e59283ecb..027a1467d009 100644
--- a/drivers/platform/x86/hp-wmi.c
+++ b/drivers/platform/x86/hp-wmi.c
@@ -21,6 +21,7 @@
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/platform_device.h>
+#include <linux/platform_profile.h>
#include <linux/acpi.h>
#include <linux/rfkill.h>
#include <linux/string.h>
@@ -85,7 +86,7 @@ enum hp_wmi_commandtype {
HPWMI_FEATURE2_QUERY = 0x0d,
HPWMI_WIRELESS2_QUERY = 0x1b,
HPWMI_POSTCODEERROR_QUERY = 0x2a,
- HPWMI_THERMAL_POLICY_QUERY = 0x4c,
+ HPWMI_THERMAL_PROFILE_QUERY = 0x4c,
};
enum hp_wmi_command {
@@ -119,6 +120,12 @@ enum hp_wireless2_bits {
HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD,
};
+enum hp_thermal_profile {
+ HP_THERMAL_PROFILE_PERFORMANCE = 0x00,
+ HP_THERMAL_PROFILE_DEFAULT = 0x01,
+ HP_THERMAL_PROFILE_COOL = 0x02
+};
+
#define IS_HWBLOCKED(x) ((x & HPWMI_POWER_FW_OR_HW) != HPWMI_POWER_FW_OR_HW)
#define IS_SWBLOCKED(x) !(x & HPWMI_POWER_SOFT)
@@ -159,6 +166,8 @@ static const struct key_entry hp_wmi_keymap[] = {
static struct input_dev *hp_wmi_input_dev;
static struct platform_device *hp_wmi_platform_dev;
+static struct platform_profile_handler platform_profile_handler;
+static bool platform_profile_support;
static struct rfkill *wifi_rfkill;
static struct rfkill *bluetooth_rfkill;
@@ -869,23 +878,98 @@ fail:
return err;
}
-static int thermal_policy_setup(struct platform_device *device)
+static int thermal_profile_get(void)
+{
+ return hp_wmi_read_int(HPWMI_THERMAL_PROFILE_QUERY);
+}
+
+static int thermal_profile_set(int thermal_profile)
+{
+ return hp_wmi_perform_query(HPWMI_THERMAL_PROFILE_QUERY, HPWMI_WRITE, &thermal_profile,
+ sizeof(thermal_profile), 0);
+}
+
+static int platform_profile_get(struct platform_profile_handler *pprof,
+ enum platform_profile_option *profile)
+{
+ int tp;
+
+ tp = thermal_profile_get();
+ if (tp < 0)
+ return tp;
+
+ switch (tp) {
+ case HP_THERMAL_PROFILE_PERFORMANCE:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
+ case HP_THERMAL_PROFILE_DEFAULT:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ break;
+ case HP_THERMAL_PROFILE_COOL:
+ *profile = PLATFORM_PROFILE_COOL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int platform_profile_set(struct platform_profile_handler *pprof,
+ enum platform_profile_option profile)
{
int err, tp;
- tp = hp_wmi_read_int(HPWMI_THERMAL_POLICY_QUERY);
+ switch (profile) {
+ case PLATFORM_PROFILE_PERFORMANCE:
+ tp = HP_THERMAL_PROFILE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_BALANCED:
+ tp = HP_THERMAL_PROFILE_DEFAULT;
+ break;
+ case PLATFORM_PROFILE_COOL:
+ tp = HP_THERMAL_PROFILE_COOL;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ err = thermal_profile_set(tp);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int thermal_profile_setup(void)
+{
+ int err, tp;
+
+ tp = thermal_profile_get();
if (tp < 0)
return tp;
/*
- * call thermal policy write command to ensure that the firmware correctly
+ * call thermal profile write command to ensure that the firmware correctly
* sets the OEM variables for the DPTF
*/
- err = hp_wmi_perform_query(HPWMI_THERMAL_POLICY_QUERY, HPWMI_WRITE, &tp,
- sizeof(tp), 0);
+ err = thermal_profile_set(tp);
if (err)
return err;
+ platform_profile_handler.profile_get = platform_profile_get,
+ platform_profile_handler.profile_set = platform_profile_set,
+
+ 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);
+
+ err = platform_profile_register(&platform_profile_handler);
+ if (err)
+ return err;
+
+ platform_profile_support = true;
+
return 0;
}
@@ -900,7 +984,7 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
if (hp_wmi_rfkill_setup(device))
hp_wmi_rfkill2_setup(device);
- thermal_policy_setup(device);
+ thermal_profile_setup();
return 0;
}
@@ -927,6 +1011,9 @@ static int __exit hp_wmi_bios_remove(struct platform_device *device)
rfkill_destroy(wwan_rfkill);
}
+ if (platform_profile_support)
+ platform_profile_remove();
+
return 0;
}
diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c
index 3fdf4cbec9ad..888a764efad1 100644
--- a/drivers/platform/x86/intel-vbtn.c
+++ b/drivers/platform/x86/intel-vbtn.c
@@ -63,9 +63,6 @@ static const struct key_entry intel_vbtn_switchmap[] = {
{ KE_END }
};
-#define KEYMAP_LEN \
- (ARRAY_SIZE(intel_vbtn_keymap) + ARRAY_SIZE(intel_vbtn_switchmap) + 1)
-
struct intel_vbtn_priv {
struct input_dev *buttons_dev;
struct input_dev *switches_dev;
diff --git a/drivers/platform/x86/intel-wmi-sbl-fw-update.c b/drivers/platform/x86/intel-wmi-sbl-fw-update.c
index ea87fa0786e8..3c86e0108a24 100644
--- a/drivers/platform/x86/intel-wmi-sbl-fw-update.c
+++ b/drivers/platform/x86/intel-wmi-sbl-fw-update.c
@@ -117,10 +117,9 @@ static int intel_wmi_sbl_fw_update_probe(struct wmi_device *wdev,
return 0;
}
-static int intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev)
+static void intel_wmi_sbl_fw_update_remove(struct wmi_device *wdev)
{
dev_info(&wdev->dev, "Slim Bootloader signaling driver removed\n");
- return 0;
}
static const struct wmi_device_id intel_wmi_sbl_id_table[] = {
diff --git a/drivers/platform/x86/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c
index 974c22a7ff61..4ae87060d18b 100644
--- a/drivers/platform/x86/intel-wmi-thunderbolt.c
+++ b/drivers/platform/x86/intel-wmi-thunderbolt.c
@@ -66,11 +66,10 @@ static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev,
return ret;
}
-static int intel_wmi_thunderbolt_remove(struct wmi_device *wdev)
+static void intel_wmi_thunderbolt_remove(struct wmi_device *wdev)
{
sysfs_remove_group(&wdev->dev.kobj, &tbt_attribute_group);
kobject_uevent(&wdev->dev.kobj, KOBJ_CHANGE);
- return 0;
}
static const struct wmi_device_id intel_wmi_thunderbolt_id_table[] = {
diff --git a/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c b/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c
index 0df2e82dd249..9606a994af22 100644
--- a/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c
+++ b/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c
@@ -58,7 +58,7 @@ static int chtdc_ti_pwrbtn_probe(struct platform_device *pdev)
err = devm_request_threaded_irq(dev, irq, NULL,
chtdc_ti_pwrbtn_interrupt,
- 0, KBUILD_MODNAME, input);
+ IRQF_ONESHOT, KBUILD_MODNAME, input);
if (err)
return err;
diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c
index b5888aeb4bcf..b0e486a6bdfb 100644
--- a/drivers/platform/x86/intel_pmc_core.c
+++ b/drivers/platform/x86/intel_pmc_core.c
@@ -23,7 +23,9 @@
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/uaccess.h>
+#include <linux/uuid.h>
+#include <acpi/acpi_bus.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include <asm/msr.h>
@@ -31,7 +33,8 @@
#include "intel_pmc_core.h"
-static struct pmc_dev pmc;
+#define ACPI_S0IX_DSM_UUID "57a6512e-3979-4e9d-9708-ff13b2508972"
+#define ACPI_GET_LOW_MODE_REGISTERS 1
/* PKGC MSRs are common across Intel Core SoCs */
static const struct pmc_bit_map msr_map[] = {
@@ -380,6 +383,8 @@ static const struct pmc_bit_map cnp_ltr_show_map[] = {
* a list of core SoCs using this.
*/
{"WIGIG", ICL_PMC_LTR_WIGIG},
+ {"THC0", TGL_PMC_LTR_THC0},
+ {"THC1", TGL_PMC_LTR_THC1},
/* Below two cannot be used for LTR_IGNORE */
{"CURRENT_PLATFORM", CNP_PMC_LTR_CUR_PLT},
{"AGGREGATED_SYSTEM", CNP_PMC_LTR_CUR_ASLT},
@@ -401,6 +406,7 @@ static const struct pmc_reg_map cnp_reg_map = {
.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
.ltr_ignore_max = CNP_NUM_IP_IGN_ALLOWED,
+ .etr3_offset = ETR3_OFFSET,
};
static const struct pmc_reg_map icl_reg_map = {
@@ -418,6 +424,7 @@ static const struct pmc_reg_map icl_reg_map = {
.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
.ltr_ignore_max = ICL_NUM_IP_IGN_ALLOWED,
+ .etr3_offset = ETR3_OFFSET,
};
static const struct pmc_bit_map tgl_clocksource_status_map[] = {
@@ -579,14 +586,65 @@ static const struct pmc_reg_map tgl_reg_map = {
.pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET,
.pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT,
.ltr_ignore_max = TGL_NUM_IP_IGN_ALLOWED,
- .lpm_modes = tgl_lpm_modes,
+ .lpm_num_maps = TGL_LPM_NUM_MAPS,
+ .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2,
+ .lpm_sts_latch_en_offset = TGL_LPM_STS_LATCH_EN_OFFSET,
.lpm_en_offset = TGL_LPM_EN_OFFSET,
+ .lpm_priority_offset = TGL_LPM_PRI_OFFSET,
.lpm_residency_offset = TGL_LPM_RESIDENCY_OFFSET,
.lpm_sts = tgl_lpm_maps,
.lpm_status_offset = TGL_LPM_STATUS_OFFSET,
.lpm_live_status_offset = TGL_LPM_LIVE_STATUS_OFFSET,
+ .etr3_offset = ETR3_OFFSET,
};
+static 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;
+ u32 lpm_size = LPM_MAX_NUM_MODES * num_maps * 4;
+ union acpi_object *out_obj;
+ struct acpi_device *adev;
+ guid_t s0ix_dsm_guid;
+ u32 *lpm_req_regs, *addr;
+
+ adev = ACPI_COMPANION(&pdev->dev);
+ if (!adev)
+ return;
+
+ guid_parse(ACPI_S0IX_DSM_UUID, &s0ix_dsm_guid);
+
+ out_obj = acpi_evaluate_dsm(adev->handle, &s0ix_dsm_guid, 0,
+ ACPI_GET_LOW_MODE_REGISTERS, NULL);
+ if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) {
+ u32 size = out_obj->buffer.length;
+
+ if (size != lpm_size) {
+ acpi_handle_debug(adev->handle,
+ "_DSM returned unexpected buffer size, have %u, expect %u\n",
+ size, lpm_size);
+ goto free_acpi_obj;
+ }
+ } else {
+ acpi_handle_debug(adev->handle,
+ "_DSM function 0 evaluation failed\n");
+ goto free_acpi_obj;
+ }
+
+ addr = (u32 *)out_obj->buffer.pointer;
+
+ lpm_req_regs = devm_kzalloc(&pdev->dev, lpm_size * sizeof(u32),
+ GFP_KERNEL);
+ if (!lpm_req_regs)
+ goto free_acpi_obj;
+
+ memcpy(lpm_req_regs, addr, lpm_size);
+ pmcdev->lpm_req_regs = lpm_req_regs;
+
+free_acpi_obj:
+ ACPI_FREE(out_obj);
+}
+
static inline u32 pmc_core_reg_read(struct pmc_dev *pmcdev, int reg_offset)
{
return readl(pmcdev->regbase + reg_offset);
@@ -603,6 +661,115 @@ static inline u64 pmc_core_adjust_slp_s0_step(struct pmc_dev *pmcdev, u32 value)
return (u64)value * pmcdev->map->slp_s0_res_counter_step;
}
+static int set_etr3(struct pmc_dev *pmcdev)
+{
+ const struct pmc_reg_map *map = pmcdev->map;
+ u32 reg;
+ int err;
+
+ if (!map->etr3_offset)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&pmcdev->lock);
+
+ /* check if CF9 is locked */
+ reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
+ if (reg & ETR3_CF9LOCK) {
+ err = -EACCES;
+ goto out_unlock;
+ }
+
+ /* write CF9 global reset bit */
+ reg |= ETR3_CF9GR;
+ pmc_core_reg_write(pmcdev, map->etr3_offset, reg);
+
+ reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
+ if (!(reg & ETR3_CF9GR)) {
+ err = -EIO;
+ goto out_unlock;
+ }
+
+ err = 0;
+
+out_unlock:
+ mutex_unlock(&pmcdev->lock);
+ return err;
+}
+static umode_t etr3_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct pmc_dev *pmcdev = dev_get_drvdata(dev);
+ const struct pmc_reg_map *map = pmcdev->map;
+ u32 reg;
+
+ mutex_lock(&pmcdev->lock);
+ reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
+ mutex_unlock(&pmcdev->lock);
+
+ return reg & ETR3_CF9LOCK ? attr->mode & (SYSFS_PREALLOC | 0444) : attr->mode;
+}
+
+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;
+ u32 reg;
+
+ if (!map->etr3_offset)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&pmcdev->lock);
+
+ reg = pmc_core_reg_read(pmcdev, map->etr3_offset);
+ reg &= ETR3_CF9GR | ETR3_CF9LOCK;
+
+ mutex_unlock(&pmcdev->lock);
+
+ return sysfs_emit(buf, "0x%08x", reg);
+}
+
+static ssize_t etr3_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pmc_dev *pmcdev = dev_get_drvdata(dev);
+ int err;
+ u32 reg;
+
+ err = kstrtouint(buf, 16, &reg);
+ if (err)
+ return err;
+
+ /* allow only CF9 writes */
+ if (reg != ETR3_CF9GR)
+ return -EINVAL;
+
+ err = set_etr3(pmcdev);
+ if (err)
+ return err;
+
+ return len;
+}
+static DEVICE_ATTR_RW(etr3);
+
+static struct attribute *pmc_attrs[] = {
+ &dev_attr_etr3.attr,
+ NULL
+};
+
+static const struct attribute_group pmc_attr_group = {
+ .attrs = pmc_attrs,
+ .is_visible = etr3_is_visible,
+};
+
+static const struct attribute_group *pmc_dev_groups[] = {
+ &pmc_attr_group,
+ NULL
+};
+
static int pmc_core_dev_state_get(void *data, u64 *val)
{
struct pmc_dev *pmcdev = data;
@@ -617,9 +784,8 @@ static int pmc_core_dev_state_get(void *data, u64 *val)
DEFINE_DEBUGFS_ATTRIBUTE(pmc_core_dev_state, pmc_core_dev_state_get, NULL, "%llu\n");
-static int pmc_core_check_read_lock_bit(void)
+static int pmc_core_check_read_lock_bit(struct pmc_dev *pmcdev)
{
- struct pmc_dev *pmcdev = &pmc;
u32 value;
value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_cfg_offset);
@@ -744,28 +910,26 @@ 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(void)
+static int pmc_core_mtpmc_link_status(struct pmc_dev *pmcdev)
{
- struct pmc_dev *pmcdev = &pmc;
u32 value;
value = pmc_core_reg_read(pmcdev, SPT_PMC_PM_STS_OFFSET);
return value & BIT(SPT_PMC_MSG_FULL_STS_BIT);
}
-static int pmc_core_send_msg(u32 *addr_xram)
+static int pmc_core_send_msg(struct pmc_dev *pmcdev, u32 *addr_xram)
{
- struct pmc_dev *pmcdev = &pmc;
u32 dest;
int timeout;
for (timeout = NUM_RETRIES; timeout > 0; timeout--) {
- if (pmc_core_mtpmc_link_status() == 0)
+ if (pmc_core_mtpmc_link_status(pmcdev) == 0)
break;
msleep(5);
}
- if (timeout <= 0 && pmc_core_mtpmc_link_status())
+ if (timeout <= 0 && pmc_core_mtpmc_link_status(pmcdev))
return -EBUSY;
dest = (*addr_xram & MTPMC_MASK) | (1U << 1);
@@ -791,7 +955,7 @@ static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused)
mutex_lock(&pmcdev->lock);
- if (pmc_core_send_msg(&mphy_core_reg_low) != 0) {
+ if (pmc_core_send_msg(pmcdev, &mphy_core_reg_low) != 0) {
err = -EBUSY;
goto out_unlock;
}
@@ -799,7 +963,7 @@ static int pmc_core_mphy_pg_show(struct seq_file *s, void *unused)
msleep(10);
val_low = pmc_core_reg_read(pmcdev, SPT_PMC_MFPMC_OFFSET);
- if (pmc_core_send_msg(&mphy_core_reg_high) != 0) {
+ if (pmc_core_send_msg(pmcdev, &mphy_core_reg_high) != 0) {
err = -EBUSY;
goto out_unlock;
}
@@ -842,7 +1006,7 @@ 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(&mphy_common_reg) != 0) {
+ if (pmc_core_send_msg(pmcdev, &mphy_common_reg) != 0) {
err = -EBUSY;
goto out_unlock;
}
@@ -863,9 +1027,8 @@ out_unlock:
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_pll);
-static int pmc_core_send_ltr_ignore(u32 value)
+static int pmc_core_send_ltr_ignore(struct pmc_dev *pmcdev, u32 value)
{
- struct pmc_dev *pmcdev = &pmc;
const struct pmc_reg_map *map = pmcdev->map;
u32 reg;
int err = 0;
@@ -891,6 +1054,8 @@ static ssize_t pmc_core_ltr_ignore_write(struct file *file,
const char __user *userbuf,
size_t count, loff_t *ppos)
{
+ struct seq_file *s = file->private_data;
+ struct pmc_dev *pmcdev = s->private;
u32 buf_size, value;
int err;
@@ -900,7 +1065,7 @@ static ssize_t pmc_core_ltr_ignore_write(struct file *file,
if (err)
return err;
- err = pmc_core_send_ltr_ignore(value);
+ err = pmc_core_send_ltr_ignore(pmcdev, value);
return err == 0 ? count : err;
}
@@ -1029,21 +1194,26 @@ static int pmc_core_ltr_show(struct seq_file *s, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_ltr);
+static inline u64 adjust_lpm_residency(struct pmc_dev *pmcdev, u32 offset,
+ const int lpm_adj_x2)
+{
+ u64 lpm_res = pmc_core_reg_read(pmcdev, offset);
+
+ return GET_X2_COUNTER((u64)lpm_adj_x2 * lpm_res);
+}
+
static int pmc_core_substate_res_show(struct seq_file *s, void *unused)
{
struct pmc_dev *pmcdev = s->private;
- const char **lpm_modes = pmcdev->map->lpm_modes;
+ const int lpm_adj_x2 = pmcdev->map->lpm_res_counter_step_x2;
u32 offset = pmcdev->map->lpm_residency_offset;
- u32 lpm_en;
- int index;
+ int i, mode;
- lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset);
- seq_printf(s, "status substate residency\n");
- for (index = 0; lpm_modes[index]; index++) {
- seq_printf(s, "%7s %7s %-15u\n",
- BIT(index) & lpm_en ? "Enabled" : " ",
- lpm_modes[index], pmc_core_reg_read(pmcdev, offset));
- offset += 4;
+ 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));
}
return 0;
@@ -1074,6 +1244,190 @@ static int pmc_core_substate_l_sts_regs_show(struct seq_file *s, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_l_sts_regs);
+static void pmc_core_substate_req_header_show(struct seq_file *s)
+{
+ struct pmc_dev *pmcdev = s->private;
+ int i, mode;
+
+ seq_printf(s, "%30s |", "Element");
+ pmc_for_each_mode(i, mode, pmcdev)
+ seq_printf(s, " %9s |", pmc_lpm_modes[mode]);
+
+ seq_printf(s, " %9s |\n", "Status");
+}
+
+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;
+ 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;
+ int mp;
+
+ /* Display the header */
+ pmc_core_substate_req_header_show(s);
+
+ /* Loop over maps */
+ for (mp = 0; mp < num_maps; mp++) {
+ u32 req_mask = 0;
+ u32 lpm_status;
+ int mode, idx, i, len = 32;
+
+ /*
+ * Capture the requirements and create a mask so that we only
+ * show an element if it's required for at least one of the
+ * enabled low power modes
+ */
+ pmc_for_each_mode(idx, mode, pmcdev)
+ 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));
+
+ /* Loop over elements in this map */
+ map = maps[mp];
+ for (i = 0; map[i].name && i < len; i++) {
+ u32 bit_mask = map[i].bit_mask;
+
+ if (!(bit_mask & req_mask))
+ /*
+ * Not required for any enabled states
+ * so don't display
+ */
+ continue;
+
+ /* Display the element name in the first column */
+ seq_printf(s, "%30s |", map[i].name);
+
+ /* Loop over the enabled states and display if required */
+ pmc_for_each_mode(idx, mode, pmcdev) {
+ if (lpm_req_regs[mp + (mode * num_maps)] & bit_mask)
+ seq_printf(s, " %9s |",
+ "Required");
+ else
+ seq_printf(s, " %9s |", " ");
+ }
+
+ /* In Status column, show the last captured state of this agent */
+ if (lpm_status & bit_mask)
+ seq_printf(s, " %9s |", "Yes");
+ else
+ seq_printf(s, " %9s |", " ");
+
+ seq_puts(s, "\n");
+ }
+ }
+
+ return 0;
+}
+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;
+ bool c10;
+ u32 reg;
+ int idx, mode;
+
+ reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset);
+ if (reg & LPM_STS_LATCH_MODE) {
+ seq_puts(s, "c10");
+ c10 = false;
+ } else {
+ seq_puts(s, "[c10]");
+ c10 = true;
+ }
+
+ pmc_for_each_mode(idx, mode, pmcdev) {
+ if ((BIT(mode) & reg) && !c10)
+ seq_printf(s, " [%s]", pmc_lpm_modes[mode]);
+ else
+ seq_printf(s, " %s", pmc_lpm_modes[mode]);
+ }
+
+ seq_puts(s, " clear\n");
+
+ return 0;
+}
+
+static ssize_t pmc_core_lpm_latch_mode_write(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *s = file->private_data;
+ struct pmc_dev *pmcdev = s->private;
+ bool clear = false, c10 = false;
+ unsigned char buf[8];
+ int idx, m, mode;
+ u32 reg;
+
+ if (count > sizeof(buf) - 1)
+ return -EINVAL;
+ if (copy_from_user(buf, userbuf, count))
+ return -EFAULT;
+ buf[count] = '\0';
+
+ /*
+ * Allowed strings are:
+ * Any enabled substate, e.g. 'S0i2.0'
+ * 'c10'
+ * 'clear'
+ */
+ mode = sysfs_match_string(pmc_lpm_modes, buf);
+
+ /* Check string matches enabled mode */
+ pmc_for_each_mode(idx, m, pmcdev)
+ if (mode == m)
+ break;
+
+ if (mode != m || mode < 0) {
+ if (sysfs_streq(buf, "clear"))
+ clear = true;
+ else if (sysfs_streq(buf, "c10"))
+ c10 = true;
+ else
+ return -EINVAL;
+ }
+
+ if (clear) {
+ mutex_lock(&pmcdev->lock);
+
+ reg = pmc_core_reg_read(pmcdev, pmcdev->map->etr3_offset);
+ reg |= ETR3_CLEAR_LPM_EVENTS;
+ pmc_core_reg_write(pmcdev, pmcdev->map->etr3_offset, reg);
+
+ mutex_unlock(&pmcdev->lock);
+
+ return count;
+ }
+
+ if (c10) {
+ mutex_lock(&pmcdev->lock);
+
+ reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset);
+ reg &= ~LPM_STS_LATCH_MODE;
+ pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg);
+
+ mutex_unlock(&pmcdev->lock);
+
+ return count;
+ }
+
+ /*
+ * For LPM mode latching we set the latch enable bit and selected mode
+ * and clear everything else.
+ */
+ reg = LPM_STS_LATCH_MODE | BIT(mode);
+ mutex_lock(&pmcdev->lock);
+ pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg);
+ mutex_unlock(&pmcdev->lock);
+
+ return count;
+}
+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;
@@ -1095,6 +1449,45 @@ static int pmc_core_pkgc_show(struct seq_file *s, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(pmc_core_pkgc);
+static void pmc_core_get_low_power_modes(struct pmc_dev *pmcdev)
+{
+ u8 lpm_priority[LPM_MAX_NUM_MODES];
+ u32 lpm_en;
+ int mode, i, p;
+
+ /* Use LPM Maps to indicate support for substates */
+ if (!pmcdev->map->lpm_num_maps)
+ return;
+
+ lpm_en = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_en_offset);
+ pmcdev->num_lpm_modes = hweight32(lpm_en);
+
+ /* Each byte contains information for 2 modes (7:4 and 3:0) */
+ for (mode = 0; mode < LPM_MAX_NUM_MODES; mode += 2) {
+ u8 priority = pmc_core_reg_read_byte(pmcdev,
+ pmcdev->map->lpm_priority_offset + (mode / 2));
+ int pri0 = GENMASK(3, 0) & priority;
+ int pri1 = (GENMASK(7, 4) & priority) >> 4;
+
+ lpm_priority[pri0] = mode;
+ lpm_priority[pri1] = mode + 1;
+ }
+
+ /*
+ * Loop though all modes from lowest to highest priority,
+ * and capture all enabled modes in order
+ */
+ i = 0;
+ for (p = LPM_MAX_NUM_MODES - 1; p >= 0; p--) {
+ int mode = lpm_priority[p];
+
+ if (!(BIT(mode) & lpm_en))
+ continue;
+
+ pmcdev->lpm_en_modes[i++] = mode;
+ }
+}
+
static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev)
{
debugfs_remove_recursive(pmcdev->dbgfs_dir);
@@ -1153,6 +1546,15 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev)
debugfs_create_file("substate_live_status_registers", 0444,
pmcdev->dbgfs_dir, pmcdev,
&pmc_core_substate_l_sts_regs_fops);
+ debugfs_create_file("lpm_latch_mode", 0644,
+ pmcdev->dbgfs_dir, pmcdev,
+ &pmc_core_lpm_latch_mode_fops);
+ }
+
+ if (pmcdev->lpm_req_regs) {
+ debugfs_create_file("substate_requirements", 0444,
+ pmcdev->dbgfs_dir, pmcdev,
+ &pmc_core_substate_req_regs_fops);
}
}
@@ -1171,6 +1573,7 @@ static const struct x86_cpu_id intel_pmc_core_ids[] = {
X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT, &tgl_reg_map),
X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_L, &icl_reg_map),
X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE, &tgl_reg_map),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, &tgl_reg_map),
{}
};
@@ -1186,9 +1589,15 @@ static const struct pci_device_id pmc_pci_ids[] = {
* the platform BIOS enforces 24Mhz crystal to shutdown
* before PMC can assert SLP_S0#.
*/
+static bool xtal_ignore;
static int quirk_xtal_ignore(const struct dmi_system_id *id)
{
- struct pmc_dev *pmcdev = &pmc;
+ xtal_ignore = true;
+ return 0;
+}
+
+static void pmc_core_xtal_ignore(struct pmc_dev *pmcdev)
+{
u32 value;
value = pmc_core_reg_read(pmcdev, pmcdev->map->pm_vric1_offset);
@@ -1197,7 +1606,6 @@ static int quirk_xtal_ignore(const struct dmi_system_id *id)
/* Low Voltage Mode Enable */
value &= ~SPT_PMC_VRIC1_SLPS0LVEN;
pmc_core_reg_write(pmcdev, pmcdev->map->pm_vric1_offset, value);
- return 0;
}
static const struct dmi_system_id pmc_core_dmi_table[] = {
@@ -1212,16 +1620,30 @@ static const struct dmi_system_id pmc_core_dmi_table[] = {
{}
};
+static void pmc_core_do_dmi_quirks(struct pmc_dev *pmcdev)
+{
+ dmi_check_system(pmc_core_dmi_table);
+
+ if (xtal_ignore)
+ pmc_core_xtal_ignore(pmcdev);
+}
+
static int pmc_core_probe(struct platform_device *pdev)
{
static bool device_initialized;
- struct pmc_dev *pmcdev = &pmc;
+ struct pmc_dev *pmcdev;
const struct x86_cpu_id *cpu_id;
u64 slp_s0_addr;
if (device_initialized)
return -ENODEV;
+ pmcdev = devm_kzalloc(&pdev->dev, sizeof(*pmcdev), GFP_KERNEL);
+ if (!pmcdev)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, pmcdev);
+
cpu_id = x86_match_cpu(intel_pmc_core_ids);
if (!cpu_id)
return -ENODEV;
@@ -1251,9 +1673,13 @@ static int pmc_core_probe(struct platform_device *pdev)
return -ENOMEM;
mutex_init(&pmcdev->lock);
- platform_set_drvdata(pdev, pmcdev);
- pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit();
- dmi_check_system(pmc_core_dmi_table);
+
+ pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(pmcdev);
+ pmc_core_get_low_power_modes(pmcdev);
+ pmc_core_do_dmi_quirks(pmcdev);
+
+ if (pmcdev->map == &tgl_reg_map)
+ pmc_core_get_tgl_lpm_reqs(pdev);
/*
* On TGL, due to a hardware limitation, the GBE LTR blocks PC10 when
@@ -1261,7 +1687,7 @@ static int pmc_core_probe(struct platform_device *pdev)
*/
if (pmcdev->map == &tgl_reg_map) {
dev_dbg(&pdev->dev, "ignoring GBE LTR\n");
- pmc_core_send_ltr_ignore(3);
+ pmc_core_send_ltr_ignore(pmcdev, 3);
}
pmc_core_dbgfs_register(pmcdev);
@@ -1384,6 +1810,7 @@ static struct platform_driver pmc_core_driver = {
.name = "intel_pmc_core",
.acpi_match_table = ACPI_PTR(pmc_core_acpi_ids),
.pm = &pmc_core_pm_ops,
+ .dev_groups = pmc_dev_groups,
},
.probe = pmc_core_probe,
.remove = pmc_core_remove,
diff --git a/drivers/platform/x86/intel_pmc_core.h b/drivers/platform/x86/intel_pmc_core.h
index f33cd2c34835..e8dae9c6c45f 100644
--- a/drivers/platform/x86/intel_pmc_core.h
+++ b/drivers/platform/x86/intel_pmc_core.h
@@ -187,20 +187,38 @@ enum ppfear_regs {
#define ICL_PMC_LTR_WIGIG 0x1BFC
#define ICL_PMC_SLP_S0_RES_COUNTER_STEP 0x64
-#define TGL_NUM_IP_IGN_ALLOWED 22
+#define LPM_MAX_NUM_MODES 8
+#define GET_X2_COUNTER(v) ((v) >> 1)
+#define LPM_STS_LATCH_MODE BIT(31)
+
#define TGL_PMC_SLP_S0_RES_COUNTER_STEP 0x7A
+#define TGL_PMC_LTR_THC0 0x1C04
+#define TGL_PMC_LTR_THC1 0x1C08
+#define TGL_NUM_IP_IGN_ALLOWED 23
+#define TGL_PMC_LPM_RES_COUNTER_STEP_X2 61 /* 30.5us * 2 */
/*
* Tigerlake Power Management Controller register offsets
*/
+#define TGL_LPM_STS_LATCH_EN_OFFSET 0x1C34
#define TGL_LPM_EN_OFFSET 0x1C78
#define TGL_LPM_RESIDENCY_OFFSET 0x1C80
/* Tigerlake Low Power Mode debug registers */
#define TGL_LPM_STATUS_OFFSET 0x1C3C
#define TGL_LPM_LIVE_STATUS_OFFSET 0x1C5C
+#define TGL_LPM_PRI_OFFSET 0x1C7C
+#define TGL_LPM_NUM_MAPS 6
+
+/* Extended Test Mode Register 3 (CNL and later) */
+#define ETR3_OFFSET 0x1048
+#define ETR3_CF9GR BIT(20)
+#define ETR3_CF9LOCK BIT(31)
+
+/* Extended Test Mode Register LPM bits (TGL and later */
+#define ETR3_CLEAR_LPM_EVENTS BIT(28)
-const char *tgl_lpm_modes[] = {
+const char *pmc_lpm_modes[] = {
"S0i2.0",
"S0i2.1",
"S0i2.2",
@@ -258,11 +276,15 @@ struct pmc_reg_map {
const u32 ltr_ignore_max;
const u32 pm_vric1_offset;
/* Low Power Mode registers */
- const char **lpm_modes;
+ const int lpm_num_maps;
+ const int lpm_res_counter_step_x2;
+ const u32 lpm_sts_latch_en_offset;
const u32 lpm_en_offset;
+ const u32 lpm_priority_offset;
const u32 lpm_residency_offset;
const u32 lpm_status_offset;
const u32 lpm_live_status_offset;
+ const u32 etr3_offset;
};
/**
@@ -278,6 +300,9 @@ struct pmc_reg_map {
* @check_counters: On resume, check if counters are getting incremented
* @pc10_counter: PC10 residency counter
* @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
*
* pmc_dev contains info about power management controller device.
*/
@@ -292,6 +317,28 @@ struct pmc_dev {
bool check_counters; /* Check for counter increments on resume */
u64 pc10_counter;
u64 s0ix_counter;
+ int num_lpm_modes;
+ int lpm_en_modes[LPM_MAX_NUM_MODES];
+ u32 *lpm_req_regs;
};
+#define pmc_for_each_mode(i, mode, pmcdev) \
+ for (i = 0, mode = pmcdev->lpm_en_modes[i]; \
+ i < pmcdev->num_lpm_modes; \
+ i++, mode = pmcdev->lpm_en_modes[i])
+
+#define DEFINE_PMC_CORE_ATTR_WRITE(__name) \
+static int __name ## _open(struct inode *inode, struct file *file) \
+{ \
+ return single_open(file, __name ## _show, inode->i_private); \
+} \
+ \
+static const struct file_operations __name ## _fops = { \
+ .owner = THIS_MODULE, \
+ .open = __name ## _open, \
+ .read = seq_read, \
+ .write = __name ## _write, \
+ .release = single_release, \
+}
+
#endif /* PMC_CORE_H */
diff --git a/drivers/platform/x86/intel_pmt_class.c b/drivers/platform/x86/intel_pmt_class.c
index ee2b3bbeb83d..c86ff15b1ed5 100644
--- a/drivers/platform/x86/intel_pmt_class.c
+++ b/drivers/platform/x86/intel_pmt_class.c
@@ -20,6 +20,28 @@
#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
/*
+ * Early implementations of PMT on client platforms have some
+ * differences from the server platforms (which use the Out Of Band
+ * Management Services Module OOBMSM). This list tracks those
+ * platforms as needed to handle those differences. Newer client
+ * platforms are expected to be fully compatible with server.
+ */
+static const struct pci_device_id pmt_telem_early_client_pci_ids[] = {
+ { PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */
+ { PCI_VDEVICE(INTEL, 0x490e) }, /* DG1 */
+ { PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */
+ { }
+};
+
+bool intel_pmt_is_early_client_hw(struct device *dev)
+{
+ struct pci_dev *parent = to_pci_dev(dev->parent);
+
+ return !!pci_match_id(pmt_telem_early_client_pci_ids, parent);
+}
+EXPORT_SYMBOL_GPL(intel_pmt_is_early_client_hw);
+
+/*
* sysfs
*/
static ssize_t
@@ -147,6 +169,30 @@ static int intel_pmt_populate_entry(struct intel_pmt_entry *entry,
* base address = end of discovery region + base offset
*/
entry->base_addr = disc_res->end + 1 + header->base_offset;
+
+ /*
+ * Some hardware use a different calculation for the base address
+ * when access_type == ACCESS_LOCAL. On the these systems
+ * ACCCESS_LOCAL refers to an address in the same BAR as the
+ * header but at a fixed offset. But as the header address was
+ * supplied to the driver, we don't know which BAR it was in.
+ * So search for the bar whose range includes the header address.
+ */
+ if (intel_pmt_is_early_client_hw(dev)) {
+ int i;
+
+ entry->base_addr = 0;
+ for (i = 0; i < 6; i++)
+ if (disc_res->start >= pci_resource_start(pci_dev, i) &&
+ (disc_res->start <= pci_resource_end(pci_dev, i))) {
+ entry->base_addr = pci_resource_start(pci_dev, i) +
+ header->base_offset;
+ break;
+ }
+ if (!entry->base_addr)
+ return -EINVAL;
+ }
+
break;
case ACCESS_BARID:
/*
diff --git a/drivers/platform/x86/intel_pmt_class.h b/drivers/platform/x86/intel_pmt_class.h
index de8f8139ba31..1337019c2873 100644
--- a/drivers/platform/x86/intel_pmt_class.h
+++ b/drivers/platform/x86/intel_pmt_class.h
@@ -44,6 +44,7 @@ struct intel_pmt_namespace {
struct device *dev);
};
+bool intel_pmt_is_early_client_hw(struct device *dev);
int intel_pmt_dev_create(struct intel_pmt_entry *entry,
struct intel_pmt_namespace *ns,
struct platform_device *pdev, int idx);
diff --git a/drivers/platform/x86/intel_pmt_telemetry.c b/drivers/platform/x86/intel_pmt_telemetry.c
index f8a87614efa4..9b95ef050457 100644
--- a/drivers/platform/x86/intel_pmt_telemetry.c
+++ b/drivers/platform/x86/intel_pmt_telemetry.c
@@ -34,26 +34,6 @@ struct pmt_telem_priv {
struct intel_pmt_entry entry[];
};
-/*
- * Early implementations of PMT on client platforms have some
- * differences from the server platforms (which use the Out Of Band
- * Management Services Module OOBMSM). This list tracks those
- * platforms as needed to handle those differences. Newer client
- * platforms are expected to be fully compatible with server.
- */
-static const struct pci_device_id pmt_telem_early_client_pci_ids[] = {
- { PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */
- { PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */
- { }
-};
-
-static bool intel_pmt_is_early_client_hw(struct device *dev)
-{
- struct pci_dev *parent = to_pci_dev(dev->parent);
-
- return !!pci_match_id(pmt_telem_early_client_pci_ids, parent);
-}
-
static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
struct device *dev)
{
diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c
index a2a2d923e60c..df1fc6c719f3 100644
--- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c
+++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c
@@ -21,12 +21,16 @@
#define PUNIT_MAILBOX_BUSY_BIT 31
/*
- * The average time to complete some commands is about 40us. The current
- * count is enough to satisfy 40us. But when the firmware is very busy, this
- * causes timeout occasionally. So increase to deal with some worst case
- * scenarios. Most of the command still complete in few us.
+ * The average time to complete mailbox commands is less than 40us. Most of
+ * the commands complete in few micro seconds. But the same firmware handles
+ * requests from all power management features.
+ * We can create a scenario where we flood the firmware with requests then
+ * the mailbox response can be delayed for 100s of micro seconds. So define
+ * two timeouts. One for average case and one for long.
+ * If the firmware is taking more than average, just call cond_resched().
*/
-#define OS_MAILBOX_RETRY_COUNT 100
+#define OS_MAILBOX_TIMEOUT_AVG_US 40
+#define OS_MAILBOX_TIMEOUT_MAX_US 1000
struct isst_if_device {
struct mutex mutex;
@@ -35,11 +39,13 @@ struct isst_if_device {
static int isst_if_mbox_cmd(struct pci_dev *pdev,
struct isst_if_mbox_cmd *mbox_cmd)
{
- u32 retries, data;
+ s64 tm_delta = 0;
+ ktime_t tm;
+ u32 data;
int ret;
/* Poll for rb bit == 0 */
- retries = OS_MAILBOX_RETRY_COUNT;
+ tm = ktime_get();
do {
ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,
&data);
@@ -48,11 +54,14 @@ static int isst_if_mbox_cmd(struct pci_dev *pdev,
if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {
ret = -EBUSY;
+ tm_delta = ktime_us_delta(ktime_get(), tm);
+ if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US)
+ cond_resched();
continue;
}
ret = 0;
break;
- } while (--retries);
+ } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US);
if (ret)
return ret;
@@ -74,7 +83,8 @@ static int isst_if_mbox_cmd(struct pci_dev *pdev,
return ret;
/* Poll for rb bit == 0 */
- retries = OS_MAILBOX_RETRY_COUNT;
+ tm_delta = 0;
+ tm = ktime_get();
do {
ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,
&data);
@@ -83,6 +93,9 @@ static int isst_if_mbox_cmd(struct pci_dev *pdev,
if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {
ret = -EBUSY;
+ tm_delta = ktime_us_delta(ktime_get(), tm);
+ if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US)
+ cond_resched();
continue;
}
@@ -96,7 +109,7 @@ static int isst_if_mbox_cmd(struct pci_dev *pdev,
mbox_cmd->resp_data = data;
ret = 0;
break;
- } while (--retries);
+ } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US);
return ret;
}
diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c
index dd900a76d8de..20145b539335 100644
--- a/drivers/platform/x86/lg-laptop.c
+++ b/drivers/platform/x86/lg-laptop.c
@@ -678,7 +678,7 @@ static int __init acpi_init(void)
result = acpi_bus_register_driver(&acpi_driver);
if (result < 0) {
- ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error registering driver\n"));
+ pr_debug("Error registering driver\n");
return -ENODEV;
}
diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 6388c3c705a6..d4f444401496 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -973,7 +973,7 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
pcc->mute = pcc->sinf[SINF_MUTE];
pcc->ac_brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
pcc->dc_brightness = pcc->sinf[SINF_DC_CUR_BRIGHT];
- result = pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT];
+ pcc->current_brightness = pcc->sinf[SINF_CUR_BRIGHT];
/* add sysfs attributes */
result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group);
diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c
index ca684ed760d1..a9d2a4b98e57 100644
--- a/drivers/platform/x86/pmc_atom.c
+++ b/drivers/platform/x86/pmc_atom.c
@@ -393,34 +393,10 @@ static const struct dmi_system_id critclk_systems[] = {
},
{
/* pmc_plt_clk* - are used for ethernet controllers */
- .ident = "Beckhoff CB3163",
+ .ident = "Beckhoff Baytrail",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"),
- DMI_MATCH(DMI_BOARD_NAME, "CB3163"),
- },
- },
- {
- /* pmc_plt_clk* - are used for ethernet controllers */
- .ident = "Beckhoff CB4063",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"),
- DMI_MATCH(DMI_BOARD_NAME, "CB4063"),
- },
- },
- {
- /* pmc_plt_clk* - are used for ethernet controllers */
- .ident = "Beckhoff CB6263",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"),
- DMI_MATCH(DMI_BOARD_NAME, "CB6263"),
- },
- },
- {
- /* pmc_plt_clk* - are used for ethernet controllers */
- .ident = "Beckhoff CB6363",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"),
- DMI_MATCH(DMI_BOARD_NAME, "CB6363"),
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "CBxx63"),
},
},
{
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index 0d9e2ddbf904..dd60c9397d35 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -175,6 +175,12 @@ enum tpacpi_hkey_event_t {
or port replicator */
TP_HKEY_EV_HOTPLUG_UNDOCK = 0x4011, /* undocked from hotplug
dock or port replicator */
+ /*
+ * Thinkpad X1 Tablet series devices emit 0x4012 and 0x4013
+ * when keyboard cover is attached, detached or folded onto the back
+ */
+ TP_HKEY_EV_KBD_COVER_ATTACH = 0x4012, /* keyboard cover attached */
+ TP_HKEY_EV_KBD_COVER_DETACH = 0x4013, /* keyboard cover detached or folded back */
/* User-interface events */
TP_HKEY_EV_LID_CLOSE = 0x5001, /* laptop lid closed */
@@ -3991,6 +3997,23 @@ static bool hotkey_notify_dockevent(const u32 hkey,
pr_info("undocked from hotplug port replicator\n");
return true;
+ /*
+ * Deliberately ignore attaching and detaching the keybord cover to avoid
+ * duplicates from intel-vbtn, which already emits SW_TABLET_MODE events
+ * to userspace.
+ *
+ * Please refer to the following thread for more information and a preliminary
+ * implementation using the GTOP ("Get Tablet OPtions") interface that could be
+ * extended to other attachment options of the ThinkPad X1 Tablet series, such as
+ * the Pico cartridge dock module:
+ * https://lore.kernel.org/platform-driver-x86/38cb8265-1e30-d547-9e12-b4ae290be737@a-kobel.de/
+ */
+ case TP_HKEY_EV_KBD_COVER_ATTACH:
+ case TP_HKEY_EV_KBD_COVER_DETACH:
+ *send_acpi_ev = false;
+ *ignore_acpi_ev = true;
+ return true;
+
default:
return false;
}
@@ -4088,7 +4111,7 @@ static bool hotkey_notify_6xxx(const u32 hkey,
return true;
case TP_HKEY_EV_KEY_FN_ESC:
- /* Get the media key status to foce the status LED to update */
+ /* Get the media key status to force the status LED to update */
acpi_evalf(hkey_handle, NULL, "GMKS", "v");
*send_acpi_ev = false;
*ignore_acpi_ev = true;
@@ -6260,6 +6283,7 @@ enum thermal_access_mode {
enum { /* TPACPI_THERMAL_TPEC_* */
TP_EC_THERMAL_TMP0 = 0x78, /* ACPI EC regs TMP 0..7 */
TP_EC_THERMAL_TMP8 = 0xC0, /* ACPI EC regs TMP 8..15 */
+ TP_EC_FUNCREV = 0xEF, /* ACPI EC Functional revision */
TP_EC_THERMAL_TMP_NA = -128, /* ACPI EC sensor not available */
TPACPI_THERMAL_SENSOR_NA = -128000, /* Sensor not available */
@@ -6272,6 +6296,8 @@ struct ibm_thermal_sensors_struct {
};
static enum thermal_access_mode thermal_read_mode;
+static const struct attribute_group *thermal_attr_group;
+static bool thermal_use_labels;
/* idx is zero-based */
static int thermal_get_sensor(int idx, s32 *value)
@@ -6454,11 +6480,33 @@ static const struct attribute_group thermal_temp_input8_group = {
#undef THERMAL_SENSOR_ATTR_TEMP
#undef THERMAL_ATTRS
+static ssize_t temp1_label_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "CPU\n");
+}
+static DEVICE_ATTR_RO(temp1_label);
+
+static ssize_t temp2_label_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "GPU\n");
+}
+static DEVICE_ATTR_RO(temp2_label);
+
+static struct attribute *temp_label_attributes[] = {
+ &dev_attr_temp1_label.attr,
+ &dev_attr_temp2_label.attr,
+ NULL
+};
+
+static const struct attribute_group temp_label_attr_group = {
+ .attrs = temp_label_attributes,
+};
+
/* --------------------------------------------------------------------- */
static int __init thermal_init(struct ibm_init_struct *iibm)
{
- u8 t, ta1, ta2;
+ u8 t, ta1, ta2, ver = 0;
int i;
int acpi_tmp7;
int res;
@@ -6473,7 +6521,14 @@ static int __init thermal_init(struct ibm_init_struct *iibm)
* 0x78-0x7F, 0xC0-0xC7. Registers return 0x00 for
* non-implemented, thermal sensors return 0x80 when
* not available
+ * The above rule is unfortunately flawed. This has been seen with
+ * 0xC2 (power supply ID) causing thermal control problems.
+ * The EC version can be determined by offset 0xEF and at least for
+ * version 3 the Lenovo firmware team confirmed that registers 0xC0-0xC7
+ * are not thermal registers.
*/
+ if (!acpi_ec_read(TP_EC_FUNCREV, &ver))
+ pr_warn("Thinkpad ACPI EC unable to access EC version\n");
ta1 = ta2 = 0;
for (i = 0; i < 8; i++) {
@@ -6483,11 +6538,13 @@ static int __init thermal_init(struct ibm_init_struct *iibm)
ta1 = 0;
break;
}
- if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) {
- ta2 |= t;
- } else {
- ta1 = 0;
- break;
+ if (ver < 3) {
+ if (acpi_ec_read(TP_EC_THERMAL_TMP8 + i, &t)) {
+ ta2 |= t;
+ } else {
+ ta1 = 0;
+ break;
+ }
}
}
if (ta1 == 0) {
@@ -6500,9 +6557,14 @@ static int __init thermal_init(struct ibm_init_struct *iibm)
thermal_read_mode = TPACPI_THERMAL_NONE;
}
} else {
- thermal_read_mode =
- (ta2 != 0) ?
- TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
+ if (ver >= 3) {
+ thermal_read_mode = TPACPI_THERMAL_TPEC_8;
+ thermal_use_labels = true;
+ } else {
+ thermal_read_mode =
+ (ta2 != 0) ?
+ TPACPI_THERMAL_TPEC_16 : TPACPI_THERMAL_TPEC_8;
+ }
}
} else if (acpi_tmp7) {
if (tpacpi_is_ibm() &&
@@ -6524,44 +6586,40 @@ static int __init thermal_init(struct ibm_init_struct *iibm)
switch (thermal_read_mode) {
case TPACPI_THERMAL_TPEC_16:
- res = sysfs_create_group(&tpacpi_hwmon->kobj,
- &thermal_temp_input16_group);
- if (res)
- return res;
+ thermal_attr_group = &thermal_temp_input16_group;
break;
case TPACPI_THERMAL_TPEC_8:
case TPACPI_THERMAL_ACPI_TMP07:
case TPACPI_THERMAL_ACPI_UPDT:
- res = sysfs_create_group(&tpacpi_hwmon->kobj,
- &thermal_temp_input8_group);
- if (res)
- return res;
+ thermal_attr_group = &thermal_temp_input8_group;
break;
case TPACPI_THERMAL_NONE:
default:
return 1;
}
+ res = sysfs_create_group(&tpacpi_hwmon->kobj, thermal_attr_group);
+ if (res)
+ return res;
+
+ if (thermal_use_labels) {
+ res = sysfs_create_group(&tpacpi_hwmon->kobj, &temp_label_attr_group);
+ if (res) {
+ sysfs_remove_group(&tpacpi_hwmon->kobj, thermal_attr_group);
+ return res;
+ }
+ }
+
return 0;
}
static void thermal_exit(void)
{
- switch (thermal_read_mode) {
- case TPACPI_THERMAL_TPEC_16:
- sysfs_remove_group(&tpacpi_hwmon->kobj,
- &thermal_temp_input16_group);
- break;
- case TPACPI_THERMAL_TPEC_8:
- case TPACPI_THERMAL_ACPI_TMP07:
- case TPACPI_THERMAL_ACPI_UPDT:
- sysfs_remove_group(&tpacpi_hwmon->kobj,
- &thermal_temp_input8_group);
- break;
- case TPACPI_THERMAL_NONE:
- default:
- break;
- }
+ if (thermal_attr_group)
+ sysfs_remove_group(&tpacpi_hwmon->kobj, thermal_attr_group);
+
+ if (thermal_use_labels)
+ sysfs_remove_group(&tpacpi_hwmon->kobj, &temp_label_attr_group);
}
static int thermal_read(struct seq_file *m)
@@ -10050,6 +10108,7 @@ static struct ibm_struct proxsensor_driver_data = {
*/
#define DYTC_CMD_SET 1 /* To enable/disable IC function mode */
+#define DYTC_CMD_MMC_GET 8 /* To get current MMC function and mode */
#define DYTC_CMD_RESET 0x1ff /* To reset back to default */
#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */
@@ -10066,6 +10125,10 @@ static struct ibm_struct proxsensor_driver_data = {
#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */
#define DYTC_MODE_LOWPOWER 3 /* Low power mode */
#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */
+#define DYTC_MODE_MMC_BALANCE 0 /* Default mode from MMC_GET, aka balanced */
+
+#define DYTC_ERR_MASK 0xF /* Bits 0-3 in cmd result are the error result */
+#define DYTC_ERR_SUCCESS 1 /* CMD completed successful */
#define DYTC_SET_COMMAND(function, mode, on) \
(DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \
@@ -10080,6 +10143,7 @@ static bool dytc_profile_available;
static enum platform_profile_option dytc_current_profile;
static atomic_t dytc_ignore_event = ATOMIC_INIT(0);
static DEFINE_MUTEX(dytc_mutex);
+static bool dytc_mmc_get_available;
static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile)
{
@@ -10088,6 +10152,7 @@ static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *p
*profile = PLATFORM_PROFILE_LOW_POWER;
break;
case DYTC_MODE_BALANCE:
+ case DYTC_MODE_MMC_BALANCE:
*profile = PLATFORM_PROFILE_BALANCED;
break;
case DYTC_MODE_PERFORM:
@@ -10165,7 +10230,6 @@ static int dytc_cql_command(int command, int *output)
if (err)
return err;
}
-
return cmd_err;
}
@@ -10222,7 +10286,10 @@ static void dytc_profile_refresh(void)
int perfmode;
mutex_lock(&dytc_mutex);
- err = dytc_cql_command(DYTC_CMD_GET, &output);
+ if (dytc_mmc_get_available)
+ err = dytc_command(DYTC_CMD_MMC_GET, &output);
+ else
+ err = dytc_cql_command(DYTC_CMD_GET, &output);
mutex_unlock(&dytc_mutex);
if (err)
return;
@@ -10271,6 +10338,16 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm)
if (dytc_version >= 5) {
dbg_printk(TPACPI_DBG_INIT,
"DYTC version %d: thermal mode available\n", dytc_version);
+ /*
+ * Check if MMC_GET functionality available
+ * Version > 6 and return success from MMC_GET command
+ */
+ dytc_mmc_get_available = false;
+ if (dytc_version >= 6) {
+ err = dytc_command(DYTC_CMD_MMC_GET, &output);
+ if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS))
+ dytc_mmc_get_available = true;
+ }
/* Create platform_profile structure and register */
err = platform_profile_register(&dytc_profile);
/*
@@ -10473,6 +10550,111 @@ static struct ibm_struct kbdlang_driver_data = {
.exit = kbdlang_exit,
};
+/*************************************************************************
+ * DPRC(Dynamic Power Reduction Control) subdriver, for the Lenovo WWAN
+ * and WLAN feature.
+ */
+#define DPRC_GET_WWAN_ANTENNA_TYPE 0x40000
+#define DPRC_WWAN_ANTENNA_TYPE_A_BIT BIT(4)
+#define DPRC_WWAN_ANTENNA_TYPE_B_BIT BIT(8)
+static bool has_antennatype;
+static int wwan_antennatype;
+
+static int dprc_command(int command, int *output)
+{
+ acpi_handle dprc_handle;
+
+ if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DPRC", &dprc_handle))) {
+ /* Platform doesn't support DPRC */
+ return -ENODEV;
+ }
+
+ if (!acpi_evalf(dprc_handle, output, NULL, "dd", command))
+ return -EIO;
+
+ /*
+ * METHOD_ERR gets returned on devices where few commands are not supported
+ * for example command to get WWAN Antenna type command is not supported on
+ * some devices.
+ */
+ if (*output & METHOD_ERR)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int get_wwan_antenna(int *wwan_antennatype)
+{
+ int output, err;
+
+ /* Get current Antenna type */
+ err = dprc_command(DPRC_GET_WWAN_ANTENNA_TYPE, &output);
+ if (err)
+ return err;
+
+ if (output & DPRC_WWAN_ANTENNA_TYPE_A_BIT)
+ *wwan_antennatype = 1;
+ else if (output & DPRC_WWAN_ANTENNA_TYPE_B_BIT)
+ *wwan_antennatype = 2;
+ else
+ return -ENODEV;
+
+ return 0;
+}
+
+/* sysfs wwan antenna type entry */
+static ssize_t wwan_antenna_type_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ switch (wwan_antennatype) {
+ case 1:
+ return sysfs_emit(buf, "type a\n");
+ case 2:
+ return sysfs_emit(buf, "type b\n");
+ default:
+ return -ENODATA;
+ }
+}
+static DEVICE_ATTR_RO(wwan_antenna_type);
+
+static int tpacpi_dprc_init(struct ibm_init_struct *iibm)
+{
+ int wwanantenna_err, err;
+
+ wwanantenna_err = get_wwan_antenna(&wwan_antennatype);
+ /*
+ * If support isn't available (ENODEV) then quit, but don't
+ * return an error.
+ */
+ if (wwanantenna_err == -ENODEV)
+ return 0;
+
+ /* if there was an error return it */
+ if (wwanantenna_err && (wwanantenna_err != -ENODEV))
+ return wwanantenna_err;
+ else if (!wwanantenna_err)
+ has_antennatype = true;
+
+ if (has_antennatype) {
+ err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_wwan_antenna_type.attr);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
+static void dprc_exit(void)
+{
+ if (has_antennatype)
+ sysfs_remove_file(&tpacpi_pdev->dev.kobj, &dev_attr_wwan_antenna_type.attr);
+}
+
+static struct ibm_struct dprc_driver_data = {
+ .name = "dprc",
+ .exit = dprc_exit,
+};
+
/****************************************************************************
****************************************************************************
*
@@ -10977,6 +11159,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
.init = tpacpi_kbdlang_init,
.data = &kbdlang_driver_data,
},
+ {
+ .init = tpacpi_dprc_init,
+ .data = &dprc_driver_data,
+ },
};
static int __init set_ibm_param(const char *val, const struct kernel_param *kp)
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index c44a6e8dceb8..90fe4f8f3c2c 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -715,6 +715,32 @@ static const struct ts_dmi_data techbite_arc_11_6_data = {
.properties = techbite_arc_11_6_props,
};
+static const struct property_entry teclast_tbook11_props[] = {
+ PROPERTY_ENTRY_U32("touchscreen-min-x", 8),
+ PROPERTY_ENTRY_U32("touchscreen-min-y", 14),
+ PROPERTY_ENTRY_U32("touchscreen-size-x", 1916),
+ PROPERTY_ENTRY_U32("touchscreen-size-y", 1264),
+ PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"),
+ PROPERTY_ENTRY_STRING("firmware-name", "gsl3692-teclast-tbook11.fw"),
+ PROPERTY_ENTRY_U32("silead,max-fingers", 10),
+ PROPERTY_ENTRY_BOOL("silead,home-button"),
+ { }
+};
+
+static const struct ts_dmi_data teclast_tbook11_data = {
+ .embedded_fw = {
+ .name = "silead/gsl3692-teclast-tbook11.fw",
+ .prefix = { 0xf0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 },
+ .length = 43560,
+ .sha256 = { 0x9d, 0xb0, 0x3d, 0xf1, 0x00, 0x3c, 0xb5, 0x25,
+ 0x62, 0x8a, 0xa0, 0x93, 0x4b, 0xe0, 0x4e, 0x75,
+ 0xd1, 0x27, 0xb1, 0x65, 0x3c, 0xba, 0xa5, 0x0f,
+ 0xcd, 0xb4, 0xbe, 0x00, 0xbb, 0xf6, 0x43, 0x29 },
+ },
+ .acpi_name = "MSSL1680:00",
+ .properties = teclast_tbook11_props,
+};
+
static const struct property_entry teclast_x3_plus_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1980),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1500),
@@ -1244,6 +1270,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
},
},
{
+ /* Teclast Tbook 11 */
+ .driver_data = (void *)&teclast_tbook11_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TECLAST"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TbooK 11"),
+ DMI_MATCH(DMI_PRODUCT_SKU, "E5A6_A1"),
+ },
+ },
+ {
/* Teclast X3 Plus */
.driver_data = (void *)&teclast_x3_plus_data,
.matches = {
@@ -1355,7 +1390,7 @@ static void ts_dmi_add_props(struct i2c_client *client)
if (has_acpi_companion(dev) &&
!strncmp(ts_data->acpi_name, client->name, I2C_NAME_SIZE)) {
- error = device_add_properties(dev, ts_data->properties);
+ error = device_create_managed_software_node(dev, ts_data->properties, NULL);
if (error)
dev_err(dev, "failed to add properties: %d\n", error);
}
diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c
index 66b434d6307f..80137afb9753 100644
--- a/drivers/platform/x86/wmi-bmof.c
+++ b/drivers/platform/x86/wmi-bmof.c
@@ -86,13 +86,12 @@ static int wmi_bmof_probe(struct wmi_device *wdev, const void *context)
return ret;
}
-static int wmi_bmof_remove(struct wmi_device *wdev)
+static void wmi_bmof_remove(struct wmi_device *wdev)
{
struct bmof_priv *priv = dev_get_drvdata(&wdev->dev);
sysfs_remove_bin_file(&wdev->dev.kobj, &priv->bmof_bin_attr);
kfree(priv->bmofdata);
- return 0;
}
static const struct wmi_device_id wmi_bmof_id_table[] = {
diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c
index c669676ea8e8..62e0d56a3332 100644
--- a/drivers/platform/x86/wmi.c
+++ b/drivers/platform/x86/wmi.c
@@ -32,7 +32,6 @@
#include <linux/fs.h>
#include <uapi/linux/wmi.h>
-ACPI_MODULE_NAME("wmi");
MODULE_AUTHOR("Carlos Corbacho");
MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
MODULE_LICENSE("GPL");
@@ -986,7 +985,6 @@ static int wmi_dev_remove(struct device *dev)
struct wmi_block *wblock = dev_to_wblock(dev);
struct wmi_driver *wdriver =
container_of(dev->driver, struct wmi_driver, driver);
- int ret = 0;
if (wdriver->filter_callback) {
misc_deregister(&wblock->char_dev);
@@ -995,12 +993,12 @@ static int wmi_dev_remove(struct device *dev)
}
if (wdriver->remove)
- ret = wdriver->remove(dev_to_wdev(dev));
+ wdriver->remove(dev_to_wdev(dev));
if (ACPI_FAILURE(wmi_method_enable(wblock, 0)))
dev_warn(dev, "failed to disable device\n");
- return ret;
+ return 0;
}
static struct class wmi_bus_class = {
diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c
index 8337c99d2ce2..97440462aa25 100644
--- a/drivers/platform/x86/xo15-ebook.c
+++ b/drivers/platform/x86/xo15-ebook.c
@@ -26,8 +26,6 @@
#define XO15_EBOOK_HID "XO15EBK"
#define XO15_EBOOK_DEVICE_NAME "EBook Switch"
-ACPI_MODULE_NAME(MODULE_NAME);
-
MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver");
MODULE_LICENSE("GPL");
@@ -66,8 +64,8 @@ static void ebook_switch_notify(struct acpi_device *device, u32 event)
ebook_send_state(device);
break;
default:
- ACPI_DEBUG_PRINT((ACPI_DB_INFO,
- "Unsupported event [0x%x]\n", event));
+ acpi_handle_debug(device->handle,
+ "Unsupported event [0x%x]\n", event);
break;
}
}
diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
index f4b1ba887384..0806796eabcb 100644
--- a/include/linux/surface_aggregator/controller.h
+++ b/include/linux/surface_aggregator/controller.h
@@ -344,16 +344,16 @@ struct ssam_request_spec_md {
* request has been fully completed. The required transport buffer will be
* allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_controller
- * *ctrl)``, returning the status of the request, which is zero on success and
- * negative on failure. The ``ctrl`` parameter is the controller via which the
- * request is being sent.
+ * The generated function is defined as ``static int name(struct
+ * ssam_controller *ctrl)``, returning the status of the request, which is
+ * zero on success and negative on failure. The ``ctrl`` parameter is the
+ * controller via which the request is being sent.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \
- int name(struct ssam_controller *ctrl) \
+ static int name(struct ssam_controller *ctrl) \
{ \
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
struct ssam_request rqst; \
@@ -383,17 +383,17 @@ struct ssam_request_spec_md {
* returning once the request has been fully completed. The required transport
* buffer will be allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_controller
- * *ctrl, const atype *arg)``, returning the status of the request, which is
- * zero on success and negative on failure. The ``ctrl`` parameter is the
- * controller via which the request is sent. The request argument is specified
- * via the ``arg`` pointer.
+ * The generated function is defined as ``static int name(struct
+ * ssam_controller *ctrl, const atype *arg)``, returning the status of the
+ * request, which is zero on success and negative on failure. The ``ctrl``
+ * parameter is the controller via which the request is sent. The request
+ * argument is specified via the ``arg`` pointer.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \
- int name(struct ssam_controller *ctrl, const atype *arg) \
+ static int name(struct ssam_controller *ctrl, const atype *arg) \
{ \
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
struct ssam_request rqst; \
@@ -424,17 +424,17 @@ struct ssam_request_spec_md {
* request itself, returning once the request has been fully completed. The
* required transport buffer will be allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_controller
- * *ctrl, rtype *ret)``, returning the status of the request, which is zero on
- * success and negative on failure. The ``ctrl`` parameter is the controller
- * via which the request is sent. The request's return value is written to the
- * memory pointed to by the ``ret`` parameter.
+ * The generated function is defined as ``static int name(struct
+ * ssam_controller *ctrl, rtype *ret)``, returning the status of the request,
+ * which is zero on success and negative on failure. The ``ctrl`` parameter is
+ * the controller via which the request is sent. The request's return value is
+ * written to the memory pointed to by the ``ret`` parameter.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \
- int name(struct ssam_controller *ctrl, rtype *ret) \
+ static int name(struct ssam_controller *ctrl, rtype *ret) \
{ \
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
struct ssam_request rqst; \
@@ -483,17 +483,17 @@ struct ssam_request_spec_md {
* returning once the request has been fully completed. The required transport
* buffer will be allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_controller
- * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is
- * zero on success and negative on failure. The ``ctrl`` parameter is the
- * controller via which the request is sent, ``tid`` the target ID for the
- * request, and ``iid`` the instance ID.
+ * The generated function is defined as ``static int name(struct
+ * ssam_controller *ctrl, u8 tid, u8 iid)``, returning the status of the
+ * request, which is zero on success and negative on failure. The ``ctrl``
+ * parameter is the controller via which the request is sent, ``tid`` the
+ * target ID for the request, and ``iid`` the instance ID.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \
- int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \
+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \
{ \
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
struct ssam_request rqst; \
@@ -524,18 +524,18 @@ struct ssam_request_spec_md {
* the request itself, returning once the request has been fully completed.
* The required transport buffer will be allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_controller
- * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the
- * request, which is zero on success and negative on failure. The ``ctrl``
- * parameter is the controller via which the request is sent, ``tid`` the
- * target ID for the request, and ``iid`` the instance ID. The request argument
- * is specified via the ``arg`` pointer.
+ * The generated function is defined as ``static int name(struct
+ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the
+ * status of the request, which is zero on success and negative on failure.
+ * The ``ctrl`` parameter is the controller via which the request is sent,
+ * ``tid`` the target ID for the request, and ``iid`` the instance ID. The
+ * request argument is specified via the ``arg`` pointer.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \
- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\
+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg) \
{ \
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
struct ssam_request rqst; \
@@ -567,18 +567,18 @@ struct ssam_request_spec_md {
* execution of the request itself, returning once the request has been fully
* completed. The required transport buffer will be allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_controller
- * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request,
- * which is zero on success and negative on failure. The ``ctrl`` parameter is
- * the controller via which the request is sent, ``tid`` the target ID for the
- * request, and ``iid`` the instance ID. The request's return value is written
- * to the memory pointed to by the ``ret`` parameter.
+ * The generated function is defined as ``static int name(struct
+ * ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status
+ * of the request, which is zero on success and negative on failure. The
+ * ``ctrl`` parameter is the controller via which the request is sent, ``tid``
+ * the target ID for the request, and ``iid`` the instance ID. The request's
+ * return value is written to the memory pointed to by the ``ret`` parameter.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \
- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \
+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \
{ \
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
struct ssam_request rqst; \
diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
index 02f3e06c0a60..4441ad667c3f 100644
--- a/include/linux/surface_aggregator/device.h
+++ b/include/linux/surface_aggregator/device.h
@@ -336,17 +336,18 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
* request has been fully completed. The required transport buffer will be
* allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_device *sdev)``,
- * returning the status of the request, which is zero on success and negative
- * on failure. The ``sdev`` parameter specifies both the target device of the
- * request and by association the controller via which the request is sent.
+ * The generated function is defined as ``static int name(struct ssam_device
+ * *sdev)``, returning the status of the request, which is zero on success and
+ * negative on failure. The ``sdev`` parameter specifies both the target
+ * device of the request and by association the controller via which the
+ * request is sent.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \
SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \
- int name(struct ssam_device *sdev) \
+ static int name(struct ssam_device *sdev) \
{ \
return __raw_##name(sdev->ctrl, sdev->uid.target, \
sdev->uid.instance); \
@@ -368,19 +369,19 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
* itself, returning once the request has been fully completed. The required
* transport buffer will be allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_device *sdev,
- * const atype *arg)``, returning the status of the request, which is zero on
- * success and negative on failure. The ``sdev`` parameter specifies both the
- * target device of the request and by association the controller via which
- * the request is sent. The request's argument is specified via the ``arg``
- * pointer.
+ * The generated function is defined as ``static int name(struct ssam_device
+ * *sdev, const atype *arg)``, returning the status of the request, which is
+ * zero on success and negative on failure. The ``sdev`` parameter specifies
+ * both the target device of the request and by association the controller via
+ * which the request is sent. The request's argument is specified via the
+ * ``arg`` pointer.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \
SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \
- int name(struct ssam_device *sdev, const atype *arg) \
+ static int name(struct ssam_device *sdev, const atype *arg) \
{ \
return __raw_##name(sdev->ctrl, sdev->uid.target, \
sdev->uid.instance, arg); \
@@ -402,8 +403,8 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
* itself, returning once the request has been fully completed. The required
* transport buffer will be allocated on the stack.
*
- * The generated function is defined as ``int name(struct ssam_device *sdev,
- * rtype *ret)``, returning the status of the request, which is zero on
+ * The generated function is defined as ``static int name(struct ssam_device
+ * *sdev, rtype *ret)``, returning the status of the request, which is zero on
* success and negative on failure. The ``sdev`` parameter specifies both the
* target device of the request and by association the controller via which
* the request is sent. The request's return value is written to the memory
@@ -414,7 +415,7 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
*/
#define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \
SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \
- int name(struct ssam_device *sdev, rtype *ret) \
+ static int name(struct ssam_device *sdev, rtype *ret) \
{ \
return __raw_##name(sdev->ctrl, sdev->uid.target, \
sdev->uid.instance, ret); \
diff --git a/include/linux/wmi.h b/include/linux/wmi.h
index 8ef7e7faea1e..2cb3913c1f50 100644
--- a/include/linux/wmi.h
+++ b/include/linux/wmi.h
@@ -37,7 +37,7 @@ struct wmi_driver {
const struct wmi_device_id *id_table;
int (*probe)(struct wmi_device *wdev, const void *context);
- int (*remove)(struct wmi_device *wdev);
+ void (*remove)(struct wmi_device *wdev);
void (*notify)(struct wmi_device *device, union acpi_object *data);
long (*filter_callback)(struct wmi_device *wdev, unsigned int cmd,
struct wmi_ioctl_buffer *arg);
diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h
new file mode 100644
index 000000000000..0833aab0d819
--- /dev/null
+++ b/include/uapi/linux/surface_aggregator/dtx.h
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Surface DTX (clipboard detachment system driver) user-space interface.
+ *
+ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This
+ * device allows user-space to control the clipboard detachment process on
+ * Surface Book series devices.
+ *
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
+#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/* Status/error categories */
+#define SDTX_CATEGORY_STATUS 0x0000
+#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000
+#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000
+#define SDTX_CATEGORY_UNKNOWN 0xf000
+
+#define SDTX_CATEGORY_MASK 0xf000
+#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK)
+
+#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS)
+#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR)
+#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR)
+#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN)
+
+#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS)
+
+/* Latch status values */
+#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00)
+#define SDTX_LATCH_OPENED SDTX_STATUS(0x01)
+
+/* Base state values */
+#define SDTX_BASE_DETACHED SDTX_STATUS(0x00)
+#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01)
+
+/* Runtime errors (non-critical) */
+#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01)
+#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02)
+
+/* Hardware errors (critical) */
+#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01)
+#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02)
+#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03)
+
+/* Base types */
+#define SDTX_DEVICE_TYPE_HID 0x0100
+#define SDTX_DEVICE_TYPE_SSH 0x0200
+
+#define SDTX_DEVICE_TYPE_MASK 0x0f00
+#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK)
+
+#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID)
+#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH)
+
+/**
+ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is
+ * attached to the base of the device.
+ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the
+ * device operates as tablet.
+ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base
+ * and the device operates as laptop.
+ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse.
+ * The device operates as tablet with keyboard and
+ * touchpad deactivated, however, the base battery
+ * and, if present in the specific device model, dGPU
+ * are available to the system.
+ */
+enum sdtx_device_mode {
+ SDTX_DEVICE_MODE_TABLET = 0x00,
+ SDTX_DEVICE_MODE_LAPTOP = 0x01,
+ SDTX_DEVICE_MODE_STUDIO = 0x02,
+};
+
+/**
+ * struct sdtx_event - Event provided by reading from the DTX device file.
+ * @length: Length of the event payload, in bytes.
+ * @code: Event code, detailing what type of event this is.
+ * @data: Payload of the event, containing @length bytes.
+ *
+ * See &enum sdtx_event_code for currently valid event codes.
+ */
+struct sdtx_event {
+ __u16 length;
+ __u16 code;
+ __u8 data[];
+} __attribute__((__packed__));
+
+/**
+ * enum sdtx_event_code - Code describing the type of an event.
+ * @SDTX_EVENT_REQUEST: Detachment request event type.
+ * @SDTX_EVENT_CANCEL: Cancel detachment process event type.
+ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type.
+ * @SDTX_EVENT_LATCH_STATUS: Latch status change event type.
+ * @SDTX_EVENT_DEVICE_MODE: Device mode change event type.
+ *
+ * Used in &struct sdtx_event to describe the type of the event. Further event
+ * codes are reserved for future use. Any event parser should be able to
+ * gracefully handle unknown events, i.e. by simply skipping them.
+ *
+ * Consult the DTX user-space interface documentation for details regarding
+ * the individual event types.
+ */
+enum sdtx_event_code {
+ SDTX_EVENT_REQUEST = 1,
+ SDTX_EVENT_CANCEL = 2,
+ SDTX_EVENT_BASE_CONNECTION = 3,
+ SDTX_EVENT_LATCH_STATUS = 4,
+ SDTX_EVENT_DEVICE_MODE = 5,
+};
+
+/**
+ * struct sdtx_base_info - Describes if and what type of base is connected.
+ * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED,
+ * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base
+ * is attached but low clipboard battery prevents detachment). Other
+ * values are currently reserved.
+ * @base_id: The type of base connected. Zero if no base is connected.
+ */
+struct sdtx_base_info {
+ __u16 state;
+ __u16 base_id;
+} __attribute__((__packed__));
+
+/* IOCTLs */
+#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21)
+#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22)
+
+#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23)
+#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24)
+
+#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25)
+#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26)
+#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27)
+#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28)
+
+#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info)
+#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16)
+#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16)
+
+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */
diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c
index 582feb88eca3..ab940c508ef0 100644
--- a/tools/power/x86/intel-speed-select/isst-config.c
+++ b/tools/power/x86/intel-speed-select/isst-config.c
@@ -15,7 +15,7 @@ struct process_cmd_struct {
int arg;
};
-static const char *version_str = "v1.8";
+static const char *version_str = "v1.9";
static const int supported_api_ver = 1;
static struct isst_if_platform_info isst_platform_info;
static char *progname;
@@ -381,6 +381,18 @@ static void set_cpu_online_offline(int cpu, int state)
close(fd);
}
+static void force_all_cpus_online(void)
+{
+ int i;
+
+ fprintf(stderr, "Forcing all CPUs online\n");
+
+ for (i = 0; i < topo_max_cpus; ++i)
+ set_cpu_online_offline(i, 1);
+
+ unlink("/var/run/isst_cpu_topology.dat");
+}
+
#define MAX_PACKAGE_COUNT 8
#define MAX_DIE_PER_PACKAGE 2
static void for_each_online_package_in_set(void (*callback)(int, void *, void *,
@@ -959,6 +971,10 @@ static void isst_print_extended_platform_info(void)
fprintf(outf, "Intel(R) SST-BF (feature base-freq) is not supported\n");
ret = isst_read_pm_config(i, &cp_state, &cp_cap);
+ if (ret) {
+ fprintf(outf, "Intel(R) SST-CP (feature core-power) status is unknown\n");
+ return;
+ }
if (cp_cap)
fprintf(outf, "Intel(R) SST-CP (feature core-power) is supported\n");
else
@@ -2763,6 +2779,7 @@ static void usage(void)
printf("\t[-f|--format] : output format [json|text]. Default: text\n");
printf("\t[-h|--help] : Print help\n");
printf("\t[-i|--info] : Print platform information\n");
+ printf("\t[-a|--all-cpus-online] : Force online every CPU in the system\n");
printf("\t[-o|--out] : Output file\n");
printf("\t\t\tDefault : stderr\n");
printf("\t[-p|--pause] : Delay between two mail box commands in milliseconds\n");
@@ -2791,7 +2808,6 @@ static void usage(void)
static void print_version(void)
{
fprintf(outf, "Version %s\n", version_str);
- fprintf(outf, "Build date %s time %s\n", __DATE__, __TIME__);
exit(0);
}
@@ -2800,11 +2816,12 @@ static void cmdline(int argc, char **argv)
const char *pathname = "/dev/isst_interface";
char *ptr;
FILE *fp;
- int opt;
+ int opt, force_cpus_online = 0;
int option_index = 0;
int ret;
static struct option long_options[] = {
+ { "all-cpus-online", no_argument, 0, 'a' },
{ "cpu", required_argument, 0, 'c' },
{ "debug", no_argument, 0, 'd' },
{ "format", required_argument, 0, 'f' },
@@ -2840,9 +2857,12 @@ static void cmdline(int argc, char **argv)
}
progname = argv[0];
- while ((opt = getopt_long_only(argc, argv, "+c:df:hio:v", long_options,
+ while ((opt = getopt_long_only(argc, argv, "+c:df:hio:va", long_options,
&option_index)) != -1) {
switch (opt) {
+ case 'a':
+ force_cpus_online = 1;
+ break;
case 'c':
parse_cpu_command(optarg);
break;
@@ -2892,6 +2912,8 @@ static void cmdline(int argc, char **argv)
exit(0);
}
set_max_cpu_num();
+ if (force_cpus_online)
+ force_all_cpus_online();
store_cpu_topology();
set_cpu_present_cpu_mask();
set_cpu_target_cpu_mask();
diff --git a/tools/power/x86/intel-speed-select/isst-display.c b/tools/power/x86/intel-speed-select/isst-display.c
index 8e54ce47648e..3bf1820c0da1 100644
--- a/tools/power/x86/intel-speed-select/isst-display.c
+++ b/tools/power/x86/intel-speed-select/isst-display.c
@@ -25,10 +25,14 @@ static void printcpulist(int str_len, char *str, int mask_size,
index = snprintf(&str[curr_index],
str_len - curr_index, ",");
curr_index += index;
+ if (curr_index >= str_len)
+ break;
}
index = snprintf(&str[curr_index], str_len - curr_index, "%d",
i);
curr_index += index;
+ if (curr_index >= str_len)
+ break;
first = 0;
}
}
@@ -64,10 +68,14 @@ static void printcpumask(int str_len, char *str, int mask_size,
index = snprintf(&str[curr_index], str_len - curr_index, "%08x",
mask[i]);
curr_index += index;
+ if (curr_index >= str_len)
+ break;
if (i) {
strncat(&str[curr_index], ",", str_len - curr_index);
curr_index++;
}
+ if (curr_index >= str_len)
+ break;
}
free(mask);
@@ -185,7 +193,7 @@ static void _isst_pbf_display_information(int cpu, FILE *outf, int level,
int disp_level)
{
char header[256];
- char value[256];
+ char value[512];
snprintf(header, sizeof(header), "speed-select-base-freq-properties");
format_and_print(outf, disp_level, header, NULL);
@@ -349,7 +357,7 @@ void isst_ctdp_display_information(int cpu, FILE *outf, int tdp_level,
struct isst_pkg_ctdp *pkg_dev)
{
char header[256];
- char value[256];
+ char value[512];
static int level;
int i;