You've already forked linux-rockchip
mirror of
https://github.com/armbian/linux-rockchip.git
synced 2026-01-06 11:08:10 -08:00
Merge tag 'platform-drivers-x86-v5.12-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86
Pull x86 platform driver updates from Hans de Goede:
"Highlights:
- Microsoft Surface devices System Aggregator Module support
- SW_TABLET_MODE reporting improvements
- thinkpad_acpi keyboard language setting support
- platform / DPTF profile settings support:
- Base / userspace API parts merged from Rafael's acpi-platform
branch
- thinkpad_acpi and ideapad-laptop support through pdx86
- Remove support for some obsolete Intel MID platforms through
merging of the shared intel-mid-removal branch
- Big cleanup of the ideapad-laptop driver
- Misc other fixes / new hw support / quirks"
* tag 'platform-drivers-x86-v5.12-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (99 commits)
platform/x86: intel_scu_ipc: Increase virtual timeout from 3 to 5 seconds
platform/surface: aggregator: Fix access of unaligned value
tools/power/x86/intel-speed-select: Update version to 1.8
tools/power/x86/intel-speed-select: Add new command to get/set TRL
tools/power/x86/intel-speed-select: Add new command turbo-mode
Platform: OLPC: Constify static struct regulator_ops
platform/surface: Add Surface Hot-Plug driver
platform/x86: intel_scu_wdt: Drop mistakenly added const
platform/x86: Kconfig: add missing selects for ideapad-laptop
platform/x86: acer-wmi: Don't use ACPI_EXCEPTION()
platform/x86: thinkpad_acpi: Replace ifdef CONFIG_ACPI_PLATFORM_PROFILE with depends on
platform/x86: thinkpad_acpi: Fix 'warning: no previous prototype for' warnings
platform/x86: msi-wmi: Fix variable 'status' set but not used compiler warning
platform/surface: surface3-wmi: Fix variable 'status' set but not used compiler warning
platform/x86: Move all dell drivers to their own subdirectory
Documentation/ABI: sysfs-platform-ideapad-laptop: conservation_mode attribute
Documentation/ABI: sysfs-platform-ideapad-laptop: update device attribute paths
platform/x86: ideapad-laptop: add "always on USB charging" control support
platform/x86: ideapad-laptop: add keyboard backlight control support
platform/x86: ideapad-laptop: send notification about touchpad state change to sysfs
...
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
What: /sys/devices/platform/ideapad/camera_power
|
||||
What: /sys/bus/platform/devices/VPC2004:*/camera_power
|
||||
Date: Dec 2010
|
||||
KernelVersion: 2.6.37
|
||||
Contact: "Ike Panhc <ike.pan@canonical.com>"
|
||||
Description:
|
||||
Control the power of camera module. 1 means on, 0 means off.
|
||||
|
||||
What: /sys/devices/platform/ideapad/fan_mode
|
||||
What: /sys/bus/platform/devices/VPC2004:*/fan_mode
|
||||
Date: June 2012
|
||||
KernelVersion: 3.6
|
||||
Contact: "Maxim Mikityanskiy <maxtram95@gmail.com>"
|
||||
@@ -18,7 +18,7 @@ Description:
|
||||
* 2 -> Dust Cleaning
|
||||
* 4 -> Efficient Thermal Dissipation Mode
|
||||
|
||||
What: /sys/devices/platform/ideapad/touchpad
|
||||
What: /sys/bus/platform/devices/VPC2004:*/touchpad
|
||||
Date: May 2017
|
||||
KernelVersion: 4.13
|
||||
Contact: "Ritesh Raj Sarraf <rrs@debian.org>"
|
||||
@@ -27,7 +27,16 @@ Description:
|
||||
* 1 -> Switched On
|
||||
* 0 -> Switched Off
|
||||
|
||||
What: /sys/bus/pci/devices/<bdf>/<device>/VPC2004:00/fn_lock
|
||||
What: /sys/bus/platform/devices/VPC2004:*/conservation_mode
|
||||
Date: Aug 2017
|
||||
KernelVersion: 4.14
|
||||
Contact: platform-driver-x86@vger.kernel.org
|
||||
Description:
|
||||
Controls whether the conservation mode is enabled or not.
|
||||
This feature limits the maximum battery charge percentage to
|
||||
around 50-60% in order to prolong the lifetime of the battery.
|
||||
|
||||
What: /sys/bus/platform/devices/VPC2004:*/fn_lock
|
||||
Date: May 2018
|
||||
KernelVersion: 4.18
|
||||
Contact: "Oleg Keri <ezhi99@gmail.com>"
|
||||
@@ -41,3 +50,12 @@ Description:
|
||||
|
||||
# echo "0" > \
|
||||
/sys/bus/pci/devices/0000:00:1f.0/PNP0C09:00/VPC2004:00/fn_lock
|
||||
|
||||
What: /sys/bus/platform/devices/VPC2004:*/usb_charging
|
||||
Date: Feb 2021
|
||||
KernelVersion: 5.12
|
||||
Contact: platform-driver-x86@vger.kernel.org
|
||||
Description:
|
||||
Controls whether the "always on USB charging" feature is
|
||||
enabled or not. This feature enables charging USB devices
|
||||
even if the computer is not turned on.
|
||||
|
||||
@@ -51,6 +51,7 @@ detailed description):
|
||||
- UWB enable and disable
|
||||
- LCD Shadow (PrivacyGuard) enable and disable
|
||||
- Lap mode sensor
|
||||
- Setting keyboard language
|
||||
|
||||
A compatibility table by model and feature is maintained on the web
|
||||
site, http://ibm-acpi.sf.net/. I appreciate any success or failure
|
||||
@@ -1466,6 +1467,30 @@ Sysfs notes
|
||||
rfkill controller switch "tpacpi_uwb_sw": refer to
|
||||
Documentation/driver-api/rfkill.rst for details.
|
||||
|
||||
|
||||
Setting keyboard language
|
||||
-------------------------
|
||||
|
||||
sysfs: keyboard_lang
|
||||
|
||||
This feature is used to set keyboard language to ECFW using ASL interface.
|
||||
Fewer thinkpads models like T580 , T590 , T15 Gen 1 etc.. has "=", "(',
|
||||
")" numeric keys, which are not displaying correctly, when keyboard language
|
||||
is other than "english". This is because the default keyboard language in ECFW
|
||||
is set as "english". Hence using this sysfs, user can set the correct keyboard
|
||||
language to ECFW and then these key's will work correctly.
|
||||
|
||||
Example of command to set keyboard language is mentioned below::
|
||||
|
||||
echo jp > /sys/devices/platform/thinkpad_acpi/keyboard_lang
|
||||
|
||||
Text corresponding to keyboard layout to be set in sysfs are: be(Belgian),
|
||||
cz(Czech), da(Danish), de(German), en(English), es(Spain), et(Estonian),
|
||||
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)
|
||||
|
||||
|
||||
Adaptive keyboard
|
||||
-----------------
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ available subsections can be seen below.
|
||||
rfkill
|
||||
serial/index
|
||||
sm501
|
||||
surface_aggregator/index
|
||||
switchtec
|
||||
sync_file
|
||||
vfio-mediated-device
|
||||
|
||||
38
Documentation/driver-api/surface_aggregator/client-api.rst
Normal file
38
Documentation/driver-api/surface_aggregator/client-api.rst
Normal file
@@ -0,0 +1,38 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
===============================
|
||||
Client Driver API Documentation
|
||||
===============================
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
|
||||
|
||||
Serial Hub Communication
|
||||
========================
|
||||
|
||||
.. kernel-doc:: include/linux/surface_aggregator/serial_hub.h
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.c
|
||||
:export:
|
||||
|
||||
|
||||
Controller and Core Interface
|
||||
=============================
|
||||
|
||||
.. kernel-doc:: include/linux/surface_aggregator/controller.h
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/controller.c
|
||||
:export:
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/core.c
|
||||
:export:
|
||||
|
||||
|
||||
Client Bus and Client Device API
|
||||
================================
|
||||
|
||||
.. kernel-doc:: include/linux/surface_aggregator/device.h
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/bus.c
|
||||
:export:
|
||||
393
Documentation/driver-api/surface_aggregator/client.rst
Normal file
393
Documentation/driver-api/surface_aggregator/client.rst
Normal file
@@ -0,0 +1,393 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
.. |ssam_controller| replace:: :c:type:`struct ssam_controller <ssam_controller>`
|
||||
.. |ssam_device| replace:: :c:type:`struct ssam_device <ssam_device>`
|
||||
.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver <ssam_device_driver>`
|
||||
.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind`
|
||||
.. |ssam_client_link| replace:: :c:func:`ssam_client_link`
|
||||
.. |ssam_get_controller| replace:: :c:func:`ssam_get_controller`
|
||||
.. |ssam_controller_get| replace:: :c:func:`ssam_controller_get`
|
||||
.. |ssam_controller_put| replace:: :c:func:`ssam_controller_put`
|
||||
.. |ssam_device_alloc| replace:: :c:func:`ssam_device_alloc`
|
||||
.. |ssam_device_add| replace:: :c:func:`ssam_device_add`
|
||||
.. |ssam_device_remove| replace:: :c:func:`ssam_device_remove`
|
||||
.. |ssam_device_driver_register| replace:: :c:func:`ssam_device_driver_register`
|
||||
.. |ssam_device_driver_unregister| replace:: :c:func:`ssam_device_driver_unregister`
|
||||
.. |module_ssam_device_driver| replace:: :c:func:`module_ssam_device_driver`
|
||||
.. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE`
|
||||
.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register`
|
||||
.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister`
|
||||
.. |ssam_request_sync| replace:: :c:func:`ssam_request_sync`
|
||||
.. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask <ssam_event_mask>`
|
||||
|
||||
|
||||
======================
|
||||
Writing Client Drivers
|
||||
======================
|
||||
|
||||
For the API documentation, refer to:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
client-api
|
||||
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
Client drivers can be set up in two main ways, depending on how the
|
||||
corresponding device is made available to the system. We specifically
|
||||
differentiate between devices that are presented to the system via one of
|
||||
the conventional ways, e.g. as platform devices via ACPI, and devices that
|
||||
are non-discoverable and instead need to be explicitly provided by some
|
||||
other mechanism, as discussed further below.
|
||||
|
||||
|
||||
Non-SSAM Client Drivers
|
||||
=======================
|
||||
|
||||
All communication with the SAM EC is handled via the |ssam_controller|
|
||||
representing that EC to the kernel. Drivers targeting a non-SSAM device (and
|
||||
thus not being a |ssam_device_driver|) need to explicitly establish a
|
||||
connection/relation to that controller. This can be done via the
|
||||
|ssam_client_bind| function. Said function returns a reference to the SSAM
|
||||
controller, but, more importantly, also establishes a device link between
|
||||
client device and controller (this can also be done separate via
|
||||
|ssam_client_link|). It is important to do this, as it, first, guarantees
|
||||
that the returned controller is valid for use in the client driver for as
|
||||
long as this driver is bound to its device, i.e. that the driver gets
|
||||
unbound before the controller ever becomes invalid, and, second, as it
|
||||
ensures correct suspend/resume ordering. This setup should be done in the
|
||||
driver's probe function, and may be used to defer probing in case the SSAM
|
||||
subsystem is not ready yet, for example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static int client_driver_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ssam_controller *ctrl;
|
||||
|
||||
ctrl = ssam_client_bind(&pdev->dev);
|
||||
if (IS_ERR(ctrl))
|
||||
return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
|
||||
|
||||
// ...
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
The controller may be separately obtained via |ssam_get_controller| and its
|
||||
lifetime be guaranteed via |ssam_controller_get| and |ssam_controller_put|.
|
||||
Note that none of these functions, however, guarantee that the controller
|
||||
will not be shut down or suspended. These functions essentially only operate
|
||||
on the reference, i.e. only guarantee a bare minimum of accessibility
|
||||
without any guarantees at all on practical operability.
|
||||
|
||||
|
||||
Adding SSAM Devices
|
||||
===================
|
||||
|
||||
If a device does not already exist/is not already provided via conventional
|
||||
means, it should be provided as |ssam_device| via the SSAM client device
|
||||
hub. New devices can be added to this hub by entering their UID into the
|
||||
corresponding registry. SSAM devices can also be manually allocated via
|
||||
|ssam_device_alloc|, subsequently to which they have to be added via
|
||||
|ssam_device_add| and eventually removed via |ssam_device_remove|. By
|
||||
default, the parent of the device is set to the controller device provided
|
||||
for allocation, however this may be changed before the device is added. Note
|
||||
that, when changing the parent device, care must be taken to ensure that the
|
||||
controller lifetime and suspend/resume ordering guarantees, in the default
|
||||
setup provided through the parent-child relation, are preserved. If
|
||||
necessary, by use of |ssam_client_link| as is done for non-SSAM client
|
||||
drivers and described in more detail above.
|
||||
|
||||
A client device must always be removed by the party which added the
|
||||
respective device before the controller shuts down. Such removal can be
|
||||
guaranteed by linking the driver providing the SSAM device to the controller
|
||||
via |ssam_client_link|, causing it to unbind before the controller driver
|
||||
unbinds. Client devices registered with the controller as parent are
|
||||
automatically removed when the controller shuts down, but this should not be
|
||||
relied upon, especially as this does not extend to client devices with a
|
||||
different parent.
|
||||
|
||||
|
||||
SSAM Client Drivers
|
||||
===================
|
||||
|
||||
SSAM client device drivers are, in essence, no different than other device
|
||||
driver types. They are represented via |ssam_device_driver| and bind to a
|
||||
|ssam_device| via its UID (:c:type:`struct ssam_device.uid <ssam_device>`)
|
||||
member and the match table
|
||||
(:c:type:`struct ssam_device_driver.match_table <ssam_device_driver>`),
|
||||
which should be set when declaring the driver struct instance. Refer to the
|
||||
|SSAM_DEVICE| macro documentation for more details on how to define members
|
||||
of the driver's match table.
|
||||
|
||||
The UID for SSAM client devices consists of a ``domain``, a ``category``,
|
||||
a ``target``, an ``instance``, and a ``function``. The ``domain`` is used
|
||||
differentiate between physical SAM devices
|
||||
(:c:type:`SSAM_DOMAIN_SERIALHUB <ssam_device_domain>`), i.e. devices that can
|
||||
be accessed via the Surface Serial Hub, and virtual ones
|
||||
(:c:type:`SSAM_DOMAIN_VIRTUAL <ssam_device_domain>`), such as client-device
|
||||
hubs, that have no real representation on the SAM EC and are solely used on
|
||||
the kernel/driver-side. For physical devices, ``category`` represents the
|
||||
target category, ``target`` the target ID, and ``instance`` the instance ID
|
||||
used to access the physical SAM device. In addition, ``function`` references
|
||||
a specific device functionality, but has no meaning to the SAM EC. The
|
||||
(default) name of a client device is generated based on its UID.
|
||||
|
||||
A driver instance can be registered via |ssam_device_driver_register| and
|
||||
unregistered via |ssam_device_driver_unregister|. For convenience, the
|
||||
|module_ssam_device_driver| macro may be used to define module init- and
|
||||
exit-functions registering the driver.
|
||||
|
||||
The controller associated with a SSAM client device can be found in its
|
||||
:c:type:`struct ssam_device.ctrl <ssam_device>` member. This reference is
|
||||
guaranteed to be valid for at least as long as the client driver is bound,
|
||||
but should also be valid for as long as the client device exists. Note,
|
||||
however, that access outside of the bound client driver must ensure that the
|
||||
controller device is not suspended while making any requests or
|
||||
(un-)registering event notifiers (and thus should generally be avoided). This
|
||||
is guaranteed when the controller is accessed from inside the bound client
|
||||
driver.
|
||||
|
||||
|
||||
Making Synchronous Requests
|
||||
===========================
|
||||
|
||||
Synchronous requests are (currently) the main form of host-initiated
|
||||
communication with the EC. There are a couple of ways to define and execute
|
||||
such requests, however, most of them boil down to something similar as shown
|
||||
in the example below. This example defines a write-read request, meaning
|
||||
that the caller provides an argument to the SAM EC and receives a response.
|
||||
The caller needs to know the (maximum) length of the response payload and
|
||||
provide a buffer for it.
|
||||
|
||||
Care must be taken to ensure that any command payload data passed to the SAM
|
||||
EC is provided in little-endian format and, similarly, any response payload
|
||||
data received from it is converted from little-endian to host endianness.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int perform_request(struct ssam_controller *ctrl, u32 arg, u32 *ret)
|
||||
{
|
||||
struct ssam_request rqst;
|
||||
struct ssam_response resp;
|
||||
int status;
|
||||
|
||||
/* Convert request argument to little-endian. */
|
||||
__le32 arg_le = cpu_to_le32(arg);
|
||||
__le32 ret_le = cpu_to_le32(0);
|
||||
|
||||
/*
|
||||
* Initialize request specification. Replace this with your values.
|
||||
* The rqst.payload field may be NULL if rqst.length is zero,
|
||||
* indicating that the request does not have any argument.
|
||||
*
|
||||
* Note: The request parameters used here are not valid, i.e.
|
||||
* they do not correspond to an actual SAM/EC request.
|
||||
*/
|
||||
rqst.target_category = SSAM_SSH_TC_SAM;
|
||||
rqst.target_id = 0x01;
|
||||
rqst.command_id = 0x02;
|
||||
rqst.instance_id = 0x03;
|
||||
rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
||||
rqst.length = sizeof(arg_le);
|
||||
rqst.payload = (u8 *)&arg_le;
|
||||
|
||||
/* Initialize request response. */
|
||||
resp.capacity = sizeof(ret_le);
|
||||
resp.length = 0;
|
||||
resp.pointer = (u8 *)&ret_le;
|
||||
|
||||
/*
|
||||
* Perform actual request. The response pointer may be null in case
|
||||
* the request does not have any response. This must be consistent
|
||||
* with the SSAM_REQUEST_HAS_RESPONSE flag set in the specification
|
||||
* above.
|
||||
*/
|
||||
status = ssam_request_sync(ctrl, &rqst, &resp);
|
||||
|
||||
/*
|
||||
* Alternatively use
|
||||
*
|
||||
* ssam_request_sync_onstack(ctrl, &rqst, &resp, sizeof(arg_le));
|
||||
*
|
||||
* to perform the request, allocating the message buffer directly
|
||||
* on the stack as opposed to allocation via kzalloc().
|
||||
*/
|
||||
|
||||
/*
|
||||
* Convert request response back to native format. Note that in the
|
||||
* error case, this value is not touched by the SSAM core, i.e.
|
||||
* 'ret_le' will be zero as specified in its initialization.
|
||||
*/
|
||||
*ret = le32_to_cpu(ret_le);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Note that |ssam_request_sync| in its essence is a wrapper over lower-level
|
||||
request primitives, which may also be used to perform requests. Refer to its
|
||||
implementation and documentation for more details.
|
||||
|
||||
An arguably more user-friendly way of defining such functions is by using
|
||||
one of the generator macros, for example via:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, {
|
||||
.target_category = SSAM_SSH_TC_TMP,
|
||||
.target_id = 0x01,
|
||||
.command_id = 0x03,
|
||||
.instance_id = 0x00,
|
||||
});
|
||||
|
||||
This example defines a function
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
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``
|
||||
pointer. Note that the generated function allocates the message buffer on
|
||||
the stack. Thus, if the argument provided via the request is large, these
|
||||
kinds of macros should be avoided. Also note that, in contrast to the
|
||||
previous non-macro example, this function does not do any endianness
|
||||
conversion, which has to be handled by the caller. Apart from those
|
||||
differences the function generated by the macro is similar to the one
|
||||
provided in the non-macro example above.
|
||||
|
||||
The full list of such function-generating macros is
|
||||
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_N` for requests without return value and
|
||||
without argument.
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_R` for requests with return value but no
|
||||
argument.
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_W` for requests without return value but
|
||||
with argument.
|
||||
|
||||
Refer to their respective documentation for more details. For each one of
|
||||
these macros, a special variant is provided, which targets request types
|
||||
applicable to multiple instances of the same device type:
|
||||
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_N`
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_R`
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_W`
|
||||
|
||||
The difference of those macros to the previously mentioned versions is, that
|
||||
the device target and instance IDs are not fixed for the generated function,
|
||||
but instead have to be provided by the caller of said function.
|
||||
|
||||
Additionally, variants for direct use with client devices, i.e.
|
||||
|ssam_device|, are also provided. These can, for example, be used as
|
||||
follows:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
|
||||
.target_category = SSAM_SSH_TC_BAT,
|
||||
.command_id = 0x01,
|
||||
});
|
||||
|
||||
This invocation of the macro defines a function
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
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:
|
||||
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_N`
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_R`
|
||||
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_W`
|
||||
|
||||
|
||||
Handling Events
|
||||
===============
|
||||
|
||||
To receive events from the SAM EC, an event notifier must be registered for
|
||||
the desired event via |ssam_notifier_register|. The notifier must be
|
||||
unregistered via |ssam_notifier_unregister| once it is not required any
|
||||
more.
|
||||
|
||||
Event notifiers are registered by providing (at minimum) a callback to call
|
||||
in case an event has been received, the registry specifying how the event
|
||||
should be enabled, an event ID specifying for which target category and,
|
||||
optionally and depending on the registry used, for which instance ID events
|
||||
should be enabled, and finally, flags describing how the EC will send these
|
||||
events. If the specific registry does not enable events by instance ID, the
|
||||
instance ID must be set to zero. Additionally, a priority for the respective
|
||||
notifier may be specified, which determines its order in relation to any
|
||||
other notifier registered for the same target category.
|
||||
|
||||
By default, event notifiers will receive all events for the specific target
|
||||
category, regardless of the instance ID specified when registering the
|
||||
notifier. The core may be instructed to only call a notifier if the target
|
||||
ID or instance ID (or both) of the event match the ones implied by the
|
||||
notifier IDs (in case of target ID, the target ID of the registry), by
|
||||
providing an event mask (see |ssam_event_mask|).
|
||||
|
||||
In general, the target ID of the registry is also the target ID of the
|
||||
enabled event (with the notable exception being keyboard input events on the
|
||||
Surface Laptop 1 and 2, which are enabled via a registry with target ID 1,
|
||||
but provide events with target ID 2).
|
||||
|
||||
A full example for registering an event notifier and handling received
|
||||
events is provided below:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
u32 notifier_callback(struct ssam_event_notifier *nf,
|
||||
const struct ssam_event *event)
|
||||
{
|
||||
int status = ...
|
||||
|
||||
/* Handle the event here ... */
|
||||
|
||||
/* Convert return value and indicate that we handled the event. */
|
||||
return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
|
||||
}
|
||||
|
||||
int setup_notifier(struct ssam_device *sdev,
|
||||
struct ssam_event_notifier *nf)
|
||||
{
|
||||
/* Set priority wrt. other handlers of same target category. */
|
||||
nf->base.priority = 1;
|
||||
|
||||
/* Set event/notifier callback. */
|
||||
nf->base.fn = notifier_callback;
|
||||
|
||||
/* Specify event registry, i.e. how events get enabled/disabled. */
|
||||
nf->event.reg = SSAM_EVENT_REGISTRY_KIP;
|
||||
|
||||
/* Specify which event to enable/disable */
|
||||
nf->event.id.target_category = sdev->uid.category;
|
||||
nf->event.id.instance = sdev->uid.instance;
|
||||
|
||||
/*
|
||||
* Specify for which events the notifier callback gets executed.
|
||||
* This essentially tells the core if it can skip notifiers that
|
||||
* don't have target or instance IDs matching those of the event.
|
||||
*/
|
||||
nf->event.mask = SSAM_EVENT_MASK_STRICT;
|
||||
|
||||
/* Specify event flags. */
|
||||
nf->event.flags = SSAM_EVENT_SEQUENCED;
|
||||
|
||||
return ssam_notifier_register(sdev->ctrl, nf);
|
||||
}
|
||||
|
||||
Multiple event notifiers can be registered for the same event. The event
|
||||
handler core takes care of enabling and disabling events when notifiers are
|
||||
registered and unregistered, by keeping track of how many notifiers for a
|
||||
specific event (combination of registry, event target category, and event
|
||||
instance ID) are currently registered. This means that a specific event will
|
||||
be enabled when the first notifier for it is being registered and disabled
|
||||
when the last notifier for it is being unregistered. Note that the event
|
||||
flags are therefore only used on the first registered notifier, however, one
|
||||
should take care that notifiers for a specific event are always registered
|
||||
with the same flag and it is considered a bug to do otherwise.
|
||||
87
Documentation/driver-api/surface_aggregator/clients/cdev.rst
Normal file
87
Documentation/driver-api/surface_aggregator/clients/cdev.rst
Normal file
@@ -0,0 +1,87 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
.. |u8| replace:: :c:type:`u8 <u8>`
|
||||
.. |u16| replace:: :c:type:`u16 <u16>`
|
||||
.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request <ssam_cdev_request>`
|
||||
.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags <ssam_cdev_request_flags>`
|
||||
|
||||
==============================
|
||||
User-Space EC Interface (cdev)
|
||||
==============================
|
||||
|
||||
The ``surface_aggregator_cdev`` module provides a misc-device for the SSAM
|
||||
controller to allow for a (more or less) direct connection from user-space to
|
||||
the SAM EC. It is intended to be used for development and debugging, and
|
||||
therefore should not be used or relied upon in any other way. Note that this
|
||||
module is not loaded automatically, but instead must be loaded manually.
|
||||
|
||||
The provided interface is accessible through the ``/dev/surface/aggregator``
|
||||
device-file. All functionality of this interface is provided via IOCTLs.
|
||||
These IOCTLs and their respective input/output parameter structs are defined in
|
||||
``include/uapi/linux/surface_aggregator/cdev.h``.
|
||||
|
||||
A small python library and scripts for accessing this interface can be found
|
||||
at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam.
|
||||
|
||||
|
||||
Controller IOCTLs
|
||||
=================
|
||||
|
||||
The following IOCTLs are provided:
|
||||
|
||||
.. flat-table:: Controller IOCTLs
|
||||
:widths: 1 1 1 1 4
|
||||
:header-rows: 1
|
||||
|
||||
* - Type
|
||||
- Number
|
||||
- Direction
|
||||
- Name
|
||||
- Description
|
||||
|
||||
* - ``0xA5``
|
||||
- ``1``
|
||||
- ``WR``
|
||||
- ``REQUEST``
|
||||
- Perform synchronous SAM request.
|
||||
|
||||
|
||||
``REQUEST``
|
||||
-----------
|
||||
|
||||
Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``.
|
||||
|
||||
Executes a synchronous SAM request. The request specification is passed in
|
||||
as argument of type |ssam_cdev_request|, which is then written to/modified
|
||||
by the IOCTL to return status and result of the request.
|
||||
|
||||
Request payload data must be allocated separately and is passed in via the
|
||||
``payload.data`` and ``payload.length`` members. If a response is required,
|
||||
the response buffer must be allocated by the caller and passed in via the
|
||||
``response.data`` member. The ``response.length`` member must be set to the
|
||||
capacity of this buffer, or if no response is required, zero. Upon
|
||||
completion of the request, the call will write the response to the response
|
||||
buffer (if its capacity allows it) and overwrite the length field with the
|
||||
actual size of the response, in bytes.
|
||||
|
||||
Additionally, if the request has a response, this must be indicated via the
|
||||
request flags, as is done with in-kernel requests. Request flags can be set
|
||||
via the ``flags`` member and the values correspond to the values found in
|
||||
|ssam_cdev_request_flags|.
|
||||
|
||||
Finally, the status of the request itself is returned in the ``status``
|
||||
member (a negative errno value indicating failure). Note that failure
|
||||
indication of the IOCTL is separated from failure indication of the request:
|
||||
The IOCTL returns a negative status code if anything failed during setup of
|
||||
the request (``-EFAULT``) or if the provided argument or any of its fields
|
||||
are invalid (``-EINVAL``). In this case, the status value of the request
|
||||
argument may be set, providing more detail on what went wrong (e.g.
|
||||
``-ENOMEM`` for out-of-memory), but this value may also be zero. The IOCTL
|
||||
will return with a zero status code in case the request has been set up,
|
||||
submitted, and completed (i.e. handed back to user-space) successfully from
|
||||
inside the IOCTL, but the request ``status`` member may still be negative in
|
||||
case the actual execution of the request failed after it has been submitted.
|
||||
|
||||
A full definition of the argument struct is provided below:
|
||||
|
||||
.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h
|
||||
@@ -0,0 +1,21 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
===========================
|
||||
Client Driver Documentation
|
||||
===========================
|
||||
|
||||
This is the documentation for client drivers themselves. Refer to
|
||||
:doc:`../client` for documentation on how to write client drivers.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
cdev
|
||||
san
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
Indices
|
||||
=======
|
||||
|
||||
* :ref:`genindex`
|
||||
44
Documentation/driver-api/surface_aggregator/clients/san.rst
Normal file
44
Documentation/driver-api/surface_aggregator/clients/san.rst
Normal file
@@ -0,0 +1,44 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
.. |san_client_link| replace:: :c:func:`san_client_link`
|
||||
.. |san_dgpu_notifier_register| replace:: :c:func:`san_dgpu_notifier_register`
|
||||
.. |san_dgpu_notifier_unregister| replace:: :c:func:`san_dgpu_notifier_unregister`
|
||||
|
||||
===================
|
||||
Surface ACPI Notify
|
||||
===================
|
||||
|
||||
The Surface ACPI Notify (SAN) device provides the bridge between ACPI and
|
||||
SAM controller. Specifically, ACPI code can execute requests and handle
|
||||
battery and thermal events via this interface. In addition to this, events
|
||||
relating to the discrete GPU (dGPU) of the Surface Book 2 can be sent from
|
||||
ACPI code (note: the Surface Book 3 uses a different method for this). The
|
||||
only currently known event sent via this interface is a dGPU power-on
|
||||
notification. While this driver handles the former part internally, it only
|
||||
relays the dGPU events to any other driver interested via its public API and
|
||||
does not handle them.
|
||||
|
||||
The public interface of this driver is split into two parts: Client
|
||||
registration and notifier-block registration.
|
||||
|
||||
A client to the SAN interface can be linked as consumer to the SAN device
|
||||
via |san_client_link|. This can be used to ensure that the a client
|
||||
receiving dGPU events does not miss any events due to the SAN interface not
|
||||
being set up as this forces the client driver to unbind once the SAN driver
|
||||
is unbound.
|
||||
|
||||
Notifier-blocks can be registered by any device for as long as the module is
|
||||
loaded, regardless of being linked as client or not. Registration is done
|
||||
with |san_dgpu_notifier_register|. If the notifier is not needed any more, it
|
||||
should be unregistered via |san_dgpu_notifier_unregister|.
|
||||
|
||||
Consult the API documentation below for more details.
|
||||
|
||||
|
||||
API Documentation
|
||||
=================
|
||||
|
||||
.. kernel-doc:: include/linux/surface_acpi_notify.h
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/surface_acpi_notify.c
|
||||
:export:
|
||||
21
Documentation/driver-api/surface_aggregator/index.rst
Normal file
21
Documentation/driver-api/surface_aggregator/index.rst
Normal file
@@ -0,0 +1,21 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
=======================================
|
||||
Surface System Aggregator Module (SSAM)
|
||||
=======================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
client
|
||||
clients/index
|
||||
ssh
|
||||
internal
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
Indices
|
||||
=======
|
||||
|
||||
* :ref:`genindex`
|
||||
67
Documentation/driver-api/surface_aggregator/internal-api.rst
Normal file
67
Documentation/driver-api/surface_aggregator/internal-api.rst
Normal file
@@ -0,0 +1,67 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
==========================
|
||||
Internal API Documentation
|
||||
==========================
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
|
||||
|
||||
Packet Transport Layer
|
||||
======================
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/ssh_parser.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/ssh_parser.c
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/ssh_msgb.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.c
|
||||
:internal:
|
||||
|
||||
|
||||
Request Transport Layer
|
||||
=======================
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/ssh_request_layer.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/ssh_request_layer.c
|
||||
:internal:
|
||||
|
||||
|
||||
Controller
|
||||
==========
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/controller.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/controller.c
|
||||
:internal:
|
||||
|
||||
|
||||
Client Device Bus
|
||||
=================
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/bus.c
|
||||
:internal:
|
||||
|
||||
|
||||
Core
|
||||
====
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/core.c
|
||||
:internal:
|
||||
|
||||
|
||||
Trace Helpers
|
||||
=============
|
||||
|
||||
.. kernel-doc:: drivers/platform/surface/aggregator/trace.h
|
||||
577
Documentation/driver-api/surface_aggregator/internal.rst
Normal file
577
Documentation/driver-api/surface_aggregator/internal.rst
Normal file
File diff suppressed because it is too large
Load Diff
77
Documentation/driver-api/surface_aggregator/overview.rst
Normal file
77
Documentation/driver-api/surface_aggregator/overview.rst
Normal file
@@ -0,0 +1,77 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
========
|
||||
Overview
|
||||
========
|
||||
|
||||
The Surface/System Aggregator Module (SAM, SSAM) is an (arguably *the*)
|
||||
embedded controller (EC) on Microsoft Surface devices. It has been originally
|
||||
introduced on 4th generation devices (Surface Pro 4, Surface Book 1), but
|
||||
its responsibilities and feature-set have since been expanded significantly
|
||||
with the following generations.
|
||||
|
||||
|
||||
Features and Integration
|
||||
========================
|
||||
|
||||
Not much is currently known about SAM on 4th generation devices (Surface Pro
|
||||
4, Surface Book 1), due to the use of a different communication interface
|
||||
between host and EC (as detailed below). On 5th (Surface Pro 2017, Surface
|
||||
Book 2, Surface Laptop 1) and later generation devices, SAM is responsible
|
||||
for providing battery information (both current status and static values,
|
||||
such as maximum capacity etc.), as well as an assortment of temperature
|
||||
sensors (e.g. skin temperature) and cooling/performance-mode setting to the
|
||||
host. On the Surface Book 2, specifically, it additionally provides an
|
||||
interface for properly handling clipboard detachment (i.e. separating the
|
||||
display part from the keyboard part of the device), on the Surface Laptop 1
|
||||
and 2 it is required for keyboard HID input. This HID subsystem has been
|
||||
restructured for 7th generation devices and on those, specifically Surface
|
||||
Laptop 3 and Surface Book 3, is responsible for all major HID input (i.e.
|
||||
keyboard and touchpad).
|
||||
|
||||
While features have not changed much on a coarse level since the 5th
|
||||
generation, internal interfaces have undergone some rather large changes. On
|
||||
5th and 6th generation devices, both battery and temperature information is
|
||||
exposed to ACPI via a shim driver (referred to as Surface ACPI Notify, or
|
||||
SAN), translating ACPI generic serial bus write-/read-accesses to SAM
|
||||
requests. On 7th generation devices, this additional layer is gone and these
|
||||
devices require a driver hooking directly into the SAM interface. Equally,
|
||||
on newer generations, less devices are declared in ACPI, making them a bit
|
||||
harder to discover and requiring us to hard-code a sort of device registry.
|
||||
Due to this, a SSAM bus and subsystem with client devices
|
||||
(:c:type:`struct ssam_device <ssam_device>`) has been implemented.
|
||||
|
||||
|
||||
Communication
|
||||
=============
|
||||
|
||||
The type of communication interface between host and EC depends on the
|
||||
generation of the Surface device. On 4th generation devices, host and EC
|
||||
communicate via HID, specifically using a HID-over-I2C device, whereas on
|
||||
5th and later generations, communication takes place via a USART serial
|
||||
device. In accordance to the drivers found on other operating systems, we
|
||||
refer to the serial device and its driver as Surface Serial Hub (SSH). When
|
||||
needed, we differentiate between both types of SAM by referring to them as
|
||||
SAM-over-SSH and SAM-over-HID.
|
||||
|
||||
Currently, this subsystem only supports SAM-over-SSH. The SSH communication
|
||||
interface is described in more detail below. The HID interface has not been
|
||||
reverse engineered yet and it is, at the moment, unclear how many (and
|
||||
which) concepts of the SSH interface detailed below can be transferred to
|
||||
it.
|
||||
|
||||
Surface Serial Hub
|
||||
------------------
|
||||
|
||||
As already elaborated above, the Surface Serial Hub (SSH) is the
|
||||
communication interface for SAM on 5th- and all later-generation Surface
|
||||
devices. On the highest level, communication can be separated into two main
|
||||
types: Requests, messages sent from host to EC that may trigger a direct
|
||||
response from the EC (explicitly associated with the request), and events
|
||||
(sometimes also referred to as notifications), sent from EC to host without
|
||||
being a direct response to a previous request. We may also refer to requests
|
||||
without response as commands. In general, events need to be enabled via one
|
||||
of multiple dedicated requests before they are sent by the EC.
|
||||
|
||||
See :doc:`ssh` for a more technical protocol documentation and
|
||||
:doc:`internal` for an overview of the internal driver architecture.
|
||||
344
Documentation/driver-api/surface_aggregator/ssh.rst
Normal file
344
Documentation/driver-api/surface_aggregator/ssh.rst
Normal file
@@ -0,0 +1,344 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
.. |u8| replace:: :c:type:`u8 <u8>`
|
||||
.. |u16| replace:: :c:type:`u16 <u16>`
|
||||
.. |TYPE| replace:: ``TYPE``
|
||||
.. |LEN| replace:: ``LEN``
|
||||
.. |SEQ| replace:: ``SEQ``
|
||||
.. |SYN| replace:: ``SYN``
|
||||
.. |NAK| replace:: ``NAK``
|
||||
.. |ACK| replace:: ``ACK``
|
||||
.. |DATA| replace:: ``DATA``
|
||||
.. |DATA_SEQ| replace:: ``DATA_SEQ``
|
||||
.. |DATA_NSQ| replace:: ``DATA_NSQ``
|
||||
.. |TC| replace:: ``TC``
|
||||
.. |TID| replace:: ``TID``
|
||||
.. |IID| replace:: ``IID``
|
||||
.. |RQID| replace:: ``RQID``
|
||||
.. |CID| replace:: ``CID``
|
||||
|
||||
===========================
|
||||
Surface Serial Hub Protocol
|
||||
===========================
|
||||
|
||||
The Surface Serial Hub (SSH) is the central communication interface for the
|
||||
embedded Surface Aggregator Module controller (SAM or EC), found on newer
|
||||
Surface generations. We will refer to this protocol and interface as
|
||||
SAM-over-SSH, as opposed to SAM-over-HID for the older generations.
|
||||
|
||||
On Surface devices with SAM-over-SSH, SAM is connected to the host via UART
|
||||
and defined in ACPI as device with ID ``MSHW0084``. On these devices,
|
||||
significant functionality is provided via SAM, including access to battery
|
||||
and power information and events, thermal read-outs and events, and many
|
||||
more. For Surface Laptops, keyboard input is handled via HID directed
|
||||
through SAM, on the Surface Laptop 3 and Surface Book 3 this also includes
|
||||
touchpad input.
|
||||
|
||||
Note that the standard disclaimer for this subsystem also applies to this
|
||||
document: All of this has been reverse-engineered and may thus be erroneous
|
||||
and/or incomplete.
|
||||
|
||||
All CRCs used in the following are two-byte ``crc_ccitt_false(0xffff, ...)``.
|
||||
All multi-byte values are little-endian, there is no implicit padding between
|
||||
values.
|
||||
|
||||
|
||||
SSH Packet Protocol: Definitions
|
||||
================================
|
||||
|
||||
The fundamental communication unit of the SSH protocol is a frame
|
||||
(:c:type:`struct ssh_frame <ssh_frame>`). A frame consists of the following
|
||||
fields, packed together and in order:
|
||||
|
||||
.. flat-table:: SSH Frame
|
||||
:widths: 1 1 4
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Type
|
||||
- Description
|
||||
|
||||
* - |TYPE|
|
||||
- |u8|
|
||||
- Type identifier of the frame.
|
||||
|
||||
* - |LEN|
|
||||
- |u16|
|
||||
- Length of the payload associated with the frame.
|
||||
|
||||
* - |SEQ|
|
||||
- |u8|
|
||||
- Sequence ID (see explanation below).
|
||||
|
||||
Each frame structure is followed by a CRC over this structure. The CRC over
|
||||
the frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directly
|
||||
after the frame structure and before the payload. The payload is followed by
|
||||
its own CRC (over all payload bytes). If the payload is not present (i.e.
|
||||
the frame has ``LEN=0``), the CRC of the payload is still present and will
|
||||
evaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, it
|
||||
equals the number of bytes inbetween the CRC of the frame and the CRC of the
|
||||
payload.
|
||||
|
||||
Additionally, the following fixed two-byte sequences are used:
|
||||
|
||||
.. flat-table:: SSH Byte Sequences
|
||||
:widths: 1 1 4
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Value
|
||||
- Description
|
||||
|
||||
* - |SYN|
|
||||
- ``[0xAA, 0x55]``
|
||||
- Synchronization bytes.
|
||||
|
||||
A message consists of |SYN|, followed by the frame (|TYPE|, |LEN|, |SEQ| and
|
||||
CRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes,
|
||||
followed finally, regardless if the payload is present, the payload CRC. The
|
||||
messages corresponding to an exchange are, in part, identified by having the
|
||||
same sequence ID (|SEQ|), stored inside the frame (more on this in the next
|
||||
section). The sequence ID is a wrapping counter.
|
||||
|
||||
A frame can have the following types
|
||||
(:c:type:`enum ssh_frame_type <ssh_frame_type>`):
|
||||
|
||||
.. flat-table:: SSH Frame Types
|
||||
:widths: 1 1 4
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Value
|
||||
- Short Description
|
||||
|
||||
* - |NAK|
|
||||
- ``0x04``
|
||||
- Sent on error in previously received message.
|
||||
|
||||
* - |ACK|
|
||||
- ``0x40``
|
||||
- Sent to acknowledge receival of |DATA| frame.
|
||||
|
||||
* - |DATA_SEQ|
|
||||
- ``0x80``
|
||||
- Sent to transfer data. Sequenced.
|
||||
|
||||
* - |DATA_NSQ|
|
||||
- ``0x00``
|
||||
- Same as |DATA_SEQ|, but does not need to be ACKed.
|
||||
|
||||
Both |NAK|- and |ACK|-type frames are used to control flow of messages and
|
||||
thus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on the
|
||||
other hand must carry a payload. The flow sequence and interaction of
|
||||
different frame types will be described in more depth in the next section.
|
||||
|
||||
|
||||
SSH Packet Protocol: Flow Sequence
|
||||
==================================
|
||||
|
||||
Each exchange begins with |SYN|, followed by a |DATA_SEQ|- or
|
||||
|DATA_NSQ|-type frame, followed by its CRC, payload, and payload CRC. In
|
||||
case of a |DATA_NSQ|-type frame, the exchange is then finished. In case of a
|
||||
|DATA_SEQ|-type frame, the receiving party has to acknowledge receival of
|
||||
the frame by responding with a message containing an |ACK|-type frame with
|
||||
the same sequence ID of the |DATA| frame. In other words, the sequence ID of
|
||||
the |ACK| frame specifies the |DATA| frame to be acknowledged. In case of an
|
||||
error, e.g. an invalid CRC, the receiving party responds with a message
|
||||
containing an |NAK|-type frame. As the sequence ID of the previous data
|
||||
frame, for which an error is indicated via the |NAK| frame, cannot be relied
|
||||
upon, the sequence ID of the |NAK| frame should not be used and is set to
|
||||
zero. After receival of an |NAK| frame, the sending party should re-send all
|
||||
outstanding (non-ACKed) messages.
|
||||
|
||||
Sequence IDs are not synchronized between the two parties, meaning that they
|
||||
are managed independently for each party. Identifying the messages
|
||||
corresponding to a single exchange thus relies on the sequence ID as well as
|
||||
the type of the message, and the context. Specifically, the sequence ID is
|
||||
used to associate an ``ACK`` with its ``DATA_SEQ``-type frame, but not
|
||||
``DATA_SEQ``- or ``DATA_NSQ``-type frames with other ``DATA``- type frames.
|
||||
|
||||
An example exchange might look like this:
|
||||
|
||||
::
|
||||
|
||||
tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) -----------------------------
|
||||
rx: ------------------------------------- SYN FRAME(A) CRC(F) CRC(P) --
|
||||
|
||||
where both frames have the same sequence ID (``SEQ``). Here, ``FRAME(D)``
|
||||
indicates a |DATA_SEQ|-type frame, ``FRAME(A)`` an ``ACK``-type frame,
|
||||
``CRC(F)`` the CRC over the previous frame, ``CRC(P)`` the CRC over the
|
||||
previous payload. In case of an error, the exchange would look like this:
|
||||
|
||||
::
|
||||
|
||||
tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) -----------------------------
|
||||
rx: ------------------------------------- SYN FRAME(N) CRC(F) CRC(P) --
|
||||
|
||||
upon which the sender should re-send the message. ``FRAME(N)`` indicates an
|
||||
|NAK|-type frame. Note that the sequence ID of the |NAK|-type frame is fixed
|
||||
to zero. For |DATA_NSQ|-type frames, both exchanges are the same:
|
||||
|
||||
::
|
||||
|
||||
tx: -- SYN FRAME(DATA_NSQ) CRC(F) PAYLOAD CRC(P) ----------------------
|
||||
rx: -------------------------------------------------------------------
|
||||
|
||||
Here, an error can be detected, but not corrected or indicated to the
|
||||
sending party. These exchanges are symmetric, i.e. switching ``rx`` and
|
||||
``tx`` results again in a valid exchange. Currently, no longer exchanges are
|
||||
known.
|
||||
|
||||
|
||||
Commands: Requests, Responses, and Events
|
||||
=========================================
|
||||
|
||||
Commands are sent as payload inside a data frame. Currently, this is the
|
||||
only known payload type of |DATA| frames, with a payload-type value of
|
||||
``0x80`` (:c:type:`SSH_PLD_TYPE_CMD <ssh_payload_type>`).
|
||||
|
||||
The command-type payload (:c:type:`struct ssh_command <ssh_command>`)
|
||||
consists of an eight-byte command structure, followed by optional and
|
||||
variable length command data. The length of this optional data is derived
|
||||
from the frame payload length given in the corresponding frame, i.e. it is
|
||||
``frame.len - sizeof(struct ssh_command)``. The command struct contains the
|
||||
following fields, packed together and in order:
|
||||
|
||||
.. flat-table:: SSH Command
|
||||
:widths: 1 1 4
|
||||
:header-rows: 1
|
||||
|
||||
* - Field
|
||||
- Type
|
||||
- Description
|
||||
|
||||
* - |TYPE|
|
||||
- |u8|
|
||||
- Type of the payload. For commands always ``0x80``.
|
||||
|
||||
* - |TC|
|
||||
- |u8|
|
||||
- Target category.
|
||||
|
||||
* - |TID| (out)
|
||||
- |u8|
|
||||
- Target ID for outgoing (host to EC) commands.
|
||||
|
||||
* - |TID| (in)
|
||||
- |u8|
|
||||
- Target ID for incoming (EC to host) commands.
|
||||
|
||||
* - |IID|
|
||||
- |u8|
|
||||
- Instance ID.
|
||||
|
||||
* - |RQID|
|
||||
- |u16|
|
||||
- Request ID.
|
||||
|
||||
* - |CID|
|
||||
- |u8|
|
||||
- Command ID.
|
||||
|
||||
The command struct and data, in general, does not contain any failure
|
||||
detection mechanism (e.g. CRCs), this is solely done on the frame level.
|
||||
|
||||
Command-type payloads are used by the host to send commands and requests to
|
||||
the EC as well as by the EC to send responses and events back to the host.
|
||||
We differentiate between requests (sent by the host), responses (sent by the
|
||||
EC in response to a request), and events (sent by the EC without a preceding
|
||||
request).
|
||||
|
||||
Commands and events are uniquely identified by their target category
|
||||
(``TC``) and command ID (``CID``). The target category specifies a general
|
||||
category for the command (e.g. system in general, vs. battery and AC, vs.
|
||||
temperature, and so on), while the command ID specifies the command inside
|
||||
that category. Only the combination of |TC| + |CID| is unique. Additionally,
|
||||
commands have an instance ID (``IID``), which is used to differentiate
|
||||
between different sub-devices. For example ``TC=3`` ``CID=1`` is a
|
||||
request to get the temperature on a thermal sensor, where |IID| specifies
|
||||
the respective sensor. If the instance ID is not used, it should be set to
|
||||
zero. If instance IDs are used, they, in general, start with a value of one,
|
||||
whereas zero may be used for instance independent queries, if applicable. A
|
||||
response to a request should have the same target category, command ID, and
|
||||
instance ID as the corresponding request.
|
||||
|
||||
Responses are matched to their corresponding request via the request ID
|
||||
(``RQID``) field. This is a 16 bit wrapping counter similar to the sequence
|
||||
ID on the frames. Note that the sequence ID of the frames for a
|
||||
request-response pair does not match. Only the request ID has to match.
|
||||
Frame-protocol wise these are two separate exchanges, and may even be
|
||||
separated, e.g. by an event being sent after the request but before the
|
||||
response. Not all commands produce a response, and this is not detectable by
|
||||
|TC| + |CID|. It is the responsibility of the issuing party to wait for a
|
||||
response (or signal this to the communication framework, as is done in
|
||||
SAN/ACPI via the ``SNC`` flag).
|
||||
|
||||
Events are identified by unique and reserved request IDs. These IDs should
|
||||
not be used by the host when sending a new request. They are used on the
|
||||
host to, first, detect events and, second, match them with a registered
|
||||
event handler. Request IDs for events are chosen by the host and directed to
|
||||
the EC when setting up and enabling an event source (via the
|
||||
enable-event-source request). The EC then uses the specified request ID for
|
||||
events sent from the respective source. Note that an event should still be
|
||||
identified by its target category, command ID, and, if applicable, instance
|
||||
ID, as a single event source can send multiple different event types. In
|
||||
general, however, a single target category should map to a single reserved
|
||||
event request ID.
|
||||
|
||||
Furthermore, requests, responses, and events have an associated target ID
|
||||
(``TID``). This target ID is split into output (host to EC) and input (EC to
|
||||
host) fields, with the respecting other field (e.g. output field on incoming
|
||||
messages) set to zero. Two ``TID`` values are known: Primary (``0x01``) and
|
||||
secondary (``0x02``). In general, the response to a request should have the
|
||||
same ``TID`` value, however, the field (output vs. input) should be used in
|
||||
accordance to the direction in which the response is sent (i.e. on the input
|
||||
field, as responses are generally sent from the EC to the host).
|
||||
|
||||
Note that, even though requests and events should be uniquely identifiable
|
||||
by target category and command ID alone, the EC may require specific
|
||||
target ID and instance ID values to accept a command. A command that is
|
||||
accepted for ``TID=1``, for example, may not be accepted for ``TID=2``
|
||||
and vice versa.
|
||||
|
||||
|
||||
Limitations and Observations
|
||||
============================
|
||||
|
||||
The protocol can, in theory, handle up to ``U8_MAX`` frames in parallel,
|
||||
with up to ``U16_MAX`` pending requests (neglecting request IDs reserved for
|
||||
events). In practice, however, this is more limited. From our testing
|
||||
(although via a python and thus a user-space program), it seems that the EC
|
||||
can handle up to four requests (mostly) reliably in parallel at a certain
|
||||
time. With five or more requests in parallel, consistent discarding of
|
||||
commands (ACKed frame but no command response) has been observed. For five
|
||||
simultaneous commands, this reproducibly resulted in one command being
|
||||
dropped and four commands being handled.
|
||||
|
||||
However, it has also been noted that, even with three requests in parallel,
|
||||
occasional frame drops happen. Apart from this, with a limit of three
|
||||
pending requests, no dropped commands (i.e. command being dropped but frame
|
||||
carrying command being ACKed) have been observed. In any case, frames (and
|
||||
possibly also commands) should be re-sent by the host if a certain timeout
|
||||
is exceeded. This is done by the EC for frames with a timeout of one second,
|
||||
up to two re-tries (i.e. three transmissions in total). The limit of
|
||||
re-tries also applies to received NAKs, and, in a worst case scenario, can
|
||||
lead to entire messages being dropped.
|
||||
|
||||
While this also seems to work fine for pending data frames as long as no
|
||||
transmission failures occur, implementation and handling of these seems to
|
||||
depend on the assumption that there is only one non-acknowledged data frame.
|
||||
In particular, the detection of repeated frames relies on the last sequence
|
||||
number. This means that, if a frame that has been successfully received by
|
||||
the EC is sent again, e.g. due to the host not receiving an |ACK|, the EC
|
||||
will only detect this if it has the sequence ID of the last frame received
|
||||
by the EC. As an example: Sending two frames with ``SEQ=0`` and ``SEQ=1``
|
||||
followed by a repetition of ``SEQ=0`` will not detect the second ``SEQ=0``
|
||||
frame as such, and thus execute the command in this frame each time it has
|
||||
been received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` and
|
||||
then repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition of
|
||||
the first one and ignore it, thus executing the contained command only once.
|
||||
|
||||
In conclusion, this suggests a limit of at most one pending un-ACKed frame
|
||||
(per party, effectively leading to synchronous communication regarding
|
||||
frames) and at most three pending commands. The limit to synchronous frame
|
||||
transfers seems to be consistent with behavior observed on Windows.
|
||||
@@ -324,6 +324,8 @@ Code Seq# Include File Comments
|
||||
0xA3 90-9F linux/dtlk.h
|
||||
0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem
|
||||
0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org>
|
||||
0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
|
||||
<mailto:luzmaximilian@gmail.com>
|
||||
0xAA 00-3F linux/uapi/linux/userfaultfd.h
|
||||
0xAB 00-1F linux/nbd.h
|
||||
0xAC 00-1F linux/raw.h
|
||||
|
||||
45
MAINTAINERS
45
MAINTAINERS
@@ -4946,17 +4946,17 @@ M: Matthew Garrett <mjg59@srcf.ucam.org>
|
||||
M: Pali Rohár <pali@kernel.org>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-laptop.c
|
||||
F: drivers/platform/x86/dell/dell-laptop.c
|
||||
|
||||
DELL LAPTOP FREEFALL DRIVER
|
||||
M: Pali Rohár <pali@kernel.org>
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-smo8800.c
|
||||
F: drivers/platform/x86/dell/dell-smo8800.c
|
||||
|
||||
DELL LAPTOP RBTN DRIVER
|
||||
M: Pali Rohár <pali@kernel.org>
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-rbtn.*
|
||||
F: drivers/platform/x86/dell/dell-rbtn.*
|
||||
|
||||
DELL LAPTOP SMM DRIVER
|
||||
M: Pali Rohár <pali@kernel.org>
|
||||
@@ -4968,26 +4968,26 @@ DELL REMOTE BIOS UPDATE DRIVER
|
||||
M: Stuart Hayes <stuart.w.hayes@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell_rbu.c
|
||||
F: drivers/platform/x86/dell/dell_rbu.c
|
||||
|
||||
DELL SMBIOS DRIVER
|
||||
M: Pali Rohár <pali@kernel.org>
|
||||
M: Mario Limonciello <mario.limonciello@dell.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-smbios.*
|
||||
F: drivers/platform/x86/dell/dell-smbios.*
|
||||
|
||||
DELL SMBIOS SMM DRIVER
|
||||
M: Mario Limonciello <mario.limonciello@dell.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-smbios-smm.c
|
||||
F: drivers/platform/x86/dell/dell-smbios-smm.c
|
||||
|
||||
DELL SMBIOS WMI DRIVER
|
||||
M: Mario Limonciello <mario.limonciello@dell.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-smbios-wmi.c
|
||||
F: drivers/platform/x86/dell/dell-smbios-wmi.c
|
||||
F: tools/wmi/dell-smbios-example.c
|
||||
|
||||
DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas)
|
||||
@@ -4995,12 +4995,12 @@ M: Stuart Hayes <stuart.w.hayes@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/driver-api/dcdbas.rst
|
||||
F: drivers/platform/x86/dcdbas.*
|
||||
F: drivers/platform/x86/dell/dcdbas.*
|
||||
|
||||
DELL WMI DESCRIPTOR DRIVER
|
||||
M: Mario Limonciello <mario.limonciello@dell.com>
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-wmi-descriptor.c
|
||||
F: drivers/platform/x86/dell/dell-wmi-descriptor.c
|
||||
|
||||
DELL WMI SYSMAN DRIVER
|
||||
M: Divya Bharathi <divya.bharathi@dell.com>
|
||||
@@ -5009,13 +5009,13 @@ M: Prasanth Ksr <prasanth.ksr@dell.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-class-firmware-attributes
|
||||
F: drivers/platform/x86/dell-wmi-sysman/
|
||||
F: drivers/platform/x86/dell/dell-wmi-sysman/
|
||||
|
||||
DELL WMI NOTIFICATIONS DRIVER
|
||||
M: Matthew Garrett <mjg59@srcf.ucam.org>
|
||||
M: Pali Rohár <pali@kernel.org>
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell-wmi.c
|
||||
F: drivers/platform/x86/dell/dell-wmi.c
|
||||
|
||||
DELTA ST MEDIA DRIVER
|
||||
M: Hugues Fruchet <hugues.fruchet@st.com>
|
||||
@@ -8908,7 +8908,6 @@ L: linux-gpio@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git
|
||||
F: drivers/gpio/gpio-ich.c
|
||||
F: drivers/gpio/gpio-intel-mid.c
|
||||
F: drivers/gpio/gpio-merrifield.c
|
||||
F: drivers/gpio/gpio-ml-ioh.c
|
||||
F: drivers/gpio/gpio-pch.c
|
||||
@@ -9080,7 +9079,6 @@ M: Andy Shevchenko <andy@kernel.org>
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git
|
||||
F: drivers/gpio/gpio-*cove.c
|
||||
F: drivers/gpio/gpio-msic.c
|
||||
|
||||
INTEL PMIC MULTIFUNCTION DEVICE DRIVERS
|
||||
M: Andy Shevchenko <andy@kernel.org>
|
||||
@@ -11798,12 +11796,31 @@ S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
|
||||
F: drivers/platform/surface/
|
||||
|
||||
MICROSOFT SURFACE HOT-PLUG DRIVER
|
||||
M: Maximilian Luz <luzmaximilian@gmail.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/surface/surface_hotplug.c
|
||||
|
||||
MICROSOFT SURFACE PRO 3 BUTTON DRIVER
|
||||
M: Chen Yu <yu.c.chen@intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/platform/surface/surfacepro3_button.c
|
||||
|
||||
MICROSOFT SURFACE SYSTEM AGGREGATOR SUBSYSTEM
|
||||
M: Maximilian Luz <luzmaximilian@gmail.com>
|
||||
S: Maintained
|
||||
W: https://github.com/linux-surface/surface-aggregator-module
|
||||
C: irc://chat.freenode.net/##linux-surface
|
||||
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: include/linux/surface_acpi_notify.h
|
||||
F: include/linux/surface_aggregator/
|
||||
F: include/uapi/linux/surface_aggregator/
|
||||
|
||||
MICROTEK X6 SCANNER
|
||||
M: Oliver Neukum <oliver@neukum.org>
|
||||
S: Maintained
|
||||
@@ -17656,7 +17673,7 @@ F: drivers/thermal/gov_power_allocator.c
|
||||
F: include/trace/events/thermal_power_allocator.h
|
||||
|
||||
THINKPAD ACPI EXTRAS DRIVER
|
||||
M: Henrique de Moraes Holschuh <ibm-acpi@hmh.eng.br>
|
||||
M: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
|
||||
L: ibm-acpi-devel@lists.sourceforge.net
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
|
||||
@@ -30,4 +30,3 @@ obj-$(subst m,y,$(CONFIG_GPIO_PCA953X)) += platform_tca6416.o
|
||||
obj-$(subst m,y,$(CONFIG_KEYBOARD_GPIO)) += platform_gpio_keys.o
|
||||
obj-$(subst m,y,$(CONFIG_INTEL_MID_POWER_BUTTON)) += platform_mrfld_power_btn.o
|
||||
obj-$(subst m,y,$(CONFIG_RTC_DRV_CMOS)) += platform_mrfld_rtc.o
|
||||
obj-$(subst m,y,$(CONFIG_INTEL_MID_WATCHDOG)) += platform_mrfld_wdt.o
|
||||
|
||||
@@ -1253,13 +1253,6 @@ config GPIO_MAX77650
|
||||
GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor.
|
||||
These chips have a single pin that can be configured as GPIO.
|
||||
|
||||
config GPIO_MSIC
|
||||
bool "Intel MSIC mixed signal gpio support"
|
||||
depends on (X86 || COMPILE_TEST) && MFD_INTEL_MSIC
|
||||
help
|
||||
Enable support for GPIO on intel MSIC controllers found in
|
||||
intel MID devices
|
||||
|
||||
config GPIO_PALMAS
|
||||
bool "TI PALMAS series PMICs GPIO"
|
||||
depends on MFD_PALMAS
|
||||
@@ -1455,13 +1448,6 @@ config GPIO_BT8XX
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config GPIO_INTEL_MID
|
||||
bool "Intel MID GPIO support"
|
||||
depends on X86_INTEL_MID
|
||||
select GPIOLIB_IRQCHIP
|
||||
help
|
||||
Say Y here to support Intel MID GPIO.
|
||||
|
||||
config GPIO_MERRIFIELD
|
||||
tristate "Intel Merrifield GPIO support"
|
||||
depends on X86_INTEL_MID
|
||||
|
||||
@@ -67,7 +67,6 @@ obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o
|
||||
obj-$(CONFIG_GPIO_HLWD) += gpio-hlwd.o
|
||||
obj-$(CONFIG_HTC_EGPIO) += gpio-htc-egpio.o
|
||||
obj-$(CONFIG_GPIO_ICH) += gpio-ich.o
|
||||
obj-$(CONFIG_GPIO_INTEL_MID) += gpio-intel-mid.o
|
||||
obj-$(CONFIG_GPIO_IOP) += gpio-iop.o
|
||||
obj-$(CONFIG_GPIO_IT87) += gpio-it87.o
|
||||
obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o
|
||||
|
||||
@@ -101,7 +101,7 @@ for a few GPIOs. Those should stay where they are.
|
||||
|
||||
At the same time it makes sense to get rid of code duplication in existing or
|
||||
new coming drivers. For example, gpio-ml-ioh should be incorporated into
|
||||
gpio-pch. In similar way gpio-intel-mid into gpio-pxa.
|
||||
gpio-pch.
|
||||
|
||||
|
||||
Generic MMIO GPIO
|
||||
|
||||
@@ -1,414 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel MID GPIO driver
|
||||
*
|
||||
* Copyright (c) 2008-2014,2016 Intel Corporation.
|
||||
*/
|
||||
|
||||
/* Supports:
|
||||
* Moorestown platform Langwell chip.
|
||||
* Medfield platform Penwell chip.
|
||||
* Clovertrail platform Cloverview chip.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stddef.h>
|
||||
|
||||
#define INTEL_MID_IRQ_TYPE_EDGE (1 << 0)
|
||||
#define INTEL_MID_IRQ_TYPE_LEVEL (1 << 1)
|
||||
|
||||
/*
|
||||
* Langwell chip has 64 pins and thus there are 2 32bit registers to control
|
||||
* each feature, while Penwell chip has 96 pins for each block, and need 3 32bit
|
||||
* registers to control them, so we only define the order here instead of a
|
||||
* structure, to get a bit offset for a pin (use GPDR as an example):
|
||||
*
|
||||
* nreg = ngpio / 32;
|
||||
* reg = offset / 32;
|
||||
* bit = offset % 32;
|
||||
* reg_addr = reg_base + GPDR * nreg * 4 + reg * 4;
|
||||
*
|
||||
* so the bit of reg_addr is to control pin offset's GPDR feature
|
||||
*/
|
||||
|
||||
enum GPIO_REG {
|
||||
GPLR = 0, /* pin level read-only */
|
||||
GPDR, /* pin direction */
|
||||
GPSR, /* pin set */
|
||||
GPCR, /* pin clear */
|
||||
GRER, /* rising edge detect */
|
||||
GFER, /* falling edge detect */
|
||||
GEDR, /* edge detect result */
|
||||
GAFR, /* alt function */
|
||||
};
|
||||
|
||||
/* intel_mid gpio driver data */
|
||||
struct intel_mid_gpio_ddata {
|
||||
u16 ngpio; /* number of gpio pins */
|
||||
u32 chip_irq_type; /* chip interrupt type */
|
||||
};
|
||||
|
||||
struct intel_mid_gpio {
|
||||
struct gpio_chip chip;
|
||||
void __iomem *reg_base;
|
||||
spinlock_t lock;
|
||||
struct pci_dev *pdev;
|
||||
};
|
||||
|
||||
static void __iomem *gpio_reg(struct gpio_chip *chip, unsigned offset,
|
||||
enum GPIO_REG reg_type)
|
||||
{
|
||||
struct intel_mid_gpio *priv = gpiochip_get_data(chip);
|
||||
unsigned nreg = chip->ngpio / 32;
|
||||
u8 reg = offset / 32;
|
||||
|
||||
return priv->reg_base + reg_type * nreg * 4 + reg * 4;
|
||||
}
|
||||
|
||||
static void __iomem *gpio_reg_2bit(struct gpio_chip *chip, unsigned offset,
|
||||
enum GPIO_REG reg_type)
|
||||
{
|
||||
struct intel_mid_gpio *priv = gpiochip_get_data(chip);
|
||||
unsigned nreg = chip->ngpio / 32;
|
||||
u8 reg = offset / 16;
|
||||
|
||||
return priv->reg_base + reg_type * nreg * 4 + reg * 4;
|
||||
}
|
||||
|
||||
static int intel_gpio_request(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
void __iomem *gafr = gpio_reg_2bit(chip, offset, GAFR);
|
||||
u32 value = readl(gafr);
|
||||
int shift = (offset % 16) << 1, af = (value >> shift) & 3;
|
||||
|
||||
if (af) {
|
||||
value &= ~(3 << shift);
|
||||
writel(value, gafr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_gpio_get(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
void __iomem *gplr = gpio_reg(chip, offset, GPLR);
|
||||
|
||||
return !!(readl(gplr) & BIT(offset % 32));
|
||||
}
|
||||
|
||||
static void intel_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
||||
{
|
||||
void __iomem *gpsr, *gpcr;
|
||||
|
||||
if (value) {
|
||||
gpsr = gpio_reg(chip, offset, GPSR);
|
||||
writel(BIT(offset % 32), gpsr);
|
||||
} else {
|
||||
gpcr = gpio_reg(chip, offset, GPCR);
|
||||
writel(BIT(offset % 32), gpcr);
|
||||
}
|
||||
}
|
||||
|
||||
static int intel_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
struct intel_mid_gpio *priv = gpiochip_get_data(chip);
|
||||
void __iomem *gpdr = gpio_reg(chip, offset, GPDR);
|
||||
u32 value;
|
||||
unsigned long flags;
|
||||
|
||||
if (priv->pdev)
|
||||
pm_runtime_get(&priv->pdev->dev);
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
value = readl(gpdr);
|
||||
value &= ~BIT(offset % 32);
|
||||
writel(value, gpdr);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
if (priv->pdev)
|
||||
pm_runtime_put(&priv->pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_gpio_direction_output(struct gpio_chip *chip,
|
||||
unsigned offset, int value)
|
||||
{
|
||||
struct intel_mid_gpio *priv = gpiochip_get_data(chip);
|
||||
void __iomem *gpdr = gpio_reg(chip, offset, GPDR);
|
||||
unsigned long flags;
|
||||
|
||||
intel_gpio_set(chip, offset, value);
|
||||
|
||||
if (priv->pdev)
|
||||
pm_runtime_get(&priv->pdev->dev);
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
value = readl(gpdr);
|
||||
value |= BIT(offset % 32);
|
||||
writel(value, gpdr);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
if (priv->pdev)
|
||||
pm_runtime_put(&priv->pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_mid_irq_type(struct irq_data *d, unsigned type)
|
||||
{
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
||||
struct intel_mid_gpio *priv = gpiochip_get_data(gc);
|
||||
u32 gpio = irqd_to_hwirq(d);
|
||||
unsigned long flags;
|
||||
u32 value;
|
||||
void __iomem *grer = gpio_reg(&priv->chip, gpio, GRER);
|
||||
void __iomem *gfer = gpio_reg(&priv->chip, gpio, GFER);
|
||||
|
||||
if (gpio >= priv->chip.ngpio)
|
||||
return -EINVAL;
|
||||
|
||||
if (priv->pdev)
|
||||
pm_runtime_get(&priv->pdev->dev);
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
if (type & IRQ_TYPE_EDGE_RISING)
|
||||
value = readl(grer) | BIT(gpio % 32);
|
||||
else
|
||||
value = readl(grer) & (~BIT(gpio % 32));
|
||||
writel(value, grer);
|
||||
|
||||
if (type & IRQ_TYPE_EDGE_FALLING)
|
||||
value = readl(gfer) | BIT(gpio % 32);
|
||||
else
|
||||
value = readl(gfer) & (~BIT(gpio % 32));
|
||||
writel(value, gfer);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
if (priv->pdev)
|
||||
pm_runtime_put(&priv->pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void intel_mid_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
}
|
||||
|
||||
static void intel_mid_irq_mask(struct irq_data *d)
|
||||
{
|
||||
}
|
||||
|
||||
static struct irq_chip intel_mid_irqchip = {
|
||||
.name = "INTEL_MID-GPIO",
|
||||
.irq_mask = intel_mid_irq_mask,
|
||||
.irq_unmask = intel_mid_irq_unmask,
|
||||
.irq_set_type = intel_mid_irq_type,
|
||||
};
|
||||
|
||||
static const struct intel_mid_gpio_ddata gpio_lincroft = {
|
||||
.ngpio = 64,
|
||||
};
|
||||
|
||||
static const struct intel_mid_gpio_ddata gpio_penwell_aon = {
|
||||
.ngpio = 96,
|
||||
.chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE,
|
||||
};
|
||||
|
||||
static const struct intel_mid_gpio_ddata gpio_penwell_core = {
|
||||
.ngpio = 96,
|
||||
.chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE,
|
||||
};
|
||||
|
||||
static const struct intel_mid_gpio_ddata gpio_cloverview_aon = {
|
||||
.ngpio = 96,
|
||||
.chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE | INTEL_MID_IRQ_TYPE_LEVEL,
|
||||
};
|
||||
|
||||
static const struct intel_mid_gpio_ddata gpio_cloverview_core = {
|
||||
.ngpio = 96,
|
||||
.chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE,
|
||||
};
|
||||
|
||||
static const struct pci_device_id intel_gpio_ids[] = {
|
||||
{
|
||||
/* Lincroft */
|
||||
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080f),
|
||||
.driver_data = (kernel_ulong_t)&gpio_lincroft,
|
||||
},
|
||||
{
|
||||
/* Penwell AON */
|
||||
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081f),
|
||||
.driver_data = (kernel_ulong_t)&gpio_penwell_aon,
|
||||
},
|
||||
{
|
||||
/* Penwell Core */
|
||||
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081a),
|
||||
.driver_data = (kernel_ulong_t)&gpio_penwell_core,
|
||||
},
|
||||
{
|
||||
/* Cloverview Aon */
|
||||
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08eb),
|
||||
.driver_data = (kernel_ulong_t)&gpio_cloverview_aon,
|
||||
},
|
||||
{
|
||||
/* Cloverview Core */
|
||||
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08f7),
|
||||
.driver_data = (kernel_ulong_t)&gpio_cloverview_core,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
static void intel_mid_irq_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
|
||||
struct intel_mid_gpio *priv = gpiochip_get_data(gc);
|
||||
struct irq_data *data = irq_desc_get_irq_data(desc);
|
||||
struct irq_chip *chip = irq_data_get_irq_chip(data);
|
||||
u32 base, gpio, mask;
|
||||
unsigned long pending;
|
||||
void __iomem *gedr;
|
||||
|
||||
/* check GPIO controller to check which pin triggered the interrupt */
|
||||
for (base = 0; base < priv->chip.ngpio; base += 32) {
|
||||
gedr = gpio_reg(&priv->chip, base, GEDR);
|
||||
while ((pending = readl(gedr))) {
|
||||
gpio = __ffs(pending);
|
||||
mask = BIT(gpio);
|
||||
/* Clear before handling so we can't lose an edge */
|
||||
writel(mask, gedr);
|
||||
generic_handle_irq(irq_find_mapping(gc->irq.domain,
|
||||
base + gpio));
|
||||
}
|
||||
}
|
||||
|
||||
chip->irq_eoi(data);
|
||||
}
|
||||
|
||||
static int intel_mid_irq_init_hw(struct gpio_chip *chip)
|
||||
{
|
||||
struct intel_mid_gpio *priv = gpiochip_get_data(chip);
|
||||
void __iomem *reg;
|
||||
unsigned base;
|
||||
|
||||
for (base = 0; base < priv->chip.ngpio; base += 32) {
|
||||
/* Clear the rising-edge detect register */
|
||||
reg = gpio_reg(&priv->chip, base, GRER);
|
||||
writel(0, reg);
|
||||
/* Clear the falling-edge detect register */
|
||||
reg = gpio_reg(&priv->chip, base, GFER);
|
||||
writel(0, reg);
|
||||
/* Clear the edge detect status register */
|
||||
reg = gpio_reg(&priv->chip, base, GEDR);
|
||||
writel(~0, reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused intel_gpio_runtime_idle(struct device *dev)
|
||||
{
|
||||
int err = pm_schedule_suspend(dev, 500);
|
||||
return err ?: -EBUSY;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops intel_gpio_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(NULL, NULL, intel_gpio_runtime_idle)
|
||||
};
|
||||
|
||||
static int intel_gpio_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
void __iomem *base;
|
||||
struct intel_mid_gpio *priv;
|
||||
u32 gpio_base;
|
||||
u32 irq_base;
|
||||
int retval;
|
||||
struct gpio_irq_chip *girq;
|
||||
struct intel_mid_gpio_ddata *ddata =
|
||||
(struct intel_mid_gpio_ddata *)id->driver_data;
|
||||
|
||||
retval = pcim_enable_device(pdev);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
retval = pcim_iomap_regions(pdev, 1 << 0 | 1 << 1, pci_name(pdev));
|
||||
if (retval) {
|
||||
dev_err(&pdev->dev, "I/O memory mapping error\n");
|
||||
return retval;
|
||||
}
|
||||
|
||||
base = pcim_iomap_table(pdev)[1];
|
||||
|
||||
irq_base = readl(base);
|
||||
gpio_base = readl(sizeof(u32) + base);
|
||||
|
||||
/* release the IO mapping, since we already get the info from bar1 */
|
||||
pcim_iounmap_regions(pdev, 1 << 1);
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->reg_base = pcim_iomap_table(pdev)[0];
|
||||
priv->chip.label = dev_name(&pdev->dev);
|
||||
priv->chip.parent = &pdev->dev;
|
||||
priv->chip.request = intel_gpio_request;
|
||||
priv->chip.direction_input = intel_gpio_direction_input;
|
||||
priv->chip.direction_output = intel_gpio_direction_output;
|
||||
priv->chip.get = intel_gpio_get;
|
||||
priv->chip.set = intel_gpio_set;
|
||||
priv->chip.base = gpio_base;
|
||||
priv->chip.ngpio = ddata->ngpio;
|
||||
priv->chip.can_sleep = false;
|
||||
priv->pdev = pdev;
|
||||
|
||||
spin_lock_init(&priv->lock);
|
||||
|
||||
girq = &priv->chip.irq;
|
||||
girq->chip = &intel_mid_irqchip;
|
||||
girq->init_hw = intel_mid_irq_init_hw;
|
||||
girq->parent_handler = intel_mid_irq_handler;
|
||||
girq->num_parents = 1;
|
||||
girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents,
|
||||
sizeof(*girq->parents),
|
||||
GFP_KERNEL);
|
||||
if (!girq->parents)
|
||||
return -ENOMEM;
|
||||
girq->parents[0] = pdev->irq;
|
||||
girq->first = irq_base;
|
||||
girq->default_type = IRQ_TYPE_NONE;
|
||||
girq->handler = handle_simple_irq;
|
||||
|
||||
pci_set_drvdata(pdev, priv);
|
||||
|
||||
retval = devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv);
|
||||
if (retval) {
|
||||
dev_err(&pdev->dev, "gpiochip_add error %d\n", retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
pm_runtime_allow(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pci_driver intel_gpio_driver = {
|
||||
.name = "intel_mid_gpio",
|
||||
.id_table = intel_gpio_ids,
|
||||
.probe = intel_gpio_probe,
|
||||
.driver = {
|
||||
.pm = &intel_gpio_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
builtin_pci_driver(intel_gpio_driver);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user