1993a9e2aSMaximilian Luz // SPDX-License-Identifier: GPL-2.0+ 2993a9e2aSMaximilian Luz /* 3993a9e2aSMaximilian Luz * Driver for Surface System Aggregator Module (SSAM) subsystem device hubs. 4993a9e2aSMaximilian Luz * 5993a9e2aSMaximilian Luz * Provides a driver for SSAM subsystems device hubs. This driver performs 6993a9e2aSMaximilian Luz * instantiation of the devices managed by said hubs and takes care of 7993a9e2aSMaximilian Luz * (hot-)removal. 8993a9e2aSMaximilian Luz * 9993a9e2aSMaximilian Luz * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com> 10993a9e2aSMaximilian Luz */ 11993a9e2aSMaximilian Luz 12993a9e2aSMaximilian Luz #include <linux/kernel.h> 13993a9e2aSMaximilian Luz #include <linux/limits.h> 14993a9e2aSMaximilian Luz #include <linux/module.h> 15993a9e2aSMaximilian Luz #include <linux/types.h> 16993a9e2aSMaximilian Luz #include <linux/workqueue.h> 17993a9e2aSMaximilian Luz 18993a9e2aSMaximilian Luz #include <linux/surface_aggregator/device.h> 19993a9e2aSMaximilian Luz 20993a9e2aSMaximilian Luz 21993a9e2aSMaximilian Luz /* -- SSAM generic subsystem hub driver framework. -------------------------- */ 22993a9e2aSMaximilian Luz 23993a9e2aSMaximilian Luz enum ssam_hub_state { 24993a9e2aSMaximilian Luz SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ 25993a9e2aSMaximilian Luz SSAM_HUB_CONNECTED, 26993a9e2aSMaximilian Luz SSAM_HUB_DISCONNECTED, 27993a9e2aSMaximilian Luz }; 28993a9e2aSMaximilian Luz 29993a9e2aSMaximilian Luz enum ssam_hub_flags { 30993a9e2aSMaximilian Luz SSAM_HUB_HOT_REMOVED, 31993a9e2aSMaximilian Luz }; 32993a9e2aSMaximilian Luz 33993a9e2aSMaximilian Luz struct ssam_hub; 34993a9e2aSMaximilian Luz 35993a9e2aSMaximilian Luz struct ssam_hub_ops { 36993a9e2aSMaximilian Luz int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); 37993a9e2aSMaximilian Luz }; 38993a9e2aSMaximilian Luz 39993a9e2aSMaximilian Luz struct ssam_hub { 40993a9e2aSMaximilian Luz struct ssam_device *sdev; 41993a9e2aSMaximilian Luz 42993a9e2aSMaximilian Luz enum ssam_hub_state state; 43993a9e2aSMaximilian Luz unsigned long flags; 44993a9e2aSMaximilian Luz 45993a9e2aSMaximilian Luz struct delayed_work update_work; 46993a9e2aSMaximilian Luz unsigned long connect_delay; 47993a9e2aSMaximilian Luz 48993a9e2aSMaximilian Luz struct ssam_event_notifier notif; 49993a9e2aSMaximilian Luz struct ssam_hub_ops ops; 50993a9e2aSMaximilian Luz }; 51993a9e2aSMaximilian Luz 52993a9e2aSMaximilian Luz struct ssam_hub_desc { 53993a9e2aSMaximilian Luz struct { 54993a9e2aSMaximilian Luz struct ssam_event_registry reg; 55993a9e2aSMaximilian Luz struct ssam_event_id id; 56993a9e2aSMaximilian Luz enum ssam_event_mask mask; 57993a9e2aSMaximilian Luz } event; 58993a9e2aSMaximilian Luz 59993a9e2aSMaximilian Luz struct { 60993a9e2aSMaximilian Luz u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); 61993a9e2aSMaximilian Luz int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); 62993a9e2aSMaximilian Luz } ops; 63993a9e2aSMaximilian Luz 64993a9e2aSMaximilian Luz unsigned long connect_delay_ms; 65993a9e2aSMaximilian Luz }; 66993a9e2aSMaximilian Luz 67993a9e2aSMaximilian Luz static void ssam_hub_update_workfn(struct work_struct *work) 68993a9e2aSMaximilian Luz { 69993a9e2aSMaximilian Luz struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); 70993a9e2aSMaximilian Luz enum ssam_hub_state state; 71993a9e2aSMaximilian Luz int status = 0; 72993a9e2aSMaximilian Luz 73993a9e2aSMaximilian Luz status = hub->ops.get_state(hub, &state); 74993a9e2aSMaximilian Luz if (status) 75993a9e2aSMaximilian Luz return; 76993a9e2aSMaximilian Luz 77993a9e2aSMaximilian Luz /* 78993a9e2aSMaximilian Luz * There is a small possibility that hub devices were hot-removed and 79993a9e2aSMaximilian Luz * re-added before we were able to remove them here. In that case, both 80993a9e2aSMaximilian Luz * the state returned by get_state() and the state of the hub will 81993a9e2aSMaximilian Luz * equal SSAM_HUB_CONNECTED and we would bail early below, which would 82993a9e2aSMaximilian Luz * leave child devices without proper (re-)initialization and the 83993a9e2aSMaximilian Luz * hot-remove flag set. 84993a9e2aSMaximilian Luz * 85993a9e2aSMaximilian Luz * Therefore, we check whether devices have been hot-removed via an 86993a9e2aSMaximilian Luz * additional flag on the hub and, in this case, override the returned 87993a9e2aSMaximilian Luz * hub state. In case of a missed disconnect (i.e. get_state returned 88993a9e2aSMaximilian Luz * "connected"), we further need to re-schedule this work (with the 89993a9e2aSMaximilian Luz * appropriate delay) as the actual connect work submission might have 90993a9e2aSMaximilian Luz * been merged with this one. 91993a9e2aSMaximilian Luz * 92993a9e2aSMaximilian Luz * This then leads to one of two cases: Either we submit an unnecessary 93993a9e2aSMaximilian Luz * work item (which will get ignored via either the queue or the state 94993a9e2aSMaximilian Luz * checks) or, in the unlikely case that the work is actually required, 95993a9e2aSMaximilian Luz * double the normal connect delay. 96993a9e2aSMaximilian Luz */ 97993a9e2aSMaximilian Luz if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { 98993a9e2aSMaximilian Luz if (state == SSAM_HUB_CONNECTED) 99993a9e2aSMaximilian Luz schedule_delayed_work(&hub->update_work, hub->connect_delay); 100993a9e2aSMaximilian Luz 101993a9e2aSMaximilian Luz state = SSAM_HUB_DISCONNECTED; 102993a9e2aSMaximilian Luz } 103993a9e2aSMaximilian Luz 104993a9e2aSMaximilian Luz if (hub->state == state) 105993a9e2aSMaximilian Luz return; 106993a9e2aSMaximilian Luz hub->state = state; 107993a9e2aSMaximilian Luz 108993a9e2aSMaximilian Luz if (hub->state == SSAM_HUB_CONNECTED) 109993a9e2aSMaximilian Luz status = ssam_device_register_clients(hub->sdev); 110993a9e2aSMaximilian Luz else 111993a9e2aSMaximilian Luz ssam_remove_clients(&hub->sdev->dev); 112993a9e2aSMaximilian Luz 113993a9e2aSMaximilian Luz if (status) 114993a9e2aSMaximilian Luz dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); 115993a9e2aSMaximilian Luz } 116993a9e2aSMaximilian Luz 117993a9e2aSMaximilian Luz static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) 118993a9e2aSMaximilian Luz { 119993a9e2aSMaximilian Luz struct ssam_device *sdev = to_ssam_device(dev); 120993a9e2aSMaximilian Luz 121993a9e2aSMaximilian Luz if (is_ssam_device(dev)) 122993a9e2aSMaximilian Luz ssam_device_mark_hot_removed(sdev); 123993a9e2aSMaximilian Luz 124993a9e2aSMaximilian Luz return 0; 125993a9e2aSMaximilian Luz } 126993a9e2aSMaximilian Luz 127993a9e2aSMaximilian Luz static void ssam_hub_update(struct ssam_hub *hub, bool connected) 128993a9e2aSMaximilian Luz { 129993a9e2aSMaximilian Luz unsigned long delay; 130993a9e2aSMaximilian Luz 131993a9e2aSMaximilian Luz /* Mark devices as hot-removed before we remove any. */ 132993a9e2aSMaximilian Luz if (!connected) { 133993a9e2aSMaximilian Luz set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); 134993a9e2aSMaximilian Luz device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); 135993a9e2aSMaximilian Luz } 136993a9e2aSMaximilian Luz 137993a9e2aSMaximilian Luz /* 138993a9e2aSMaximilian Luz * Delay update when the base/keyboard cover is being connected to give 139993a9e2aSMaximilian Luz * devices/EC some time to set up. 140993a9e2aSMaximilian Luz */ 141993a9e2aSMaximilian Luz delay = connected ? hub->connect_delay : 0; 142993a9e2aSMaximilian Luz 143993a9e2aSMaximilian Luz schedule_delayed_work(&hub->update_work, delay); 144993a9e2aSMaximilian Luz } 145993a9e2aSMaximilian Luz 146993a9e2aSMaximilian Luz static int __maybe_unused ssam_hub_resume(struct device *dev) 147993a9e2aSMaximilian Luz { 148993a9e2aSMaximilian Luz struct ssam_hub *hub = dev_get_drvdata(dev); 149993a9e2aSMaximilian Luz 150993a9e2aSMaximilian Luz schedule_delayed_work(&hub->update_work, 0); 151993a9e2aSMaximilian Luz return 0; 152993a9e2aSMaximilian Luz } 153993a9e2aSMaximilian Luz static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); 154993a9e2aSMaximilian Luz 155993a9e2aSMaximilian Luz static int ssam_hub_probe(struct ssam_device *sdev) 156993a9e2aSMaximilian Luz { 157993a9e2aSMaximilian Luz const struct ssam_hub_desc *desc; 158993a9e2aSMaximilian Luz struct ssam_hub *hub; 159993a9e2aSMaximilian Luz int status; 160993a9e2aSMaximilian Luz 161993a9e2aSMaximilian Luz desc = ssam_device_get_match_data(sdev); 162993a9e2aSMaximilian Luz if (!desc) { 163993a9e2aSMaximilian Luz WARN(1, "no driver match data specified"); 164993a9e2aSMaximilian Luz return -EINVAL; 165993a9e2aSMaximilian Luz } 166993a9e2aSMaximilian Luz 167993a9e2aSMaximilian Luz hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); 168993a9e2aSMaximilian Luz if (!hub) 169993a9e2aSMaximilian Luz return -ENOMEM; 170993a9e2aSMaximilian Luz 171993a9e2aSMaximilian Luz hub->sdev = sdev; 172993a9e2aSMaximilian Luz hub->state = SSAM_HUB_UNINITIALIZED; 173993a9e2aSMaximilian Luz 174993a9e2aSMaximilian Luz hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ 175993a9e2aSMaximilian Luz hub->notif.base.fn = desc->ops.notify; 176993a9e2aSMaximilian Luz hub->notif.event.reg = desc->event.reg; 177993a9e2aSMaximilian Luz hub->notif.event.id = desc->event.id; 178993a9e2aSMaximilian Luz hub->notif.event.mask = desc->event.mask; 179993a9e2aSMaximilian Luz hub->notif.event.flags = SSAM_EVENT_SEQUENCED; 180993a9e2aSMaximilian Luz 181993a9e2aSMaximilian Luz hub->connect_delay = msecs_to_jiffies(desc->connect_delay_ms); 182993a9e2aSMaximilian Luz hub->ops.get_state = desc->ops.get_state; 183993a9e2aSMaximilian Luz 184993a9e2aSMaximilian Luz INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); 185993a9e2aSMaximilian Luz 186993a9e2aSMaximilian Luz ssam_device_set_drvdata(sdev, hub); 187993a9e2aSMaximilian Luz 188993a9e2aSMaximilian Luz status = ssam_device_notifier_register(sdev, &hub->notif); 189993a9e2aSMaximilian Luz if (status) 190993a9e2aSMaximilian Luz return status; 191993a9e2aSMaximilian Luz 192993a9e2aSMaximilian Luz schedule_delayed_work(&hub->update_work, 0); 193993a9e2aSMaximilian Luz return 0; 194993a9e2aSMaximilian Luz } 195993a9e2aSMaximilian Luz 196993a9e2aSMaximilian Luz static void ssam_hub_remove(struct ssam_device *sdev) 197993a9e2aSMaximilian Luz { 198993a9e2aSMaximilian Luz struct ssam_hub *hub = ssam_device_get_drvdata(sdev); 199993a9e2aSMaximilian Luz 200993a9e2aSMaximilian Luz ssam_device_notifier_unregister(sdev, &hub->notif); 201993a9e2aSMaximilian Luz cancel_delayed_work_sync(&hub->update_work); 202993a9e2aSMaximilian Luz ssam_remove_clients(&sdev->dev); 203993a9e2aSMaximilian Luz } 204993a9e2aSMaximilian Luz 205993a9e2aSMaximilian Luz 206993a9e2aSMaximilian Luz /* -- SSAM base-subsystem hub driver. --------------------------------------- */ 207993a9e2aSMaximilian Luz 208993a9e2aSMaximilian Luz /* 209993a9e2aSMaximilian Luz * Some devices (especially battery) may need a bit of time to be fully usable 210993a9e2aSMaximilian Luz * after being (re-)connected. This delay has been determined via 211993a9e2aSMaximilian Luz * experimentation. 212993a9e2aSMaximilian Luz */ 213993a9e2aSMaximilian Luz #define SSAM_BASE_UPDATE_CONNECT_DELAY 2500 214993a9e2aSMaximilian Luz 215993a9e2aSMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { 216993a9e2aSMaximilian Luz .target_category = SSAM_SSH_TC_BAS, 2170a603d71SMaximilian Luz .target_id = SSAM_SSH_TID_SAM, 218993a9e2aSMaximilian Luz .command_id = 0x0d, 219993a9e2aSMaximilian Luz .instance_id = 0x00, 220993a9e2aSMaximilian Luz }); 221993a9e2aSMaximilian Luz 222993a9e2aSMaximilian Luz #define SSAM_BAS_OPMODE_TABLET 0x00 223993a9e2aSMaximilian Luz #define SSAM_EVENT_BAS_CID_CONNECTION 0x0c 224993a9e2aSMaximilian Luz 225993a9e2aSMaximilian Luz static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) 226993a9e2aSMaximilian Luz { 227993a9e2aSMaximilian Luz u8 opmode; 228993a9e2aSMaximilian Luz int status; 229993a9e2aSMaximilian Luz 230993a9e2aSMaximilian Luz status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); 231993a9e2aSMaximilian Luz if (status < 0) { 232993a9e2aSMaximilian Luz dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); 233993a9e2aSMaximilian Luz return status; 234993a9e2aSMaximilian Luz } 235993a9e2aSMaximilian Luz 236993a9e2aSMaximilian Luz if (opmode != SSAM_BAS_OPMODE_TABLET) 237993a9e2aSMaximilian Luz *state = SSAM_HUB_CONNECTED; 238993a9e2aSMaximilian Luz else 239993a9e2aSMaximilian Luz *state = SSAM_HUB_DISCONNECTED; 240993a9e2aSMaximilian Luz 241993a9e2aSMaximilian Luz return 0; 242993a9e2aSMaximilian Luz } 243993a9e2aSMaximilian Luz 244993a9e2aSMaximilian Luz static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) 245993a9e2aSMaximilian Luz { 246993a9e2aSMaximilian Luz struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); 247993a9e2aSMaximilian Luz 248993a9e2aSMaximilian Luz if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) 249993a9e2aSMaximilian Luz return 0; 250993a9e2aSMaximilian Luz 251993a9e2aSMaximilian Luz if (event->length < 1) { 252993a9e2aSMaximilian Luz dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); 253993a9e2aSMaximilian Luz return 0; 254993a9e2aSMaximilian Luz } 255993a9e2aSMaximilian Luz 256993a9e2aSMaximilian Luz ssam_hub_update(hub, event->data[0]); 257993a9e2aSMaximilian Luz 258993a9e2aSMaximilian Luz /* 259993a9e2aSMaximilian Luz * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and 260993a9e2aSMaximilian Luz * consumed by the detachment system driver. We're just a (more or less) 261993a9e2aSMaximilian Luz * silent observer. 262993a9e2aSMaximilian Luz */ 263993a9e2aSMaximilian Luz return 0; 264993a9e2aSMaximilian Luz } 265993a9e2aSMaximilian Luz 266993a9e2aSMaximilian Luz static const struct ssam_hub_desc base_hub = { 267993a9e2aSMaximilian Luz .event = { 268993a9e2aSMaximilian Luz .reg = SSAM_EVENT_REGISTRY_SAM, 269993a9e2aSMaximilian Luz .id = { 270993a9e2aSMaximilian Luz .target_category = SSAM_SSH_TC_BAS, 271993a9e2aSMaximilian Luz .instance = 0, 272993a9e2aSMaximilian Luz }, 273993a9e2aSMaximilian Luz .mask = SSAM_EVENT_MASK_NONE, 274993a9e2aSMaximilian Luz }, 275993a9e2aSMaximilian Luz .ops = { 276993a9e2aSMaximilian Luz .notify = ssam_base_hub_notif, 277993a9e2aSMaximilian Luz .get_state = ssam_base_hub_query_state, 278993a9e2aSMaximilian Luz }, 279993a9e2aSMaximilian Luz .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY, 280993a9e2aSMaximilian Luz }; 281993a9e2aSMaximilian Luz 282993a9e2aSMaximilian Luz 283993a9e2aSMaximilian Luz /* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ 284993a9e2aSMaximilian Luz 285993a9e2aSMaximilian Luz /* 286993a9e2aSMaximilian Luz * Some devices may need a bit of time to be fully usable after being 287993a9e2aSMaximilian Luz * (re-)connected. This delay has been determined via experimentation. 288993a9e2aSMaximilian Luz */ 289993a9e2aSMaximilian Luz #define SSAM_KIP_UPDATE_CONNECT_DELAY 250 290993a9e2aSMaximilian Luz 291993a9e2aSMaximilian Luz #define SSAM_EVENT_KIP_CID_CONNECTION 0x2c 292993a9e2aSMaximilian Luz 293993a9e2aSMaximilian Luz SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, { 294993a9e2aSMaximilian Luz .target_category = SSAM_SSH_TC_KIP, 2950a603d71SMaximilian Luz .target_id = SSAM_SSH_TID_SAM, 296993a9e2aSMaximilian Luz .command_id = 0x2c, 297993a9e2aSMaximilian Luz .instance_id = 0x00, 298993a9e2aSMaximilian Luz }); 299993a9e2aSMaximilian Luz 300993a9e2aSMaximilian Luz static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) 301993a9e2aSMaximilian Luz { 302993a9e2aSMaximilian Luz int status; 303993a9e2aSMaximilian Luz u8 connected; 304993a9e2aSMaximilian Luz 305993a9e2aSMaximilian Luz status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected); 306993a9e2aSMaximilian Luz if (status < 0) { 307993a9e2aSMaximilian Luz dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); 308993a9e2aSMaximilian Luz return status; 309993a9e2aSMaximilian Luz } 310993a9e2aSMaximilian Luz 311993a9e2aSMaximilian Luz *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; 312993a9e2aSMaximilian Luz return 0; 313993a9e2aSMaximilian Luz } 314993a9e2aSMaximilian Luz 315993a9e2aSMaximilian Luz static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) 316993a9e2aSMaximilian Luz { 317993a9e2aSMaximilian Luz struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); 318993a9e2aSMaximilian Luz 319993a9e2aSMaximilian Luz if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) 320993a9e2aSMaximilian Luz return 0; /* Return "unhandled". */ 321993a9e2aSMaximilian Luz 322993a9e2aSMaximilian Luz if (event->length < 1) { 323993a9e2aSMaximilian Luz dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); 324993a9e2aSMaximilian Luz return 0; 325993a9e2aSMaximilian Luz } 326993a9e2aSMaximilian Luz 327993a9e2aSMaximilian Luz ssam_hub_update(hub, event->data[0]); 328993a9e2aSMaximilian Luz return SSAM_NOTIF_HANDLED; 329993a9e2aSMaximilian Luz } 330993a9e2aSMaximilian Luz 331993a9e2aSMaximilian Luz static const struct ssam_hub_desc kip_hub = { 332993a9e2aSMaximilian Luz .event = { 333993a9e2aSMaximilian Luz .reg = SSAM_EVENT_REGISTRY_SAM, 334993a9e2aSMaximilian Luz .id = { 335993a9e2aSMaximilian Luz .target_category = SSAM_SSH_TC_KIP, 336993a9e2aSMaximilian Luz .instance = 0, 337993a9e2aSMaximilian Luz }, 338993a9e2aSMaximilian Luz .mask = SSAM_EVENT_MASK_TARGET, 339993a9e2aSMaximilian Luz }, 340993a9e2aSMaximilian Luz .ops = { 341993a9e2aSMaximilian Luz .notify = ssam_kip_hub_notif, 342993a9e2aSMaximilian Luz .get_state = ssam_kip_hub_query_state, 343993a9e2aSMaximilian Luz }, 344993a9e2aSMaximilian Luz .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY, 345993a9e2aSMaximilian Luz }; 346993a9e2aSMaximilian Luz 347993a9e2aSMaximilian Luz 348993a9e2aSMaximilian Luz /* -- Driver registration. -------------------------------------------------- */ 349993a9e2aSMaximilian Luz 350993a9e2aSMaximilian Luz static const struct ssam_device_id ssam_hub_match[] = { 351*78abf1b5SMaximilian Luz { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, 352*78abf1b5SMaximilian Luz { SSAM_VDEV(HUB, KIP, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, 353993a9e2aSMaximilian Luz { } 354993a9e2aSMaximilian Luz }; 355993a9e2aSMaximilian Luz MODULE_DEVICE_TABLE(ssam, ssam_hub_match); 356993a9e2aSMaximilian Luz 357993a9e2aSMaximilian Luz static struct ssam_device_driver ssam_subsystem_hub_driver = { 358993a9e2aSMaximilian Luz .probe = ssam_hub_probe, 359993a9e2aSMaximilian Luz .remove = ssam_hub_remove, 360993a9e2aSMaximilian Luz .match_table = ssam_hub_match, 361993a9e2aSMaximilian Luz .driver = { 362993a9e2aSMaximilian Luz .name = "surface_aggregator_subsystem_hub", 363993a9e2aSMaximilian Luz .probe_type = PROBE_PREFER_ASYNCHRONOUS, 364993a9e2aSMaximilian Luz .pm = &ssam_hub_pm_ops, 365993a9e2aSMaximilian Luz }, 366993a9e2aSMaximilian Luz }; 367993a9e2aSMaximilian Luz module_ssam_device_driver(ssam_subsystem_hub_driver); 368993a9e2aSMaximilian Luz 369993a9e2aSMaximilian Luz MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 370993a9e2aSMaximilian Luz MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module"); 371993a9e2aSMaximilian Luz MODULE_LICENSE("GPL"); 372