From 88e2e104f2ef2e461ca1256ab51f8d0c2272790f Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 15 Feb 2021 21:36:42 +0100 Subject: [PATCH] Update v5.10 patches Changes: - SAM: - Move and split drivers to closer reflect upstreaming process - Various updates to HID and battery/AC drivers for upstreaming - Hotplug: - Various fixes and improvements - GPE: - Fix Kconfig dependency - IPTS - Various fixes and improvements Links: - kernel: https://github.com/linux-surface/kernel/commit/5c7b5b588e18eda5a1e2375110b241a79d9ccf43 - SAM: https://github.com/linux-surface/surface-aggregator-module/commit/bee2add45fb668a794406ce6a648cece4fd00d51 - GPE: https://github.com/linux-surface/surface-gpe/commit/6ecfdb39050129bf17e5f1fff784e4df7aa56171 - Hotplug: https://github.com/linux-surface/surface-hotplug/commit/595ed62f24417b2ac97f4658cfc30776c9b888cf - IPTS: https://github.com/linux-surface/intel-precise-touch/commit/3642d0e4ebf98ded318cf4bcbc79c90c449b321f --- configs/surface-5.10.config | 9 +- patches/5.10/0001-surface3-oemb.patch | 4 +- patches/5.10/0002-wifi.patch | 44 +- patches/5.10/0003-ipts.patch | 537 +- patches/5.10/0004-surface-gpe.patch | 4 +- patches/5.10/0005-surface-sam-over-hid.patch | 8 +- patches/5.10/0006-surface-sam.patch | 10399 +++++++++-------- patches/5.10/0007-surface-hotplug.patch | 155 +- patches/5.10/0008-surface-typecover.patch | 4 +- patches/5.10/0009-surface-sensors.patch | 4 +- patches/5.10/0010-cameras.patch | 124 +- pkg/arch/kernel/PKGBUILD | 22 +- 12 files changed, 5711 insertions(+), 5603 deletions(-) diff --git a/configs/surface-5.10.config b/configs/surface-5.10.config index c98a7bd84..f7a91e17c 100644 --- a/configs/surface-5.10.config +++ b/configs/surface-5.10.config @@ -6,12 +6,17 @@ CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION=n CONFIG_SURFACE_AGGREGATOR_BUS=y CONFIG_SURFACE_AGGREGATOR_CDEV=m CONFIG_SURFACE_AGGREGATOR_REGISTRY=m + CONFIG_SURFACE_ACPI_NOTIFY=m -CONFIG_SURFACE_BATTERY=m CONFIG_SURFACE_DTX=m -CONFIG_SURFACE_HID=m CONFIG_SURFACE_PERFMODE=m +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + # # Surface Hotplug # diff --git a/patches/5.10/0001-surface3-oemb.patch b/patches/5.10/0001-surface3-oemb.patch index 53f0523a2..c12faf343 100644 --- a/patches/5.10/0001-surface3-oemb.patch +++ b/patches/5.10/0001-surface3-oemb.patch @@ -1,4 +1,4 @@ -From c49b5dc630a1a24d1f0b21e5eb70339ef97ba0de Mon Sep 17 00:00:00 2001 +From f696bf9c577af759678b5296ac4b944dbb188c9e Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Sun, 18 Oct 2020 16:42:44 +0900 Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI @@ -97,5 +97,5 @@ index 2752dc955733..ef36a316e2ed 100644 }; -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0002-wifi.patch b/patches/5.10/0002-wifi.patch index 1f58c5a9e..db1b1e580 100644 --- a/patches/5.10/0002-wifi.patch +++ b/patches/5.10/0002-wifi.patch @@ -1,4 +1,4 @@ -From 87009a45b3fb8bd697cca306828bb7bc7f6d80d5 Mon Sep 17 00:00:00 2001 +From 7142aae4519bf0728c574940e21cc7ab2c4a9e0f Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Thu, 24 Sep 2020 18:02:06 +0900 Subject: [PATCH] mwifiex: pcie: skip cancel_work_sync() on reset failure path @@ -154,9 +154,9 @@ index 843d57eda820..5ed613d65709 100644 static inline int -- -2.30.0 +2.30.1 -From ea4297c1c464ab216c09d25c2f1af292c228b18e Mon Sep 17 00:00:00 2001 +From 8ee97a2be558e3ee3fbdcee4c3934ffc46df6a66 Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Mon, 28 Sep 2020 17:46:49 +0900 Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices @@ -362,9 +362,9 @@ index 000000000000..5326ae7e5671 + +void mwifiex_initialize_quirks(struct pcie_service_card *card); -- -2.30.0 +2.30.1 -From f36f5936f9fbca30a9e9ef785015186bca8ee006 Mon Sep 17 00:00:00 2001 +From 012378c78ce0aa9f54e6233fb03f15ab662b4bff Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Tue, 29 Sep 2020 17:25:22 +0900 Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ @@ -563,9 +563,9 @@ index 5326ae7e5671..8b9dcb5070d8 100644 void mwifiex_initialize_quirks(struct pcie_service_card *card); +int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -- -2.30.0 +2.30.1 -From c8cbe2b6d4c0b8d5a6d30f6e4cab17390adf5b77 Mon Sep 17 00:00:00 2001 +From e0e72a8bd4641edbd1f4f3c386f8573b3359a713 Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Tue, 29 Sep 2020 17:32:22 +0900 Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 @@ -742,9 +742,9 @@ index 8b9dcb5070d8..3ef7440418e3 100644 int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); -- -2.30.0 +2.30.1 -From 970c5d56583d2a940706580cea083a14431d7aa0 Mon Sep 17 00:00:00 2001 +From 7690864649999f7373e5800a04b4b053efb9232a Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Wed, 30 Sep 2020 18:08:24 +0900 Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI @@ -804,9 +804,9 @@ index f0a6fa0a7ae5..34dcd84f02a6 100644 .ident = "Surface Pro 3", .matches = { -- -2.30.0 +2.30.1 -From 30ac4d3ad1f5f70e9cf12e249d8f78d680359fe0 Mon Sep 17 00:00:00 2001 +From 9db151b0136ea9d7d9170020e0c74d45fa17a824 Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Thu, 24 Sep 2020 01:56:34 +0900 Subject: [PATCH] mwifiex: pcie: use shutdown_sw()/reinit_sw() on @@ -946,9 +946,9 @@ index 94561ddaf126..7b25335f1df3 100644 return 0; } -- -2.30.0 +2.30.1 -From 0e150dd486af10093035f0cc54a1e3a67cf11b65 Mon Sep 17 00:00:00 2001 +From 3b864ce43d794995d3f3ea182ad5bff74be4a573 Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Mon, 24 Aug 2020 17:11:35 +0900 Subject: [PATCH] mwifiex: pcie: add enable_device_dump module parameter @@ -995,9 +995,9 @@ index 7b25335f1df3..f7e0b86eb553 100644 if (!adapter->devdump_data) { mwifiex_dbg(adapter, ERROR, -- -2.30.0 +2.30.1 -From a17560e923a7e1e8928e051bf2edecdf5d4dafc5 Mon Sep 17 00:00:00 2001 +From 31ab9786797227365c34354d842efe0aaf6e62dd Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Sun, 4 Oct 2020 00:11:49 +0900 Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ @@ -1150,9 +1150,9 @@ index 3ef7440418e3..a95ebac06e13 100644 void mwifiex_initialize_quirks(struct pcie_service_card *card); int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -- -2.30.0 +2.30.1 -From e1b592170064cb85d811337b40ad0f30ec7526f8 Mon Sep 17 00:00:00 2001 +From 9c5b054473350dae8139e9b5c1ceea86b0560264 Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Sun, 4 Oct 2020 00:25:48 +0900 Subject: [PATCH] mwifiex: add allow_ps_mode module parameter @@ -1212,9 +1212,9 @@ index a6b9dc6700b1..943bc1e8ceae 100644 } -- -2.30.0 +2.30.1 -From cad3b0ba3e206ec40d3f425da7f7308d2f43556b Mon Sep 17 00:00:00 2001 +From 635922ff9b7d620d892a0bf5757dbc852d432a2e Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Sun, 4 Oct 2020 00:38:48 +0900 Subject: [PATCH] mwifiex: print message when changing ps_mode @@ -1247,9 +1247,9 @@ index 943bc1e8ceae..a2eb8df8d385 100644 } -- -2.30.0 +2.30.1 -From dfa6f63ee590f00a02c0cf7fbb899a573f034ed0 Mon Sep 17 00:00:00 2001 +From c53651a257e2f7af1daa566df43e7724ac75e04f Mon Sep 17 00:00:00 2001 From: Tsuchiya Yuto Date: Sun, 4 Oct 2020 00:59:37 +0900 Subject: [PATCH] mwifiex: disable ps_mode explicitly by default instead @@ -1295,5 +1295,5 @@ index d3a968ef21ef..9b7b52fbc9c4 100644 if (drcs) { -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0003-ipts.patch b/patches/5.10/0003-ipts.patch index 1f301d80b..3e9383092 100644 --- a/patches/5.10/0003-ipts.patch +++ b/patches/5.10/0003-ipts.patch @@ -1,33 +1,4 @@ -From ea5fed3f7755a0753d74233fed09257e2f0d3ba7 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Fri, 25 Sep 2020 18:06:05 +0200 -Subject: [PATCH] mei: Remove client devices before shutting down - -Patchset: ipts ---- - drivers/misc/mei/init.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c -index bcee77768b91..21ed765003e1 100644 ---- a/drivers/misc/mei/init.c -+++ b/drivers/misc/mei/init.c -@@ -302,10 +302,10 @@ void mei_stop(struct mei_device *dev) - { - dev_dbg(dev->dev, "stopping the device.\n"); - -+ mei_cl_bus_remove_devices(dev); - mutex_lock(&dev->device_lock); - mei_set_devstate(dev, MEI_DEV_POWER_DOWN); - mutex_unlock(&dev->device_lock); -- mei_cl_bus_remove_devices(dev); - - mei_cancel_work(dev); - --- -2.30.0 - -From 2b77466eb637e9418cb661b61e3c18de800c3c6f Mon Sep 17 00:00:00 2001 +From b0ddc5d69f3ed78e7689a7d1e143567fbc16e55e Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Thu, 30 Jul 2020 13:21:53 +0200 Subject: [PATCH] misc: mei: Add missing IPTS device IDs @@ -63,9 +34,9 @@ index 1de9ef7a272b..e12484840f88 100644 {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)}, -- -2.30.0 +2.30.1 -From 99cb44358ee4d8ed8be3f1e01963a5e264e010c9 Mon Sep 17 00:00:00 2001 +From 23313b15879627928315f6d97d1002264dc511f7 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Thu, 6 Aug 2020 11:20:41 +0200 Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus @@ -79,18 +50,18 @@ Patchset: ipts drivers/misc/Makefile | 1 + drivers/misc/ipts/Kconfig | 17 ++ drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 48 +++++ - drivers/misc/ipts/control.c | 73 ++++++++ - drivers/misc/ipts/control.h | 23 +++ - drivers/misc/ipts/mei.c | 128 ++++++++++++++ - drivers/misc/ipts/protocol.h | 319 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 183 +++++++++++++++++++ - drivers/misc/ipts/receiver.h | 17 ++ - drivers/misc/ipts/resources.c | 134 ++++++++++++++ - drivers/misc/ipts/resources.h | 18 ++ - drivers/misc/ipts/uapi.c | 190 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1211 insertions(+) + drivers/misc/ipts/context.h | 47 ++++++ + drivers/misc/ipts/control.c | 76 +++++++++ + drivers/misc/ipts/control.h | 22 +++ + drivers/misc/ipts/mei.c | 118 ++++++++++++++ + drivers/misc/ipts/protocol.h | 284 ++++++++++++++++++++++++++++++++++ + drivers/misc/ipts/receiver.c | 188 ++++++++++++++++++++++ + drivers/misc/ipts/receiver.h | 16 ++ + drivers/misc/ipts/resources.c | 128 +++++++++++++++ + drivers/misc/ipts/resources.h | 17 ++ + drivers/misc/ipts/uapi.c | 211 +++++++++++++++++++++++++ + drivers/misc/ipts/uapi.h | 47 ++++++ + 15 files changed, 1185 insertions(+) create mode 100644 drivers/misc/ipts/Kconfig create mode 100644 drivers/misc/ipts/Makefile create mode 100644 drivers/misc/ipts/context.h @@ -167,10 +138,10 @@ index 000000000000..8f58b9adbc94 + diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h new file mode 100644 -index 000000000000..6e8eba3a47e5 +index 000000000000..f4b06a2d3f72 --- /dev/null +++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,48 @@ +@@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation @@ -218,13 +189,12 @@ index 000000000000..6e8eba3a47e5 +}; + +#endif /* _IPTS_CONTEXT_H_ */ -+ diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c new file mode 100644 -index 000000000000..98787d7ea292 +index 000000000000..24d3d19a667a --- /dev/null +++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,73 @@ +@@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation @@ -240,8 +210,8 @@ index 000000000000..98787d7ea292 +#include "resources.h" +#include "uapi.h" + -+int ipts_control_send(struct ipts_context *ipts, -+ u32 code, void *payload, size_t size) ++int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, ++ size_t size) +{ + int ret; + struct ipts_command cmd; @@ -286,6 +256,10 @@ index 000000000000..98787d7ea292 + + ipts_uapi_unlink(); + ipts_resources_free(ipts); ++ ++ if (!mei_cldev_enabled(ipts->cldev)) ++ return 0; ++ + return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); +} + @@ -297,13 +271,12 @@ index 000000000000..98787d7ea292 + ipts->restart = true; + return ipts_control_stop(ipts); +} -+ diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h new file mode 100644 -index 000000000000..2b3172c16063 +index 000000000000..4ee0ceb84749 --- /dev/null +++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,23 @@ +@@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation @@ -319,20 +292,19 @@ index 000000000000..2b3172c16063 + +#include "context.h" + -+int ipts_control_send(struct ipts_context *ipts, -+ u32 cmd, void *payload, size_t size); ++int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, ++ size_t size); +int ipts_control_start(struct ipts_context *ipts); +int ipts_control_restart(struct ipts_context *ipts); +int ipts_control_stop(struct ipts_context *ipts); + +#endif /* _IPTS_CONTROL_H_ */ -+ diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c new file mode 100644 -index 000000000000..b74e45c55b62 +index 000000000000..2945809d5b46 --- /dev/null +++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,128 @@ +@@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation @@ -366,7 +338,7 @@ index 000000000000..b74e45c55b62 +} + +static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) ++ const struct mei_cl_device_id *id) +{ + int ret; + struct ipts_context *ipts; @@ -382,7 +354,7 @@ index 000000000000..b74e45c55b62 + return ret; + } + -+ ipts = kzalloc(sizeof(struct ipts_context), GFP_KERNEL); ++ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); + if (!ipts) { + mei_cldev_disable(cldev); + return -ENOMEM; @@ -400,19 +372,10 @@ index 000000000000..b74e45c55b62 + +static int ipts_mei_remove(struct mei_cl_device *cldev) +{ -+ int i; + struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); + -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ + mei_cldev_disable(cldev); ++ ipts_control_stop(ipts); + kfree(ipts); + + return 0; @@ -420,7 +383,7 @@ index 000000000000..b74e45c55b62 + +static struct mei_cl_device_id ipts_mei_device_id_table[] = { + { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ { }, ++ {}, +}; +MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); + @@ -460,13 +423,12 @@ index 000000000000..b74e45c55b62 + +module_init(ipts_mei_init); +module_exit(ipts_mei_exit); -+ diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h new file mode 100644 -index 000000000000..2e179cbb9af3 +index 000000000000..381068504d46 --- /dev/null +++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,319 @@ +@@ -0,0 +1,284 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation @@ -483,8 +445,9 @@ index 000000000000..2e179cbb9af3 +/* + * The MEI client ID for IPTS functionality. + */ -+#define IPTS_MEI_UUID UUID_LE(0x3e8d0870, 0x271a, 0x4208, \ -+ 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) ++#define IPTS_MEI_UUID \ ++ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ ++ 0x02, 0xae, 0x04) + +/* + * Queries the device for vendor specific information. @@ -543,149 +506,99 @@ index 000000000000..2e179cbb9af3 +#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 + +/* -+ * Singletouch mode is a fallback that does not support -+ * a stylus or more than one touch input. The data is -+ * received as a HID report with report ID 64. ++ * Instructs the ME to reset the touch sensor. ++ * ++ * The command must contain struct ipts_reset_sensor_cmd as payload. ++ * The response will not contain any payload. + */ -+#define IPTS_MODE_SINGLETOUCH 0x0 ++#define IPTS_CMD_RESET_SENSOR 0x0000000B ++#define IPTS_RSP_RESET_SENSOR 0x8000000B + -+/* -+ * Multitouch mode is the "proper" operation mode for IPTS. It will -+ * return stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace before it can be used. ++/** ++ * enum ipts_status - Possible status codes returned by IPTS commands. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * The host must wait for a response before sending another ++ * command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it ++ * has not been initialized yet, or the system is improperly ++ * configured. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. ++ * The host can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. + */ -+#define IPTS_MODE_MULTITOUCH 0x1 -+ -+/* -+ * Operation completed successfully. -+ */ -+#define IPTS_STATUS_SUCCESS 0x0 -+ -+/* -+ * Command contained a payload with invalid parameters. -+ */ -+#define IPTS_STATUS_INVALID_PARAMS 0x1 -+ -+/* -+ * ME was unable to validate buffer addresses supplied by the host. -+ */ -+#define IPTS_STATUS_ACCESS_DENIED 0x2 -+ -+/* -+ * Command contained a payload with an invalid size. -+ */ -+#define IPTS_STATUS_CMD_SIZE_ERROR 0x3 -+ -+/* -+ * Buffer addresses have not been set, or the -+ * device is not ready for operation yet. -+ */ -+#define IPTS_STATUS_NOT_READY 0x4 -+ -+/* -+ * There is an outstanding command of the same type. The host must -+ * wait for a response before sending another command of the same type. -+ */ -+#define IPTS_STATUS_REQUEST_OUTSTANDING 0x5 -+ -+/* -+ * No sensor could be found. Either no sensor is connected, it has not -+ * been initialized yet, or the system is improperly configured. -+ */ -+#define IPTS_STATUS_NO_SENSOR_FOUND 0x6 -+ -+/* -+ * Not enough free memory for requested operation. -+ */ -+#define IPTS_STATUS_OUT_OF_MEMORY 0x7 -+ -+/* -+ * An unexpected error occured. -+ */ -+#define IPTS_STATUS_INTERNAL_ERROR 0x8 -+ -+/* -+ * The sensor has been disabled / reset and must be reinitialized. -+ */ -+#define IPTS_STATUS_SENSOR_DISABLED 0x9 -+ -+/* -+ * Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ */ -+#define IPTS_STATUS_COMPAT_CHECK_FAIL 0xA -+ -+/* -+ * The sensor went through a reset initiated by the ME / the host. -+ */ -+#define IPTS_STATUS_SENSOR_EXPECTED_RESET 0xB -+ -+/* -+ * The sensor went through an unexpected reset. -+ */ -+#define IPTS_STATUS_SENSOR_UNEXPECTED_RESET 0xC -+ -+/* -+ * Requested sensor reset failed to complete. -+ */ -+#define IPTS_STATUS_RESET_FAILED 0xD -+ -+/* -+ * The operation timed out. -+ */ -+#define IPTS_STATUS_TIMEOUT 0xE -+ -+/* -+ * Test mode pattern did not match expected values. -+ */ -+#define IPTS_STATUS_TEST_MODE_FAIL 0xF -+ -+/* -+ * The sensor reported fatal error during reset sequence. -+ * Futher progress is not possible. -+ */ -+#define IPTS_STATUS_SENSOR_FAIL_FATAL 0x10 -+ -+/* -+ * The sensor reported fatal error during reset sequence. -+ * The host can attempt to continue. -+ */ -+#define IPTS_STATUS_SENSOR_FAIL_NONFATAL 0x11 -+ -+/* -+ * The sensor reported invalid capabilities. -+ */ -+#define IPTS_STATUS_INVALID_DEVICE_CAPS 0x12 -+ -+/* -+ * The command cannot be completed until Quiesce IO flow has completed. -+ */ -+#define IPTS_STATUS_QUIESCE_IO_IN_PROGRESS 0x13 ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0, ++ IPTS_STATUS_INVALID_PARAMS = 1, ++ IPTS_STATUS_ACCESS_DENIED = 2, ++ IPTS_STATUS_CMD_SIZE_ERROR = 3, ++ IPTS_STATUS_NOT_READY = 4, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 5, ++ IPTS_STATUS_NO_SENSOR_FOUND = 6, ++ IPTS_STATUS_OUT_OF_MEMORY = 7, ++ IPTS_STATUS_INTERNAL_ERROR = 8, ++ IPTS_STATUS_SENSOR_DISABLED = 9, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, ++ IPTS_STATUS_RESET_FAILED = 13, ++ IPTS_STATUS_TIMEOUT = 14, ++ IPTS_STATUS_TEST_MODE_FAIL = 15, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, ++}; + +/* + * The amount of buffers that is used for IPTS + */ +#define IPTS_BUFFERS 16 + -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++/** ++ * enum ipts_mode - Operation mode for IPTS hardware ++ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. ++ * The data is received as a HID report with ID 64. ++ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return ++ * stylus data as well as capacitive heatmap touch data. ++ * This data needs to be processed in userspace. ++ */ ++enum ipts_mode { ++ IPTS_MODE_SINGLETOUCH = 0, ++ IPTS_MODE_MULTITOUCH = 1, ++}; + +/** + * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * -+ * @mode: The mode that IPTS should operate in. (IPTS_MODE_*) -+ * -+ * This driver only supports multitouch mode. Singletouch mode -+ * requires a different control flow that is not implemented. ++ * @mode: The mode that IPTS should operate in. + */ +struct ipts_set_mode_cmd { -+ u32 mode; ++ enum ipts_mode mode; + u8 reserved[12]; +} __packed; + ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ +/** + * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * + * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. + * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. + * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. @@ -696,8 +609,8 @@ index 000000000000..2e179cbb9af3 + * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. + * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. + * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Constant value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Constant value. (IPTS_WORKQUEUE_SIZE) ++ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) ++ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) + * + * The data buffers are buffers that get filled with touch data by the ME. + * The doorbell buffer is a u32 that gets incremented by the ME once a data @@ -705,8 +618,8 @@ index 000000000000..2e179cbb9af3 + * + * The other buffers are required for using GuC submission with binary + * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated to ensure proper -+ * operation. ++ * they are not used anymore, but they need to be allocated and passed, ++ * otherwise the hardware will refuse to start. + */ +struct ipts_set_mem_window_cmd { + u32 data_buffer_addr_lower[IPTS_BUFFERS]; @@ -728,7 +641,6 @@ index 000000000000..2e179cbb9af3 + +/** + * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * + * @buffer: The buffer that the ME should refill. + */ +struct ipts_feedback_cmd { @@ -737,9 +649,27 @@ index 000000000000..2e179cbb9af3 +} __packed; + +/** ++ * enum ipts_reset_type - Possible ways of resetting the touch sensor ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0, ++ IPTS_RESET_TYPE_SOFT = 1, ++}; ++ ++/** ++ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. ++ * @type: What type of reset should be performed. ++ */ ++struct ipts_reset_sensor_cmd { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++/** + * struct ipts_command - A message sent from the host to the ME. -+ * -+ * @code: The message code describing the command (IPTS_CMD_*) ++ * @code: The message code describing the command. (see IPTS_CMD_*) + * @payload: Payload for the command, or 0 if no payload is required. + */ +struct ipts_command { @@ -749,14 +679,13 @@ index 000000000000..2e179cbb9af3 + +/** + * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * + * @vendor_id: Vendor ID of the touch sensor. + * @device_id: Device ID of the touch sensor. + * @hw_rev: Hardware revision of the touch sensor. + * @fw_rev: Firmware revision of the touch sensor. + * @data_size: Required size of one data buffer. + * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS (IPTS_MODE_*) ++ * @mode: Current operation mode of IPTS. + * @max_contacts: The amount of concurrent touches supported by the sensor. + */ +struct ipts_get_device_info_rsp { @@ -766,32 +695,30 @@ index 000000000000..2e179cbb9af3 + u32 fw_rev; + u32 data_size; + u32 feedback_size; -+ u32 mode; ++ enum ipts_mode mode; + u8 max_contacts; + u8 reserved[19]; +} __packed; + +/** + * struct ipts_response - A message sent from the ME to the host. -+ * -+ * @code: The message code describing the response (IPTS_RSP_*) -+ * @status: The status code returned by the command. (IPTS_STATUS_*) ++ * @code: The message code describing the response. (see IPTS_RSP_*) ++ * @status: The status code returned by the command. + * @payload: Payload returned by the command. + */ +struct ipts_response { + u32 code; -+ u32 status; ++ enum ipts_status status; + u8 payload[80]; +} __packed; + +#endif /* _IPTS_PROTOCOL_H_ */ -+ diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c new file mode 100644 -index 000000000000..3660a1dcfff9 +index 000000000000..916ba3ec211b --- /dev/null +++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,183 @@ +@@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation @@ -809,18 +736,18 @@ index 000000000000..3660a1dcfff9 +#include "resources.h" + +static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) ++ struct ipts_response *rsp) +{ + struct ipts_set_mode_cmd cmd; + + memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); ++ sizeof(struct ipts_get_device_info_rsp)); + + memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); + cmd.mode = IPTS_MODE_MULTITOUCH; + -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, -+ &cmd, sizeof(struct ipts_set_mode_cmd)); ++ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, ++ sizeof(struct ipts_set_mode_cmd)); +} + +static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) @@ -862,15 +789,14 @@ index 000000000000..3660a1dcfff9 + cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; + cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; + -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, -+ &cmd, sizeof(struct ipts_set_mem_window_cmd)); ++ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, ++ sizeof(struct ipts_set_mem_window_cmd)); +} + +static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) +{ + dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, -+ ipts->device_info.device_id); ++ ipts->device_info.vendor_id, ipts->device_info.device_id); + ipts->status = IPTS_HOST_STATUS_STARTED; + + return ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); @@ -878,15 +804,22 @@ index 000000000000..3660a1dcfff9 + +static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) +{ ++ ipts->status = IPTS_HOST_STATUS_STOPPED; ++ + if (ipts->restart) + return ipts_control_start(ipts); + -+ ipts->status = IPTS_HOST_STATUS_STOPPED; + return 0; +} + ++static bool ipts_receiver_sensor_was_reset(u32 status) ++{ ++ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || ++ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; ++} ++ +static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) ++ struct ipts_response *rsp) +{ + bool error; + @@ -909,10 +842,10 @@ index 000000000000..3660a1dcfff9 + if (!error) + return false; + -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", -+ rsp->code, rsp->status); ++ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, ++ rsp->status); + -+ if (rsp->code == IPTS_STATUS_SENSOR_UNEXPECTED_RESET) { ++ if (ipts_receiver_sensor_was_reset(rsp->status)) { + dev_err(ipts->dev, "Sensor was reset\n"); + + if (ipts_control_restart(ipts)) @@ -923,7 +856,7 @@ index 000000000000..3660a1dcfff9 +} + +static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) ++ struct ipts_response *rsp) +{ + int ret; + @@ -952,7 +885,7 @@ index 000000000000..3660a1dcfff9 + return; + + dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); ++ rsp->code, ret); + + if (ipts_control_stop(ipts)) + dev_err(ipts->dev, "Failed to stop IPTS\n"); @@ -974,13 +907,12 @@ index 000000000000..3660a1dcfff9 + + ipts_receiver_handle_response(ipts, &rsp); +} -+ diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h new file mode 100644 -index 000000000000..c061d57a9320 +index 000000000000..7f075afa7ef8 --- /dev/null +++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,17 @@ +@@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation @@ -997,13 +929,12 @@ index 000000000000..c061d57a9320 +void ipts_receiver_callback(struct mei_cl_device *cldev); + +#endif /* _IPTS_RECEIVER_H_ */ -+ diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c new file mode 100644 -index 000000000000..bcf4dd8d7e95 +index 000000000000..8e3a2409e438 --- /dev/null +++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,134 @@ +@@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation @@ -1030,7 +961,7 @@ index 000000000000..bcf4dd8d7e95 + continue; + + dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); ++ buffers[i].address, buffers[i].dma_address); + + buffers[i].address = NULL; + buffers[i].dma_address = 0; @@ -1042,7 +973,7 @@ index 000000000000..bcf4dd8d7e95 + continue; + + dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); ++ buffers[i].address, buffers[i].dma_address); + + buffers[i].address = NULL; + buffers[i].dma_address = 0; @@ -1050,8 +981,8 @@ index 000000000000..bcf4dd8d7e95 + + if (ipts->doorbell.address) { + dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); ++ ipts->doorbell.address, ++ ipts->doorbell.dma_address); + + ipts->doorbell.address = NULL; + ipts->doorbell.dma_address = 0; @@ -1059,8 +990,8 @@ index 000000000000..bcf4dd8d7e95 + + if (ipts->workqueue.address) { + dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); ++ ipts->workqueue.address, ++ ipts->workqueue.dma_address); + + ipts->workqueue.address = NULL; + ipts->workqueue.dma_address = 0; @@ -1068,8 +999,8 @@ index 000000000000..bcf4dd8d7e95 + + if (ipts->host2me.address) { + dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); ++ ipts->host2me.address, ++ ipts->host2me.dma_address); + + ipts->host2me.address = NULL; + ipts->host2me.dma_address = 0; @@ -1086,10 +1017,9 @@ index 000000000000..bcf4dd8d7e95 + + buffers = ipts->data; + for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = dma_alloc_coherent(ipts->dev, -+ data_buffer_size, -+ &buffers[i].dma_address, -+ GFP_KERNEL); ++ buffers[i].address = ++ dma_alloc_coherent(ipts->dev, data_buffer_size, ++ &buffers[i].dma_address, GFP_KERNEL); + + if (!buffers[i].address) + goto release_resources; @@ -1097,35 +1027,31 @@ index 000000000000..bcf4dd8d7e95 + + buffers = ipts->feedback; + for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = dma_alloc_coherent(ipts->dev, -+ feedback_buffer_size, -+ &buffers[i].dma_address, -+ GFP_KERNEL); ++ buffers[i].address = ++ dma_alloc_coherent(ipts->dev, feedback_buffer_size, ++ &buffers[i].dma_address, GFP_KERNEL); + + if (!buffers[i].address) + goto release_resources; + } + -+ ipts->doorbell.address = dma_alloc_coherent(ipts->dev, -+ sizeof(u32), -+ &ipts->doorbell.dma_address, -+ GFP_KERNEL); ++ ipts->doorbell.address = ++ dma_alloc_coherent(ipts->dev, sizeof(u32), ++ &ipts->doorbell.dma_address, GFP_KERNEL); + + if (!ipts->doorbell.address) + goto release_resources; + -+ ipts->workqueue.address = dma_alloc_coherent(ipts->dev, -+ sizeof(u32), -+ &ipts->workqueue.dma_address, -+ GFP_KERNEL); ++ ipts->workqueue.address = ++ dma_alloc_coherent(ipts->dev, sizeof(u32), ++ &ipts->workqueue.dma_address, GFP_KERNEL); + + if (!ipts->workqueue.address) + goto release_resources; + -+ ipts->host2me.address = dma_alloc_coherent(ipts->dev, -+ feedback_buffer_size, -+ &ipts->host2me.dma_address, -+ GFP_KERNEL); ++ ipts->host2me.address = ++ dma_alloc_coherent(ipts->dev, feedback_buffer_size, ++ &ipts->host2me.dma_address, GFP_KERNEL); + + if (!ipts->workqueue.address) + goto release_resources; @@ -1137,13 +1063,12 @@ index 000000000000..bcf4dd8d7e95 + ipts_resources_free(ipts); + return -ENOMEM; +} -+ diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h new file mode 100644 -index 000000000000..8f55af7aae0f +index 000000000000..fdac0eee9156 --- /dev/null +++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,18 @@ +@@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2016 Intel Corporation @@ -1161,13 +1086,12 @@ index 000000000000..8f55af7aae0f +void ipts_resources_free(struct ipts_context *ipts); + +#endif /* _IPTS_RESOURCES_H_ */ -+ diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c new file mode 100644 -index 000000000000..1b59dbc9a1ad +index 000000000000..8107a027223f --- /dev/null +++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,190 @@ +@@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Intel Corporation @@ -1190,8 +1114,8 @@ index 000000000000..1b59dbc9a1ad + +struct ipts_uapi uapi; + -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, -+ size_t count, loff_t *offset) ++static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, ++ loff_t *offset) +{ + int buffer; + int maxbytes; @@ -1213,10 +1137,13 @@ index 000000000000..1b59dbc9a1ad +} + +static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) ++ unsigned long arg) +{ + void __user *buffer = (void __user *)arg; -+ u8 ready = ipts->status == IPTS_HOST_STATUS_STARTED; ++ u8 ready = 0; ++ ++ if (ipts) ++ ready = ipts->status == IPTS_HOST_STATUS_STARTED; + + if (copy_to_user(buffer, &ready, sizeof(u8))) + return -EFAULT; @@ -1225,12 +1152,12 @@ index 000000000000..1b59dbc9a1ad +} + +static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) ++ unsigned long arg) +{ + struct ipts_device_info info; + void __user *buffer = (void __user *)arg; + -+ if (ipts->status != IPTS_HOST_STATUS_STARTED) ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + info.vendor = ipts->device_info.vendor_id; @@ -1246,11 +1173,11 @@ index 000000000000..1b59dbc9a1ad +} + +static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) ++ unsigned long arg) +{ + void __user *buffer = (void __user *)arg; + -+ if (ipts->status != IPTS_HOST_STATUS_STARTED) ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) @@ -1260,19 +1187,39 @@ index 000000000000..1b59dbc9a1ad +} + +static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) ++ struct file *file) +{ + int ret; + struct ipts_feedback_cmd cmd; + -+ if (ipts->status != IPTS_HOST_STATUS_STARTED) ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) + return -ENODEV; + + memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); + cmd.buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); + -+ ret = ipts_control_send(ipts, IPTS_CMD_FEEDBACK, -+ &cmd, sizeof(struct ipts_feedback_cmd)); ++ ret = ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, ++ sizeof(struct ipts_feedback_cmd)); ++ ++ if (ret) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) ++{ ++ int ret; ++ struct ipts_reset_sensor_cmd cmd; ++ ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) ++ return -ENODEV; ++ ++ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); ++ cmd.type = IPTS_RESET_TYPE_SOFT; ++ ++ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, ++ sizeof(struct ipts_reset_sensor_cmd)); + + if (ret) + return -EFAULT; @@ -1281,13 +1228,10 @@ index 000000000000..1b59dbc9a1ad +} + +static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) ++ unsigned long arg) +{ + struct ipts_context *ipts = uapi.ipts; + -+ if (!ipts) -+ return -ENODEV; -+ + switch (cmd) { + case IPTS_IOCTL_GET_DEVICE_READY: + return ipts_uapi_ioctl_get_device_ready(ipts, arg); @@ -1297,6 +1241,8 @@ index 000000000000..1b59dbc9a1ad + return ipts_uapi_ioctl_get_doorbell(ipts, arg); + case IPTS_IOCTL_SEND_FEEDBACK: + return ipts_uapi_ioctl_send_feedback(ipts, file); ++ case IPTS_IOCTL_SEND_RESET: ++ return ipts_uapi_ioctl_send_reset(ipts); + default: + return -ENOTTY; + } @@ -1335,8 +1281,8 @@ index 000000000000..1b59dbc9a1ad + cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); + + for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, -+ MKDEV(major, i), NULL, "ipts/%d", i); ++ device_create(uapi.class, NULL, MKDEV(major, i), NULL, ++ "ipts/%d", i); + } + + return 0; @@ -1357,10 +1303,9 @@ index 000000000000..1b59dbc9a1ad + unregister_chrdev_region(MKDEV(major, 0), MINORMASK); + class_destroy(uapi.class); +} -+ diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h new file mode 100644 -index 000000000000..4c667bb6a7f2 +index 000000000000..53fb86a88f97 --- /dev/null +++ b/drivers/misc/ipts/uapi.h @@ -0,0 +1,47 @@ @@ -1400,8 +1345,9 @@ index 000000000000..4c667bb6a7f2 + +#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) +#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) ++#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) +#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) ++#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) + +void ipts_uapi_link(struct ipts_context *ipts); +void ipts_uapi_unlink(void); @@ -1410,7 +1356,6 @@ index 000000000000..4c667bb6a7f2 +void ipts_uapi_free(void); + +#endif /* _IPTS_UAPI_H_ */ -+ -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0004-surface-gpe.patch b/patches/5.10/0004-surface-gpe.patch index 6b2486371..9bc615abb 100644 --- a/patches/5.10/0004-surface-gpe.patch +++ b/patches/5.10/0004-surface-gpe.patch @@ -1,4 +1,4 @@ -From c15d0471a2ab876031795b0002666e2d774a27bb Mon Sep 17 00:00:00 2001 +From ae725f58a769c0b98d72e612af0657fd21801ef4 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 16 Aug 2020 23:39:56 +0200 Subject: [PATCH] platform/x86: Add Driver to set up lid GPEs on MS Surface @@ -397,5 +397,5 @@ index 000000000000..86f6991b1215 +MODULE_LICENSE("GPL"); +MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*"); -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0005-surface-sam-over-hid.patch b/patches/5.10/0005-surface-sam-over-hid.patch index 10a5f2561..325f0c5ee 100644 --- a/patches/5.10/0005-surface-sam-over-hid.patch +++ b/patches/5.10/0005-surface-sam-over-hid.patch @@ -1,4 +1,4 @@ -From d46cbd24104243e4e7c4c01d28ed503fab46c8ea Mon Sep 17 00:00:00 2001 +From 7fcb1bb56b0aeef64bb650f5cd7e40d56c1b8a06 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 25 Jul 2020 17:19:53 +0200 Subject: [PATCH] i2c: acpi: Implement RawBytes read access @@ -108,9 +108,9 @@ index 37c510d9347a..aed579942436 100644 dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", accessor_type, client->addr); -- -2.30.0 +2.30.1 -From ebd07cf190014ef57f7bc9f179cb92c2bcd54d90 Mon Sep 17 00:00:00 2001 +From 636027109cba56a411620d37a35fb9b56d6ec302 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 6 Sep 2020 04:01:19 +0200 Subject: [PATCH] platform/x86: Add driver for Surface Book 1 dGPU switch @@ -331,5 +331,5 @@ index 000000000000..8c66ed5110fd +MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); +MODULE_LICENSE("GPL"); -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0006-surface-sam.patch b/patches/5.10/0006-surface-sam.patch index d34c8bd69..986f3a5cf 100644 --- a/patches/5.10/0006-surface-sam.patch +++ b/patches/5.10/0006-surface-sam.patch @@ -1,8 +1,7 @@ -From 248862ab189d99edec0f98b5bb546c7311b435bb Mon Sep 17 00:00:00 2001 +From ba3917a0ce79087cc77219c47d2aa9fb9b7f0b9b Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 17 Aug 2020 01:23:20 +0200 -Subject: [PATCH] misc: surface_sam: Add file2alias support for Surface SAM - devices +Subject: [PATCH] Add file2alias support for Surface Aggregator Module devices Implement file2alias support for Surface System Aggregator Module (SSAM) devices. This allows modules to be auto-loaded for specific devices via @@ -98,12 +97,13 @@ index 2417dd1dee33..a6c583362b92 100644 /* Create MODULE_ALIAS() statements. -- -2.30.0 +2.30.1 -From c510e853cb20d7a1936322825993da0788b6e96e Mon Sep 17 00:00:00 2001 +From 18dfadbd32eae7eb88d5062564daf977c62d9c67 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 17 Aug 2020 01:44:30 +0200 -Subject: [PATCH] misc: Add support for Surface System Aggregator Module +Subject: [PATCH] platform/x86: Add support for Surface System Aggregator + Module Add support for the Surface System Aggregator Module (SSAM), an embedded controller that can be found on 5th and later generation Microsoft @@ -129,32 +129,40 @@ Patchset: surface-sam .../surface_aggregator/internal.rst | 577 ++++ .../surface_aggregator/overview.rst | 77 + .../driver-api/surface_aggregator/ssh.rst | 344 +++ - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/surface_aggregator/Kconfig | 67 + - drivers/misc/surface_aggregator/Makefile | 18 + - drivers/misc/surface_aggregator/bus.c | 415 +++ - drivers/misc/surface_aggregator/bus.h | 27 + - .../misc/surface_aggregator/clients/Kconfig | 134 + - .../misc/surface_aggregator/clients/Makefile | 10 + - .../clients/surface_acpi_notify.c | 886 ++++++ - .../clients/surface_aggregator_cdev.c | 322 ++ - .../clients/surface_aggregator_registry.c | 664 +++++ - .../clients/surface_battery.c | 1168 ++++++++ - .../surface_aggregator/clients/surface_dtx.c | 1277 ++++++++ - .../surface_aggregator/clients/surface_hid.c | 924 ++++++ - .../clients/surface_perfmode.c | 122 + - drivers/misc/surface_aggregator/controller.c | 2579 +++++++++++++++++ - drivers/misc/surface_aggregator/controller.h | 285 ++ - drivers/misc/surface_aggregator/core.c | 839 ++++++ - drivers/misc/surface_aggregator/ssh_msgb.h | 205 ++ - .../surface_aggregator/ssh_packet_layer.c | 2074 +++++++++++++ - .../surface_aggregator/ssh_packet_layer.h | 190 ++ - drivers/misc/surface_aggregator/ssh_parser.c | 228 ++ - drivers/misc/surface_aggregator/ssh_parser.h | 155 + + drivers/hid/Kconfig | 4 +- + drivers/hid/Makefile | 2 + + drivers/hid/surface-hid/Kconfig | 42 + + drivers/hid/surface-hid/Makefile | 7 + + drivers/hid/surface-hid/surface_hid.c | 256 ++ + drivers/hid/surface-hid/surface_hid_core.c | 272 ++ + drivers/hid/surface-hid/surface_hid_core.h | 77 + + drivers/hid/surface-hid/surface_kbd.c | 303 ++ + drivers/platform/x86/Kconfig | 102 + + drivers/platform/x86/Makefile | 7 + + drivers/platform/x86/surface_acpi_notify.c | 886 ++++++ + .../platform/x86/surface_aggregator/Kconfig | 69 + + .../platform/x86/surface_aggregator/Makefile | 17 + + drivers/platform/x86/surface_aggregator/bus.c | 415 +++ + drivers/platform/x86/surface_aggregator/bus.h | 27 + + .../x86/surface_aggregator/controller.c | 2579 +++++++++++++++++ + .../x86/surface_aggregator/controller.h | 285 ++ + .../platform/x86/surface_aggregator/core.c | 839 ++++++ + .../x86/surface_aggregator/ssh_msgb.h | 205 ++ + .../x86/surface_aggregator/ssh_packet_layer.c | 2074 +++++++++++++ + .../x86/surface_aggregator/ssh_packet_layer.h | 190 ++ + .../x86/surface_aggregator/ssh_parser.c | 228 ++ + .../x86/surface_aggregator/ssh_parser.h | 154 + .../surface_aggregator/ssh_request_layer.c | 1263 ++++++++ .../surface_aggregator/ssh_request_layer.h | 143 + - drivers/misc/surface_aggregator/trace.h | 632 ++++ + .../platform/x86/surface_aggregator/trace.h | 632 ++++ + .../platform/x86/surface_aggregator_cdev.c | 322 ++ + .../x86/surface_aggregator_registry.c | 641 ++++ + drivers/platform/x86/surface_dtx.c | 1289 ++++++++ + drivers/platform/x86/surface_perfmode.c | 122 + + drivers/power/supply/Kconfig | 32 + + drivers/power/supply/Makefile | 2 + + drivers/power/supply/surface_battery.c | 901 ++++++ + drivers/power/supply/surface_charger.c | 296 ++ include/linux/mod_devicetable.h | 5 +- include/linux/surface_acpi_notify.h | 39 + include/linux/surface_aggregator/controller.h | 824 ++++++ @@ -164,7 +172,7 @@ Patchset: surface-sam include/uapi/linux/surface_aggregator/dtx.h | 146 + scripts/mod/devicetable-offsets.c | 3 +- scripts/mod/file2alias.c | 10 +- - 47 files changed, 19205 insertions(+), 7 deletions(-) + 55 files changed, 19258 insertions(+), 8 deletions(-) create mode 100644 Documentation/driver-api/surface_aggregator/client-api.rst create mode 100644 Documentation/driver-api/surface_aggregator/client.rst create mode 100644 Documentation/driver-api/surface_aggregator/clients/cdev.rst @@ -176,30 +184,34 @@ Patchset: surface-sam create mode 100644 Documentation/driver-api/surface_aggregator/internal.rst create mode 100644 Documentation/driver-api/surface_aggregator/overview.rst create mode 100644 Documentation/driver-api/surface_aggregator/ssh.rst - create mode 100644 drivers/misc/surface_aggregator/Kconfig - create mode 100644 drivers/misc/surface_aggregator/Makefile - create mode 100644 drivers/misc/surface_aggregator/bus.c - create mode 100644 drivers/misc/surface_aggregator/bus.h - create mode 100644 drivers/misc/surface_aggregator/clients/Kconfig - create mode 100644 drivers/misc/surface_aggregator/clients/Makefile - create mode 100644 drivers/misc/surface_aggregator/clients/surface_acpi_notify.c - create mode 100644 drivers/misc/surface_aggregator/clients/surface_aggregator_cdev.c - create mode 100644 drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c - create mode 100644 drivers/misc/surface_aggregator/clients/surface_battery.c - create mode 100644 drivers/misc/surface_aggregator/clients/surface_dtx.c - create mode 100644 drivers/misc/surface_aggregator/clients/surface_hid.c - create mode 100644 drivers/misc/surface_aggregator/clients/surface_perfmode.c - create mode 100644 drivers/misc/surface_aggregator/controller.c - create mode 100644 drivers/misc/surface_aggregator/controller.h - create mode 100644 drivers/misc/surface_aggregator/core.c - create mode 100644 drivers/misc/surface_aggregator/ssh_msgb.h - create mode 100644 drivers/misc/surface_aggregator/ssh_packet_layer.c - create mode 100644 drivers/misc/surface_aggregator/ssh_packet_layer.h - create mode 100644 drivers/misc/surface_aggregator/ssh_parser.c - create mode 100644 drivers/misc/surface_aggregator/ssh_parser.h - create mode 100644 drivers/misc/surface_aggregator/ssh_request_layer.c - create mode 100644 drivers/misc/surface_aggregator/ssh_request_layer.h - create mode 100644 drivers/misc/surface_aggregator/trace.h + 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 + create mode 100644 drivers/hid/surface-hid/surface_kbd.c + create mode 100644 drivers/platform/x86/surface_acpi_notify.c + create mode 100644 drivers/platform/x86/surface_aggregator/Kconfig + create mode 100644 drivers/platform/x86/surface_aggregator/Makefile + create mode 100644 drivers/platform/x86/surface_aggregator/bus.c + create mode 100644 drivers/platform/x86/surface_aggregator/bus.h + create mode 100644 drivers/platform/x86/surface_aggregator/controller.c + create mode 100644 drivers/platform/x86/surface_aggregator/controller.h + create mode 100644 drivers/platform/x86/surface_aggregator/core.c + create mode 100644 drivers/platform/x86/surface_aggregator/ssh_msgb.h + create mode 100644 drivers/platform/x86/surface_aggregator/ssh_packet_layer.c + create mode 100644 drivers/platform/x86/surface_aggregator/ssh_packet_layer.h + create mode 100644 drivers/platform/x86/surface_aggregator/ssh_parser.c + create mode 100644 drivers/platform/x86/surface_aggregator/ssh_parser.h + create mode 100644 drivers/platform/x86/surface_aggregator/ssh_request_layer.c + create mode 100644 drivers/platform/x86/surface_aggregator/ssh_request_layer.h + create mode 100644 drivers/platform/x86/surface_aggregator/trace.h + create mode 100644 drivers/platform/x86/surface_aggregator_cdev.c + create mode 100644 drivers/platform/x86/surface_aggregator_registry.c + create mode 100644 drivers/platform/x86/surface_dtx.c + create mode 100644 drivers/platform/x86/surface_perfmode.c + create mode 100644 drivers/power/supply/surface_battery.c + create mode 100644 drivers/power/supply/surface_charger.c create mode 100644 include/linux/surface_acpi_notify.h create mode 100644 include/linux/surface_aggregator/controller.h create mode 100644 include/linux/surface_aggregator/device.h @@ -2667,585 +2679,1065 @@ index 000000000000..bf007d6c9873 +(per party, effectively leading to synchronous communication regarding +frames) and at most three pending commands. The limit to synchronous frame +transfers seems to be consistent with behavior observed on Windows. -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index c795c56e8d42..391971a5909e 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -481,5 +481,6 @@ source "drivers/misc/ocxl/Kconfig" - source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" -+source "drivers/misc/surface_aggregator/Kconfig" - source "drivers/misc/ipts/Kconfig" +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 9b56226ce0d1..ee04d30d6d39 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -910,7 +910,7 @@ config HID_SONY + * Logitech Harmony adapter for Sony Playstation 3 (Bluetooth) + + config SONY_FF +- bool "Sony PS2/3/4 accessories force feedback support" ++ bool "Sony PS2/3/4 accessories force feedback support" + depends on HID_SONY + select INPUT_FF_MEMLESS + help +@@ -1184,4 +1184,6 @@ source "drivers/hid/i2c-hid/Kconfig" + + source "drivers/hid/intel-ish-hid/Kconfig" + ++source "drivers/hid/surface-hid/Kconfig" ++ endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index 9e6e3e2f2ea9..e909c4542f8b 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -58,3 +58,4 @@ obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o - obj-$(CONFIG_MISC_IPTS) += ipts/ -+obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator/ -diff --git a/drivers/misc/surface_aggregator/Kconfig b/drivers/misc/surface_aggregator/Kconfig +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 4acb583c92a6..a13d89358b0c 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -142,3 +142,5 @@ obj-$(CONFIG_I2C_HID) += i2c-hid/ + + obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ + obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-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..47dd8fdffac3 +index 000000000000..7ce9b5d641eb --- /dev/null -+++ b/drivers/misc/surface_aggregator/Kconfig -@@ -0,0 +1,67 @@ ++++ b/drivers/hid/surface-hid/Kconfig +@@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2020 Maximilian Luz -+ -+menuconfig SURFACE_AGGREGATOR -+ tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -+ depends on SERIAL_DEV_BUS -+ depends on ACPI -+ select CRC_CCITT -+ help -+ The Surface System Aggregator Module (Surface SAM or SSAM) is an -+ embedded controller (EC) found on 5th- and later-generation Microsoft -+ Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop, -+ and newer, with exception of Surface Go series devices). -+ -+ Depending on the device in question, this EC provides varying -+ functionality, including: -+ - EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation) -+ - battery status information (all devices) -+ - thermal sensor access (all devices) -+ - performance mode / cooling mode control (all devices) -+ - clipboard detachment system control (Surface Book 2 and 3) -+ - HID / keyboard input (Surface Laptops, Surface Book 3) -+ -+ This option controls whether the Surface SAM subsystem core will be -+ built. This includes a driver for the Surface Serial Hub (SSH), which -+ is the device responsible for the communication with the EC, and a -+ basic kernel interface exposing the EC functionality to other client -+ drivers, i.e. allowing them to make requests to the EC and receive -+ events from it. Selecting this option alone will not provide any -+ client drivers and therefore no functionality beyond the in-kernel -+ interface. Said functionality is the repsonsibility of the respective -+ client drivers. -+ -+ Note: While 4th-generation Surface devices also make use of a SAM EC, -+ due to a difference in the communication interface of the controller, -+ only 5th and later generations are currently supported. Specifically, -+ devices using SAM-over-SSH are supported, whereas devices using -+ SAM-over-HID, which is used on the 4th generation, are currently not -+ supported. -+ -+config SURFACE_AGGREGATOR_BUS -+ bool "Surface System Aggregator Module Bus" ++menu "Surface System Aggregator Module HID support" + depends on SURFACE_AGGREGATOR -+ default y ++ 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 -+ Expands the Surface System Aggregator Module (SSAM) core driver by -+ providing a dedicated bus and client-device type. ++ Driver to support integrated HID devices on newer Microsoft Surface ++ models. + -+ This bus and device type are intended to provide and simplify support -+ for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are -+ not auto-detectable via the conventional means (e.g. ACPI). ++ 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. + -+config SURFACE_AGGREGATOR_ERROR_INJECTION -+ bool "Surface System Aggregator Module Error Injection Capabilities" -+ depends on SURFACE_AGGREGATOR -+ depends on FUNCTION_ERROR_INJECTION ++ 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. ++ ++config SURFACE_KBD ++ tristate "HID keyboard transport driver for Surface System Aggregator Module" ++ select SURFACE_HID_CORE + help -+ Provides error-injection capabilities for the Surface System -+ Aggregator Module subsystem and Surface Serial Hub driver. ++ Driver to support HID keyboards on Surface Laptop 1 and 2 devices. + -+ Specifically, exports error injection hooks to be used with the -+ kernel's function error injection capabilities to simulate underlying -+ transport and communication problems, such as invalid data sent to or -+ received from the EC, dropped data, and communication timeouts. -+ Intended for development and debugging. ++ 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. + -+source "drivers/misc/surface_aggregator/clients/Kconfig" -diff --git a/drivers/misc/surface_aggregator/Makefile b/drivers/misc/surface_aggregator/Makefile ++ 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 ++ tristate ++ select HID +diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile new file mode 100644 -index 000000000000..b48ffc37ab52 +index 000000000000..4ae11cf09b25 --- /dev/null -+++ b/drivers/misc/surface_aggregator/Makefile -@@ -0,0 +1,18 @@ ++++ b/drivers/hid/surface-hid/Makefile +@@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2020 Maximilian Luz -+ -+# For include/trace/define_trace.h to include trace.h -+CFLAGS_core.o = -I$(src) -+ -+obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o -+obj-$(CONFIG_SURFACE_AGGREGATOR) += clients/ -+ -+surface_aggregator-objs := core.o -+surface_aggregator-objs += ssh_parser.o -+surface_aggregator-objs += ssh_packet_layer.o -+surface_aggregator-objs += ssh_request_layer.o -+surface_aggregator-objs += controller.o -+ -+ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) -+surface_aggregator-objs += bus.o -+endif -diff --git a/drivers/misc/surface_aggregator/bus.c b/drivers/misc/surface_aggregator/bus.c ++# ++# 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 ++obj-$(CONFIG_SURFACE_KBD) += surface_kbd.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..a9b660af0917 +index 000000000000..e4477c328536 --- /dev/null -+++ b/drivers/misc/surface_aggregator/bus.c -@@ -0,0 +1,415 @@ ++++ b/drivers/hid/surface-hid/surface_hid.c +@@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* -+ * Surface System Aggregator Module bus and device integration. ++ * 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-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Blaž Hrastnik , ++ * Maximilian Luz + */ + -+#include -+#include ++#include ++#include ++#include ++#include ++#include + +#include +#include + -+#include "bus.h" -+#include "controller.h" ++#include "surface_hid_core.h" + -+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); + -+ return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n", -+ sdev->uid.domain, sdev->uid.category, sdev->uid.target, -+ sdev->uid.instance, sdev->uid.function); -+} -+static DEVICE_ATTR_RO(modalias); ++/* -- SAM interface. -------------------------------------------------------- */ + -+static struct attribute *ssam_device_attrs[] = { -+ &dev_attr_modalias.attr, -+ NULL, ++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, +}; -+ATTRIBUTE_GROUPS(ssam_device); + -+static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X", -+ sdev->uid.domain, sdev->uid.category, -+ sdev->uid.target, sdev->uid.instance, -+ sdev->uid.function); -+} -+ -+static void ssam_device_release(struct device *dev) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ ssam_controller_put(sdev->ctrl); -+ kfree(sdev); -+} -+ -+const struct device_type ssam_device_type = { -+ .name = "surface_aggregator_device", -+ .groups = ssam_device_groups, -+ .uevent = ssam_device_uevent, -+ .release = ssam_device_release, -+}; -+EXPORT_SYMBOL_GPL(ssam_device_type); -+ -+/** -+ * ssam_device_alloc() - Allocate and initialize a SSAM client device. -+ * @ctrl: The controller under which the device should be added. -+ * @uid: The UID of the device to be added. -+ * -+ * Allocates and initializes a new client device. The parent of the device -+ * will be set to the controller device and the name will be set based on the -+ * UID. Note that the device still has to be added via ssam_device_add(). -+ * Refer to that function for more details. -+ * -+ * Return: Returns the newly allocated and initialized SSAM client device, or -+ * %NULL if it could not be allocated. -+ */ -+struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, -+ struct ssam_device_uid uid) -+{ -+ struct ssam_device *sdev; -+ -+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); -+ if (!sdev) -+ return NULL; -+ -+ device_initialize(&sdev->dev); -+ sdev->dev.bus = &ssam_bus_type; -+ sdev->dev.type = &ssam_device_type; -+ sdev->dev.parent = ssam_controller_device(ctrl); -+ sdev->ctrl = ssam_controller_get(ctrl); -+ sdev->uid = uid; -+ -+ dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x", -+ sdev->uid.domain, sdev->uid.category, sdev->uid.target, -+ sdev->uid.instance, sdev->uid.function); -+ -+ return sdev; -+} -+EXPORT_SYMBOL_GPL(ssam_device_alloc); -+ -+/** -+ * ssam_device_add() - Add a SSAM client device. -+ * @sdev: The SSAM client device to be added. -+ * -+ * Added client devices must be guaranteed to always have a valid and active -+ * controller. Thus, this function will fail with %-ENODEV if the controller -+ * of the device has not been initialized yet, has been suspended, or has been -+ * shut down. -+ * -+ * The caller of this function should ensure that the corresponding call to -+ * ssam_device_remove() is issued before the controller is shut down. If the -+ * added device is a direct child of the controller device (default), it will -+ * be automatically removed when the controller is shut down. -+ * -+ * By default, the controller device will become the parent of the newly -+ * created client device. The parent may be changed before ssam_device_add is -+ * called, but care must be taken that a) the correct suspend/resume ordering -+ * is guaranteed and b) the client device does not outlive the controller, -+ * i.e. that the device is removed before the controller is being shut down. -+ * In case these guarantees have to be manually enforced, please refer to the -+ * ssam_client_link() and ssam_client_bind() functions, which are intended to -+ * set up device-links for this purpose. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssam_device_add(struct ssam_device *sdev) ++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; + + /* -+ * Ensure that we can only add new devices to a controller if it has -+ * been started and is not going away soon. This works in combination -+ * with ssam_controller_remove_clients to ensure driver presence for the -+ * controller device, i.e. it ensures that the controller (sdev->ctrl) -+ * is always valid and can be used for requests as long as the client -+ * device we add here is registered as child under it. This essentially -+ * guarantees that the client driver can always expect the preconditions -+ * for functions like ssam_request_sync (controller has to be started -+ * and is not suspended) to hold and thus does not have to check for -+ * them. -+ * -+ * Note that for this to work, the controller has to be a parent device. -+ * If it is not a direct parent, care has to be taken that the device is -+ * removed via ssam_device_remove(), as device_unregister does not -+ * remove child devices recursively. ++ * 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. + */ -+ ssam_controller_statelock(sdev->ctrl); + -+ if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_stateunlock(sdev->ctrl); -+ return -ENODEV; ++ 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; + } + -+ status = device_add(&sdev->dev); ++ if (offset != len) { ++ dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n", ++ offset, len); ++ return -EPROTO; ++ } + -+ ssam_controller_stateunlock(sdev->ctrl); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_device_add); -+ -+/** -+ * ssam_device_remove() - Remove a SSAM client device. -+ * @sdev: The device to remove. -+ * -+ * Removes and unregisters the provided SSAM client device. -+ */ -+void ssam_device_remove(struct ssam_device *sdev) -+{ -+ device_unregister(&sdev->dev); -+} -+EXPORT_SYMBOL_GPL(ssam_device_remove); -+ -+/** -+ * ssam_device_id_compatible() - Check if a device ID matches a UID. -+ * @id: The device ID as potential match. -+ * @uid: The device UID matching against. -+ * -+ * Check if the given ID is a match for the given UID, i.e. if a device with -+ * the provided UID is compatible to the given ID following the match rules -+ * described in its &ssam_device_id.match_flags member. -+ * -+ * Return: Returns %true if the given UID is compatible to the match rule -+ * described by the given ID, %false otherwise. -+ */ -+static bool ssam_device_id_compatible(const struct ssam_device_id *id, -+ struct ssam_device_uid uid) -+{ -+ if (id->domain != uid.domain || id->category != uid.category) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) -+ return false; -+ -+ return true; ++ return 0; +} + -+/** -+ * ssam_device_id_is_null() - Check if a device ID is null. -+ * @id: The device ID to check. -+ * -+ * Check if a given device ID is null, i.e. all zeros. Used to check for the -+ * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists. -+ * -+ * Return: Returns %true if the given ID represents a null ID, %false -+ * otherwise. -+ */ -+static bool ssam_device_id_is_null(const struct ssam_device_id *id) ++static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature, ++ u8 *buf, size_t len) +{ -+ return id->match_flags == 0 && -+ id->domain == 0 && -+ id->category == 0 && -+ id->target == 0 && -+ id->instance == 0 && -+ id->function == 0 && -+ id->driver_data == 0; ++ 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); +} + -+/** -+ * ssam_device_id_match() - Find the matching ID table entry for the given UID. -+ * @table: The table to search in. -+ * @uid: The UID to matched against the individual table entries. -+ * -+ * Find the first match for the provided device UID in the provided ID table -+ * and return it. Returns %NULL if no match could be found. -+ */ -+const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, -+ const struct ssam_device_uid uid) ++static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) +{ -+ const struct ssam_device_id *id; ++ struct ssam_request rqst; ++ struct ssam_response rsp; + -+ for (id = table; !ssam_device_id_is_null(id); ++id) -+ if (ssam_device_id_compatible(id, uid)) -+ return id; ++ 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; + -+ return NULL; ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id)); +} -+EXPORT_SYMBOL_GPL(ssam_device_id_match); + -+/** -+ * ssam_device_get_match() - Find and return the ID matching the device in the -+ * ID table of the bound driver. -+ * @dev: The device for which to get the matching ID table entry. -+ * -+ * Find the fist match for the UID of the device in the ID table of the -+ * currently bound driver and return it. Returns %NULL if the device does not -+ * have a driver bound to it, the driver does not have match_table (i.e. it is -+ * %NULL), or there is no match in the driver's match_table. -+ * -+ * This function essentially calls ssam_device_id_match() with the ID table of -+ * the bound device driver and the UID of the device. -+ * -+ * Return: Returns the first match for the UID of the device in the device -+ * driver's match table, or %NULL if no such match could be found. -+ */ -+const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev) ++static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ -+ const struct ssam_device_driver *sdrv; ++ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); ++ int status; + -+ sdrv = to_ssam_device_driver(dev->dev.driver); -+ if (!sdrv) -+ return NULL; -+ -+ if (!sdrv->match_table) -+ return NULL; -+ -+ return ssam_device_id_match(sdrv->match_table, dev->uid); -+} -+EXPORT_SYMBOL_GPL(ssam_device_get_match); -+ -+/** -+ * ssam_device_get_match_data() - Find the ID matching the device in the -+ * ID table of the bound driver and return its ``driver_data`` member. -+ * @dev: The device for which to get the match data. -+ * -+ * Find the fist match for the UID of the device in the ID table of the -+ * corresponding driver and return its driver_data. Returns %NULL if the -+ * device does not have a driver bound to it, the driver does not have -+ * match_table (i.e. it is %NULL), there is no match in the driver's -+ * match_table, or the match does not have any driver_data. -+ * -+ * This function essentially calls ssam_device_get_match() and, if any match -+ * could be found, returns its ``struct ssam_device_id.driver_data`` member. -+ * -+ * Return: Returns the driver data associated with the first match for the UID -+ * of the device in the device driver's match table, or %NULL if no such match -+ * could be found. -+ */ -+const void *ssam_device_get_match_data(const struct ssam_device *dev) -+{ -+ const struct ssam_device_id *id; -+ -+ id = ssam_device_get_match(dev); -+ if (!id) -+ return NULL; -+ -+ return (const void *)id->driver_data; -+} -+EXPORT_SYMBOL_GPL(ssam_device_get_match_data); -+ -+static int ssam_bus_match(struct device *dev, struct device_driver *drv) -+{ -+ struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (!is_ssam_device(dev)) ++ if (event->command_id != 0x00) + return 0; + -+ return !!ssam_device_id_match(sdrv->match_table, sdev->uid); ++ status = hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], ++ event->length, 0); ++ ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; +} + -+static int ssam_bus_probe(struct device *dev) ++ ++/* -- Transport driver. ----------------------------------------------------- */ ++ ++static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) +{ -+ return to_ssam_device_driver(dev->driver) -+ ->probe(to_ssam_device(dev)); ++ int status; ++ ++ status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len); ++ return status >= 0 ? len : status; +} + -+static int ssam_bus_remove(struct device *dev) ++static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) +{ -+ struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); ++ int status; + -+ if (sdrv->remove) -+ sdrv->remove(to_ssam_device(dev)); -+ -+ return 0; ++ status = ssam_hid_get_raw_report(shid, rprt_id, buf, len); ++ return status >= 0 ? len : status; +} + -+struct bus_type ssam_bus_type = { -+ .name = "surface_aggregator", -+ .match = ssam_bus_match, -+ .probe = ssam_bus_probe, -+ .remove = ssam_bus_remove, ++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) }, ++ { }, +}; -+EXPORT_SYMBOL_GPL(ssam_bus_type); ++MODULE_DEVICE_TABLE(ssam, surface_hid_match); + -+/** -+ * __ssam_device_driver_register() - Register a SSAM client device driver. -+ * @sdrv: The driver to register. -+ * @owner: The module owning the provided driver. -+ * -+ * Please refer to the ssam_device_driver_register() macro for the normal way -+ * to register a driver from inside its owning module. -+ */ -+int __ssam_device_driver_register(struct ssam_device_driver *sdrv, -+ struct module *owner) -+{ -+ sdrv->driver.owner = owner; -+ sdrv->driver.bus = &ssam_bus_type; ++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); + -+ /* force drivers to async probe so I/O is possible in probe */ -+ sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; -+ -+ return driver_register(&sdrv->driver); -+} -+EXPORT_SYMBOL_GPL(__ssam_device_driver_register); -+ -+/** -+ * ssam_device_driver_unregister - Unregister a SSAM device driver. -+ * @sdrv: The driver to unregister. -+ */ -+void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) -+{ -+ driver_unregister(&sdrv->driver); -+} -+EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); -+ -+static int ssam_remove_device(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_remove(sdev); -+ -+ return 0; -+} -+ -+/** -+ * ssam_controller_remove_clients() - Remove SSAM client devices registered as -+ * direct children under the given controller. -+ * @ctrl: The controller to remove all direct clients for. -+ * -+ * Remove all SSAM client devices registered as direct children under the -+ * given controller. Note that this only accounts for direct children of the -+ * controller device. This does not take care of any client devices where the -+ * parent device has been manually set before calling ssam_device_add. Refer -+ * to ssam_device_add()/ssam_device_remove() for more details on those cases. -+ * -+ * To avoid new devices being added in parallel to this call, the main -+ * controller lock (not statelock) must be held during this (and if -+ * necessary, any subsequent deinitialization) call. -+ */ -+void ssam_controller_remove_clients(struct ssam_controller *ctrl) -+{ -+ struct device *dev; -+ -+ dev = ssam_controller_device(ctrl); -+ device_for_each_child_reverse(dev, NULL, ssam_remove_device); -+} -+ -+/** -+ * ssam_bus_register() - Register and set-up the SSAM client device bus. -+ */ -+int ssam_bus_register(void) -+{ -+ return bus_register(&ssam_bus_type); -+} -+ -+/** -+ * ssam_bus_unregister() - Unregister the SSAM client device bus. -+ */ -+void ssam_bus_unregister(void) -+{ -+ return bus_unregister(&ssam_bus_type); -+} -diff --git a/drivers/misc/surface_aggregator/bus.h b/drivers/misc/surface_aggregator/bus.h ++MODULE_AUTHOR("Blaž Hrastnik "); ++MODULE_AUTHOR("Maximilian Luz "); ++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..7712baaed6a5 +index 000000000000..7b27ec392232 --- /dev/null -+++ b/drivers/misc/surface_aggregator/bus.h -@@ -0,0 +1,27 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ ++++ b/drivers/hid/surface-hid/surface_hid_core.c +@@ -0,0 +1,272 @@ ++// SPDX-License-Identifier: GPL-2.0+ +/* -+ * Surface System Aggregator Module bus and device integration. ++ * 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-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + -+#ifndef _SURFACE_AGGREGATOR_BUS_H -+#define _SURFACE_AGGREGATOR_BUS_H ++#include ++#include ++#include ++#include ++#include ++#include + +#include + -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS ++#include "surface_hid_core.h" + -+void ssam_controller_remove_clients(struct ssam_controller *ctrl); + -+int ssam_bus_register(void); -+void ssam_bus_unregister(void); ++/* -- Device descriptor access. --------------------------------------------- */ + -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) ++{ ++ int status; + -+static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} -+static inline int ssam_bus_register(void) { return 0; } -+static inline void ssam_bus_unregister(void) {} ++ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, ++ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); ++ if (status) ++ return status; + -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+#endif /* _SURFACE_AGGREGATOR_BUS_H */ -diff --git a/drivers/misc/surface_aggregator/clients/Kconfig b/drivers/misc/surface_aggregator/clients/Kconfig ++ 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 "); ++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..4d2b0528ca2e +index 000000000000..4b1a7b57e035 --- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/Kconfig -@@ -0,0 +1,134 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2020 Maximilian Luz ++++ 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 ++ */ ++ ++#ifndef SURFACE_HID_CORE_H ++#define SURFACE_HID_CORE_H ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++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 */ +diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c +new file mode 100644 +index 000000000000..e72baac952ec +--- /dev/null ++++ b/drivers/hid/surface-hid/surface_kbd.c +@@ -0,0 +1,303 @@ ++// 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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#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); ++ int status; ++ ++ /* ++ * 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; ++ ++ status = hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], ++ event->length, 0); ++ ++ return ssam_notifier_from_errno(status) | 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 "); ++MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index a9b12f4dcbd1..dbb07644c312 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -870,6 +870,108 @@ config INTEL_VBTN + To compile this driver as a module, choose M here: the module will + be called intel_vbtn. + ++source "drivers/platform/x86/surface_aggregator/Kconfig" ++ ++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_AGGREGATOR_CDEV + tristate "Surface System Aggregator Module User-Space Interface" @@ -3259,37 +3751,14 @@ index 000000000000..4d2b0528ca2e + the SSAM controller. Said client device manages a misc-device + interface (/dev/surface/aggregator), which can be used by user-space + tools to directly communicate with the SSAM EC by sending requests and -+ receiving the correspondign responses. ++ receiving the corresponding responses. + + 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_BUS -+ default m -+ help -+ Device-registry and device-hubs for Surface System Aggregator Module -+ (SSAM) devices. -+ -+ Provides a module and driver which act as 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: -+ - performance / cooling mode device (all generations) -+ - battery/AC devices (7th generation) -+ - HID input devices (7th generation) -+ -+ Note that this module only provides the respective client devices. -+ Drivers for these devices still need to be selected via the other -+ options. -+ +config SURFACE_ACPI_NOTIFY + tristate "Surface ACPI Notify Driver" + depends on SURFACE_AGGREGATOR -+ default m + help + Surface ACPI Notify (SAN) driver for Microsoft Surface devices. + @@ -3306,26 +3775,27 @@ index 000000000000..4d2b0528ca2e + thermal sensor access, and real-time clock information, depending on + the Surface device in question. + -+config SURFACE_BATTERY -+ tristate "Surface Battery Driver" ++config SURFACE_PERFMODE ++ tristate "Surface Performance-Mode Driver" + depends on SURFACE_AGGREGATOR_BUS -+ select POWER_SUPPLY -+ default m ++ depends on SYSFS + help -+ Driver for battery and AC-adapter devices connected/managed via the -+ Surface System Aggregator Module (SSAM) EC. ++ Driver for the performance-/cooling-mode interface of Microsoft ++ Surface devices. + -+ This driver provides battery-/AC-information and -status support for -+ Surface devices where said data is not exposed via the standard ACPI -+ devices. On those models (7th-generation, i.e. Surface pro 7, Surface -+ Laptop 3, and Surface Book 3), battery-/AC-status and -information is -+ instead handled directly via SSAM client devices. ++ Microsoft Surface devices using the Surface System Aggregator Module ++ (SSAM) can be switched between different performance modes. This, ++ depending on the device, can influence their cooling behavior and may ++ influence power limits, allowing users to choose between performance ++ and higher power-draw, or lower power-draw and more silent operation. ++ ++ This driver provides a user-space interface (via sysfs) for ++ controlling said mode via the corresponding client device. + +config SURFACE_DTX -+ tristate "Surface Detachment System Driver" ++ tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR + depends on INPUT -+ default m + help + Driver for the Surface Book clipboard detachment system (DTX). + @@ -3342,63 +3812,32 @@ index 000000000000..4d2b0528ca2e + 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_HID -+ tristate "Surface HID Transport Driver" -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on HID -+ default m -+ help -+ Transport driver for HID devices connected via the Surface System -+ Aggregator Module (SSAM). + config SURFACE3_WMI + tristate "Surface 3 WMI Driver" + depends on ACPI_WMI +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index 562d83940e7b..2e0a2896c78d 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -82,6 +82,13 @@ obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o + obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o + + # Microsoft ++obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator/ ++obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o ++obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o ++obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o ++obj-$(CONFIG_SURFACE_PERFMODE) += surface_perfmode.o ++obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + -+ This driver provides support for HID input devices (e.g. touchpad and -+ keyboard) connected via SSAM. It is required for keyboard input on the -+ Surface Laptop 1 and 2, as well as keyboard and touchpad input on the -+ Surface Laptop 3 and Surface Book 3. -+ -+ 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 and Surface Laptop 3, -+ will not be supported. -+ -+config SURFACE_PERFMODE -+ tristate "Surface Performance-Mode Driver" -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on SYSFS -+ default m -+ help -+ Driver for the performance-/cooling-mode interface of Microsoft -+ Surface devices. -+ -+ Microsoft Surface devices using the Surface System Aggregator Module -+ (SSAM) can be switched between different performance modes. This, -+ depending on the device, can influence their cooling behavior and may -+ influence power limits, allowing users to choose between performance -+ and higher power-draw, or lower power-draw and more silent operation. -+ -+ This driver provides a user-space interface (via sysfs) for -+ controlling said mode via the corresponding client device. -diff --git a/drivers/misc/surface_aggregator/clients/Makefile b/drivers/misc/surface_aggregator/clients/Makefile -new file mode 100644 -index 000000000000..4249af06d738 ---- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/Makefile -@@ -0,0 +1,10 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2020 Maximilian Luz -+ -+obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o -+obj-$(CONFIG_SURFACE_BATTERY) += surface_battery.o -+obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o -+obj-$(CONFIG_SURFACE_HID) += surface_hid.o -+obj-$(CONFIG_SURFACE_PERFMODE) += surface_perfmode.o -diff --git a/drivers/misc/surface_aggregator/clients/surface_acpi_notify.c b/drivers/misc/surface_aggregator/clients/surface_acpi_notify.c + obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o + obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o + obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o +diff --git a/drivers/platform/x86/surface_acpi_notify.c b/drivers/platform/x86/surface_acpi_notify.c new file mode 100644 index 000000000000..ef9c1f8e8336 --- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/surface_acpi_notify.c ++++ b/drivers/platform/x86/surface_acpi_notify.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* @@ -4286,4524 +4725,563 @@ index 000000000000..ef9c1f8e8336 +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_aggregator/clients/surface_aggregator_cdev.c b/drivers/misc/surface_aggregator/clients/surface_aggregator_cdev.c +diff --git a/drivers/platform/x86/surface_aggregator/Kconfig b/drivers/platform/x86/surface_aggregator/Kconfig new file mode 100644 -index 000000000000..79e28fab7e40 +index 000000000000..44c2493706bc --- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/surface_aggregator_cdev.c -@@ -0,0 +1,322 @@ ++++ b/drivers/platform/x86/surface_aggregator/Kconfig +@@ -0,0 +1,69 @@ ++# SPDX-License-Identifier: GPL-2.0+ ++# Copyright (C) 2019-2020 Maximilian Luz ++ ++menuconfig SURFACE_AGGREGATOR ++ tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" ++ depends on SERIAL_DEV_BUS ++ depends on ACPI ++ select CRC_CCITT ++ help ++ The Surface System Aggregator Module (Surface SAM or SSAM) is an ++ embedded controller (EC) found on 5th- and later-generation Microsoft ++ Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop, ++ and newer, with exception of Surface Go series devices). ++ ++ Depending on the device in question, this EC provides varying ++ functionality, including: ++ - EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation) ++ - battery status information (all devices) ++ - thermal sensor access (all devices) ++ - performance mode / cooling mode control (all devices) ++ - clipboard detachment system control (Surface Book 2 and 3) ++ - HID / keyboard input (Surface Laptops, Surface Book 3) ++ ++ This option controls whether the Surface SAM subsystem core will be ++ built. This includes a driver for the Surface Serial Hub (SSH), which ++ is the device responsible for the communication with the EC, and a ++ basic kernel interface exposing the EC functionality to other client ++ drivers, i.e. allowing them to make requests to the EC and receive ++ events from it. Selecting this option alone will not provide any ++ client drivers and therefore no functionality beyond the in-kernel ++ interface. Said functionality is the responsibility of the respective ++ client drivers. ++ ++ Note: While 4th-generation Surface devices also make use of a SAM EC, ++ due to a difference in the communication interface of the controller, ++ only 5th and later generations are currently supported. Specifically, ++ devices using SAM-over-SSH are supported, whereas devices using ++ SAM-over-HID, which is used on the 4th generation, are currently not ++ supported. ++ ++ Choose m if you want to build the SAM subsystem core and SSH driver as ++ module, y if you want to build it into the kernel and n if you don't ++ want it at all. ++ ++config SURFACE_AGGREGATOR_BUS ++ bool "Surface System Aggregator Module Bus" ++ depends on SURFACE_AGGREGATOR ++ default y ++ help ++ Expands the Surface System Aggregator Module (SSAM) core driver by ++ providing a dedicated bus and client-device type. ++ ++ This bus and device type are intended to provide and simplify support ++ for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are ++ not auto-detectable via the conventional means (e.g. ACPI). ++ ++config SURFACE_AGGREGATOR_ERROR_INJECTION ++ bool "Surface System Aggregator Module Error Injection Capabilities" ++ depends on SURFACE_AGGREGATOR ++ depends on FUNCTION_ERROR_INJECTION ++ help ++ Provides error-injection capabilities for the Surface System ++ Aggregator Module subsystem and Surface Serial Hub driver. ++ ++ Specifically, exports error injection hooks to be used with the ++ kernel's function error injection capabilities to simulate underlying ++ transport and communication problems, such as invalid data sent to or ++ received from the EC, dropped data, and communication timeouts. ++ Intended for development and debugging. +diff --git a/drivers/platform/x86/surface_aggregator/Makefile b/drivers/platform/x86/surface_aggregator/Makefile +new file mode 100644 +index 000000000000..c112e2c7112b +--- /dev/null ++++ b/drivers/platform/x86/surface_aggregator/Makefile +@@ -0,0 +1,17 @@ ++# SPDX-License-Identifier: GPL-2.0+ ++# Copyright (C) 2019-2020 Maximilian Luz ++ ++# For include/trace/define_trace.h to include trace.h ++CFLAGS_core.o = -I$(src) ++ ++obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o ++ ++surface_aggregator-objs := core.o ++surface_aggregator-objs += ssh_parser.o ++surface_aggregator-objs += ssh_packet_layer.o ++surface_aggregator-objs += ssh_request_layer.o ++surface_aggregator-objs += controller.o ++ ++ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) ++surface_aggregator-objs += bus.o ++endif +diff --git a/drivers/platform/x86/surface_aggregator/bus.c b/drivers/platform/x86/surface_aggregator/bus.c +new file mode 100644 +index 000000000000..a9b660af0917 +--- /dev/null ++++ b/drivers/platform/x86/surface_aggregator/bus.c +@@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* -+ * Provides user-space access to the SSAM EC via the /dev/surface/aggregator -+ * misc device. Intended for debugging and development. ++ * Surface System Aggregator Module bus and device integration. + * -+ * Copyright (C) 2020 Maximilian Luz ++ * Copyright (C) 2019-2020 Maximilian Luz + */ + -+#include -+#include -+#include -+#include -+#include -+#include -+#include ++#include +#include -+#include -+ -+#include -+#include -+ -+#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" -+ -+struct ssam_cdev { -+ struct kref kref; -+ struct rw_semaphore lock; -+ struct ssam_controller *ctrl; -+ struct miscdevice mdev; -+}; -+ -+static void __ssam_cdev_release(struct kref *kref) -+{ -+ kfree(container_of(kref, struct ssam_cdev, kref)); -+} -+ -+static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) -+{ -+ if (cdev) -+ kref_get(&cdev->kref); -+ -+ return cdev; -+} -+ -+static void ssam_cdev_put(struct ssam_cdev *cdev) -+{ -+ if (cdev) -+ kref_put(&cdev->kref, __ssam_cdev_release); -+} -+ -+static int ssam_cdev_device_open(struct inode *inode, struct file *filp) -+{ -+ struct miscdevice *mdev = filp->private_data; -+ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); -+ -+ filp->private_data = ssam_cdev_get(cdev); -+ return stream_open(inode, filp); -+} -+ -+static int ssam_cdev_device_release(struct inode *inode, struct file *filp) -+{ -+ ssam_cdev_put(filp->private_data); -+ return 0; -+} -+ -+static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) -+{ -+ struct ssam_cdev_request __user *r; -+ struct ssam_cdev_request rqst; -+ struct ssam_request spec = {}; -+ struct ssam_response rsp = {}; -+ const void __user *plddata; -+ void __user *rspdata; -+ int status = 0, ret = 0, tmp; -+ -+ r = (struct ssam_cdev_request __user *)arg; -+ ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); -+ if (ret) -+ goto out; -+ -+ plddata = u64_to_user_ptr(rqst.payload.data); -+ rspdata = u64_to_user_ptr(rqst.response.data); -+ -+ /* Setup basic request fields. */ -+ spec.target_category = rqst.target_category; -+ spec.target_id = rqst.target_id; -+ spec.command_id = rqst.command_id; -+ spec.instance_id = rqst.instance_id; -+ spec.flags = 0; -+ spec.length = rqst.payload.length; -+ spec.payload = NULL; -+ -+ if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE) -+ spec.flags |= SSAM_REQUEST_HAS_RESPONSE; -+ -+ if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED) -+ spec.flags |= SSAM_REQUEST_UNSEQUENCED; -+ -+ rsp.capacity = rqst.response.length; -+ rsp.length = 0; -+ rsp.pointer = NULL; -+ -+ /* Get request payload from user-space. */ -+ if (spec.length) { -+ if (!plddata) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ /* -+ * Note: spec.length is limited to U16_MAX bytes via struct -+ * ssam_cdev_request. This is slightly larger than the -+ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the -+ * underlying protocol (note that nothing remotely this size -+ * should ever be allocated in any normal case). This size is -+ * validated later in ssam_request_sync(), for allocation the -+ * bound imposed by u16 should be enough. -+ */ -+ spec.payload = kzalloc(spec.length, GFP_KERNEL); -+ if (!spec.payload) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ if (copy_from_user((void *)spec.payload, plddata, spec.length)) { -+ ret = -EFAULT; -+ goto out; -+ } -+ } -+ -+ /* Allocate response buffer. */ -+ if (rsp.capacity) { -+ if (!rspdata) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ /* -+ * Note: rsp.capacity is limited to U16_MAX bytes via struct -+ * ssam_cdev_request. This is slightly larger than the -+ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the -+ * underlying protocol (note that nothing remotely this size -+ * should ever be allocated in any normal case). In later use, -+ * this capacity does not have to be strictly bounded, as it -+ * is only used as an output buffer to be written to. For -+ * allocation the bound imposed by u16 should be enough. -+ */ -+ rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); -+ if (!rsp.pointer) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ } -+ -+ /* Perform request. */ -+ status = ssam_request_sync(cdev->ctrl, &spec, &rsp); -+ if (status) -+ goto out; -+ -+ /* Copy response to user-space. */ -+ if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length)) -+ ret = -EFAULT; -+ -+out: -+ /* Always try to set response-length and status. */ -+ tmp = put_user(rsp.length, &r->response.length); -+ if (tmp) -+ ret = tmp; -+ -+ tmp = put_user(status, &r->status); -+ if (tmp) -+ ret = tmp; -+ -+ /* Cleanup. */ -+ kfree(spec.payload); -+ kfree(rsp.pointer); -+ -+ return ret; -+} -+ -+static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, -+ unsigned long arg) -+{ -+ switch (cmd) { -+ case SSAM_CDEV_REQUEST: -+ return ssam_cdev_request(cdev, arg); -+ -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ssam_cdev *cdev = file->private_data; -+ long status; -+ -+ /* Ensure that controller is valid for as long as we need it. */ -+ if (down_read_killable(&cdev->lock)) -+ return -ERESTARTSYS; -+ -+ if (!cdev->ctrl) { -+ up_read(&cdev->lock); -+ return -ENODEV; -+ } -+ -+ status = __ssam_cdev_device_ioctl(cdev, cmd, arg); -+ -+ up_read(&cdev->lock); -+ return status; -+} -+ -+static const struct file_operations ssam_controller_fops = { -+ .owner = THIS_MODULE, -+ .open = ssam_cdev_device_open, -+ .release = ssam_cdev_device_release, -+ .unlocked_ioctl = ssam_cdev_device_ioctl, -+ .compat_ioctl = ssam_cdev_device_ioctl, -+ .llseek = noop_llseek, -+}; -+ -+static int ssam_dbg_device_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct ssam_cdev *cdev; -+ int status; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); -+ if (!cdev) -+ return -ENOMEM; -+ -+ kref_init(&cdev->kref); -+ init_rwsem(&cdev->lock); -+ cdev->ctrl = ctrl; -+ -+ cdev->mdev.parent = &pdev->dev; -+ cdev->mdev.minor = MISC_DYNAMIC_MINOR; -+ cdev->mdev.name = "surface_aggregator"; -+ cdev->mdev.nodename = "surface/aggregator"; -+ cdev->mdev.fops = &ssam_controller_fops; -+ -+ status = misc_register(&cdev->mdev); -+ if (status) { -+ kfree(cdev); -+ return status; -+ } -+ -+ platform_set_drvdata(pdev, cdev); -+ return 0; -+} -+ -+static int ssam_dbg_device_remove(struct platform_device *pdev) -+{ -+ struct ssam_cdev *cdev = platform_get_drvdata(pdev); -+ -+ misc_deregister(&cdev->mdev); -+ -+ /* -+ * The controller is only guaranteed to be valid for as long as the -+ * driver is bound. Remove controller so that any lingering open files -+ * cannot access it any more after we're gone. -+ */ -+ down_write(&cdev->lock); -+ cdev->ctrl = NULL; -+ up_write(&cdev->lock); -+ -+ ssam_cdev_put(cdev); -+ return 0; -+} -+ -+static struct platform_device *ssam_cdev_device; -+ -+static struct platform_driver ssam_cdev_driver = { -+ .probe = ssam_dbg_device_probe, -+ .remove = ssam_dbg_device_remove, -+ .driver = { -+ .name = SSAM_CDEV_DEVICE_NAME, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static int __init ssam_debug_init(void) -+{ -+ int status; -+ -+ ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME, -+ PLATFORM_DEVID_NONE); -+ if (!ssam_cdev_device) -+ return -ENOMEM; -+ -+ status = platform_device_add(ssam_cdev_device); -+ if (status) -+ goto err_device; -+ -+ status = platform_driver_register(&ssam_cdev_driver); -+ if (status) -+ goto err_driver; -+ -+ return 0; -+ -+err_driver: -+ platform_device_del(ssam_cdev_device); -+err_device: -+ platform_device_put(ssam_cdev_device); -+ return status; -+} -+module_init(ssam_debug_init); -+ -+static void __exit ssam_debug_exit(void) -+{ -+ platform_driver_unregister(&ssam_cdev_driver); -+ platform_device_unregister(ssam_cdev_device); -+} -+module_exit(ssam_debug_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c b/drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c -new file mode 100644 -index 000000000000..8b4f0b3c1f31 ---- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c -@@ -0,0 +1,664 @@ -+// 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 for these devices. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include + +#include +#include + ++#include "bus.h" ++#include "controller.h" + -+/* -- Device registry. ------------------------------------------------------ */ -+ -+static const struct software_node ssam_node_root = { -+ .name = "ssam_platform_hub", -+}; -+ -+static const struct software_node ssam_node_hub_main = { -+ .name = "ssam:00:00:01:00:00", -+ .parent = &ssam_node_root, -+}; -+ -+static const struct software_node ssam_node_hub_base = { -+ .name = "ssam:00:00:02:00:00", -+ .parent = &ssam_node_root, -+}; -+ -+static const struct software_node ssam_node_bat_ac = { -+ .name = "ssam:01:02:01:01:01", -+ .parent = &ssam_node_hub_main, -+}; -+ -+static const struct software_node ssam_node_bat_main = { -+ .name = "ssam:01:02:01:01:00", -+ .parent = &ssam_node_hub_main, -+}; -+ -+static const struct software_node ssam_node_bat_sb3base = { -+ .name = "ssam:01:02:02:01:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+static const struct software_node ssam_node_tmp_perf = { -+ .name = "ssam:01:03:01:00:01", -+ .parent = &ssam_node_hub_main, -+}; -+ -+static const struct software_node ssam_node_bas_dtx = { -+ .name = "ssam:01:11:01:00:00", -+ .parent = &ssam_node_hub_main, -+}; -+ -+static const struct software_node ssam_node_hid_main_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_main, -+}; -+ -+static const struct software_node ssam_node_hid_main_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_main, -+}; -+ -+static const struct software_node ssam_node_hid_main_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_main, -+}; -+ -+static const struct software_node ssam_node_hid_base_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+static const struct software_node ssam_node_hid_base_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+static const struct software_node ssam_node_hid_base_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+static const struct software_node ssam_node_hid_base_iid6 = { -+ .name = "ssam:01:15:02:06:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+static const struct software_node *ssam_node_group_sb2[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_tmp_perf, -+ NULL, -+}; -+ -+static const struct software_node *ssam_node_group_sb3[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_hub_base, -+ &ssam_node_tmp_perf, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_bat_sb3base, -+ &ssam_node_hid_base_keyboard, -+ &ssam_node_hid_base_touchpad, -+ &ssam_node_hid_base_iid5, -+ &ssam_node_hid_base_iid6, -+ &ssam_node_bas_dtx, -+ NULL, -+}; -+ -+static const struct software_node *ssam_node_group_sl1[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_tmp_perf, -+ NULL, -+}; -+ -+static const struct software_node *ssam_node_group_sl2[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_tmp_perf, -+ NULL, -+}; -+ -+static const struct software_node *ssam_node_group_sl3[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_tmp_perf, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_hid_main_keyboard, -+ &ssam_node_hid_main_touchpad, -+ &ssam_node_hid_main_iid5, -+ NULL, -+}; -+ -+static const struct software_node *ssam_node_group_slg1[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_tmp_perf, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ NULL, -+}; -+ -+static const struct software_node *ssam_node_group_sp5[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_tmp_perf, -+ NULL, -+}; -+ -+static const struct software_node *ssam_node_group_sp6[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_tmp_perf, -+ NULL, -+}; -+ -+static const struct software_node *ssam_node_group_sp7[] = { -+ &ssam_node_root, -+ &ssam_node_hub_main, -+ &ssam_node_tmp_perf, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ NULL, -+}; -+ -+ -+/* -- Device registry helper functions. ------------------------------------- */ -+ -+static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) ++static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, ++ char *buf) +{ -+ u8 d, tc, tid, iid, fn; -+ int n; ++ struct ssam_device *sdev = to_ssam_device(dev); + -+ n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -+ if (n != 5) -+ return -EINVAL; ++ return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n", ++ sdev->uid.domain, sdev->uid.category, sdev->uid.target, ++ sdev->uid.instance, sdev->uid.function); ++} ++static DEVICE_ATTR_RO(modalias); + -+ uid->domain = d; -+ uid->category = tc; -+ uid->target = tid; -+ uid->instance = iid; -+ uid->function = fn; ++static struct attribute *ssam_device_attrs[] = { ++ &dev_attr_modalias.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(ssam_device); + -+ return 0; ++static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X", ++ sdev->uid.domain, sdev->uid.category, ++ sdev->uid.target, sdev->uid.instance, ++ sdev->uid.function); +} + -+static int ssam_hub_remove_devices_fn(struct device *dev, void *data) ++static void ssam_device_release(struct device *dev) +{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ ssam_controller_put(sdev->ctrl); ++ kfree(sdev); ++} ++ ++const struct device_type ssam_device_type = { ++ .name = "surface_aggregator_device", ++ .groups = ssam_device_groups, ++ .uevent = ssam_device_uevent, ++ .release = ssam_device_release, ++}; ++EXPORT_SYMBOL_GPL(ssam_device_type); ++ ++/** ++ * ssam_device_alloc() - Allocate and initialize a SSAM client device. ++ * @ctrl: The controller under which the device should be added. ++ * @uid: The UID of the device to be added. ++ * ++ * Allocates and initializes a new client device. The parent of the device ++ * will be set to the controller device and the name will be set based on the ++ * UID. Note that the device still has to be added via ssam_device_add(). ++ * Refer to that function for more details. ++ * ++ * Return: Returns the newly allocated and initialized SSAM client device, or ++ * %NULL if it could not be allocated. ++ */ ++struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, ++ struct ssam_device_uid uid) ++{ ++ struct ssam_device *sdev; ++ ++ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); ++ if (!sdev) ++ return NULL; ++ ++ device_initialize(&sdev->dev); ++ sdev->dev.bus = &ssam_bus_type; ++ sdev->dev.type = &ssam_device_type; ++ sdev->dev.parent = ssam_controller_device(ctrl); ++ sdev->ctrl = ssam_controller_get(ctrl); ++ sdev->uid = uid; ++ ++ dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x", ++ sdev->uid.domain, sdev->uid.category, sdev->uid.target, ++ sdev->uid.instance, sdev->uid.function); ++ ++ return sdev; ++} ++EXPORT_SYMBOL_GPL(ssam_device_alloc); ++ ++/** ++ * ssam_device_add() - Add a SSAM client device. ++ * @sdev: The SSAM client device to be added. ++ * ++ * Added client devices must be guaranteed to always have a valid and active ++ * controller. Thus, this function will fail with %-ENODEV if the controller ++ * of the device has not been initialized yet, has been suspended, or has been ++ * shut down. ++ * ++ * The caller of this function should ensure that the corresponding call to ++ * ssam_device_remove() is issued before the controller is shut down. If the ++ * added device is a direct child of the controller device (default), it will ++ * be automatically removed when the controller is shut down. ++ * ++ * By default, the controller device will become the parent of the newly ++ * created client device. The parent may be changed before ssam_device_add is ++ * called, but care must be taken that a) the correct suspend/resume ordering ++ * is guaranteed and b) the client device does not outlive the controller, ++ * i.e. that the device is removed before the controller is being shut down. ++ * In case these guarantees have to be manually enforced, please refer to the ++ * ssam_client_link() and ssam_client_bind() functions, which are intended to ++ * set up device-links for this purpose. ++ * ++ * Return: Returns zero on success, a negative error code on failure. ++ */ ++int ssam_device_add(struct ssam_device *sdev) ++{ ++ int status; ++ ++ /* ++ * Ensure that we can only add new devices to a controller if it has ++ * been started and is not going away soon. This works in combination ++ * with ssam_controller_remove_clients to ensure driver presence for the ++ * controller device, i.e. it ensures that the controller (sdev->ctrl) ++ * is always valid and can be used for requests as long as the client ++ * device we add here is registered as child under it. This essentially ++ * guarantees that the client driver can always expect the preconditions ++ * for functions like ssam_request_sync (controller has to be started ++ * and is not suspended) to hold and thus does not have to check for ++ * them. ++ * ++ * Note that for this to work, the controller has to be a parent device. ++ * If it is not a direct parent, care has to be taken that the device is ++ * removed via ssam_device_remove(), as device_unregister does not ++ * remove child devices recursively. ++ */ ++ ssam_controller_statelock(sdev->ctrl); ++ ++ if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) { ++ ssam_controller_stateunlock(sdev->ctrl); ++ return -ENODEV; ++ } ++ ++ status = device_add(&sdev->dev); ++ ++ ssam_controller_stateunlock(sdev->ctrl); ++ return status; ++} ++EXPORT_SYMBOL_GPL(ssam_device_add); ++ ++/** ++ * ssam_device_remove() - Remove a SSAM client device. ++ * @sdev: The device to remove. ++ * ++ * Removes and unregisters the provided SSAM client device. ++ */ ++void ssam_device_remove(struct ssam_device *sdev) ++{ ++ device_unregister(&sdev->dev); ++} ++EXPORT_SYMBOL_GPL(ssam_device_remove); ++ ++/** ++ * ssam_device_id_compatible() - Check if a device ID matches a UID. ++ * @id: The device ID as potential match. ++ * @uid: The device UID matching against. ++ * ++ * Check if the given ID is a match for the given UID, i.e. if a device with ++ * the provided UID is compatible to the given ID following the match rules ++ * described in its &ssam_device_id.match_flags member. ++ * ++ * Return: Returns %true if the given UID is compatible to the match rule ++ * described by the given ID, %false otherwise. ++ */ ++static bool ssam_device_id_compatible(const struct ssam_device_id *id, ++ struct ssam_device_uid uid) ++{ ++ if (id->domain != uid.domain || id->category != uid.category) ++ return false; ++ ++ if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target) ++ return false; ++ ++ if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) ++ return false; ++ ++ if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) ++ return false; ++ ++ return true; ++} ++ ++/** ++ * ssam_device_id_is_null() - Check if a device ID is null. ++ * @id: The device ID to check. ++ * ++ * Check if a given device ID is null, i.e. all zeros. Used to check for the ++ * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists. ++ * ++ * Return: Returns %true if the given ID represents a null ID, %false ++ * otherwise. ++ */ ++static bool ssam_device_id_is_null(const struct ssam_device_id *id) ++{ ++ return id->match_flags == 0 && ++ id->domain == 0 && ++ id->category == 0 && ++ id->target == 0 && ++ id->instance == 0 && ++ id->function == 0 && ++ id->driver_data == 0; ++} ++ ++/** ++ * ssam_device_id_match() - Find the matching ID table entry for the given UID. ++ * @table: The table to search in. ++ * @uid: The UID to matched against the individual table entries. ++ * ++ * Find the first match for the provided device UID in the provided ID table ++ * and return it. Returns %NULL if no match could be found. ++ */ ++const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, ++ const struct ssam_device_uid uid) ++{ ++ const struct ssam_device_id *id; ++ ++ for (id = table; !ssam_device_id_is_null(id); ++id) ++ if (ssam_device_id_compatible(id, uid)) ++ return id; ++ ++ return NULL; ++} ++EXPORT_SYMBOL_GPL(ssam_device_id_match); ++ ++/** ++ * ssam_device_get_match() - Find and return the ID matching the device in the ++ * ID table of the bound driver. ++ * @dev: The device for which to get the matching ID table entry. ++ * ++ * Find the fist match for the UID of the device in the ID table of the ++ * currently bound driver and return it. Returns %NULL if the device does not ++ * have a driver bound to it, the driver does not have match_table (i.e. it is ++ * %NULL), or there is no match in the driver's match_table. ++ * ++ * This function essentially calls ssam_device_id_match() with the ID table of ++ * the bound device driver and the UID of the device. ++ * ++ * Return: Returns the first match for the UID of the device in the device ++ * driver's match table, or %NULL if no such match could be found. ++ */ ++const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev) ++{ ++ const struct ssam_device_driver *sdrv; ++ ++ sdrv = to_ssam_device_driver(dev->dev.driver); ++ if (!sdrv) ++ return NULL; ++ ++ if (!sdrv->match_table) ++ return NULL; ++ ++ return ssam_device_id_match(sdrv->match_table, dev->uid); ++} ++EXPORT_SYMBOL_GPL(ssam_device_get_match); ++ ++/** ++ * ssam_device_get_match_data() - Find the ID matching the device in the ++ * ID table of the bound driver and return its ``driver_data`` member. ++ * @dev: The device for which to get the match data. ++ * ++ * Find the fist match for the UID of the device in the ID table of the ++ * corresponding driver and return its driver_data. Returns %NULL if the ++ * device does not have a driver bound to it, the driver does not have ++ * match_table (i.e. it is %NULL), there is no match in the driver's ++ * match_table, or the match does not have any driver_data. ++ * ++ * This function essentially calls ssam_device_get_match() and, if any match ++ * could be found, returns its ``struct ssam_device_id.driver_data`` member. ++ * ++ * Return: Returns the driver data associated with the first match for the UID ++ * of the device in the device driver's match table, or %NULL if no such match ++ * could be found. ++ */ ++const void *ssam_device_get_match_data(const struct ssam_device *dev) ++{ ++ const struct ssam_device_id *id; ++ ++ id = ssam_device_get_match(dev); ++ if (!id) ++ return NULL; ++ ++ return (const void *)id->driver_data; ++} ++EXPORT_SYMBOL_GPL(ssam_device_get_match_data); ++ ++static int ssam_bus_match(struct device *dev, struct device_driver *drv) ++{ ++ struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); ++ struct ssam_device *sdev = to_ssam_device(dev); ++ + if (!is_ssam_device(dev)) + return 0; + -+ ssam_device_remove(to_ssam_device(dev)); -+ return 0; ++ return !!ssam_device_id_match(sdrv->match_table, sdev->uid); +} + -+static void ssam_hub_remove_devices(struct device *parent) ++static int ssam_bus_probe(struct device *dev) +{ -+ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); ++ return to_ssam_device_driver(dev->driver) ++ ->probe(to_ssam_device(dev)); +} + -+static int ssam_hub_add_device(struct device *parent, -+ struct ssam_controller *ctrl, -+ struct fwnode_handle *node) ++static int ssam_bus_remove(struct device *dev) +{ -+ struct ssam_device_uid uid; -+ struct ssam_device *sdev; -+ int status; ++ struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); + -+ status = ssam_uid_from_string(fwnode_get_name(node), &uid); -+ if (status) -+ return -ENODEV; -+ -+ 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) { -+ status = ssam_hub_add_device(parent, ctrl, child); -+ if (status && status != -ENODEV) -+ goto err; -+ } -+ -+ return 0; -+err: -+ ssam_hub_remove_devices(parent); -+ return status; -+} -+ -+ -+/* -- SSAM main-hub driver. ------------------------------------------------- */ -+ -+static int ssam_hub_probe(struct ssam_device *sdev) -+{ -+ struct fwnode_handle *node = dev_fwnode(&sdev->dev); -+ -+ if (!node) -+ return -ENODEV; -+ -+ return ssam_hub_add_devices(&sdev->dev, sdev->ctrl, node); -+} -+ -+static void ssam_hub_remove(struct ssam_device *sdev) -+{ -+ ssam_hub_remove_devices(&sdev->dev); -+} -+ -+static const struct ssam_device_id ssam_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_hub_driver = { -+ .probe = ssam_hub_probe, -+ .remove = ssam_hub_remove, -+ .match_table = ssam_hub_match, -+ .driver = { -+ .name = "surface_aggregator_device_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- 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; -+ 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_device *sdev, -+ enum ssam_base_hub_state *state) -+{ -+ u8 opmode; -+ int status; -+ -+ status = ssam_retry(ssam_bas_query_opmode, sdev->ctrl, &opmode); -+ if (status < 0) { -+ dev_err(&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; ++ if (sdrv->remove) ++ sdrv->remove(to_ssam_device(dev)); + + return 0; +} + -+static ssize_t ssam_base_hub_state_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ 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, ++struct bus_type ssam_bus_type = { ++ .name = "surface_aggregator", ++ .match = ssam_bus_match, ++ .probe = ssam_bus_probe, ++ .remove = ssam_bus_remove, +}; ++EXPORT_SYMBOL_GPL(ssam_bus_type); + -+const struct attribute_group ssam_base_hub_group = { -+ .attrs = ssam_base_hub_attrs, -+}; -+ -+static int ssam_base_hub_update(struct ssam_device *sdev, -+ enum ssam_base_hub_state new) -+{ -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ struct fwnode_handle *node = dev_fwnode(&sdev->dev); -+ int status = 0; -+ -+ mutex_lock(&hub->lock); -+ if (hub->state == new) { -+ mutex_unlock(&hub->lock); -+ return 0; -+ } -+ hub->state = new; -+ -+ if (hub->state == SSAM_BASE_HUB_CONNECTED) -+ status = ssam_hub_add_devices(&sdev->dev, sdev->ctrl, node); -+ -+ if (hub->state != SSAM_BASE_HUB_CONNECTED || status) -+ ssam_hub_remove_devices(&sdev->dev); -+ -+ mutex_unlock(&hub->lock); -+ -+ if (status) { -+ dev_err(&sdev->dev, "failed to update base-hub devices: %d\n", -+ status); -+ } -+ -+ 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; -+ -+ ssam_base_hub_update(sdev, new); -+ -+ /* -+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -+ * consumed by the detachment system driver. We're just a (more or less) -+ * silent observer. -+ */ -+ return 0; -+} -+ -+static int __maybe_unused ssam_base_hub_resume(struct device *dev) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ enum ssam_base_hub_state state; -+ int status; -+ -+ status = ssam_base_hub_query_state(sdev, &state); -+ if (status) -+ return status; -+ -+ return ssam_base_hub_update(sdev, state); -+} -+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) -+{ -+ enum ssam_base_hub_state state; -+ 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 = 1000; /* 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; -+ -+ status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ if (status) -+ return status; -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_base_hub_query_state(sdev, &state); -+ if (status) { -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ return status; -+ } -+ -+ status = ssam_base_hub_update(sdev, state); -+ if (status) { -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ return status; -+ } -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ if (status) { -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_hub_remove_devices(&sdev->dev); -+ } -+ -+ return status; -+} -+ -+static void ssam_base_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_hub_remove_devices(&sdev->dev); -+ -+ mutex_destroy(&hub->lock); -+} -+ -+static const struct ssam_device_id ssam_base_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x02, 0x00, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_base_hub_driver = { -+ .probe = ssam_base_hub_probe, -+ .remove = ssam_base_hub_remove, -+ .match_table = ssam_base_hub_match, -+ .driver = { -+ .name = "surface_aggregator_base_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_base_hub_pm_ops, -+ }, -+}; -+ -+ -+/* -- SSAM platform/meta-hub driver. ---------------------------------------- */ -+ -+static const struct acpi_device_id ssam_platform_hub_match[] = { -+ /* Surface Pro 4, 5, and 6 */ -+ { "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) -+ return -ENOENT; -+ -+ set_secondary_fwnode(&pdev->dev, root); -+ -+ status = ssam_hub_add_devices(&pdev->dev, ctrl, root); -+ if (status) { -+ software_node_unregister_node_group(nodes); -+ return status; -+ } -+ -+ platform_set_drvdata(pdev, nodes); -+ return 0; -+} -+ -+static int ssam_platform_hub_remove(struct platform_device *pdev) -+{ -+ const struct software_node **nodes = platform_get_drvdata(pdev); -+ -+ ssam_hub_remove_devices(&pdev->dev); -+ set_secondary_fwnode(&pdev->dev, NULL); -+ software_node_unregister_node_group(nodes); -+ return 0; -+} -+ -+static struct platform_driver ssam_platform_hub_driver = { -+ .probe = ssam_platform_hub_probe, -+ .remove = ssam_platform_hub_remove, -+ .driver = { -+ .name = "surface_aggregator_platform_hub", -+ .acpi_match_table = ssam_platform_hub_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- Module initialization. ------------------------------------------------ */ -+ -+static int __init ssam_device_hub_init(void) -+{ -+ int status; -+ -+ status = platform_driver_register(&ssam_platform_hub_driver); -+ if (status) -+ goto err_platform; -+ -+ status = ssam_device_driver_register(&ssam_hub_driver); -+ if (status) -+ goto err_main; -+ -+ status = ssam_device_driver_register(&ssam_base_hub_driver); -+ if (status) -+ goto err_base; -+ -+ return 0; -+ -+err_base: -+ ssam_device_driver_unregister(&ssam_hub_driver); -+err_main: -+ platform_driver_unregister(&ssam_platform_hub_driver); -+err_platform: -+ return status; -+} -+module_init(ssam_device_hub_init); -+ -+static void __exit ssam_device_hub_exit(void) -+{ -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+ ssam_device_driver_unregister(&ssam_hub_driver); -+ platform_driver_unregister(&ssam_platform_hub_driver); -+} -+module_exit(ssam_device_hub_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_aggregator/clients/surface_battery.c b/drivers/misc/surface_aggregator/clients/surface_battery.c -new file mode 100644 -index 000000000000..77f7842e05a4 ---- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/surface_battery.c -@@ -0,0 +1,1168 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface battery and AC device driver. ++/** ++ * __ssam_device_driver_register() - Register a SSAM client device driver. ++ * @sdrv: The driver to register. ++ * @owner: The module owning the provided driver. + * -+ * Provides support for battery and AC devices connected via the Surface -+ * System Aggregator Module. ++ * Please refer to the ssam_device_driver_register() macro for the normal way ++ * to register a driver from inside its owning module. ++ */ ++int __ssam_device_driver_register(struct ssam_device_driver *sdrv, ++ struct module *owner) ++{ ++ sdrv->driver.owner = owner; ++ sdrv->driver.bus = &ssam_bus_type; ++ ++ /* force drivers to async probe so I/O is possible in probe */ ++ sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; ++ ++ return driver_register(&sdrv->driver); ++} ++EXPORT_SYMBOL_GPL(__ssam_device_driver_register); ++ ++/** ++ * ssam_device_driver_unregister - Unregister a SSAM device driver. ++ * @sdrv: The driver to unregister. ++ */ ++void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) ++{ ++ driver_unregister(&sdrv->driver); ++} ++EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); ++ ++static int ssam_remove_device(struct device *dev, void *_data) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ if (is_ssam_device(dev)) ++ ssam_device_remove(sdev); ++ ++ return 0; ++} ++ ++/** ++ * ssam_controller_remove_clients() - Remove SSAM client devices registered as ++ * direct children under the given controller. ++ * @ctrl: The controller to remove all direct clients for. ++ * ++ * Remove all SSAM client devices registered as direct children under the ++ * given controller. Note that this only accounts for direct children of the ++ * controller device. This does not take care of any client devices where the ++ * parent device has been manually set before calling ssam_device_add. Refer ++ * to ssam_device_add()/ssam_device_remove() for more details on those cases. ++ * ++ * To avoid new devices being added in parallel to this call, the main ++ * controller lock (not statelock) must be held during this (and if ++ * necessary, any subsequent deinitialization) call. ++ */ ++void ssam_controller_remove_clients(struct ssam_controller *ctrl) ++{ ++ struct device *dev; ++ ++ dev = ssam_controller_device(ctrl); ++ device_for_each_child_reverse(dev, NULL, ssam_remove_device); ++} ++ ++/** ++ * ssam_bus_register() - Register and set-up the SSAM client device bus. ++ */ ++int ssam_bus_register(void) ++{ ++ return bus_register(&ssam_bus_type); ++} ++ ++/** ++ * ssam_bus_unregister() - Unregister the SSAM client device bus. ++ */ ++void ssam_bus_unregister(void) ++{ ++ return bus_unregister(&ssam_bus_type); ++} +diff --git a/drivers/platform/x86/surface_aggregator/bus.h b/drivers/platform/x86/surface_aggregator/bus.h +new file mode 100644 +index 000000000000..7712baaed6a5 +--- /dev/null ++++ b/drivers/platform/x86/surface_aggregator/bus.h +@@ -0,0 +1,27 @@ ++/* SPDX-License-Identifier: GPL-2.0+ */ ++/* ++ * Surface System Aggregator Module bus and device integration. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) -+ -+ -+/* -- 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]"); -+ -+ -+/* -- 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); -+ -+#define SPWR_BIX_REVISION 0 -+ -+/* 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_BATTERY_VALUE_UNKNOWN 0xffffffff -+ -+/* Get battery status (_STA) */ -+static 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) */ -+static 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) */ -+static 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) */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x04, -+}); -+ -+/* Get platform power source for battery (DPTF PSRC). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0d, -+}); -+ -+/* -+ * The following requests are currently unused. They are nevertheless included -+ * for documentation of the SAM interface. -+ */ -+ -+/* Get maximum platform power for battery (DPTF PMAX) */ -+__always_unused -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_pmax, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0b, -+}); -+ -+/* Get adapter rating (DPTF ARTG) */ -+__always_unused -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_artg, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0f, -+}); -+ -+/* Unknown (DPTF PSOC) */ -+__always_unused -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psoc, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0c, -+}); -+ -+/* Unknown (DPTF CHGI/ INT3403 SPPC) */ -+__always_unused -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_chgi, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0e, -+}); -+ -+ -+/* -- Common power-subsystem interface. ------------------------------------- */ -+ -+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; -+ unsigned long timestamp; -+ -+ __le32 sta; -+ struct spwr_bix bix; -+ struct spwr_bst bst; -+ u32 alarm; -+}; -+ -+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; -+ -+ __le32 state; -+}; -+ -+static enum power_supply_property spwr_ac_props[] = { -+ POWER_SUPPLY_PROP_ONLINE, -+}; -+ -+static 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 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 bool spwr_battery_present(struct spwr_battery_device *bat) -+{ -+ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; -+} -+ -+static int spwr_battery_load_sta(struct spwr_battery_device *bat) -+{ -+ return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta); -+} -+ -+static int spwr_battery_load_bix(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ 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) -+{ -+ 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); -+ -+ bat->alarm = value; -+ return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le); -+} -+ -+static int spwr_battery_set_alarm(struct spwr_battery_device *bat, u32 value) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_set_alarm_unlocked(bat, value); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, -+ bool cached) -+{ -+ unsigned long cache_deadline; -+ int status; -+ -+ cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); -+ 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; -+ -+ 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 int spwr_ac_update_unlocked(struct spwr_ac_device *ac) -+{ -+ int status; -+ u32 old = ac->state; -+ -+ 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 u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ -+ 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); -+ -+ 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 int spwr_battery_recheck_adapter(struct spwr_battery_device *bat) -+{ -+ /* -+ * 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); -+ return 0; -+} -+ -+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_bat(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct spwr_battery_device *bat; -+ int status; -+ -+ bat = container_of(nf, struct spwr_battery_device, notif); -+ -+ dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ /* Handled here, needs to be handled for all targets/instances. */ -+ if (event->command_id == SAM_EVENT_CID_BAT_ADP) { -+ status = spwr_battery_recheck_adapter(bat); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ } -+ -+ if (bat->sdev->uid.target != event->target_id) -+ return 0; -+ -+ if (bat->sdev->uid.instance != event->instance_id) -+ return 0; -+ -+ 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 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; -+ } -+} -+ -+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) -+ power_supply_changed(bat->psy); -+ -+ if (status) { -+ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", -+ status); -+ } -+} -+ -+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); -+ -+ 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) -+{ -+ 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); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODEV; -+ -+ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODEV; -+ -+ 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); -+ -+ 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_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; -+} -+ -+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 = -ENODEV; -+ 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 = -ENODEV; -+ 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 = -ENODEV; -+ 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 = -ENODEV; -+ 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 = -ENODEV; -+ 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 = -ENODEV; -+ 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 = -ENODEV; -+ 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; -+} -+ -+static ssize_t spwr_battery_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); -+ -+ return sprintf(buf, "%d\n", bat->alarm * 1000); -+} -+ -+static ssize_t spwr_battery_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; -+ -+ if (!spwr_battery_present(bat)) -+ return -ENODEV; -+ -+ status = spwr_battery_set_alarm(bat, value / 1000); -+ if (status) -+ return status; -+ -+ return count; -+} -+ -+static const struct device_attribute alarm_attr = { -+ .attr = {.name = "alarm", .mode = 0644}, -+ .show = spwr_battery_alarm_show, -+ .store = spwr_battery_alarm_store, -+}; -+ -+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 void spwr_ac_destroy(struct spwr_ac_device *ac) -+{ -+ mutex_destroy(&ac->lock); -+} -+ -+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; -+ ac->psy = power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); -+ if (IS_ERR(ac->psy)) -+ return PTR_ERR(ac->psy); -+ -+ status = ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+ if (status) -+ power_supply_unregister(ac->psy); -+ -+ return status; -+} -+ -+static int spwr_ac_unregister(struct spwr_ac_device *ac) -+{ -+ ssam_notifier_unregister(ac->sdev->ctrl, &ac->notif); -+ power_supply_unregister(ac->psy); -+ return 0; -+} -+ -+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_NONE; -+ 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 void spwr_battery_destroy(struct spwr_battery_device *bat) -+{ -+ mutex_destroy(&bat->lock); -+} -+ -+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; -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) -+ 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) -+ return status; -+ } -+ -+ 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 -ENOTSUPP; -+ } -+ -+ psy_cfg.drv_data = bat; -+ bat->psy = power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); -+ if (IS_ERR(bat->psy)) -+ return PTR_ERR(bat->psy); -+ -+ status = ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+ if (status) -+ goto err_notif; -+ -+ status = device_create_file(&bat->psy->dev, &alarm_attr); -+ if (status) -+ goto err_file; -+ -+ return 0; -+ -+err_file: -+ ssam_notifier_unregister(bat->sdev->ctrl, &bat->notif); -+err_notif: -+ power_supply_unregister(bat->psy); -+ return status; -+} -+ -+static void spwr_battery_unregister(struct spwr_battery_device *bat) -+{ -+ ssam_notifier_unregister(bat->sdev->ctrl, &bat->notif); -+ cancel_delayed_work_sync(&bat->update_work); -+ device_remove_file(&bat->psy->dev, &alarm_attr); -+ power_supply_unregister(bat->psy); -+} -+ -+ -+/* -- Power management. ----------------------------------------------------- */ -+ -+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 __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); -+ -+ -+/* -- Battery driver. ------------------------------------------------------- */ -+ -+static int surface_battery_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_battery_device *bat; -+ int status; -+ -+ 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); -+ -+ status = spwr_battery_register(bat); -+ if (status) -+ spwr_battery_destroy(bat); -+ -+ return status; -+} -+ -+static void surface_battery_remove(struct ssam_device *sdev) -+{ -+ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); -+ -+ spwr_battery_unregister(bat); -+ spwr_battery_destroy(bat); -+} -+ -+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, -+ }, -+}; -+ -+ -+/* -- AC driver. ------------------------------------------------------------ */ -+ -+static int surface_ac_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_ac_device *ac; -+ int status; -+ -+ 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); -+ -+ status = spwr_ac_register(ac); -+ if (status) -+ spwr_ac_destroy(ac); -+ -+ return status; -+} -+ -+static void surface_ac_remove(struct ssam_device *sdev) -+{ -+ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); -+ -+ spwr_ac_unregister(ac); -+ spwr_ac_destroy(ac); -+} -+ -+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 setup. --------------------------------------------------------- */ -+ -+static int __init surface_battery_init(void) -+{ -+ int status; -+ -+ status = ssam_device_driver_register(&surface_battery_driver); -+ if (status) -+ return status; -+ -+ status = ssam_device_driver_register(&surface_ac_driver); -+ if (status) -+ ssam_device_driver_unregister(&surface_battery_driver); -+ -+ return status; -+} -+module_init(surface_battery_init); -+ -+static void __exit surface_battery_exit(void) -+{ -+ ssam_device_driver_unregister(&surface_battery_driver); -+ ssam_device_driver_unregister(&surface_ac_driver); -+} -+module_exit(surface_battery_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Battery/AC driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_aggregator/clients/surface_dtx.c b/drivers/misc/surface_aggregator/clients/surface_dtx.c -new file mode 100644 -index 000000000000..b7a3b8ce7726 ---- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/surface_dtx.c -@@ -0,0 +1,1277 @@ -+// 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-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include ++#ifndef _SURFACE_AGGREGATOR_BUS_H ++#define _SURFACE_AGGREGATOR_BUS_H + +#include -+#include -+#include -+ -+ -+/* -- 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); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x06, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x07, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x08, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x09, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0a, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0b, -+ .instance_id = 0x00, -+}); -+ -+static 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, -+}); -+ -+static 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, -+}); -+ -+static 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; -+ -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ unsigned long flags; -+ -+ struct miscdevice mdev; -+ wait_queue_head_t waitq; -+ struct mutex write_lock; -+ struct rw_semaphore client_lock; -+ 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; -+ 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; -+ -+ 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; -+ -+ 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; -+ -+ 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; -+ -+ 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; -+ struct sdtx_client *client; -+ -+ ddev = container_of(file->private_data, struct sdtx_device, mdev); -+ -+ /* 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. */ -+ 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); -+ -+ 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) -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay); -+ -+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; -+}; -+ -+/* 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; -+ -+ 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; -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ len = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ len = 1; -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ len = 1; -+ 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; -+ struct sdtx_status_event event; -+ struct ssam_bas_base_info base; -+ int status, tablet; -+ u8 mode; -+ -+ ddev = container_of(work, struct sdtx_device, mode_work.work); -+ -+ /* 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); -+} -+ -+static void __sdtx_device_state_update_base(struct sdtx_device *ddev, -+ struct ssam_bas_base_info info) -+{ -+ struct sdtx_base_info_event event; -+ -+ /* 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); -+} -+ -+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. -+ */ -+ -+ 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); -+} -+ -+static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status) -+{ -+ struct sdtx_status_event event; -+ -+ /* 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; -+ struct ssam_bas_base_info base; -+ u8 mode, latch; -+ int status; -+ -+ ddev = container_of(work, struct sdtx_device, state_work.work); -+ -+ /* 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); -+ 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_setup(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) { -+ kfree(ddev); -+ return ERR_PTR(status); -+ } -+ -+ return ddev; -+} -+ -+static void sdtx_device_destroy(struct sdtx_device *ddev) -+{ -+ struct sdtx_client *client; -+ -+ /* 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); -+ -+ /* -+ * 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); -+ -+ /* 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_setup(&pdev->dev, ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ platform_set_drvdata(pdev, ddev); -+ return 0; -+} -+ -+static int surface_dtx_platform_remove(struct platform_device *pdev) -+{ -+ sdtx_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_dtx_acpi_match[] = { -+ { "MSHW0133", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match); -+ -+static struct platform_driver surface_dtx_platform_driver = { -+ .probe = surface_dtx_platform_probe, -+ .remove = surface_dtx_platform_remove, -+ .driver = { -+ .name = "surface_dtx_pltf", -+ .acpi_match_table = surface_dtx_acpi_match, -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- SSAM device driver. --------------------------------------------------- */ + +#ifdef CONFIG_SURFACE_AGGREGATOR_BUS + -+static int surface_dtx_ssam_probe(struct ssam_device *sdev) -+{ -+ struct sdtx_device *ddev; ++void ssam_controller_remove_clients(struct ssam_controller *ctrl); + -+ ddev = sdtx_device_setup(&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", -+ .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); -+} ++int ssam_bus_register(void); ++void ssam_bus_unregister(void); + +#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + -+static int ssam_dtx_driver_register(void) -+{ -+ return 0; -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+} ++static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} ++static inline int ssam_bus_register(void) { return 0; } ++static inline void ssam_bus_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 "); -+MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_aggregator/clients/surface_hid.c b/drivers/misc/surface_aggregator/clients/surface_hid.c -new file mode 100644 -index 000000000000..7d0362ae31d4 ---- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/surface_hid.c -@@ -0,0 +1,924 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID device driver. -+ * -+ * Provides support for HID input devices connected via the Surface System -+ * Aggregator Module. -+ * -+ * Copyright (C) 2019-2020 Blaž Hrastnik , -+ * Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+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 report_id, -+ u8 *data, size_t len); -+ int (*get_feature_report)(struct surface_hid_device *shid, u8 report_id, -+ u8 *data, size_t len); -+ int (*set_feature_report)(struct surface_hid_device *shid, u8 report_id, -+ u8 *data, 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; -+}; -+ -+ -+/* -- SAM interface (HID). -------------------------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+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 report_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] = report_id; -+ -+ return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL); -+} -+ -+static int ssam_hid_get_raw_report(struct surface_hid_device *shid, -+ u8 report_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(report_id); -+ rqst.payload = &report_id; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, -+ sizeof(report_id)); -+} -+ -+static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid; -+ int status; -+ -+ shid = container_of(nf, struct surface_hid_device, notif); -+ -+ if (event->command_id != 0x00) -+ return 0; -+ -+ status = hid_input_report(shid->hid, HID_INPUT_REPORT, -+ (u8 *)&event->data[0], event->length, 0); -+ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver (HID). ----------------------------------------------- */ -+ -+static int shid_output_report(struct surface_hid_device *shid, u8 report_id, -+ u8 *data, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, report_id, false, data, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_get_feature_report(struct surface_hid_device *shid, -+ u8 report_id, u8 *data, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_get_raw_report(shid, report_id, data, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_set_feature_report(struct surface_hid_device *shid, -+ u8 report_id, u8 *data, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, report_id, true, data, len); -+ return status >= 0 ? len : status; -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ -+/* -- 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; -+ int status; -+ -+ 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; -+ -+ status = hid_input_report(shid->hid, HID_INPUT_REPORT, -+ (u8 *)&event->data[0], event->length, 0); -+ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver (KBD). ----------------------------------------------- */ -+ -+static int skbd_get_caps_led_value(struct hid_device *hid, u8 report_id, -+ u8 *data, 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 (report_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, data + 1, size, offset); -+} -+ -+static int skbd_output_report(struct surface_hid_device *shid, u8 report_id, -+ u8 *data, size_t len) -+{ -+ int caps_led; -+ int status; -+ -+ caps_led = skbd_get_caps_led_value(shid->hid, report_id, data, len); -+ if (caps_led < 0) -+ return -EIO; /* Only caps 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 report_id, u8 *data, 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 (report_id != report[0]) -+ return -ENOENT; -+ -+ memcpy(data, report, ARRAY_SIZE(report)); -+ return len; -+} -+ -+static int skbd_set_feature_report(struct surface_hid_device *shid, -+ u8 report_id, u8 *data, size_t len) -+{ -+ return -EIO; -+} -+ -+ -+/* -- 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. -------------------------------------------------- */ -+ -+static 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; // TODO: BUS_SURFACE -+ 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); -+ -+ strlcpy(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; -+} -+ -+static void surface_hid_device_destroy(struct surface_hid_device *shid) -+{ -+ hid_destroy_device(shid->hid); -+} -+ -+ -+/* -- 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, -+}; -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+const struct dev_pm_ops surface_hid_pm_ops = { }; -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+ -+/* -- Driver setup (HID). --------------------------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+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, -+ }, -+}; -+ -+static int surface_hid_driver_register(void) -+{ -+ return ssam_device_driver_register(&surface_hid_driver); -+} -+ -+static void surface_hid_driver_unregister(void) -+{ -+ ssam_device_driver_unregister(&surface_hid_driver); -+} -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static int surface_hid_driver_register(void) -+{ -+ return 0; -+} -+ -+static void surface_hid_driver_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ -+/* -- Driver setup (KBD). --------------------------------------------------- */ -+ -+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 setup. --------------------------------------------------------- */ -+ -+static int __init surface_hid_init(void) -+{ -+ int status; -+ -+ status = surface_hid_driver_register(); -+ if (status) -+ return status; -+ -+ status = platform_driver_register(&surface_kbd_driver); -+ if (status) -+ surface_hid_driver_unregister(); -+ -+ return status; -+} -+module_init(surface_hid_init); -+ -+static void __exit surface_hid_exit(void) -+{ -+ platform_driver_unregister(&surface_kbd_driver); -+ surface_hid_driver_unregister(); -+} -+module_exit(surface_hid_exit); -+ -+MODULE_AUTHOR("Blaž Hrastnik "); -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport-/device-driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_aggregator/clients/surface_perfmode.c b/drivers/misc/surface_aggregator/clients/surface_perfmode.c -new file mode 100644 -index 000000000000..cac7227f27ea ---- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/surface_perfmode.c -@@ -0,0 +1,122 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface performance-mode driver. -+ * -+ * Provides a user-space interface for the performance mode control provided -+ * by the Surface System Aggregator Module (SSAM), influencing cooling -+ * behavior of the device and potentially managing power limits. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+enum sam_perf_mode { -+ SAM_PERF_MODE_NORMAL = 1, -+ SAM_PERF_MODE_BATTERY = 2, -+ SAM_PERF_MODE_PERF1 = 3, -+ SAM_PERF_MODE_PERF2 = 4, -+ -+ __SAM_PERF_MODE__MIN = 1, -+ __SAM_PERF_MODE__MAX = 4, -+}; -+ -+struct ssam_perf_info { -+ __le32 mode; -+ __le16 unknown1; -+ __le16 unknown2; -+} __packed; -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x02, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x03, -+}); -+ -+static int ssam_tmp_perf_mode_set(struct ssam_device *sdev, u32 mode) -+{ -+ __le32 mode_le = cpu_to_le32(mode); -+ -+ if (mode < __SAM_PERF_MODE__MIN || mode > __SAM_PERF_MODE__MAX) -+ return -EINVAL; -+ -+ return ssam_retry(__ssam_tmp_perf_mode_set, sdev, &mode_le); -+} -+ -+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, -+ char *data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ struct ssam_perf_info info; -+ int status; -+ -+ status = ssam_retry(ssam_tmp_perf_mode_get, sdev, &info); -+ if (status) { -+ dev_err(dev, "failed to get current performance mode: %d\n", -+ status); -+ return -EIO; -+ } -+ -+ return sprintf(data, "%d\n", le32_to_cpu(info.mode)); -+} -+ -+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(data, 0, &perf_mode); -+ if (status < 0) -+ return status; -+ -+ status = ssam_tmp_perf_mode_set(sdev, perf_mode); -+ if (status < 0) -+ return status; -+ -+ return count; -+} -+ -+static const DEVICE_ATTR_RW(perf_mode); -+ -+static int surface_sam_sid_perfmode_probe(struct ssam_device *sdev) -+{ -+ return sysfs_create_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+} -+ -+static void surface_sam_sid_perfmode_remove(struct ssam_device *sdev) -+{ -+ sysfs_remove_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+} -+ -+static const struct ssam_device_id ssam_perfmode_match[] = { -+ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_perfmode_match); -+ -+static struct ssam_device_driver surface_sam_sid_perfmode = { -+ .probe = surface_sam_sid_perfmode_probe, -+ .remove = surface_sam_sid_perfmode_remove, -+ .match_table = ssam_perfmode_match, -+ .driver = { -+ .name = "surface_performance_mode", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_sam_sid_perfmode); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Performance mode interface for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_aggregator/controller.c b/drivers/misc/surface_aggregator/controller.c ++#endif /* _SURFACE_AGGREGATOR_BUS_H */ +diff --git a/drivers/platform/x86/surface_aggregator/controller.c b/drivers/platform/x86/surface_aggregator/controller.c new file mode 100644 index 000000000000..5bcb59ed579d --- /dev/null -+++ b/drivers/misc/surface_aggregator/controller.c ++++ b/drivers/platform/x86/surface_aggregator/controller.c @@ -0,0 +1,2579 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* @@ -11384,11 +7862,11 @@ index 000000000000..5bcb59ed579d + } + disable_irq(ctrl->irq.num); +} -diff --git a/drivers/misc/surface_aggregator/controller.h b/drivers/misc/surface_aggregator/controller.h +diff --git a/drivers/platform/x86/surface_aggregator/controller.h b/drivers/platform/x86/surface_aggregator/controller.h new file mode 100644 index 000000000000..8297d34e7489 --- /dev/null -+++ b/drivers/misc/surface_aggregator/controller.h ++++ b/drivers/platform/x86/surface_aggregator/controller.h @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* @@ -11675,11 +8153,11 @@ index 000000000000..8297d34e7489 +void ssam_event_item_cache_destroy(void); + +#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ -diff --git a/drivers/misc/surface_aggregator/core.c b/drivers/misc/surface_aggregator/core.c +diff --git a/drivers/platform/x86/surface_aggregator/core.c b/drivers/platform/x86/surface_aggregator/core.c new file mode 100644 index 000000000000..8dc2c267bcd6 --- /dev/null -+++ b/drivers/misc/surface_aggregator/core.c ++++ b/drivers/platform/x86/surface_aggregator/core.c @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* @@ -12520,11 +8998,11 @@ index 000000000000..8dc2c267bcd6 +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_aggregator/ssh_msgb.h b/drivers/misc/surface_aggregator/ssh_msgb.h +diff --git a/drivers/platform/x86/surface_aggregator/ssh_msgb.h b/drivers/platform/x86/surface_aggregator/ssh_msgb.h new file mode 100644 index 000000000000..1221f642dda1 --- /dev/null -+++ b/drivers/misc/surface_aggregator/ssh_msgb.h ++++ b/drivers/platform/x86/surface_aggregator/ssh_msgb.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* @@ -12731,11 +9209,11 @@ index 000000000000..1221f642dda1 +} + +#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */ -diff --git a/drivers/misc/surface_aggregator/ssh_packet_layer.c b/drivers/misc/surface_aggregator/ssh_packet_layer.c +diff --git a/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c new file mode 100644 -index 000000000000..583315db8b02 +index 000000000000..15d96eac6811 --- /dev/null -+++ b/drivers/misc/surface_aggregator/ssh_packet_layer.c ++++ b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c @@ -0,0 +1,2074 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* @@ -14513,7 +10991,7 @@ index 000000000000..583315db8b02 + break; + } + -+ return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(frame->len); ++ return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(payload.len); +} + +static int ssh_ptl_rx_threadfn(void *data) @@ -14811,11 +11289,11 @@ index 000000000000..583315db8b02 + kfifo_free(&ptl->rx.fifo); + sshp_buf_free(&ptl->rx.buf); +} -diff --git a/drivers/misc/surface_aggregator/ssh_packet_layer.h b/drivers/misc/surface_aggregator/ssh_packet_layer.h +diff --git a/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h new file mode 100644 index 000000000000..e8757d03f279 --- /dev/null -+++ b/drivers/misc/surface_aggregator/ssh_packet_layer.h ++++ b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* @@ -15007,11 +11485,11 @@ index 000000000000..e8757d03f279 +void ssh_ctrl_packet_cache_destroy(void); + +#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ -diff --git a/drivers/misc/surface_aggregator/ssh_parser.c b/drivers/misc/surface_aggregator/ssh_parser.c +diff --git a/drivers/platform/x86/surface_aggregator/ssh_parser.c b/drivers/platform/x86/surface_aggregator/ssh_parser.c new file mode 100644 index 000000000000..e2dead8de94a --- /dev/null -+++ b/drivers/misc/surface_aggregator/ssh_parser.c ++++ b/drivers/platform/x86/surface_aggregator/ssh_parser.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* @@ -15241,12 +11719,12 @@ index 000000000000..e2dead8de94a + + return 0; +} -diff --git a/drivers/misc/surface_aggregator/ssh_parser.h b/drivers/misc/surface_aggregator/ssh_parser.h +diff --git a/drivers/platform/x86/surface_aggregator/ssh_parser.h b/drivers/platform/x86/surface_aggregator/ssh_parser.h new file mode 100644 -index 000000000000..395c61ef890b +index 000000000000..63c38d350988 --- /dev/null -+++ b/drivers/misc/surface_aggregator/ssh_parser.h -@@ -0,0 +1,155 @@ ++++ b/drivers/platform/x86/surface_aggregator/ssh_parser.h +@@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH message parser. @@ -15264,7 +11742,6 @@ index 000000000000..395c61ef890b + +#include + -+ +/** + * struct sshp_buf - Parser buffer for SSH messages. + * @ptr: Pointer to the beginning of the buffer. @@ -15402,11 +11879,11 @@ index 000000000000..395c61ef890b + struct ssam_span *command_data); + +#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */ -diff --git a/drivers/misc/surface_aggregator/ssh_request_layer.c b/drivers/misc/surface_aggregator/ssh_request_layer.c +diff --git a/drivers/platform/x86/surface_aggregator/ssh_request_layer.c b/drivers/platform/x86/surface_aggregator/ssh_request_layer.c new file mode 100644 index 000000000000..52a83a8fcf82 --- /dev/null -+++ b/drivers/misc/surface_aggregator/ssh_request_layer.c ++++ b/drivers/platform/x86/surface_aggregator/ssh_request_layer.c @@ -0,0 +1,1263 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* @@ -16671,11 +13148,11 @@ index 000000000000..52a83a8fcf82 + ssh_request_put(r); + } +} -diff --git a/drivers/misc/surface_aggregator/ssh_request_layer.h b/drivers/misc/surface_aggregator/ssh_request_layer.h +diff --git a/drivers/platform/x86/surface_aggregator/ssh_request_layer.h b/drivers/platform/x86/surface_aggregator/ssh_request_layer.h new file mode 100644 index 000000000000..cb35815858d1 --- /dev/null -+++ b/drivers/misc/surface_aggregator/ssh_request_layer.h ++++ b/drivers/platform/x86/surface_aggregator/ssh_request_layer.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* @@ -16820,11 +13297,11 @@ index 000000000000..cb35815858d1 + const struct ssh_request_ops *ops); + +#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */ -diff --git a/drivers/misc/surface_aggregator/trace.h b/drivers/misc/surface_aggregator/trace.h +diff --git a/drivers/platform/x86/surface_aggregator/trace.h b/drivers/platform/x86/surface_aggregator/trace.h new file mode 100644 index 000000000000..eb332bb53ae4 --- /dev/null -+++ b/drivers/misc/surface_aggregator/trace.h ++++ b/drivers/platform/x86/surface_aggregator/trace.h @@ -0,0 +1,632 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* @@ -17458,6 +13935,3664 @@ index 000000000000..eb332bb53ae4 +#define TRACE_INCLUDE_FILE trace + +#include +diff --git a/drivers/platform/x86/surface_aggregator_cdev.c b/drivers/platform/x86/surface_aggregator_cdev.c +new file mode 100644 +index 000000000000..79e28fab7e40 +--- /dev/null ++++ b/drivers/platform/x86/surface_aggregator_cdev.c +@@ -0,0 +1,322 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Provides user-space access to the SSAM EC via the /dev/surface/aggregator ++ * misc device. Intended for debugging and development. ++ * ++ * Copyright (C) 2020 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" ++ ++struct ssam_cdev { ++ struct kref kref; ++ struct rw_semaphore lock; ++ struct ssam_controller *ctrl; ++ struct miscdevice mdev; ++}; ++ ++static void __ssam_cdev_release(struct kref *kref) ++{ ++ kfree(container_of(kref, struct ssam_cdev, kref)); ++} ++ ++static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) ++{ ++ if (cdev) ++ kref_get(&cdev->kref); ++ ++ return cdev; ++} ++ ++static void ssam_cdev_put(struct ssam_cdev *cdev) ++{ ++ if (cdev) ++ kref_put(&cdev->kref, __ssam_cdev_release); ++} ++ ++static int ssam_cdev_device_open(struct inode *inode, struct file *filp) ++{ ++ struct miscdevice *mdev = filp->private_data; ++ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); ++ ++ filp->private_data = ssam_cdev_get(cdev); ++ return stream_open(inode, filp); ++} ++ ++static int ssam_cdev_device_release(struct inode *inode, struct file *filp) ++{ ++ ssam_cdev_put(filp->private_data); ++ return 0; ++} ++ ++static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) ++{ ++ struct ssam_cdev_request __user *r; ++ struct ssam_cdev_request rqst; ++ struct ssam_request spec = {}; ++ struct ssam_response rsp = {}; ++ const void __user *plddata; ++ void __user *rspdata; ++ int status = 0, ret = 0, tmp; ++ ++ r = (struct ssam_cdev_request __user *)arg; ++ ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); ++ if (ret) ++ goto out; ++ ++ plddata = u64_to_user_ptr(rqst.payload.data); ++ rspdata = u64_to_user_ptr(rqst.response.data); ++ ++ /* Setup basic request fields. */ ++ spec.target_category = rqst.target_category; ++ spec.target_id = rqst.target_id; ++ spec.command_id = rqst.command_id; ++ spec.instance_id = rqst.instance_id; ++ spec.flags = 0; ++ spec.length = rqst.payload.length; ++ spec.payload = NULL; ++ ++ if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE) ++ spec.flags |= SSAM_REQUEST_HAS_RESPONSE; ++ ++ if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED) ++ spec.flags |= SSAM_REQUEST_UNSEQUENCED; ++ ++ rsp.capacity = rqst.response.length; ++ rsp.length = 0; ++ rsp.pointer = NULL; ++ ++ /* Get request payload from user-space. */ ++ if (spec.length) { ++ if (!plddata) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ /* ++ * Note: spec.length is limited to U16_MAX bytes via struct ++ * ssam_cdev_request. This is slightly larger than the ++ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the ++ * underlying protocol (note that nothing remotely this size ++ * should ever be allocated in any normal case). This size is ++ * validated later in ssam_request_sync(), for allocation the ++ * bound imposed by u16 should be enough. ++ */ ++ spec.payload = kzalloc(spec.length, GFP_KERNEL); ++ if (!spec.payload) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ if (copy_from_user((void *)spec.payload, plddata, spec.length)) { ++ ret = -EFAULT; ++ goto out; ++ } ++ } ++ ++ /* Allocate response buffer. */ ++ if (rsp.capacity) { ++ if (!rspdata) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ /* ++ * Note: rsp.capacity is limited to U16_MAX bytes via struct ++ * ssam_cdev_request. This is slightly larger than the ++ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the ++ * underlying protocol (note that nothing remotely this size ++ * should ever be allocated in any normal case). In later use, ++ * this capacity does not have to be strictly bounded, as it ++ * is only used as an output buffer to be written to. For ++ * allocation the bound imposed by u16 should be enough. ++ */ ++ rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); ++ if (!rsp.pointer) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ } ++ ++ /* Perform request. */ ++ status = ssam_request_sync(cdev->ctrl, &spec, &rsp); ++ if (status) ++ goto out; ++ ++ /* Copy response to user-space. */ ++ if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length)) ++ ret = -EFAULT; ++ ++out: ++ /* Always try to set response-length and status. */ ++ tmp = put_user(rsp.length, &r->response.length); ++ if (tmp) ++ ret = tmp; ++ ++ tmp = put_user(status, &r->status); ++ if (tmp) ++ ret = tmp; ++ ++ /* Cleanup. */ ++ kfree(spec.payload); ++ kfree(rsp.pointer); ++ ++ return ret; ++} ++ ++static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, ++ unsigned long arg) ++{ ++ switch (cmd) { ++ case SSAM_CDEV_REQUEST: ++ return ssam_cdev_request(cdev, arg); ++ ++ default: ++ return -ENOTTY; ++ } ++} ++ ++static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct ssam_cdev *cdev = file->private_data; ++ long status; ++ ++ /* Ensure that controller is valid for as long as we need it. */ ++ if (down_read_killable(&cdev->lock)) ++ return -ERESTARTSYS; ++ ++ if (!cdev->ctrl) { ++ up_read(&cdev->lock); ++ return -ENODEV; ++ } ++ ++ status = __ssam_cdev_device_ioctl(cdev, cmd, arg); ++ ++ up_read(&cdev->lock); ++ return status; ++} ++ ++static const struct file_operations ssam_controller_fops = { ++ .owner = THIS_MODULE, ++ .open = ssam_cdev_device_open, ++ .release = ssam_cdev_device_release, ++ .unlocked_ioctl = ssam_cdev_device_ioctl, ++ .compat_ioctl = ssam_cdev_device_ioctl, ++ .llseek = noop_llseek, ++}; ++ ++static int ssam_dbg_device_probe(struct platform_device *pdev) ++{ ++ struct ssam_controller *ctrl; ++ struct ssam_cdev *cdev; ++ int status; ++ ++ ctrl = ssam_client_bind(&pdev->dev); ++ if (IS_ERR(ctrl)) ++ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); ++ ++ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); ++ if (!cdev) ++ return -ENOMEM; ++ ++ kref_init(&cdev->kref); ++ init_rwsem(&cdev->lock); ++ cdev->ctrl = ctrl; ++ ++ cdev->mdev.parent = &pdev->dev; ++ cdev->mdev.minor = MISC_DYNAMIC_MINOR; ++ cdev->mdev.name = "surface_aggregator"; ++ cdev->mdev.nodename = "surface/aggregator"; ++ cdev->mdev.fops = &ssam_controller_fops; ++ ++ status = misc_register(&cdev->mdev); ++ if (status) { ++ kfree(cdev); ++ return status; ++ } ++ ++ platform_set_drvdata(pdev, cdev); ++ return 0; ++} ++ ++static int ssam_dbg_device_remove(struct platform_device *pdev) ++{ ++ struct ssam_cdev *cdev = platform_get_drvdata(pdev); ++ ++ misc_deregister(&cdev->mdev); ++ ++ /* ++ * The controller is only guaranteed to be valid for as long as the ++ * driver is bound. Remove controller so that any lingering open files ++ * cannot access it any more after we're gone. ++ */ ++ down_write(&cdev->lock); ++ cdev->ctrl = NULL; ++ up_write(&cdev->lock); ++ ++ ssam_cdev_put(cdev); ++ return 0; ++} ++ ++static struct platform_device *ssam_cdev_device; ++ ++static struct platform_driver ssam_cdev_driver = { ++ .probe = ssam_dbg_device_probe, ++ .remove = ssam_dbg_device_remove, ++ .driver = { ++ .name = SSAM_CDEV_DEVICE_NAME, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++static int __init ssam_debug_init(void) ++{ ++ int status; ++ ++ ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME, ++ PLATFORM_DEVID_NONE); ++ if (!ssam_cdev_device) ++ return -ENOMEM; ++ ++ status = platform_device_add(ssam_cdev_device); ++ if (status) ++ goto err_device; ++ ++ status = platform_driver_register(&ssam_cdev_driver); ++ if (status) ++ goto err_driver; ++ ++ return 0; ++ ++err_driver: ++ platform_device_del(ssam_cdev_device); ++err_device: ++ platform_device_put(ssam_cdev_device); ++ return status; ++} ++module_init(ssam_debug_init); ++ ++static void __exit ssam_debug_exit(void) ++{ ++ platform_driver_unregister(&ssam_cdev_driver); ++ platform_device_unregister(ssam_cdev_device); ++} ++module_exit(ssam_debug_exit); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/surface_aggregator_registry.c b/drivers/platform/x86/surface_aggregator_registry.c +new file mode 100644 +index 000000000000..caee90d135c5 +--- /dev/null ++++ b/drivers/platform/x86/surface_aggregator_registry.c +@@ -0,0 +1,641 @@ ++// 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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++ ++/* -- Device registry. ------------------------------------------------------ */ ++ ++/* ++ * SSAM device names follow the SSAM module alias, meaning they are prefixed ++ * with 'ssam:', followed by domain, category, target ID, instance ID, and ++ * function, each encoded as two-digit hexadecimal, separated by ':'. In other ++ * words, it follows the scheme ++ * ++ * ssam:dd:cc:tt:ii:ff ++ * ++ * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal ++ * values mentioned above, respectively. ++ */ ++ ++/* Root node. */ ++static const struct software_node ssam_node_root = { ++ .name = "ssam_platform_hub", ++}; ++ ++/* Base device hub (devices attached to Surface Book 3 base). */ ++static const struct software_node ssam_node_hub_base = { ++ .name = "ssam:00:00:02:00:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* AC adapter. */ ++static const struct software_node ssam_node_bat_ac = { ++ .name = "ssam:01:02:01:01:01", ++ .parent = &ssam_node_root, ++}; ++ ++/* Primary battery. */ ++static const struct software_node ssam_node_bat_main = { ++ .name = "ssam:01:02:01:01:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* Secondary battery (Surface Book 3). */ ++static const struct software_node ssam_node_bat_sb3base = { ++ .name = "ssam:01:02:02:01:00", ++ .parent = &ssam_node_hub_base, ++}; ++ ++/* Platform profile / performance-mode device. */ ++static const struct software_node ssam_node_tmp_pprof = { ++ .name = "ssam:01:03:01:00:01", ++ .parent = &ssam_node_root, ++}; ++ ++/* DTX / detachment-system device (Surface Book 3). */ ++static const struct software_node ssam_node_bas_dtx = { ++ .name = "ssam:01:11:01:00:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* HID keyboard. */ ++static const struct software_node ssam_node_hid_main_keyboard = { ++ .name = "ssam:01:15:02:01:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* HID touchpad. */ ++static const struct software_node ssam_node_hid_main_touchpad = { ++ .name = "ssam:01:15:02:03:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* HID device instance 5 (unknown HID device). */ ++static const struct software_node ssam_node_hid_main_iid5 = { ++ .name = "ssam:01:15:02:05:00", ++ .parent = &ssam_node_root, ++}; ++ ++/* HID keyboard (base hub). */ ++static const struct software_node ssam_node_hid_base_keyboard = { ++ .name = "ssam:01:15:02:01:00", ++ .parent = &ssam_node_hub_base, ++}; ++ ++/* HID touchpad (base hub). */ ++static const struct software_node ssam_node_hid_base_touchpad = { ++ .name = "ssam:01:15:02:03:00", ++ .parent = &ssam_node_hub_base, ++}; ++ ++/* HID device instance 5 (unknown HID device, base hub). */ ++static const struct software_node ssam_node_hid_base_iid5 = { ++ .name = "ssam:01:15:02:05:00", ++ .parent = &ssam_node_hub_base, ++}; ++ ++/* HID device instance 6 (unknown HID device, base hub). */ ++static const struct software_node ssam_node_hid_base_iid6 = { ++ .name = "ssam:01:15:02:06:00", ++ .parent = &ssam_node_hub_base, ++}; ++ ++/* Devices for Surface Book 2. */ ++static const struct software_node *ssam_node_group_sb2[] = { ++ &ssam_node_root, ++ &ssam_node_tmp_pprof, ++ NULL, ++}; ++ ++/* Devices for Surface Book 3. */ ++static const struct software_node *ssam_node_group_sb3[] = { ++ &ssam_node_root, ++ &ssam_node_hub_base, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, ++ &ssam_node_bat_sb3base, ++ &ssam_node_tmp_pprof, ++ &ssam_node_bas_dtx, ++ &ssam_node_hid_base_keyboard, ++ &ssam_node_hid_base_touchpad, ++ &ssam_node_hid_base_iid5, ++ &ssam_node_hid_base_iid6, ++ NULL, ++}; ++ ++/* Devices for Surface Laptop 1. */ ++static const struct software_node *ssam_node_group_sl1[] = { ++ &ssam_node_root, ++ &ssam_node_tmp_pprof, ++ NULL, ++}; ++ ++/* Devices for Surface Laptop 2. */ ++static const struct software_node *ssam_node_group_sl2[] = { ++ &ssam_node_root, ++ &ssam_node_tmp_pprof, ++ NULL, ++}; ++ ++/* Devices for Surface Laptop 3. */ ++static const struct software_node *ssam_node_group_sl3[] = { ++ &ssam_node_root, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, ++ &ssam_node_tmp_pprof, ++ &ssam_node_hid_main_keyboard, ++ &ssam_node_hid_main_touchpad, ++ &ssam_node_hid_main_iid5, ++ NULL, ++}; ++ ++/* Devices for Surface Laptop Go. */ ++static const struct software_node *ssam_node_group_slg1[] = { ++ &ssam_node_root, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, ++ &ssam_node_tmp_pprof, ++ NULL, ++}; ++ ++/* Devices for Surface Pro 5. */ ++static const struct software_node *ssam_node_group_sp5[] = { ++ &ssam_node_root, ++ &ssam_node_tmp_pprof, ++ NULL, ++}; ++ ++/* Devices for Surface Pro 6. */ ++static const struct software_node *ssam_node_group_sp6[] = { ++ &ssam_node_root, ++ &ssam_node_tmp_pprof, ++ NULL, ++}; ++ ++/* Devices for Surface Pro 7. */ ++static const struct software_node *ssam_node_group_sp7[] = { ++ &ssam_node_root, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, ++ &ssam_node_tmp_pprof, ++ NULL, ++}; ++ ++ ++/* -- Device registry helper functions. ------------------------------------- */ ++ ++static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) ++{ ++ u8 d, tc, tid, iid, fn; ++ int n; ++ ++ n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); ++ if (n != 5) ++ return -EINVAL; ++ ++ uid->domain = d; ++ uid->category = tc; ++ uid->target = tid; ++ uid->instance = iid; ++ uid->function = fn; ++ ++ return 0; ++} ++ ++static int ssam_hub_remove_devices_fn(struct device *dev, void *data) ++{ ++ if (!is_ssam_device(dev)) ++ return 0; ++ ++ ssam_device_remove(to_ssam_device(dev)); ++ return 0; ++} ++ ++static void ssam_hub_remove_devices(struct device *parent) ++{ ++ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); ++} ++ ++static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, ++ struct fwnode_handle *node) ++{ ++ struct ssam_device_uid uid; ++ struct ssam_device *sdev; ++ int status; ++ ++ status = ssam_uid_from_string(fwnode_get_name(node), &uid); ++ if (status) ++ return status; ++ ++ sdev = ssam_device_alloc(ctrl, uid); ++ if (!sdev) ++ return -ENOMEM; ++ ++ sdev->dev.parent = parent; ++ sdev->dev.fwnode = node; ++ ++ status = ssam_device_add(sdev); ++ if (status) ++ ssam_device_put(sdev); ++ ++ return status; ++} ++ ++static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, ++ struct fwnode_handle *node) ++{ ++ struct fwnode_handle *child; ++ int status; ++ ++ fwnode_for_each_child_node(node, child) { ++ /* ++ * Try to add the device specified in the firmware node. If ++ * this fails with -EINVAL, the node does not specify any SSAM ++ * device, so ignore it and continue with the next one. ++ */ ++ ++ status = ssam_hub_add_device(parent, ctrl, child); ++ if (status && status != -EINVAL) ++ goto err; ++ } ++ ++ return 0; ++err: ++ ssam_hub_remove_devices(parent); ++ return status; ++} ++ ++ ++/* -- SSAM base-hub driver. ------------------------------------------------- */ ++ ++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[] = { ++ /* 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 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 "); ++MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/surface_dtx.c b/drivers/platform/x86/surface_dtx.c +new file mode 100644 +index 000000000000..4bb5d286bf95 +--- /dev/null ++++ b/drivers/platform/x86/surface_dtx.c +@@ -0,0 +1,1289 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface Book (gen. 2 and later) detachment system (DTX) driver. ++ * ++ * Provides a user-space interface to properly handle clipboard/tablet ++ * (containing screen and processor) detachment from the base of the device ++ * (containing the keyboard and optionally a discrete GPU). Allows to ++ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in ++ * use), or request detachment via user-space. ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++ ++/* -- 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); ++ ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x06, ++ .instance_id = 0x00, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x07, ++ .instance_id = 0x00, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x08, ++ .instance_id = 0x00, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x09, ++ .instance_id = 0x00, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0a, ++ .instance_id = 0x00, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0b, ++ .instance_id = 0x00, ++}); ++ ++static 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, ++}); ++ ++static 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, ++}); ++ ++static 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, ++ }, ++}; ++ ++ ++/* -- 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 "); ++MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/surface_perfmode.c b/drivers/platform/x86/surface_perfmode.c +new file mode 100644 +index 000000000000..3b92a43f8606 +--- /dev/null ++++ b/drivers/platform/x86/surface_perfmode.c +@@ -0,0 +1,122 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface performance-mode driver. ++ * ++ * Provides a user-space interface for the performance mode control provided ++ * by the Surface System Aggregator Module (SSAM), influencing cooling ++ * behavior of the device and potentially managing power limits. ++ * ++ * Copyright (C) 2019-2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++enum sam_perf_mode { ++ SAM_PERF_MODE_NORMAL = 1, ++ SAM_PERF_MODE_BATTERY = 2, ++ SAM_PERF_MODE_PERF1 = 3, ++ SAM_PERF_MODE_PERF2 = 4, ++ ++ __SAM_PERF_MODE__MIN = 1, ++ __SAM_PERF_MODE__MAX = 4, ++}; ++ ++struct ssam_perf_info { ++ __le32 mode; ++ __le16 unknown1; ++ __le16 unknown2; ++} __packed; ++ ++static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x02, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x03, ++}); ++ ++static int ssam_tmp_perf_mode_set(struct ssam_device *sdev, u32 mode) ++{ ++ __le32 mode_le = cpu_to_le32(mode); ++ ++ if (mode < __SAM_PERF_MODE__MIN || mode > __SAM_PERF_MODE__MAX) ++ return -EINVAL; ++ ++ return ssam_retry(__ssam_tmp_perf_mode_set, sdev, &mode_le); ++} ++ ++static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, ++ char *data) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ struct ssam_perf_info info; ++ int status; ++ ++ status = ssam_retry(ssam_tmp_perf_mode_get, sdev, &info); ++ if (status) { ++ dev_err(dev, "failed to get current performance mode: %d\n", ++ status); ++ return -EIO; ++ } ++ ++ return sprintf(data, "%d\n", le32_to_cpu(info.mode)); ++} ++ ++static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, ++ const char *data, size_t count) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ int perf_mode; ++ int status; ++ ++ status = kstrtoint(data, 0, &perf_mode); ++ if (status < 0) ++ return status; ++ ++ status = ssam_tmp_perf_mode_set(sdev, perf_mode); ++ if (status < 0) ++ return status; ++ ++ return count; ++} ++ ++static const DEVICE_ATTR_RW(perf_mode); ++ ++static int surface_sam_sid_perfmode_probe(struct ssam_device *sdev) ++{ ++ return sysfs_create_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); ++} ++ ++static void surface_sam_sid_perfmode_remove(struct ssam_device *sdev) ++{ ++ sysfs_remove_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); ++} ++ ++static const struct ssam_device_id ssam_perfmode_match[] = { ++ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, ssam_perfmode_match); ++ ++static struct ssam_device_driver surface_sam_sid_perfmode = { ++ .probe = surface_sam_sid_perfmode_probe, ++ .remove = surface_sam_sid_perfmode_remove, ++ .match_table = ssam_perfmode_match, ++ .driver = { ++ .name = "surface_performance_mode", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_sam_sid_perfmode); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Performance mode interface for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig +index eec646c568b7..d4105228a2a5 100644 +--- a/drivers/power/supply/Kconfig ++++ b/drivers/power/supply/Kconfig +@@ -774,4 +774,36 @@ config RN5T618_POWER + This driver can also be built as a module. If so, the module will be + called rn5t618_power. + ++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. ++ ++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 dd4b86318cd9..9fdd34956153 100644 +--- a/drivers/power/supply/Makefile ++++ b/drivers/power/supply/Makefile +@@ -98,3 +98,5 @@ obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o + obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o + obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o + obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o ++obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o ++obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +new file mode 100644 +index 000000000000..327fd7af386b +--- /dev/null ++++ b/drivers/power/supply/surface_battery.c +@@ -0,0 +1,901 @@ ++// 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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++/* -- 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) */ ++static 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). */ ++static 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). */ ++static 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). */ ++static 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_battery_recheck_adapter() 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 int spwr_battery_recheck_adapter(struct spwr_battery_device *bat) ++{ ++ /* ++ * 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); ++ return 0; ++} ++ ++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); ++ ++ /* Handled here, needs to be handled for all targets/instances. */ ++ if (event->command_id == SAM_EVENT_CID_BAT_ADP) { ++ status = spwr_battery_recheck_adapter(bat); ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++ } ++ ++ if (bat->sdev->uid.target != event->target_id) ++ return 0; ++ ++ if (bat->sdev->uid.instance != event->instance_id) ++ return 0; ++ ++ 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) ++ power_supply_changed(bat->psy); ++ ++ if (status) ++ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status); ++} ++ ++ ++/* -- Properties. ----------------------------------------------------------- */ ++ ++static 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 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 -ENODEV; ++ ++ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN) ++ return -ENODEV; ++ ++ 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 = -ENODEV; ++ 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 = -ENODEV; ++ 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 = -ENODEV; ++ 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 = -ENODEV; ++ 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 = -ENODEV; ++ 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 = -ENODEV; ++ 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 = -ENODEV; ++ 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 spwr_battery_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 spwr_battery_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; ++} ++ ++static const struct device_attribute alarm_attr = { ++ .attr = {.name = "alarm", .mode = 0644}, ++ .show = spwr_battery_alarm_show, ++ .store = spwr_battery_alarm_store, ++}; ++ ++ ++/* -- 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_NONE; ++ 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 void spwr_battery_destroy(struct spwr_battery_device *bat) ++{ ++ mutex_destroy(&bat->lock); ++} ++ ++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); ++ ++ 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; ++ bat->psy = power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); ++ if (IS_ERR(bat->psy)) ++ return PTR_ERR(bat->psy); ++ ++ status = ssam_notifier_register(bat->sdev->ctrl, &bat->notif); ++ if (status) ++ goto err_notif; ++ ++ status = device_create_file(&bat->psy->dev, &alarm_attr); ++ if (status) ++ goto err_file; ++ ++ return 0; ++ ++err_file: ++ ssam_notifier_unregister(bat->sdev->ctrl, &bat->notif); ++err_notif: ++ power_supply_unregister(bat->psy); ++ return status; ++} ++ ++static void spwr_battery_unregister(struct spwr_battery_device *bat) ++{ ++ ssam_notifier_unregister(bat->sdev->ctrl, &bat->notif); ++ cancel_delayed_work_sync(&bat->update_work); ++ device_remove_file(&bat->psy->dev, &alarm_attr); ++ power_supply_unregister(bat->psy); ++} ++ ++ ++/* -- 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; ++ int status; ++ ++ 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); ++ ++ status = spwr_battery_register(bat); ++ if (status) ++ spwr_battery_destroy(bat); ++ ++ return status; ++} ++ ++static void surface_battery_remove(struct ssam_device *sdev) ++{ ++ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); ++ ++ spwr_battery_unregister(bat); ++ spwr_battery_destroy(bat); ++} ++ ++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 "); ++MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +new file mode 100644 +index 000000000000..982f9b9ef6f5 +--- /dev/null ++++ b/drivers/power/supply/surface_charger.c +@@ -0,0 +1,296 @@ ++// 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 ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++/* -- 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). */ ++static 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). */ ++static 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 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 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 void spwr_ac_destroy(struct spwr_ac_device *ac) ++{ ++ mutex_destroy(&ac->lock); ++} ++ ++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; ++ ac->psy = power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); ++ if (IS_ERR(ac->psy)) ++ return PTR_ERR(ac->psy); ++ ++ status = ssam_notifier_register(ac->sdev->ctrl, &ac->notif); ++ if (status) ++ power_supply_unregister(ac->psy); ++ ++ return status; ++} ++ ++static int spwr_ac_unregister(struct spwr_ac_device *ac) ++{ ++ ssam_notifier_unregister(ac->sdev->ctrl, &ac->notif); ++ power_supply_unregister(ac->psy); ++ return 0; ++} ++ ++ ++/* -- 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; ++ int status; ++ ++ 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); ++ ++ status = spwr_ac_register(ac); ++ if (status) ++ spwr_ac_destroy(ac); ++ ++ return status; ++} ++ ++static void surface_ac_remove(struct ssam_device *sdev) ++{ ++ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); ++ ++ spwr_ac_unregister(ac); ++ spwr_ac_destroy(ac); ++} ++ ++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 "); ++MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index ef64063fac30..0b8f1feefe0e 100644 --- a/include/linux/mod_devicetable.h @@ -19746,5 +19881,5 @@ index a6c583362b92..5b79fdc42641 100644 ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0007-surface-hotplug.patch b/patches/5.10/0007-surface-hotplug.patch index e2a9506da..33e6ae957 100644 --- a/patches/5.10/0007-surface-hotplug.patch +++ b/patches/5.10/0007-surface-hotplug.patch @@ -1,4 +1,4 @@ -From 57ef85688b82ee9f409b65c6fb065b6a529aef12 Mon Sep 17 00:00:00 2001 +From 97b4cb5fe354ae2aa11a51bf0add7434f65da636 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 9 Nov 2020 14:23:00 +0100 Subject: [PATCH] PCI: Run platform power transition on initial D0 entry @@ -56,9 +56,9 @@ index 6427cbd0a5be..3200afed2604 100644 return err; -- -2.30.0 +2.30.1 -From 249b96f567db1adb80007428177141f6aec5c61f Mon Sep 17 00:00:00 2001 +From e99a49e3321f5f210825bd1cd35b04f9aff73a92 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 31 Oct 2020 20:46:33 +0100 Subject: [PATCH] PCI: Add sysfs attribute for PCI device power state @@ -129,9 +129,9 @@ index d15c881e2e7e..b15f754e6346 100644 &dev_attr_vendor.attr, &dev_attr_device.attr, -- -2.30.0 +2.30.1 -From de32407f6bf024ae5550c26f8a21546ab46c97ec Mon Sep 17 00:00:00 2001 +From 0702027c25539792b81325e843377c54010d8d56 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 14 Dec 2020 20:50:59 +0100 Subject: [PATCH] platform/x86: Add Surface Hotplug driver @@ -144,17 +144,17 @@ appropriate signal to the PCIe hot-plug driver core. Signed-off-by: Maximilian Luz Patchset: surface-hotplug --- - drivers/platform/x86/Kconfig | 12 ++ + drivers/platform/x86/Kconfig | 20 ++ drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_hotplug.c | 267 +++++++++++++++++++++++++ - 3 files changed, 280 insertions(+) + drivers/platform/x86/surface_hotplug.c | 282 +++++++++++++++++++++++++ + 3 files changed, 303 insertions(+) create mode 100644 drivers/platform/x86/surface_hotplug.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index a9b12f4dcbd1..3e882b1e1f74 100644 +index dbb07644c312..0c513c8bbd2b 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig -@@ -910,6 +910,18 @@ config SURFACE_GPE +@@ -1012,6 +1012,26 @@ config SURFACE_GPE accordingly. It is required on those devices to allow wake-ups from suspend by opening the lid. @@ -163,21 +163,29 @@ index a9b12f4dcbd1..3e882b1e1f74 100644 + depends on ACPI + default m + help -+ Driver for the Surface discrete GPU (dGPU) hot-plug system. ++ Driver for out-of-band hot-plug event signaling on Microsoft Surface ++ devices with hot-pluggable PCIe cards. + -+ This driver provides support for out-of-band hot-plug event signaling -+ on Surface Book 2 and 3 devices. This out-of-band signaling is -+ required to notify the kernel of any hot-plug events when the dGPU is -+ powered off, i.e. in D3cold. ++ This driver is used on Surface Book (2 and 3) devices with a ++ hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those ++ devices can enter D3cold, which prevents in-band (standard) PCIe ++ hot-plug signaling. Thus, without this driver, detaching the base ++ containing the dGPU will not correctly update the state of the ++ corresponding PCIe device if it is in D3cold. This driver adds support ++ for out-of-band hot-plug notifications, ensuring that the device state ++ is properly updated even when the device in question is in D3cold. ++ ++ 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_BOOK1_DGPU_SWITCH tristate "Surface Book 1 dGPU Switch Driver" depends on ACPI && SYSFS diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 562d83940e7b..2009224dcaae 100644 +index 2e0a2896c78d..f552cbfb7914 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile -@@ -87,6 +87,7 @@ obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o +@@ -94,6 +94,7 @@ obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o @@ -187,13 +195,13 @@ index 562d83940e7b..2009224dcaae 100644 # MSI diff --git a/drivers/platform/x86/surface_hotplug.c b/drivers/platform/x86/surface_hotplug.c new file mode 100644 -index 000000000000..572fba30cd77 +index 000000000000..cfcc15cfbacb --- /dev/null +++ b/drivers/platform/x86/surface_hotplug.c -@@ -0,0 +1,267 @@ +@@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* -+ * Surface Book (gen. 2 and later) hot-plug driver. ++ * Surface Book (2 and later) hot-plug driver. + * + * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This + * driver is responsible for out-of-band hot-plug event signaling on these @@ -203,7 +211,7 @@ index 000000000000..572fba30cd77 + * Event signaling is handled via ACPI, which will generate the appropriate + * device-check notifications to be picked up by the PCIe hot-plug driver. + * -+ * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + +#include @@ -233,8 +241,7 @@ index 000000000000..572fba30cd77 + +/* 5515a847-ed55-4b27-8352-cd320e10360a */ +static const guid_t shps_dsm_guid = -+ GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, -+ 0x32, 0x0e, 0x10, 0x36, 0x0a); ++ GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a); + +#define SHPS_DSM_REVISION 1 + @@ -247,11 +254,10 @@ index 000000000000..572fba30cd77 +}; + +enum shps_irq_type { -+ /* NOTE: Must be in order of DSM function */ ++ /* NOTE: Must be in order of enum shps_dsm_fn above. */ + SHPS_IRQ_TYPE_BASE_PRESENCE = 0, + SHPS_IRQ_TYPE_DEVICE_POWER = 1, + SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2, -+ + SHPS_NUM_IRQS, +}; + @@ -262,15 +268,19 @@ index 000000000000..572fba30cd77 +}; + +struct shps_device { -+ struct mutex lock[SHPS_NUM_IRQS]; ++ struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */ + struct gpio_desc *gpio[SHPS_NUM_IRQS]; + unsigned int irq[SHPS_NUM_IRQS]; +}; + +#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1) + -+static void shps_dsm_notify_irq(struct platform_device *pdev, -+ enum shps_irq_type type) ++static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type) ++{ ++ return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type; ++} ++ ++static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type) +{ + struct shps_device *sdev = platform_get_drvdata(pdev); + acpi_handle handle = ACPI_HANDLE(&pdev->dev); @@ -283,42 +293,37 @@ index 000000000000..572fba30cd77 + value = gpiod_get_value_cansleep(sdev->gpio[type]); + if (value < 0) { + mutex_unlock(&sdev->lock[type]); -+ dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", -+ type, value); ++ dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value); + return; + } + -+ dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", -+ type, value); ++ dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value); + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = value; + + result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, -+ SHPS_DSM_FN_IRQ_BASE_PRESENCE + type, ¶m); ++ shps_dsm_fn_for_irq(type), ¶m); + + if (!result) { -+ mutex_unlock(&sdev->lock[type]); -+ dev_err(&pdev->dev, -+ "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", ++ dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", + type, value); -+ return; -+ } + -+ if (result->type != ACPI_TYPE_BUFFER) { ++ } else if (result->type != ACPI_TYPE_BUFFER) { + dev_err(&pdev->dev, + "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n", + type, value); -+ } + -+ if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { ++ } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { + dev_err(&pdev->dev, + "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n", + type, value); + } + + mutex_unlock(&sdev->lock[type]); -+ ACPI_FREE(result); ++ ++ if (result) ++ ACPI_FREE(result); +} + +static irqreturn_t shps_handle_irq(int irq, void *data) @@ -348,17 +353,16 @@ index 000000000000..572fba30cd77 + struct gpio_desc *gpiod; + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + const char *irq_name; -+ const int dsm = SHPS_DSM_FN_IRQ_BASE_PRESENCE + type; ++ const int dsm = shps_dsm_fn_for_irq(type); + int status, irq; + -+ /* Initialize as "not present". */ -+ sdev->gpio[type] = NULL; -+ sdev->irq[type] = SHPS_IRQ_NOT_PRESENT; -+ -+ /* Only set up interrupts that we actually need. */ ++ /* ++ * Only set up interrupts that we actually need: The Surface Book 3 ++ * does not have a DSM for base presence, so don't set up an interrupt ++ * for that. ++ */ + if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) { -+ dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", -+ type); ++ dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type); + return 0; + } + @@ -387,11 +391,33 @@ index 000000000000..572fba30cd77 + return 0; +} + ++static int surface_hotplug_remove(struct platform_device *pdev) ++{ ++ struct shps_device *sdev = platform_get_drvdata(pdev); ++ int i; ++ ++ /* Ensure that IRQs have been fully handled and won't trigger any more. */ ++ for (i = 0; i < SHPS_NUM_IRQS; i++) { ++ if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) ++ disable_irq(sdev->irq[i]); ++ ++ mutex_destroy(&sdev->lock[i]); ++ } ++ ++ return 0; ++} ++ +static int surface_hotplug_probe(struct platform_device *pdev) +{ + struct shps_device *sdev; + int status, i; + ++ /* ++ * The MSHW0153 device is also present on the Surface Laptop 3, ++ * however that doesn't have a hot-pluggable PCIe device. It also ++ * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter ++ * it out here. ++ */ + if (gpiod_count(&pdev->dev, NULL) < 0) + return -ENODEV; + @@ -405,15 +431,21 @@ index 000000000000..572fba30cd77 + + platform_set_drvdata(pdev, sdev); + ++ /* ++ * Initialize IRQs so that we can safely call surface_hotplug_remove() ++ * on errors. ++ */ ++ for (i = 0; i < SHPS_NUM_IRQS; i++) ++ sdev->irq[i] = SHPS_IRQ_NOT_PRESENT; ++ + /* Set up IRQs. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) { + mutex_init(&sdev->lock[i]); + + status = shps_setup_irq(pdev, i); + if (status) { -+ dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", -+ i, status); -+ return status; ++ dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status); ++ goto err; + } + } + @@ -423,19 +455,10 @@ index 000000000000..572fba30cd77 + shps_dsm_notify_irq(pdev, i); + + return 0; -+} + -+static int surface_hotplug_remove(struct platform_device *pdev) -+{ -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ int i; -+ -+ /* Ensure that IRQs have been fully handled and won't trigger any more. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) -+ if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) -+ disable_irq(sdev->irq[i]); -+ -+ return 0; ++err: ++ surface_hotplug_remove(pdev); ++ return status; +} + +static const struct acpi_device_id surface_hotplug_acpi_match[] = { @@ -459,5 +482,5 @@ index 000000000000..572fba30cd77 +MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices"); +MODULE_LICENSE("GPL"); -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0008-surface-typecover.patch b/patches/5.10/0008-surface-typecover.patch index 7c3d87935..2a36afd44 100644 --- a/patches/5.10/0008-surface-typecover.patch +++ b/patches/5.10/0008-surface-typecover.patch @@ -1,4 +1,4 @@ -From 9312f4e776a9360673602ce5675a6f637c92bc9e Mon Sep 17 00:00:00 2001 +From e1b4a113984cf38f01c2a0e2bbf4e8192664ce0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Thu, 5 Nov 2020 13:09:45 +0100 Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when @@ -229,5 +229,5 @@ index 8429ebe7097e..44d48e8bbe1a 100644 { .driver_data = MT_CLS_GOOGLE, HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0009-surface-sensors.patch b/patches/5.10/0009-surface-sensors.patch index 5cce55331..e850c098d 100644 --- a/patches/5.10/0009-surface-sensors.patch +++ b/patches/5.10/0009-surface-sensors.patch @@ -1,4 +1,4 @@ -From d0827ea95a7772ae5116adf1633c97ff34c9d4e8 Mon Sep 17 00:00:00 2001 +From 46132805b14e21b99d61fb2f35b459c638c87dca Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sat, 19 Dec 2020 17:50:55 -0800 Subject: [PATCH] iio:light:apds9960 add detection for MSHW0184 ACPI device in @@ -49,5 +49,5 @@ index 9afb3fcc74e6..20719141c03a 100644 .probe = apds9960_probe, .remove = apds9960_remove, -- -2.30.0 +2.30.1 diff --git a/patches/5.10/0010-cameras.patch b/patches/5.10/0010-cameras.patch index ebc425924..64148940d 100644 --- a/patches/5.10/0010-cameras.patch +++ b/patches/5.10/0010-cameras.patch @@ -1,4 +1,4 @@ -From ed24364fb437a6054fbc366d8fc698d2fc79af7f Mon Sep 17 00:00:00 2001 +From 3b09d327318b99c630c758d32d28aff5ca65c072 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 12 Oct 2020 21:04:11 +0300 Subject: [PATCH] ipu3-cio2: Use unsigned values where appropriate @@ -314,9 +314,9 @@ index 146492383aa5..7650d7998a3f 100644 struct cio2_csi2_timing { -- -2.30.0 +2.30.1 -From 8aed30a215d2c096e1940429fff3624a67637737 Mon Sep 17 00:00:00 2001 +From c6fc4d5d0b225173cfbe1df908c8950c3f40f0bc Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 12 Oct 2020 21:04:12 +0300 Subject: [PATCH] ipu3-cio2: Remove explicit type from frame size checks @@ -348,9 +348,9 @@ index 1fcd131482e0..b2679ff185fc 100644 mutex_lock(&q->subdev_lock); -- -2.30.0 +2.30.1 -From 97685366515d61f30839738c7b479d0ba5f4f5a0 Mon Sep 17 00:00:00 2001 +From 74f14515d35868f08cb32111ae06c33d0a7eda81 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 12 Oct 2020 21:04:13 +0300 Subject: [PATCH] ipu3-cio2: Rename CIO2_IMAGE_MAX_LENGTH as @@ -407,9 +407,9 @@ index 7650d7998a3f..ccf0b85ae36f 100644 /* 32MB = 8xFBPT_entry */ #define CIO2_MAX_LOPS 8 -- -2.30.0 +2.30.1 -From 6fc30f110028daed2167569c6dce718cb20dc31d Mon Sep 17 00:00:00 2001 +From 656111f0523d4ab97cac8437c1617a241bece72e Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 13 Oct 2020 17:25:35 +0300 Subject: [PATCH] ipu3-cio2: Check receved the size against payload size, not @@ -461,9 +461,9 @@ index 51c4dd6a8f9a..c557d189200b 100644 } atomic_inc(&q->frame_sequence); -- -2.30.0 +2.30.1 -From 58617cfcef69ce39c279cb2f4ca9b33c48d0fca6 Mon Sep 17 00:00:00 2001 +From 02ff48a15f0e0da02d088fc4c7ec959b97d6016d Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sat, 10 Oct 2020 23:42:09 +0100 Subject: [PATCH] software_node: Fix refcounts in @@ -509,9 +509,9 @@ index 010828fc785b..615a0c93e116 100644 static struct fwnode_handle * -- -2.30.0 +2.30.1 -From cd8c81083dcefac62552fbe2e0232c0c24531556 Mon Sep 17 00:00:00 2001 +From 012323f01e585cb17073d859cba7392a9119c613 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 30 Dec 2020 22:44:05 +0200 Subject: [PATCH] media: ipu3-cio2: Add headers that ipu3-cio2.h is direct user @@ -561,9 +561,9 @@ index ccf0b85ae36f..62187ab5ae43 100644 #define CIO2_DEVICE_NAME "Intel IPU3 CIO2" #define CIO2_ENTITY_NAME "ipu3-csi2" -- -2.30.0 +2.30.1 -From 95ab872413990945f24729a3f0517b5ce1cdb2c7 Mon Sep 17 00:00:00 2001 +From 02efb9f2e40c323cf51d9b3807e2dbea40f08d26 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sat, 24 Oct 2020 22:42:28 +0100 Subject: [PATCH] device property: Return true in fwnode_device_is_available @@ -606,9 +606,9 @@ index 4c43d30145c6..bc9c634df6df 100644 } EXPORT_SYMBOL_GPL(fwnode_device_is_available); -- -2.30.0 +2.30.1 -From 8008601acd83d358cfe0308224bb7e886a134419 Mon Sep 17 00:00:00 2001 +From 41d95371ec70fb792fdf404e09c7bc422116741d Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sat, 21 Nov 2020 22:06:38 +0000 Subject: [PATCH] device property: Call fwnode_graph_get_endpoint_by_id() for @@ -650,9 +650,9 @@ index bc9c634df6df..ddba75d90af2 100644 EXPORT_SYMBOL_GPL(fwnode_graph_get_endpoint_by_id); -- -2.30.0 +2.30.1 -From 3e0e09a48830c0cb4c15dec7c5c3760d238ad884 Mon Sep 17 00:00:00 2001 +From 077ca29de10ed13810d77e4b5283b2b71141b685 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sun, 25 Oct 2020 22:49:08 +0000 Subject: [PATCH] software_node: Enforce parent before child ordering of nodes @@ -753,9 +753,9 @@ index 615a0c93e116..ade49173ff8d 100644 } EXPORT_SYMBOL_GPL(software_node_unregister_nodes); -- -2.30.0 +2.30.1 -From 772283d34ad71fc6dd4ebc74fb62bd680c7bcd0f Mon Sep 17 00:00:00 2001 +From 9cfcb92a9d56040c58453f7ff517112103f96a96 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 21 Oct 2020 22:25:03 +0100 Subject: [PATCH] software_node: unregister software_nodes in reverse order @@ -808,9 +808,9 @@ index ade49173ff8d..1f43c51b431e 100644 } EXPORT_SYMBOL_GPL(software_node_unregister_node_group); -- -2.30.0 +2.30.1 -From 0bf93927e968bd0ac2a0b137f8416e5ea2cd4fe4 Mon Sep 17 00:00:00 2001 +From b07d850a68e50eaa31b6d51d3a235c6a3d027f7c Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 22 Dec 2020 13:09:05 +0000 Subject: [PATCH] device property: Define format macros for ports and endpoints @@ -849,9 +849,9 @@ index 9506f8ec0974..72d36d46287d 100644 /** -- -2.30.0 +2.30.1 -From 1524841c902cef0d456370e9ec40773a8fe6b74b Mon Sep 17 00:00:00 2001 +From 048747fc7cf291e76bc88d36ce7e174dbe67c86d Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 15 Sep 2020 15:47:46 +0100 Subject: [PATCH] software_node: Add support for fwnode_graph*() family of @@ -1017,9 +1017,9 @@ index 1f43c51b431e..2ff504aca0be 100644 /* -------------------------------------------------------------------------- */ -- -2.30.0 +2.30.1 -From de435b7905156d7a5ce8c3eeeffd14b73526b949 Mon Sep 17 00:00:00 2001 +From e5a525987762827d42408d301651ddd7dee58a13 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sat, 10 Oct 2020 23:07:22 +0100 Subject: [PATCH] lib/test_printf.c: Use helper function to unwind array of @@ -1055,9 +1055,9 @@ index 7ac87f18a10f..7d60f24240a4 100644 static void __init -- -2.30.0 +2.30.1 -From 441936189d0bb02ecc81b9c199e6b7c11db462df Mon Sep 17 00:00:00 2001 +From 7db14aecb682f2821c02420dc3356f311591ff12 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sat, 10 Oct 2020 23:11:36 +0100 Subject: [PATCH] ipu3-cio2: Add T: entry to MAINTAINERS @@ -1086,9 +1086,9 @@ index 281de213ef47..5a1c6e959aa8 100644 F: drivers/media/pci/intel/ipu3/ -- -2.30.0 +2.30.1 -From e1228c81e5c347a906bfb35909d0a933e9f3b0f5 Mon Sep 17 00:00:00 2001 +From 5cd26f650205295b0162859db1855119daa50b30 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sat, 10 Oct 2020 22:47:21 +0100 Subject: [PATCH] ipu3-cio2: Rename ipu3-cio2.c @@ -1121,9 +1121,9 @@ similarity index 100% rename from drivers/media/pci/intel/ipu3/ipu3-cio2.c rename to drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -- -2.30.0 +2.30.1 -From d3156ac8e9ec15e8d8b048566208fc79fd4d8310 Mon Sep 17 00:00:00 2001 +From 173774fc8274db26b11e9518948d7dc2fbd2a286 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 21 Oct 2020 21:53:05 +0100 Subject: [PATCH] media: v4l2-core: v4l2-async: Check sd->fwnode->secondary in @@ -1162,9 +1162,9 @@ index e3ab003a6c85..9dd896d085ec 100644 * Otherwise, check if the sd fwnode and the asd fwnode refer to an * endpoint or a device. If they're of the same type, there's no match. -- -2.30.0 +2.30.1 -From 8f6d6ea7131a9410c50d62c8bd90b8cd0a6f7c28 Mon Sep 17 00:00:00 2001 +From 54866c066f7eb157f72611427af44096d6ed388e Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sun, 15 Nov 2020 08:15:34 +0000 Subject: [PATCH] ACPI / bus: Add acpi_dev_get_next_match_dev() and helper @@ -1271,9 +1271,9 @@ index 6d1879bf9440..02a716a0af5d 100644 { put_device(&adev->dev); -- -2.30.0 +2.30.1 -From 0da8a119fe89d3af5619454979cd674e5b432649 Mon Sep 17 00:00:00 2001 +From 3a8c20986a3b297f30269464f628439b23b0ed7e Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sat, 19 Dec 2020 23:55:04 +0000 Subject: [PATCH] media: v4l2-fwnode: Include v4l2_fwnode_bus_type @@ -1348,9 +1348,9 @@ index ed0840f3d5df..6ca337c28b3c 100644 * v4l2_fwnode_endpoint_parse() - parse all fwnode node properties * @fwnode: pointer to the endpoint's fwnode handle -- -2.30.0 +2.30.1 -From 41237a915cef967de91160c85d7493a6b5be7ade Mon Sep 17 00:00:00 2001 +From d3d67a61296add16ecd644971ec0ac25a6ba1022 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 21 Oct 2020 21:53:44 +0100 Subject: [PATCH] ipu3-cio2: Add cio2-bridge to ipu3-cio2 driver @@ -1946,9 +1946,9 @@ index 62187ab5ae43..dc3e343a37fb 100644 + #endif -- -2.30.0 +2.30.1 -From 688186f7c659840fd6c340a892f0a8a21efb18b9 Mon Sep 17 00:00:00 2001 +From 66c0bc2e1a548ce3d1f6e7b3356c29c702c137ac Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 2 Dec 2020 12:38:10 +0000 Subject: [PATCH] acpi: utils: move acpi_lpss_dep() to utils @@ -2048,9 +2048,9 @@ index ddca1550cce6..78b38775f18b 100644 * acpi_dev_present - Detect that a given ACPI device is present * @hid: Hardware ID of the device. -- -2.30.0 +2.30.1 -From a146f0d32f0171eb9ae965cd61f68912e600cba1 Mon Sep 17 00:00:00 2001 +From 19bdf96ef080ad643ced9c7810156857d30d21ee Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 26 Nov 2020 21:12:41 +0000 Subject: [PATCH] acpi: utils: Add function to fetch dependent acpi_devices @@ -2134,9 +2134,9 @@ index 02a716a0af5d..33deb22294f2 100644 acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); struct acpi_device * -- -2.30.0 +2.30.1 -From 197141d81abfb7f5b1ad712ffffb4bcc69311a68 Mon Sep 17 00:00:00 2001 +From ec31436438fd065f39e144fbd8652d292bf07f05 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 16 Nov 2020 21:38:49 +0000 Subject: [PATCH] i2c: i2c-core-base: Use format macro in i2c_dev_set_name() @@ -2202,9 +2202,9 @@ index 56622658b215..65acae61dc5c 100644 { return NULL; -- -2.30.0 +2.30.1 -From 4d0822207995850a85f0404045605c2088eaf90f Mon Sep 17 00:00:00 2001 +From 8b7807ff6bca711e87af1b6e5b11f0495f36c6ef Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 2 Dec 2020 16:41:42 +0000 Subject: [PATCH] i2c: i2c-core-acpi: Add i2c_acpi_dev_name() @@ -2260,9 +2260,9 @@ index 65acae61dc5c..b82aac05b17f 100644 #else static inline bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares, -- -2.30.0 +2.30.1 -From 7770ea608e64e8be3394cb2a9ec3d533f0a6b972 Mon Sep 17 00:00:00 2001 +From df44db561d8855d0255503e20268c08c01c3c5ee Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 16 Nov 2020 00:16:56 +0000 Subject: [PATCH] gpio: gpiolib-acpi: Export acpi_get_gpiod() @@ -2324,9 +2324,9 @@ index 5b1dc1ad4fb3..47ae139e8781 100644 /* Device properties */ -- -2.30.0 +2.30.1 -From 31aff03814589c16f1d18c56086a577d6aad37dd Mon Sep 17 00:00:00 2001 +From 89313343dd227867456401fe62cf944058e47534 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sat, 12 Dec 2020 23:56:59 +0000 Subject: [PATCH] mfd: Remove tps68470 MFD driver @@ -2515,9 +2515,9 @@ index 4a4df4ffd18c..000000000000 -}; -builtin_i2c_driver(tps68470_driver); -- -2.30.0 +2.30.1 -From bd908144bb8a2bea3e2dc7505231c55bd1adf193 Mon Sep 17 00:00:00 2001 +From c8f82a485887fff61743f6612812d7b66e9ebe6d Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Fri, 15 Jan 2021 12:37:31 +0000 Subject: [PATCH] platform: x86: Add intel_skl_int3472 driver @@ -2575,7 +2575,7 @@ index a6924e3401e8..3ed02216251b 100644 M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 3e882b1e1f74..277353eaeb36 100644 +index 0c513c8bbd2b..c50e8596b440 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -807,6 +807,31 @@ config INTEL_CHT_INT33FE @@ -2611,7 +2611,7 @@ index 3e882b1e1f74..277353eaeb36 100644 tristate "INTEL HID Event" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 2009224dcaae..e92538799514 100644 +index f552cbfb7914..dd8fc06b224c 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -79,6 +79,11 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o @@ -3484,9 +3484,9 @@ index 000000000000..3fe27ec0caff + return ret; +} -- -2.30.0 +2.30.1 -From 48a351bcbdc02836cc22bc4cc3ef0747e40d32e9 Mon Sep 17 00:00:00 2001 +From 0564d5c307f712e18fffa6816cb0853c1e6a1224 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Dec 2020 21:17:35 +0100 Subject: [PATCH] PCI: ACPI: Fix up ACPI companion lookup for device 0 on the @@ -3554,9 +3554,9 @@ index 745a4e0c4994..87e45a800919 100644 /** -- -2.30.0 +2.30.1 -From 439048a506f3de2b24bfb91db533c1c0ea285c7a Mon Sep 17 00:00:00 2001 +From 8fea36502f89e914226e1159c094b83d9ec3d226 Mon Sep 17 00:00:00 2001 From: Jake Day Date: Fri, 25 Sep 2020 10:24:53 -0400 Subject: [PATCH] media: i2c: Add support for the OV5693 image sensor @@ -6911,9 +6911,9 @@ index 000000000000..9a508e1f3624 +static unsigned long N_RES = N_RES_VIDEO; +#endif -- -2.30.0 +2.30.1 -From 843d1cc14679a508d76685d653ffb9797f5e9e1f Mon Sep 17 00:00:00 2001 +From 73624b0f335955c8cc097bdbdffe954bc5466c75 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sun, 17 Jan 2021 19:08:18 +0000 Subject: [PATCH] media: i2c: Add reset pin toggling to ov5693 @@ -6952,9 +6952,9 @@ index 32485e4ed42b..f9ced52ad37a 100644 if (dev->indicator_led) -- -2.30.0 +2.30.1 -From 21af55c2dce8dc03a49944ae311aaf9ce9a794a0 Mon Sep 17 00:00:00 2001 +From f3982cd7cf3ec7bca291cfc821c4d5dedd94d7ac Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sun, 17 Jan 2021 21:39:15 +0000 Subject: [PATCH] media: i2c: Fix misnamed variable in power_down() for ov5693 @@ -6981,5 +6981,5 @@ index f9ced52ad37a..9fd44a3d1d85 100644 clk_disable_unprepare(dev->clk); -- -2.30.0 +2.30.1 diff --git a/pkg/arch/kernel/PKGBUILD b/pkg/arch/kernel/PKGBUILD index f3f400d5f..e52a18b90 100644 --- a/pkg/arch/kernel/PKGBUILD +++ b/pkg/arch/kernel/PKGBUILD @@ -45,18 +45,18 @@ validpgpkeys=( ) sha256sums=('8d2c3d8fc7b382c3db6b8bacbc7785e164150b952e706350f2a5a88d42939cb4' '47a5c0cbe4f75e77fe64953b516a6a9f277c1dfb812e31edf5cb25feb1b3819a' - '1ad719eb0d2eea4e364894b71199ee694c23b44269d1e5e9f4fc341fdf09b991' + '3fbd134c61de6f4fa86567bb0faf02c4eb2c54111b6dc45367b8f6e9ef06556e' '8cd2b019aac6d3807a5cdcbbbe0aad81e63193ff3e8dffd7a79d4a1421b858f6' - '4ce28ed4c0387a44c7f0cf8a28d8b17cbc9a9be037d5d1b2a4cba07bc2a76712' - 'ba6d197064c41b1e547fa8c86ff11380a7c9177cfbdfd1efe8501e358979ee81' - '8982c023f5a5d85052eaa3e78988dcd4b492cbea8a8a61c409e944e55f4e720f' - 'bc77b1ec74301dec34c9d3b2cdc2df21f458f9e338f6107f4b0c1c91e4e8163a' - 'daba63b194d4106d8ad5deb15053960ceb87d0f21ecc740c2cb5ed010b7f342a' - '66481ae447822b88a1186fb9f47711a90d9a1786133ad1755adb524fb6c6e3e4' - 'fb6712bef9c309af8cd137a8bdce2dc886521e0a6d3217cdaea7ac7e3d63aa44' - 'd6df043772f2e1dc21dce74ff805a64ad19de9154d0d8d8c12f4fe982c883554' - '0e595489ed968d6db9ac37c1c6dd141328d1317fe52425a25434ee783d51994c' - 'f78ca3696110fe4256ccb8e1795a26f58f0776ce6ca0a5b69a13c711bc03bca6') + 'eca6f81db4efc8d71a4f5dbaf1d6ca5b46202cb9a8ef5a7eff6b0bbe26a34a82' + 'a10905d215739fa148870e0a8fff03f3d101f48d1f7b8028e5460adfd1b8ac25' + '84438dad91d0375bf1c80859081e6b10d5fe9ce3127fed654d64a56aad820b13' + '34aab05229d9fb782ac8dad4ac411cfd7a4f24509ba385461997e1d40000153c' + 'e04a672f6bec169d37ee6584c5f1aa252277af1cd38b322eb70602e5bcaeae4d' + '8510597e396b72f898e58abcb77b6d356627720741a76d2551aeb0df4af5919f' + 'a6653fdaa655ee61dbc8118458a844fc9261ef2f2733b8ad83adda02cfbd65b2' + 'a9f815bc52a2610b3f0b8f878f564acab1d437ac20b7d83790cc7f67d644522f' + '16a0d17039426b4c5fb27fb13de0a4dc0b5fb42cacc5100c99b610b0571b8a86' + '3645b929503176a4ea671d47ac2b3a5bf6f2bd517b54d848d93934b049a9d0aa') export KBUILD_BUILD_HOST=archlinux