surface_aggregator_hub.c 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * Driver for Surface System Aggregator Module (SSAM) subsystem device hubs.
  4. *
  5. * Provides a driver for SSAM subsystems device hubs. This driver performs
  6. * instantiation of the devices managed by said hubs and takes care of
  7. * (hot-)removal.
  8. *
  9. * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com>
  10. */
  11. #include <linux/kernel.h>
  12. #include <linux/limits.h>
  13. #include <linux/module.h>
  14. #include <linux/types.h>
  15. #include <linux/workqueue.h>
  16. #include <linux/surface_aggregator/device.h>
  17. /* -- SSAM generic subsystem hub driver framework. -------------------------- */
  18. enum ssam_hub_state {
  19. SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */
  20. SSAM_HUB_CONNECTED,
  21. SSAM_HUB_DISCONNECTED,
  22. };
  23. enum ssam_hub_flags {
  24. SSAM_HUB_HOT_REMOVED,
  25. };
  26. struct ssam_hub;
  27. struct ssam_hub_ops {
  28. int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state);
  29. };
  30. struct ssam_hub {
  31. struct ssam_device *sdev;
  32. enum ssam_hub_state state;
  33. unsigned long flags;
  34. struct delayed_work update_work;
  35. unsigned long connect_delay;
  36. struct ssam_event_notifier notif;
  37. struct ssam_hub_ops ops;
  38. };
  39. struct ssam_hub_desc {
  40. struct {
  41. struct ssam_event_registry reg;
  42. struct ssam_event_id id;
  43. enum ssam_event_mask mask;
  44. } event;
  45. struct {
  46. u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event);
  47. int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state);
  48. } ops;
  49. unsigned long connect_delay_ms;
  50. };
  51. static void ssam_hub_update_workfn(struct work_struct *work)
  52. {
  53. struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work);
  54. enum ssam_hub_state state;
  55. int status = 0;
  56. status = hub->ops.get_state(hub, &state);
  57. if (status)
  58. return;
  59. /*
  60. * There is a small possibility that hub devices were hot-removed and
  61. * re-added before we were able to remove them here. In that case, both
  62. * the state returned by get_state() and the state of the hub will
  63. * equal SSAM_HUB_CONNECTED and we would bail early below, which would
  64. * leave child devices without proper (re-)initialization and the
  65. * hot-remove flag set.
  66. *
  67. * Therefore, we check whether devices have been hot-removed via an
  68. * additional flag on the hub and, in this case, override the returned
  69. * hub state. In case of a missed disconnect (i.e. get_state returned
  70. * "connected"), we further need to re-schedule this work (with the
  71. * appropriate delay) as the actual connect work submission might have
  72. * been merged with this one.
  73. *
  74. * This then leads to one of two cases: Either we submit an unnecessary
  75. * work item (which will get ignored via either the queue or the state
  76. * checks) or, in the unlikely case that the work is actually required,
  77. * double the normal connect delay.
  78. */
  79. if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) {
  80. if (state == SSAM_HUB_CONNECTED)
  81. schedule_delayed_work(&hub->update_work, hub->connect_delay);
  82. state = SSAM_HUB_DISCONNECTED;
  83. }
  84. if (hub->state == state)
  85. return;
  86. hub->state = state;
  87. if (hub->state == SSAM_HUB_CONNECTED)
  88. status = ssam_device_register_clients(hub->sdev);
  89. else
  90. ssam_remove_clients(&hub->sdev->dev);
  91. if (status)
  92. dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status);
  93. }
  94. static int ssam_hub_mark_hot_removed(struct device *dev, void *_data)
  95. {
  96. struct ssam_device *sdev = to_ssam_device(dev);
  97. if (is_ssam_device(dev))
  98. ssam_device_mark_hot_removed(sdev);
  99. return 0;
  100. }
  101. static void ssam_hub_update(struct ssam_hub *hub, bool connected)
  102. {
  103. unsigned long delay;
  104. /* Mark devices as hot-removed before we remove any. */
  105. if (!connected) {
  106. set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags);
  107. device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed);
  108. }
  109. /*
  110. * Delay update when the base/keyboard cover is being connected to give
  111. * devices/EC some time to set up.
  112. */
  113. delay = connected ? hub->connect_delay : 0;
  114. schedule_delayed_work(&hub->update_work, delay);
  115. }
  116. static int __maybe_unused ssam_hub_resume(struct device *dev)
  117. {
  118. struct ssam_hub *hub = dev_get_drvdata(dev);
  119. schedule_delayed_work(&hub->update_work, 0);
  120. return 0;
  121. }
  122. static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume);
  123. static int ssam_hub_probe(struct ssam_device *sdev)
  124. {
  125. const struct ssam_hub_desc *desc;
  126. struct ssam_hub *hub;
  127. int status;
  128. desc = ssam_device_get_match_data(sdev);
  129. if (!desc) {
  130. WARN(1, "no driver match data specified");
  131. return -EINVAL;
  132. }
  133. hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
  134. if (!hub)
  135. return -ENOMEM;
  136. hub->sdev = sdev;
  137. hub->state = SSAM_HUB_UNINITIALIZED;
  138. hub->notif.base.priority = INT_MAX; /* This notifier should run first. */
  139. hub->notif.base.fn = desc->ops.notify;
  140. hub->notif.event.reg = desc->event.reg;
  141. hub->notif.event.id = desc->event.id;
  142. hub->notif.event.mask = desc->event.mask;
  143. hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
  144. hub->connect_delay = msecs_to_jiffies(desc->connect_delay_ms);
  145. hub->ops.get_state = desc->ops.get_state;
  146. INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn);
  147. ssam_device_set_drvdata(sdev, hub);
  148. status = ssam_device_notifier_register(sdev, &hub->notif);
  149. if (status)
  150. return status;
  151. schedule_delayed_work(&hub->update_work, 0);
  152. return 0;
  153. }
  154. static void ssam_hub_remove(struct ssam_device *sdev)
  155. {
  156. struct ssam_hub *hub = ssam_device_get_drvdata(sdev);
  157. ssam_device_notifier_unregister(sdev, &hub->notif);
  158. cancel_delayed_work_sync(&hub->update_work);
  159. ssam_remove_clients(&sdev->dev);
  160. }
  161. /* -- SSAM base-subsystem hub driver. --------------------------------------- */
  162. /*
  163. * Some devices (especially battery) may need a bit of time to be fully usable
  164. * after being (re-)connected. This delay has been determined via
  165. * experimentation.
  166. */
  167. #define SSAM_BASE_UPDATE_CONNECT_DELAY 2500
  168. SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
  169. .target_category = SSAM_SSH_TC_BAS,
  170. .target_id = SSAM_SSH_TID_SAM,
  171. .command_id = 0x0d,
  172. .instance_id = 0x00,
  173. });
  174. #define SSAM_BAS_OPMODE_TABLET 0x00
  175. #define SSAM_EVENT_BAS_CID_CONNECTION 0x0c
  176. static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state)
  177. {
  178. u8 opmode;
  179. int status;
  180. status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
  181. if (status < 0) {
  182. dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
  183. return status;
  184. }
  185. if (opmode != SSAM_BAS_OPMODE_TABLET)
  186. *state = SSAM_HUB_CONNECTED;
  187. else
  188. *state = SSAM_HUB_DISCONNECTED;
  189. return 0;
  190. }
  191. static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
  192. {
  193. struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif);
  194. if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
  195. return 0;
  196. if (event->length < 1) {
  197. dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
  198. return 0;
  199. }
  200. ssam_hub_update(hub, event->data[0]);
  201. /*
  202. * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
  203. * consumed by the detachment system driver. We're just a (more or less)
  204. * silent observer.
  205. */
  206. return 0;
  207. }
  208. static const struct ssam_hub_desc base_hub = {
  209. .event = {
  210. .reg = SSAM_EVENT_REGISTRY_SAM,
  211. .id = {
  212. .target_category = SSAM_SSH_TC_BAS,
  213. .instance = 0,
  214. },
  215. .mask = SSAM_EVENT_MASK_NONE,
  216. },
  217. .ops = {
  218. .notify = ssam_base_hub_notif,
  219. .get_state = ssam_base_hub_query_state,
  220. },
  221. .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY,
  222. };
  223. /* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */
  224. /*
  225. * Some devices may need a bit of time to be fully usable after being
  226. * (re-)connected. This delay has been determined via experimentation.
  227. */
  228. #define SSAM_KIP_UPDATE_CONNECT_DELAY 250
  229. #define SSAM_EVENT_KIP_CID_CONNECTION 0x2c
  230. SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, {
  231. .target_category = SSAM_SSH_TC_KIP,
  232. .target_id = SSAM_SSH_TID_SAM,
  233. .command_id = 0x2c,
  234. .instance_id = 0x00,
  235. });
  236. static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state)
  237. {
  238. int status;
  239. u8 connected;
  240. status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected);
  241. if (status < 0) {
  242. dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status);
  243. return status;
  244. }
  245. *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED;
  246. return 0;
  247. }
  248. static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
  249. {
  250. struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif);
  251. if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION)
  252. return 0; /* Return "unhandled". */
  253. if (event->length < 1) {
  254. dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
  255. return 0;
  256. }
  257. ssam_hub_update(hub, event->data[0]);
  258. return SSAM_NOTIF_HANDLED;
  259. }
  260. static const struct ssam_hub_desc kip_hub = {
  261. .event = {
  262. .reg = SSAM_EVENT_REGISTRY_SAM,
  263. .id = {
  264. .target_category = SSAM_SSH_TC_KIP,
  265. .instance = 0,
  266. },
  267. .mask = SSAM_EVENT_MASK_TARGET,
  268. },
  269. .ops = {
  270. .notify = ssam_kip_hub_notif,
  271. .get_state = ssam_kip_hub_query_state,
  272. },
  273. .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY,
  274. };
  275. /* -- Driver registration. -------------------------------------------------- */
  276. static const struct ssam_device_id ssam_hub_match[] = {
  277. { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub },
  278. { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub },
  279. { }
  280. };
  281. MODULE_DEVICE_TABLE(ssam, ssam_hub_match);
  282. static struct ssam_device_driver ssam_subsystem_hub_driver = {
  283. .probe = ssam_hub_probe,
  284. .remove = ssam_hub_remove,
  285. .match_table = ssam_hub_match,
  286. .driver = {
  287. .name = "surface_aggregator_subsystem_hub",
  288. .probe_type = PROBE_PREFER_ASYNCHRONOUS,
  289. .pm = &ssam_hub_pm_ops,
  290. },
  291. };
  292. module_ssam_device_driver(ssam_subsystem_hub_driver);
  293. MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
  294. MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module");
  295. MODULE_LICENSE("GPL");