2021-05-19 06:27:58 +00:00
|
|
|
From 71af2529df7a873e906944e30c6ee2fd96276051 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Fri, 12 Feb 2021 12:54:34 +0100
|
|
|
|
Subject: [PATCH] platform/surface: Set up Surface Aggregator device registry
|
|
|
|
|
|
|
|
The Surface System Aggregator Module (SSAM) subsystem provides various
|
|
|
|
functionalities, which are separated by spreading them across multiple
|
|
|
|
devices and corresponding drivers. Parts of that functionality / some of
|
|
|
|
those devices, however, can (as far as we currently know) not be
|
|
|
|
auto-detected by conventional means. While older (specifically 5th- and
|
|
|
|
6th-)generation models do advertise most of their functionality via
|
|
|
|
standard platform devices in ACPI, newer generations do not.
|
|
|
|
|
|
|
|
As we are currently also not aware of any feasible way to query said
|
|
|
|
functionalities dynamically, this poses a problem. There is, however, a
|
|
|
|
device in ACPI that seems to be used by Windows for identifying
|
|
|
|
different Surface models: The Windows Surface Integration Device (WSID).
|
|
|
|
This device seems to have a HID corresponding to the overall set of
|
|
|
|
functionalities SSAM provides for the associated model.
|
|
|
|
|
|
|
|
This commit introduces a registry providing non-detectable device
|
|
|
|
information via software nodes. In addition, a SSAM platform hub driver
|
|
|
|
is introduced, which takes care of creating and managing the SSAM
|
|
|
|
devices specified in this registry. This approach allows for a
|
|
|
|
hierarchical setup akin to ACPI and is easily extendable, e.g. via
|
|
|
|
firmware node properties.
|
|
|
|
|
|
|
|
Note that this commit only provides the basis for the platform hub and
|
|
|
|
registry, and does not add any content to it. The registry will be
|
|
|
|
expanded in subsequent commits.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-2-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
MAINTAINERS | 1 +
|
|
|
|
drivers/platform/surface/Kconfig | 27 ++
|
|
|
|
drivers/platform/surface/Makefile | 1 +
|
|
|
|
.../surface/surface_aggregator_registry.c | 284 ++++++++++++++++++
|
|
|
|
4 files changed, 313 insertions(+)
|
|
|
|
create mode 100644 drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
|
|
index 9450e052f1b1..f6c524630575 100644
|
|
|
|
--- a/MAINTAINERS
|
|
|
|
+++ b/MAINTAINERS
|
|
|
|
@@ -11904,6 +11904,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/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
|
|
index fd45940ab6ce..c51c55204b5f 100644
|
|
|
|
--- a/drivers/platform/surface/Kconfig
|
|
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
|
|
@@ -77,6 +77,33 @@ 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_BOOK1_DGPU_SWITCH
|
|
|
|
tristate "Surface Book 1 dGPU Switch Driver"
|
|
|
|
depends on SYSFS
|
|
|
|
diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
|
|
|
|
index 6b69175598ab..ed12676f06e6 100644
|
|
|
|
--- a/drivers/platform/surface/Makefile
|
|
|
|
+++ b/drivers/platform/surface/Makefile
|
|
|
|
@@ -10,6 +10,7 @@ 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_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
|
|
|
|
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
|
|
|
|
obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..a051d941ad96
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -0,0 +1,284 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+/*
|
|
|
|
+ * Surface System Aggregator Module (SSAM) client device registry.
|
|
|
|
+ *
|
|
|
|
+ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that
|
|
|
|
+ * cannot be auto-detected. Provides device-hubs and performs instantiation
|
|
|
|
+ * for these devices.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <linux/acpi.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/platform_device.h>
|
|
|
|
+#include <linux/property.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",
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Book 2. */
|
|
|
|
+static const struct software_node *ssam_node_group_sb2[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Book 3. */
|
|
|
|
+static const struct software_node *ssam_node_group_sb3[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Laptop 1. */
|
|
|
|
+static const struct software_node *ssam_node_group_sl1[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Laptop 2. */
|
|
|
|
+static const struct software_node *ssam_node_group_sl2[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Laptop 3. */
|
|
|
|
+static const struct software_node *ssam_node_group_sl3[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Laptop Go. */
|
|
|
|
+static const struct software_node *ssam_node_group_slg1[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Pro 5. */
|
|
|
|
+static const struct software_node *ssam_node_group_sp5[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Pro 6. */
|
|
|
|
+static const struct software_node *ssam_node_group_sp6[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Devices for Surface Pro 7. */
|
|
|
|
+static const struct software_node *ssam_node_group_sp7[] = {
|
|
|
|
+ &ssam_node_root,
|
|
|
|
+ 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 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 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_platform_driver(ssam_platform_hub_driver);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From f9f59d226fdb643e6d136ddadc0040b594174623 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Fri, 12 Feb 2021 12:54:35 +0100
|
|
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add base device hub
|
|
|
|
|
|
|
|
The Surface Book 3 has a detachable base part. While the top part
|
|
|
|
(so-called clipboard) contains the CPU, touchscreen, and primary
|
|
|
|
battery, the base contains, among other things, a keyboard, touchpad,
|
|
|
|
and secondary battery.
|
|
|
|
|
|
|
|
Those devices do not react well to being accessed when the base part is
|
|
|
|
detached and should thus be removed and added in sync with the base. To
|
|
|
|
facilitate this, we introduce a virtual base device hub, which
|
|
|
|
automatically removes or adds the devices registered under it.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-3-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../surface/surface_aggregator_registry.c | 261 +++++++++++++++++-
|
|
|
|
1 file changed, 260 insertions(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
index a051d941ad96..6c23d75a044c 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -11,9 +11,12 @@
|
|
|
|
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
+#include <linux/limits.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
+#include <linux/mutex.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/property.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
|
|
|
|
#include <linux/surface_aggregator/controller.h>
|
|
|
|
#include <linux/surface_aggregator/device.h>
|
|
|
|
@@ -38,6 +41,12 @@ 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,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
/* Devices for Surface Book 2. */
|
|
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
|
|
&ssam_node_root,
|
|
|
|
@@ -47,6 +56,7 @@ static const struct software_node *ssam_node_group_sb2[] = {
|
|
|
|
/* Devices for Surface Book 3. */
|
|
|
|
static const struct software_node *ssam_node_group_sb3[] = {
|
|
|
|
&ssam_node_root,
|
|
|
|
+ &ssam_node_hub_base,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
@@ -177,6 +187,230 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+/* -- SSAM base-hub driver. ------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+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;
|
|
|
|
+
|
|
|
|
+ struct mutex lock; /* Guards state update checks and transitions. */
|
|
|
|
+ enum ssam_base_hub_state state;
|
|
|
|
+
|
|
|
|
+ struct ssam_event_notifier notif;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static 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;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&hub->lock);
|
|
|
|
+ connected = hub->state == SSAM_BASE_HUB_CONNECTED;
|
|
|
|
+ mutex_unlock(&hub->lock);
|
|
|
|
+
|
|
|
|
+ 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,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const struct attribute_group ssam_base_hub_group = {
|
|
|
|
+ .attrs = ssam_base_hub_attrs,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
|
|
|
|
+{
|
|
|
|
+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
|
|
|
|
+ int status = 0;
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&hub->lock);
|
|
|
|
+
|
|
|
|
+ if (hub->state == new)
|
|
|
|
+ return 0;
|
|
|
|
+ hub->state = new;
|
|
|
|
+
|
|
|
|
+ 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);
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ssam_base_hub_update(struct ssam_base_hub *hub)
|
|
|
|
+{
|
|
|
|
+ enum ssam_base_hub_state state;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&hub->lock);
|
|
|
|
+
|
|
|
|
+ status = ssam_base_hub_query_state(hub, &state);
|
|
|
|
+ if (!status)
|
|
|
|
+ status = __ssam_base_hub_update(hub, state);
|
|
|
|
+
|
|
|
|
+ mutex_unlock(&hub->lock);
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
|
|
+{
|
|
|
|
+ struct ssam_base_hub *hub;
|
|
|
|
+ struct ssam_device *sdev;
|
|
|
|
+ enum ssam_base_hub_state new;
|
|
|
|
+
|
|
|
|
+ hub = container_of(nf, struct ssam_base_hub, notif);
|
|
|
|
+ sdev = hub->sdev;
|
|
|
|
+
|
|
|
|
+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ if (event->length < 1) {
|
|
|
|
+ dev_err(&sdev->dev, "unexpected payload size: %u\n",
|
|
|
|
+ event->length);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (event->data[0])
|
|
|
|
+ new = SSAM_BASE_HUB_CONNECTED;
|
|
|
|
+ else
|
|
|
|
+ new = SSAM_BASE_HUB_DISCONNECTED;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&hub->lock);
|
|
|
|
+ __ssam_base_hub_update(hub, new);
|
|
|
|
+ mutex_unlock(&hub->lock);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * 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)
|
|
|
|
+{
|
|
|
|
+ return ssam_base_hub_update(dev_get_drvdata(dev));
|
|
|
|
+}
|
|
|
|
+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;
|
|
|
|
+
|
|
|
|
+ mutex_init(&hub->lock);
|
|
|
|
+
|
|
|
|
+ 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;
|
|
|
|
+
|
|
|
|
+ ssam_device_set_drvdata(sdev, hub);
|
|
|
|
+
|
|
|
|
+ status = ssam_notifier_register(sdev->ctrl, &hub->notif);
|
|
|
|
+ if (status)
|
|
|
|
+ goto err_register;
|
|
|
|
+
|
|
|
|
+ status = ssam_base_hub_update(hub);
|
|
|
|
+ if (status)
|
|
|
|
+ goto err_update;
|
|
|
|
+
|
|
|
|
+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
|
|
|
|
+ if (status)
|
|
|
|
+ goto err_update;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+err_update:
|
|
|
|
+ ssam_notifier_unregister(sdev->ctrl, &hub->notif);
|
|
|
|
+ ssam_hub_remove_devices(&sdev->dev);
|
|
|
|
+err_register:
|
|
|
|
+ mutex_destroy(&hub->lock);
|
|
|
|
+ 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);
|
|
|
|
+ ssam_hub_remove_devices(&sdev->dev);
|
|
|
|
+
|
|
|
|
+ mutex_destroy(&hub->lock);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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[] = {
|
|
|
|
@@ -277,7 +511,32 @@ static struct platform_driver ssam_platform_hub_driver = {
|
|
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
-module_platform_driver(ssam_platform_hub_driver);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Module initialization. ------------------------------------------------ */
|
|
|
|
+
|
|
|
|
+static int __init ssam_device_hub_init(void)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = platform_driver_register(&ssam_platform_hub_driver);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ status = ssam_device_driver_register(&ssam_base_hub_driver);
|
|
|
|
+ if (status)
|
|
|
|
+ platform_driver_unregister(&ssam_platform_hub_driver);
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+module_init(ssam_device_hub_init);
|
|
|
|
+
|
|
|
|
+static void __exit ssam_device_hub_exit(void)
|
|
|
|
+{
|
|
|
|
+ ssam_device_driver_unregister(&ssam_base_hub_driver);
|
|
|
|
+ platform_driver_unregister(&ssam_platform_hub_driver);
|
|
|
|
+}
|
|
|
|
+module_exit(ssam_device_hub_exit);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 77d0eb721463484c22c7700355985aa3ff7e72c2 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Fri, 12 Feb 2021 12:54:36 +0100
|
|
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add battery subsystem
|
|
|
|
devices
|
|
|
|
|
|
|
|
Add battery subsystem (TC=0x02) devices (battery and AC) to the SSAM
|
|
|
|
device registry. These devices need to be registered for 7th-generation
|
|
|
|
Surface models. On 5th- and 6th-generation models, these devices are
|
|
|
|
handled via the standard ACPI battery/AC interface, which in turn
|
|
|
|
accesses the same SSAM interface via the Surface ACPI Notify (SAN)
|
|
|
|
driver.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-4-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../surface/surface_aggregator_registry.c | 27 +++++++++++++++++++
|
|
|
|
1 file changed, 27 insertions(+)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
index 6c23d75a044c..cde279692842 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -47,6 +47,24 @@ static const struct software_node ssam_node_hub_base = {
|
|
|
|
.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,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
/* Devices for Surface Book 2. */
|
|
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
|
|
&ssam_node_root,
|
|
|
|
@@ -57,6 +75,9 @@ static const struct software_node *ssam_node_group_sb2[] = {
|
|
|
|
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,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
@@ -75,12 +96,16 @@ static const struct software_node *ssam_node_group_sl2[] = {
|
|
|
|
/* 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,
|
|
|
|
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,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
@@ -99,6 +124,8 @@ static const struct software_node *ssam_node_group_sp6[] = {
|
|
|
|
/* Devices for Surface Pro 7. */
|
|
|
|
static const struct software_node *ssam_node_group_sp7[] = {
|
|
|
|
&ssam_node_root,
|
|
|
|
+ &ssam_node_bat_ac,
|
|
|
|
+ &ssam_node_bat_main,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 9850764bc9908858c8ae0292fe0c07589803803f Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Fri, 12 Feb 2021 12:54:37 +0100
|
|
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add platform profile
|
|
|
|
device
|
|
|
|
|
|
|
|
Add the SSAM platform profile device to the SSAM device registry. This
|
|
|
|
device is accessible under the thermal subsystem (TC=0x03) and needs to
|
|
|
|
be registered for all Surface models.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-5-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../surface/surface_aggregator_registry.c | 15 +++++++++++++++
|
|
|
|
1 file changed, 15 insertions(+)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
index cde279692842..33904613dd4b 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -65,9 +65,16 @@ static const struct software_node ssam_node_bat_sb3base = {
|
|
|
|
.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,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
/* Devices for Surface Book 2. */
|
|
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
|
|
&ssam_node_root,
|
|
|
|
+ &ssam_node_tmp_pprof,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
@@ -78,18 +85,21 @@ static const struct software_node *ssam_node_group_sb3[] = {
|
|
|
|
&ssam_node_bat_ac,
|
|
|
|
&ssam_node_bat_main,
|
|
|
|
&ssam_node_bat_sb3base,
|
|
|
|
+ &ssam_node_tmp_pprof,
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
|
|
|
@@ -98,6 +108,7 @@ static const struct software_node *ssam_node_group_sl3[] = {
|
|
|
|
&ssam_node_root,
|
|
|
|
&ssam_node_bat_ac,
|
|
|
|
&ssam_node_bat_main,
|
|
|
|
+ &ssam_node_tmp_pprof,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
@@ -106,18 +117,21 @@ 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,
|
|
|
|
};
|
|
|
|
|
|
|
|
@@ -126,6 +140,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,
|
|
|
|
};
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 64f866b5702ffb3fa8f1820753109af01ae271f5 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Fri, 12 Feb 2021 12:54:38 +0100
|
|
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add DTX device
|
|
|
|
|
|
|
|
Add the detachment system (DTX) SSAM device for the Surface Book 3. This
|
|
|
|
device is accessible under the base (TC=0x11) subsystem.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-6-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++
|
|
|
|
1 file changed, 7 insertions(+)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
index 33904613dd4b..dc044d06828b 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -71,6 +71,12 @@ static const struct software_node ssam_node_tmp_pprof = {
|
|
|
|
.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,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
/* Devices for Surface Book 2. */
|
|
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
|
|
&ssam_node_root,
|
|
|
|
@@ -86,6 +92,7 @@ static const struct software_node *ssam_node_group_sb3[] = {
|
|
|
|
&ssam_node_bat_main,
|
|
|
|
&ssam_node_bat_sb3base,
|
|
|
|
&ssam_node_tmp_pprof,
|
|
|
|
+ &ssam_node_bas_dtx,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 31833a307a36926c7aebce35c7be4c08d1522ca5 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Fri, 12 Feb 2021 12:54:39 +0100
|
|
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add HID subsystem
|
|
|
|
devices
|
|
|
|
|
|
|
|
Add HID subsystem (TC=0x15) devices. These devices need to be registered
|
|
|
|
for 7th-generation Surface models. On previous generations, these
|
|
|
|
devices are either provided as platform devices via ACPI (Surface Laptop
|
|
|
|
1 and 2) or implemented as standard USB device.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-7-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../surface/surface_aggregator_registry.c | 49 +++++++++++++++++++
|
|
|
|
1 file changed, 49 insertions(+)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
index dc044d06828b..caee90d135c5 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -77,6 +77,48 @@ static const struct software_node ssam_node_bas_dtx = {
|
|
|
|
.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,
|
|
|
|
@@ -93,6 +135,10 @@ static const struct software_node *ssam_node_group_sb3[] = {
|
|
|
|
&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,
|
|
|
|
};
|
|
|
|
|
|
|
|
@@ -116,6 +162,9 @@ static const struct software_node *ssam_node_group_sl3[] = {
|
|
|
|
&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,
|
|
|
|
};
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From c130068f4a980f5d7ad6e655c9299cb1b747a7ba Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Thu, 11 Feb 2021 21:17:03 +0100
|
|
|
|
Subject: [PATCH] platform/surface: Add platform profile driver
|
|
|
|
|
|
|
|
Add a driver to provide platform profile support on 5th- and later
|
|
|
|
generation Microsoft Surface devices with a Surface System Aggregator
|
|
|
|
Module. On those devices, the platform profile can be used to influence
|
|
|
|
cooling behavior and power consumption.
|
|
|
|
|
|
|
|
For example, the default 'quiet' profile limits fan noise and in turn
|
|
|
|
sacrifices performance of the discrete GPU found on Surface Books. Its
|
|
|
|
full performance can only be unlocked on the 'performance' profile.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210211201703.658240-5-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
MAINTAINERS | 6 +
|
|
|
|
drivers/platform/surface/Kconfig | 22 ++
|
|
|
|
drivers/platform/surface/Makefile | 1 +
|
|
|
|
.../surface/surface_platform_profile.c | 190 ++++++++++++++++++
|
|
|
|
4 files changed, 219 insertions(+)
|
|
|
|
create mode 100644 drivers/platform/surface/surface_platform_profile.c
|
|
|
|
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
|
|
index f6c524630575..fce5cdcefc0b 100644
|
|
|
|
--- a/MAINTAINERS
|
|
|
|
+++ b/MAINTAINERS
|
|
|
|
@@ -11889,6 +11889,12 @@ L: platform-driver-x86@vger.kernel.org
|
|
|
|
S: Maintained
|
|
|
|
F: drivers/platform/surface/surface_hotplug.c
|
|
|
|
|
|
|
|
+MICROSOFT SURFACE PLATFORM PROFILE DRIVER
|
|
|
|
+M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
|
|
+S: Maintained
|
|
|
|
+F: drivers/platform/surface/surface_platform_profile.c
|
|
|
|
+
|
|
|
|
MICROSOFT SURFACE PRO 3 BUTTON DRIVER
|
|
|
|
M: Chen Yu <yu.c.chen@intel.com>
|
|
|
|
L: platform-driver-x86@vger.kernel.org
|
|
|
|
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
|
|
index c51c55204b5f..6fb304da845f 100644
|
|
|
|
--- a/drivers/platform/surface/Kconfig
|
|
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
|
|
@@ -139,6 +139,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 ed12676f06e6..f7187bae1729 100644
|
|
|
|
--- a/drivers/platform/surface/Makefile
|
|
|
|
+++ b/drivers/platform/surface/Makefile
|
|
|
|
@@ -14,4 +14,5 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
|
|
|
|
obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.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/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..0081b01a5b0f
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/platform/surface/surface_platform_profile.c
|
|
|
|
@@ -0,0 +1,190 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+/*
|
|
|
|
+ * Surface Platform Profile / Performance Mode driver for Surface System
|
|
|
|
+ * Aggregator Module (thermal subsystem).
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <asm/unaligned.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/platform_profile.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
+
|
|
|
|
+#include <linux/surface_aggregator/device.h>
|
|
|
|
+
|
|
|
|
+enum ssam_tmp_profile {
|
|
|
|
+ SSAM_TMP_PROFILE_NORMAL = 1,
|
|
|
|
+ SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
|
|
|
|
+ SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
|
|
|
|
+ SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct ssam_tmp_profile_info {
|
|
|
|
+ __le32 profile;
|
|
|
|
+ __le16 unknown1;
|
|
|
|
+ __le16 unknown2;
|
|
|
|
+} __packed;
|
|
|
|
+
|
|
|
|
+struct ssam_tmp_profile_device {
|
|
|
|
+ struct ssam_device *sdev;
|
|
|
|
+ struct platform_profile_handler handler;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
|
|
|
|
+ .target_category = SSAM_SSH_TC_TMP,
|
|
|
|
+ .command_id = 0x02,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
|
|
|
|
+ .target_category = SSAM_SSH_TC_TMP,
|
|
|
|
+ .command_id = 0x03,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
|
|
|
|
+{
|
|
|
|
+ struct ssam_tmp_profile_info info;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
|
|
|
|
+ if (status < 0)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ *p = le32_to_cpu(info.profile);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
|
|
|
|
+{
|
|
|
|
+ __le32 profile_le = cpu_to_le32(p);
|
|
|
|
+
|
|
|
|
+ return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
|
|
|
|
+{
|
|
|
|
+ switch (p) {
|
|
|
|
+ case SSAM_TMP_PROFILE_NORMAL:
|
|
|
|
+ return PLATFORM_PROFILE_BALANCED;
|
|
|
|
+
|
|
|
|
+ case SSAM_TMP_PROFILE_BATTERY_SAVER:
|
|
|
|
+ return PLATFORM_PROFILE_LOW_POWER;
|
|
|
|
+
|
|
|
|
+ case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
|
|
|
|
+ return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
|
|
|
|
+
|
|
|
|
+ case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
|
|
|
|
+ return PLATFORM_PROFILE_PERFORMANCE;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ dev_err(&sdev->dev, "invalid performance profile: %d", p);
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
|
|
|
|
+{
|
|
|
|
+ switch (p) {
|
|
|
|
+ case PLATFORM_PROFILE_LOW_POWER:
|
|
|
|
+ return SSAM_TMP_PROFILE_BATTERY_SAVER;
|
|
|
|
+
|
|
|
|
+ case PLATFORM_PROFILE_BALANCED:
|
|
|
|
+ return SSAM_TMP_PROFILE_NORMAL;
|
|
|
|
+
|
|
|
|
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
|
|
|
|
+ return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
|
|
|
|
+
|
|
|
|
+ case PLATFORM_PROFILE_PERFORMANCE:
|
|
|
|
+ return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ /* This should have already been caught by platform_profile_store(). */
|
|
|
|
+ WARN(true, "unsupported platform profile");
|
|
|
|
+ return -EOPNOTSUPP;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
|
|
|
|
+ enum platform_profile_option *profile)
|
|
|
|
+{
|
|
|
|
+ struct ssam_tmp_profile_device *tpd;
|
|
|
|
+ enum ssam_tmp_profile tp;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
|
|
|
|
+
|
|
|
|
+ status = ssam_tmp_profile_get(tpd->sdev, &tp);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ status = convert_ssam_to_profile(tpd->sdev, tp);
|
|
|
|
+ if (status < 0)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ *profile = status;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
|
|
|
|
+ enum platform_profile_option profile)
|
|
|
|
+{
|
|
|
|
+ struct ssam_tmp_profile_device *tpd;
|
|
|
|
+ int tp;
|
|
|
|
+
|
|
|
|
+ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
|
|
|
|
+
|
|
|
|
+ tp = convert_profile_to_ssam(tpd->sdev, profile);
|
|
|
|
+ if (tp < 0)
|
|
|
|
+ return tp;
|
|
|
|
+
|
|
|
|
+ return ssam_tmp_profile_set(tpd->sdev, tp);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_platform_profile_probe(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ struct ssam_tmp_profile_device *tpd;
|
|
|
|
+
|
|
|
|
+ tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
|
|
|
|
+ if (!tpd)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ tpd->sdev = sdev;
|
|
|
|
+
|
|
|
|
+ tpd->handler.profile_get = ssam_platform_profile_get;
|
|
|
|
+ tpd->handler.profile_set = ssam_platform_profile_set;
|
|
|
|
+
|
|
|
|
+ set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
|
|
|
|
+ set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
|
|
|
|
+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
|
|
|
|
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices);
|
|
|
|
+
|
|
|
|
+ platform_profile_register(&tpd->handler);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void surface_platform_profile_remove(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ platform_profile_remove();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct ssam_device_id ssam_platform_profile_match[] = {
|
|
|
|
+ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) },
|
|
|
|
+ { },
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
|
|
|
|
+
|
|
|
|
+static struct ssam_device_driver surface_platform_profile = {
|
|
|
|
+ .probe = surface_platform_profile_probe,
|
|
|
|
+ .remove = surface_platform_profile_remove,
|
|
|
|
+ .match_table = ssam_platform_profile_match,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "surface_platform_profile",
|
|
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+module_ssam_device_driver(surface_platform_profile);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 2035a53463aa515cd76d615de6229f1ba4c02281 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Thu, 4 Mar 2021 20:05:24 +0100
|
|
|
|
Subject: [PATCH] platform/surface: aggregator: Make SSAM_DEFINE_SYNC_REQUEST_x
|
|
|
|
define static functions
|
|
|
|
|
|
|
|
The SSAM_DEFINE_SYNC_REQUEST_x() macros are intended to reduce
|
|
|
|
boiler-plate code for SSAM request definitions by defining a wrapper
|
|
|
|
function for the specified request. The client device variants of those
|
|
|
|
macros, i.e. SSAM_DEFINE_SYNC_REQUEST_CL_x() in particular rely on the
|
|
|
|
multi-device (MD) variants, e.g.:
|
|
|
|
|
|
|
|
#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) \
|
|
|
|
{ \
|
|
|
|
return __raw_##name(sdev->ctrl, sdev->uid.target, \
|
|
|
|
sdev->uid.instance, ret); \
|
|
|
|
}
|
|
|
|
|
|
|
|
This now creates the problem that it is not possible to declare the
|
|
|
|
generated functions static via
|
|
|
|
|
|
|
|
static SSAM_DEFINE_SYNC_REQUEST_CL_R(...)
|
|
|
|
|
|
|
|
as this will only apply to the function defined by the multi-device
|
|
|
|
macro, i.e. SSAM_DEFINE_SYNC_REQUEST_MD_R(). Thus compiling with
|
|
|
|
`-Wmissing-prototypes' rightfully complains that there is a 'static'
|
|
|
|
keyword missing.
|
|
|
|
|
|
|
|
To solve this, make all SSAM_DEFINE_SYNC_REQUEST_x() macros define
|
|
|
|
static functions. Non-client-device macros are also changed for
|
|
|
|
consistency. In general, we expect those functions to be only used
|
|
|
|
locally in the respective drivers for the corresponding interfaces, so
|
|
|
|
having to define a wrapper function to be able to export this should be
|
|
|
|
the odd case out.
|
|
|
|
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
|
|
Fixes: b78b4982d763 ("platform/surface: Add platform profile driver")
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210304190524.1172197-1-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../driver-api/surface_aggregator/client.rst | 4 +-
|
|
|
|
.../platform/surface/aggregator/controller.c | 10 +--
|
|
|
|
.../surface/surface_aggregator_registry.c | 2 +-
|
|
|
|
.../surface/surface_platform_profile.c | 4 +-
|
|
|
|
include/linux/surface_aggregator/controller.h | 74 +++++++++----------
|
|
|
|
include/linux/surface_aggregator/device.h | 31 ++++----
|
|
|
|
6 files changed, 63 insertions(+), 62 deletions(-)
|
|
|
|
|
|
|
|
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/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
2021-05-19 06:27:58 +00:00
|
|
|
index 89761d3e1a47..88ec47cae5bf 100644
|
2021-05-05 13:57:58 +00:00
|
|
|
--- 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
|
|
|
|
index caee90d135c5..cdb4a95af3e8 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -302,7 +302,7 @@ struct ssam_base_hub {
|
|
|
|
struct ssam_event_notifier notif;
|
|
|
|
};
|
|
|
|
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
|
|
|
|
.target_category = SSAM_SSH_TC_BAS,
|
|
|
|
.target_id = 0x01,
|
|
|
|
.command_id = 0x0d,
|
|
|
|
diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
|
|
|
|
index 0081b01a5b0f..6373d3b5eb7f 100644
|
|
|
|
--- a/drivers/platform/surface/surface_platform_profile.c
|
|
|
|
+++ b/drivers/platform/surface/surface_platform_profile.c
|
|
|
|
@@ -32,12 +32,12 @@ struct ssam_tmp_profile_device {
|
|
|
|
struct platform_profile_handler handler;
|
|
|
|
};
|
|
|
|
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
|
|
|
|
.target_category = SSAM_SSH_TC_TMP,
|
|
|
|
.command_id = 0x02,
|
|
|
|
});
|
|
|
|
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
|
|
|
|
.target_category = SSAM_SSH_TC_TMP,
|
|
|
|
.command_id = 0x03,
|
|
|
|
});
|
|
|
|
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); \
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 349394726da739c934ca9f67fecbd2ffed88e013 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Mon, 8 Mar 2021 19:48:17 +0100
|
|
|
|
Subject: [PATCH] platform/surface: Add DTX driver
|
|
|
|
|
|
|
|
The Microsoft Surface Book series devices consist of a so-called
|
|
|
|
clipboard part (containing the CPU, touchscreen, and primary battery)
|
|
|
|
and a base part (containing keyboard, secondary battery, and optional
|
|
|
|
discrete GPU). These parts can be separated, i.e. the clipboard can be
|
|
|
|
detached and used as tablet.
|
|
|
|
|
|
|
|
This detachment process is initiated by pressing a button. On the
|
|
|
|
Surface Book 2 and 3 (targeted with this commit), the Surface Aggregator
|
|
|
|
Module (i.e. the embedded controller on those devices) attempts to send
|
|
|
|
a notification to any listening client driver and waits for further
|
|
|
|
instructions (i.e. whether the detachment process should continue or be
|
|
|
|
aborted). If it does not receive a response in a certain time-frame, the
|
|
|
|
detachment process (by default) continues and the clipboard can be
|
|
|
|
physically separated. In other words, (by default and) without a driver,
|
|
|
|
the detachment process takes about 10 seconds to complete.
|
|
|
|
|
|
|
|
This commit introduces a driver for this detachment system (called DTX).
|
|
|
|
This driver allows a user-space daemon to control and influence the
|
|
|
|
detachment behavior. Specifically, it forwards any detachment requests
|
|
|
|
to user-space, allows user-space to make such requests itself, and
|
|
|
|
allows handling of those requests. Requests can be handled by either
|
|
|
|
aborting, continuing/allowing, or delaying (i.e. resetting the timeout
|
|
|
|
via a heartbeat commend). The user-space API is implemented via the
|
|
|
|
/dev/surface/dtx miscdevice.
|
|
|
|
|
|
|
|
In addition, user-space can change the default behavior on timeout from
|
|
|
|
allowing detachment to disallowing it, which is useful if the (optional)
|
|
|
|
discrete GPU is in use.
|
|
|
|
|
|
|
|
Furthermore, this driver allows user-space to receive notifications
|
|
|
|
about the state of the base, specifically when it is physically removed
|
|
|
|
(as opposed to detachment requested), in what manner it is connected
|
|
|
|
(i.e. in reverse-/tent-/studio- or laptop-mode), and what type of base
|
|
|
|
is connected. Based on this information, the driver also provides a
|
|
|
|
simple tablet-mode switch (aliasing all modes without keyboard access,
|
|
|
|
i.e. tablet-mode and studio-mode to its reported tablet-mode).
|
|
|
|
|
|
|
|
An implementation of such a user-space daemon, allowing configuration of
|
|
|
|
detachment behavior via scripts (e.g. safely unmounting USB devices
|
|
|
|
connected to the base before continuing) can be found at [1].
|
|
|
|
|
|
|
|
[1]: https://github.com/linux-surface/surface-dtx-daemon
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210308184819.437438-2-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../userspace-api/ioctl/ioctl-number.rst | 2 +
|
|
|
|
MAINTAINERS | 7 +
|
|
|
|
drivers/platform/surface/Kconfig | 16 +
|
|
|
|
drivers/platform/surface/Makefile | 1 +
|
|
|
|
drivers/platform/surface/surface_dtx.c | 1201 +++++++++++++++++
|
|
|
|
include/uapi/linux/surface_aggregator/dtx.h | 146 ++
|
|
|
|
6 files changed, 1373 insertions(+)
|
|
|
|
create mode 100644 drivers/platform/surface/surface_dtx.c
|
|
|
|
create mode 100644 include/uapi/linux/surface_aggregator/dtx.h
|
|
|
|
|
|
|
|
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
|
|
index 599bd4493944..1c28b8ef6677 100644
|
|
|
|
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
|
|
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
|
|
@@ -327,6 +327,8 @@ Code Seq# Include File Comments
|
|
|
|
0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org>
|
|
|
|
0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
|
|
|
|
<mailto:luzmaximilian@gmail.com>
|
|
|
|
+0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
|
|
|
|
+ <mailto:luzmaximilian@gmail.com>
|
|
|
|
0xAA 00-3F linux/uapi/linux/userfaultfd.h
|
|
|
|
0xAB 00-1F linux/nbd.h
|
|
|
|
0xAC 00-1F linux/raw.h
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
|
|
index fce5cdcefc0b..3917e7363520 100644
|
|
|
|
--- a/MAINTAINERS
|
|
|
|
+++ b/MAINTAINERS
|
|
|
|
@@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
|
|
|
|
F: include/linux/cciss*.h
|
|
|
|
F: include/uapi/linux/cciss*.h
|
|
|
|
|
|
|
|
+MICROSOFT SURFACE DTX DRIVER
|
|
|
|
+M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
|
|
+S: Maintained
|
|
|
|
+F: drivers/platform/surface/surface_dtx.c
|
|
|
|
+F: include/uapi/linux/surface_aggregator/dtx.h
|
|
|
|
+
|
|
|
|
MICROSOFT SURFACE GPE LID SUPPORT DRIVER
|
|
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
L: platform-driver-x86@vger.kernel.org
|
|
|
|
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
|
|
index 6fb304da845f..41d67bb250fd 100644
|
|
|
|
--- a/drivers/platform/surface/Kconfig
|
|
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
|
|
@@ -111,6 +111,22 @@ config SURFACE_BOOK1_DGPU_SWITCH
|
|
|
|
This driver provides a sysfs switch to set the power-state of the
|
|
|
|
discrete GPU found on the Microsoft Surface Book 1.
|
|
|
|
|
|
|
|
+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.
|
|
|
|
+
|
|
|
|
config SURFACE_GPE
|
|
|
|
tristate "Surface GPE/Lid Support Driver"
|
|
|
|
depends on DMI
|
|
|
|
diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
|
|
|
|
index f7187bae1729..0cc63440328d 100644
|
|
|
|
--- a/drivers/platform/surface/Makefile
|
|
|
|
+++ b/drivers/platform/surface/Makefile
|
|
|
|
@@ -12,6 +12,7 @@ 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_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.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
|
|
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..1301fab0ea14
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
|
|
@@ -0,0 +1,1201 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+/*
|
|
|
|
+ * Surface Book (gen. 2 and later) detachment system (DTX) driver.
|
|
|
|
+ *
|
|
|
|
+ * Provides a user-space interface to properly handle clipboard/tablet
|
|
|
|
+ * (containing screen and processor) detachment from the base of the device
|
|
|
|
+ * (containing the keyboard and optionally a discrete GPU). Allows to
|
|
|
|
+ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in
|
|
|
|
+ * use), or request detachment via user-space.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <linux/fs.h>
|
|
|
|
+#include <linux/input.h>
|
|
|
|
+#include <linux/ioctl.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/kfifo.h>
|
|
|
|
+#include <linux/kref.h>
|
|
|
|
+#include <linux/miscdevice.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/mutex.h>
|
|
|
|
+#include <linux/platform_device.h>
|
|
|
|
+#include <linux/poll.h>
|
|
|
|
+#include <linux/rwsem.h>
|
|
|
|
+#include <linux/slab.h>
|
|
|
|
+#include <linux/workqueue.h>
|
|
|
|
+
|
|
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
|
|
+#include <linux/surface_aggregator/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(&client->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,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+module_platform_driver(surface_dtx_platform_driver);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..0833aab0d819
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/include/uapi/linux/surface_aggregator/dtx.h
|
|
|
|
@@ -0,0 +1,146 @@
|
|
|
|
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
|
|
|
|
+/*
|
|
|
|
+ * Surface DTX (clipboard detachment system driver) user-space interface.
|
|
|
|
+ *
|
|
|
|
+ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This
|
|
|
|
+ * device allows user-space to control the clipboard detachment process on
|
|
|
|
+ * Surface Book series devices.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
|
|
|
|
+#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
|
|
|
|
+
|
|
|
|
+#include <linux/ioctl.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
+
|
|
|
|
+/* Status/error categories */
|
|
|
|
+#define SDTX_CATEGORY_STATUS 0x0000
|
|
|
|
+#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000
|
|
|
|
+#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000
|
|
|
|
+#define SDTX_CATEGORY_UNKNOWN 0xf000
|
|
|
|
+
|
|
|
|
+#define SDTX_CATEGORY_MASK 0xf000
|
|
|
|
+#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK)
|
|
|
|
+
|
|
|
|
+#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS)
|
|
|
|
+#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR)
|
|
|
|
+#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR)
|
|
|
|
+#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN)
|
|
|
|
+
|
|
|
|
+#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS)
|
|
|
|
+
|
|
|
|
+/* Latch status values */
|
|
|
|
+#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00)
|
|
|
|
+#define SDTX_LATCH_OPENED SDTX_STATUS(0x01)
|
|
|
|
+
|
|
|
|
+/* Base state values */
|
|
|
|
+#define SDTX_BASE_DETACHED SDTX_STATUS(0x00)
|
|
|
|
+#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01)
|
|
|
|
+
|
|
|
|
+/* Runtime errors (non-critical) */
|
|
|
|
+#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01)
|
|
|
|
+#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02)
|
|
|
|
+
|
|
|
|
+/* Hardware errors (critical) */
|
|
|
|
+#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01)
|
|
|
|
+#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02)
|
|
|
|
+#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03)
|
|
|
|
+
|
|
|
|
+/* Base types */
|
|
|
|
+#define SDTX_DEVICE_TYPE_HID 0x0100
|
|
|
|
+#define SDTX_DEVICE_TYPE_SSH 0x0200
|
|
|
|
+
|
|
|
|
+#define SDTX_DEVICE_TYPE_MASK 0x0f00
|
|
|
|
+#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK)
|
|
|
|
+
|
|
|
|
+#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID)
|
|
|
|
+#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH)
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is
|
|
|
|
+ * attached to the base of the device.
|
|
|
|
+ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the
|
|
|
|
+ * device operates as tablet.
|
|
|
|
+ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base
|
|
|
|
+ * and the device operates as laptop.
|
|
|
|
+ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse.
|
|
|
|
+ * The device operates as tablet with keyboard and
|
|
|
|
+ * touchpad deactivated, however, the base battery
|
|
|
|
+ * and, if present in the specific device model, dGPU
|
|
|
|
+ * are available to the system.
|
|
|
|
+ */
|
|
|
|
+enum sdtx_device_mode {
|
|
|
|
+ SDTX_DEVICE_MODE_TABLET = 0x00,
|
|
|
|
+ SDTX_DEVICE_MODE_LAPTOP = 0x01,
|
|
|
|
+ SDTX_DEVICE_MODE_STUDIO = 0x02,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * struct sdtx_event - Event provided by reading from the DTX device file.
|
|
|
|
+ * @length: Length of the event payload, in bytes.
|
|
|
|
+ * @code: Event code, detailing what type of event this is.
|
|
|
|
+ * @data: Payload of the event, containing @length bytes.
|
|
|
|
+ *
|
|
|
|
+ * See &enum sdtx_event_code for currently valid event codes.
|
|
|
|
+ */
|
|
|
|
+struct sdtx_event {
|
|
|
|
+ __u16 length;
|
|
|
|
+ __u16 code;
|
|
|
|
+ __u8 data[];
|
|
|
|
+} __attribute__((__packed__));
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * enum sdtx_event_code - Code describing the type of an event.
|
|
|
|
+ * @SDTX_EVENT_REQUEST: Detachment request event type.
|
|
|
|
+ * @SDTX_EVENT_CANCEL: Cancel detachment process event type.
|
|
|
|
+ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type.
|
|
|
|
+ * @SDTX_EVENT_LATCH_STATUS: Latch status change event type.
|
|
|
|
+ * @SDTX_EVENT_DEVICE_MODE: Device mode change event type.
|
|
|
|
+ *
|
|
|
|
+ * Used in &struct sdtx_event to describe the type of the event. Further event
|
|
|
|
+ * codes are reserved for future use. Any event parser should be able to
|
|
|
|
+ * gracefully handle unknown events, i.e. by simply skipping them.
|
|
|
|
+ *
|
|
|
|
+ * Consult the DTX user-space interface documentation for details regarding
|
|
|
|
+ * the individual event types.
|
|
|
|
+ */
|
|
|
|
+enum sdtx_event_code {
|
|
|
|
+ SDTX_EVENT_REQUEST = 1,
|
|
|
|
+ SDTX_EVENT_CANCEL = 2,
|
|
|
|
+ SDTX_EVENT_BASE_CONNECTION = 3,
|
|
|
|
+ SDTX_EVENT_LATCH_STATUS = 4,
|
|
|
|
+ SDTX_EVENT_DEVICE_MODE = 5,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * struct sdtx_base_info - Describes if and what type of base is connected.
|
|
|
|
+ * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED,
|
|
|
|
+ * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base
|
|
|
|
+ * is attached but low clipboard battery prevents detachment). Other
|
|
|
|
+ * values are currently reserved.
|
|
|
|
+ * @base_id: The type of base connected. Zero if no base is connected.
|
|
|
|
+ */
|
|
|
|
+struct sdtx_base_info {
|
|
|
|
+ __u16 state;
|
|
|
|
+ __u16 base_id;
|
|
|
|
+} __attribute__((__packed__));
|
|
|
|
+
|
|
|
|
+/* IOCTLs */
|
|
|
|
+#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21)
|
|
|
|
+#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22)
|
|
|
|
+
|
|
|
|
+#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23)
|
|
|
|
+#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24)
|
|
|
|
+
|
|
|
|
+#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25)
|
|
|
|
+#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26)
|
|
|
|
+#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27)
|
|
|
|
+#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28)
|
|
|
|
+
|
|
|
|
+#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info)
|
|
|
|
+#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16)
|
|
|
|
+#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16)
|
|
|
|
+
|
|
|
|
+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From a2fa661f97077f34d67468bbc645144f59134fe3 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Mon, 8 Mar 2021 19:48:18 +0100
|
|
|
|
Subject: [PATCH] platform/surface: dtx: Add support for native SSAM devices
|
|
|
|
|
|
|
|
Add support for native SSAM devices to the DTX driver. This allows
|
|
|
|
support for the Surface Book 3, on which the DTX device is not present
|
|
|
|
in ACPI.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210308184819.437438-3-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/platform/surface/Kconfig | 4 ++
|
|
|
|
drivers/platform/surface/surface_dtx.c | 90 +++++++++++++++++++++++++-
|
|
|
|
2 files changed, 93 insertions(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
|
|
index 41d67bb250fd..53beaedefdd1 100644
|
|
|
|
--- a/drivers/platform/surface/Kconfig
|
|
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
|
|
@@ -127,6 +127,10 @@ config SURFACE_DTX
|
|
|
|
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
|
|
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
|
|
index 1301fab0ea14..85451eb94d98 100644
|
|
|
|
--- a/drivers/platform/surface/surface_dtx.c
|
|
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
|
|
@@ -27,6 +27,7 @@
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
|
|
|
|
#include <linux/surface_aggregator/controller.h>
|
|
|
|
+#include <linux/surface_aggregator/device.h>
|
|
|
|
#include <linux/surface_aggregator/dtx.h>
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1194,7 +1195,94 @@ static struct platform_driver surface_dtx_platform_driver = {
|
|
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
-module_platform_driver(surface_dtx_platform_driver);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- SSAM device driver. --------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
|
|
|
|
+
|
|
|
|
+static int surface_dtx_ssam_probe(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ struct sdtx_device *ddev;
|
|
|
|
+
|
|
|
|
+ ddev = sdtx_device_create(&sdev->dev, sdev->ctrl);
|
|
|
|
+ if (IS_ERR(ddev))
|
|
|
|
+ return PTR_ERR(ddev);
|
|
|
|
+
|
|
|
|
+ ssam_device_set_drvdata(sdev, ddev);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void surface_dtx_ssam_remove(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ sdtx_device_destroy(ssam_device_get_drvdata(sdev));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct ssam_device_id surface_dtx_ssam_match[] = {
|
|
|
|
+ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) },
|
|
|
|
+ { },
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match);
|
|
|
|
+
|
|
|
|
+static struct ssam_device_driver surface_dtx_ssam_driver = {
|
|
|
|
+ .probe = surface_dtx_ssam_probe,
|
|
|
|
+ .remove = surface_dtx_ssam_remove,
|
|
|
|
+ .match_table = surface_dtx_ssam_match,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "surface_dtx",
|
|
|
|
+ .pm = &surface_dtx_pm_ops,
|
|
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int ssam_dtx_driver_register(void)
|
|
|
|
+{
|
|
|
|
+ return ssam_device_driver_register(&surface_dtx_ssam_driver);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void ssam_dtx_driver_unregister(void)
|
|
|
|
+{
|
|
|
|
+ ssam_device_driver_unregister(&surface_dtx_ssam_driver);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
|
|
|
|
+
|
|
|
|
+static int ssam_dtx_driver_register(void)
|
|
|
|
+{
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void ssam_dtx_driver_unregister(void)
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Module setup. --------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int __init surface_dtx_init(void)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = ssam_dtx_driver_register();
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ status = platform_driver_register(&surface_dtx_platform_driver);
|
|
|
|
+ if (status)
|
|
|
|
+ ssam_dtx_driver_unregister();
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+module_init(surface_dtx_init);
|
|
|
|
+
|
|
|
|
+static void __exit surface_dtx_exit(void)
|
|
|
|
+{
|
|
|
|
+ platform_driver_unregister(&surface_dtx_platform_driver);
|
|
|
|
+ ssam_dtx_driver_unregister();
|
|
|
|
+}
|
|
|
|
+module_exit(surface_dtx_exit);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From bc764eb22303f1a8b5919abcf7b34fae0083519b Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Mon, 8 Mar 2021 19:48:19 +0100
|
|
|
|
Subject: [PATCH] docs: driver-api: Add Surface DTX driver documentation
|
|
|
|
|
|
|
|
Add documentation for the user-space interface of the Surface DTX
|
|
|
|
(detachment system) driver, used on Microsoft Surface Book series
|
|
|
|
devices.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210308184819.437438-4-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../surface_aggregator/clients/dtx.rst | 718 ++++++++++++++++++
|
|
|
|
.../surface_aggregator/clients/index.rst | 1 +
|
|
|
|
MAINTAINERS | 1 +
|
|
|
|
3 files changed, 720 insertions(+)
|
|
|
|
create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst
|
|
|
|
|
|
|
|
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/MAINTAINERS b/MAINTAINERS
|
|
|
|
index 3917e7363520..da1487d672a8 100644
|
|
|
|
--- a/MAINTAINERS
|
|
|
|
+++ b/MAINTAINERS
|
|
|
|
@@ -11872,6 +11872,7 @@ MICROSOFT SURFACE DTX DRIVER
|
|
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
L: platform-driver-x86@vger.kernel.org
|
|
|
|
S: Maintained
|
|
|
|
+F: Documentation/driver-api/surface_aggregator/clients/dtx.rst
|
|
|
|
F: drivers/platform/surface/surface_dtx.c
|
|
|
|
F: include/uapi/linux/surface_aggregator/dtx.h
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 2bab8e6866b5abd357b15d0941e35500957a8d08 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Wei Yongjun <weiyongjun1@huawei.com>
|
|
|
|
Date: Tue, 9 Mar 2021 13:15:00 +0000
|
|
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Make symbol
|
|
|
|
'ssam_base_hub_group' static
|
|
|
|
|
|
|
|
The sparse tool complains as follows:
|
|
|
|
|
|
|
|
drivers/platform/surface/surface_aggregator_registry.c:355:30: warning:
|
|
|
|
symbol 'ssam_base_hub_group' was not declared. Should it be static?
|
|
|
|
|
|
|
|
This symbol is not used outside of surface_aggregator_registry.c, so this
|
|
|
|
commit marks it static.
|
|
|
|
|
|
|
|
Fixes: 797e78564634 ("platform/surface: aggregator_registry: Add base device hub")
|
|
|
|
Reported-by: Hulk Robot <hulkci@huawei.com>
|
|
|
|
Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
|
|
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210309131500.1885772-1-weiyongjun1@huawei.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/platform/surface/surface_aggregator_registry.c | 2 +-
|
|
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
index cdb4a95af3e8..86cff5fce3cd 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -352,7 +352,7 @@ static struct attribute *ssam_base_hub_attrs[] = {
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
-const struct attribute_group ssam_base_hub_group = {
|
|
|
|
+static const struct attribute_group ssam_base_hub_group = {
|
|
|
|
.attrs = ssam_base_hub_attrs,
|
|
|
|
};
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 13c24e8cf534ee4c35a00486f11b696295181ffc Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Tue, 9 Mar 2021 17:25:50 +0100
|
|
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add support for
|
|
|
|
Surface Pro 7+
|
|
|
|
|
|
|
|
The Surface Pro 7+ is essentially a refresh of the Surface Pro 7 with
|
|
|
|
updated hardware and a new WSID identifier.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210309162550.302161-1-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/platform/surface/surface_aggregator_registry.c | 5 ++++-
|
|
|
|
1 file changed, 4 insertions(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
index 86cff5fce3cd..eccb9d1007cd 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -191,7 +191,7 @@ static const struct software_node *ssam_node_group_sp6[] = {
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
-/* Devices for Surface Pro 7. */
|
|
|
|
+/* 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,
|
|
|
|
@@ -521,6 +521,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|
|
|
/* 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 },
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 14402dfc92f94872ecc684295d7ce91c0fc19ee4 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: kernel test robot <lkp@intel.com>
|
|
|
|
Date: Fri, 19 Mar 2021 13:19:19 +0800
|
|
|
|
Subject: [PATCH] platform/surface: fix semicolon.cocci warnings
|
|
|
|
|
|
|
|
drivers/platform/surface/surface_dtx.c:651:2-3: Unneeded semicolon
|
|
|
|
|
|
|
|
Remove unneeded semicolon.
|
|
|
|
|
|
|
|
Generated by: scripts/coccinelle/misc/semicolon.cocci
|
|
|
|
|
|
|
|
Fixes: 1d609992832e ("platform/surface: Add DTX driver")
|
|
|
|
CC: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
|
|
Signed-off-by: kernel test robot <lkp@intel.com>
|
|
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210319051919.GA39801@ae4f36e4f012
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/platform/surface/surface_dtx.c | 2 +-
|
|
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
|
|
index 85451eb94d98..1fedacf74050 100644
|
|
|
|
--- a/drivers/platform/surface/surface_dtx.c
|
|
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
|
|
@@ -649,7 +649,7 @@ static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event
|
|
|
|
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
- };
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (in->length != len) {
|
|
|
|
dev_err(ddev->dev,
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From eb6b89a681467ca6f9ab63e7b7ded69af2032c30 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Dan Carpenter <dan.carpenter@oracle.com>
|
|
|
|
Date: Fri, 26 Mar 2021 15:28:48 +0300
|
|
|
|
Subject: [PATCH] platform/surface: clean up a variable in surface_dtx_read()
|
|
|
|
|
|
|
|
The "&client->ddev->lock" and "&ddev->lock" are the same thing. Let's
|
|
|
|
use "&ddev->lock" consistently.
|
|
|
|
|
|
|
|
Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
|
|
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/YF3TgCcpcCYl3a//@mwanda
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/platform/surface/surface_dtx.c | 2 +-
|
|
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
|
|
index 1fedacf74050..63ce587e79e3 100644
|
|
|
|
--- a/drivers/platform/surface/surface_dtx.c
|
|
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
|
|
@@ -487,7 +487,7 @@ static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t coun
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
- if (down_read_killable(&client->ddev->lock))
|
|
|
|
+ if (down_read_killable(&ddev->lock))
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
|
|
|
/* Need to check that we're not shut down again. */
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 249d35e4c768ab3204232758bdcc1081512205d2 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Tue, 6 Apr 2021 01:12:22 +0200
|
|
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Give devices time to
|
|
|
|
set up when connecting
|
|
|
|
|
|
|
|
Sometimes, the "base connected" event that we rely on to (re-)attach the
|
|
|
|
device connected to the base is sent a bit too early. When this happens,
|
|
|
|
some devices may not be completely ready yet.
|
|
|
|
|
|
|
|
Specifically, the battery has been observed to report zero-values for
|
|
|
|
things like full charge capacity, which, however, is only loaded once
|
|
|
|
when the driver for that device probes. This can thus result in battery
|
|
|
|
readings being unavailable.
|
|
|
|
|
|
|
|
As we cannot easily and reliably discern between devices that are not
|
|
|
|
ready yet and devices that are not connected (i.e. will never be ready),
|
|
|
|
delay adding these devices. This should give them enough time to set up.
|
|
|
|
|
|
|
|
The delay is set to 2.5 seconds, which should give us a good safety
|
|
|
|
margin based on testing and still be fairly responsive for users.
|
|
|
|
|
|
|
|
To achieve that delay switch to updating via a delayed work struct,
|
|
|
|
which means that we can also get rid of some locking.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/20210405231222.358113-1-luzmaximilian@gmail.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../surface/surface_aggregator_registry.c | 98 ++++++++-----------
|
|
|
|
1 file changed, 40 insertions(+), 58 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
index eccb9d1007cd..685d37a7add1 100644
|
|
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
@@ -13,10 +13,10 @@
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/limits.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
-#include <linux/mutex.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>
|
|
|
|
@@ -287,6 +287,13 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
|
|
|
|
|
|
|
|
/* -- 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,
|
|
|
|
@@ -296,8 +303,8 @@ enum ssam_base_hub_state {
|
|
|
|
struct ssam_base_hub {
|
|
|
|
struct ssam_device *sdev;
|
|
|
|
|
|
|
|
- struct mutex lock; /* Guards state update checks and transitions. */
|
|
|
|
enum ssam_base_hub_state state;
|
|
|
|
+ struct delayed_work update_work;
|
|
|
|
|
|
|
|
struct ssam_event_notifier notif;
|
|
|
|
};
|
|
|
|
@@ -335,11 +342,7 @@ static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attrib
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct ssam_base_hub *hub = dev_get_drvdata(dev);
|
|
|
|
- bool connected;
|
|
|
|
-
|
|
|
|
- mutex_lock(&hub->lock);
|
|
|
|
- connected = hub->state == SSAM_BASE_HUB_CONNECTED;
|
|
|
|
- mutex_unlock(&hub->lock);
|
|
|
|
+ bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
|
|
|
|
|
|
|
|
return sysfs_emit(buf, "%d\n", connected);
|
|
|
|
}
|
|
|
|
@@ -356,16 +359,20 @@ static const struct attribute_group ssam_base_hub_group = {
|
|
|
|
.attrs = ssam_base_hub_attrs,
|
|
|
|
};
|
|
|
|
|
|
|
|
-static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
|
|
|
|
+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;
|
|
|
|
|
|
|
|
- lockdep_assert_held(&hub->lock);
|
|
|
|
+ status = ssam_base_hub_query_state(hub, &state);
|
|
|
|
+ if (status)
|
|
|
|
+ return;
|
|
|
|
|
|
|
|
- if (hub->state == new)
|
|
|
|
- return 0;
|
|
|
|
- hub->state = new;
|
|
|
|
+ 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);
|
|
|
|
@@ -374,51 +381,28 @@ static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_
|
|
|
|
|
|
|
|
if (status)
|
|
|
|
dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
|
|
|
|
-
|
|
|
|
- return status;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static int ssam_base_hub_update(struct ssam_base_hub *hub)
|
|
|
|
-{
|
|
|
|
- enum ssam_base_hub_state state;
|
|
|
|
- int status;
|
|
|
|
-
|
|
|
|
- mutex_lock(&hub->lock);
|
|
|
|
-
|
|
|
|
- status = ssam_base_hub_query_state(hub, &state);
|
|
|
|
- if (!status)
|
|
|
|
- status = __ssam_base_hub_update(hub, state);
|
|
|
|
-
|
|
|
|
- mutex_unlock(&hub->lock);
|
|
|
|
- return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
|
|
{
|
|
|
|
- struct ssam_base_hub *hub;
|
|
|
|
- struct ssam_device *sdev;
|
|
|
|
- enum ssam_base_hub_state new;
|
|
|
|
-
|
|
|
|
- hub = container_of(nf, struct ssam_base_hub, notif);
|
|
|
|
- sdev = hub->sdev;
|
|
|
|
+ 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(&sdev->dev, "unexpected payload size: %u\n",
|
|
|
|
- event->length);
|
|
|
|
+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
- if (event->data[0])
|
|
|
|
- new = SSAM_BASE_HUB_CONNECTED;
|
|
|
|
- else
|
|
|
|
- new = SSAM_BASE_HUB_DISCONNECTED;
|
|
|
|
+ /*
|
|
|
|
+ * 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;
|
|
|
|
|
|
|
|
- mutex_lock(&hub->lock);
|
|
|
|
- __ssam_base_hub_update(hub, new);
|
|
|
|
- mutex_unlock(&hub->lock);
|
|
|
|
+ schedule_delayed_work(&hub->update_work, delay);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
|
|
|
|
@@ -430,7 +414,10 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam
|
|
|
|
|
|
|
|
static int __maybe_unused ssam_base_hub_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
- return ssam_base_hub_update(dev_get_drvdata(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);
|
|
|
|
|
|
|
|
@@ -443,8 +430,6 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
|
|
|
|
if (!hub)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
- mutex_init(&hub->lock);
|
|
|
|
-
|
|
|
|
hub->sdev = sdev;
|
|
|
|
hub->state = SSAM_BASE_HUB_UNINITIALIZED;
|
|
|
|
|
|
|
|
@@ -456,27 +441,25 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
|
|
|
|
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)
|
|
|
|
- goto err_register;
|
|
|
|
-
|
|
|
|
- status = ssam_base_hub_update(hub);
|
|
|
|
- if (status)
|
|
|
|
- goto err_update;
|
|
|
|
+ return status;
|
|
|
|
|
|
|
|
status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
|
|
|
|
if (status)
|
|
|
|
- goto err_update;
|
|
|
|
+ goto err;
|
|
|
|
|
|
|
|
+ schedule_delayed_work(&hub->update_work, 0);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
-err_update:
|
|
|
|
+err:
|
|
|
|
ssam_notifier_unregister(sdev->ctrl, &hub->notif);
|
|
|
|
+ cancel_delayed_work_sync(&hub->update_work);
|
|
|
|
ssam_hub_remove_devices(&sdev->dev);
|
|
|
|
-err_register:
|
|
|
|
- mutex_destroy(&hub->lock);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
@@ -487,9 +470,8 @@ static void ssam_base_hub_remove(struct ssam_device *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);
|
|
|
|
-
|
|
|
|
- mutex_destroy(&hub->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct ssam_device_id ssam_base_hub_match[] = {
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From a6be18a2e6a37d75b5fffd7fc19912d5b9a56944 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Barry Song <song.bao.hua@hisilicon.com>
|
|
|
|
Date: Wed, 3 Mar 2021 11:49:15 +1300
|
|
|
|
Subject: [PATCH] genirq: Add IRQF_NO_AUTOEN for request_irq/nmi()
|
|
|
|
|
|
|
|
Many drivers don't want interrupts enabled automatically via request_irq().
|
|
|
|
So they are handling this issue by either way of the below two:
|
|
|
|
|
|
|
|
(1)
|
|
|
|
irq_set_status_flags(irq, IRQ_NOAUTOEN);
|
|
|
|
request_irq(dev, irq...);
|
|
|
|
|
|
|
|
(2)
|
|
|
|
request_irq(dev, irq...);
|
|
|
|
disable_irq(irq);
|
|
|
|
|
|
|
|
The code in the second way is silly and unsafe. In the small time gap
|
|
|
|
between request_irq() and disable_irq(), interrupts can still come.
|
|
|
|
|
|
|
|
The code in the first way is safe though it's subobtimal.
|
|
|
|
|
|
|
|
Add a new IRQF_NO_AUTOEN flag which can be handed in by drivers to
|
|
|
|
request_irq() and request_nmi(). It prevents the automatic enabling of the
|
|
|
|
requested interrupt/nmi in the same safe way as #1 above. With that the
|
|
|
|
various usage sites of #1 and #2 above can be simplified and corrected.
|
|
|
|
|
|
|
|
Signed-off-by: Barry Song <song.bao.hua@hisilicon.com>
|
|
|
|
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
|
|
|
|
Signed-off-by: Ingo Molnar <mingo@kernel.org>
|
|
|
|
Cc: dmitry.torokhov@gmail.com
|
|
|
|
Link: https://lore.kernel.org/r/20210302224916.13980-2-song.bao.hua@hisilicon.com
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
include/linux/interrupt.h | 4 ++++
|
|
|
|
kernel/irq/manage.c | 11 +++++++++--
|
|
|
|
2 files changed, 13 insertions(+), 2 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
|
|
|
|
index 967e25767153..76f1161a441a 100644
|
|
|
|
--- a/include/linux/interrupt.h
|
|
|
|
+++ b/include/linux/interrupt.h
|
|
|
|
@@ -61,6 +61,9 @@
|
|
|
|
* interrupt handler after suspending interrupts. For system
|
|
|
|
* wakeup devices users need to implement wakeup detection in
|
|
|
|
* their interrupt handlers.
|
|
|
|
+ * IRQF_NO_AUTOEN - Don't enable IRQ or NMI automatically when users request it.
|
|
|
|
+ * Users will enable it explicitly by enable_irq() or enable_nmi()
|
|
|
|
+ * later.
|
|
|
|
*/
|
|
|
|
#define IRQF_SHARED 0x00000080
|
|
|
|
#define IRQF_PROBE_SHARED 0x00000100
|
|
|
|
@@ -74,6 +77,7 @@
|
|
|
|
#define IRQF_NO_THREAD 0x00010000
|
|
|
|
#define IRQF_EARLY_RESUME 0x00020000
|
|
|
|
#define IRQF_COND_SUSPEND 0x00040000
|
|
|
|
+#define IRQF_NO_AUTOEN 0x00080000
|
|
|
|
|
|
|
|
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
|
|
|
|
|
|
|
|
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
|
|
|
|
index 21ea370fccda..49288e941365 100644
|
|
|
|
--- a/kernel/irq/manage.c
|
|
|
|
+++ b/kernel/irq/manage.c
|
|
|
|
@@ -1697,7 +1697,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
|
|
|
irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
|
|
|
|
}
|
|
|
|
|
|
|
|
- if (irq_settings_can_autoenable(desc)) {
|
|
|
|
+ if (!(new->flags & IRQF_NO_AUTOEN) &&
|
|
|
|
+ irq_settings_can_autoenable(desc)) {
|
|
|
|
irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
@@ -2090,10 +2091,15 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
|
|
|
|
* which interrupt is which (messes up the interrupt freeing
|
|
|
|
* logic etc).
|
|
|
|
*
|
|
|
|
+ * Also shared interrupts do not go well with disabling auto enable.
|
|
|
|
+ * The sharing interrupt might request it while it's still disabled
|
|
|
|
+ * and then wait for interrupts forever.
|
|
|
|
+ *
|
|
|
|
* Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
|
|
|
|
* it cannot be set along with IRQF_NO_SUSPEND.
|
|
|
|
*/
|
|
|
|
if (((irqflags & IRQF_SHARED) && !dev_id) ||
|
|
|
|
+ ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) ||
|
|
|
|
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
|
|
|
|
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
|
|
|
|
return -EINVAL;
|
|
|
|
@@ -2249,7 +2255,8 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
|
|
|
|
|
|
|
|
desc = irq_to_desc(irq);
|
|
|
|
|
|
|
|
- if (!desc || irq_settings_can_autoenable(desc) ||
|
|
|
|
+ if (!desc || (irq_settings_can_autoenable(desc) &&
|
|
|
|
+ !(irqflags & IRQF_NO_AUTOEN)) ||
|
|
|
|
!irq_settings_can_request(desc) ||
|
|
|
|
WARN_ON(irq_settings_is_per_cpu_devid(desc)) ||
|
|
|
|
!irq_supports_nmi(desc))
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 92d7d1462eb709aed1a4620f0a2cf1b976f7a1b1 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Tian Tao <tiantao6@hisilicon.com>
|
|
|
|
Date: Wed, 7 Apr 2021 15:00:52 +0800
|
|
|
|
Subject: [PATCH] platform/surface: aggregator: move to use request_irq by
|
|
|
|
IRQF_NO_AUTOEN flag
|
|
|
|
|
|
|
|
disable_irq() after request_irq() still has a time gap in which
|
|
|
|
interrupts can come. request_irq() with IRQF_NO_AUTOEN flag will
|
|
|
|
disable IRQ auto-enable because of requesting.
|
|
|
|
|
|
|
|
this patch is made base on "add IRQF_NO_AUTOEN for request_irq" which
|
|
|
|
is being merged: https://lore.kernel.org/patchwork/patch/1388765/
|
|
|
|
|
|
|
|
Signed-off-by: Tian Tao <tiantao6@hisilicon.com>
|
|
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Link: https://lore.kernel.org/r/1617778852-26492-1-git-send-email-tiantao6@hisilicon.com
|
|
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/platform/surface/aggregator/controller.c | 4 ++--
|
|
|
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
2021-05-19 06:27:58 +00:00
|
|
|
index 88ec47cae5bf..69e86cd599d3 100644
|
2021-05-05 13:57:58 +00:00
|
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
|
|
@@ -2483,7 +2483,8 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
|
|
|
|
* interrupt, and let the SAM resume callback during the controller
|
|
|
|
* resume process clear it.
|
|
|
|
*/
|
|
|
|
- const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING;
|
|
|
|
+ const int irqf = IRQF_SHARED | IRQF_ONESHOT |
|
|
|
|
+ IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
|
|
|
|
|
|
|
|
gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
|
|
|
|
if (IS_ERR(gpiod))
|
|
|
|
@@ -2501,7 +2502,6 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
ctrl->irq.num = irq;
|
|
|
|
- disable_irq(ctrl->irq.num);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 5d87d26872336e61301f895710f7dcfd7e67c070 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Wed, 5 May 2021 14:53:45 +0200
|
|
|
|
Subject: [PATCH] platform/surface: aggregator: Do not mark interrupt as shared
|
|
|
|
|
|
|
|
Having both IRQF_NO_AUTOEN and IRQF_SHARED set causes
|
|
|
|
request_threaded_irq() to return with -EINVAL (see comment in flag
|
|
|
|
validation in that function). As the interrupt is currently not shared
|
|
|
|
between multiple devices, drop the IRQF_SHARED flag.
|
|
|
|
|
|
|
|
Fixes: 507cf5a2f1e2 ("platform/surface: aggregator: move to use request_irq by IRQF_NO_AUTOEN flag")
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/platform/surface/aggregator/controller.c | 3 +--
|
|
|
|
1 file changed, 1 insertion(+), 2 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
2021-05-19 06:27:58 +00:00
|
|
|
index 69e86cd599d3..8a70df60142c 100644
|
2021-05-05 13:57:58 +00:00
|
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
|
|
@@ -2483,8 +2483,7 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
|
|
|
|
* interrupt, and let the SAM resume callback during the controller
|
|
|
|
* resume process clear it.
|
|
|
|
*/
|
|
|
|
- const int irqf = IRQF_SHARED | IRQF_ONESHOT |
|
|
|
|
- IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
|
|
|
|
+ const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
|
|
|
|
|
|
|
|
gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
|
|
|
|
if (IS_ERR(gpiod))
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From d8c132bd854d4950aa39dd6f7522b8d5cd0810c8 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Wed, 10 Mar 2021 23:53:28 +0100
|
|
|
|
Subject: [PATCH] HID: Add support for Surface Aggregator Module HID transport
|
|
|
|
MIME-Version: 1.0
|
|
|
|
Content-Type: text/plain; charset=UTF-8
|
|
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
|
|
|
|
Add a HID transport driver to support integrated HID devices on newer
|
|
|
|
Microsoft Surface models (specifically 7th-generation, i.e. Surface
|
|
|
|
Laptop 3, Surface Book 3, and later).
|
|
|
|
|
|
|
|
On those models, the internal keyboard and touchpad (as well as some
|
|
|
|
other HID devices with currently unknown function) are connected via the
|
|
|
|
generic HID subsystem (TC=0x15) of the Surface System Aggregator Module
|
|
|
|
(SSAM). This subsystem provides a generic HID transport layer, support
|
|
|
|
for which is implemented by this driver.
|
|
|
|
|
|
|
|
Co-developed-by: Blaž Hrastnik <blaz@mxxn.io>
|
|
|
|
Signed-off-by: Blaž Hrastnik <blaz@mxxn.io>
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
MAINTAINERS | 7 +
|
|
|
|
drivers/hid/Kconfig | 2 +
|
|
|
|
drivers/hid/Makefile | 2 +
|
|
|
|
drivers/hid/surface-hid/Kconfig | 28 +++
|
|
|
|
drivers/hid/surface-hid/Makefile | 6 +
|
|
|
|
drivers/hid/surface-hid/surface_hid.c | 253 +++++++++++++++++++
|
|
|
|
drivers/hid/surface-hid/surface_hid_core.c | 272 +++++++++++++++++++++
|
|
|
|
drivers/hid/surface-hid/surface_hid_core.h | 77 ++++++
|
|
|
|
8 files changed, 647 insertions(+)
|
|
|
|
create mode 100644 drivers/hid/surface-hid/Kconfig
|
|
|
|
create mode 100644 drivers/hid/surface-hid/Makefile
|
|
|
|
create mode 100644 drivers/hid/surface-hid/surface_hid.c
|
|
|
|
create mode 100644 drivers/hid/surface-hid/surface_hid_core.c
|
|
|
|
create mode 100644 drivers/hid/surface-hid/surface_hid_core.h
|
|
|
|
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
|
|
index da1487d672a8..f54b22333ec6 100644
|
|
|
|
--- a/MAINTAINERS
|
|
|
|
+++ b/MAINTAINERS
|
|
|
|
@@ -11891,6 +11891,13 @@ S: Maintained
|
|
|
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
|
|
|
|
F: drivers/platform/surface/
|
|
|
|
|
|
|
|
+MICROSOFT SURFACE HID TRANSPORT DRIVER
|
|
|
|
+M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+L: linux-input@vger.kernel.org
|
|
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
|
|
+S: Maintained
|
|
|
|
+F: drivers/hid/surface-hid/
|
|
|
|
+
|
|
|
|
MICROSOFT SURFACE HOT-PLUG DRIVER
|
|
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
L: platform-driver-x86@vger.kernel.org
|
|
|
|
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
|
|
|
|
index 786b71ef7738..26e06097ba08 100644
|
|
|
|
--- a/drivers/hid/Kconfig
|
|
|
|
+++ b/drivers/hid/Kconfig
|
|
|
|
@@ -1206,4 +1206,6 @@ source "drivers/hid/intel-ish-hid/Kconfig"
|
|
|
|
|
|
|
|
source "drivers/hid/amd-sfh-hid/Kconfig"
|
|
|
|
|
|
|
|
+source "drivers/hid/surface-hid/Kconfig"
|
|
|
|
+
|
|
|
|
endmenu
|
|
|
|
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
|
|
|
|
index c4f6d5c613dc..1044ed238856 100644
|
|
|
|
--- a/drivers/hid/Makefile
|
|
|
|
+++ b/drivers/hid/Makefile
|
|
|
|
@@ -145,3 +145,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
|
|
|
|
obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
|
|
|
|
|
|
|
|
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
|
|
|
|
+
|
|
|
|
+obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
|
|
|
|
diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..642c7f0e64fe
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/hid/surface-hid/Kconfig
|
|
|
|
@@ -0,0 +1,28 @@
|
|
|
|
+# SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+menu "Surface System Aggregator Module HID support"
|
|
|
|
+ depends on SURFACE_AGGREGATOR
|
|
|
|
+ depends on INPUT
|
|
|
|
+
|
|
|
|
+config SURFACE_HID
|
|
|
|
+ tristate "HID transport driver for Surface System Aggregator Module"
|
|
|
|
+ depends on SURFACE_AGGREGATOR_REGISTRY
|
|
|
|
+ select SURFACE_HID_CORE
|
|
|
|
+ help
|
|
|
|
+ Driver to support integrated HID devices on newer Microsoft Surface
|
|
|
|
+ models.
|
|
|
|
+
|
|
|
|
+ This driver provides support for the HID transport protocol provided
|
|
|
|
+ by the Surface Aggregator Module (i.e. the embedded controller) on
|
|
|
|
+ 7th-generation Microsoft Surface devices, i.e. Surface Book 3 and
|
|
|
|
+ Surface Laptop 3. On those models, it is mainly used to connect the
|
|
|
|
+ integrated touchpad and keyboard.
|
|
|
|
+
|
|
|
|
+ Say M or Y here, if you want support for integrated HID devices, i.e.
|
|
|
|
+ integrated touchpad and keyboard, on 7th generation Microsoft Surface
|
|
|
|
+ models.
|
|
|
|
+
|
|
|
|
+endmenu
|
|
|
|
+
|
|
|
|
+config SURFACE_HID_CORE
|
|
|
|
+ tristate
|
|
|
|
+ select HID
|
|
|
|
diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..62fc04632d3d
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/hid/surface-hid/Makefile
|
|
|
|
@@ -0,0 +1,6 @@
|
|
|
|
+# SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+#
|
|
|
|
+# Makefile - Surface System Aggregator Module (SSAM) HID transport driver.
|
|
|
|
+#
|
|
|
|
+obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
|
|
|
|
+obj-$(CONFIG_SURFACE_HID) += surface_hid.o
|
|
|
|
diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..3477b31611ae
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/hid/surface-hid/surface_hid.c
|
|
|
|
@@ -0,0 +1,253 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+/*
|
|
|
|
+ * Surface System Aggregator Module (SSAM) HID transport driver for the
|
|
|
|
+ * generic HID interface (HID/TC=0x15 subsystem). Provides support for
|
|
|
|
+ * integrated HID devices on Surface Laptop 3, Book 3, and later.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2019-2021 Blaž Hrastnik <blaz@mxxn.io>,
|
|
|
|
+ * Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <asm/unaligned.h>
|
|
|
|
+#include <linux/hid.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
+
|
|
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
|
|
+#include <linux/surface_aggregator/device.h>
|
|
|
|
+
|
|
|
|
+#include "surface_hid_core.h"
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- SAM interface. -------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+struct surface_hid_buffer_slice {
|
|
|
|
+ __u8 entry;
|
|
|
|
+ __le32 offset;
|
|
|
|
+ __le32 length;
|
|
|
|
+ __u8 end;
|
|
|
|
+ __u8 data[];
|
|
|
|
+} __packed;
|
|
|
|
+
|
|
|
|
+static_assert(sizeof(struct surface_hid_buffer_slice) == 10);
|
|
|
|
+
|
|
|
|
+enum surface_hid_cid {
|
|
|
|
+ SURFACE_HID_CID_OUTPUT_REPORT = 0x01,
|
|
|
|
+ SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02,
|
|
|
|
+ SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03,
|
|
|
|
+ SURFACE_HID_CID_GET_DESCRIPTOR = 0x04,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76];
|
|
|
|
+ struct surface_hid_buffer_slice *slice;
|
|
|
|
+ struct ssam_request rqst;
|
|
|
|
+ struct ssam_response rsp;
|
|
|
|
+ u32 buffer_len, offset, length;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Note: The 0x76 above has been chosen because that's what's used by
|
|
|
|
+ * the Windows driver. Together with the header, this leads to a 128
|
|
|
|
+ * byte payload in total.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice);
|
|
|
|
+
|
|
|
|
+ rqst.target_category = shid->uid.category;
|
|
|
|
+ rqst.target_id = shid->uid.target;
|
|
|
|
+ rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR;
|
|
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
|
|
+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
|
|
|
+ rqst.length = sizeof(struct surface_hid_buffer_slice);
|
|
|
|
+ rqst.payload = buffer;
|
|
|
|
+
|
|
|
|
+ rsp.capacity = ARRAY_SIZE(buffer);
|
|
|
|
+ rsp.pointer = buffer;
|
|
|
|
+
|
|
|
|
+ slice = (struct surface_hid_buffer_slice *)buffer;
|
|
|
|
+ slice->entry = entry;
|
|
|
|
+ slice->end = 0;
|
|
|
|
+
|
|
|
|
+ offset = 0;
|
|
|
|
+ length = buffer_len;
|
|
|
|
+
|
|
|
|
+ while (!slice->end && offset < len) {
|
|
|
|
+ put_unaligned_le32(offset, &slice->offset);
|
|
|
|
+ put_unaligned_le32(length, &slice->length);
|
|
|
|
+
|
|
|
|
+ rsp.length = 0;
|
|
|
|
+
|
|
|
|
+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp,
|
|
|
|
+ sizeof(*slice));
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ offset = get_unaligned_le32(&slice->offset);
|
|
|
|
+ length = get_unaligned_le32(&slice->length);
|
|
|
|
+
|
|
|
|
+ /* Don't mess stuff up in case we receive garbage. */
|
|
|
|
+ if (length > buffer_len || offset > len)
|
|
|
|
+ return -EPROTO;
|
|
|
|
+
|
|
|
|
+ if (offset + length > len)
|
|
|
|
+ length = len - offset;
|
|
|
|
+
|
|
|
|
+ memcpy(buf + offset, &slice->data[0], length);
|
|
|
|
+
|
|
|
|
+ offset += length;
|
|
|
|
+ length = buffer_len;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (offset != len) {
|
|
|
|
+ dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n",
|
|
|
|
+ offset, len);
|
|
|
|
+ return -EPROTO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature,
|
|
|
|
+ u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ struct ssam_request rqst;
|
|
|
|
+ u8 cid;
|
|
|
|
+
|
|
|
|
+ if (feature)
|
|
|
|
+ cid = SURFACE_HID_CID_SET_FEATURE_REPORT;
|
|
|
|
+ else
|
|
|
|
+ cid = SURFACE_HID_CID_OUTPUT_REPORT;
|
|
|
|
+
|
|
|
|
+ rqst.target_category = shid->uid.category;
|
|
|
|
+ rqst.target_id = shid->uid.target;
|
|
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
|
|
+ rqst.command_id = cid;
|
|
|
|
+ rqst.flags = 0;
|
|
|
|
+ rqst.length = len;
|
|
|
|
+ rqst.payload = buf;
|
|
|
|
+
|
|
|
|
+ buf[0] = rprt_id;
|
|
|
|
+
|
|
|
|
+ return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ struct ssam_request rqst;
|
|
|
|
+ struct ssam_response rsp;
|
|
|
|
+
|
|
|
|
+ rqst.target_category = shid->uid.category;
|
|
|
|
+ rqst.target_id = shid->uid.target;
|
|
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
|
|
+ rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
|
|
|
|
+ rqst.flags = 0;
|
|
|
|
+ rqst.length = sizeof(rprt_id);
|
|
|
|
+ rqst.payload = &rprt_id;
|
|
|
|
+
|
|
|
|
+ rsp.capacity = len;
|
|
|
|
+ rsp.length = 0;
|
|
|
|
+ rsp.pointer = buf;
|
|
|
|
+
|
|
|
|
+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
|
|
|
|
+
|
|
|
|
+ if (event->command_id != 0x00)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
|
|
|
|
+ return SSAM_NOTIF_HANDLED;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Transport driver. ----------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len);
|
|
|
|
+ return status >= 0 ? len : status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = ssam_hid_get_raw_report(shid, rprt_id, buf, len);
|
|
|
|
+ return status >= 0 ? len : status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len);
|
|
|
|
+ return status >= 0 ? len : status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Driver setup. --------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int surface_hid_probe(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *shid;
|
|
|
|
+
|
|
|
|
+ shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL);
|
|
|
|
+ if (!shid)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ shid->dev = &sdev->dev;
|
|
|
|
+ shid->ctrl = sdev->ctrl;
|
|
|
|
+ shid->uid = sdev->uid;
|
|
|
|
+
|
|
|
|
+ shid->notif.base.priority = 1;
|
|
|
|
+ shid->notif.base.fn = ssam_hid_event_fn;
|
|
|
|
+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG;
|
|
|
|
+ shid->notif.event.id.target_category = sdev->uid.category;
|
|
|
|
+ shid->notif.event.id.instance = sdev->uid.instance;
|
|
|
|
+ shid->notif.event.mask = SSAM_EVENT_MASK_STRICT;
|
|
|
|
+ shid->notif.event.flags = 0;
|
|
|
|
+
|
|
|
|
+ shid->ops.get_descriptor = ssam_hid_get_descriptor;
|
|
|
|
+ shid->ops.output_report = shid_output_report;
|
|
|
|
+ shid->ops.get_feature_report = shid_get_feature_report;
|
|
|
|
+ shid->ops.set_feature_report = shid_set_feature_report;
|
|
|
|
+
|
|
|
|
+ ssam_device_set_drvdata(sdev, shid);
|
|
|
|
+ return surface_hid_device_add(shid);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void surface_hid_remove(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ surface_hid_device_destroy(ssam_device_get_drvdata(sdev));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct ssam_device_id surface_hid_match[] = {
|
|
|
|
+ { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) },
|
|
|
|
+ { },
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(ssam, surface_hid_match);
|
|
|
|
+
|
|
|
|
+static struct ssam_device_driver surface_hid_driver = {
|
|
|
|
+ .probe = surface_hid_probe,
|
|
|
|
+ .remove = surface_hid_remove,
|
|
|
|
+ .match_table = surface_hid_match,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "surface_hid",
|
|
|
|
+ .pm = &surface_hid_pm_ops,
|
|
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+module_ssam_device_driver(surface_hid_driver);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Blaž Hrastnik <blaz@mxxn.io>");
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..7b27ec392232
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/hid/surface-hid/surface_hid_core.c
|
|
|
|
@@ -0,0 +1,272 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+/*
|
|
|
|
+ * Common/core components for the Surface System Aggregator Module (SSAM) HID
|
|
|
|
+ * transport driver. Provides support for integrated HID devices on Microsoft
|
|
|
|
+ * Surface models.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <asm/unaligned.h>
|
|
|
|
+#include <linux/hid.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
+#include <linux/usb/ch9.h>
|
|
|
|
+
|
|
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
|
|
+
|
|
|
|
+#include "surface_hid_core.h"
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Device descriptor access. --------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
|
|
|
|
+ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) {
|
|
|
|
+ dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n",
|
|
|
|
+ shid->hid_desc.desc_len, sizeof(shid->hid_desc));
|
|
|
|
+ return -EPROTO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (shid->hid_desc.desc_type != HID_DT_HID) {
|
|
|
|
+ dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n",
|
|
|
|
+ shid->hid_desc.desc_type, HID_DT_HID);
|
|
|
|
+ return -EPROTO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (shid->hid_desc.num_descriptors != 1) {
|
|
|
|
+ dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n",
|
|
|
|
+ shid->hid_desc.num_descriptors);
|
|
|
|
+ return -EPROTO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) {
|
|
|
|
+ dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n",
|
|
|
|
+ shid->hid_desc.report_desc_type, HID_DT_REPORT);
|
|
|
|
+ return -EPROTO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
|
|
|
|
+ (u8 *)&shid->attrs, sizeof(shid->attrs));
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) {
|
|
|
|
+ dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n",
|
|
|
|
+ get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs));
|
|
|
|
+ return -EPROTO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Transport driver (common). -------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int surface_hid_start(struct hid_device *hid)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *shid = hid->driver_data;
|
|
|
|
+
|
|
|
|
+ return ssam_notifier_register(shid->ctrl, &shid->notif);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void surface_hid_stop(struct hid_device *hid)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *shid = hid->driver_data;
|
|
|
|
+
|
|
|
|
+ /* Note: This call will log errors for us, so ignore them here. */
|
|
|
|
+ ssam_notifier_unregister(shid->ctrl, &shid->notif);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_hid_open(struct hid_device *hid)
|
|
|
|
+{
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void surface_hid_close(struct hid_device *hid)
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_hid_parse(struct hid_device *hid)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *shid = hid->driver_data;
|
|
|
|
+ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len);
|
|
|
|
+ u8 *buf;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ buf = kzalloc(len, GFP_KERNEL);
|
|
|
|
+ if (!buf)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len);
|
|
|
|
+ if (!status)
|
|
|
|
+ status = hid_parse_report(hid, buf, len);
|
|
|
|
+
|
|
|
|
+ kfree(buf);
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf,
|
|
|
|
+ size_t len, unsigned char rtype, int reqtype)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *shid = hid->driver_data;
|
|
|
|
+
|
|
|
|
+ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
|
|
|
|
+ return shid->ops.output_report(shid, reportnum, buf, len);
|
|
|
|
+
|
|
|
|
+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT)
|
|
|
|
+ return shid->ops.get_feature_report(shid, reportnum, buf, len);
|
|
|
|
+
|
|
|
|
+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT)
|
|
|
|
+ return shid->ops.set_feature_report(shid, reportnum, buf, len);
|
|
|
|
+
|
|
|
|
+ return -EIO;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct hid_ll_driver surface_hid_ll_driver = {
|
|
|
|
+ .start = surface_hid_start,
|
|
|
|
+ .stop = surface_hid_stop,
|
|
|
|
+ .open = surface_hid_open,
|
|
|
|
+ .close = surface_hid_close,
|
|
|
|
+ .parse = surface_hid_parse,
|
|
|
|
+ .raw_request = surface_hid_raw_request,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Common device setup. -------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+int surface_hid_device_add(struct surface_hid_device *shid)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = surface_hid_load_hid_descriptor(shid);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ status = surface_hid_load_device_attributes(shid);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ shid->hid = hid_allocate_device();
|
|
|
|
+ if (IS_ERR(shid->hid))
|
|
|
|
+ return PTR_ERR(shid->hid);
|
|
|
|
+
|
|
|
|
+ shid->hid->dev.parent = shid->dev;
|
|
|
|
+ shid->hid->bus = BUS_HOST;
|
|
|
|
+ shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
|
|
|
|
+ shid->hid->product = cpu_to_le16(shid->attrs.product);
|
|
|
|
+ shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
|
|
|
|
+ shid->hid->country = shid->hid_desc.country_code;
|
|
|
|
+
|
|
|
|
+ snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
|
|
|
|
+ shid->hid->vendor, shid->hid->product);
|
|
|
|
+
|
|
|
|
+ strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys));
|
|
|
|
+
|
|
|
|
+ shid->hid->driver_data = shid;
|
|
|
|
+ shid->hid->ll_driver = &surface_hid_ll_driver;
|
|
|
|
+
|
|
|
|
+ status = hid_add_device(shid->hid);
|
|
|
|
+ if (status)
|
|
|
|
+ hid_destroy_device(shid->hid);
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL_GPL(surface_hid_device_add);
|
|
|
|
+
|
|
|
|
+void surface_hid_device_destroy(struct surface_hid_device *shid)
|
|
|
|
+{
|
|
|
|
+ hid_destroy_device(shid->hid);
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- PM ops. --------------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
|
|
+
|
|
|
|
+static int surface_hid_suspend(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
|
|
+
|
|
|
|
+ if (d->hid->driver && d->hid->driver->suspend)
|
|
|
|
+ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_hid_resume(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
|
|
+
|
|
|
|
+ if (d->hid->driver && d->hid->driver->resume)
|
|
|
|
+ return d->hid->driver->resume(d->hid);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_hid_freeze(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
|
|
+
|
|
|
|
+ if (d->hid->driver && d->hid->driver->suspend)
|
|
|
|
+ return d->hid->driver->suspend(d->hid, PMSG_FREEZE);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_hid_poweroff(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
|
|
+
|
|
|
|
+ if (d->hid->driver && d->hid->driver->suspend)
|
|
|
|
+ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_hid_restore(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
|
|
+
|
|
|
|
+ if (d->hid->driver && d->hid->driver->reset_resume)
|
|
|
|
+ return d->hid->driver->reset_resume(d->hid);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const struct dev_pm_ops surface_hid_pm_ops = {
|
|
|
|
+ .freeze = surface_hid_freeze,
|
|
|
|
+ .thaw = surface_hid_resume,
|
|
|
|
+ .suspend = surface_hid_suspend,
|
|
|
|
+ .resume = surface_hid_resume,
|
|
|
|
+ .poweroff = surface_hid_poweroff,
|
|
|
|
+ .restore = surface_hid_restore,
|
|
|
|
+};
|
|
|
|
+EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
|
|
|
|
+
|
|
|
|
+#else /* CONFIG_PM_SLEEP */
|
|
|
|
+
|
|
|
|
+const struct dev_pm_ops surface_hid_pm_ops = { };
|
|
|
|
+EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
|
|
|
|
+
|
|
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..4b1a7b57e035
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/hid/surface-hid/surface_hid_core.h
|
|
|
|
@@ -0,0 +1,77 @@
|
|
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
|
|
+/*
|
|
|
|
+ * Common/core components for the Surface System Aggregator Module (SSAM) HID
|
|
|
|
+ * transport driver. Provides support for integrated HID devices on Microsoft
|
|
|
|
+ * Surface models.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#ifndef SURFACE_HID_CORE_H
|
|
|
|
+#define SURFACE_HID_CORE_H
|
|
|
|
+
|
|
|
|
+#include <linux/hid.h>
|
|
|
|
+#include <linux/pm.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
+
|
|
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
|
|
+#include <linux/surface_aggregator/device.h>
|
|
|
|
+
|
|
|
|
+enum surface_hid_descriptor_entry {
|
|
|
|
+ SURFACE_HID_DESC_HID = 0,
|
|
|
|
+ SURFACE_HID_DESC_REPORT = 1,
|
|
|
|
+ SURFACE_HID_DESC_ATTRS = 2,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct surface_hid_descriptor {
|
|
|
|
+ __u8 desc_len; /* = 9 */
|
|
|
|
+ __u8 desc_type; /* = HID_DT_HID */
|
|
|
|
+ __le16 hid_version;
|
|
|
|
+ __u8 country_code;
|
|
|
|
+ __u8 num_descriptors; /* = 1 */
|
|
|
|
+
|
|
|
|
+ __u8 report_desc_type; /* = HID_DT_REPORT */
|
|
|
|
+ __le16 report_desc_len;
|
|
|
|
+} __packed;
|
|
|
|
+
|
|
|
|
+static_assert(sizeof(struct surface_hid_descriptor) == 9);
|
|
|
|
+
|
|
|
|
+struct surface_hid_attributes {
|
|
|
|
+ __le32 length;
|
|
|
|
+ __le16 vendor;
|
|
|
|
+ __le16 product;
|
|
|
|
+ __le16 version;
|
|
|
|
+ __u8 _unknown[22];
|
|
|
|
+} __packed;
|
|
|
|
+
|
|
|
|
+static_assert(sizeof(struct surface_hid_attributes) == 32);
|
|
|
|
+
|
|
|
|
+struct surface_hid_device;
|
|
|
|
+
|
|
|
|
+struct surface_hid_device_ops {
|
|
|
|
+ int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len);
|
|
|
|
+ int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
|
|
|
|
+ int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
|
|
|
|
+ int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct surface_hid_device {
|
|
|
|
+ struct device *dev;
|
|
|
|
+ struct ssam_controller *ctrl;
|
|
|
|
+ struct ssam_device_uid uid;
|
|
|
|
+
|
|
|
|
+ struct surface_hid_descriptor hid_desc;
|
|
|
|
+ struct surface_hid_attributes attrs;
|
|
|
|
+
|
|
|
|
+ struct ssam_event_notifier notif;
|
|
|
|
+ struct hid_device *hid;
|
|
|
|
+
|
|
|
|
+ struct surface_hid_device_ops ops;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+int surface_hid_device_add(struct surface_hid_device *shid);
|
|
|
|
+void surface_hid_device_destroy(struct surface_hid_device *shid);
|
|
|
|
+
|
|
|
|
+extern const struct dev_pm_ops surface_hid_pm_ops;
|
|
|
|
+
|
|
|
|
+#endif /* SURFACE_HID_CORE_H */
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 340f05cab02d45f1b87906e007328b8b6bf29a51 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Wed, 10 Mar 2021 23:53:29 +0100
|
|
|
|
Subject: [PATCH] HID: surface-hid: Add support for legacy keyboard interface
|
|
|
|
|
|
|
|
Add support for the legacy keyboard (KBD/TC=0x08) HID transport layer of
|
|
|
|
the Surface System Aggregator Module (SSAM) to the Surface HID driver.
|
|
|
|
On Surface Laptops 1 and 2, this interface is used to connect the
|
|
|
|
integrated keyboard.
|
|
|
|
|
|
|
|
Note that this subsystem interface essentially provides a limited HID
|
|
|
|
transport layer. In contrast to the generic HID interface (TC=0x15) used
|
|
|
|
on newer Surface models, this interface only allows (as far as we know)
|
|
|
|
for a single device to be connected and is otherwise severely limited in
|
|
|
|
terms of support for feature- and output-reports. Specifically, only
|
|
|
|
caps-lock-LED output-reports and a single read-only feature-report are
|
|
|
|
supported.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/hid/surface-hid/Kconfig | 14 ++
|
|
|
|
drivers/hid/surface-hid/Makefile | 1 +
|
|
|
|
drivers/hid/surface-hid/surface_kbd.c | 300 ++++++++++++++++++++++++++
|
|
|
|
3 files changed, 315 insertions(+)
|
|
|
|
create mode 100644 drivers/hid/surface-hid/surface_kbd.c
|
|
|
|
|
|
|
|
diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
|
|
|
|
index 642c7f0e64fe..7ce9b5d641eb 100644
|
|
|
|
--- a/drivers/hid/surface-hid/Kconfig
|
|
|
|
+++ b/drivers/hid/surface-hid/Kconfig
|
|
|
|
@@ -21,6 +21,20 @@ config SURFACE_HID
|
|
|
|
integrated touchpad and keyboard, on 7th generation Microsoft Surface
|
|
|
|
models.
|
|
|
|
|
|
|
|
+config SURFACE_KBD
|
|
|
|
+ tristate "HID keyboard transport driver for Surface System Aggregator Module"
|
|
|
|
+ select SURFACE_HID_CORE
|
|
|
|
+ help
|
|
|
|
+ Driver to support HID keyboards on Surface Laptop 1 and 2 devices.
|
|
|
|
+
|
|
|
|
+ This driver provides support for the HID transport protocol provided
|
|
|
|
+ by the Surface Aggregator Module (i.e. the embedded controller) on
|
|
|
|
+ Microsoft Surface Laptops 1 and 2. It is used to connect the
|
|
|
|
+ integrated keyboard on those devices.
|
|
|
|
+
|
|
|
|
+ Say M or Y here, if you want support for the integrated keyboard on
|
|
|
|
+ Microsoft Surface Laptops 1 and 2.
|
|
|
|
+
|
|
|
|
endmenu
|
|
|
|
|
|
|
|
config SURFACE_HID_CORE
|
|
|
|
diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
|
|
|
|
index 62fc04632d3d..4ae11cf09b25 100644
|
|
|
|
--- a/drivers/hid/surface-hid/Makefile
|
|
|
|
+++ b/drivers/hid/surface-hid/Makefile
|
|
|
|
@@ -4,3 +4,4 @@
|
|
|
|
#
|
|
|
|
obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
|
|
|
|
obj-$(CONFIG_SURFACE_HID) += surface_hid.o
|
|
|
|
+obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o
|
|
|
|
diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..0635341bc517
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/hid/surface-hid/surface_kbd.c
|
|
|
|
@@ -0,0 +1,300 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+/*
|
|
|
|
+ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy
|
|
|
|
+ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the
|
|
|
|
+ * integrated HID keyboard on Surface Laptops 1 and 2.
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <asm/unaligned.h>
|
|
|
|
+#include <linux/hid.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/platform_device.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
+
|
|
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
|
|
+
|
|
|
|
+#include "surface_hid_core.h"
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- SAM interface (KBD). -------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */
|
|
|
|
+
|
|
|
|
+enum surface_kbd_cid {
|
|
|
|
+ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00,
|
|
|
|
+ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01,
|
|
|
|
+ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03,
|
|
|
|
+ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04,
|
|
|
|
+ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ struct ssam_request rqst;
|
|
|
|
+ struct ssam_response rsp;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ rqst.target_category = shid->uid.category;
|
|
|
|
+ rqst.target_id = shid->uid.target;
|
|
|
|
+ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR;
|
|
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
|
|
+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
|
|
|
+ rqst.length = sizeof(entry);
|
|
|
|
+ rqst.payload = &entry;
|
|
|
|
+
|
|
|
|
+ rsp.capacity = len;
|
|
|
|
+ rsp.length = 0;
|
|
|
|
+ rsp.pointer = buf;
|
|
|
|
+
|
|
|
|
+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if (rsp.length != len) {
|
|
|
|
+ dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n",
|
|
|
|
+ rsp.length, len);
|
|
|
|
+ return -EPROTO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value)
|
|
|
|
+{
|
|
|
|
+ struct ssam_request rqst;
|
|
|
|
+ u8 value_u8 = value;
|
|
|
|
+
|
|
|
|
+ rqst.target_category = shid->uid.category;
|
|
|
|
+ rqst.target_id = shid->uid.target;
|
|
|
|
+ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED;
|
|
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
|
|
+ rqst.flags = 0;
|
|
|
|
+ rqst.length = sizeof(value_u8);
|
|
|
|
+ rqst.payload = &value_u8;
|
|
|
|
+
|
|
|
|
+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ struct ssam_request rqst;
|
|
|
|
+ struct ssam_response rsp;
|
|
|
|
+ u8 payload = 0;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ rqst.target_category = shid->uid.category;
|
|
|
|
+ rqst.target_id = shid->uid.target;
|
|
|
|
+ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT;
|
|
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
|
|
+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
|
|
|
+ rqst.length = sizeof(payload);
|
|
|
|
+ rqst.payload = &payload;
|
|
|
|
+
|
|
|
|
+ rsp.capacity = len;
|
|
|
|
+ rsp.length = 0;
|
|
|
|
+ rsp.pointer = buf;
|
|
|
|
+
|
|
|
|
+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if (rsp.length != len) {
|
|
|
|
+ dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n",
|
|
|
|
+ rsp.length, len);
|
|
|
|
+ return -EPROTO;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool ssam_kbd_is_input_event(const struct ssam_event *event)
|
|
|
|
+{
|
|
|
|
+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC)
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS)
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
|
|
+{
|
|
|
|
+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Check against device UID manually, as registry and device target
|
|
|
|
+ * category doesn't line up.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ if (shid->uid.category != event->target_category)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ if (shid->uid.target != event->target_id)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ if (shid->uid.instance != event->instance_id)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ if (!ssam_kbd_is_input_event(event))
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
|
|
|
|
+ return SSAM_NOTIF_HANDLED;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Transport driver (KBD). ----------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ struct hid_field *field;
|
|
|
|
+ unsigned int offset, size;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ /* Get LED field. */
|
|
|
|
+ field = hidinput_get_led_field(hid);
|
|
|
|
+ if (!field)
|
|
|
|
+ return -ENOENT;
|
|
|
|
+
|
|
|
|
+ /* Check if we got the correct report. */
|
|
|
|
+ if (len != hid_report_len(field->report))
|
|
|
|
+ return -ENOENT;
|
|
|
|
+
|
|
|
|
+ if (rprt_id != field->report->id)
|
|
|
|
+ return -ENOENT;
|
|
|
|
+
|
|
|
|
+ /* Get caps lock LED index. */
|
|
|
|
+ for (i = 0; i < field->report_count; i++)
|
|
|
|
+ if ((field->usage[i].hid & 0xffff) == 0x02)
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ if (i == field->report_count)
|
|
|
|
+ return -ENOENT;
|
|
|
|
+
|
|
|
|
+ /* Extract value. */
|
|
|
|
+ size = field->report_size;
|
|
|
|
+ offset = field->report_offset + i * size;
|
|
|
|
+ return !!hid_field_extract(hid, buf + 1, size, offset);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ int caps_led;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len);
|
|
|
|
+ if (caps_led < 0)
|
|
|
|
+ return -EIO; /* Only caps LED output reports are supported. */
|
|
|
|
+
|
|
|
|
+ status = ssam_kbd_set_caps_led(shid, caps_led);
|
|
|
|
+ if (status < 0)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ return len;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ u8 report[KBD_FEATURE_REPORT_SIZE];
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * The keyboard only has a single hard-coded read-only feature report
|
|
|
|
+ * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its
|
|
|
|
+ * report ID against the requested one.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ if (len < ARRAY_SIZE(report))
|
|
|
|
+ return -ENOSPC;
|
|
|
|
+
|
|
|
|
+ status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report));
|
|
|
|
+ if (status < 0)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if (rprt_id != report[0])
|
|
|
|
+ return -ENOENT;
|
|
|
|
+
|
|
|
|
+ memcpy(buf, report, ARRAY_SIZE(report));
|
|
|
|
+ return len;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ /* Not supported. See skbd_get_feature_report() for details. */
|
|
|
|
+ return -EIO;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Driver setup. --------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int surface_kbd_probe(struct platform_device *pdev)
|
|
|
|
+{
|
|
|
|
+ struct ssam_controller *ctrl;
|
|
|
|
+ struct surface_hid_device *shid;
|
|
|
|
+
|
|
|
|
+ /* Add device link to EC. */
|
|
|
|
+ ctrl = ssam_client_bind(&pdev->dev);
|
|
|
|
+ if (IS_ERR(ctrl))
|
|
|
|
+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
|
|
|
|
+
|
|
|
|
+ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL);
|
|
|
|
+ if (!shid)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ shid->dev = &pdev->dev;
|
|
|
|
+ shid->ctrl = ctrl;
|
|
|
|
+
|
|
|
|
+ shid->uid.domain = SSAM_DOMAIN_SERIALHUB;
|
|
|
|
+ shid->uid.category = SSAM_SSH_TC_KBD;
|
|
|
|
+ shid->uid.target = 2;
|
|
|
|
+ shid->uid.instance = 0;
|
|
|
|
+ shid->uid.function = 0;
|
|
|
|
+
|
|
|
|
+ shid->notif.base.priority = 1;
|
|
|
|
+ shid->notif.base.fn = ssam_kbd_event_fn;
|
|
|
|
+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
|
|
|
|
+ shid->notif.event.id.target_category = shid->uid.category;
|
|
|
|
+ shid->notif.event.id.instance = shid->uid.instance;
|
|
|
|
+ shid->notif.event.mask = SSAM_EVENT_MASK_NONE;
|
|
|
|
+ shid->notif.event.flags = 0;
|
|
|
|
+
|
|
|
|
+ shid->ops.get_descriptor = ssam_kbd_get_descriptor;
|
|
|
|
+ shid->ops.output_report = skbd_output_report;
|
|
|
|
+ shid->ops.get_feature_report = skbd_get_feature_report;
|
|
|
|
+ shid->ops.set_feature_report = skbd_set_feature_report;
|
|
|
|
+
|
|
|
|
+ platform_set_drvdata(pdev, shid);
|
|
|
|
+ return surface_hid_device_add(shid);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int surface_kbd_remove(struct platform_device *pdev)
|
|
|
|
+{
|
|
|
|
+ surface_hid_device_destroy(platform_get_drvdata(pdev));
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct acpi_device_id surface_kbd_match[] = {
|
|
|
|
+ { "MSHW0096" },
|
|
|
|
+ { },
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
|
|
|
|
+
|
|
|
|
+static struct platform_driver surface_kbd_driver = {
|
|
|
|
+ .probe = surface_kbd_probe,
|
|
|
|
+ .remove = surface_kbd_remove,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "surface_keyboard",
|
|
|
|
+ .acpi_match_table = surface_kbd_match,
|
|
|
|
+ .pm = &surface_hid_pm_ops,
|
|
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+module_platform_driver(surface_kbd_driver);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From cb0c6ca32fa6eef32a3de17d43d065e5ad45a40b Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Fri, 23 Apr 2021 00:51:22 +0200
|
|
|
|
Subject: [PATCH] HID: surface-hid: Fix integer endian conversion
|
|
|
|
|
|
|
|
We want to convert from 16 bit (unsigned) little endian values contained
|
|
|
|
in a packed struct to CPU native endian values here, not the other way
|
|
|
|
around. So replace cpu_to_le16() with get_unaligned_le16(), using the
|
|
|
|
latter instead of le16_to_cpu() to acknowledge that we are reading from
|
|
|
|
a packed struct.
|
|
|
|
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
|
|
Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport")
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/hid/surface-hid/surface_hid_core.c | 6 +++---
|
|
|
|
1 file changed, 3 insertions(+), 3 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
|
|
|
|
index 7b27ec392232..5571e74abe91 100644
|
|
|
|
--- a/drivers/hid/surface-hid/surface_hid_core.c
|
|
|
|
+++ b/drivers/hid/surface-hid/surface_hid_core.c
|
|
|
|
@@ -168,9 +168,9 @@ int surface_hid_device_add(struct surface_hid_device *shid)
|
|
|
|
|
|
|
|
shid->hid->dev.parent = shid->dev;
|
|
|
|
shid->hid->bus = BUS_HOST;
|
|
|
|
- shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
|
|
|
|
- shid->hid->product = cpu_to_le16(shid->attrs.product);
|
|
|
|
- shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
|
|
|
|
+ shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor);
|
|
|
|
+ shid->hid->product = get_unaligned_le16(&shid->attrs.product);
|
|
|
|
+ shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version);
|
|
|
|
shid->hid->country = shid->hid_desc.country_code;
|
|
|
|
|
|
|
|
snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From b66f48a5c7a971a5fc0f41d5c96620f25c2df81d Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Tue, 6 Apr 2021 01:41:25 +0200
|
|
|
|
Subject: [PATCH] power: supply: Add battery driver for Surface Aggregator
|
|
|
|
Module
|
|
|
|
|
|
|
|
On newer Microsoft Surface models (specifically 7th-generation, i.e.
|
|
|
|
Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go),
|
|
|
|
battery and AC status/information is no longer handled via standard ACPI
|
|
|
|
devices, but instead directly via the Surface System Aggregator Module
|
|
|
|
(SSAM), i.e. the embedded controller on those devices.
|
|
|
|
|
|
|
|
While on previous generation models, battery status is also handled via
|
|
|
|
SSAM, an ACPI shim was present to translate the standard ACPI battery
|
|
|
|
interface to SSAM requests. The SSAM interface itself, which is modeled
|
|
|
|
closely after the ACPI interface, has not changed.
|
|
|
|
|
|
|
|
This commit introduces a new SSAM client device driver to support
|
|
|
|
battery status/information via the aforementioned interface on said
|
|
|
|
Surface models. It is in parts based on the standard ACPI battery
|
|
|
|
driver.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
.../ABI/testing/sysfs-class-power-surface | 15 +
|
|
|
|
MAINTAINERS | 7 +
|
|
|
|
drivers/power/supply/Kconfig | 16 +
|
|
|
|
drivers/power/supply/Makefile | 1 +
|
|
|
|
drivers/power/supply/surface_battery.c | 865 ++++++++++++++++++
|
|
|
|
5 files changed, 904 insertions(+)
|
|
|
|
create mode 100644 Documentation/ABI/testing/sysfs-class-power-surface
|
|
|
|
create mode 100644 drivers/power/supply/surface_battery.c
|
|
|
|
|
|
|
|
diff --git a/Documentation/ABI/testing/sysfs-class-power-surface b/Documentation/ABI/testing/sysfs-class-power-surface
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..79cde4dcf2f5
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/Documentation/ABI/testing/sysfs-class-power-surface
|
|
|
|
@@ -0,0 +1,15 @@
|
|
|
|
+What: /sys/class/power_supply/<supply_name>/alarm
|
|
|
|
+Date: April 2021
|
|
|
|
+KernelVersion: 5.13
|
|
|
|
+Contact: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+Description:
|
|
|
|
+ Battery trip point. When the remaining battery capacity crosses this
|
|
|
|
+ value in either direction, the system will be notified and if
|
|
|
|
+ necessary woken.
|
|
|
|
+
|
|
|
|
+ Set to zero to clear/disable.
|
|
|
|
+
|
|
|
|
+ Access: Read, Write
|
|
|
|
+
|
|
|
|
+ Valid values: In micro-Wh or micro-Ah, depending on the power unit
|
|
|
|
+ of the battery
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
|
|
index f54b22333ec6..7ee93b732270 100644
|
|
|
|
--- a/MAINTAINERS
|
|
|
|
+++ b/MAINTAINERS
|
|
|
|
@@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
|
|
|
|
F: include/linux/cciss*.h
|
|
|
|
F: include/uapi/linux/cciss*.h
|
|
|
|
|
|
|
|
+MICROSOFT SURFACE BATTERY AND AC DRIVERS
|
|
|
|
+M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+L: linux-pm@vger.kernel.org
|
|
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
|
|
+S: Maintained
|
|
|
|
+F: drivers/power/supply/surface_battery.c
|
|
|
|
+
|
|
|
|
MICROSOFT SURFACE DTX DRIVER
|
|
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
L: platform-driver-x86@vger.kernel.org
|
|
|
|
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
|
|
|
|
index 006b95eca673..cebeff10d543 100644
|
|
|
|
--- a/drivers/power/supply/Kconfig
|
|
|
|
+++ b/drivers/power/supply/Kconfig
|
|
|
|
@@ -801,4 +801,20 @@ config BATTERY_ACER_A500
|
|
|
|
help
|
|
|
|
Say Y to include support for Acer Iconia Tab A500 battery fuel gauge.
|
|
|
|
|
|
|
|
+config BATTERY_SURFACE
|
|
|
|
+ tristate "Battery driver for 7th-generation Microsoft Surface devices"
|
|
|
|
+ depends on SURFACE_AGGREGATOR_REGISTRY
|
|
|
|
+ help
|
|
|
|
+ Driver for battery devices connected via/managed by the Surface System
|
|
|
|
+ Aggregator Module (SSAM).
|
|
|
|
+
|
|
|
|
+ This driver provides battery-information and -status support for
|
|
|
|
+ Surface devices where said data is not exposed via the standard ACPI
|
|
|
|
+ devices. On those models (7th-generation), battery-information is
|
|
|
|
+ instead handled directly via SSAM client devices and this driver.
|
|
|
|
+
|
|
|
|
+ Say M or Y here to include battery status support for 7th-generation
|
|
|
|
+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
|
|
|
|
+ Surface Book 3, and Surface Laptop Go.
|
|
|
|
+
|
|
|
|
endif # POWER_SUPPLY
|
|
|
|
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
|
|
|
|
index 5e5fdbbef531..134041538d2c 100644
|
|
|
|
--- a/drivers/power/supply/Makefile
|
|
|
|
+++ b/drivers/power/supply/Makefile
|
|
|
|
@@ -101,3 +101,4 @@ obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
|
|
|
|
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
|
|
|
|
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
|
|
|
|
obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
|
|
|
|
+obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
|
|
|
|
diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..4116dd839ecd
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/power/supply/surface_battery.c
|
|
|
|
@@ -0,0 +1,865 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+/*
|
|
|
|
+ * Battery driver for 7th-generation Microsoft Surface devices via Surface
|
|
|
|
+ * System Aggregator Module (SSAM).
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <asm/unaligned.h>
|
|
|
|
+#include <linux/jiffies.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/mutex.h>
|
|
|
|
+#include <linux/power_supply.h>
|
|
|
|
+#include <linux/sysfs.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
+#include <linux/workqueue.h>
|
|
|
|
+
|
|
|
|
+#include <linux/surface_aggregator/device.h>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- SAM interface. -------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+enum sam_event_cid_bat {
|
|
|
|
+ SAM_EVENT_CID_BAT_BIX = 0x15,
|
|
|
|
+ SAM_EVENT_CID_BAT_BST = 0x16,
|
|
|
|
+ SAM_EVENT_CID_BAT_ADP = 0x17,
|
|
|
|
+ SAM_EVENT_CID_BAT_PROT = 0x18,
|
|
|
|
+ SAM_EVENT_CID_BAT_DPTF = 0x53,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+enum sam_battery_sta {
|
|
|
|
+ SAM_BATTERY_STA_OK = 0x0f,
|
|
|
|
+ SAM_BATTERY_STA_PRESENT = 0x10,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+enum sam_battery_state {
|
|
|
|
+ SAM_BATTERY_STATE_DISCHARGING = BIT(0),
|
|
|
|
+ SAM_BATTERY_STATE_CHARGING = BIT(1),
|
|
|
|
+ SAM_BATTERY_STATE_CRITICAL = BIT(2),
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+enum sam_battery_power_unit {
|
|
|
|
+ SAM_BATTERY_POWER_UNIT_mW = 0,
|
|
|
|
+ SAM_BATTERY_POWER_UNIT_mA = 1,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Equivalent to data returned in ACPI _BIX method, revision 0. */
|
|
|
|
+struct spwr_bix {
|
|
|
|
+ u8 revision;
|
|
|
|
+ __le32 power_unit;
|
|
|
|
+ __le32 design_cap;
|
|
|
|
+ __le32 last_full_charge_cap;
|
|
|
|
+ __le32 technology;
|
|
|
|
+ __le32 design_voltage;
|
|
|
|
+ __le32 design_cap_warn;
|
|
|
|
+ __le32 design_cap_low;
|
|
|
|
+ __le32 cycle_count;
|
|
|
|
+ __le32 measurement_accuracy;
|
|
|
|
+ __le32 max_sampling_time;
|
|
|
|
+ __le32 min_sampling_time;
|
|
|
|
+ __le32 max_avg_interval;
|
|
|
|
+ __le32 min_avg_interval;
|
|
|
|
+ __le32 bat_cap_granularity_1;
|
|
|
|
+ __le32 bat_cap_granularity_2;
|
|
|
|
+ __u8 model[21];
|
|
|
|
+ __u8 serial[11];
|
|
|
|
+ __u8 type[5];
|
|
|
|
+ __u8 oem_info[21];
|
|
|
|
+} __packed;
|
|
|
|
+
|
|
|
|
+static_assert(sizeof(struct spwr_bix) == 119);
|
|
|
|
+
|
|
|
|
+/* Equivalent to data returned in ACPI _BST method. */
|
|
|
|
+struct spwr_bst {
|
|
|
|
+ __le32 state;
|
|
|
|
+ __le32 present_rate;
|
|
|
|
+ __le32 remaining_cap;
|
|
|
|
+ __le32 present_voltage;
|
|
|
|
+} __packed;
|
|
|
|
+
|
|
|
|
+static_assert(sizeof(struct spwr_bst) == 16);
|
|
|
|
+
|
|
|
|
+#define SPWR_BIX_REVISION 0
|
|
|
|
+#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff
|
|
|
|
+
|
|
|
|
+/* Get battery status (_STA) */
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
|
|
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
|
|
+ .command_id = 0x01,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+/* Get battery static information (_BIX). */
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, {
|
|
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
|
|
+ .command_id = 0x02,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+/* Get battery dynamic information (_BST). */
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, {
|
|
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
|
|
+ .command_id = 0x03,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+/* Set battery trip point (_BTP). */
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, {
|
|
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
|
|
+ .command_id = 0x04,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Device structures. ---------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+struct spwr_psy_properties {
|
|
|
|
+ const char *name;
|
|
|
|
+ struct ssam_event_registry registry;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct spwr_battery_device {
|
|
|
|
+ struct ssam_device *sdev;
|
|
|
|
+
|
|
|
|
+ char name[32];
|
|
|
|
+ struct power_supply *psy;
|
|
|
|
+ struct power_supply_desc psy_desc;
|
|
|
|
+
|
|
|
|
+ struct delayed_work update_work;
|
|
|
|
+
|
|
|
|
+ struct ssam_event_notifier notif;
|
|
|
|
+
|
|
|
|
+ struct mutex lock; /* Guards access to state data below. */
|
|
|
|
+ unsigned long timestamp;
|
|
|
|
+
|
|
|
|
+ __le32 sta;
|
|
|
|
+ struct spwr_bix bix;
|
|
|
|
+ struct spwr_bst bst;
|
|
|
|
+ u32 alarm;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Module parameters. ---------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static unsigned int cache_time = 1000;
|
|
|
|
+module_param(cache_time, uint, 0644);
|
|
|
|
+MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]");
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- State management. ----------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Delay for battery update quirk. See spwr_external_power_changed() below
|
|
|
|
+ * for more details.
|
|
|
|
+ */
|
|
|
|
+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000)
|
|
|
|
+
|
|
|
|
+static bool spwr_battery_present(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_load_sta(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_load_bix(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (!spwr_battery_present(bat))
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix);
|
|
|
|
+
|
|
|
|
+ /* Enforce NULL terminated strings in case anything goes wrong... */
|
|
|
|
+ bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0;
|
|
|
|
+ bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0;
|
|
|
|
+ bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0;
|
|
|
|
+ bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0;
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_load_bst(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (!spwr_battery_present(bat))
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value)
|
|
|
|
+{
|
|
|
|
+ __le32 value_le = cpu_to_le32(value);
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ bat->alarm = value;
|
|
|
|
+ return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached)
|
|
|
|
+{
|
|
|
|
+ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time);
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline))
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_load_sta(bat);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_load_bst(bat);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ bat->timestamp = jiffies;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&bat->lock);
|
|
|
|
+ status = spwr_battery_update_bst_unlocked(bat, cached);
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_load_sta(bat);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_load_bix(bat);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_load_bst(bat);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if (bat->bix.revision != SPWR_BIX_REVISION)
|
|
|
|
+ dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision);
|
|
|
|
+
|
|
|
|
+ bat->timestamp = jiffies;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap);
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ full_cap = get_unaligned_le32(&bat->bix.design_cap);
|
|
|
|
+
|
|
|
|
+ return full_cap;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool spwr_battery_is_full(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ u32 state = get_unaligned_le32(&bat->bst.state);
|
|
|
|
+ u32 full_cap = sprw_battery_get_full_cap_safe(bat);
|
|
|
|
+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 &&
|
|
|
|
+ remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN &&
|
|
|
|
+ remaining_cap >= full_cap &&
|
|
|
|
+ state == 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_recheck_full(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ bool present;
|
|
|
|
+ u32 unit;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&bat->lock);
|
|
|
|
+ unit = get_unaligned_le32(&bat->bix.power_unit);
|
|
|
|
+ present = spwr_battery_present(bat);
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_update_bix_unlocked(bat);
|
|
|
|
+ if (status)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ /* If battery has been attached, (re-)initialize alarm. */
|
|
|
|
+ if (!present && spwr_battery_present(bat)) {
|
|
|
|
+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
|
|
|
|
+ if (status)
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Warn if the unit has changed. This is something we genuinely don't
|
|
|
|
+ * expect to happen, so make this a big warning. If it does, we'll
|
|
|
|
+ * need to add support for it.
|
|
|
|
+ */
|
|
|
|
+ WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit));
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (!status)
|
|
|
|
+ power_supply_changed(bat->psy);
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_recheck_status(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_update_bst(bat, false);
|
|
|
|
+ if (!status)
|
|
|
|
+ power_supply_changed(bat->psy);
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
|
|
+{
|
|
|
|
+ struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
|
|
|
|
+ event->command_id, event->instance_id, event->target_id);
|
|
|
|
+
|
|
|
|
+ switch (event->command_id) {
|
|
|
|
+ case SAM_EVENT_CID_BAT_BIX:
|
|
|
|
+ status = spwr_battery_recheck_full(bat);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case SAM_EVENT_CID_BAT_BST:
|
|
|
|
+ status = spwr_battery_recheck_status(bat);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case SAM_EVENT_CID_BAT_PROT:
|
|
|
|
+ /*
|
|
|
|
+ * TODO: Implement support for battery protection status change
|
|
|
|
+ * event.
|
|
|
|
+ */
|
|
|
|
+ status = 0;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case SAM_EVENT_CID_BAT_DPTF:
|
|
|
|
+ /*
|
|
|
|
+ * TODO: Implement support for DPTF event.
|
|
|
|
+ */
|
|
|
|
+ status = 0;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void spwr_battery_update_bst_workfn(struct work_struct *work)
|
|
|
|
+{
|
|
|
|
+ struct delayed_work *dwork = to_delayed_work(work);
|
|
|
|
+ struct spwr_battery_device *bat;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ bat = container_of(dwork, struct spwr_battery_device, update_work);
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_update_bst(bat, false);
|
|
|
|
+ if (status) {
|
|
|
|
+ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ power_supply_changed(bat->psy);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void spwr_external_power_changed(struct power_supply *psy)
|
|
|
|
+{
|
|
|
|
+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Handle battery update quirk: When the battery is fully charged (or
|
|
|
|
+ * charged up to the limit imposed by the UEFI battery limit) and the
|
|
|
|
+ * adapter is plugged in or removed, the EC does not send a separate
|
|
|
|
+ * event for the state (charging/discharging) change. Furthermore it
|
|
|
|
+ * may take some time until the state is updated on the battery.
|
|
|
|
+ * Schedule an update to solve this.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Properties. ----------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static const enum power_supply_property spwr_battery_props_chg[] = {
|
|
|
|
+ POWER_SUPPLY_PROP_STATUS,
|
|
|
|
+ POWER_SUPPLY_PROP_PRESENT,
|
|
|
|
+ POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
|
|
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
|
|
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
|
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
|
|
+ POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
|
|
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
|
|
|
|
+ POWER_SUPPLY_PROP_CHARGE_FULL,
|
|
|
|
+ POWER_SUPPLY_PROP_CHARGE_NOW,
|
|
|
|
+ POWER_SUPPLY_PROP_CAPACITY,
|
|
|
|
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
|
|
|
+ POWER_SUPPLY_PROP_MODEL_NAME,
|
|
|
|
+ POWER_SUPPLY_PROP_MANUFACTURER,
|
|
|
|
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const enum power_supply_property spwr_battery_props_eng[] = {
|
|
|
|
+ POWER_SUPPLY_PROP_STATUS,
|
|
|
|
+ POWER_SUPPLY_PROP_PRESENT,
|
|
|
|
+ POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
|
|
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
|
|
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
|
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
|
|
+ POWER_SUPPLY_PROP_POWER_NOW,
|
|
|
|
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
|
|
|
|
+ POWER_SUPPLY_PROP_ENERGY_FULL,
|
|
|
|
+ POWER_SUPPLY_PROP_ENERGY_NOW,
|
|
|
|
+ POWER_SUPPLY_PROP_CAPACITY,
|
|
|
|
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
|
|
|
+ POWER_SUPPLY_PROP_MODEL_NAME,
|
|
|
|
+ POWER_SUPPLY_PROP_MANUFACTURER,
|
|
|
|
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int spwr_battery_prop_status(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ u32 state = get_unaligned_le32(&bat->bst.state);
|
|
|
|
+ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate);
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (state & SAM_BATTERY_STATE_DISCHARGING)
|
|
|
|
+ return POWER_SUPPLY_STATUS_DISCHARGING;
|
|
|
|
+
|
|
|
|
+ if (state & SAM_BATTERY_STATE_CHARGING)
|
|
|
|
+ return POWER_SUPPLY_STATUS_CHARGING;
|
|
|
|
+
|
|
|
|
+ if (spwr_battery_is_full(bat))
|
|
|
|
+ return POWER_SUPPLY_STATUS_FULL;
|
|
|
|
+
|
|
|
|
+ if (present_rate == 0)
|
|
|
|
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
|
|
+
|
|
|
|
+ return POWER_SUPPLY_STATUS_UNKNOWN;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_prop_technology(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (!strcasecmp("NiCd", bat->bix.type))
|
|
|
|
+ return POWER_SUPPLY_TECHNOLOGY_NiCd;
|
|
|
|
+
|
|
|
|
+ if (!strcasecmp("NiMH", bat->bix.type))
|
|
|
|
+ return POWER_SUPPLY_TECHNOLOGY_NiMH;
|
|
|
|
+
|
|
|
|
+ if (!strcasecmp("LION", bat->bix.type))
|
|
|
|
+ return POWER_SUPPLY_TECHNOLOGY_LION;
|
|
|
|
+
|
|
|
|
+ if (!strncasecmp("LI-ION", bat->bix.type, 6))
|
|
|
|
+ return POWER_SUPPLY_TECHNOLOGY_LION;
|
|
|
|
+
|
|
|
|
+ if (!strcasecmp("LiP", bat->bix.type))
|
|
|
|
+ return POWER_SUPPLY_TECHNOLOGY_LIPO;
|
|
|
|
+
|
|
|
|
+ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_prop_capacity(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ u32 full_cap = sprw_battery_get_full_cap_safe(bat);
|
|
|
|
+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ return -ENODATA;
|
|
|
|
+
|
|
|
|
+ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ return -ENODATA;
|
|
|
|
+
|
|
|
|
+ return remaining_cap * 100 / full_cap;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ u32 state = get_unaligned_le32(&bat->bst.state);
|
|
|
|
+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (state & SAM_BATTERY_STATE_CRITICAL)
|
|
|
|
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
|
|
|
+
|
|
|
|
+ if (spwr_battery_is_full(bat))
|
|
|
|
+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
|
|
|
|
+
|
|
|
|
+ if (remaining_cap <= bat->alarm)
|
|
|
|
+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
|
|
|
|
+
|
|
|
|
+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp,
|
|
|
|
+ union power_supply_propval *val)
|
|
|
|
+{
|
|
|
|
+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
|
|
|
|
+ u32 value;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&bat->lock);
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_update_bst_unlocked(bat, true);
|
|
|
|
+ if (status)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ /* Abort if battery is not present. */
|
|
|
|
+ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) {
|
|
|
|
+ status = -ENODEV;
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch (psp) {
|
|
|
|
+ case POWER_SUPPLY_PROP_STATUS:
|
|
|
|
+ val->intval = spwr_battery_prop_status(bat);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_PRESENT:
|
|
|
|
+ val->intval = spwr_battery_present(bat);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
|
|
+ val->intval = spwr_battery_prop_technology(bat);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
|
|
|
|
+ value = get_unaligned_le32(&bat->bix.cycle_count);
|
|
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ val->intval = value;
|
|
|
|
+ else
|
|
|
|
+ status = -ENODATA;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
|
|
+ value = get_unaligned_le32(&bat->bix.design_voltage);
|
|
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ val->intval = value * 1000;
|
|
|
|
+ else
|
|
|
|
+ status = -ENODATA;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
|
|
+ value = get_unaligned_le32(&bat->bst.present_voltage);
|
|
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ val->intval = value * 1000;
|
|
|
|
+ else
|
|
|
|
+ status = -ENODATA;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
|
|
+ case POWER_SUPPLY_PROP_POWER_NOW:
|
|
|
|
+ value = get_unaligned_le32(&bat->bst.present_rate);
|
|
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ val->intval = value * 1000;
|
|
|
|
+ else
|
|
|
|
+ status = -ENODATA;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
|
|
|
|
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
|
|
|
|
+ value = get_unaligned_le32(&bat->bix.design_cap);
|
|
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ val->intval = value * 1000;
|
|
|
|
+ else
|
|
|
|
+ status = -ENODATA;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
|
|
|
|
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
|
|
|
|
+ value = get_unaligned_le32(&bat->bix.last_full_charge_cap);
|
|
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ val->intval = value * 1000;
|
|
|
|
+ else
|
|
|
|
+ status = -ENODATA;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
|
|
|
|
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
|
|
|
|
+ value = get_unaligned_le32(&bat->bst.remaining_cap);
|
|
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
|
|
+ val->intval = value * 1000;
|
|
|
|
+ else
|
|
|
|
+ status = -ENODATA;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_CAPACITY:
|
|
|
|
+ val->intval = spwr_battery_prop_capacity(bat);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
|
|
|
+ val->intval = spwr_battery_prop_capacity_level(bat);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
|
|
+ val->strval = bat->bix.model;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_MANUFACTURER:
|
|
|
|
+ val->strval = bat->bix.oem_info;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
|
|
|
|
+ val->strval = bat->bix.serial;
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ status = -EINVAL;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Alarm attribute. ------------------------------------------------------ */
|
|
|
|
+
|
|
|
|
+static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
|
|
+{
|
|
|
|
+ struct power_supply *psy = dev_get_drvdata(dev);
|
|
|
|
+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&bat->lock);
|
|
|
|
+ status = sysfs_emit(buf, "%d\n", bat->alarm * 1000);
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf,
|
|
|
|
+ size_t count)
|
|
|
|
+{
|
|
|
|
+ struct power_supply *psy = dev_get_drvdata(dev);
|
|
|
|
+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
|
|
|
|
+ unsigned long value;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = kstrtoul(buf, 0, &value);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&bat->lock);
|
|
|
|
+
|
|
|
|
+ if (!spwr_battery_present(bat)) {
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+ return -ENODEV;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_set_alarm_unlocked(bat, value / 1000);
|
|
|
|
+ if (status) {
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+ return status;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+ return count;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+DEVICE_ATTR_RW(alarm);
|
|
|
|
+
|
|
|
|
+static struct attribute *spwr_battery_attrs[] = {
|
|
|
|
+ &dev_attr_alarm.attr,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+ATTRIBUTE_GROUPS(spwr_battery);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Device setup. --------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev,
|
|
|
|
+ struct ssam_event_registry registry, const char *name)
|
|
|
|
+{
|
|
|
|
+ mutex_init(&bat->lock);
|
|
|
|
+ strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1);
|
|
|
|
+
|
|
|
|
+ bat->sdev = sdev;
|
|
|
|
+
|
|
|
|
+ bat->notif.base.priority = 1;
|
|
|
|
+ bat->notif.base.fn = spwr_notify_bat;
|
|
|
|
+ bat->notif.event.reg = registry;
|
|
|
|
+ bat->notif.event.id.target_category = sdev->uid.category;
|
|
|
|
+ bat->notif.event.id.instance = 0;
|
|
|
|
+ bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
|
|
|
|
+ bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
|
|
+
|
|
|
|
+ bat->psy_desc.name = bat->name;
|
|
|
|
+ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
|
|
+ bat->psy_desc.get_property = spwr_battery_get_property;
|
|
|
|
+
|
|
|
|
+ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_battery_register(struct spwr_battery_device *bat)
|
|
|
|
+{
|
|
|
|
+ struct power_supply_config psy_cfg = {};
|
|
|
|
+ __le32 sta;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ /* Make sure the device is there and functioning properly. */
|
|
|
|
+ status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ /* Satisfy lockdep although we are in an exclusive context here. */
|
|
|
|
+ mutex_lock(&bat->lock);
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_update_bix_unlocked(bat);
|
|
|
|
+ if (status) {
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+ return status;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (spwr_battery_present(bat)) {
|
|
|
|
+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
|
|
|
|
+
|
|
|
|
+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
|
|
|
|
+ if (status) {
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+ return status;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mutex_unlock(&bat->lock);
|
|
|
|
+
|
|
|
|
+ bat->psy_desc.external_power_changed = spwr_external_power_changed;
|
|
|
|
+
|
|
|
|
+ switch (get_unaligned_le32(&bat->bix.power_unit)) {
|
|
|
|
+ case SAM_BATTERY_POWER_UNIT_mW:
|
|
|
|
+ bat->psy_desc.properties = spwr_battery_props_eng;
|
|
|
|
+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case SAM_BATTERY_POWER_UNIT_mA:
|
|
|
|
+ bat->psy_desc.properties = spwr_battery_props_chg;
|
|
|
|
+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n",
|
|
|
|
+ get_unaligned_le32(&bat->bix.power_unit));
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ psy_cfg.drv_data = bat;
|
|
|
|
+ psy_cfg.attr_grp = spwr_battery_groups;
|
|
|
|
+
|
|
|
|
+ bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg);
|
|
|
|
+ if (IS_ERR(bat->psy))
|
|
|
|
+ return PTR_ERR(bat->psy);
|
|
|
|
+
|
|
|
|
+ return ssam_notifier_register(bat->sdev->ctrl, &bat->notif);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Driver setup. --------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int __maybe_unused surface_battery_resume(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ return spwr_battery_recheck_full(dev_get_drvdata(dev));
|
|
|
|
+}
|
|
|
|
+SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
|
|
|
|
+
|
|
|
|
+static int surface_battery_probe(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ const struct spwr_psy_properties *p;
|
|
|
|
+ struct spwr_battery_device *bat;
|
|
|
|
+
|
|
|
|
+ p = ssam_device_get_match_data(sdev);
|
|
|
|
+ if (!p)
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL);
|
|
|
|
+ if (!bat)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ spwr_battery_init(bat, sdev, p->registry, p->name);
|
|
|
|
+ ssam_device_set_drvdata(sdev, bat);
|
|
|
|
+
|
|
|
|
+ return spwr_battery_register(bat);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void surface_battery_remove(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev);
|
|
|
|
+
|
|
|
|
+ ssam_notifier_unregister(sdev->ctrl, &bat->notif);
|
|
|
|
+ cancel_delayed_work_sync(&bat->update_work);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct spwr_psy_properties spwr_psy_props_bat1 = {
|
|
|
|
+ .name = "BAT1",
|
|
|
|
+ .registry = SSAM_EVENT_REGISTRY_SAM,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = {
|
|
|
|
+ .name = "BAT2",
|
|
|
|
+ .registry = SSAM_EVENT_REGISTRY_KIP,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct ssam_device_id surface_battery_match[] = {
|
|
|
|
+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 },
|
|
|
|
+ { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 },
|
|
|
|
+ { },
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(ssam, surface_battery_match);
|
|
|
|
+
|
|
|
|
+static struct ssam_device_driver surface_battery_driver = {
|
|
|
|
+ .probe = surface_battery_probe,
|
|
|
|
+ .remove = surface_battery_remove,
|
|
|
|
+ .match_table = surface_battery_match,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "surface_battery",
|
|
|
|
+ .pm = &surface_battery_pm_ops,
|
|
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+module_ssam_device_driver(surface_battery_driver);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 0b7e636936451ccf7e0bd7fa8c6e5331ffd53424 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Tue, 6 Apr 2021 01:41:26 +0200
|
|
|
|
Subject: [PATCH] power: supply: Add AC driver for Surface Aggregator Module
|
|
|
|
|
|
|
|
On newer Microsoft Surface models (specifically 7th-generation, i.e.
|
|
|
|
Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go),
|
|
|
|
battery and AC status/information is no longer handled via standard ACPI
|
|
|
|
devices, but instead directly via the Surface System Aggregator Module
|
|
|
|
(SSAM), i.e. the embedded controller on those devices.
|
|
|
|
|
|
|
|
While on previous generation models, AC status is also handled via SSAM,
|
|
|
|
an ACPI shim was present to translate the standard ACPI AC interface to
|
|
|
|
SSAM requests. The SSAM interface itself, which is modeled closely after
|
|
|
|
the ACPI interface, has not changed.
|
|
|
|
|
|
|
|
This commit introduces a new SSAM client device driver to support AC
|
|
|
|
status/information via the aforementioned interface on said Surface
|
|
|
|
models.
|
|
|
|
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
MAINTAINERS | 1 +
|
|
|
|
drivers/power/supply/Kconfig | 16 ++
|
|
|
|
drivers/power/supply/Makefile | 1 +
|
|
|
|
drivers/power/supply/surface_charger.c | 282 +++++++++++++++++++++++++
|
|
|
|
4 files changed, 300 insertions(+)
|
|
|
|
create mode 100644 drivers/power/supply/surface_charger.c
|
|
|
|
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
|
|
index 7ee93b732270..710617e26f3e 100644
|
|
|
|
--- a/MAINTAINERS
|
|
|
|
+++ b/MAINTAINERS
|
|
|
|
@@ -11874,6 +11874,7 @@ L: linux-pm@vger.kernel.org
|
|
|
|
L: platform-driver-x86@vger.kernel.org
|
|
|
|
S: Maintained
|
|
|
|
F: drivers/power/supply/surface_battery.c
|
|
|
|
+F: drivers/power/supply/surface_charger.c
|
|
|
|
|
|
|
|
MICROSOFT SURFACE DTX DRIVER
|
|
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
|
|
|
|
index cebeff10d543..91f7cf425ac9 100644
|
|
|
|
--- a/drivers/power/supply/Kconfig
|
|
|
|
+++ b/drivers/power/supply/Kconfig
|
|
|
|
@@ -817,4 +817,20 @@ config BATTERY_SURFACE
|
|
|
|
Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
|
|
|
|
Surface Book 3, and Surface Laptop Go.
|
|
|
|
|
|
|
|
+config CHARGER_SURFACE
|
|
|
|
+ tristate "AC driver for 7th-generation Microsoft Surface devices"
|
|
|
|
+ depends on SURFACE_AGGREGATOR_REGISTRY
|
|
|
|
+ help
|
|
|
|
+ Driver for AC devices connected via/managed by the Surface System
|
|
|
|
+ Aggregator Module (SSAM).
|
|
|
|
+
|
|
|
|
+ This driver provides AC-information and -status support for Surface
|
|
|
|
+ devices where said data is not exposed via the standard ACPI devices.
|
|
|
|
+ On those models (7th-generation), AC-information is instead handled
|
|
|
|
+ directly via a SSAM client device and this driver.
|
|
|
|
+
|
|
|
|
+ Say M or Y here to include AC status support for 7th-generation
|
|
|
|
+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
|
|
|
|
+ Surface Book 3, and Surface Laptop Go.
|
|
|
|
+
|
|
|
|
endif # POWER_SUPPLY
|
|
|
|
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
|
|
|
|
index 134041538d2c..a7309a3d1a47 100644
|
|
|
|
--- a/drivers/power/supply/Makefile
|
|
|
|
+++ b/drivers/power/supply/Makefile
|
|
|
|
@@ -102,3 +102,4 @@ obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
|
|
|
|
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
|
|
|
|
obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
|
|
|
|
obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
|
|
|
|
+obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
|
|
|
|
diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..c2dd7e604d14
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/power/supply/surface_charger.c
|
|
|
|
@@ -0,0 +1,282 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
+/*
|
|
|
|
+ * AC driver for 7th-generation Microsoft Surface devices via Surface System
|
|
|
|
+ * Aggregator Module (SSAM).
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <asm/unaligned.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/mutex.h>
|
|
|
|
+#include <linux/power_supply.h>
|
|
|
|
+#include <linux/types.h>
|
|
|
|
+
|
|
|
|
+#include <linux/surface_aggregator/device.h>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- SAM interface. -------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+enum sam_event_cid_bat {
|
|
|
|
+ SAM_EVENT_CID_BAT_ADP = 0x17,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+enum sam_battery_sta {
|
|
|
|
+ SAM_BATTERY_STA_OK = 0x0f,
|
|
|
|
+ SAM_BATTERY_STA_PRESENT = 0x10,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* Get battery status (_STA). */
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
|
|
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
|
|
+ .command_id = 0x01,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+/* Get platform power source for battery (_PSR / DPTF PSRC). */
|
|
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, {
|
|
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
|
|
+ .command_id = 0x0d,
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Device structures. ---------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+struct spwr_psy_properties {
|
|
|
|
+ const char *name;
|
|
|
|
+ struct ssam_event_registry registry;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct spwr_ac_device {
|
|
|
|
+ struct ssam_device *sdev;
|
|
|
|
+
|
|
|
|
+ char name[32];
|
|
|
|
+ struct power_supply *psy;
|
|
|
|
+ struct power_supply_desc psy_desc;
|
|
|
|
+
|
|
|
|
+ struct ssam_event_notifier notif;
|
|
|
|
+
|
|
|
|
+ struct mutex lock; /* Guards access to state below. */
|
|
|
|
+
|
|
|
|
+ __le32 state;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- State management. ----------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
|
|
|
|
+{
|
|
|
|
+ u32 old = ac->state;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ lockdep_assert_held(&ac->lock);
|
|
|
|
+
|
|
|
|
+ status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state);
|
|
|
|
+ if (status < 0)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ return old != ac->state;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_ac_update(struct spwr_ac_device *ac)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&ac->lock);
|
|
|
|
+ status = spwr_ac_update_unlocked(ac);
|
|
|
|
+ mutex_unlock(&ac->lock);
|
|
|
|
+
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_ac_recheck(struct spwr_ac_device *ac)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = spwr_ac_update(ac);
|
|
|
|
+ if (status > 0)
|
|
|
|
+ power_supply_changed(ac->psy);
|
|
|
|
+
|
|
|
|
+ return status >= 0 ? 0 : status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
|
|
+{
|
|
|
|
+ struct spwr_ac_device *ac;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ ac = container_of(nf, struct spwr_ac_device, notif);
|
|
|
|
+
|
|
|
|
+ dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
|
|
|
|
+ event->command_id, event->instance_id, event->target_id);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Allow events of all targets/instances here. Global adapter status
|
|
|
|
+ * seems to be handled via target=1 and instance=1, but events are
|
|
|
|
+ * reported on all targets/instances in use.
|
|
|
|
+ *
|
|
|
|
+ * While it should be enough to just listen on 1/1, listen everywhere to
|
|
|
|
+ * make sure we don't miss anything.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ switch (event->command_id) {
|
|
|
|
+ case SAM_EVENT_CID_BAT_ADP:
|
|
|
|
+ status = spwr_ac_recheck(ac);
|
|
|
|
+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Properties. ----------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static const enum power_supply_property spwr_ac_props[] = {
|
|
|
|
+ POWER_SUPPLY_PROP_ONLINE,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp,
|
|
|
|
+ union power_supply_propval *val)
|
|
|
|
+{
|
|
|
|
+ struct spwr_ac_device *ac = power_supply_get_drvdata(psy);
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&ac->lock);
|
|
|
|
+
|
|
|
|
+ status = spwr_ac_update_unlocked(ac);
|
|
|
|
+ if (status)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ switch (psp) {
|
|
|
|
+ case POWER_SUPPLY_PROP_ONLINE:
|
|
|
|
+ val->intval = !!le32_to_cpu(ac->state);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ status = -EINVAL;
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ mutex_unlock(&ac->lock);
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Device setup. --------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static char *battery_supplied_to[] = {
|
|
|
|
+ "BAT1",
|
|
|
|
+ "BAT2",
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev,
|
|
|
|
+ struct ssam_event_registry registry, const char *name)
|
|
|
|
+{
|
|
|
|
+ mutex_init(&ac->lock);
|
|
|
|
+ strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1);
|
|
|
|
+
|
|
|
|
+ ac->sdev = sdev;
|
|
|
|
+
|
|
|
|
+ ac->notif.base.priority = 1;
|
|
|
|
+ ac->notif.base.fn = spwr_notify_ac;
|
|
|
|
+ ac->notif.event.reg = registry;
|
|
|
|
+ ac->notif.event.id.target_category = sdev->uid.category;
|
|
|
|
+ ac->notif.event.id.instance = 0;
|
|
|
|
+ ac->notif.event.mask = SSAM_EVENT_MASK_NONE;
|
|
|
|
+ ac->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
|
|
+
|
|
|
|
+ ac->psy_desc.name = ac->name;
|
|
|
|
+ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
|
|
|
|
+ ac->psy_desc.properties = spwr_ac_props;
|
|
|
|
+ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props);
|
|
|
|
+ ac->psy_desc.get_property = spwr_ac_get_property;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int spwr_ac_register(struct spwr_ac_device *ac)
|
|
|
|
+{
|
|
|
|
+ struct power_supply_config psy_cfg = {};
|
|
|
|
+ __le32 sta;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ /* Make sure the device is there and functioning properly. */
|
|
|
|
+ status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta);
|
|
|
|
+ if (status)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ psy_cfg.drv_data = ac;
|
|
|
|
+ psy_cfg.supplied_to = battery_supplied_to;
|
|
|
|
+ psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to);
|
|
|
|
+
|
|
|
|
+ ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg);
|
|
|
|
+ if (IS_ERR(ac->psy))
|
|
|
|
+ return PTR_ERR(ac->psy);
|
|
|
|
+
|
|
|
|
+ return ssam_notifier_register(ac->sdev->ctrl, &ac->notif);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/* -- Driver setup. --------------------------------------------------------- */
|
|
|
|
+
|
|
|
|
+static int __maybe_unused surface_ac_resume(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ return spwr_ac_recheck(dev_get_drvdata(dev));
|
|
|
|
+}
|
|
|
|
+SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
|
|
|
|
+
|
|
|
|
+static int surface_ac_probe(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ const struct spwr_psy_properties *p;
|
|
|
|
+ struct spwr_ac_device *ac;
|
|
|
|
+
|
|
|
|
+ p = ssam_device_get_match_data(sdev);
|
|
|
|
+ if (!p)
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL);
|
|
|
|
+ if (!ac)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ spwr_ac_init(ac, sdev, p->registry, p->name);
|
|
|
|
+ ssam_device_set_drvdata(sdev, ac);
|
|
|
|
+
|
|
|
|
+ return spwr_ac_register(ac);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void surface_ac_remove(struct ssam_device *sdev)
|
|
|
|
+{
|
|
|
|
+ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev);
|
|
|
|
+
|
|
|
|
+ ssam_notifier_unregister(sdev->ctrl, &ac->notif);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct spwr_psy_properties spwr_psy_props_adp1 = {
|
|
|
|
+ .name = "ADP1",
|
|
|
|
+ .registry = SSAM_EVENT_REGISTRY_SAM,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct ssam_device_id surface_ac_match[] = {
|
|
|
|
+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 },
|
|
|
|
+ { },
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(ssam, surface_ac_match);
|
|
|
|
+
|
|
|
|
+static struct ssam_device_driver surface_ac_driver = {
|
|
|
|
+ .probe = surface_ac_probe,
|
|
|
|
+ .remove = surface_ac_remove,
|
|
|
|
+ .match_table = surface_ac_match,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "surface_ac",
|
|
|
|
+ .pm = &surface_ac_pm_ops,
|
|
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+module_ssam_device_driver(surface_ac_driver);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From b45091742f2fae559b4091a0c4d2a0660b03a98a Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Qiheng Lin <linqiheng@huawei.com>
|
|
|
|
Date: Sat, 10 Apr 2021 12:12:46 +0800
|
|
|
|
Subject: [PATCH] power: supply: surface-battery: Make some symbols static
|
|
|
|
|
|
|
|
The sparse tool complains as follows:
|
|
|
|
|
|
|
|
drivers/power/supply/surface_battery.c:700:1: warning:
|
|
|
|
symbol 'dev_attr_alarm' was not declared. Should it be static?
|
|
|
|
drivers/power/supply/surface_battery.c:805:1: warning:
|
|
|
|
symbol 'surface_battery_pm_ops' was not declared. Should it be static?
|
|
|
|
|
|
|
|
This symbol is not used outside of surface_battery.c, so this
|
|
|
|
commit marks it static.
|
|
|
|
|
|
|
|
Reported-by: Hulk Robot <hulkci@huawei.com>
|
|
|
|
Signed-off-by: Qiheng Lin <linqiheng@huawei.com>
|
|
|
|
Acked-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/power/supply/surface_battery.c | 4 ++--
|
|
|
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
|
|
|
|
index 4116dd839ecd..7efa431a62b2 100644
|
|
|
|
--- a/drivers/power/supply/surface_battery.c
|
|
|
|
+++ b/drivers/power/supply/surface_battery.c
|
|
|
|
@@ -697,7 +697,7 @@ static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, co
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
-DEVICE_ATTR_RW(alarm);
|
|
|
|
+static DEVICE_ATTR_RW(alarm);
|
|
|
|
|
|
|
|
static struct attribute *spwr_battery_attrs[] = {
|
|
|
|
&dev_attr_alarm.attr,
|
|
|
|
@@ -802,7 +802,7 @@ static int __maybe_unused surface_battery_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
return spwr_battery_recheck_full(dev_get_drvdata(dev));
|
|
|
|
}
|
|
|
|
-SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
|
|
|
|
+static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
|
|
|
|
|
|
|
|
static int surface_battery_probe(struct ssam_device *sdev)
|
|
|
|
{
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 6202c110bfad78f064d8687d5b433146cde21cd3 Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Qiheng Lin <linqiheng@huawei.com>
|
|
|
|
Date: Sat, 10 Apr 2021 12:12:49 +0800
|
|
|
|
Subject: [PATCH] power: supply: surface-charger: Make symbol
|
|
|
|
'surface_ac_pm_ops' static
|
|
|
|
|
|
|
|
The sparse tool complains as follows:
|
|
|
|
|
|
|
|
drivers/power/supply/surface_charger.c:229:1: warning:
|
|
|
|
symbol 'surface_ac_pm_ops' was not declared. Should it be static?
|
|
|
|
|
|
|
|
This symbol is not used outside of surface_charger.c, so this
|
|
|
|
commit marks it static.
|
|
|
|
|
|
|
|
Reported-by: Hulk Robot <hulkci@huawei.com>
|
|
|
|
Signed-off-by: Qiheng Lin <linqiheng@huawei.com>
|
|
|
|
Acked-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/power/supply/surface_charger.c | 2 +-
|
|
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
|
|
|
|
index c2dd7e604d14..81a5b79822c9 100644
|
|
|
|
--- a/drivers/power/supply/surface_charger.c
|
|
|
|
+++ b/drivers/power/supply/surface_charger.c
|
|
|
|
@@ -226,7 +226,7 @@ static int __maybe_unused surface_ac_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
return spwr_ac_recheck(dev_get_drvdata(dev));
|
|
|
|
}
|
|
|
|
-SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
|
|
|
|
+static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
|
|
|
|
|
|
|
|
static int surface_ac_probe(struct ssam_device *sdev)
|
|
|
|
{
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From 47f3073898bb0e27542ec5dddca2816b44fdda5a Mon Sep 17 00:00:00 2001
|
2021-05-05 13:57:58 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Tue, 4 May 2021 20:00:46 +0200
|
|
|
|
Subject: [PATCH] power: supply: surface_battery: Fix battery event handling
|
|
|
|
|
|
|
|
The battery subsystem of the Surface Aggregator Module EC requires us to
|
|
|
|
register the battery notifier with instance ID 0. However, battery
|
|
|
|
events are actually sent with the instance ID corresponding to the
|
|
|
|
device, which is nonzero. Thus, the strict-matching approach doesn't
|
|
|
|
work here and will discard events that the driver is expected to handle.
|
|
|
|
|
|
|
|
To fix this we have to fall back on notifier matching by target-category
|
|
|
|
only and have to manually check the instance ID in the notifier
|
|
|
|
callback.
|
|
|
|
|
|
|
|
Fixes: 167f77f7d0b3 ("power: supply: Add battery driver for Surface Aggregator Module")
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/power/supply/surface_battery.c | 14 ++++++++++++--
|
|
|
|
1 file changed, 12 insertions(+), 2 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
|
|
|
|
index 7efa431a62b2..5ec2e6bb2465 100644
|
|
|
|
--- a/drivers/power/supply/surface_battery.c
|
|
|
|
+++ b/drivers/power/supply/surface_battery.c
|
|
|
|
@@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve
|
|
|
|
struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
|
|
|
|
int status;
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * We cannot use strict matching when registering the notifier as the
|
|
|
|
+ * EC expects us to register it against instance ID 0. Strict matching
|
|
|
|
+ * would thus drop events, as those may have non-zero instance IDs in
|
|
|
|
+ * this subsystem. So we need to check the instance ID of the event
|
|
|
|
+ * here manually.
|
|
|
|
+ */
|
|
|
|
+ if (event->instance_id != bat->sdev->uid.instance)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
|
|
|
|
event->command_id, event->instance_id, event->target_id);
|
|
|
|
|
|
|
|
@@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic
|
|
|
|
bat->notif.base.fn = spwr_notify_bat;
|
|
|
|
bat->notif.event.reg = registry;
|
|
|
|
bat->notif.event.id.target_category = sdev->uid.category;
|
|
|
|
- bat->notif.event.id.instance = 0;
|
|
|
|
- bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
|
|
|
|
+ bat->notif.event.id.instance = 0; /* need to register with instance 0 */
|
|
|
|
+ bat->notif.event.mask = SSAM_EVENT_MASK_TARGET;
|
|
|
|
bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
|
|
|
|
|
|
bat->psy_desc.name = bat->name;
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From c747b2f975ecd2b3fe4e129ef6d644edfb1f8bda Mon Sep 17 00:00:00 2001
|
2021-05-11 17:53:44 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Tue, 11 May 2021 11:24:21 +0200
|
|
|
|
Subject: [PATCH] power: supply: surface-charger: Fix type of integer variable
|
|
|
|
|
|
|
|
The ac->state field is __le32, not u32. So change the variable we're
|
|
|
|
temporarily storing it in to __le32 as well.
|
|
|
|
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
|
|
Fixes: e61ffb344591 ("power: supply: Add AC driver for Surface Aggregator Module")
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/power/supply/surface_charger.c | 2 +-
|
|
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
|
|
|
|
diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
|
|
|
|
index 81a5b79822c9..a060c36c7766 100644
|
|
|
|
--- a/drivers/power/supply/surface_charger.c
|
|
|
|
+++ b/drivers/power/supply/surface_charger.c
|
|
|
|
@@ -66,7 +66,7 @@ struct spwr_ac_device {
|
|
|
|
|
|
|
|
static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
|
|
|
|
{
|
|
|
|
- u32 old = ac->state;
|
|
|
|
+ __le32 old = ac->state;
|
|
|
|
int status;
|
|
|
|
|
|
|
|
lockdep_assert_held(&ac->lock);
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From cc579fa79a6e8ebc837dc3e469210fcfc454191e Mon Sep 17 00:00:00 2001
|
2021-05-11 17:53:44 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Wed, 5 May 2021 18:17:04 +0200
|
|
|
|
Subject: [PATCH] serial: 8250_dw: Add device HID for new AMD UART controller
|
|
|
|
|
|
|
|
Add device HID AMDI0022 to the AMD UART controller driver match table
|
|
|
|
and create a platform device for it. This controller can be found on
|
|
|
|
Microsoft Surface Laptop 4 devices and seems similar enough that we can
|
|
|
|
just copy the existing AMDI0020 entries.
|
|
|
|
|
|
|
|
Cc: <stable@vger.kernel.org> # 5.10+
|
|
|
|
Tested-by: Sachi King <nakato@nakato.io>
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/acpi/acpi_apd.c | 1 +
|
|
|
|
drivers/tty/serial/8250/8250_dw.c | 1 +
|
|
|
|
2 files changed, 2 insertions(+)
|
|
|
|
|
|
|
|
diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c
|
|
|
|
index 39359ce0eb2c..645e82a66bb0 100644
|
|
|
|
--- a/drivers/acpi/acpi_apd.c
|
|
|
|
+++ b/drivers/acpi/acpi_apd.c
|
|
|
|
@@ -226,6 +226,7 @@ static const struct acpi_device_id acpi_apd_device_ids[] = {
|
|
|
|
{ "AMDI0010", APD_ADDR(wt_i2c_desc) },
|
|
|
|
{ "AMD0020", APD_ADDR(cz_uart_desc) },
|
|
|
|
{ "AMDI0020", APD_ADDR(cz_uart_desc) },
|
|
|
|
+ { "AMDI0022", APD_ADDR(cz_uart_desc) },
|
|
|
|
{ "AMD0030", },
|
|
|
|
{ "AMD0040", APD_ADDR(fch_misc_desc)},
|
|
|
|
{ "HYGO0010", APD_ADDR(wt_i2c_desc) },
|
|
|
|
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
|
|
|
|
index 9e204f9b799a..a3a0154da567 100644
|
|
|
|
--- a/drivers/tty/serial/8250/8250_dw.c
|
|
|
|
+++ b/drivers/tty/serial/8250/8250_dw.c
|
|
|
|
@@ -714,6 +714,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = {
|
|
|
|
{ "APMC0D08", 0},
|
|
|
|
{ "AMD0020", 0 },
|
|
|
|
{ "AMDI0020", 0 },
|
|
|
|
+ { "AMDI0022", 0 },
|
|
|
|
{ "BRCM2032", 0 },
|
|
|
|
{ "HISI0031", 0 },
|
|
|
|
{ },
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From c559b9d8aca9eab3a2c29bae5976404318f15e24 Mon Sep 17 00:00:00 2001
|
2021-05-11 17:53:44 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Wed, 5 May 2021 18:22:04 +0200
|
|
|
|
Subject: [PATCH] pinctrl/amd: Add device HID for new AMD GPIO controller
|
|
|
|
|
|
|
|
Add device HID AMDI0031 to the AMD GPIO controller driver match table.
|
|
|
|
This controller can be found on Microsoft Surface Laptop 4 devices and
|
|
|
|
seems similar enough that we can just copy the existing AMDI0030 entry.
|
|
|
|
|
|
|
|
Cc: <stable@vger.kernel.org> # 5.10+
|
|
|
|
Tested-by: Sachi King <nakato@nakato.io>
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
drivers/pinctrl/pinctrl-amd.c | 1 +
|
|
|
|
1 file changed, 1 insertion(+)
|
|
|
|
|
|
|
|
diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c
|
|
|
|
index 2d4acf21117c..c5950a3b4e4c 100644
|
|
|
|
--- a/drivers/pinctrl/pinctrl-amd.c
|
|
|
|
+++ b/drivers/pinctrl/pinctrl-amd.c
|
|
|
|
@@ -991,6 +991,7 @@ static int amd_gpio_remove(struct platform_device *pdev)
|
|
|
|
static const struct acpi_device_id amd_gpio_acpi_match[] = {
|
|
|
|
{ "AMD0030", 0 },
|
|
|
|
{ "AMDI0030", 0},
|
|
|
|
+ { "AMDI0031", 0},
|
|
|
|
{ },
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(acpi, amd_gpio_acpi_match);
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|
2021-05-19 06:27:58 +00:00
|
|
|
From a09349d48459ca05841738c4c0a3be9c0072e9ce Mon Sep 17 00:00:00 2001
|
2021-05-11 17:53:44 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Mon, 10 May 2021 23:33:17 +0200
|
|
|
|
Subject: [PATCH] x86/i8259: Work around buggy legacy PIC
|
|
|
|
|
|
|
|
The legacy PIC on the AMD variant of the Microsoft Surface Laptop 4 has
|
|
|
|
some problems on boot. For some reason it consistently does not respond
|
|
|
|
on the first try, requiring a couple more tries before it finally
|
|
|
|
responds.
|
|
|
|
|
|
|
|
This currently leads to the PIC not being properly recognized, which
|
|
|
|
prevents interrupt handling down the line. Ultimately, this also leads
|
|
|
|
to the pinctrl-amd driver failing to probe due to platform_get_irq()
|
|
|
|
returning -EINVAL for its base IRQ. That, in turn, means that several
|
|
|
|
interrupts are not available and device drivers relying on those will
|
|
|
|
defer probing indefinitely, as querying those interrupts returns
|
|
|
|
-EPROBE_DEFER.
|
|
|
|
|
|
|
|
Add a quirk table and a retry-loop to work around that.
|
|
|
|
|
|
|
|
Also switch to pr_info() due to complaints by checkpatch and add a
|
|
|
|
pr_fmt() definition for completeness.
|
|
|
|
|
|
|
|
Cc: <stable@vger.kernel.org> # 5.10+
|
|
|
|
Co-developed-by: Sachi King <nakato@nakato.io>
|
|
|
|
Signed-off-by: Sachi King <nakato@nakato.io>
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Patchset: surface-sam
|
|
|
|
---
|
|
|
|
arch/x86/kernel/i8259.c | 51 +++++++++++++++++++++++++++++++++++++----
|
|
|
|
1 file changed, 46 insertions(+), 5 deletions(-)
|
|
|
|
|
|
|
|
diff --git a/arch/x86/kernel/i8259.c b/arch/x86/kernel/i8259.c
|
|
|
|
index 282b4ee1339f..0da757c6b292 100644
|
|
|
|
--- a/arch/x86/kernel/i8259.c
|
|
|
|
+++ b/arch/x86/kernel/i8259.c
|
|
|
|
@@ -1,4 +1,7 @@
|
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
+
|
|
|
|
+#define pr_fmt(fmt) "i8259: " fmt
|
|
|
|
+
|
|
|
|
#include <linux/linkage.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/signal.h>
|
|
|
|
@@ -16,6 +19,7 @@
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/pgtable.h>
|
|
|
|
+#include <linux/dmi.h>
|
|
|
|
|
|
|
|
#include <linux/atomic.h>
|
|
|
|
#include <asm/timer.h>
|
|
|
|
@@ -298,11 +302,39 @@ static void unmask_8259A(void)
|
|
|
|
raw_spin_unlock_irqrestore(&i8259A_lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * DMI table to identify devices with quirky probe behavior. See comment in
|
|
|
|
+ * probe_8259A() for more details.
|
|
|
|
+ */
|
|
|
|
+static const struct dmi_system_id retry_probe_quirk_table[] = {
|
|
|
|
+ {
|
|
|
|
+ .ident = "Microsoft Surface Laptop 4 (AMD)",
|
|
|
|
+ .matches = {
|
|
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
|
|
|
+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953")
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {}
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
static int probe_8259A(void)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
unsigned char probe_val = ~(1 << PIC_CASCADE_IR);
|
|
|
|
unsigned char new_val;
|
|
|
|
+ unsigned int i, imax = 1;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Some systems have a legacy PIC that doesn't immediately respond
|
|
|
|
+ * after boot. We know it's there, we know it should respond and is
|
|
|
|
+ * required for proper interrupt handling later on, so let's try a
|
|
|
|
+ * couple of times.
|
|
|
|
+ */
|
|
|
|
+ if (dmi_check_system(retry_probe_quirk_table)) {
|
|
|
|
+ pr_warn("system with broken legacy PIC detected, re-trying multiple times if necessary\n");
|
|
|
|
+ imax = 10;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
/*
|
|
|
|
* Check to see if we have a PIC.
|
|
|
|
* Mask all except the cascade and read
|
|
|
|
@@ -312,15 +344,24 @@ static int probe_8259A(void)
|
|
|
|
*/
|
|
|
|
raw_spin_lock_irqsave(&i8259A_lock, flags);
|
|
|
|
|
|
|
|
- outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */
|
|
|
|
- outb(probe_val, PIC_MASTER_IMR);
|
|
|
|
- new_val = inb(PIC_MASTER_IMR);
|
|
|
|
- if (new_val != probe_val) {
|
|
|
|
- printk(KERN_INFO "Using NULL legacy PIC\n");
|
|
|
|
+ for (i = 0; i < imax; i++) {
|
|
|
|
+ outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */
|
|
|
|
+ outb(probe_val, PIC_MASTER_IMR);
|
|
|
|
+ new_val = inb(PIC_MASTER_IMR);
|
|
|
|
+ if (new_val == probe_val)
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (i == imax) {
|
|
|
|
+ pr_info("using NULL legacy PIC\n");
|
|
|
|
legacy_pic = &null_legacy_pic;
|
|
|
|
}
|
|
|
|
|
|
|
|
raw_spin_unlock_irqrestore(&i8259A_lock, flags);
|
|
|
|
+
|
|
|
|
+ if (imax > 1 && i < imax)
|
|
|
|
+ pr_info("got legacy PIC after %d tries\n", i + 1);
|
|
|
|
+
|
|
|
|
return nr_legacy_irqs();
|
|
|
|
}
|
|
|
|
|
|
|
|
--
|
|
|
|
2.31.1
|
|
|
|
|