linux/drivers/platform/surface/surface_aggregator_registry.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Surface System Aggregator Module (SSAM) client device registry.
   4 *
   5 * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that
   6 * cannot be auto-detected. Provides device-hubs and performs instantiation
   7 * for these devices.
   8 *
   9 * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
  10 */
  11
  12#include <linux/acpi.h>
  13#include <linux/kernel.h>
  14#include <linux/limits.h>
  15#include <linux/module.h>
  16#include <linux/platform_device.h>
  17#include <linux/property.h>
  18#include <linux/types.h>
  19#include <linux/workqueue.h>
  20
  21#include <linux/surface_aggregator/controller.h>
  22#include <linux/surface_aggregator/device.h>
  23
  24
  25/* -- Device registry. ------------------------------------------------------ */
  26
  27/*
  28 * SSAM device names follow the SSAM module alias, meaning they are prefixed
  29 * with 'ssam:', followed by domain, category, target ID, instance ID, and
  30 * function, each encoded as two-digit hexadecimal, separated by ':'. In other
  31 * words, it follows the scheme
  32 *
  33 *      ssam:dd:cc:tt:ii:ff
  34 *
  35 * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal
  36 * values mentioned above, respectively.
  37 */
  38
  39/* Root node. */
  40static const struct software_node ssam_node_root = {
  41        .name = "ssam_platform_hub",
  42};
  43
  44/* Base device hub (devices attached to Surface Book 3 base). */
  45static const struct software_node ssam_node_hub_base = {
  46        .name = "ssam:00:00:02:00:00",
  47        .parent = &ssam_node_root,
  48};
  49
  50/* AC adapter. */
  51static const struct software_node ssam_node_bat_ac = {
  52        .name = "ssam:01:02:01:01:01",
  53        .parent = &ssam_node_root,
  54};
  55
  56/* Primary battery. */
  57static const struct software_node ssam_node_bat_main = {
  58        .name = "ssam:01:02:01:01:00",
  59        .parent = &ssam_node_root,
  60};
  61
  62/* Secondary battery (Surface Book 3). */
  63static const struct software_node ssam_node_bat_sb3base = {
  64        .name = "ssam:01:02:02:01:00",
  65        .parent = &ssam_node_hub_base,
  66};
  67
  68/* Platform profile / performance-mode device. */
  69static const struct software_node ssam_node_tmp_pprof = {
  70        .name = "ssam:01:03:01:00:01",
  71        .parent = &ssam_node_root,
  72};
  73
  74/* DTX / detachment-system device (Surface Book 3). */
  75static const struct software_node ssam_node_bas_dtx = {
  76        .name = "ssam:01:11:01:00:00",
  77        .parent = &ssam_node_root,
  78};
  79
  80/* HID keyboard. */
  81static const struct software_node ssam_node_hid_main_keyboard = {
  82        .name = "ssam:01:15:02:01:00",
  83        .parent = &ssam_node_root,
  84};
  85
  86/* HID touchpad. */
  87static const struct software_node ssam_node_hid_main_touchpad = {
  88        .name = "ssam:01:15:02:03:00",
  89        .parent = &ssam_node_root,
  90};
  91
  92/* HID device instance 5 (unknown HID device). */
  93static const struct software_node ssam_node_hid_main_iid5 = {
  94        .name = "ssam:01:15:02:05:00",
  95        .parent = &ssam_node_root,
  96};
  97
  98/* HID keyboard (base hub). */
  99static const struct software_node ssam_node_hid_base_keyboard = {
 100        .name = "ssam:01:15:02:01:00",
 101        .parent = &ssam_node_hub_base,
 102};
 103
 104/* HID touchpad (base hub). */
 105static const struct software_node ssam_node_hid_base_touchpad = {
 106        .name = "ssam:01:15:02:03:00",
 107        .parent = &ssam_node_hub_base,
 108};
 109
 110/* HID device instance 5 (unknown HID device, base hub). */
 111static const struct software_node ssam_node_hid_base_iid5 = {
 112        .name = "ssam:01:15:02:05:00",
 113        .parent = &ssam_node_hub_base,
 114};
 115
 116/* HID device instance 6 (unknown HID device, base hub). */
 117static const struct software_node ssam_node_hid_base_iid6 = {
 118        .name = "ssam:01:15:02:06:00",
 119        .parent = &ssam_node_hub_base,
 120};
 121
 122/* Devices for Surface Book 2. */
 123static const struct software_node *ssam_node_group_sb2[] = {
 124        &ssam_node_root,
 125        &ssam_node_tmp_pprof,
 126        NULL,
 127};
 128
 129/* Devices for Surface Book 3. */
 130static const struct software_node *ssam_node_group_sb3[] = {
 131        &ssam_node_root,
 132        &ssam_node_hub_base,
 133        &ssam_node_bat_ac,
 134        &ssam_node_bat_main,
 135        &ssam_node_bat_sb3base,
 136        &ssam_node_tmp_pprof,
 137        &ssam_node_bas_dtx,
 138        &ssam_node_hid_base_keyboard,
 139        &ssam_node_hid_base_touchpad,
 140        &ssam_node_hid_base_iid5,
 141        &ssam_node_hid_base_iid6,
 142        NULL,
 143};
 144
 145/* Devices for Surface Laptop 1. */
 146static const struct software_node *ssam_node_group_sl1[] = {
 147        &ssam_node_root,
 148        &ssam_node_tmp_pprof,
 149        NULL,
 150};
 151
 152/* Devices for Surface Laptop 2. */
 153static const struct software_node *ssam_node_group_sl2[] = {
 154        &ssam_node_root,
 155        &ssam_node_tmp_pprof,
 156        NULL,
 157};
 158
 159/* Devices for Surface Laptop 3 and 4. */
 160static const struct software_node *ssam_node_group_sl3[] = {
 161        &ssam_node_root,
 162        &ssam_node_bat_ac,
 163        &ssam_node_bat_main,
 164        &ssam_node_tmp_pprof,
 165        &ssam_node_hid_main_keyboard,
 166        &ssam_node_hid_main_touchpad,
 167        &ssam_node_hid_main_iid5,
 168        NULL,
 169};
 170
 171/* Devices for Surface Laptop Go. */
 172static const struct software_node *ssam_node_group_slg1[] = {
 173        &ssam_node_root,
 174        &ssam_node_bat_ac,
 175        &ssam_node_bat_main,
 176        &ssam_node_tmp_pprof,
 177        NULL,
 178};
 179
 180/* Devices for Surface Pro 5. */
 181static const struct software_node *ssam_node_group_sp5[] = {
 182        &ssam_node_root,
 183        &ssam_node_tmp_pprof,
 184        NULL,
 185};
 186
 187/* Devices for Surface Pro 6. */
 188static const struct software_node *ssam_node_group_sp6[] = {
 189        &ssam_node_root,
 190        &ssam_node_tmp_pprof,
 191        NULL,
 192};
 193
 194/* Devices for Surface Pro 7 and Surface Pro 7+. */
 195static const struct software_node *ssam_node_group_sp7[] = {
 196        &ssam_node_root,
 197        &ssam_node_bat_ac,
 198        &ssam_node_bat_main,
 199        &ssam_node_tmp_pprof,
 200        NULL,
 201};
 202
 203
 204/* -- Device registry helper functions. ------------------------------------- */
 205
 206static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
 207{
 208        u8 d, tc, tid, iid, fn;
 209        int n;
 210
 211        n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
 212        if (n != 5)
 213                return -EINVAL;
 214
 215        uid->domain = d;
 216        uid->category = tc;
 217        uid->target = tid;
 218        uid->instance = iid;
 219        uid->function = fn;
 220
 221        return 0;
 222}
 223
 224static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
 225{
 226        if (!is_ssam_device(dev))
 227                return 0;
 228
 229        ssam_device_remove(to_ssam_device(dev));
 230        return 0;
 231}
 232
 233static void ssam_hub_remove_devices(struct device *parent)
 234{
 235        device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
 236}
 237
 238static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
 239                               struct fwnode_handle *node)
 240{
 241        struct ssam_device_uid uid;
 242        struct ssam_device *sdev;
 243        int status;
 244
 245        status = ssam_uid_from_string(fwnode_get_name(node), &uid);
 246        if (status)
 247                return status;
 248
 249        sdev = ssam_device_alloc(ctrl, uid);
 250        if (!sdev)
 251                return -ENOMEM;
 252
 253        sdev->dev.parent = parent;
 254        sdev->dev.fwnode = node;
 255
 256        status = ssam_device_add(sdev);
 257        if (status)
 258                ssam_device_put(sdev);
 259
 260        return status;
 261}
 262
 263static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
 264                                struct fwnode_handle *node)
 265{
 266        struct fwnode_handle *child;
 267        int status;
 268
 269        fwnode_for_each_child_node(node, child) {
 270                /*
 271                 * Try to add the device specified in the firmware node. If
 272                 * this fails with -EINVAL, the node does not specify any SSAM
 273                 * device, so ignore it and continue with the next one.
 274                 */
 275
 276                status = ssam_hub_add_device(parent, ctrl, child);
 277                if (status && status != -EINVAL)
 278                        goto err;
 279        }
 280
 281        return 0;
 282err:
 283        ssam_hub_remove_devices(parent);
 284        return status;
 285}
 286
 287
 288/* -- SSAM base-hub driver. ------------------------------------------------- */
 289
 290/*
 291 * Some devices (especially battery) may need a bit of time to be fully usable
 292 * after being (re-)connected. This delay has been determined via
 293 * experimentation.
 294 */
 295#define SSAM_BASE_UPDATE_CONNECT_DELAY          msecs_to_jiffies(2500)
 296
 297enum ssam_base_hub_state {
 298        SSAM_BASE_HUB_UNINITIALIZED,
 299        SSAM_BASE_HUB_CONNECTED,
 300        SSAM_BASE_HUB_DISCONNECTED,
 301};
 302
 303struct ssam_base_hub {
 304        struct ssam_device *sdev;
 305
 306        enum ssam_base_hub_state state;
 307        struct delayed_work update_work;
 308
 309        struct ssam_event_notifier notif;
 310};
 311
 312SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
 313        .target_category = SSAM_SSH_TC_BAS,
 314        .target_id       = 0x01,
 315        .command_id      = 0x0d,
 316        .instance_id     = 0x00,
 317});
 318
 319#define SSAM_BAS_OPMODE_TABLET          0x00
 320#define SSAM_EVENT_BAS_CID_CONNECTION   0x0c
 321
 322static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state)
 323{
 324        u8 opmode;
 325        int status;
 326
 327        status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
 328        if (status < 0) {
 329                dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
 330                return status;
 331        }
 332
 333        if (opmode != SSAM_BAS_OPMODE_TABLET)
 334                *state = SSAM_BASE_HUB_CONNECTED;
 335        else
 336                *state = SSAM_BASE_HUB_DISCONNECTED;
 337
 338        return 0;
 339}
 340
 341static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr,
 342                                        char *buf)
 343{
 344        struct ssam_base_hub *hub = dev_get_drvdata(dev);
 345        bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
 346
 347        return sysfs_emit(buf, "%d\n", connected);
 348}
 349
 350static struct device_attribute ssam_base_hub_attr_state =
 351        __ATTR(state, 0444, ssam_base_hub_state_show, NULL);
 352
 353static struct attribute *ssam_base_hub_attrs[] = {
 354        &ssam_base_hub_attr_state.attr,
 355        NULL,
 356};
 357
 358static const struct attribute_group ssam_base_hub_group = {
 359        .attrs = ssam_base_hub_attrs,
 360};
 361
 362static void ssam_base_hub_update_workfn(struct work_struct *work)
 363{
 364        struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work);
 365        struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
 366        enum ssam_base_hub_state state;
 367        int status = 0;
 368
 369        status = ssam_base_hub_query_state(hub, &state);
 370        if (status)
 371                return;
 372
 373        if (hub->state == state)
 374                return;
 375        hub->state = state;
 376
 377        if (hub->state == SSAM_BASE_HUB_CONNECTED)
 378                status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
 379        else
 380                ssam_hub_remove_devices(&hub->sdev->dev);
 381
 382        if (status)
 383                dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
 384}
 385
 386static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
 387{
 388        struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif);
 389        unsigned long delay;
 390
 391        if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
 392                return 0;
 393
 394        if (event->length < 1) {
 395                dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
 396                return 0;
 397        }
 398
 399        /*
 400         * Delay update when the base is being connected to give devices/EC
 401         * some time to set up.
 402         */
 403        delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0;
 404
 405        schedule_delayed_work(&hub->update_work, delay);
 406
 407        /*
 408         * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
 409         * consumed by the detachment system driver. We're just a (more or less)
 410         * silent observer.
 411         */
 412        return 0;
 413}
 414
 415static int __maybe_unused ssam_base_hub_resume(struct device *dev)
 416{
 417        struct ssam_base_hub *hub = dev_get_drvdata(dev);
 418
 419        schedule_delayed_work(&hub->update_work, 0);
 420        return 0;
 421}
 422static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
 423
 424static int ssam_base_hub_probe(struct ssam_device *sdev)
 425{
 426        struct ssam_base_hub *hub;
 427        int status;
 428
 429        hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
 430        if (!hub)
 431                return -ENOMEM;
 432
 433        hub->sdev = sdev;
 434        hub->state = SSAM_BASE_HUB_UNINITIALIZED;
 435
 436        hub->notif.base.priority = INT_MAX;  /* This notifier should run first. */
 437        hub->notif.base.fn = ssam_base_hub_notif;
 438        hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
 439        hub->notif.event.id.target_category = SSAM_SSH_TC_BAS,
 440        hub->notif.event.id.instance = 0,
 441        hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
 442        hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
 443
 444        INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn);
 445
 446        ssam_device_set_drvdata(sdev, hub);
 447
 448        status = ssam_notifier_register(sdev->ctrl, &hub->notif);
 449        if (status)
 450                return status;
 451
 452        status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
 453        if (status)
 454                goto err;
 455
 456        schedule_delayed_work(&hub->update_work, 0);
 457        return 0;
 458
 459err:
 460        ssam_notifier_unregister(sdev->ctrl, &hub->notif);
 461        cancel_delayed_work_sync(&hub->update_work);
 462        ssam_hub_remove_devices(&sdev->dev);
 463        return status;
 464}
 465
 466static void ssam_base_hub_remove(struct ssam_device *sdev)
 467{
 468        struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev);
 469
 470        sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
 471
 472        ssam_notifier_unregister(sdev->ctrl, &hub->notif);
 473        cancel_delayed_work_sync(&hub->update_work);
 474        ssam_hub_remove_devices(&sdev->dev);
 475}
 476
 477static const struct ssam_device_id ssam_base_hub_match[] = {
 478        { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
 479        { },
 480};
 481
 482static struct ssam_device_driver ssam_base_hub_driver = {
 483        .probe = ssam_base_hub_probe,
 484        .remove = ssam_base_hub_remove,
 485        .match_table = ssam_base_hub_match,
 486        .driver = {
 487                .name = "surface_aggregator_base_hub",
 488                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
 489                .pm = &ssam_base_hub_pm_ops,
 490        },
 491};
 492
 493
 494/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
 495
 496static const struct acpi_device_id ssam_platform_hub_match[] = {
 497        /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
 498        { "MSHW0081", (unsigned long)ssam_node_group_sp5 },
 499
 500        /* Surface Pro 6 (OMBR >= 0x10) */
 501        { "MSHW0111", (unsigned long)ssam_node_group_sp6 },
 502
 503        /* Surface Pro 7 */
 504        { "MSHW0116", (unsigned long)ssam_node_group_sp7 },
 505
 506        /* Surface Pro 7+ */
 507        { "MSHW0119", (unsigned long)ssam_node_group_sp7 },
 508
 509        /* Surface Book 2 */
 510        { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
 511
 512        /* Surface Book 3 */
 513        { "MSHW0117", (unsigned long)ssam_node_group_sb3 },
 514
 515        /* Surface Laptop 1 */
 516        { "MSHW0086", (unsigned long)ssam_node_group_sl1 },
 517
 518        /* Surface Laptop 2 */
 519        { "MSHW0112", (unsigned long)ssam_node_group_sl2 },
 520
 521        /* Surface Laptop 3 (13", Intel) */
 522        { "MSHW0114", (unsigned long)ssam_node_group_sl3 },
 523
 524        /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */
 525        { "MSHW0110", (unsigned long)ssam_node_group_sl3 },
 526
 527        /* Surface Laptop 4 (13", Intel) */
 528        { "MSHW0250", (unsigned long)ssam_node_group_sl3 },
 529
 530        /* Surface Laptop Go 1 */
 531        { "MSHW0118", (unsigned long)ssam_node_group_slg1 },
 532
 533        { },
 534};
 535MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match);
 536
 537static int ssam_platform_hub_probe(struct platform_device *pdev)
 538{
 539        const struct software_node **nodes;
 540        struct ssam_controller *ctrl;
 541        struct fwnode_handle *root;
 542        int status;
 543
 544        nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
 545        if (!nodes)
 546                return -ENODEV;
 547
 548        /*
 549         * As we're adding the SSAM client devices as children under this device
 550         * and not the SSAM controller, we need to add a device link to the
 551         * controller to ensure that we remove all of our devices before the
 552         * controller is removed. This also guarantees proper ordering for
 553         * suspend/resume of the devices on this hub.
 554         */
 555        ctrl = ssam_client_bind(&pdev->dev);
 556        if (IS_ERR(ctrl))
 557                return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
 558
 559        status = software_node_register_node_group(nodes);
 560        if (status)
 561                return status;
 562
 563        root = software_node_fwnode(&ssam_node_root);
 564        if (!root) {
 565                software_node_unregister_node_group(nodes);
 566                return -ENOENT;
 567        }
 568
 569        set_secondary_fwnode(&pdev->dev, root);
 570
 571        status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
 572        if (status) {
 573                set_secondary_fwnode(&pdev->dev, NULL);
 574                software_node_unregister_node_group(nodes);
 575        }
 576
 577        platform_set_drvdata(pdev, nodes);
 578        return status;
 579}
 580
 581static int ssam_platform_hub_remove(struct platform_device *pdev)
 582{
 583        const struct software_node **nodes = platform_get_drvdata(pdev);
 584
 585        ssam_hub_remove_devices(&pdev->dev);
 586        set_secondary_fwnode(&pdev->dev, NULL);
 587        software_node_unregister_node_group(nodes);
 588        return 0;
 589}
 590
 591static struct platform_driver ssam_platform_hub_driver = {
 592        .probe = ssam_platform_hub_probe,
 593        .remove = ssam_platform_hub_remove,
 594        .driver = {
 595                .name = "surface_aggregator_platform_hub",
 596                .acpi_match_table = ssam_platform_hub_match,
 597                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
 598        },
 599};
 600
 601
 602/* -- Module initialization. ------------------------------------------------ */
 603
 604static int __init ssam_device_hub_init(void)
 605{
 606        int status;
 607
 608        status = platform_driver_register(&ssam_platform_hub_driver);
 609        if (status)
 610                return status;
 611
 612        status = ssam_device_driver_register(&ssam_base_hub_driver);
 613        if (status)
 614                platform_driver_unregister(&ssam_platform_hub_driver);
 615
 616        return status;
 617}
 618module_init(ssam_device_hub_init);
 619
 620static void __exit ssam_device_hub_exit(void)
 621{
 622        ssam_device_driver_unregister(&ssam_base_hub_driver);
 623        platform_driver_unregister(&ssam_platform_hub_driver);
 624}
 625module_exit(ssam_device_hub_exit);
 626
 627MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
 628MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
 629MODULE_LICENSE("GPL");
 630