diff options
57 files changed, 3978 insertions, 312 deletions
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:[email protected]> 0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator <mailto:[email protected]> +0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver + <mailto:[email protected]> 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 d92f85ca831d..7dd6b67f0f51 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 <[email protected]> +S: Maintained +F: drivers/platform/x86/adv_swbutton.c + ADXL34X THREE-AXIS DIGITAL ACCELEROMETER DRIVER (ADXL345/ADXL346) M: Michael Hennerich <[email protected]> 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 +S: Maintained +F: drivers/platform/x86/dell/alienware-wmi.c + ALL SENSORS DLH SERIES PRESSURE SENSORS DRIVER M: Tomislav Denis <[email protected]> @@ -5034,19 +5045,19 @@ F: drivers/platform/x86/dell/dell_rbu.c DELL SMBIOS DRIVER M: Pali Rohár <[email protected]> -M: Mario Limonciello <[email protected]> S: Maintained F: drivers/platform/x86/dell/dell-smbios.* DELL SMBIOS SMM DRIVER -M: Mario Limonciello <[email protected]> S: Maintained F: drivers/platform/x86/dell/dell-smbios-smm.c DELL SMBIOS WMI DRIVER -M: Mario Limonciello <[email protected]> S: Maintained F: drivers/platform/x86/dell/dell-smbios-wmi.c @@ -5060,14 +5071,14 @@ F: Documentation/driver-api/dcdbas.rst F: drivers/platform/x86/dell/dcdbas.* DELL WMI DESCRIPTOR DRIVER -M: Mario Limonciello <[email protected]> S: Maintained F: drivers/platform/x86/dell/dell-wmi-descriptor.c DELL WMI SYSMAN DRIVER M: Divya Bharathi <[email protected]> -M: Mario Limonciello <[email protected]> M: Prasanth Ksr <[email protected]> S: Maintained F: Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -9244,7 +9255,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 <[email protected]> S: Maintained F: drivers/platform/x86/intel-wmi-thunderbolt.c @@ -11437,8 +11448,8 @@ Q: https://patchwork.kernel.org/project/netdevbpf/list/ F: drivers/net/ethernet/mellanox/mlxfw/ MELLANOX HARDWARE PLATFORM SUPPORT -M: Andy Shevchenko <[email protected]> -M: Darren Hart <[email protected]> +M: Hans de Goede <[email protected]> +M: Mark Gross <[email protected]> M: Vadim Pasternak <[email protected]> S: Supported @@ -11861,6 +11872,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 <[email protected]> +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 <[email protected]> @@ -11882,6 +11901,12 @@ L: [email protected] S: Maintained F: drivers/platform/surface/surface_hotplug.c +MICROSOFT SURFACE PLATFORM PROFILE DRIVER +M: Maximilian Luz <[email protected]> +S: Maintained +F: drivers/platform/surface/surface_platform_profile.c + MICROSOFT SURFACE PRO 3 BUTTON DRIVER M: Chen Yu <[email protected]> @@ -11897,6 +11922,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/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..aa6f37b4f46e 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -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, 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 <[email protected]> + */ + +#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 <[email protected]>"); +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 <[email protected]> + */ + +#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 <[email protected]>"); +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 <[email protected]> + */ + +#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 <[email protected]>"); +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 ad4e630e73e2..53973ee59bf7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -193,6 +193,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 +421,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. @@ -1173,21 +1185,26 @@ config INTEL_PMC_CORE depends on PCI help The Intel Platform Controller Hub for Intel Core SoCs provides access - to Power Management Controller registers via a PCI interface. This + to Power Management Controller registers via various interfaces. This driver can utilize debugging capabilities and supported features as - exposed by the Power Management Controller. + exposed by the Power Management Controller. It also may perform some + tasks in the PMC in order to enable transition into the SLPS0 state. + It should be selected on all Intel platforms supported by the driver. Supported features: - SLP_S0_RESIDENCY counter - PCH IP Power Gating status - - LTR Ignore + - LTR Ignore / LTR Show - MPHY/PLL gating status (Sunrisepoint PCH only) + - SLPS0 Debug registers (Cannonlake/Icelake PCH) + - Low Power Mode registers (Tigerlake and beyond) + - PMC quirks as needed to enable SLPS0/S0ix 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..3e364941e8a7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -24,6 +24,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 <[email protected]>, " 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 <[email protected]> + * Copyright (C) 2014 Dell Inc <[email protected]> */ #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 <[email protected]>"); +MODULE_AUTHOR("Mario Limonciello <[email protected]>"); 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 <[email protected]>"); MODULE_AUTHOR("Gabriele Mazzotta <[email protected]>"); MODULE_AUTHOR("Pali Rohár <[email protected]>"); -MODULE_AUTHOR("Mario Limonciello <[email protected]>"); +MODULE_AUTHOR("Mario Limonciello <[email protected]>"); 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 <[email protected]>"); +MODULE_AUTHOR("Mario Limonciello <[email protected]>"); 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/enum-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c index 80f4b7785c6c..091e48c217ed 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c @@ -185,5 +185,8 @@ void exit_enum_attributes(void) sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj, &enumeration_attr_group); } + wmi_priv.enumeration_instances_count = 0; + kfree(wmi_priv.enumeration_data); + wmi_priv.enumeration_data = NULL; } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c index 75aedbb733be..8a49ba6e44f9 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c @@ -175,5 +175,8 @@ void exit_int_attributes(void) sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj, &integer_attr_group); } + wmi_priv.integer_instances_count = 0; + kfree(wmi_priv.integer_data); + wmi_priv.integer_data = NULL; } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c index 3abcd95477c0..834b3e82ad9f 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c @@ -183,5 +183,8 @@ void exit_po_attributes(void) sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj, &po_attr_group); } + wmi_priv.po_instances_count = 0; + kfree(wmi_priv.po_data); + wmi_priv.po_data = NULL; } 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/string-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c index ac75dce88a4c..552537852459 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c @@ -155,5 +155,8 @@ void exit_str_attributes(void) sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj, &str_attr_group); } + wmi_priv.str_instances_count = 0; + kfree(wmi_priv.str_data); + wmi_priv.str_data = NULL; } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c index cb81010ba1a2..c8d276d78e92 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -210,25 +210,17 @@ static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); */ static int create_attributes_level_sysfs_files(void) { - int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + int ret; - if (ret) { - pr_debug("could not create reset_bios file\n"); + ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + if (ret) return ret; - } ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); - if (ret) { - pr_debug("could not create changing_pending_reboot file\n"); - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); - } - return ret; -} + if (ret) + return ret; -static void release_reset_bios_data(void) -{ - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); + return 0; } static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, @@ -373,8 +365,6 @@ static void destroy_attribute_objs(struct kset *kset) */ static void release_attributes_data(void) { - release_reset_bios_data(); - mutex_lock(&wmi_priv.mutex); exit_enum_attributes(); exit_int_attributes(); @@ -386,11 +376,13 @@ static void release_attributes_data(void) wmi_priv.authentication_dir_kset = NULL; } if (wmi_priv.main_dir_kset) { + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); destroy_attribute_objs(wmi_priv.main_dir_kset); kset_unregister(wmi_priv.main_dir_kset); + wmi_priv.main_dir_kset = NULL; } mutex_unlock(&wmi_priv.mutex); - } /** @@ -407,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 @@ -417,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"); @@ -489,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); @@ -497,7 +513,6 @@ nextobj: err_attr_init: mutex_unlock(&wmi_priv.mutex); - release_attributes_data(); kfree(obj); return retval; } @@ -513,102 +528,91 @@ static int __init sysman_init(void) } ret = init_bios_attr_set_interface(); - if (ret || !wmi_priv.bios_attr_wdev) { - pr_debug("failed to initialize set interface\n"); - goto fail_set_interface; - } + if (ret) + return ret; ret = init_bios_attr_pass_interface(); - if (ret || !wmi_priv.password_attr_wdev) { - pr_debug("failed to initialize pass interface\n"); - goto fail_pass_interface; + if (ret) + goto err_exit_bios_attr_set_interface; + + if (!wmi_priv.bios_attr_wdev || !wmi_priv.password_attr_wdev) { + pr_debug("failed to find set or pass interface\n"); + ret = -ENODEV; + goto err_exit_bios_attr_pass_interface; } ret = class_register(&firmware_attributes_class); if (ret) - goto fail_class; + goto err_exit_bios_attr_pass_interface; wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), NULL, "%s", DRIVER_NAME); if (IS_ERR(wmi_priv.class_dev)) { ret = PTR_ERR(wmi_priv.class_dev); - goto fail_classdev; + goto err_unregister_class; } wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL, &wmi_priv.class_dev->kobj); if (!wmi_priv.main_dir_kset) { ret = -ENOMEM; - goto fail_main_kset; + goto err_destroy_classdev; } wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL, &wmi_priv.class_dev->kobj); if (!wmi_priv.authentication_dir_kset) { ret = -ENOMEM; - goto fail_authentication_kset; + goto err_release_attributes_data; } ret = create_attributes_level_sysfs_files(); if (ret) { pr_debug("could not create reset BIOS attribute\n"); - goto fail_reset_bios; + goto err_release_attributes_data; } ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); if (ret) { pr_debug("failed to populate enumeration type attributes\n"); - goto fail_create_group; + goto err_release_attributes_data; } ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); if (ret) { pr_debug("failed to populate integer type attributes\n"); - goto fail_create_group; + goto err_release_attributes_data; } ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); if (ret) { pr_debug("failed to populate string type attributes\n"); - goto fail_create_group; + goto err_release_attributes_data; } ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); if (ret) { pr_debug("failed to populate pass object type attributes\n"); - goto fail_create_group; + goto err_release_attributes_data; } return 0; -fail_create_group: +err_release_attributes_data: release_attributes_data(); -fail_reset_bios: - if (wmi_priv.authentication_dir_kset) { - kset_unregister(wmi_priv.authentication_dir_kset); - wmi_priv.authentication_dir_kset = NULL; - } - -fail_authentication_kset: - if (wmi_priv.main_dir_kset) { - kset_unregister(wmi_priv.main_dir_kset); - wmi_priv.main_dir_kset = NULL; - } - -fail_main_kset: +err_destroy_classdev: device_destroy(&firmware_attributes_class, MKDEV(0, 0)); -fail_classdev: +err_unregister_class: class_unregister(&firmware_attributes_class); -fail_class: +err_exit_bios_attr_pass_interface: exit_bios_attr_pass_interface(); -fail_pass_interface: +err_exit_bios_attr_set_interface: exit_bios_attr_set_interface(); -fail_set_interface: return ret; } @@ -624,7 +628,7 @@ static void __exit sysman_exit(void) module_init(sysman_init); module_exit(sysman_exit); -MODULE_AUTHOR("Mario Limonciello <[email protected]>"); +MODULE_AUTHOR("Mario Limonciello <[email protected]>"); MODULE_AUTHOR("Prasanth Ksr <[email protected]>"); MODULE_AUTHOR("Divya Bharathi <[email protected]>"); 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/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-hid.c b/drivers/platform/x86/intel-hid.c index 2f5b8d09143e..078648a9201b 100644 --- a/drivers/platform/x86/intel-hid.c +++ b/drivers/platform/x86/intel-hid.c @@ -90,6 +90,13 @@ static const struct dmi_system_id button_array_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x2 Detachable"), }, }, + { + .ident = "Lenovo ThinkPad X1 Tablet Gen 2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkPad X1 Tablet Gen 2"), + }, + }, { } }; @@ -476,11 +483,16 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) goto wakeup; /* - * Switch events will wake the device and report the new switch - * position to the input subsystem. + * Some devices send (duplicate) tablet-mode events when moved + * around even though the mode has not changed; and they do this + * even when suspended. + * Update the switch state in case it changed and then return + * without waking up to avoid spurious wakeups. */ - if (priv->switches && (event == 0xcc || event == 0xcd)) - goto wakeup; + if (event == 0xcc || event == 0xcd) { + report_tablet_mode_event(priv->switches, event); + return; + } /* Wake up on 5-button array events only. */ if (event == 0xc0 || !priv->array) @@ -494,9 +506,6 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) wakeup: pm_wakeup_hard_event(&device->dev); - if (report_tablet_mode_event(priv->switches, event)) - return; - return; } diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 8a8017f9ca91..888a764efad1 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -48,16 +48,21 @@ static const struct key_entry intel_vbtn_keymap[] = { }; static const struct key_entry intel_vbtn_switchmap[] = { - { KE_SW, 0xCA, { .sw = { SW_DOCK, 1 } } }, /* Docked */ - { KE_SW, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */ + /* + * SW_DOCK should only be reported for docking stations, but DSDTs using the + * intel-vbtn code, always seem to use this for 2-in-1s / convertibles and set + * SW_DOCK=1 when in laptop-mode (in tandem with setting SW_TABLET_MODE=0). + * This causes userspace to think the laptop is docked to a port-replicator + * and to disable suspend-on-lid-close, which is undesirable. + * Map the dock events to KEY_IGNORE to avoid this broken SW_DOCK reporting. + */ + { KE_IGNORE, 0xCA, { .sw = { SW_DOCK, 1 } } }, /* Docked */ + { KE_IGNORE, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */ { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ { 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_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index ee2f757515b0..b5888aeb4bcf 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -863,34 +863,45 @@ out_unlock: } DEFINE_SHOW_ATTRIBUTE(pmc_core_pll); -static ssize_t pmc_core_ltr_ignore_write(struct file *file, - const char __user *userbuf, - size_t count, loff_t *ppos) +static int pmc_core_send_ltr_ignore(u32 value) { struct pmc_dev *pmcdev = &pmc; const struct pmc_reg_map *map = pmcdev->map; - u32 val, buf_size, fd; - int err; - - buf_size = count < 64 ? count : 64; - - err = kstrtou32_from_user(userbuf, buf_size, 10, &val); - if (err) - return err; + u32 reg; + int err = 0; mutex_lock(&pmcdev->lock); - if (val > map->ltr_ignore_max) { + if (value > map->ltr_ignore_max) { err = -EINVAL; goto out_unlock; } - fd = pmc_core_reg_read(pmcdev, map->ltr_ignore_offset); - fd |= (1U << val); - pmc_core_reg_write(pmcdev, map->ltr_ignore_offset, fd); + reg = pmc_core_reg_read(pmcdev, map->ltr_ignore_offset); + reg |= BIT(value); + pmc_core_reg_write(pmcdev, map->ltr_ignore_offset, reg); out_unlock: mutex_unlock(&pmcdev->lock); + + return err; +} + +static ssize_t pmc_core_ltr_ignore_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + u32 buf_size, value; + int err; + + buf_size = min_t(u32, count, 64); + + err = kstrtou32_from_user(userbuf, buf_size, 10, &value); + if (err) + return err; + + err = pmc_core_send_ltr_ignore(value); + return err == 0 ? count : err; } @@ -1244,6 +1255,15 @@ static int pmc_core_probe(struct platform_device *pdev) pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(); dmi_check_system(pmc_core_dmi_table); + /* + * On TGL, due to a hardware limitation, the GBE LTR blocks PC10 when + * a cable is attached. Tell the PMC to ignore it. + */ + if (pmcdev->map == &tgl_reg_map) { + dev_dbg(&pdev->dev, "ignoring GBE LTR\n"); + pmc_core_send_ltr_ignore(3); + } + pmc_core_dbgfs_register(pmcdev); device_initialized = true; diff --git a/drivers/platform/x86/intel_pmt_class.c b/drivers/platform/x86/intel_pmt_class.c index c8939fba4509..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: /* @@ -173,7 +219,7 @@ static int intel_pmt_dev_register(struct intel_pmt_entry *entry, struct intel_pmt_namespace *ns, struct device *parent) { - struct resource res; + struct resource res = {0}; struct device *dev; int ret; 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_crashlog.c b/drivers/platform/x86/intel_pmt_crashlog.c index 97dd749c8290..92d315a16cfd 100644 --- a/drivers/platform/x86/intel_pmt_crashlog.c +++ b/drivers/platform/x86/intel_pmt_crashlog.c @@ -23,18 +23,17 @@ #define CRASH_TYPE_OOBMSM 1 /* Control Flags */ -#define CRASHLOG_FLAG_DISABLE BIT(27) +#define CRASHLOG_FLAG_DISABLE BIT(28) /* - * Bits 28 and 29 control the state of bit 31. + * Bits 29 and 30 control the state of bit 31. * - * Bit 28 will clear bit 31, if set, allowing a new crashlog to be captured. - * Bit 29 will immediately trigger a crashlog to be generated, setting bit 31. - * Bit 30 is read-only and reserved as 0. + * Bit 29 will clear bit 31, if set, allowing a new crashlog to be captured. + * Bit 30 will immediately trigger a crashlog to be generated, setting bit 31. * Bit 31 is the read-only status with a 1 indicating log is complete. */ -#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(28) -#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(29) +#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(29) +#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(30) #define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31) #define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28) 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/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index b881044b31b0..968e0e6b3fa4 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/[email protected]/ + */ + 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; } @@ -4081,13 +4104,19 @@ static bool hotkey_notify_6xxx(const u32 hkey, case TP_HKEY_EV_KEY_NUMLOCK: case TP_HKEY_EV_KEY_FN: - case TP_HKEY_EV_KEY_FN_ESC: /* key press events, we just ignore them as long as the EC * is still reporting them in the normal keyboard stream */ *send_acpi_ev = false; *ignore_acpi_ev = true; return true; + case TP_HKEY_EV_KEY_FN_ESC: + /* 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; + return true; + case TP_HKEY_EV_TABLET_CHANGED: tpacpi_input_send_tabletsw(); hotkey_tablet_mode_notify_change(); @@ -9845,6 +9874,11 @@ static struct ibm_struct lcdshadow_driver_data = { * Thinkpad sensor interfaces */ +#define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ +#define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ +#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ +#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ + #define DYTC_CMD_GET 2 /* To get current IC function and mode */ #define DYTC_GET_LAPMODE_BIT 17 /* Set when in lapmode */ @@ -9855,6 +9889,7 @@ static bool has_palmsensor; static bool has_lapsensor; static bool palm_state; static bool lap_state; +static int dytc_version; static int dytc_command(int command, int *output) { @@ -9869,6 +9904,33 @@ static int dytc_command(int command, int *output) return 0; } +static int dytc_get_version(void) +{ + int err, output; + + /* Check if we've been called before - and just return cached value */ + if (dytc_version) + return dytc_version; + + /* Otherwise query DYTC and extract version information */ + err = dytc_command(DYTC_CMD_QUERY, &output); + /* + * If support isn't available (ENODEV) then don't return an error + * and don't create the sysfs group + */ + if (err == -ENODEV) + return 0; + /* For all other errors we can flag the failure */ + if (err) + return err; + + /* Check DYTC is enabled and supports mode setting */ + if (output & BIT(DYTC_QUERY_ENABLE_BIT)) + dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; + + return 0; +} + static int lapsensor_get(bool *present, bool *state) { int output, err; @@ -9974,7 +10036,18 @@ static int tpacpi_proxsensor_init(struct ibm_init_struct *iibm) if (err) return err; } - if (has_lapsensor) { + + /* Check if we know the DYTC version, if we don't then get it */ + if (!dytc_version) { + err = dytc_get_version(); + if (err) + return err; + } + /* + * Platforms before DYTC version 5 claim to have a lap sensor, but it doesn't work, so we + * ignore them + */ + if (has_lapsensor && (dytc_version >= 5)) { err = sysfs_create_file(&tpacpi_pdev->dev.kobj, &dev_attr_dytc_lapmode.attr); if (err) return err; @@ -9999,14 +10072,10 @@ static struct ibm_struct proxsensor_driver_data = { * DYTC Platform Profile interface */ -#define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ #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_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ -#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ -#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ - #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ @@ -10021,6 +10090,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 | \ @@ -10035,6 +10108,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) { @@ -10043,6 +10117,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: @@ -10120,7 +10195,6 @@ static int dytc_cql_command(int command, int *output) if (err) return err; } - return cmd_err; } @@ -10142,8 +10216,13 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, return err; if (profile == PLATFORM_PROFILE_BALANCED) { - /* To get back to balanced mode we just issue a reset command */ - err = dytc_command(DYTC_CMD_RESET, &output); + /* + * To get back to balanced mode we need to issue a reset command. + * Note we still need to disable CQL mode before hand and re-enable + * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays + * stuck at 0 for aprox. 30 minutes. + */ + err = dytc_cql_command(DYTC_CMD_RESET, &output); if (err) goto unlock; } else { @@ -10172,7 +10251,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; @@ -10211,28 +10293,38 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) if (err) return err; + /* Check if we know the DYTC version, if we don't then get it */ + if (!dytc_version) { + err = dytc_get_version(); + if (err) + return err; + } /* Check DYTC is enabled and supports mode setting */ - if (output & BIT(DYTC_QUERY_ENABLE_BIT)) { - /* Only DYTC v5.0 and later has this feature. */ - int dytc_version; - - dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; - if (dytc_version >= 5) { - dbg_printk(TPACPI_DBG_INIT, - "DYTC version %d: thermal mode available\n", dytc_version); - /* Create platform_profile structure and register */ - err = platform_profile_register(&dytc_profile); - /* - * If for some reason platform_profiles aren't enabled - * don't quit terminally. - */ - if (err) - return 0; - - dytc_profile_available = true; - /* Ensure initial values are correct */ - dytc_profile_refresh(); + 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); + /* + * If for some reason platform_profiles aren't enabled + * don't quit terminally. + */ + if (err) + return 0; + + dytc_profile_available = true; + /* Ensure initial values are correct */ + dytc_profile_refresh(); } return 0; } @@ -10423,6 +10515,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, +}; + /**************************************************************************** **************************************************************************** * @@ -10927,6 +11124,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..45203e333f57 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -1355,7 +1355,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 <[email protected]> + */ + +#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; |