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 
ssam_hub_update_workfn(struct work_struct * work)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 
ssam_hub_mark_hot_removed(struct device * dev,void * _data)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 
ssam_hub_update(struct ssam_hub * hub,bool connected)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 
ssam_hub_resume(struct device * dev)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 
ssam_hub_probe(struct ssam_device * sdev)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 
ssam_hub_remove(struct ssam_device * sdev)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 
ssam_base_hub_query_state(struct ssam_hub * hub,enum ssam_hub_state * state)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 
ssam_base_hub_notif(struct ssam_event_notifier * nf,const struct ssam_event * event)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 
ssam_kip_hub_query_state(struct ssam_hub * hub,enum ssam_hub_state * state)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 
ssam_kip_hub_notif(struct ssam_event_notifier * nf,const struct ssam_event * event)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[] = {
35178abf1b5SMaximilian Luz 	{ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub  },
352*13eca7d7SMaximilian Luz 	{ SSAM_VDEV(HUB, SAM, 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