895 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Surface Serial Hub (SSH) driver for communication with the Surface/System4 * Aggregator Module (SSAM/SAM).5 *6 * Provides access to a SAM-over-SSH connected EC via a controller device.7 * Handles communication via requests as well as enabling, disabling, and8 * relaying of events.9 *10 * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>11 */12 13#include <linux/acpi.h>14#include <linux/atomic.h>15#include <linux/completion.h>16#include <linux/gpio/consumer.h>17#include <linux/kernel.h>18#include <linux/kref.h>19#include <linux/module.h>20#include <linux/of.h>21#include <linux/platform_device.h>22#include <linux/pm.h>23#include <linux/serdev.h>24#include <linux/sysfs.h>25#include <linux/units.h>26 27#include <linux/surface_aggregator/controller.h>28#include <linux/surface_aggregator/device.h>29 30#include "bus.h"31#include "controller.h"32 33#define CREATE_TRACE_POINTS34#include "trace.h"35 36 37/* -- Static controller reference. ------------------------------------------ */38 39/*40 * Main controller reference. The corresponding lock must be held while41 * accessing (reading/writing) the reference.42 */43static struct ssam_controller *__ssam_controller;44static DEFINE_SPINLOCK(__ssam_controller_lock);45 46/**47 * ssam_get_controller() - Get reference to SSAM controller.48 *49 * Returns a reference to the SSAM controller of the system or %NULL if there50 * is none, it hasn't been set up yet, or it has already been unregistered.51 * This function automatically increments the reference count of the52 * controller, thus the calling party must ensure that ssam_controller_put()53 * is called when it doesn't need the controller any more.54 */55struct ssam_controller *ssam_get_controller(void)56{57 struct ssam_controller *ctrl;58 59 spin_lock(&__ssam_controller_lock);60 61 ctrl = __ssam_controller;62 if (!ctrl)63 goto out;64 65 if (WARN_ON(!kref_get_unless_zero(&ctrl->kref)))66 ctrl = NULL;67 68out:69 spin_unlock(&__ssam_controller_lock);70 return ctrl;71}72EXPORT_SYMBOL_GPL(ssam_get_controller);73 74/**75 * ssam_try_set_controller() - Try to set the main controller reference.76 * @ctrl: The controller to which the reference should point.77 *78 * Set the main controller reference to the given pointer if the reference79 * hasn't been set already.80 *81 * Return: Returns zero on success or %-EEXIST if the reference has already82 * been set.83 */84static int ssam_try_set_controller(struct ssam_controller *ctrl)85{86 int status = 0;87 88 spin_lock(&__ssam_controller_lock);89 if (!__ssam_controller)90 __ssam_controller = ctrl;91 else92 status = -EEXIST;93 spin_unlock(&__ssam_controller_lock);94 95 return status;96}97 98/**99 * ssam_clear_controller() - Remove/clear the main controller reference.100 *101 * Clears the main controller reference, i.e. sets it to %NULL. This function102 * should be called before the controller is shut down.103 */104static void ssam_clear_controller(void)105{106 spin_lock(&__ssam_controller_lock);107 __ssam_controller = NULL;108 spin_unlock(&__ssam_controller_lock);109}110 111/**112 * ssam_client_link() - Link an arbitrary client device to the controller.113 * @c: The controller to link to.114 * @client: The client device.115 *116 * Link an arbitrary client device to the controller by creating a device link117 * between it as consumer and the controller device as provider. This function118 * can be used for non-SSAM devices (or SSAM devices not registered as child119 * under the controller) to guarantee that the controller is valid for as long120 * as the driver of the client device is bound, and that proper suspend and121 * resume ordering is guaranteed.122 *123 * The device link does not have to be destructed manually. It is removed124 * automatically once the driver of the client device unbinds.125 *126 * Return: Returns zero on success, %-ENODEV if the controller is not ready or127 * going to be removed soon, or %-ENOMEM if the device link could not be128 * created for other reasons.129 */130int ssam_client_link(struct ssam_controller *c, struct device *client)131{132 const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;133 struct device_link *link;134 struct device *ctrldev;135 136 ssam_controller_statelock(c);137 138 if (c->state != SSAM_CONTROLLER_STARTED) {139 ssam_controller_stateunlock(c);140 return -ENODEV;141 }142 143 ctrldev = ssam_controller_device(c);144 if (!ctrldev) {145 ssam_controller_stateunlock(c);146 return -ENODEV;147 }148 149 link = device_link_add(client, ctrldev, flags);150 if (!link) {151 ssam_controller_stateunlock(c);152 return -ENOMEM;153 }154 155 /*156 * Return -ENODEV if supplier driver is on its way to be removed. In157 * this case, the controller won't be around for much longer and the158 * device link is not going to save us any more, as unbinding is159 * already in progress.160 */161 if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) {162 ssam_controller_stateunlock(c);163 return -ENODEV;164 }165 166 ssam_controller_stateunlock(c);167 return 0;168}169EXPORT_SYMBOL_GPL(ssam_client_link);170 171/**172 * ssam_client_bind() - Bind an arbitrary client device to the controller.173 * @client: The client device.174 *175 * Link an arbitrary client device to the controller by creating a device link176 * between it as consumer and the main controller device as provider. This177 * function can be used for non-SSAM devices to guarantee that the controller178 * returned by this function is valid for as long as the driver of the client179 * device is bound, and that proper suspend and resume ordering is guaranteed.180 *181 * This function does essentially the same as ssam_client_link(), except that182 * it first fetches the main controller reference, then creates the link, and183 * finally returns this reference. Note that this function does not increment184 * the reference counter of the controller, as, due to the link, the185 * controller lifetime is assured as long as the driver of the client device186 * is bound.187 *188 * It is not valid to use the controller reference obtained by this method189 * outside of the driver bound to the client device at the time of calling190 * this function, without first incrementing the reference count of the191 * controller via ssam_controller_get(). Even after doing this, care must be192 * taken that requests are only submitted and notifiers are only193 * (un-)registered when the controller is active and not suspended. In other194 * words: The device link only lives as long as the client driver is bound and195 * any guarantees enforced by this link (e.g. active controller state) can196 * only be relied upon as long as this link exists and may need to be enforced197 * in other ways afterwards.198 *199 * The created device link does not have to be destructed manually. It is200 * removed automatically once the driver of the client device unbinds.201 *202 * Return: Returns the controller on success, an error pointer with %-ENODEV203 * if the controller is not present, not ready or going to be removed soon, or204 * %-ENOMEM if the device link could not be created for other reasons.205 */206struct ssam_controller *ssam_client_bind(struct device *client)207{208 struct ssam_controller *c;209 int status;210 211 c = ssam_get_controller();212 if (!c)213 return ERR_PTR(-ENODEV);214 215 status = ssam_client_link(c, client);216 217 /*218 * Note that we can drop our controller reference in both success and219 * failure cases: On success, we have bound the controller lifetime220 * inherently to the client driver lifetime, i.e. it the controller is221 * now guaranteed to outlive the client driver. On failure, we're not222 * going to use the controller any more.223 */224 ssam_controller_put(c);225 226 return status >= 0 ? c : ERR_PTR(status);227}228EXPORT_SYMBOL_GPL(ssam_client_bind);229 230 231/* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */232 233static size_t ssam_receive_buf(struct serdev_device *dev, const u8 *buf,234 size_t n)235{236 struct ssam_controller *ctrl;237 int ret;238 239 ctrl = serdev_device_get_drvdata(dev);240 ret = ssam_controller_receive_buf(ctrl, buf, n);241 242 return ret < 0 ? 0 : ret;243}244 245static void ssam_write_wakeup(struct serdev_device *dev)246{247 ssam_controller_write_wakeup(serdev_device_get_drvdata(dev));248}249 250static const struct serdev_device_ops ssam_serdev_ops = {251 .receive_buf = ssam_receive_buf,252 .write_wakeup = ssam_write_wakeup,253};254 255 256/* -- SysFS and misc. ------------------------------------------------------- */257 258static int ssam_log_firmware_version(struct ssam_controller *ctrl)259{260 u32 version, a, b, c;261 int status;262 263 status = ssam_get_firmware_version(ctrl, &version);264 if (status)265 return status;266 267 a = (version >> 24) & 0xff;268 b = ((version >> 8) & 0xffff);269 c = version & 0xff;270 271 ssam_info(ctrl, "SAM firmware version: %u.%u.%u\n", a, b, c);272 return 0;273}274 275static ssize_t firmware_version_show(struct device *dev,276 struct device_attribute *attr, char *buf)277{278 struct ssam_controller *ctrl = dev_get_drvdata(dev);279 u32 version, a, b, c;280 int status;281 282 status = ssam_get_firmware_version(ctrl, &version);283 if (status < 0)284 return status;285 286 a = (version >> 24) & 0xff;287 b = ((version >> 8) & 0xffff);288 c = version & 0xff;289 290 return sysfs_emit(buf, "%u.%u.%u\n", a, b, c);291}292static DEVICE_ATTR_RO(firmware_version);293 294static struct attribute *ssam_sam_attrs[] = {295 &dev_attr_firmware_version.attr,296 NULL297};298 299static const struct attribute_group ssam_sam_group = {300 .name = "sam",301 .attrs = ssam_sam_attrs,302};303 304 305/* -- Serial device setup. -------------------------------------------------- */306 307static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc,308 void *ctx)309{310 struct serdev_device *serdev = ctx;311 struct acpi_resource_uart_serialbus *uart;312 bool flow_control;313 int status = 0;314 315 if (!serdev_acpi_get_uart_resource(rsc, &uart))316 return AE_OK;317 318 /* Set up serdev device. */319 serdev_device_set_baudrate(serdev, uart->default_baud_rate);320 321 /* serdev currently only supports RTSCTS flow control. */322 if (uart->flow_control & (~((u8)ACPI_UART_FLOW_CONTROL_HW))) {323 dev_warn(&serdev->dev, "setup: unsupported flow control (value: %#04x)\n",324 uart->flow_control);325 }326 327 /* Set RTSCTS flow control. */328 flow_control = uart->flow_control & ACPI_UART_FLOW_CONTROL_HW;329 serdev_device_set_flow_control(serdev, flow_control);330 331 /* serdev currently only supports EVEN/ODD parity. */332 switch (uart->parity) {333 case ACPI_UART_PARITY_NONE:334 status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);335 break;336 case ACPI_UART_PARITY_EVEN:337 status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN);338 break;339 case ACPI_UART_PARITY_ODD:340 status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD);341 break;342 default:343 dev_warn(&serdev->dev, "setup: unsupported parity (value: %#04x)\n",344 uart->parity);345 break;346 }347 348 if (status) {349 dev_err(&serdev->dev, "setup: failed to set parity (value: %#04x, error: %d)\n",350 uart->parity, status);351 return AE_ERROR;352 }353 354 /* We've found the resource and are done. */355 return AE_CTRL_TERMINATE;356}357 358static int ssam_serdev_setup_via_acpi(struct serdev_device *serdev, acpi_handle handle)359{360 acpi_status status;361 362 status = acpi_walk_resources(handle, METHOD_NAME__CRS,363 ssam_serdev_setup_via_acpi_crs, serdev);364 365 return status ? -ENXIO : 0;366}367 368static int ssam_serdev_setup(struct acpi_device *ssh, struct serdev_device *serdev)369{370 if (ssh)371 return ssam_serdev_setup_via_acpi(serdev, ssh->handle);372 373 /* TODO: these values may differ per board/implementation */374 serdev_device_set_baudrate(serdev, 4 * HZ_PER_MHZ);375 serdev_device_set_flow_control(serdev, true);376 serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);377 378 return 0;379}380 381/* -- Power management. ----------------------------------------------------- */382 383static void ssam_serial_hub_shutdown(struct device *dev)384{385 struct ssam_controller *c = dev_get_drvdata(dev);386 int status;387 388 /*389 * Try to disable notifiers, signal display-off and D0-exit, ignore any390 * errors.391 *392 * Note: It has not been established yet if this is actually393 * necessary/useful for shutdown.394 */395 396 status = ssam_notifier_disable_registered(c);397 if (status) {398 ssam_err(c, "pm: failed to disable notifiers for shutdown: %d\n",399 status);400 }401 402 status = ssam_ctrl_notif_display_off(c);403 if (status)404 ssam_err(c, "pm: display-off notification failed: %d\n", status);405 406 status = ssam_ctrl_notif_d0_exit(c);407 if (status)408 ssam_err(c, "pm: D0-exit notification failed: %d\n", status);409}410 411#ifdef CONFIG_PM_SLEEP412 413static int ssam_serial_hub_pm_prepare(struct device *dev)414{415 struct ssam_controller *c = dev_get_drvdata(dev);416 int status;417 418 /*419 * Try to signal display-off, This will quiesce events.420 *421 * Note: Signaling display-off/display-on should normally be done from422 * some sort of display state notifier. As that is not available,423 * signal it here.424 */425 426 status = ssam_ctrl_notif_display_off(c);427 if (status)428 ssam_err(c, "pm: display-off notification failed: %d\n", status);429 430 return status;431}432 433static void ssam_serial_hub_pm_complete(struct device *dev)434{435 struct ssam_controller *c = dev_get_drvdata(dev);436 int status;437 438 /*439 * Try to signal display-on. This will restore events.440 *441 * Note: Signaling display-off/display-on should normally be done from442 * some sort of display state notifier. As that is not available,443 * signal it here.444 */445 446 status = ssam_ctrl_notif_display_on(c);447 if (status)448 ssam_err(c, "pm: display-on notification failed: %d\n", status);449}450 451static int ssam_serial_hub_pm_suspend(struct device *dev)452{453 struct ssam_controller *c = dev_get_drvdata(dev);454 int status;455 456 /*457 * Try to signal D0-exit, enable IRQ wakeup if specified. Abort on458 * error.459 */460 461 status = ssam_ctrl_notif_d0_exit(c);462 if (status) {463 ssam_err(c, "pm: D0-exit notification failed: %d\n", status);464 goto err_notif;465 }466 467 status = ssam_irq_arm_for_wakeup(c);468 if (status)469 goto err_irq;470 471 WARN_ON(ssam_controller_suspend(c));472 return 0;473 474err_irq:475 ssam_ctrl_notif_d0_entry(c);476err_notif:477 ssam_ctrl_notif_display_on(c);478 return status;479}480 481static int ssam_serial_hub_pm_resume(struct device *dev)482{483 struct ssam_controller *c = dev_get_drvdata(dev);484 int status;485 486 WARN_ON(ssam_controller_resume(c));487 488 /*489 * Try to disable IRQ wakeup (if specified) and signal D0-entry. In490 * case of errors, log them and try to restore normal operation state491 * as far as possible.492 *493 * Note: Signaling display-off/display-on should normally be done from494 * some sort of display state notifier. As that is not available,495 * signal it here.496 */497 498 ssam_irq_disarm_wakeup(c);499 500 status = ssam_ctrl_notif_d0_entry(c);501 if (status)502 ssam_err(c, "pm: D0-entry notification failed: %d\n", status);503 504 return 0;505}506 507static int ssam_serial_hub_pm_freeze(struct device *dev)508{509 struct ssam_controller *c = dev_get_drvdata(dev);510 int status;511 512 /*513 * During hibernation image creation, we only have to ensure that the514 * EC doesn't send us any events. This is done via the display-off515 * and D0-exit notifications. Note that this sets up the wakeup IRQ516 * on the EC side, however, we have disabled it by default on our side517 * and won't enable it here.518 *519 * See ssam_serial_hub_poweroff() for more details on the hibernation520 * process.521 */522 523 status = ssam_ctrl_notif_d0_exit(c);524 if (status) {525 ssam_err(c, "pm: D0-exit notification failed: %d\n", status);526 ssam_ctrl_notif_display_on(c);527 return status;528 }529 530 WARN_ON(ssam_controller_suspend(c));531 return 0;532}533 534static int ssam_serial_hub_pm_thaw(struct device *dev)535{536 struct ssam_controller *c = dev_get_drvdata(dev);537 int status;538 539 WARN_ON(ssam_controller_resume(c));540 541 status = ssam_ctrl_notif_d0_entry(c);542 if (status)543 ssam_err(c, "pm: D0-exit notification failed: %d\n", status);544 545 return status;546}547 548static int ssam_serial_hub_pm_poweroff(struct device *dev)549{550 struct ssam_controller *c = dev_get_drvdata(dev);551 int status;552 553 /*554 * When entering hibernation and powering off the system, the EC, at555 * least on some models, may disable events. Without us taking care of556 * that, this leads to events not being enabled/restored when the557 * system resumes from hibernation, resulting SAM-HID subsystem devices558 * (i.e. keyboard, touchpad) not working, AC-plug/AC-unplug events being559 * gone, etc.560 *561 * To avoid these issues, we disable all registered events here (this is562 * likely not actually required) and restore them during the drivers PM563 * restore callback.564 *565 * Wakeup from the EC interrupt is not supported during hibernation,566 * so don't arm the IRQ here.567 */568 569 status = ssam_notifier_disable_registered(c);570 if (status) {571 ssam_err(c, "pm: failed to disable notifiers for hibernation: %d\n",572 status);573 return status;574 }575 576 status = ssam_ctrl_notif_d0_exit(c);577 if (status) {578 ssam_err(c, "pm: D0-exit notification failed: %d\n", status);579 ssam_notifier_restore_registered(c);580 return status;581 }582 583 WARN_ON(ssam_controller_suspend(c));584 return 0;585}586 587static int ssam_serial_hub_pm_restore(struct device *dev)588{589 struct ssam_controller *c = dev_get_drvdata(dev);590 int status;591 592 /*593 * Ignore but log errors, try to restore state as much as possible in594 * case of failures. See ssam_serial_hub_poweroff() for more details on595 * the hibernation process.596 */597 598 WARN_ON(ssam_controller_resume(c));599 600 status = ssam_ctrl_notif_d0_entry(c);601 if (status)602 ssam_err(c, "pm: D0-entry notification failed: %d\n", status);603 604 ssam_notifier_restore_registered(c);605 return 0;606}607 608static const struct dev_pm_ops ssam_serial_hub_pm_ops = {609 .prepare = ssam_serial_hub_pm_prepare,610 .complete = ssam_serial_hub_pm_complete,611 .suspend = ssam_serial_hub_pm_suspend,612 .resume = ssam_serial_hub_pm_resume,613 .freeze = ssam_serial_hub_pm_freeze,614 .thaw = ssam_serial_hub_pm_thaw,615 .poweroff = ssam_serial_hub_pm_poweroff,616 .restore = ssam_serial_hub_pm_restore,617};618 619#else /* CONFIG_PM_SLEEP */620 621static const struct dev_pm_ops ssam_serial_hub_pm_ops = { };622 623#endif /* CONFIG_PM_SLEEP */624 625 626/* -- Device/driver setup. -------------------------------------------------- */627 628static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false };629static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false };630 631static const struct acpi_gpio_mapping ssam_acpi_gpios[] = {632 { "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 },633 { "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 },634 { },635};636 637static int ssam_serial_hub_probe(struct serdev_device *serdev)638{639 struct device *dev = &serdev->dev;640 struct acpi_device *ssh = ACPI_COMPANION(dev);641 struct ssam_controller *ctrl;642 int status;643 644 if (ssh) {645 status = gpiod_count(dev, NULL);646 if (status < 0)647 return dev_err_probe(dev, status, "no GPIO found\n");648 649 status = devm_acpi_dev_add_driver_gpios(dev, ssam_acpi_gpios);650 if (status)651 return status;652 }653 654 /* Allocate controller. */655 ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);656 if (!ctrl)657 return -ENOMEM;658 659 /* Initialize controller. */660 status = ssam_controller_init(ctrl, serdev);661 if (status) {662 dev_err_probe(dev, status, "failed to initialize ssam controller\n");663 goto err_ctrl_init;664 }665 666 ssam_controller_lock(ctrl);667 668 /* Set up serdev device. */669 serdev_device_set_drvdata(serdev, ctrl);670 serdev_device_set_client_ops(serdev, &ssam_serdev_ops);671 status = serdev_device_open(serdev);672 if (status) {673 dev_err_probe(dev, status, "failed to open serdev device\n");674 goto err_devopen;675 }676 677 status = ssam_serdev_setup(ssh, serdev);678 if (status) {679 status = dev_err_probe(dev, status, "failed to setup serdev\n");680 goto err_devinit;681 }682 683 /* Start controller. */684 status = ssam_controller_start(ctrl);685 if (status)686 goto err_devinit;687 688 ssam_controller_unlock(ctrl);689 690 /*691 * Initial SAM requests: Log version and notify default/init power692 * states.693 */694 status = ssam_log_firmware_version(ctrl);695 if (status) {696 dev_err_probe(dev, status, "failed to get firmware version\n");697 goto err_initrq;698 }699 700 status = ssam_ctrl_notif_d0_entry(ctrl);701 if (status) {702 dev_err_probe(dev, status, "D0-entry notification failed\n");703 goto err_initrq;704 }705 706 status = ssam_ctrl_notif_display_on(ctrl);707 if (status) {708 dev_err_probe(dev, status, "display-on notification failed\n");709 goto err_initrq;710 }711 712 status = sysfs_create_group(&dev->kobj, &ssam_sam_group);713 if (status)714 goto err_initrq;715 716 /* Set up IRQ. */717 status = ssam_irq_setup(ctrl);718 if (status) {719 dev_err_probe(dev, status, "failed to setup IRQ\n");720 goto err_irq;721 }722 723 /* Finally, set main controller reference. */724 status = ssam_try_set_controller(ctrl);725 if (WARN_ON(status)) /* Currently, we're the only provider. */726 goto err_mainref;727 728 /*729 * TODO: The EC can wake up the system via the associated GPIO interrupt730 * in multiple situations. One of which is the remaining battery731 * capacity falling below a certain threshold. Normally, we should732 * use the device_init_wakeup function, however, the EC also seems733 * to have other reasons for waking up the system and it seems734 * that Windows has additional checks whether the system should be735 * resumed. In short, this causes some spurious unwanted wake-ups.736 * For now let's thus default power/wakeup to false.737 */738 device_set_wakeup_capable(dev, true);739 740 /*741 * When using DT, we have to register the platform hub driver manually,742 * as it can't be matched based on top-level board compatible (like it743 * does the ACPI case).744 */745 if (!ssh) {746 struct platform_device *ph_pdev =747 platform_device_register_simple("surface_aggregator_platform_hub",748 0, NULL, 0);749 if (IS_ERR(ph_pdev))750 return dev_err_probe(dev, PTR_ERR(ph_pdev),751 "Failed to register the platform hub driver\n");752 }753 754 if (ssh)755 acpi_dev_clear_dependencies(ssh);756 757 return 0;758 759err_mainref:760 ssam_irq_free(ctrl);761err_irq:762 sysfs_remove_group(&dev->kobj, &ssam_sam_group);763err_initrq:764 ssam_controller_lock(ctrl);765 ssam_controller_shutdown(ctrl);766err_devinit:767 serdev_device_close(serdev);768err_devopen:769 ssam_controller_destroy(ctrl);770 ssam_controller_unlock(ctrl);771err_ctrl_init:772 kfree(ctrl);773 return status;774}775 776static void ssam_serial_hub_remove(struct serdev_device *serdev)777{778 struct ssam_controller *ctrl = serdev_device_get_drvdata(serdev);779 int status;780 781 /* Clear static reference so that no one else can get a new one. */782 ssam_clear_controller();783 784 /* Disable and free IRQ. */785 ssam_irq_free(ctrl);786 787 sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group);788 ssam_controller_lock(ctrl);789 790 /* Remove all client devices. */791 ssam_remove_clients(&serdev->dev);792 793 /* Act as if suspending to silence events. */794 status = ssam_ctrl_notif_display_off(ctrl);795 if (status) {796 dev_err(&serdev->dev, "display-off notification failed: %d\n",797 status);798 }799 800 status = ssam_ctrl_notif_d0_exit(ctrl);801 if (status) {802 dev_err(&serdev->dev, "D0-exit notification failed: %d\n",803 status);804 }805 806 /* Shut down controller and remove serdev device reference from it. */807 ssam_controller_shutdown(ctrl);808 809 /* Shut down actual transport. */810 serdev_device_wait_until_sent(serdev, 0);811 serdev_device_close(serdev);812 813 /* Drop our controller reference. */814 ssam_controller_unlock(ctrl);815 ssam_controller_put(ctrl);816 817 device_set_wakeup_capable(&serdev->dev, false);818}819 820static const struct acpi_device_id ssam_serial_hub_acpi_match[] = {821 { "MSHW0084", 0 },822 { },823};824MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_acpi_match);825 826#ifdef CONFIG_OF827static const struct of_device_id ssam_serial_hub_of_match[] = {828 { .compatible = "microsoft,surface-sam", },829 { },830};831MODULE_DEVICE_TABLE(of, ssam_serial_hub_of_match);832#endif833 834static struct serdev_device_driver ssam_serial_hub = {835 .probe = ssam_serial_hub_probe,836 .remove = ssam_serial_hub_remove,837 .driver = {838 .name = "surface_serial_hub",839 .acpi_match_table = ACPI_PTR(ssam_serial_hub_acpi_match),840 .of_match_table = of_match_ptr(ssam_serial_hub_of_match),841 .pm = &ssam_serial_hub_pm_ops,842 .shutdown = ssam_serial_hub_shutdown,843 .probe_type = PROBE_PREFER_ASYNCHRONOUS,844 },845};846 847 848/* -- Module setup. --------------------------------------------------------- */849 850static int __init ssam_core_init(void)851{852 int status;853 854 status = ssam_bus_register();855 if (status)856 goto err_bus;857 858 status = ssh_ctrl_packet_cache_init();859 if (status)860 goto err_cpkg;861 862 status = ssam_event_item_cache_init();863 if (status)864 goto err_evitem;865 866 status = serdev_device_driver_register(&ssam_serial_hub);867 if (status)868 goto err_register;869 870 return 0;871 872err_register:873 ssam_event_item_cache_destroy();874err_evitem:875 ssh_ctrl_packet_cache_destroy();876err_cpkg:877 ssam_bus_unregister();878err_bus:879 return status;880}881subsys_initcall(ssam_core_init);882 883static void __exit ssam_core_exit(void)884{885 serdev_device_driver_unregister(&ssam_serial_hub);886 ssam_event_item_cache_destroy();887 ssh_ctrl_packet_cache_destroy();888 ssam_bus_unregister();889}890module_exit(ssam_core_exit);891 892MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");893MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module");894MODULE_LICENSE("GPL");895