0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #include <linux/kernel.h>
0013 #include <linux/limits.h>
0014 #include <linux/module.h>
0015 #include <linux/types.h>
0016 #include <linux/workqueue.h>
0017
0018 #include <linux/surface_aggregator/device.h>
0019
0020
0021
0022
0023 enum ssam_hub_state {
0024 SSAM_HUB_UNINITIALIZED,
0025 SSAM_HUB_CONNECTED,
0026 SSAM_HUB_DISCONNECTED,
0027 };
0028
0029 enum ssam_hub_flags {
0030 SSAM_HUB_HOT_REMOVED,
0031 };
0032
0033 struct ssam_hub;
0034
0035 struct ssam_hub_ops {
0036 int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state);
0037 };
0038
0039 struct ssam_hub {
0040 struct ssam_device *sdev;
0041
0042 enum ssam_hub_state state;
0043 unsigned long flags;
0044
0045 struct delayed_work update_work;
0046 unsigned long connect_delay;
0047
0048 struct ssam_event_notifier notif;
0049 struct ssam_hub_ops ops;
0050 };
0051
0052 struct ssam_hub_desc {
0053 struct {
0054 struct ssam_event_registry reg;
0055 struct ssam_event_id id;
0056 enum ssam_event_mask mask;
0057 } event;
0058
0059 struct {
0060 u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event);
0061 int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state);
0062 } ops;
0063
0064 unsigned long connect_delay_ms;
0065 };
0066
0067 static void ssam_hub_update_workfn(struct work_struct *work)
0068 {
0069 struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work);
0070 enum ssam_hub_state state;
0071 int status = 0;
0072
0073 status = hub->ops.get_state(hub, &state);
0074 if (status)
0075 return;
0076
0077
0078
0079
0080
0081
0082
0083
0084
0085
0086
0087
0088
0089
0090
0091
0092
0093
0094
0095
0096
0097 if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) {
0098 if (state == SSAM_HUB_CONNECTED)
0099 schedule_delayed_work(&hub->update_work, hub->connect_delay);
0100
0101 state = SSAM_HUB_DISCONNECTED;
0102 }
0103
0104 if (hub->state == state)
0105 return;
0106 hub->state = state;
0107
0108 if (hub->state == SSAM_HUB_CONNECTED)
0109 status = ssam_device_register_clients(hub->sdev);
0110 else
0111 ssam_remove_clients(&hub->sdev->dev);
0112
0113 if (status)
0114 dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status);
0115 }
0116
0117 static int ssam_hub_mark_hot_removed(struct device *dev, void *_data)
0118 {
0119 struct ssam_device *sdev = to_ssam_device(dev);
0120
0121 if (is_ssam_device(dev))
0122 ssam_device_mark_hot_removed(sdev);
0123
0124 return 0;
0125 }
0126
0127 static void ssam_hub_update(struct ssam_hub *hub, bool connected)
0128 {
0129 unsigned long delay;
0130
0131
0132 if (!connected) {
0133 set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags);
0134 device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed);
0135 }
0136
0137
0138
0139
0140
0141 delay = connected ? hub->connect_delay : 0;
0142
0143 schedule_delayed_work(&hub->update_work, delay);
0144 }
0145
0146 static int __maybe_unused ssam_hub_resume(struct device *dev)
0147 {
0148 struct ssam_hub *hub = dev_get_drvdata(dev);
0149
0150 schedule_delayed_work(&hub->update_work, 0);
0151 return 0;
0152 }
0153 static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume);
0154
0155 static int ssam_hub_probe(struct ssam_device *sdev)
0156 {
0157 const struct ssam_hub_desc *desc;
0158 struct ssam_hub *hub;
0159 int status;
0160
0161 desc = ssam_device_get_match_data(sdev);
0162 if (!desc) {
0163 WARN(1, "no driver match data specified");
0164 return -EINVAL;
0165 }
0166
0167 hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
0168 if (!hub)
0169 return -ENOMEM;
0170
0171 hub->sdev = sdev;
0172 hub->state = SSAM_HUB_UNINITIALIZED;
0173
0174 hub->notif.base.priority = INT_MAX;
0175 hub->notif.base.fn = desc->ops.notify;
0176 hub->notif.event.reg = desc->event.reg;
0177 hub->notif.event.id = desc->event.id;
0178 hub->notif.event.mask = desc->event.mask;
0179 hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
0180
0181 hub->connect_delay = msecs_to_jiffies(desc->connect_delay_ms);
0182 hub->ops.get_state = desc->ops.get_state;
0183
0184 INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn);
0185
0186 ssam_device_set_drvdata(sdev, hub);
0187
0188 status = ssam_device_notifier_register(sdev, &hub->notif);
0189 if (status)
0190 return status;
0191
0192 schedule_delayed_work(&hub->update_work, 0);
0193 return 0;
0194 }
0195
0196 static void ssam_hub_remove(struct ssam_device *sdev)
0197 {
0198 struct ssam_hub *hub = ssam_device_get_drvdata(sdev);
0199
0200 ssam_device_notifier_unregister(sdev, &hub->notif);
0201 cancel_delayed_work_sync(&hub->update_work);
0202 ssam_remove_clients(&sdev->dev);
0203 }
0204
0205
0206
0207
0208
0209
0210
0211
0212
0213 #define SSAM_BASE_UPDATE_CONNECT_DELAY 2500
0214
0215 SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
0216 .target_category = SSAM_SSH_TC_BAS,
0217 .target_id = 0x01,
0218 .command_id = 0x0d,
0219 .instance_id = 0x00,
0220 });
0221
0222 #define SSAM_BAS_OPMODE_TABLET 0x00
0223 #define SSAM_EVENT_BAS_CID_CONNECTION 0x0c
0224
0225 static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state)
0226 {
0227 u8 opmode;
0228 int status;
0229
0230 status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
0231 if (status < 0) {
0232 dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
0233 return status;
0234 }
0235
0236 if (opmode != SSAM_BAS_OPMODE_TABLET)
0237 *state = SSAM_HUB_CONNECTED;
0238 else
0239 *state = SSAM_HUB_DISCONNECTED;
0240
0241 return 0;
0242 }
0243
0244 static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
0245 {
0246 struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif);
0247
0248 if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
0249 return 0;
0250
0251 if (event->length < 1) {
0252 dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
0253 return 0;
0254 }
0255
0256 ssam_hub_update(hub, event->data[0]);
0257
0258
0259
0260
0261
0262
0263 return 0;
0264 }
0265
0266 static const struct ssam_hub_desc base_hub = {
0267 .event = {
0268 .reg = SSAM_EVENT_REGISTRY_SAM,
0269 .id = {
0270 .target_category = SSAM_SSH_TC_BAS,
0271 .instance = 0,
0272 },
0273 .mask = SSAM_EVENT_MASK_NONE,
0274 },
0275 .ops = {
0276 .notify = ssam_base_hub_notif,
0277 .get_state = ssam_base_hub_query_state,
0278 },
0279 .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY,
0280 };
0281
0282
0283
0284
0285
0286
0287
0288
0289 #define SSAM_KIP_UPDATE_CONNECT_DELAY 250
0290
0291 #define SSAM_EVENT_KIP_CID_CONNECTION 0x2c
0292
0293 SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, {
0294 .target_category = SSAM_SSH_TC_KIP,
0295 .target_id = 0x01,
0296 .command_id = 0x2c,
0297 .instance_id = 0x00,
0298 });
0299
0300 static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state)
0301 {
0302 int status;
0303 u8 connected;
0304
0305 status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected);
0306 if (status < 0) {
0307 dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status);
0308 return status;
0309 }
0310
0311 *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED;
0312 return 0;
0313 }
0314
0315 static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
0316 {
0317 struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif);
0318
0319 if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION)
0320 return 0;
0321
0322 if (event->length < 1) {
0323 dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
0324 return 0;
0325 }
0326
0327 ssam_hub_update(hub, event->data[0]);
0328 return SSAM_NOTIF_HANDLED;
0329 }
0330
0331 static const struct ssam_hub_desc kip_hub = {
0332 .event = {
0333 .reg = SSAM_EVENT_REGISTRY_SAM,
0334 .id = {
0335 .target_category = SSAM_SSH_TC_KIP,
0336 .instance = 0,
0337 },
0338 .mask = SSAM_EVENT_MASK_TARGET,
0339 },
0340 .ops = {
0341 .notify = ssam_kip_hub_notif,
0342 .get_state = ssam_kip_hub_query_state,
0343 },
0344 .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY,
0345 };
0346
0347
0348
0349
0350 static const struct ssam_device_id ssam_hub_match[] = {
0351 { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub },
0352 { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub },
0353 { }
0354 };
0355 MODULE_DEVICE_TABLE(ssam, ssam_hub_match);
0356
0357 static struct ssam_device_driver ssam_subsystem_hub_driver = {
0358 .probe = ssam_hub_probe,
0359 .remove = ssam_hub_remove,
0360 .match_table = ssam_hub_match,
0361 .driver = {
0362 .name = "surface_aggregator_subsystem_hub",
0363 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
0364 .pm = &ssam_hub_pm_ops,
0365 },
0366 };
0367 module_ssam_device_driver(ssam_subsystem_hub_driver);
0368
0369 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
0370 MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module");
0371 MODULE_LICENSE("GPL");