diff options
Diffstat (limited to 'drivers/platform/surface')
30 files changed, 2049 insertions, 923 deletions
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 5f0578e25f71..f775c6ca1ec1 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -5,7 +5,8 @@ menuconfig SURFACE_PLATFORMS bool "Microsoft Surface Platform-Specific Device Drivers" - default y + depends on ARM64 || X86 || COMPILE_TEST + default y if ARM64 || X86 help Say Y here to get to see options for platform-specific device drivers for Microsoft Surface devices. This option alone does not add any @@ -27,13 +28,6 @@ config SURFACE3_WMI To compile this driver as a module, choose M here: the module will be called surface3-wmi. -config SURFACE_3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" - depends on ACPI - depends on KEYBOARD_GPIO && I2C - help - This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. - config SURFACE_3_POWER_OPREGION tristate "Surface 3 battery platform operation region support" depends on ACPI @@ -78,18 +72,45 @@ config SURFACE_AGGREGATOR_CDEV The provided interface is intended for debugging and development only, and should not be used otherwise. +config SURFACE_AGGREGATOR_HUB + tristate "Surface System Aggregator Module Subsystem Device Hubs" + depends on SURFACE_AGGREGATOR + depends on SURFACE_AGGREGATOR_BUS + help + Device-hub drivers for Surface System Aggregator Module (SSAM) subsystem + devices. + + Provides subsystem hub drivers which manage client devices on various + SSAM subsystems. In some subsystems, notably the BAS subsystem managing + devices contained in the base of the Surface Book 3 and the KIP subsystem + managing type-cover devices in the Surface Pro 8 and Surface Pro X, + devices can be (hot-)removed. Hub devices and drivers are required to + manage these subdevices. + + Devices managed via these hubs are: + - Battery/AC devices (Surface Book 3). + - HID input devices (7th-generation and later models with detachable + input devices). + + 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 mentioned above will not be instantiated + and thus any functionality provided by them will be missing, even when + drivers for these devices are present. This module only provides the + respective subsystem hubs. Both drivers and device specification (e.g. + via the Surface Aggregator Registry) for these devices still need to be + selected via other options. + 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. + Device-registry 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. + Such devices are instead provided and managed via this registry. Devices provided via this registry are: - Platform profile (performance-/cooling-mode) device (5th- and later @@ -105,6 +126,29 @@ config SURFACE_AGGREGATOR_REGISTRY the respective client devices. Drivers for these devices still need to be selected via the other options. +config SURFACE_AGGREGATOR_TABLET_SWITCH + tristate "Surface Aggregator Generic Tablet-Mode Switch Driver" + depends on SURFACE_AGGREGATOR + depends on SURFACE_AGGREGATOR_BUS + depends on INPUT + help + Provides a tablet-mode switch input device on Microsoft Surface models + using the KIP subsystem for detachable keyboards (e.g. keyboard covers) + or the POS subsystem for device/screen posture changes. + + The KIP subsystem is used on newer Surface generations to handle + detachable input peripherals, specifically the keyboard cover (containing + keyboard and touchpad) on the Surface Pro 8 and Surface Pro X. The POS + subsystem is used for device posture change notifications on the Surface + Laptop Studio. This module provides a driver to let user-space know when + the device should be considered in tablet-mode due to the keyboard cover + being detached or folded back (essentially signaling when the keyboard is + not available for input). It does so by creating a tablet-mode switch + input device, sending the standard SW_TABLET_MODE event on mode change. + + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + config SURFACE_DTX tristate "Surface DTX (Detachment System) Driver" depends on SURFACE_AGGREGATOR diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 32889482de55..53344330939b 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -5,12 +5,13 @@ # obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o -obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o +obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o +obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig index cab020324256..957c216c180c 100644 --- a/drivers/platform/surface/aggregator/Kconfig +++ b/drivers/platform/surface/aggregator/Kconfig @@ -1,11 +1,11 @@ # SPDX-License-Identifier: GPL-2.0+ -# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> +# Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> menuconfig SURFACE_AGGREGATOR tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" depends on SERIAL_DEV_BUS - depends on ACPI - select CRC_CCITT + depends on ACPI && !RISCV + select CRC_ITU_T help The Surface System Aggregator Module (Surface SAM or SSAM) is an embedded controller (EC) found on 5th- and later-generation Microsoft diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile index c0d550eda5cd..fdf664a217f9 100644 --- a/drivers/platform/surface/aggregator/Makefile +++ b/drivers/platform/surface/aggregator/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0+ -# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> +# Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> # For include/trace/define_trace.h to include trace.h CFLAGS_core.o = -I$(src) diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c index abbbb5b08b07..d68d231e716e 100644 --- a/drivers/platform/surface/aggregator/bus.c +++ b/drivers/platform/surface/aggregator/bus.c @@ -2,10 +2,12 @@ /* * Surface System Aggregator Module bus and device integration. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/device.h> +#include <linux/of.h> +#include <linux/property.h> #include <linux/slab.h> #include <linux/surface_aggregator/controller.h> @@ -14,6 +16,9 @@ #include "bus.h" #include "controller.h" + +/* -- Device and bus functions. --------------------------------------------- */ + static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -31,9 +36,11 @@ static struct attribute *ssam_device_attrs[] = { }; ATTRIBUTE_GROUPS(ssam_device); -static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) +static const struct bus_type ssam_bus_type; + +static int ssam_device_uevent(const struct device *dev, struct kobj_uevent_env *env) { - struct ssam_device *sdev = to_ssam_device(dev); + const 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, @@ -46,6 +53,7 @@ static void ssam_device_release(struct device *dev) struct ssam_device *sdev = to_ssam_device(dev); ssam_controller_put(sdev->ctrl); + fwnode_handle_put(sdev->dev.fwnode); kfree(sdev); } @@ -131,9 +139,9 @@ int ssam_device_add(struct ssam_device *sdev) * 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. + * for functions like ssam_request_do_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 @@ -299,9 +307,9 @@ const void *ssam_device_get_match_data(const struct ssam_device *dev) } EXPORT_SYMBOL_GPL(ssam_device_get_match_data); -static int ssam_bus_match(struct device *dev, struct device_driver *drv) +static int ssam_bus_match(struct device *dev, const struct device_driver *drv) { - struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); + const struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); struct ssam_device *sdev = to_ssam_device(dev); if (!is_ssam_device(dev)) @@ -324,13 +332,12 @@ static void ssam_bus_remove(struct device *dev) sdrv->remove(to_ssam_device(dev)); } -struct bus_type ssam_bus_type = { +static const 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); /** * __ssam_device_driver_register() - Register a SSAM client device driver. @@ -363,6 +370,137 @@ void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) } EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); + +/* -- Bus registration. ----------------------------------------------------- */ + +/** + * 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); +} + + +/* -- Helpers for controller and hub devices. ------------------------------- */ + +static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid) +{ + u8 d, tc, tid, iid, fn; + int n; + + n = sscanf(str, "%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_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid) +{ + const char *str = fwnode_get_name(node); + + /* + * To simplify definitions of firmware nodes, we set the device name + * based on the UID of the device, prefixed with "ssam:". + */ + if (strncmp(str, "ssam:", strlen("ssam:")) != 0) + return -ENODEV; + + str += strlen("ssam:"); + return ssam_device_uid_from_string(str, uid); +} + +static int ssam_add_client_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_get_uid_for_node(node, &uid); + if (status) + return status; + + sdev = ssam_device_alloc(ctrl, uid); + if (!sdev) + return -ENOMEM; + + sdev->dev.parent = parent; + sdev->dev.fwnode = fwnode_handle_get(node); + sdev->dev.of_node = to_of_node(node); + + status = ssam_device_add(sdev); + if (status) + ssam_device_put(sdev); + + return status; +} + +/** + * __ssam_register_clients() - Register client devices defined under the + * given firmware node as children of the given device. + * @parent: The parent device under which clients should be registered. + * @ctrl: The controller with which client should be registered. + * @node: The firmware node holding definitions of the devices to be added. + * + * Register all clients that have been defined as children of the given root + * firmware node as children of the given parent device. The respective child + * firmware nodes will be associated with the correspondingly created child + * devices. + * + * The given controller will be used to instantiate the new devices. See + * ssam_device_add() for details. + * + * Note that, generally, the use of either ssam_device_register_clients() or + * ssam_register_clients() should be preferred as they directly use the + * firmware node and/or controller associated with the given device. This + * function is only intended for use when different device specifications (e.g. + * ACPI and firmware nodes) need to be combined (as is done in the platform hub + * of the device registry). + * + * Return: Returns zero on success, nonzero on failure. + */ +int __ssam_register_clients(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 -ENODEV, the node does not specify any SSAM + * device, so ignore it and continue with the next one. + */ + status = ssam_add_client_device(parent, ctrl, child); + if (status && status != -ENODEV) { + fwnode_handle_put(child); + goto err; + } + } + + return 0; +err: + ssam_remove_clients(parent); + return status; +} +EXPORT_SYMBOL_GPL(__ssam_register_clients); + static int ssam_remove_device(struct device *dev, void *_data) { struct ssam_device *sdev = to_ssam_device(dev); @@ -387,19 +525,3 @@ void ssam_remove_clients(struct device *dev) device_for_each_child_reverse(dev, NULL, ssam_remove_device); } EXPORT_SYMBOL_GPL(ssam_remove_clients); - -/** - * 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/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h index 6964ee84e79c..5b4dbf21906c 100644 --- a/drivers/platform/surface/aggregator/bus.h +++ b/drivers/platform/surface/aggregator/bus.h @@ -2,7 +2,7 @@ /* * Surface System Aggregator Module bus and device integration. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #ifndef _SURFACE_AGGREGATOR_BUS_H diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index b8c377b3f932..a265e667538c 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -2,7 +2,7 @@ /* * Main SSAM/SSH controller structure and functionality. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/acpi.h> @@ -825,7 +825,7 @@ static int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev) cplt->dev = dev; - cplt->wq = create_workqueue(SSAM_CPLT_WQ_NAME); + cplt->wq = alloc_workqueue(SSAM_CPLT_WQ_NAME, WQ_UNBOUND | WQ_MEM_RECLAIM, 0); if (!cplt->wq) return -ENOMEM; @@ -994,7 +994,7 @@ static void ssam_handle_event(struct ssh_rtl *rtl, item->rqid = get_unaligned_le16(&cmd->rqid); item->event.target_category = cmd->tc; - item->event.target_id = cmd->tid_in; + item->event.target_id = cmd->sid; item->event.command_id = cmd->cid; item->event.instance_id = cmd->iid; memcpy(&item->event.data[0], data->ptr, data->len); @@ -1104,13 +1104,6 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle, u64 funcs; int status; - /* Set defaults. */ - caps->ssh_power_profile = U32_MAX; - caps->screen_on_sleep_idle_timeout = U32_MAX; - caps->screen_off_sleep_idle_timeout = U32_MAX; - caps->d3_closes_handle = false; - caps->ssh_buffer_size = U32_MAX; - /* Pre-load supported DSM functions. */ status = ssam_dsm_get_functions(handle, &funcs); if (status) @@ -1150,6 +1143,52 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle, } /** + * ssam_controller_caps_load_from_of() - Load controller capabilities from OF/DT. + * @dev: A pointer to the controller device + * @caps: Where to store the capabilities in. + * + * Return: Returns zero on success, a negative error code on failure. + */ +static int ssam_controller_caps_load_from_of(struct device *dev, struct ssam_controller_caps *caps) +{ + /* + * Every device starting with Surface Pro X through Laptop 7 uses these + * identical values, which makes them good defaults. + */ + caps->d3_closes_handle = true; + caps->screen_on_sleep_idle_timeout = 5000; + caps->screen_off_sleep_idle_timeout = 30; + caps->ssh_buffer_size = 48; + /* TODO: figure out power profile */ + + return 0; +} + +/** + * ssam_controller_caps_load() - Load controller capabilities + * @dev: A pointer to the controller device + * @caps: Where to store the capabilities in. + * + * Return: Returns zero on success, a negative error code on failure. + */ +static int ssam_controller_caps_load(struct device *dev, struct ssam_controller_caps *caps) +{ + acpi_handle handle = ACPI_HANDLE(dev); + + /* Set defaults. */ + caps->ssh_power_profile = U32_MAX; + caps->screen_on_sleep_idle_timeout = U32_MAX; + caps->screen_off_sleep_idle_timeout = U32_MAX; + caps->d3_closes_handle = false; + caps->ssh_buffer_size = U32_MAX; + + if (handle) + return ssam_controller_caps_load_from_acpi(handle, caps); + else + return ssam_controller_caps_load_from_of(dev, caps); +} + +/** * ssam_controller_init() - Initialize SSAM controller. * @ctrl: The controller to initialize. * @serdev: The serial device representing the underlying data transport. @@ -1165,13 +1204,12 @@ int ssam_controller_caps_load_from_acpi(acpi_handle handle, int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *serdev) { - acpi_handle handle = ACPI_HANDLE(&serdev->dev); int status; init_rwsem(&ctrl->lock); kref_init(&ctrl->kref); - status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps); + status = ssam_controller_caps_load(&serdev->dev, &ctrl->caps); if (status) return status; @@ -1354,7 +1392,8 @@ void ssam_controller_destroy(struct ssam_controller *ctrl) if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) return; - WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); + WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED && + ctrl->state != SSAM_CONTROLLER_INITIALIZED); /* * Note: New events could still have been received after the previous @@ -1674,7 +1713,7 @@ int ssam_request_sync_submit(struct ssam_controller *ctrl, EXPORT_SYMBOL_GPL(ssam_request_sync_submit); /** - * ssam_request_sync() - Execute a synchronous request. + * ssam_request_do_sync() - Execute a synchronous request. * @ctrl: The controller via which the request will be submitted. * @spec: The request specification and payload. * @rsp: The response buffer. @@ -1686,9 +1725,9 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_submit); * * Return: Returns the status of the request or any failure during setup. */ -int ssam_request_sync(struct ssam_controller *ctrl, - const struct ssam_request *spec, - struct ssam_response *rsp) +int ssam_request_do_sync(struct ssam_controller *ctrl, + const struct ssam_request *spec, + struct ssam_response *rsp) { struct ssam_request_sync *rqst; struct ssam_span buf; @@ -1700,8 +1739,10 @@ int ssam_request_sync(struct ssam_controller *ctrl, return status; status = ssam_request_sync_init(rqst, spec->flags); - if (status) + if (status) { + ssam_request_sync_free(rqst); return status; + } ssam_request_sync_set_resp(rqst, rsp); @@ -1720,10 +1761,10 @@ int ssam_request_sync(struct ssam_controller *ctrl, ssam_request_sync_free(rqst); return status; } -EXPORT_SYMBOL_GPL(ssam_request_sync); +EXPORT_SYMBOL_GPL(ssam_request_do_sync); /** - * ssam_request_sync_with_buffer() - Execute a synchronous request with the + * ssam_request_do_sync_with_buffer() - Execute a synchronous request with the * provided buffer as back-end for the message buffer. * @ctrl: The controller via which the request will be submitted. * @spec: The request specification and payload. @@ -1736,17 +1777,17 @@ EXPORT_SYMBOL_GPL(ssam_request_sync); * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required * message buffer size. * - * This function does essentially the same as ssam_request_sync(), but instead - * of dynamically allocating the request and message data buffer, it uses the - * provided message data buffer and stores the (small) request struct on the - * heap. + * This function does essentially the same as ssam_request_do_sync(), but + * instead of dynamically allocating the request and message data buffer, it + * uses the provided message data buffer and stores the (small) request struct + * on the heap. * * Return: Returns the status of the request or any failure during setup. */ -int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, - const struct ssam_request *spec, - struct ssam_response *rsp, - struct ssam_span *buf) +int ssam_request_do_sync_with_buffer(struct ssam_controller *ctrl, + const struct ssam_request *spec, + struct ssam_response *rsp, + struct ssam_span *buf) { struct ssam_request_sync rqst; ssize_t len; @@ -1770,42 +1811,42 @@ int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, return status; } -EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); +EXPORT_SYMBOL_GPL(ssam_request_do_sync_with_buffer); /* -- Internal SAM requests. ------------------------------------------------ */ SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x13, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x15, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x16, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x33, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x34, .instance_id = 0x00, }); @@ -1862,7 +1903,7 @@ static int __ssam_ssh_event_request(struct ssam_controller *ctrl, result.length = 0; result.pointer = &buf; - status = ssam_retry(ssam_request_sync_onstack, ctrl, &rqst, &result, + status = ssam_retry(ssam_request_do_sync_onstack, ctrl, &rqst, &result, sizeof(params)); return status < 0 ? status : buf; @@ -2199,16 +2240,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, } /** - * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is - * no longer in use and free the corresponding entry. + * ssam_nf_refcount_disable_free() - Disable event for reference count entry if + * it is no longer in use and free the corresponding entry. * @ctrl: The controller to disable the event on. * @entry: The reference count entry for the event to be disabled. * @flags: The flags used for enabling the event on the EC. + * @ec: Flag specifying if the event should actually be disabled on the EC. * - * If the reference count equals zero, i.e. the event is no longer requested by - * any client, the event will be disabled and the corresponding reference count - * entry freed. The reference count entry must not be used any more after a - * call to this function. + * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the + * event is no longer requested by any client), the specified event will be + * disabled on the EC via the corresponding request. + * + * If ``ec`` equals ``false``, no request will be sent to the EC and the event + * can be considered in a detached state (i.e. no longer used but still + * enabled). Disabling an event via this method may be required for + * hot-removable devices, where event disable requests may time out after the + * device has been physically removed. + * + * In both cases, if the reference count equals zero, the corresponding + * reference count entry will be freed. The reference count entry must not be + * used any more after a call to this function. * * Also checks if the flags used for disabling the event match the flags used * for enabling the event and warns if they do not (regardless of reference @@ -2223,7 +2274,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, * returns the status of the event-enable EC command. */ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - struct ssam_nf_refcount_entry *entry, u8 flags) + struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) { const struct ssam_event_registry reg = entry->key.reg; const struct ssam_event_id id = entry->key.id; @@ -2232,8 +2283,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, lockdep_assert_held(&nf->lock); - ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", - reg.target_category, id.target_category, id.instance, entry->refcount); + ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + ec ? "disabling" : "detaching", reg.target_category, id.target_category, + id.instance, entry->refcount); if (entry->flags != flags) { ssam_warn(ctrl, @@ -2242,7 +2294,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, id.instance); } - if (entry->refcount == 0) { + if (ec && entry->refcount == 0) { status = ssam_ssh_event_disable(ctrl, reg, id, flags); kfree(entry); } @@ -2322,20 +2374,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif EXPORT_SYMBOL_GPL(ssam_notifier_register); /** - * ssam_notifier_unregister() - Unregister an event notifier. - * @ctrl: The controller the notifier has been registered on. - * @n: The event notifier to unregister. + * __ssam_notifier_unregister() - Unregister an event notifier. + * @ctrl: The controller the notifier has been registered on. + * @n: The event notifier to unregister. + * @disable: Whether to disable the corresponding event on the EC. * * Unregister an event notifier. Decrement the usage counter of the associated * SAM event if the notifier is not marked as an observer. If the usage counter - * reaches zero, the event will be disabled. + * reaches zero and ``disable`` equals ``true``, the event will be disabled. + * + * Useful for hot-removable devices, where communication may fail once the + * device has been physically removed. In that case, specifying ``disable`` as + * ``false`` avoids communication with the EC. * * Return: Returns zero on success, %-ENOENT if the given notifier block has * not been registered on the controller. If the given notifier block was the * last one associated with its specific event, returns the status of the * event-disable EC-command. */ -int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) +int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, + bool disable) { u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); struct ssam_nf_refcount_entry *entry; @@ -2373,7 +2431,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not goto remove; } - status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); + status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); } remove: @@ -2383,7 +2441,7 @@ remove: return status; } -EXPORT_SYMBOL_GPL(ssam_notifier_unregister); +EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); /** * ssam_controller_event_enable() - Enable the specified event. @@ -2477,7 +2535,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, return -ENOENT; } - status = ssam_nf_refcount_disable_free(ctrl, entry, flags); + status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); mutex_unlock(&nf->lock); return status; @@ -2696,11 +2754,12 @@ int ssam_irq_setup(struct ssam_controller *ctrl) const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); - if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); - - irq = gpiod_to_irq(gpiod); - gpiod_put(gpiod); + if (IS_ERR(gpiod)) { + irq = fwnode_irq_get(dev_fwnode(dev), 0); + } else { + irq = gpiod_to_irq(gpiod); + gpiod_put(gpiod); + } if (irq < 0) return irq; diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h index a0963c3562ff..f1638c2081e8 100644 --- a/drivers/platform/surface/aggregator/controller.h +++ b/drivers/platform/surface/aggregator/controller.h @@ -2,7 +2,7 @@ /* * Main SSAM/SSH controller structure and functionality. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H @@ -238,8 +238,8 @@ struct ssam_controller { * layer of the controller has been shut down, %-ESHUTDOWN. */ static inline -int ssam_controller_receive_buf(struct ssam_controller *ctrl, - const unsigned char *buf, size_t n) +ssize_t ssam_controller_receive_buf(struct ssam_controller *ctrl, const u8 *buf, + size_t n) { return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n); } diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index d384d36098c2..c7e05f7bc199 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -7,7 +7,7 @@ * Handles communication via requests as well as enabling, disabling, and * relaying of events. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/acpi.h> @@ -17,9 +17,12 @@ #include <linux/kernel.h> #include <linux/kref.h> #include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> #include <linux/pm.h> #include <linux/serdev.h> #include <linux/sysfs.h> +#include <linux/units.h> #include <linux/surface_aggregator/controller.h> #include <linux/surface_aggregator/device.h> @@ -227,13 +230,16 @@ EXPORT_SYMBOL_GPL(ssam_client_bind); /* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */ -static int ssam_receive_buf(struct serdev_device *dev, const unsigned char *buf, - size_t n) +static size_t ssam_receive_buf(struct serdev_device *dev, const u8 *buf, + size_t n) { struct ssam_controller *ctrl; + int ret; ctrl = serdev_device_get_drvdata(dev); - return ssam_controller_receive_buf(ctrl, buf, n); + ret = ssam_controller_receive_buf(ctrl, buf, n); + + return ret < 0 ? 0 : ret; } static void ssam_write_wakeup(struct serdev_device *dev) @@ -296,7 +302,7 @@ static const struct attribute_group ssam_sam_group = { }; -/* -- ACPI based device setup. ---------------------------------------------- */ +/* -- Serial device setup. -------------------------------------------------- */ static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, void *ctx) @@ -349,13 +355,28 @@ static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, return AE_CTRL_TERMINATE; } -static acpi_status ssam_serdev_setup_via_acpi(acpi_handle handle, - struct serdev_device *serdev) +static int ssam_serdev_setup_via_acpi(struct serdev_device *serdev, acpi_handle handle) { - return acpi_walk_resources(handle, METHOD_NAME__CRS, - ssam_serdev_setup_via_acpi_crs, serdev); + acpi_status status; + + status = acpi_walk_resources(handle, METHOD_NAME__CRS, + ssam_serdev_setup_via_acpi_crs, serdev); + + return status ? -ENXIO : 0; } +static int ssam_serdev_setup(struct acpi_device *ssh, struct serdev_device *serdev) +{ + if (ssh) + return ssam_serdev_setup_via_acpi(serdev, ssh->handle); + + /* TODO: these values may differ per board/implementation */ + serdev_device_set_baudrate(serdev, 4 * HZ_PER_MHZ); + serdev_device_set_flow_control(serdev, true); + serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + + return 0; +} /* -- Power management. ----------------------------------------------------- */ @@ -615,17 +636,20 @@ static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { static int ssam_serial_hub_probe(struct serdev_device *serdev) { - struct acpi_device *ssh = ACPI_COMPANION(&serdev->dev); + struct device *dev = &serdev->dev; + struct acpi_device *ssh = ACPI_COMPANION(dev); struct ssam_controller *ctrl; - acpi_status astatus; int status; - if (gpiod_count(&serdev->dev, NULL) < 0) - return -ENODEV; + if (ssh) { + status = gpiod_count(dev, NULL); + if (status < 0) + return dev_err_probe(dev, status, "no GPIO found\n"); - status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); - if (status) - return status; + status = devm_acpi_dev_add_driver_gpios(dev, ssam_acpi_gpios); + if (status) + return status; + } /* Allocate controller. */ ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); @@ -634,8 +658,10 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) /* Initialize controller. */ status = ssam_controller_init(ctrl, serdev); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to initialize ssam controller\n"); goto err_ctrl_init; + } ssam_controller_lock(ctrl); @@ -643,12 +669,14 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) serdev_device_set_drvdata(serdev, ctrl); serdev_device_set_client_ops(serdev, &ssam_serdev_ops); status = serdev_device_open(serdev); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to open serdev device\n"); goto err_devopen; + } - astatus = ssam_serdev_setup_via_acpi(ssh->handle, serdev); - if (ACPI_FAILURE(astatus)) { - status = -ENXIO; + status = ssam_serdev_setup(ssh, serdev); + if (status) { + dev_err_probe(dev, status, "failed to setup serdev\n"); goto err_devinit; } @@ -664,25 +692,33 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) * states. */ status = ssam_log_firmware_version(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to get firmware version\n"); goto err_initrq; + } status = ssam_ctrl_notif_d0_entry(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "D0-entry notification failed\n"); goto err_initrq; + } status = ssam_ctrl_notif_display_on(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "display-on notification failed\n"); goto err_initrq; + } - status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); + status = sysfs_create_group(&dev->kobj, &ssam_sam_group); if (status) goto err_initrq; /* Set up IRQ. */ status = ssam_irq_setup(ctrl); - if (status) + if (status) { + dev_err_probe(dev, status, "failed to setup IRQ\n"); goto err_irq; + } /* Finally, set main controller reference. */ status = ssam_try_set_controller(ctrl); @@ -699,15 +735,31 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) * resumed. In short, this causes some spurious unwanted wake-ups. * For now let's thus default power/wakeup to false. */ - device_set_wakeup_capable(&serdev->dev, true); - acpi_dev_clear_dependencies(ssh); + device_set_wakeup_capable(dev, true); + + /* + * When using DT, we have to register the platform hub driver manually, + * as it can't be matched based on top-level board compatible (like it + * does the ACPI case). + */ + if (!ssh) { + struct platform_device *ph_pdev = + platform_device_register_simple("surface_aggregator_platform_hub", + 0, NULL, 0); + if (IS_ERR(ph_pdev)) + return dev_err_probe(dev, PTR_ERR(ph_pdev), + "Failed to register the platform hub driver\n"); + } + + if (ssh) + acpi_dev_clear_dependencies(ssh); return 0; err_mainref: ssam_irq_free(ctrl); err_irq: - sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); + sysfs_remove_group(&dev->kobj, &ssam_sam_group); err_initrq: ssam_controller_lock(ctrl); ssam_controller_shutdown(ctrl); @@ -765,18 +817,27 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev) device_set_wakeup_capable(&serdev->dev, false); } -static const struct acpi_device_id ssam_serial_hub_match[] = { +static const struct acpi_device_id ssam_serial_hub_acpi_match[] = { { "MSHW0084", 0 }, { }, }; -MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_match); +MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_acpi_match); + +#ifdef CONFIG_OF +static const struct of_device_id ssam_serial_hub_of_match[] = { + { .compatible = "microsoft,surface-sam", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ssam_serial_hub_of_match); +#endif static struct serdev_device_driver ssam_serial_hub = { .probe = ssam_serial_hub_probe, .remove = ssam_serial_hub_remove, .driver = { .name = "surface_serial_hub", - .acpi_match_table = ssam_serial_hub_match, + .acpi_match_table = ACPI_PTR(ssam_serial_hub_acpi_match), + .of_match_table = of_match_ptr(ssam_serial_hub_of_match), .pm = &ssam_serial_hub_pm_ops, .shutdown = ssam_serial_hub_shutdown, .probe_type = PROBE_PREFER_ASYNCHRONOUS, @@ -817,7 +878,7 @@ err_cpkg: err_bus: return status; } -module_init(ssam_core_init); +subsys_initcall(ssam_core_init); static void __exit ssam_core_exit(void) { diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h index e562958ffdf0..80aa568a0759 100644 --- a/drivers/platform/surface/aggregator/ssh_msgb.h +++ b/drivers/platform/surface/aggregator/ssh_msgb.h @@ -2,13 +2,13 @@ /* * SSH message builder functions. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H #define _SURFACE_AGGREGATOR_SSH_MSGB_H -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/types.h> #include <linux/surface_aggregator/controller.h> @@ -189,8 +189,8 @@ static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, __msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */ __msgb_push_u8(msgb, rqst->target_category); /* Target category. */ - __msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */ - __msgb_push_u8(msgb, 0x00); /* Target ID (in). */ + __msgb_push_u8(msgb, rqst->target_id); /* Target ID. */ + __msgb_push_u8(msgb, SSAM_SSH_TID_HOST); /* Source ID. */ __msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */ __msgb_push_u16(msgb, rqid); /* Request ID. */ __msgb_push_u8(msgb, rqst->command_id); /* Command ID. */ diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index 8a4451c1ffe5..3dd22856570f 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -2,10 +2,10 @@ /* * SSH packet transport layer. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/atomic.h> #include <linux/error-injection.h> #include <linux/jiffies.h> @@ -671,7 +671,7 @@ static void ssh_ptl_timeout_reaper_mod(struct ssh_ptl *ptl, ktime_t now, /* Re-adjust / schedule reaper only if it is above resolution delta. */ if (ktime_before(aexp, ptl->rtx_timeout.expires)) { ptl->rtx_timeout.expires = expires; - mod_delayed_work(system_wq, &ptl->rtx_timeout.reaper, delta); + mod_delayed_work(system_percpu_wq, &ptl->rtx_timeout.reaper, delta); } spin_unlock(&ptl->rtx_timeout.lock); @@ -1596,16 +1596,32 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) ssh_ptl_tx_wakeup_packet(ptl); } -static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) +static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, const struct ssh_frame *frame) { int i; /* + * Ignore unsequenced packets. On some devices (notably Surface Pro 9), + * unsequenced events will always be sent with SEQ=0x00. Attempting to + * detect retransmission would thus just block all events. + * + * While sequence numbers would also allow detection of retransmitted + * packets in unsequenced communication, they have only ever been used + * to cover edge-cases in sequenced transmission. In particular, the + * only instance of packets being retransmitted (that we are aware of) + * is due to an ACK timeout. As this does not happen in unsequenced + * communication, skip the retransmission check for those packets + * entirely. + */ + if (frame->type == SSH_FRAME_TYPE_DATA_NSQ) + return false; + + /* * Check if SEQ has been seen recently (i.e. packet was * re-transmitted and we should ignore it). */ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) { - if (likely(ptl->rx.blocked.seqs[i] != seq)) + if (likely(ptl->rx.blocked.seqs[i] != frame->seq)) continue; ptl_dbg(ptl, "ptl: ignoring repeated data packet\n"); @@ -1613,7 +1629,7 @@ static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) } /* Update list of blocked sequence IDs. */ - ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = seq; + ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = frame->seq; ptl->rx.blocked.offset = (ptl->rx.blocked.offset + 1) % ARRAY_SIZE(ptl->rx.blocked.seqs); @@ -1624,7 +1640,7 @@ static void ssh_ptl_rx_dataframe(struct ssh_ptl *ptl, const struct ssh_frame *frame, const struct ssam_span *payload) { - if (ssh_ptl_rx_retransmit_check(ptl, frame->seq)) + if (ssh_ptl_rx_retransmit_check(ptl, frame)) return; ptl->ops.data_received(ptl, payload); @@ -1871,9 +1887,9 @@ int ssh_ptl_rx_stop(struct ssh_ptl *ptl) * Return: Returns the number of bytes transferred (positive or zero) on * success. Returns %-ESHUTDOWN if the packet layer has been shut down. */ -int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) +ssize_t ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) { - int used; + size_t used; if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) return -ESHUTDOWN; diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h index 2eb329f0b91a..c80e822070df 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.h +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h @@ -2,7 +2,7 @@ /* * SSH packet transport layer. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H @@ -162,7 +162,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl); int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p); void ssh_ptl_cancel(struct ssh_packet *p); -int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n); +ssize_t ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n); /** * ssh_ptl_tx_wakeup_transfer() - Wake up packet transmitter thread for diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c index b77912f8f13b..6cfda85d3b33 100644 --- a/drivers/platform/surface/aggregator/ssh_parser.c +++ b/drivers/platform/surface/aggregator/ssh_parser.c @@ -2,10 +2,10 @@ /* * SSH message parser. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/compiler.h> #include <linux/device.h> #include <linux/types.h> diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h index 3bd6e180fd16..801d8fa69fb5 100644 --- a/drivers/platform/surface/aggregator/ssh_parser.h +++ b/drivers/platform/surface/aggregator/ssh_parser.h @@ -2,7 +2,7 @@ /* * SSH message parser. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index 790f7f0eee98..a356e4956562 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -2,10 +2,10 @@ /* * SSH request transport layer. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/atomic.h> #include <linux/completion.h> #include <linux/error-injection.h> @@ -434,7 +434,7 @@ static void ssh_rtl_timeout_reaper_mod(struct ssh_rtl *rtl, ktime_t now, /* Re-adjust / schedule reaper only if it is above resolution delta. */ if (ktime_before(aexp, rtl->rtx_timeout.expires)) { rtl->rtx_timeout.expires = expires; - mod_delayed_work(system_wq, &rtl->rtx_timeout.reaper, delta); + mod_delayed_work(system_percpu_wq, &rtl->rtx_timeout.reaper, delta); } spin_unlock(&rtl->rtx_timeout.lock); @@ -916,6 +916,21 @@ static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) if (sshp_parse_command(dev, data, &command, &command_data)) return; + /* + * Check if the message was intended for us. If not, drop it. + * + * Note: We will need to change this to handle debug messages. On newer + * generation devices, these seem to be sent to SSAM_SSH_TID_DEBUG. We + * as host can still receive them as they can be forwarded via an + * override option on SAM, but doing so does not change the target ID + * to SSAM_SSH_TID_HOST. + */ + if (command->tid != SSAM_SSH_TID_HOST) { + rtl_warn(rtl, "rtl: dropping message not intended for us (tid = %#04x)\n", + command->tid); + return; + } + if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) ssh_rtl_rx_event(rtl, command, &command_data); else diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h index 9c3cbae2d4bd..4e387a031351 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.h +++ b/drivers/platform/surface/aggregator/ssh_request_layer.h @@ -2,7 +2,7 @@ /* * SSH request transport layer. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h index de64cf169060..caf7d3cb5d8b 100644 --- a/drivers/platform/surface/aggregator/trace.h +++ b/drivers/platform/surface/aggregator/trace.h @@ -2,7 +2,7 @@ /* * Trace points for SSAM/SSH. * - * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #undef TRACE_SYSTEM @@ -13,7 +13,7 @@ #include <linux/surface_aggregator/serial_hub.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/tracepoint.h> TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); @@ -76,7 +76,7 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); -TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC0); TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); @@ -85,12 +85,18 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SPT); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SYS); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC1); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SHB); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS); #define SSAM_PTR_UID_LEN 9 #define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) #define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) #define SSAM_RQID_NOT_APPLICABLE ((u32)-1) #define SSAM_SSH_TC_NOT_APPLICABLE 0 +#define SSAM_SSH_TID_NOT_APPLICABLE ((u8)-1) #ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS #define _SURFACE_AGGREGATOR_TRACE_HELPERS @@ -146,11 +152,43 @@ static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) } /** + * ssam_trace_get_request_tid() - Read the packet's request target ID. + * @p: The packet. + * + * Return: Returns the packet's request target ID (TID) field if the packet + * represents a request with command data, or %SSAM_SSH_TID_NOT_APPLICABLE + * if not (e.g. flush request, control packet). + */ +static inline u32 ssam_trace_get_request_tid(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) + return SSAM_SSH_TID_NOT_APPLICABLE; + + return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tid)]); +} + +/** + * ssam_trace_get_request_sid() - Read the packet's request source ID. + * @p: The packet. + * + * Return: Returns the packet's request source ID (SID) field if the packet + * represents a request with command data, or %SSAM_SSH_TID_NOT_APPLICABLE + * if not (e.g. flush request, control packet). + */ +static inline u32 ssam_trace_get_request_sid(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) + return SSAM_SSH_TID_NOT_APPLICABLE; + + return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(sid)]); +} + +/** * ssam_trace_get_request_tc() - Read the packet's request target category. * @p: The packet. * * Return: Returns the packet's request target category (TC) field if the - * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE + * packet represents a request with command data, or %SSAM_SSH_TC_NOT_APPLICABLE * if not (e.g. flush request, control packet). */ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) @@ -227,42 +265,57 @@ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ ) -#define ssam_show_ssh_tc(rqid) \ - __print_symbolic(rqid, \ - { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ - { SSAM_SSH_TC_SAM, "SAM" }, \ - { SSAM_SSH_TC_BAT, "BAT" }, \ - { SSAM_SSH_TC_TMP, "TMP" }, \ - { SSAM_SSH_TC_PMC, "PMC" }, \ - { SSAM_SSH_TC_FAN, "FAN" }, \ - { SSAM_SSH_TC_PoM, "PoM" }, \ - { SSAM_SSH_TC_DBG, "DBG" }, \ - { SSAM_SSH_TC_KBD, "KBD" }, \ - { SSAM_SSH_TC_FWU, "FWU" }, \ - { SSAM_SSH_TC_UNI, "UNI" }, \ - { SSAM_SSH_TC_LPC, "LPC" }, \ - { SSAM_SSH_TC_TCL, "TCL" }, \ - { SSAM_SSH_TC_SFL, "SFL" }, \ - { SSAM_SSH_TC_KIP, "KIP" }, \ - { SSAM_SSH_TC_EXT, "EXT" }, \ - { SSAM_SSH_TC_BLD, "BLD" }, \ - { SSAM_SSH_TC_BAS, "BAS" }, \ - { SSAM_SSH_TC_SEN, "SEN" }, \ - { SSAM_SSH_TC_SRQ, "SRQ" }, \ - { SSAM_SSH_TC_MCU, "MCU" }, \ - { SSAM_SSH_TC_HID, "HID" }, \ - { SSAM_SSH_TC_TCH, "TCH" }, \ - { SSAM_SSH_TC_BKL, "BKL" }, \ - { SSAM_SSH_TC_TAM, "TAM" }, \ - { SSAM_SSH_TC_ACC, "ACC" }, \ - { SSAM_SSH_TC_UFI, "UFI" }, \ - { SSAM_SSH_TC_USC, "USC" }, \ - { SSAM_SSH_TC_PEN, "PEN" }, \ - { SSAM_SSH_TC_VID, "VID" }, \ - { SSAM_SSH_TC_AUD, "AUD" }, \ - { SSAM_SSH_TC_SMC, "SMC" }, \ - { SSAM_SSH_TC_KPD, "KPD" }, \ - { SSAM_SSH_TC_REG, "REG" } \ +#define ssam_show_ssh_tid(tid) \ + __print_symbolic(tid, \ + { SSAM_SSH_TID_NOT_APPLICABLE, "N/A" }, \ + { SSAM_SSH_TID_HOST, "Host" }, \ + { SSAM_SSH_TID_SAM, "SAM" }, \ + { SSAM_SSH_TID_KIP, "KIP" }, \ + { SSAM_SSH_TID_DEBUG, "Debug" }, \ + { SSAM_SSH_TID_SURFLINK, "SurfLink" } \ + ) + +#define ssam_show_ssh_tc(tc) \ + __print_symbolic(tc, \ + { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ + { SSAM_SSH_TC_SAM, "SAM" }, \ + { SSAM_SSH_TC_BAT, "BAT" }, \ + { SSAM_SSH_TC_TMP, "TMP" }, \ + { SSAM_SSH_TC_PMC, "PMC" }, \ + { SSAM_SSH_TC_FAN, "FAN" }, \ + { SSAM_SSH_TC_PoM, "PoM" }, \ + { SSAM_SSH_TC_DBG, "DBG" }, \ + { SSAM_SSH_TC_KBD, "KBD" }, \ + { SSAM_SSH_TC_FWU, "FWU" }, \ + { SSAM_SSH_TC_UNI, "UNI" }, \ + { SSAM_SSH_TC_LPC, "LPC" }, \ + { SSAM_SSH_TC_TCL, "TCL" }, \ + { SSAM_SSH_TC_SFL, "SFL" }, \ + { SSAM_SSH_TC_KIP, "KIP" }, \ + { SSAM_SSH_TC_EXT, "EXT" }, \ + { SSAM_SSH_TC_BLD, "BLD" }, \ + { SSAM_SSH_TC_BAS, "BAS" }, \ + { SSAM_SSH_TC_SEN, "SEN" }, \ + { SSAM_SSH_TC_SRQ, "SRQ" }, \ + { SSAM_SSH_TC_MCU, "MCU" }, \ + { SSAM_SSH_TC_HID, "HID" }, \ + { SSAM_SSH_TC_TCH, "TCH" }, \ + { SSAM_SSH_TC_BKL, "BKL" }, \ + { SSAM_SSH_TC_TAM, "TAM" }, \ + { SSAM_SSH_TC_ACC0, "ACC0" }, \ + { SSAM_SSH_TC_UFI, "UFI" }, \ + { SSAM_SSH_TC_USC, "USC" }, \ + { SSAM_SSH_TC_PEN, "PEN" }, \ + { SSAM_SSH_TC_VID, "VID" }, \ + { SSAM_SSH_TC_AUD, "AUD" }, \ + { SSAM_SSH_TC_SMC, "SMC" }, \ + { SSAM_SSH_TC_KPD, "KPD" }, \ + { SSAM_SSH_TC_REG, "REG" }, \ + { SSAM_SSH_TC_SPT, "SPT" }, \ + { SSAM_SSH_TC_SYS, "SYS" }, \ + { SSAM_SSH_TC_ACC1, "ACC1" }, \ + { SSAM_SSH_TC_SHB, "SMB" }, \ + { SSAM_SSH_TC_POS, "POS" } \ ) DECLARE_EVENT_CLASS(ssam_frame_class, @@ -303,6 +356,8 @@ DECLARE_EVENT_CLASS(ssam_command_class, TP_STRUCT__entry( __field(u16, rqid) __field(u16, len) + __field(u8, tid) + __field(u8, sid) __field(u8, tc) __field(u8, cid) __field(u8, iid) @@ -310,14 +365,18 @@ DECLARE_EVENT_CLASS(ssam_command_class, TP_fast_assign( __entry->rqid = get_unaligned_le16(&cmd->rqid); + __entry->tid = cmd->tid; + __entry->sid = cmd->sid; __entry->tc = cmd->tc; __entry->cid = cmd->cid; __entry->iid = cmd->iid; __entry->len = len; ), - TP_printk("rqid=%#06x, tc=%s, cid=%#04x, iid=%#04x, len=%u", + TP_printk("rqid=%#06x, tid=%s, sid=%s, tc=%s, cid=%#04x, iid=%#04x, len=%u", __entry->rqid, + ssam_show_ssh_tid(__entry->tid), + ssam_show_ssh_tid(__entry->sid), ssam_show_ssh_tc(__entry->tc), __entry->cid, __entry->iid, @@ -420,6 +479,8 @@ DECLARE_EVENT_CLASS(ssam_request_class, __field(u8, tc) __field(u16, cid) __field(u16, iid) + __field(u8, tid) + __field(u8, sid) ), TP_fast_assign( @@ -429,16 +490,20 @@ DECLARE_EVENT_CLASS(ssam_request_class, __entry->state = READ_ONCE(request->state); __entry->rqid = ssam_trace_get_request_id(p); ssam_trace_ptr_uid(p, __entry->uid); + __entry->tid = ssam_trace_get_request_tid(p); + __entry->sid = ssam_trace_get_request_sid(p); __entry->tc = ssam_trace_get_request_tc(p); __entry->cid = ssam_trace_get_command_field_u8(p, cid); __entry->iid = ssam_trace_get_command_field_u8(p, iid); ), - TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", + TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tid=%s, sid=%s, tc=%s, cid=%s, iid=%s", __entry->uid, ssam_show_request_id(__entry->rqid), ssam_show_request_type(__entry->state), ssam_show_request_state(__entry->state), + ssam_show_ssh_tid(__entry->tid), + ssam_show_ssh_tid(__entry->sid), ssam_show_ssh_tc(__entry->tc), ssam_show_generic_u8_field(__entry->cid), ssam_show_generic_u8_field(__entry->iid) @@ -464,6 +529,8 @@ DECLARE_EVENT_CLASS(ssam_request_status_class, __field(u8, tc) __field(u16, cid) __field(u16, iid) + __field(u8, tid) + __field(u8, sid) ), TP_fast_assign( @@ -474,16 +541,20 @@ DECLARE_EVENT_CLASS(ssam_request_status_class, __entry->rqid = ssam_trace_get_request_id(p); __entry->status = status; ssam_trace_ptr_uid(p, __entry->uid); + __entry->tid = ssam_trace_get_request_tid(p); + __entry->sid = ssam_trace_get_request_sid(p); __entry->tc = ssam_trace_get_request_tc(p); __entry->cid = ssam_trace_get_command_field_u8(p, cid); __entry->iid = ssam_trace_get_command_field_u8(p, iid); ), - TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", + TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tid=%s, sid=%s, tc=%s, cid=%s, iid=%s, status=%d", __entry->uid, ssam_show_request_id(__entry->rqid), ssam_show_request_type(__entry->state), ssam_show_request_state(__entry->state), + ssam_show_ssh_tid(__entry->tid), + ssam_show_ssh_tid(__entry->sid), ssam_show_ssh_tc(__entry->tc), ssam_show_generic_u8_field(__entry->cid), ssam_show_generic_u8_field(__entry->iid), diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 09ac9cfc40d8..6c8fb7a4dde4 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -116,15 +116,11 @@ static acpi_status s3_wmi_attach_spi_device(acpi_handle handle, void *data, void **return_value) { - struct acpi_device *adev, **ts_adev; + struct acpi_device *adev = acpi_fetch_acpi_dev(handle); + struct acpi_device **ts_adev = data; - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - - ts_adev = data; - - if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, - strlen(SPI_TS_OBJ_NAME))) + if (!adev || strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, + strlen(SPI_TS_OBJ_NAME))) return AE_OK; if (*ts_adev) { @@ -190,14 +186,11 @@ static int s3_wmi_create_and_register_input(struct platform_device *pdev) error = input_register_device(input); if (error) - goto out_err; + return error; s3_wmi.input = input; return 0; - out_err: - input_free_device(s3_wmi.input); - return error; } static int __init s3_wmi_probe(struct platform_device *pdev) @@ -233,14 +226,13 @@ static int __init s3_wmi_probe(struct platform_device *pdev) return error; } -static int s3_wmi_remove(struct platform_device *device) +static void s3_wmi_remove(struct platform_device *device) { /* remove the hotplug context from the acpi device */ s3_wmi.touchscreen_adev->hp = NULL; /* reinstall the actual PNPC0C0D LID default handle */ acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); - return 0; } static int __maybe_unused s3_wmi_resume(struct device *dev) diff --git a/drivers/platform/surface/surface3_button.c b/drivers/platform/surface/surface3_button.c deleted file mode 100644 index 48d77e7aae76..000000000000 --- a/drivers/platform/surface/surface3_button.c +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Supports for the button array on the Surface tablets. - * - * (C) Copyright 2016 Red Hat, Inc - * - * Based on soc_button_array.c: - * - * {C} Copyright 2014 Intel Corporation - */ - -#include <linux/module.h> -#include <linux/input.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/i2c.h> -#include <linux/slab.h> -#include <linux/acpi.h> -#include <linux/gpio/consumer.h> -#include <linux/gpio_keys.h> -#include <linux/gpio.h> -#include <linux/platform_device.h> - - -#define SURFACE_BUTTON_OBJ_NAME "TEV2" -#define MAX_NBUTTONS 4 - -/* - * Some of the buttons like volume up/down are auto repeat, while others - * are not. To support both, we register two platform devices, and put - * buttons into them based on whether the key should be auto repeat. - */ -#define BUTTON_TYPES 2 - -/* - * Power button, Home button, Volume buttons support is supposed to - * be covered by drivers/input/misc/soc_button_array.c, which is implemented - * according to "Windows ACPI Design Guide for SoC Platforms". - * However surface 3 seems not to obey the specs, instead it uses - * device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly - * different in which the Home button is active high. - * Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3 - * is a reduce platform and thus uses GPIOs, not ACPI events. - * We choose an I2C driver here because we need to access the resources - * declared under the device node, while surfacepro3_button.c only needs - * the ACPI companion node. - */ -static const struct acpi_device_id surface3_acpi_match[] = { - { "MSHW0028", 0 }, - { } -}; -MODULE_DEVICE_TABLE(acpi, surface3_acpi_match); - -struct surface3_button_info { - const char *name; - int acpi_index; - unsigned int event_type; - unsigned int event_code; - bool autorepeat; - bool wakeup; - bool active_low; -}; - -struct surface3_button_data { - struct platform_device *children[BUTTON_TYPES]; -}; - -/* - * Get the Nth GPIO number from the ACPI object. - */ -static int surface3_button_lookup_gpio(struct device *dev, int acpi_index) -{ - struct gpio_desc *desc; - int gpio; - - desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS); - if (IS_ERR(desc)) - return PTR_ERR(desc); - - gpio = desc_to_gpio(desc); - - gpiod_put(desc); - - return gpio; -} - -static struct platform_device * -surface3_button_device_create(struct i2c_client *client, - const struct surface3_button_info *button_info, - bool autorepeat) -{ - const struct surface3_button_info *info; - struct platform_device *pd; - struct gpio_keys_button *gpio_keys; - struct gpio_keys_platform_data *gpio_keys_pdata; - int n_buttons = 0; - int gpio; - int error; - - gpio_keys_pdata = devm_kzalloc(&client->dev, - sizeof(*gpio_keys_pdata) + - sizeof(*gpio_keys) * MAX_NBUTTONS, - GFP_KERNEL); - if (!gpio_keys_pdata) - return ERR_PTR(-ENOMEM); - - gpio_keys = (void *)(gpio_keys_pdata + 1); - - for (info = button_info; info->name; info++) { - if (info->autorepeat != autorepeat) - continue; - - gpio = surface3_button_lookup_gpio(&client->dev, - info->acpi_index); - if (!gpio_is_valid(gpio)) - continue; - - gpio_keys[n_buttons].type = info->event_type; - gpio_keys[n_buttons].code = info->event_code; - gpio_keys[n_buttons].gpio = gpio; - gpio_keys[n_buttons].active_low = info->active_low; - gpio_keys[n_buttons].desc = info->name; - gpio_keys[n_buttons].wakeup = info->wakeup; - n_buttons++; - } - - if (n_buttons == 0) { - error = -ENODEV; - goto err_free_mem; - } - - gpio_keys_pdata->buttons = gpio_keys; - gpio_keys_pdata->nbuttons = n_buttons; - gpio_keys_pdata->rep = autorepeat; - - pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO); - if (!pd) { - error = -ENOMEM; - goto err_free_mem; - } - - error = platform_device_add_data(pd, gpio_keys_pdata, - sizeof(*gpio_keys_pdata)); - if (error) - goto err_free_pdev; - - error = platform_device_add(pd); - if (error) - goto err_free_pdev; - - return pd; - -err_free_pdev: - platform_device_put(pd); -err_free_mem: - devm_kfree(&client->dev, gpio_keys_pdata); - return ERR_PTR(error); -} - -static int surface3_button_remove(struct i2c_client *client) -{ - struct surface3_button_data *priv = i2c_get_clientdata(client); - - int i; - - for (i = 0; i < BUTTON_TYPES; i++) - if (priv->children[i]) - platform_device_unregister(priv->children[i]); - - return 0; -} - -static struct surface3_button_info surface3_button_surface3[] = { - { "power", 0, EV_KEY, KEY_POWER, false, true, true }, - { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, - { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, - { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, - { } -}; - -static int surface3_button_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct device *dev = &client->dev; - struct surface3_button_data *priv; - struct platform_device *pd; - int i; - int error; - - if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)), - SURFACE_BUTTON_OBJ_NAME, - strlen(SURFACE_BUTTON_OBJ_NAME))) - return -ENODEV; - - error = gpiod_count(dev, NULL); - if (error < 0) { - dev_dbg(dev, "no GPIO attached, ignoring...\n"); - return error; - } - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - i2c_set_clientdata(client, priv); - - for (i = 0; i < BUTTON_TYPES; i++) { - pd = surface3_button_device_create(client, - surface3_button_surface3, - i == 0); - if (IS_ERR(pd)) { - error = PTR_ERR(pd); - if (error != -ENODEV) { - surface3_button_remove(client); - return error; - } - continue; - } - - priv->children[i] = pd; - } - - if (!priv->children[0] && !priv->children[1]) - return -ENODEV; - - return 0; -} - -static const struct i2c_device_id surface3_id[] = { - { } -}; -MODULE_DEVICE_TABLE(i2c, surface3_id); - -static struct i2c_driver surface3_driver = { - .probe = surface3_button_probe, - .remove = surface3_button_remove, - .id_table = surface3_id, - .driver = { - .name = "surface3", - .acpi_match_table = ACPI_PTR(surface3_acpi_match), - }, -}; -module_i2c_driver(surface3_driver); - -MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); -MODULE_DESCRIPTION("surface3 button array driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/surface/surface3_power.c b/drivers/platform/surface/surface3_power.c index abac3eec565e..1ee5239269ae 100644 --- a/drivers/platform/surface/surface3_power.c +++ b/drivers/platform/surface/surface3_power.c @@ -40,7 +40,7 @@ #include <linux/slab.h> #include <linux/types.h> #include <linux/uuid.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #define SURFACE_3_POLL_INTERVAL (2 * HZ) #define SURFACE_3_STRLEN 10 @@ -232,14 +232,21 @@ static int mshw0011_bix(struct mshw0011_data *cdata, struct bix *bix) } bix->last_full_charg_capacity = ret; - /* get serial number */ + /* + * Get serial number, on some devices (with unofficial replacement + * battery?) reading any of the serial number range addresses gets + * nacked in this case just leave the serial number empty. + */ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_SERIAL_NO, sizeof(buf), buf); - if (ret != sizeof(buf)) { + if (ret == -EREMOTEIO) { + /* no serial number available */ + } else if (ret != sizeof(buf)) { dev_err(&client->dev, "Error reading serial no: %d\n", ret); return ret; + } else { + snprintf(bix->serial, ARRAY_SIZE(bix->serial), "%3pE%6pE", buf + 7, buf); } - snprintf(bix->serial, ARRAY_SIZE(bix->serial), "%3pE%6pE", buf + 7, buf); /* get cycle count */ ret = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CYCLE_CNT); @@ -512,7 +519,7 @@ static int mshw0011_probe(struct i2c_client *client) i2c_set_clientdata(client, data); memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "MSHW0011-bat0", I2C_NAME_SIZE); + strscpy(board_info.type, "MSHW0011-bat0", I2C_NAME_SIZE); bat0 = i2c_acpi_new_device(dev, 1, &board_info); if (IS_ERR(bat0)) @@ -547,7 +554,7 @@ out_err: return error; } -static int mshw0011_remove(struct i2c_client *client) +static void mshw0011_remove(struct i2c_client *client) { struct mshw0011_data *cdata = i2c_get_clientdata(client); @@ -557,8 +564,6 @@ static int mshw0011_remove(struct i2c_client *client) kthread_stop(cdata->poll_task); i2c_unregister_device(cdata->bat0); - - return 0; } static const struct acpi_device_id mshw0011_acpi_match[] = { @@ -568,7 +573,7 @@ static const struct acpi_device_id mshw0011_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, mshw0011_acpi_match); static struct i2c_driver mshw0011_driver = { - .probe_new = mshw0011_probe, + .probe = mshw0011_probe, .remove = mshw0011_remove, .driver = { .name = "mshw0011", diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c index 8339988d95c1..a9dcb0bbe90e 100644 --- a/drivers/platform/surface/surface_acpi_notify.c +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -8,10 +8,10 @@ * notifications sent from ACPI via the SAN interface by providing them to any * registered external driver. * - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/acpi.h> #include <linux/delay.h> #include <linux/jiffies.h> @@ -37,6 +37,7 @@ struct san_data { #define to_san_data(ptr, member) \ container_of(ptr, struct san_data, member) +static struct workqueue_struct *san_wq; /* -- dGPU notifier interface. ---------------------------------------------- */ @@ -354,9 +355,10 @@ static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); work->dev = d->dev; - memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); + work->event = *event; + memcpy(work->event.data, event->data, event->length); - schedule_delayed_work(&work->work, delay); + queue_delayed_work(san_wq, &work->work, delay); return SSAM_NOTIF_HANDLED; } @@ -588,7 +590,7 @@ static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) return san_rqst_fixup_suspended(d, &rqst, buffer); } - status = __ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, + status = __ssam_retry(ssam_request_do_sync_onstack, SAN_REQUEST_NUM_TRIES, d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); if (!status) { @@ -734,30 +736,6 @@ do { \ #define san_consumer_warn(dev, handle, fmt, ...) \ san_consumer_printk(warn, dev, handle, fmt, ##__VA_ARGS__) -static bool is_san_consumer(struct platform_device *pdev, acpi_handle handle) -{ - struct acpi_handle_list dep_devices; - acpi_handle supplier = ACPI_HANDLE(&pdev->dev); - acpi_status status; - int i; - - if (!acpi_has_method(handle, "_DEP")) - return false; - - status = acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices); - if (ACPI_FAILURE(status)) { - san_consumer_dbg(&pdev->dev, handle, "failed to evaluate _DEP\n"); - return false; - } - - for (i = 0; i < dep_devices.count; i++) { - if (dep_devices.handles[i] == supplier) - return true; - } - - return false; -} - static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, void *context, void **rv) { @@ -766,11 +744,12 @@ static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, struct acpi_device *adev; struct device_link *link; - if (!is_san_consumer(pdev, handle)) + if (!acpi_device_dep(handle, ACPI_HANDLE(&pdev->dev))) return AE_OK; /* Ignore ACPI devices that are not present. */ - if (acpi_bus_get_device(handle, &adev) != 0) + adev = acpi_fetch_acpi_dev(handle); + if (!adev) return AE_OK; san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); @@ -847,7 +826,7 @@ err_enable_events: return status; } -static int san_remove(struct platform_device *pdev) +static void san_remove(struct platform_device *pdev) { acpi_handle san = ACPI_HANDLE(&pdev->dev); @@ -860,9 +839,7 @@ static int san_remove(struct platform_device *pdev) * We have unregistered our event sources. Now we need to ensure that * all delayed works they may have spawned are run to completion. */ - flush_scheduled_work(); - - return 0; + flush_workqueue(san_wq); } static const struct acpi_device_id san_match[] = { @@ -880,7 +857,27 @@ static struct platform_driver surface_acpi_notify = { .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, }; -module_platform_driver(surface_acpi_notify); + +static int __init san_init(void) +{ + int ret; + + san_wq = alloc_workqueue("san_wq", WQ_PERCPU, 0); + if (!san_wq) + return -ENOMEM; + ret = platform_driver_register(&surface_acpi_notify); + if (ret) + destroy_workqueue(san_wq); + return ret; +} +module_init(san_init); + +static void __exit san_exit(void) +{ + platform_driver_unregister(&surface_acpi_notify); + destroy_workqueue(san_wq); +} +module_exit(san_exit); MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c index 30fb50fde450..bfaa09d1648b 100644 --- a/drivers/platform/surface/surface_aggregator_cdev.c +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -3,7 +3,7 @@ * Provides user-space access to the SSAM EC via the /dev/surface/aggregator * misc device. Intended for debugging and development. * - * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/fs.h> @@ -302,8 +302,8 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_ * 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. + * validated later in ssam_request_do_sync(), for allocation + * the bound imposed by u16 should be enough. */ spec.payload = kzalloc(spec.length, GFP_KERNEL); if (!spec.payload) { @@ -342,7 +342,7 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_ } /* Perform request. */ - status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); + status = ssam_request_do_sync(client->cdev->ctrl, &spec, &rsp); if (status) goto out; @@ -670,7 +670,6 @@ static const struct file_operations ssam_controller_fops = { .fasync = ssam_cdev_fasync, .unlocked_ioctl = ssam_cdev_device_ioctl, .compat_ioctl = ssam_cdev_device_ioctl, - .llseek = no_llseek, }; @@ -714,7 +713,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) return 0; } -static int ssam_dbg_device_remove(struct platform_device *pdev) +static void ssam_dbg_device_remove(struct platform_device *pdev) { struct ssam_cdev *cdev = platform_get_drvdata(pdev); struct ssam_cdev_client *client; @@ -757,7 +756,6 @@ static int ssam_dbg_device_remove(struct platform_device *pdev) misc_deregister(&cdev->mdev); ssam_cdev_put(cdev); - return 0; } static struct platform_device *ssam_cdev_device; diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c new file mode 100644 index 000000000000..8b8b80228c14 --- /dev/null +++ b/drivers/platform/surface/surface_aggregator_hub.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Surface System Aggregator Module (SSAM) subsystem device hubs. + * + * Provides a driver for SSAM subsystems device hubs. This driver performs + * instantiation of the devices managed by said hubs and takes care of + * (hot-)removal. + * + * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include <linux/surface_aggregator/device.h> + + +/* -- SSAM generic subsystem hub driver framework. -------------------------- */ + +enum ssam_hub_state { + SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ + SSAM_HUB_CONNECTED, + SSAM_HUB_DISCONNECTED, +}; + +enum ssam_hub_flags { + SSAM_HUB_HOT_REMOVED, +}; + +struct ssam_hub; + +struct ssam_hub_ops { + int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); +}; + +struct ssam_hub { + struct ssam_device *sdev; + + enum ssam_hub_state state; + unsigned long flags; + + struct delayed_work update_work; + unsigned long connect_delay; + + struct ssam_event_notifier notif; + struct ssam_hub_ops ops; +}; + +struct ssam_hub_desc { + struct { + struct ssam_event_registry reg; + struct ssam_event_id id; + enum ssam_event_mask mask; + } event; + + struct { + u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); + int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); + } ops; + + unsigned long connect_delay_ms; +}; + +static void ssam_hub_update_workfn(struct work_struct *work) +{ + struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); + enum ssam_hub_state state; + int status = 0; + + status = hub->ops.get_state(hub, &state); + if (status) + return; + + /* + * There is a small possibility that hub devices were hot-removed and + * re-added before we were able to remove them here. In that case, both + * the state returned by get_state() and the state of the hub will + * equal SSAM_HUB_CONNECTED and we would bail early below, which would + * leave child devices without proper (re-)initialization and the + * hot-remove flag set. + * + * Therefore, we check whether devices have been hot-removed via an + * additional flag on the hub and, in this case, override the returned + * hub state. In case of a missed disconnect (i.e. get_state returned + * "connected"), we further need to re-schedule this work (with the + * appropriate delay) as the actual connect work submission might have + * been merged with this one. + * + * This then leads to one of two cases: Either we submit an unnecessary + * work item (which will get ignored via either the queue or the state + * checks) or, in the unlikely case that the work is actually required, + * double the normal connect delay. + */ + if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { + if (state == SSAM_HUB_CONNECTED) + schedule_delayed_work(&hub->update_work, hub->connect_delay); + + state = SSAM_HUB_DISCONNECTED; + } + + if (hub->state == state) + return; + hub->state = state; + + if (hub->state == SSAM_HUB_CONNECTED) + status = ssam_device_register_clients(hub->sdev); + else + ssam_remove_clients(&hub->sdev->dev); + + if (status) + dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); +} + +static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + if (is_ssam_device(dev)) + ssam_device_mark_hot_removed(sdev); + + return 0; +} + +static void ssam_hub_update(struct ssam_hub *hub, bool connected) +{ + unsigned long delay; + + /* Mark devices as hot-removed before we remove any. */ + if (!connected) { + set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); + device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); + } + + /* + * Delay update when the base/keyboard cover is being connected to give + * devices/EC some time to set up. + */ + delay = connected ? hub->connect_delay : 0; + + schedule_delayed_work(&hub->update_work, delay); +} + +static int __maybe_unused ssam_hub_resume(struct device *dev) +{ + struct ssam_hub *hub = dev_get_drvdata(dev); + + schedule_delayed_work(&hub->update_work, 0); + return 0; +} +static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); + +static int ssam_hub_probe(struct ssam_device *sdev) +{ + const struct ssam_hub_desc *desc; + struct ssam_hub *hub; + int status; + + desc = ssam_device_get_match_data(sdev); + if (!desc) { + WARN(1, "no driver match data specified"); + return -EINVAL; + } + + hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + hub->sdev = sdev; + hub->state = SSAM_HUB_UNINITIALIZED; + + hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ + hub->notif.base.fn = desc->ops.notify; + hub->notif.event.reg = desc->event.reg; + hub->notif.event.id = desc->event.id; + hub->notif.event.mask = desc->event.mask; + hub->notif.event.flags = SSAM_EVENT_SEQUENCED; + + hub->connect_delay = msecs_to_jiffies(desc->connect_delay_ms); + hub->ops.get_state = desc->ops.get_state; + + INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); + + ssam_device_set_drvdata(sdev, hub); + + status = ssam_device_notifier_register(sdev, &hub->notif); + if (status) + return status; + + schedule_delayed_work(&hub->update_work, 0); + return 0; +} + +static void ssam_hub_remove(struct ssam_device *sdev) +{ + struct ssam_hub *hub = ssam_device_get_drvdata(sdev); + + ssam_device_notifier_unregister(sdev, &hub->notif); + cancel_delayed_work_sync(&hub->update_work); + ssam_remove_clients(&sdev->dev); +} + + +/* -- SSAM base-subsystem hub driver. --------------------------------------- */ + +/* + * Some devices (especially battery) may need a bit of time to be fully usable + * after being (re-)connected. This delay has been determined via + * experimentation. + */ +#define SSAM_BASE_UPDATE_CONNECT_DELAY 2500 + +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { + .target_category = SSAM_SSH_TC_BAS, + .target_id = SSAM_SSH_TID_SAM, + .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_hub *hub, enum ssam_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_HUB_CONNECTED; + else + *state = SSAM_HUB_DISCONNECTED; + + return 0; +} + +static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); + + if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) + return 0; + + if (event->length < 1) { + dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); + return 0; + } + + ssam_hub_update(hub, event->data[0]); + + /* + * 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 const struct ssam_hub_desc base_hub = { + .event = { + .reg = SSAM_EVENT_REGISTRY_SAM, + .id = { + .target_category = SSAM_SSH_TC_BAS, + .instance = 0, + }, + .mask = SSAM_EVENT_MASK_NONE, + }, + .ops = { + .notify = ssam_base_hub_notif, + .get_state = ssam_base_hub_query_state, + }, + .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY, +}; + + +/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ + +/* + * Some devices may need a bit of time to be fully usable after being + * (re-)connected. This delay has been determined via experimentation. + */ +#define SSAM_KIP_UPDATE_CONNECT_DELAY 250 + +#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c + +SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, { + .target_category = SSAM_SSH_TC_KIP, + .target_id = SSAM_SSH_TID_SAM, + .command_id = 0x2c, + .instance_id = 0x00, +}); + +static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) +{ + int status; + u8 connected; + + status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected); + if (status < 0) { + dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); + return status; + } + + *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; + return 0; +} + +static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); + + if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) + return 0; /* Return "unhandled". */ + + if (event->length < 1) { + dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); + return 0; + } + + ssam_hub_update(hub, event->data[0]); + return SSAM_NOTIF_HANDLED; +} + +static const struct ssam_hub_desc kip_hub = { + .event = { + .reg = SSAM_EVENT_REGISTRY_SAM, + .id = { + .target_category = SSAM_SSH_TC_KIP, + .instance = 0, + }, + .mask = SSAM_EVENT_MASK_TARGET, + }, + .ops = { + .notify = ssam_kip_hub_notif, + .get_state = ssam_kip_hub_query_state, + }, + .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY, +}; + + +/* -- Driver registration. -------------------------------------------------- */ + +static const struct ssam_device_id ssam_hub_match[] = { + { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, + { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, + { } +}; +MODULE_DEVICE_TABLE(ssam, ssam_hub_match); + +static struct ssam_device_driver ssam_subsystem_hub_driver = { + .probe = ssam_hub_probe, + .remove = ssam_hub_remove, + .match_table = ssam_hub_match, + .driver = { + .name = "surface_aggregator_subsystem_hub", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .pm = &ssam_hub_pm_ops, + }, +}; +module_ssam_device_driver(ssam_subsystem_hub_driver); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index ce2bd88feeaa..78ac3a8fbb73 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -6,19 +6,17 @@ * cannot be auto-detected. Provides device-hubs and performs instantiation * for these devices. * - * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/acpi.h> #include <linux/kernel.h> -#include <linux/limits.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/types.h> -#include <linux/workqueue.h> -#include <linux/surface_aggregator/controller.h> #include <linux/surface_aggregator/device.h> @@ -41,9 +39,15 @@ static const struct software_node ssam_node_root = { .name = "ssam_platform_hub", }; +/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ +static const struct software_node ssam_node_hub_kip = { + .name = "ssam:00:00:01:0e:00", + .parent = &ssam_node_root, +}; + /* 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", + .name = "ssam:00:00:01:11:00", .parent = &ssam_node_root, }; @@ -65,50 +69,82 @@ static const struct software_node ssam_node_bat_sb3base = { .parent = &ssam_node_hub_base, }; -/* Platform profile / performance-mode device. */ -static const struct software_node ssam_node_tmp_pprof = { +/* Platform profile / performance-mode device without a fan. */ +static const struct software_node ssam_node_tmp_perf_profile = { .name = "ssam:01:03:01:00:01", .parent = &ssam_node_root, }; +/* Platform profile / performance-mode device with a fan, such that + * the fan controller profile can also be switched. + */ +static const struct property_entry ssam_node_tmp_perf_profile_has_fan[] = { + PROPERTY_ENTRY_BOOL("has_fan"), + { } +}; + +static const struct software_node ssam_node_tmp_perf_profile_with_fan = { + .name = "ssam:01:03:01:00:01", + .parent = &ssam_node_root, + .properties = ssam_node_tmp_perf_profile_has_fan, +}; + +/* Thermal sensors. */ +static const struct software_node ssam_node_tmp_sensors = { + .name = "ssam:01:03:01:00:02", + .parent = &ssam_node_root, +}; + +/* Fan speed function. */ +static const struct software_node ssam_node_fan_speed = { + .name = "ssam:01:05:01:01:01", + .parent = &ssam_node_root, +}; + +/* Tablet-mode switch via KIP subsystem. */ +static const struct software_node ssam_node_kip_tablet_switch = { + .name = "ssam:01:0e: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 (TID1). */ -static const struct software_node ssam_node_hid_tid1_keyboard = { +/* HID keyboard (SAM, TID=1). */ +static const struct software_node ssam_node_hid_sam_keyboard = { .name = "ssam:01:15:01:01:00", .parent = &ssam_node_root, }; -/* HID pen stash (TID1; pen taken / stashed away evens). */ -static const struct software_node ssam_node_hid_tid1_penstash = { +/* HID pen stash (SAM, TID=1; pen taken / stashed away evens). */ +static const struct software_node ssam_node_hid_sam_penstash = { .name = "ssam:01:15:01:02:00", .parent = &ssam_node_root, }; -/* HID touchpad (TID1). */ -static const struct software_node ssam_node_hid_tid1_touchpad = { +/* HID touchpad (SAM, TID=1). */ +static const struct software_node ssam_node_hid_sam_touchpad = { .name = "ssam:01:15:01:03:00", .parent = &ssam_node_root, }; -/* HID device instance 6 (TID1, unknown HID device). */ -static const struct software_node ssam_node_hid_tid1_iid6 = { +/* HID device instance 6 (SAM, TID=1, HID sensor collection). */ +static const struct software_node ssam_node_hid_sam_sensors = { .name = "ssam:01:15:01:06:00", .parent = &ssam_node_root, }; -/* HID device instance 7 (TID1, unknown HID device). */ -static const struct software_node ssam_node_hid_tid1_iid7 = { +/* HID device instance 7 (SAM, TID=1, UCM UCSI HID client). */ +static const struct software_node ssam_node_hid_sam_ucm_ucsi = { .name = "ssam:01:15:01:07:00", .parent = &ssam_node_root, }; -/* HID system controls (TID1). */ -static const struct software_node ssam_node_hid_tid1_sysctrl = { +/* HID system controls (SAM, TID=1). */ +static const struct software_node ssam_node_hid_sam_sysctrl = { .name = "ssam:01:15:01:08:00", .parent = &ssam_node_root, }; @@ -155,6 +191,36 @@ static const struct software_node ssam_node_hid_base_iid6 = { .parent = &ssam_node_hub_base, }; +/* HID keyboard (KIP hub). */ +static const struct software_node ssam_node_hid_kip_keyboard = { + .name = "ssam:01:15:02:01:00", + .parent = &ssam_node_hub_kip, +}; + +/* HID pen stash (KIP hub; pen taken / stashed away evens). */ +static const struct software_node ssam_node_hid_kip_penstash = { + .name = "ssam:01:15:02:02:00", + .parent = &ssam_node_hub_kip, +}; + +/* HID touchpad (KIP hub). */ +static const struct software_node ssam_node_hid_kip_touchpad = { + .name = "ssam:01:15:02:03:00", + .parent = &ssam_node_hub_kip, +}; + +/* HID device instance 5 (KIP hub, type-cover firmware update). */ +static const struct software_node ssam_node_hid_kip_fwupd = { + .name = "ssam:01:15:02:05:00", + .parent = &ssam_node_hub_kip, +}; + +/* Tablet-mode switch via POS subsystem. */ +static const struct software_node ssam_node_pos_tablet_switch = { + .name = "ssam:01:26:01:00:01", + .parent = &ssam_node_root, +}; + /* * Devices for 5th- and 6th-generations models: * - Surface Book 2, @@ -163,7 +229,7 @@ static const struct software_node ssam_node_hid_base_iid6 = { */ static const struct software_node *ssam_node_group_gen5[] = { &ssam_node_root, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile, NULL, }; @@ -174,7 +240,7 @@ static const struct software_node *ssam_node_group_sb3[] = { &ssam_node_bat_ac, &ssam_node_bat_main, &ssam_node_bat_sb3base, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile, &ssam_node_bas_dtx, &ssam_node_hid_base_keyboard, &ssam_node_hid_base_touchpad, @@ -188,335 +254,161 @@ 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_tmp_perf_profile, &ssam_node_hid_main_keyboard, &ssam_node_hid_main_touchpad, &ssam_node_hid_main_iid5, NULL, }; -/* Devices for Surface Laptop Studio. */ -static const struct software_node *ssam_node_group_sls[] = { +/* Devices for Surface Laptop 5. */ +static const struct software_node *ssam_node_group_sl5[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, - &ssam_node_hid_tid1_keyboard, - &ssam_node_hid_tid1_penstash, - &ssam_node_hid_tid1_touchpad, - &ssam_node_hid_tid1_iid6, - &ssam_node_hid_tid1_iid7, - &ssam_node_hid_tid1_sysctrl, + &ssam_node_tmp_perf_profile_with_fan, + &ssam_node_tmp_sensors, + &ssam_node_fan_speed, + &ssam_node_hid_main_keyboard, + &ssam_node_hid_main_touchpad, + &ssam_node_hid_main_iid5, + &ssam_node_hid_sam_ucm_ucsi, NULL, }; -/* Devices for Surface Laptop Go. */ -static const struct software_node *ssam_node_group_slg1[] = { +/* Devices for Surface Laptop 6. */ +static const struct software_node *ssam_node_group_sl6[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile_with_fan, + &ssam_node_tmp_sensors, + &ssam_node_fan_speed, + &ssam_node_hid_main_keyboard, + &ssam_node_hid_main_touchpad, + &ssam_node_hid_main_iid5, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_ucsi, NULL, }; -/* Devices for Surface Pro 7 and Surface Pro 7+. */ -static const struct software_node *ssam_node_group_sp7[] = { +/* Devices for Surface Laptop 7. */ +static const struct software_node *ssam_node_group_sl7[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, + &ssam_node_tmp_perf_profile_with_fan, + &ssam_node_fan_speed, + &ssam_node_hid_sam_keyboard, + /* TODO: evaluate thermal sensors devices when we get a driver for that */ NULL, }; -static const struct software_node *ssam_node_group_sp8[] = { +/* Devices for Surface Laptop Studio 1. */ +static const struct software_node *ssam_node_group_sls1[] = { &ssam_node_root, &ssam_node_bat_ac, &ssam_node_bat_main, - &ssam_node_tmp_pprof, - /* TODO: Add support for keyboard cover. */ + &ssam_node_tmp_perf_profile, + &ssam_node_pos_tablet_switch, + &ssam_node_hid_sam_keyboard, + &ssam_node_hid_sam_penstash, + &ssam_node_hid_sam_touchpad, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_ucsi, + &ssam_node_hid_sam_sysctrl, 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_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_register_clients(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_remove_clients(parent); - return status; -} - - -/* -- SSAM base-hub driver. ------------------------------------------------- */ - -/* - * Some devices (especially battery) may need a bit of time to be fully usable - * after being (re-)connected. This delay has been determined via - * experimentation. - */ -#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) - -enum ssam_base_hub_state { - SSAM_BASE_HUB_UNINITIALIZED, - SSAM_BASE_HUB_CONNECTED, - SSAM_BASE_HUB_DISCONNECTED, +/* Devices for Surface Laptop Studio 2. */ +static const struct software_node *ssam_node_group_sls2[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_perf_profile_with_fan, + &ssam_node_tmp_sensors, + &ssam_node_fan_speed, + &ssam_node_pos_tablet_switch, + &ssam_node_hid_sam_keyboard, + &ssam_node_hid_sam_penstash, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_ucsi, + NULL, }; -struct ssam_base_hub { - struct ssam_device *sdev; - - enum ssam_base_hub_state state; - struct delayed_work update_work; - - struct ssam_event_notifier notif; +/* 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_perf_profile, + NULL, }; -SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x0d, - .instance_id = 0x00, -}); - -#define SSAM_BAS_OPMODE_TABLET 0x00 -#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c - -static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) -{ - u8 opmode; - int status; - - status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); - if (status < 0) { - dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); - return status; - } - - if (opmode != SSAM_BAS_OPMODE_TABLET) - *state = SSAM_BASE_HUB_CONNECTED; - else - *state = SSAM_BASE_HUB_DISCONNECTED; - - return 0; -} - -static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ssam_base_hub *hub = dev_get_drvdata(dev); - bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; - - return sysfs_emit(buf, "%d\n", connected); -} - -static struct device_attribute ssam_base_hub_attr_state = - __ATTR(state, 0444, ssam_base_hub_state_show, NULL); - -static struct attribute *ssam_base_hub_attrs[] = { - &ssam_base_hub_attr_state.attr, +/* Devices for Surface Pro 7 and Surface Pro 7+. */ +static const struct software_node *ssam_node_group_sp7[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_perf_profile, NULL, }; -static const struct attribute_group ssam_base_hub_group = { - .attrs = ssam_base_hub_attrs, +/* Devices for Surface Pro 8 */ +static const struct software_node *ssam_node_group_sp8[] = { + &ssam_node_root, + &ssam_node_hub_kip, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_perf_profile, + &ssam_node_kip_tablet_switch, + &ssam_node_hid_kip_keyboard, + &ssam_node_hid_kip_penstash, + &ssam_node_hid_kip_touchpad, + &ssam_node_hid_kip_fwupd, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_ucsi, + NULL, }; -static void ssam_base_hub_update_workfn(struct work_struct *work) -{ - struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); - struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); - enum ssam_base_hub_state state; - int status = 0; - - status = ssam_base_hub_query_state(hub, &state); - if (status) - return; - - if (hub->state == state) - return; - hub->state = state; - - if (hub->state == SSAM_BASE_HUB_CONNECTED) - status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); - else - ssam_remove_clients(&hub->sdev->dev); - - if (status) - dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -} - -static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -{ - struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); - unsigned long delay; - - if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) - return 0; - - if (event->length < 1) { - dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); - return 0; - } - - /* - * Delay update when the base is being connected to give devices/EC - * some time to set up. - */ - delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; - - schedule_delayed_work(&hub->update_work, delay); - - /* - * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and - * consumed by the detachment system driver. We're just a (more or less) - * silent observer. - */ - return 0; -} - -static int __maybe_unused ssam_base_hub_resume(struct device *dev) -{ - struct ssam_base_hub *hub = dev_get_drvdata(dev); - - schedule_delayed_work(&hub->update_work, 0); - return 0; -} -static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); - -static int ssam_base_hub_probe(struct ssam_device *sdev) -{ - struct ssam_base_hub *hub; - int status; - - hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); - if (!hub) - return -ENOMEM; - - hub->sdev = sdev; - hub->state = SSAM_BASE_HUB_UNINITIALIZED; - - hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ - hub->notif.base.fn = ssam_base_hub_notif; - hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; - hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, - hub->notif.event.id.instance = 0, - hub->notif.event.mask = SSAM_EVENT_MASK_NONE; - hub->notif.event.flags = SSAM_EVENT_SEQUENCED; - - INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); - - ssam_device_set_drvdata(sdev, hub); - - status = ssam_notifier_register(sdev->ctrl, &hub->notif); - if (status) - return status; - - status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); - if (status) - goto err; - - schedule_delayed_work(&hub->update_work, 0); - return 0; - -err: - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - return status; -} - -static void ssam_base_hub_remove(struct ssam_device *sdev) -{ - struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); - - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); -} - -static const struct ssam_device_id ssam_base_hub_match[] = { - { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, - { }, +/* Devices for Surface Pro 9, 10 and 11 (Intel/x86) */ +static const struct software_node *ssam_node_group_sp9[] = { + &ssam_node_root, + &ssam_node_hub_kip, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_perf_profile_with_fan, + &ssam_node_tmp_sensors, + &ssam_node_fan_speed, + &ssam_node_pos_tablet_switch, + &ssam_node_hid_kip_keyboard, + &ssam_node_hid_kip_penstash, + &ssam_node_hid_kip_touchpad, + &ssam_node_hid_kip_fwupd, + &ssam_node_hid_sam_sensors, + &ssam_node_hid_sam_ucm_ucsi, + NULL, }; -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, - }, +/* Devices for Surface Pro 9 5G (ARM/QCOM) */ +static const struct software_node *ssam_node_group_sp9_5g[] = { + &ssam_node_root, + &ssam_node_hub_kip, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_sensors, + &ssam_node_hid_kip_keyboard, + &ssam_node_hid_kip_penstash, + &ssam_node_hid_kip_touchpad, + &ssam_node_hid_kip_fwupd, + &ssam_node_hid_sam_sensors, + &ssam_node_kip_tablet_switch, + NULL, }; - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ -static const struct acpi_device_id ssam_platform_hub_match[] = { +static const struct acpi_device_id ssam_platform_hub_acpi_match[] = { /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ { "MSHW0081", (unsigned long)ssam_node_group_gen5 }, @@ -532,6 +424,15 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Pro 8 */ { "MSHW0263", (unsigned long)ssam_node_group_sp8 }, + /* Surface Pro 9 */ + { "MSHW0343", (unsigned long)ssam_node_group_sp9 }, + + /* Surface Pro 10 */ + { "MSHW0510", (unsigned long)ssam_node_group_sp9 }, + + /* Surface Pro 11 */ + { "MSHW0583", (unsigned long)ssam_node_group_sp9 }, + /* Surface Book 2 */ { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, @@ -553,15 +454,39 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop 4 (13", Intel) */ { "MSHW0250", (unsigned long)ssam_node_group_sl3 }, + /* Surface Laptop 5 */ + { "MSHW0350", (unsigned long)ssam_node_group_sl5 }, + + /* Surface Laptop 6 */ + { "MSHW0530", (unsigned long)ssam_node_group_sl6 }, + /* Surface Laptop Go 1 */ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, - /* Surface Laptop Studio */ - { "MSHW0123", (unsigned long)ssam_node_group_sls }, + /* Surface Laptop Go 2 */ + { "MSHW0290", (unsigned long)ssam_node_group_slg1 }, + + /* Surface Laptop Go 3 */ + { "MSHW0440", (unsigned long)ssam_node_group_slg1 }, + + /* Surface Laptop Studio 1 */ + { "MSHW0123", (unsigned long)ssam_node_group_sls1 }, + + /* Surface Laptop Studio 2 */ + { "MSHW0360", (unsigned long)ssam_node_group_sls2 }, { }, }; -MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); +MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match); + +static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = { + /* Surface Pro 9 5G (ARM/QCOM) */ + { .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g }, + /* Surface Laptop 7 */ + { .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 }, + { .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 }, + { }, +}; static int ssam_platform_hub_probe(struct platform_device *pdev) { @@ -571,8 +496,11 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) int status; nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev); - if (!nodes) - return -ENODEV; + if (!nodes) { + nodes = (const struct software_node **)of_machine_get_match_data(ssam_platform_hub_of_match); + if (!nodes) + return -ENODEV; + } /* * As we're adding the SSAM client devices as children under this device @@ -597,7 +525,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) set_secondary_fwnode(&pdev->dev, root); - status = ssam_hub_register_clients(&pdev->dev, ctrl, root); + status = __ssam_register_clients(&pdev->dev, ctrl, root); if (status) { set_secondary_fwnode(&pdev->dev, NULL); software_node_unregister_node_group(nodes); @@ -607,14 +535,13 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) return status; } -static int ssam_platform_hub_remove(struct platform_device *pdev) +static void ssam_platform_hub_remove(struct platform_device *pdev) { const struct software_node **nodes = platform_get_drvdata(pdev); ssam_remove_clients(&pdev->dev); set_secondary_fwnode(&pdev->dev, NULL); software_node_unregister_node_group(nodes); - return 0; } static struct platform_driver ssam_platform_hub_driver = { @@ -622,37 +549,13 @@ static struct platform_driver ssam_platform_hub_driver = { .remove = ssam_platform_hub_remove, .driver = { .name = "surface_aggregator_platform_hub", - .acpi_match_table = ssam_platform_hub_match, + .acpi_match_table = ssam_platform_hub_acpi_match, .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, }; +module_platform_driver(ssam_platform_hub_driver); - -/* -- Module initialization. ------------------------------------------------ */ - -static int __init ssam_device_hub_init(void) -{ - int status; - - status = platform_driver_register(&ssam_platform_hub_driver); - if (status) - return status; - - status = ssam_device_driver_register(&ssam_base_hub_driver); - if (status) - platform_driver_unregister(&ssam_platform_hub_driver); - - return status; -} -module_init(ssam_device_hub_init); - -static void __exit ssam_device_hub_exit(void) -{ - ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); -} -module_exit(ssam_device_hub_exit); - +MODULE_ALIAS("platform:surface_aggregator_platform_hub"); MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c new file mode 100644 index 000000000000..ffa36ed92897 --- /dev/null +++ b/drivers/platform/surface/surface_aggregator_tabletsw.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module (SSAM) tablet mode switch driver. + * + * Copyright (C) 2022 Maximilian Luz <luzmaximilian@gmail.com> + */ + +#include <linux/unaligned.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include <linux/surface_aggregator/controller.h> +#include <linux/surface_aggregator/device.h> + + +/* -- SSAM generic tablet switch driver framework. -------------------------- */ + +struct ssam_tablet_sw; + +struct ssam_tablet_sw_state { + u32 source; + u32 state; +}; + +struct ssam_tablet_sw_ops { + int (*get_state)(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state); + const char *(*state_name)(struct ssam_tablet_sw *sw, + const struct ssam_tablet_sw_state *state); + bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, + const struct ssam_tablet_sw_state *state); +}; + +struct ssam_tablet_sw { + struct ssam_device *sdev; + + struct ssam_tablet_sw_state state; + struct work_struct update_work; + struct input_dev *mode_switch; + + struct ssam_tablet_sw_ops ops; + struct ssam_event_notifier notif; +}; + +struct ssam_tablet_sw_desc { + struct { + const char *name; + const char *phys; + } dev; + + struct { + u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); + int (*get_state)(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state); + const char *(*state_name)(struct ssam_tablet_sw *sw, + const struct ssam_tablet_sw_state *state); + bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, + const struct ssam_tablet_sw_state *state); + } ops; + + struct { + struct ssam_event_registry reg; + struct ssam_event_id id; + enum ssam_event_mask mask; + u8 flags; + } event; +}; + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ssam_tablet_sw *sw = dev_get_drvdata(dev); + const char *state = sw->ops.state_name(sw, &sw->state); + + return sysfs_emit(buf, "%s\n", state); +} +static DEVICE_ATTR_RO(state); + +static struct attribute *ssam_tablet_sw_attrs[] = { + &dev_attr_state.attr, + NULL, +}; + +static const struct attribute_group ssam_tablet_sw_group = { + .attrs = ssam_tablet_sw_attrs, +}; + +static void ssam_tablet_sw_update_workfn(struct work_struct *work) +{ + struct ssam_tablet_sw *sw = container_of(work, struct ssam_tablet_sw, update_work); + struct ssam_tablet_sw_state state; + int tablet, status; + + status = sw->ops.get_state(sw, &state); + if (status) + return; + + if (sw->state.source == state.source && sw->state.state == state.state) + return; + sw->state = state; + + /* Send SW_TABLET_MODE event. */ + tablet = sw->ops.state_is_tablet_mode(sw, &state); + input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); + input_sync(sw->mode_switch); +} + +static int __maybe_unused ssam_tablet_sw_resume(struct device *dev) +{ + struct ssam_tablet_sw *sw = dev_get_drvdata(dev); + + schedule_work(&sw->update_work); + return 0; +} +static SIMPLE_DEV_PM_OPS(ssam_tablet_sw_pm_ops, NULL, ssam_tablet_sw_resume); + +static int ssam_tablet_sw_probe(struct ssam_device *sdev) +{ + const struct ssam_tablet_sw_desc *desc; + struct ssam_tablet_sw *sw; + int tablet, status; + + desc = ssam_device_get_match_data(sdev); + if (!desc) { + WARN(1, "no driver match data specified"); + return -EINVAL; + } + + sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); + if (!sw) + return -ENOMEM; + + sw->sdev = sdev; + + sw->ops.get_state = desc->ops.get_state; + sw->ops.state_name = desc->ops.state_name; + sw->ops.state_is_tablet_mode = desc->ops.state_is_tablet_mode; + + INIT_WORK(&sw->update_work, ssam_tablet_sw_update_workfn); + + ssam_device_set_drvdata(sdev, sw); + + /* Get initial state. */ + status = sw->ops.get_state(sw, &sw->state); + if (status) + return status; + + /* Set up tablet mode switch. */ + sw->mode_switch = devm_input_allocate_device(&sdev->dev); + if (!sw->mode_switch) + return -ENOMEM; + + sw->mode_switch->name = desc->dev.name; + sw->mode_switch->phys = desc->dev.phys; + sw->mode_switch->id.bustype = BUS_HOST; + sw->mode_switch->dev.parent = &sdev->dev; + + tablet = sw->ops.state_is_tablet_mode(sw, &sw->state); + input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); + input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); + + status = input_register_device(sw->mode_switch); + if (status) + return status; + + /* Set up notifier. */ + sw->notif.base.priority = 0; + sw->notif.base.fn = desc->ops.notify; + sw->notif.event.reg = desc->event.reg; + sw->notif.event.id = desc->event.id; + sw->notif.event.mask = desc->event.mask; + sw->notif.event.flags = SSAM_EVENT_SEQUENCED; + + status = ssam_device_notifier_register(sdev, &sw->notif); + if (status) + return status; + + status = sysfs_create_group(&sdev->dev.kobj, &ssam_tablet_sw_group); + if (status) + goto err; + + /* We might have missed events during setup, so check again. */ + schedule_work(&sw->update_work); + return 0; + +err: + ssam_device_notifier_unregister(sdev, &sw->notif); + cancel_work_sync(&sw->update_work); + return status; +} + +static void ssam_tablet_sw_remove(struct ssam_device *sdev) +{ + struct ssam_tablet_sw *sw = ssam_device_get_drvdata(sdev); + + sysfs_remove_group(&sdev->dev.kobj, &ssam_tablet_sw_group); + + ssam_device_notifier_unregister(sdev, &sw->notif); + cancel_work_sync(&sw->update_work); +} + + +/* -- SSAM KIP tablet switch implementation. -------------------------------- */ + +#define SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED 0x1d + +enum ssam_kip_cover_state { + SSAM_KIP_COVER_STATE_DISCONNECTED = 0x01, + SSAM_KIP_COVER_STATE_CLOSED = 0x02, + SSAM_KIP_COVER_STATE_LAPTOP = 0x03, + SSAM_KIP_COVER_STATE_FOLDED_CANVAS = 0x04, + SSAM_KIP_COVER_STATE_FOLDED_BACK = 0x05, + SSAM_KIP_COVER_STATE_BOOK = 0x06, +}; + +static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, + const struct ssam_tablet_sw_state *state) +{ + switch (state->state) { + case SSAM_KIP_COVER_STATE_DISCONNECTED: + return "disconnected"; + + case SSAM_KIP_COVER_STATE_CLOSED: + return "closed"; + + case SSAM_KIP_COVER_STATE_LAPTOP: + return "laptop"; + + case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: + return "folded-canvas"; + + case SSAM_KIP_COVER_STATE_FOLDED_BACK: + return "folded-back"; + + case SSAM_KIP_COVER_STATE_BOOK: + return "book"; + + default: + dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state->state); + return "<unknown>"; + } +} + +static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, + const struct ssam_tablet_sw_state *state) +{ + switch (state->state) { + case SSAM_KIP_COVER_STATE_DISCONNECTED: + case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: + case SSAM_KIP_COVER_STATE_FOLDED_BACK: + case SSAM_KIP_COVER_STATE_BOOK: + return true; + + case SSAM_KIP_COVER_STATE_CLOSED: + case SSAM_KIP_COVER_STATE_LAPTOP: + return false; + + default: + dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", state->state); + return true; + } +} + +SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, { + .target_category = SSAM_SSH_TC_KIP, + .target_id = SSAM_SSH_TID_SAM, + .command_id = 0x1d, + .instance_id = 0x00, +}); + +static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state) +{ + int status; + u8 raw; + + status = ssam_retry(__ssam_kip_get_cover_state, sw->sdev->ctrl, &raw); + if (status < 0) { + dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); + return status; + } + + state->source = 0; /* Unused for KIP switch. */ + state->state = raw; + return 0; +} + +static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); + + if (event->command_id != SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED) + return 0; /* Return "unhandled". */ + + if (event->length < 1) + dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); + + schedule_work(&sw->update_work); + return SSAM_NOTIF_HANDLED; +} + +static const struct ssam_tablet_sw_desc ssam_kip_sw_desc = { + .dev = { + .name = "Microsoft Surface KIP Tablet Mode Switch", + .phys = "ssam/01:0e:01:00:01/input0", + }, + .ops = { + .notify = ssam_kip_sw_notif, + .get_state = ssam_kip_get_cover_state, + .state_name = ssam_kip_cover_state_name, + .state_is_tablet_mode = ssam_kip_cover_state_is_tablet_mode, + }, + .event = { + .reg = SSAM_EVENT_REGISTRY_SAM, + .id = { + .target_category = SSAM_SSH_TC_KIP, + .instance = 0, + }, + .mask = SSAM_EVENT_MASK_TARGET, + }, +}; + + +/* -- SSAM POS tablet switch implementation. -------------------------------- */ + +static bool tablet_mode_in_slate_state = true; +module_param(tablet_mode_in_slate_state, bool, 0644); +MODULE_PARM_DESC(tablet_mode_in_slate_state, "Enable tablet mode in slate device posture, default is 'true'"); + +#define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03 +#define SSAM_POS_MAX_SOURCES 4 + +enum ssam_pos_source_id { + SSAM_POS_SOURCE_COVER = 0x00, + SSAM_POS_SOURCE_SLS = 0x03, +}; + +enum ssam_pos_state_cover { + SSAM_POS_COVER_DISCONNECTED = 0x01, + SSAM_POS_COVER_CLOSED = 0x02, + SSAM_POS_COVER_LAPTOP = 0x03, + SSAM_POS_COVER_FOLDED_CANVAS = 0x04, + SSAM_POS_COVER_FOLDED_BACK = 0x05, + SSAM_POS_COVER_BOOK = 0x06, +}; + +enum ssam_pos_state_sls { + SSAM_POS_SLS_LID_CLOSED = 0x00, + SSAM_POS_SLS_LAPTOP = 0x01, + SSAM_POS_SLS_SLATE = 0x02, + SSAM_POS_SLS_TABLET = 0x03, +}; + +struct ssam_sources_list { + __le32 count; + __le32 id[SSAM_POS_MAX_SOURCES]; +} __packed; + +static const char *ssam_pos_state_name_cover(struct ssam_tablet_sw *sw, u32 state) +{ + switch (state) { + case SSAM_POS_COVER_DISCONNECTED: + return "disconnected"; + + case SSAM_POS_COVER_CLOSED: + return "closed"; + + case SSAM_POS_COVER_LAPTOP: + return "laptop"; + + case SSAM_POS_COVER_FOLDED_CANVAS: + return "folded-canvas"; + + case SSAM_POS_COVER_FOLDED_BACK: + return "folded-back"; + + case SSAM_POS_COVER_BOOK: + return "book"; + + default: + dev_warn(&sw->sdev->dev, "unknown device posture for type-cover: %u\n", state); + return "<unknown>"; + } +} + +static const char *ssam_pos_state_name_sls(struct ssam_tablet_sw *sw, u32 state) +{ + switch (state) { + case SSAM_POS_SLS_LID_CLOSED: + return "closed"; + + case SSAM_POS_SLS_LAPTOP: + return "laptop"; + + case SSAM_POS_SLS_SLATE: + return "slate"; + + case SSAM_POS_SLS_TABLET: + return "tablet"; + + default: + dev_warn(&sw->sdev->dev, "unknown device posture for SLS: %u\n", state); + return "<unknown>"; + } +} + +static const char *ssam_pos_state_name(struct ssam_tablet_sw *sw, + const struct ssam_tablet_sw_state *state) +{ + switch (state->source) { + case SSAM_POS_SOURCE_COVER: + return ssam_pos_state_name_cover(sw, state->state); + + case SSAM_POS_SOURCE_SLS: + return ssam_pos_state_name_sls(sw, state->state); + + default: + dev_warn(&sw->sdev->dev, "unknown device posture source: %u\n", state->source); + return "<unknown>"; + } +} + +static bool ssam_pos_state_is_tablet_mode_cover(struct ssam_tablet_sw *sw, u32 state) +{ + switch (state) { + case SSAM_POS_COVER_DISCONNECTED: + case SSAM_POS_COVER_FOLDED_CANVAS: + case SSAM_POS_COVER_FOLDED_BACK: + case SSAM_POS_COVER_BOOK: + return true; + + case SSAM_POS_COVER_CLOSED: + case SSAM_POS_COVER_LAPTOP: + return false; + + default: + dev_warn(&sw->sdev->dev, "unknown device posture for type-cover: %u\n", state); + return true; + } +} + +static bool ssam_pos_state_is_tablet_mode_sls(struct ssam_tablet_sw *sw, u32 state) +{ + switch (state) { + case SSAM_POS_SLS_LAPTOP: + case SSAM_POS_SLS_LID_CLOSED: + return false; + + case SSAM_POS_SLS_SLATE: + return tablet_mode_in_slate_state; + + case SSAM_POS_SLS_TABLET: + return true; + + default: + dev_warn(&sw->sdev->dev, "unknown device posture for SLS: %u\n", state); + return true; + } +} + +static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw, + const struct ssam_tablet_sw_state *state) +{ + switch (state->source) { + case SSAM_POS_SOURCE_COVER: + return ssam_pos_state_is_tablet_mode_cover(sw, state->state); + + case SSAM_POS_SOURCE_SLS: + return ssam_pos_state_is_tablet_mode_sls(sw, state->state); + + default: + dev_warn(&sw->sdev->dev, "unknown device posture source: %u\n", state->source); + return true; + } +} + +static int ssam_pos_get_sources_list(struct ssam_tablet_sw *sw, struct ssam_sources_list *sources) +{ + struct ssam_request rqst; + struct ssam_response rsp; + int status; + + rqst.target_category = SSAM_SSH_TC_POS; + rqst.target_id = SSAM_SSH_TID_SAM; + rqst.command_id = 0x01; + rqst.instance_id = 0x00; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; + rqst.length = 0; + rqst.payload = NULL; + + rsp.capacity = sizeof(*sources); + rsp.length = 0; + rsp.pointer = (u8 *)sources; + + status = ssam_retry(ssam_request_do_sync_onstack, sw->sdev->ctrl, &rqst, &rsp, 0); + if (status) + return status; + + /* We need at least the 'sources->count' field. */ + if (rsp.length < sizeof(__le32)) { + dev_err(&sw->sdev->dev, "received source list response is too small\n"); + return -EPROTO; + } + + /* Make sure 'sources->count' matches with the response length. */ + if (get_unaligned_le32(&sources->count) * sizeof(__le32) + sizeof(__le32) != rsp.length) { + dev_err(&sw->sdev->dev, "mismatch between number of sources and response size\n"); + return -EPROTO; + } + + return 0; +} + +static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) +{ + struct ssam_sources_list sources = {}; + int status; + + status = ssam_pos_get_sources_list(sw, &sources); + if (status) + return status; + + if (get_unaligned_le32(&sources.count) == 0) { + dev_err(&sw->sdev->dev, "no posture sources found\n"); + return -ENODEV; + } + + /* + * We currently don't know what to do with more than one posture + * source. At the moment, only one source seems to be used/provided. + * The WARN_ON() here should hopefully let us know quickly once there + * is a device that provides multiple sources, at which point we can + * then try to figure out how to handle them. + */ + WARN_ON(get_unaligned_le32(&sources.count) > 1); + + *source_id = get_unaligned_le32(&sources.id[0]); + return 0; +} + +SSAM_DEFINE_SYNC_REQUEST_WR(__ssam_pos_get_posture_for_source, __le32, __le32, { + .target_category = SSAM_SSH_TC_POS, + .target_id = SSAM_SSH_TID_SAM, + .command_id = 0x02, + .instance_id = 0x00, +}); + +static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw *sw, u32 source_id, u32 *posture) +{ + __le32 source_le = cpu_to_le32(source_id); + __le32 rspval_le = 0; + int status; + + status = ssam_retry(__ssam_pos_get_posture_for_source, sw->sdev->ctrl, + &source_le, &rspval_le); + if (status) + return status; + + *posture = le32_to_cpu(rspval_le); + return 0; +} + +static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state) +{ + u32 source_id; + u32 source_state; + int status; + + status = ssam_pos_get_source(sw, &source_id); + if (status) { + dev_err(&sw->sdev->dev, "failed to get posture source ID: %d\n", status); + return status; + } + + status = ssam_pos_get_posture_for_source(sw, source_id, &source_state); + if (status) { + dev_err(&sw->sdev->dev, "failed to get posture value for source %u: %d\n", + source_id, status); + return status; + } + + state->source = source_id; + state->state = source_state; + return 0; +} + +static u32 ssam_pos_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +{ + struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); + + if (event->command_id != SSAM_EVENT_POS_CID_POSTURE_CHANGED) + return 0; /* Return "unhandled". */ + + if (event->length != sizeof(__le32) * 3) + dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); + + schedule_work(&sw->update_work); + return SSAM_NOTIF_HANDLED; +} + +static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { + .dev = { + .name = "Microsoft Surface POS Tablet Mode Switch", + .phys = "ssam/01:26:01:00:01/input0", + }, + .ops = { + .notify = ssam_pos_sw_notif, + .get_state = ssam_pos_get_posture, + .state_name = ssam_pos_state_name, + .state_is_tablet_mode = ssam_pos_state_is_tablet_mode, + }, + .event = { + .reg = SSAM_EVENT_REGISTRY_SAM, + .id = { + .target_category = SSAM_SSH_TC_POS, + .instance = 0, + }, + .mask = SSAM_EVENT_MASK_TARGET, + }, +}; + + +/* -- Driver registration. -------------------------------------------------- */ + +static const struct ssam_device_id ssam_tablet_sw_match[] = { + { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, + { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, + { }, +}; +MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); + +static struct ssam_device_driver ssam_tablet_sw_driver = { + .probe = ssam_tablet_sw_probe, + .remove = ssam_tablet_sw_remove, + .match_table = ssam_tablet_sw_match, + .driver = { + .name = "surface_aggregator_tablet_mode_switch", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .pm = &ssam_tablet_sw_pm_ops, + }, +}; +module_ssam_device_driver(ssam_tablet_sw_driver); + +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); +MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using the Surface Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c index 1203b9a82993..97ae010069e4 100644 --- a/drivers/platform/surface/surface_dtx.c +++ b/drivers/platform/surface/surface_dtx.c @@ -8,7 +8,7 @@ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in * use), or request detachment via user-space. * - * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/fs.h> @@ -71,63 +71,63 @@ static_assert(sizeof(struct ssam_bas_base_info) == 2); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x06, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x07, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x08, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x09, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0a, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0b, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0c, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0d, .instance_id = 0x00, }); SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, + .target_id = SSAM_SSH_TID_SAM, .command_id = 0x11, .instance_id = 0x00, }); @@ -555,7 +555,6 @@ static const struct file_operations surface_dtx_fops = { .fasync = surface_dtx_fasync, .unlocked_ioctl = surface_dtx_ioctl, .compat_ioctl = surface_dtx_ioctl, - .llseek = no_llseek, }; @@ -1168,10 +1167,9 @@ static int surface_dtx_platform_probe(struct platform_device *pdev) return 0; } -static int surface_dtx_platform_remove(struct platform_device *pdev) +static void 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[] = { @@ -1214,7 +1212,7 @@ static void surface_dtx_ssam_remove(struct ssam_device *sdev) } static const struct ssam_device_id surface_dtx_ssam_match[] = { - { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, + { SSAM_SDEV(BAS, SAM, 0x00, 0x00) }, { }, }; MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index c1775db29efb..b359413903b1 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -4,7 +4,7 @@ * properly configuring the respective GPEs. Required for wakeup via lid on * newer Intel-based Microsoft Surface devices. * - * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -100,6 +100,14 @@ static const struct dmi_system_id dmi_lid_device_table[] = { .driver_data = (void *)lid_device_props_l4D, }, { + .ident = "Surface Pro 8", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"), + }, + .driver_data = (void *)lid_device_props_l4B, + }, + { .ident = "Surface Book 1", .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), @@ -164,6 +172,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { .driver_data = (void *)lid_device_props_l4D, }, { + .ident = "Surface Laptop 4 (Intel 13\")", + .matches = { + /* + * We match for SKU here due to different variants: The + * AMD (15") version does not rely on GPEs. + */ + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"), + }, + .driver_data = (void *)lid_device_props_l4B, + }, + { .ident = "Surface Laptop Studio", .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), @@ -247,15 +267,13 @@ static int surface_gpe_probe(struct platform_device *pdev) return ret; } -static int surface_gpe_remove(struct platform_device *pdev) +static void surface_gpe_remove(struct platform_device *pdev) { struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev); /* restore default behavior without this module */ surface_lid_enable_wakeup(&pdev->dev, false); acpi_disable_gpe(NULL, lid->gpe_number); - - return 0; } static struct platform_driver surface_gpe_driver = { diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c index cfcc15cfbacb..c0d83ed5a208 100644 --- a/drivers/platform/surface/surface_hotplug.c +++ b/drivers/platform/surface/surface_hotplug.c @@ -10,7 +10,7 @@ * 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-2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/acpi.h> @@ -101,18 +101,12 @@ static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type param.type = ACPI_TYPE_INTEGER; param.integer.value = value; - result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, - shps_dsm_fn_for_irq(type), ¶m); - + result = acpi_evaluate_dsm_typed(handle, &shps_dsm_guid, SHPS_DSM_REVISION, + shps_dsm_fn_for_irq(type), ¶m, ACPI_TYPE_BUFFER); if (!result) { dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", type, value); - } 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); - } 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", @@ -121,8 +115,7 @@ static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type mutex_unlock(&sdev->lock[type]); - if (result) - ACPI_FREE(result); + ACPI_FREE(result); } static irqreturn_t shps_handle_irq(int irq, void *data) @@ -190,7 +183,7 @@ static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type) return 0; } -static int surface_hotplug_remove(struct platform_device *pdev) +static void surface_hotplug_remove(struct platform_device *pdev) { struct shps_device *sdev = platform_get_drvdata(pdev); int i; @@ -202,8 +195,6 @@ static int surface_hotplug_remove(struct platform_device *pdev) mutex_destroy(&sdev->lock[i]); } - - return 0; } static int surface_hotplug_probe(struct platform_device *pdev) diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c index 6373d3b5eb7f..0e479e35e66e 100644 --- a/drivers/platform/surface/surface_platform_profile.c +++ b/drivers/platform/surface/surface_platform_profile.c @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Surface Platform Profile / Performance Mode driver for Surface System - * Aggregator Module (thermal subsystem). + * Aggregator Module (thermal and fan subsystem). * - * Copyright (C) 2021 Maximilian Luz <luzmaximilian@gmail.com> + * Copyright (C) 2021-2022 Maximilian Luz <luzmaximilian@gmail.com> */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_profile.h> @@ -14,6 +14,7 @@ #include <linux/surface_aggregator/device.h> +// Enum for the platform performance profile sent to the TMP module. enum ssam_tmp_profile { SSAM_TMP_PROFILE_NORMAL = 1, SSAM_TMP_PROFILE_BATTERY_SAVER = 2, @@ -21,15 +22,26 @@ enum ssam_tmp_profile { SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4, }; +// Enum for the fan profile sent to the FAN module. This fan profile is +// only sent to the EC if the 'has_fan' property is set. The integers are +// not a typo, they differ from the performance profile indices. +enum ssam_fan_profile { + SSAM_FAN_PROFILE_NORMAL = 2, + SSAM_FAN_PROFILE_BATTERY_SAVER = 1, + SSAM_FAN_PROFILE_BETTER_PERFORMANCE = 3, + SSAM_FAN_PROFILE_BEST_PERFORMANCE = 4, +}; + struct ssam_tmp_profile_info { __le32 profile; __le16 unknown1; __le16 unknown2; } __packed; -struct ssam_tmp_profile_device { +struct ssam_platform_profile_device { struct ssam_device *sdev; - struct platform_profile_handler handler; + struct device *ppdev; + bool has_fan; }; SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { @@ -42,6 +54,13 @@ SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { .command_id = 0x03, }); +SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, u8, { + .target_category = SSAM_SSH_TC_FAN, + .target_id = SSAM_SSH_TID_SAM, + .command_id = 0x0e, + .instance_id = 0x01, +}); + static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p) { struct ssam_tmp_profile_info info; @@ -57,12 +76,19 @@ static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p) { - __le32 profile_le = cpu_to_le32(p); + const __le32 profile_le = cpu_to_le32(p); return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le); } -static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p) +static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p) +{ + const u8 profile = p; + + return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile); +} + +static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p) { switch (p) { case SSAM_TMP_PROFILE_NORMAL: @@ -83,7 +109,8 @@ static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profi } } -static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p) + +static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p) { switch (p) { case PLATFORM_PROFILE_LOW_POWER: @@ -105,20 +132,42 @@ static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profi } } -static int ssam_platform_profile_get(struct platform_profile_handler *pprof, +static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p) +{ + switch (p) { + case PLATFORM_PROFILE_LOW_POWER: + return SSAM_FAN_PROFILE_BATTERY_SAVER; + + case PLATFORM_PROFILE_BALANCED: + return SSAM_FAN_PROFILE_NORMAL; + + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + return SSAM_FAN_PROFILE_BETTER_PERFORMANCE; + + case PLATFORM_PROFILE_PERFORMANCE: + return SSAM_FAN_PROFILE_BEST_PERFORMANCE; + + default: + /* This should have already been caught by platform_profile_store(). */ + WARN(true, "unsupported platform profile"); + return -EOPNOTSUPP; + } +} + +static int ssam_platform_profile_get(struct device *dev, enum platform_profile_option *profile) { - struct ssam_tmp_profile_device *tpd; + struct ssam_platform_profile_device *tpd; enum ssam_tmp_profile tp; int status; - tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); + tpd = dev_get_drvdata(dev); status = ssam_tmp_profile_get(tpd->sdev, &tp); if (status) return status; - status = convert_ssam_to_profile(tpd->sdev, tp); + status = convert_ssam_tmp_to_profile(tpd->sdev, tp); if (status < 0) return status; @@ -126,57 +175,75 @@ static int ssam_platform_profile_get(struct platform_profile_handler *pprof, return 0; } -static int ssam_platform_profile_set(struct platform_profile_handler *pprof, +static int ssam_platform_profile_set(struct device *dev, enum platform_profile_option profile) { - struct ssam_tmp_profile_device *tpd; + struct ssam_platform_profile_device *tpd; int tp; - tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); + tpd = dev_get_drvdata(dev); - tp = convert_profile_to_ssam(tpd->sdev, profile); + tp = convert_profile_to_ssam_tmp(tpd->sdev, profile); if (tp < 0) return tp; - return ssam_tmp_profile_set(tpd->sdev, tp); + tp = ssam_tmp_profile_set(tpd->sdev, tp); + if (tp < 0) + return tp; + + if (tpd->has_fan) { + tp = convert_profile_to_ssam_fan(tpd->sdev, profile); + if (tp < 0) + return tp; + tp = ssam_fan_profile_set(tpd->sdev, tp); + } + + return tp; } +static int ssam_platform_profile_probe(void *drvdata, unsigned long *choices) +{ + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + + return 0; +} + +static const struct platform_profile_ops ssam_platform_profile_ops = { + .probe = ssam_platform_profile_probe, + .profile_get = ssam_platform_profile_get, + .profile_set = ssam_platform_profile_set, +}; + static int surface_platform_profile_probe(struct ssam_device *sdev) { - struct ssam_tmp_profile_device *tpd; + struct ssam_platform_profile_device *tpd; tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL); if (!tpd) return -ENOMEM; tpd->sdev = sdev; + ssam_device_set_drvdata(sdev, tpd); - tpd->handler.profile_get = ssam_platform_profile_get; - tpd->handler.profile_set = ssam_platform_profile_set; + tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan"); - set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices); - set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices); - set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices); - set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices); + tpd->ppdev = devm_platform_profile_register(&sdev->dev, "Surface Platform Profile", + tpd, &ssam_platform_profile_ops); - platform_profile_register(&tpd->handler); - return 0; -} - -static void surface_platform_profile_remove(struct ssam_device *sdev) -{ - platform_profile_remove(); + return PTR_ERR_OR_ZERO(tpd->ppdev); } static const struct ssam_device_id ssam_platform_profile_match[] = { - { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, + { SSAM_SDEV(TMP, SAM, 0x00, 0x01) }, { }, }; MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match); static struct ssam_device_driver surface_platform_profile = { .probe = surface_platform_profile_probe, - .remove = surface_platform_profile_remove, .match_table = ssam_platform_profile_match, .driver = { .name = "surface_platform_profile", diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index 242fb690dcaf..2755601f979c 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -239,13 +239,12 @@ static int surface_button_add(struct acpi_device *device) return error; } -static int surface_button_remove(struct acpi_device *device) +static void surface_button_remove(struct acpi_device *device) { struct surface_button *button = acpi_driver_data(device); input_unregister_device(button->input); kfree(button); - return 0; } static SIMPLE_DEV_PM_OPS(surface_button_pm, |
