1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Surface System Aggregator Module (SSAM) tablet mode switch driver. 4 * 5 * Copyright (C) 2022 Maximilian Luz <luzmaximilian@gmail.com> 6 */ 7 8 #include <asm/unaligned.h> 9 #include <linux/input.h> 10 #include <linux/kernel.h> 11 #include <linux/module.h> 12 #include <linux/types.h> 13 #include <linux/workqueue.h> 14 15 #include <linux/surface_aggregator/controller.h> 16 #include <linux/surface_aggregator/device.h> 17 18 19 /* -- SSAM generic tablet switch driver framework. -------------------------- */ 20 21 struct ssam_tablet_sw; 22 23 struct ssam_tablet_sw_state { 24 u32 source; 25 u32 state; 26 }; 27 28 struct ssam_tablet_sw_ops { 29 int (*get_state)(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state); 30 const char *(*state_name)(struct ssam_tablet_sw *sw, 31 const struct ssam_tablet_sw_state *state); 32 bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, 33 const struct ssam_tablet_sw_state *state); 34 }; 35 36 struct ssam_tablet_sw { 37 struct ssam_device *sdev; 38 39 struct ssam_tablet_sw_state state; 40 struct work_struct update_work; 41 struct input_dev *mode_switch; 42 43 struct ssam_tablet_sw_ops ops; 44 struct ssam_event_notifier notif; 45 }; 46 47 struct ssam_tablet_sw_desc { 48 struct { 49 const char *name; 50 const char *phys; 51 } dev; 52 53 struct { 54 u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); 55 int (*get_state)(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state); 56 const char *(*state_name)(struct ssam_tablet_sw *sw, 57 const struct ssam_tablet_sw_state *state); 58 bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, 59 const struct ssam_tablet_sw_state *state); 60 } ops; 61 62 struct { 63 struct ssam_event_registry reg; 64 struct ssam_event_id id; 65 enum ssam_event_mask mask; 66 u8 flags; 67 } event; 68 }; 69 70 static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) 71 { 72 struct ssam_tablet_sw *sw = dev_get_drvdata(dev); 73 const char *state = sw->ops.state_name(sw, &sw->state); 74 75 return sysfs_emit(buf, "%s\n", state); 76 } 77 static DEVICE_ATTR_RO(state); 78 79 static struct attribute *ssam_tablet_sw_attrs[] = { 80 &dev_attr_state.attr, 81 NULL, 82 }; 83 84 static const struct attribute_group ssam_tablet_sw_group = { 85 .attrs = ssam_tablet_sw_attrs, 86 }; 87 88 static void ssam_tablet_sw_update_workfn(struct work_struct *work) 89 { 90 struct ssam_tablet_sw *sw = container_of(work, struct ssam_tablet_sw, update_work); 91 struct ssam_tablet_sw_state state; 92 int tablet, status; 93 94 status = sw->ops.get_state(sw, &state); 95 if (status) 96 return; 97 98 if (sw->state.source == state.source && sw->state.state == state.state) 99 return; 100 sw->state = state; 101 102 /* Send SW_TABLET_MODE event. */ 103 tablet = sw->ops.state_is_tablet_mode(sw, &state); 104 input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); 105 input_sync(sw->mode_switch); 106 } 107 108 static int __maybe_unused ssam_tablet_sw_resume(struct device *dev) 109 { 110 struct ssam_tablet_sw *sw = dev_get_drvdata(dev); 111 112 schedule_work(&sw->update_work); 113 return 0; 114 } 115 static SIMPLE_DEV_PM_OPS(ssam_tablet_sw_pm_ops, NULL, ssam_tablet_sw_resume); 116 117 static int ssam_tablet_sw_probe(struct ssam_device *sdev) 118 { 119 const struct ssam_tablet_sw_desc *desc; 120 struct ssam_tablet_sw *sw; 121 int tablet, status; 122 123 desc = ssam_device_get_match_data(sdev); 124 if (!desc) { 125 WARN(1, "no driver match data specified"); 126 return -EINVAL; 127 } 128 129 sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); 130 if (!sw) 131 return -ENOMEM; 132 133 sw->sdev = sdev; 134 135 sw->ops.get_state = desc->ops.get_state; 136 sw->ops.state_name = desc->ops.state_name; 137 sw->ops.state_is_tablet_mode = desc->ops.state_is_tablet_mode; 138 139 INIT_WORK(&sw->update_work, ssam_tablet_sw_update_workfn); 140 141 ssam_device_set_drvdata(sdev, sw); 142 143 /* Get initial state. */ 144 status = sw->ops.get_state(sw, &sw->state); 145 if (status) 146 return status; 147 148 /* Set up tablet mode switch. */ 149 sw->mode_switch = devm_input_allocate_device(&sdev->dev); 150 if (!sw->mode_switch) 151 return -ENOMEM; 152 153 sw->mode_switch->name = desc->dev.name; 154 sw->mode_switch->phys = desc->dev.phys; 155 sw->mode_switch->id.bustype = BUS_HOST; 156 sw->mode_switch->dev.parent = &sdev->dev; 157 158 tablet = sw->ops.state_is_tablet_mode(sw, &sw->state); 159 input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); 160 input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); 161 162 status = input_register_device(sw->mode_switch); 163 if (status) 164 return status; 165 166 /* Set up notifier. */ 167 sw->notif.base.priority = 0; 168 sw->notif.base.fn = desc->ops.notify; 169 sw->notif.event.reg = desc->event.reg; 170 sw->notif.event.id = desc->event.id; 171 sw->notif.event.mask = desc->event.mask; 172 sw->notif.event.flags = SSAM_EVENT_SEQUENCED; 173 174 status = ssam_device_notifier_register(sdev, &sw->notif); 175 if (status) 176 return status; 177 178 status = sysfs_create_group(&sdev->dev.kobj, &ssam_tablet_sw_group); 179 if (status) 180 goto err; 181 182 /* We might have missed events during setup, so check again. */ 183 schedule_work(&sw->update_work); 184 return 0; 185 186 err: 187 ssam_device_notifier_unregister(sdev, &sw->notif); 188 cancel_work_sync(&sw->update_work); 189 return status; 190 } 191 192 static void ssam_tablet_sw_remove(struct ssam_device *sdev) 193 { 194 struct ssam_tablet_sw *sw = ssam_device_get_drvdata(sdev); 195 196 sysfs_remove_group(&sdev->dev.kobj, &ssam_tablet_sw_group); 197 198 ssam_device_notifier_unregister(sdev, &sw->notif); 199 cancel_work_sync(&sw->update_work); 200 } 201 202 203 /* -- SSAM KIP tablet switch implementation. -------------------------------- */ 204 205 #define SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED 0x1d 206 207 enum ssam_kip_cover_state { 208 SSAM_KIP_COVER_STATE_DISCONNECTED = 0x01, 209 SSAM_KIP_COVER_STATE_CLOSED = 0x02, 210 SSAM_KIP_COVER_STATE_LAPTOP = 0x03, 211 SSAM_KIP_COVER_STATE_FOLDED_CANVAS = 0x04, 212 SSAM_KIP_COVER_STATE_FOLDED_BACK = 0x05, 213 }; 214 215 static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, 216 const struct ssam_tablet_sw_state *state) 217 { 218 switch (state->state) { 219 case SSAM_KIP_COVER_STATE_DISCONNECTED: 220 return "disconnected"; 221 222 case SSAM_KIP_COVER_STATE_CLOSED: 223 return "closed"; 224 225 case SSAM_KIP_COVER_STATE_LAPTOP: 226 return "laptop"; 227 228 case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: 229 return "folded-canvas"; 230 231 case SSAM_KIP_COVER_STATE_FOLDED_BACK: 232 return "folded-back"; 233 234 default: 235 dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state->state); 236 return "<unknown>"; 237 } 238 } 239 240 static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, 241 const struct ssam_tablet_sw_state *state) 242 { 243 switch (state->state) { 244 case SSAM_KIP_COVER_STATE_DISCONNECTED: 245 case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: 246 case SSAM_KIP_COVER_STATE_FOLDED_BACK: 247 return true; 248 249 case SSAM_KIP_COVER_STATE_CLOSED: 250 case SSAM_KIP_COVER_STATE_LAPTOP: 251 return false; 252 253 default: 254 dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", state->state); 255 return true; 256 } 257 } 258 259 SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, { 260 .target_category = SSAM_SSH_TC_KIP, 261 .target_id = SSAM_SSH_TID_SAM, 262 .command_id = 0x1d, 263 .instance_id = 0x00, 264 }); 265 266 static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state) 267 { 268 int status; 269 u8 raw; 270 271 status = ssam_retry(__ssam_kip_get_cover_state, sw->sdev->ctrl, &raw); 272 if (status < 0) { 273 dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); 274 return status; 275 } 276 277 state->source = 0; /* Unused for KIP switch. */ 278 state->state = raw; 279 return 0; 280 } 281 282 static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) 283 { 284 struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); 285 286 if (event->command_id != SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED) 287 return 0; /* Return "unhandled". */ 288 289 if (event->length < 1) 290 dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); 291 292 schedule_work(&sw->update_work); 293 return SSAM_NOTIF_HANDLED; 294 } 295 296 static const struct ssam_tablet_sw_desc ssam_kip_sw_desc = { 297 .dev = { 298 .name = "Microsoft Surface KIP Tablet Mode Switch", 299 .phys = "ssam/01:0e:01:00:01/input0", 300 }, 301 .ops = { 302 .notify = ssam_kip_sw_notif, 303 .get_state = ssam_kip_get_cover_state, 304 .state_name = ssam_kip_cover_state_name, 305 .state_is_tablet_mode = ssam_kip_cover_state_is_tablet_mode, 306 }, 307 .event = { 308 .reg = SSAM_EVENT_REGISTRY_SAM, 309 .id = { 310 .target_category = SSAM_SSH_TC_KIP, 311 .instance = 0, 312 }, 313 .mask = SSAM_EVENT_MASK_TARGET, 314 }, 315 }; 316 317 318 /* -- SSAM POS tablet switch implementation. -------------------------------- */ 319 320 static bool tablet_mode_in_slate_state = true; 321 module_param(tablet_mode_in_slate_state, bool, 0644); 322 MODULE_PARM_DESC(tablet_mode_in_slate_state, "Enable tablet mode in slate device posture, default is 'true'"); 323 324 #define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03 325 #define SSAM_POS_MAX_SOURCES 4 326 327 enum ssam_pos_source_id { 328 SSAM_POS_SOURCE_COVER = 0x00, 329 SSAM_POS_SOURCE_SLS = 0x03, 330 }; 331 332 enum ssam_pos_state_cover { 333 SSAM_POS_COVER_DISCONNECTED = 0x01, 334 SSAM_POS_COVER_CLOSED = 0x02, 335 SSAM_POS_COVER_LAPTOP = 0x03, 336 SSAM_POS_COVER_FOLDED_CANVAS = 0x04, 337 SSAM_POS_COVER_FOLDED_BACK = 0x05, 338 }; 339 340 enum ssam_pos_state_sls { 341 SSAM_POS_SLS_LID_CLOSED = 0x00, 342 SSAM_POS_SLS_LAPTOP = 0x01, 343 SSAM_POS_SLS_SLATE = 0x02, 344 SSAM_POS_SLS_TABLET = 0x03, 345 }; 346 347 struct ssam_sources_list { 348 __le32 count; 349 __le32 id[SSAM_POS_MAX_SOURCES]; 350 } __packed; 351 352 static const char *ssam_pos_state_name_cover(struct ssam_tablet_sw *sw, u32 state) 353 { 354 switch (state) { 355 case SSAM_POS_COVER_DISCONNECTED: 356 return "disconnected"; 357 358 case SSAM_POS_COVER_CLOSED: 359 return "closed"; 360 361 case SSAM_POS_COVER_LAPTOP: 362 return "laptop"; 363 364 case SSAM_POS_COVER_FOLDED_CANVAS: 365 return "folded-canvas"; 366 367 case SSAM_POS_COVER_FOLDED_BACK: 368 return "folded-back"; 369 370 default: 371 dev_warn(&sw->sdev->dev, "unknown device posture for type-cover: %u\n", state); 372 return "<unknown>"; 373 } 374 } 375 376 static const char *ssam_pos_state_name_sls(struct ssam_tablet_sw *sw, u32 state) 377 { 378 switch (state) { 379 case SSAM_POS_SLS_LID_CLOSED: 380 return "closed"; 381 382 case SSAM_POS_SLS_LAPTOP: 383 return "laptop"; 384 385 case SSAM_POS_SLS_SLATE: 386 return "slate"; 387 388 case SSAM_POS_SLS_TABLET: 389 return "tablet"; 390 391 default: 392 dev_warn(&sw->sdev->dev, "unknown device posture for SLS: %u\n", state); 393 return "<unknown>"; 394 } 395 } 396 397 static const char *ssam_pos_state_name(struct ssam_tablet_sw *sw, 398 const struct ssam_tablet_sw_state *state) 399 { 400 switch (state->source) { 401 case SSAM_POS_SOURCE_COVER: 402 return ssam_pos_state_name_cover(sw, state->state); 403 404 case SSAM_POS_SOURCE_SLS: 405 return ssam_pos_state_name_sls(sw, state->state); 406 407 default: 408 dev_warn(&sw->sdev->dev, "unknown device posture source: %u\n", state->source); 409 return "<unknown>"; 410 } 411 } 412 413 static bool ssam_pos_state_is_tablet_mode_cover(struct ssam_tablet_sw *sw, u32 state) 414 { 415 switch (state) { 416 case SSAM_POS_COVER_DISCONNECTED: 417 case SSAM_POS_COVER_FOLDED_CANVAS: 418 case SSAM_POS_COVER_FOLDED_BACK: 419 return true; 420 421 case SSAM_POS_COVER_CLOSED: 422 case SSAM_POS_COVER_LAPTOP: 423 return false; 424 425 default: 426 dev_warn(&sw->sdev->dev, "unknown device posture for type-cover: %u\n", state); 427 return true; 428 } 429 } 430 431 static bool ssam_pos_state_is_tablet_mode_sls(struct ssam_tablet_sw *sw, u32 state) 432 { 433 switch (state) { 434 case SSAM_POS_SLS_LAPTOP: 435 case SSAM_POS_SLS_LID_CLOSED: 436 return false; 437 438 case SSAM_POS_SLS_SLATE: 439 return tablet_mode_in_slate_state; 440 441 case SSAM_POS_SLS_TABLET: 442 return true; 443 444 default: 445 dev_warn(&sw->sdev->dev, "unknown device posture for SLS: %u\n", state); 446 return true; 447 } 448 } 449 450 static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw, 451 const struct ssam_tablet_sw_state *state) 452 { 453 switch (state->source) { 454 case SSAM_POS_SOURCE_COVER: 455 return ssam_pos_state_is_tablet_mode_cover(sw, state->state); 456 457 case SSAM_POS_SOURCE_SLS: 458 return ssam_pos_state_is_tablet_mode_sls(sw, state->state); 459 460 default: 461 dev_warn(&sw->sdev->dev, "unknown device posture source: %u\n", state->source); 462 return true; 463 } 464 } 465 466 static int ssam_pos_get_sources_list(struct ssam_tablet_sw *sw, struct ssam_sources_list *sources) 467 { 468 struct ssam_request rqst; 469 struct ssam_response rsp; 470 int status; 471 472 rqst.target_category = SSAM_SSH_TC_POS; 473 rqst.target_id = SSAM_SSH_TID_SAM; 474 rqst.command_id = 0x01; 475 rqst.instance_id = 0x00; 476 rqst.flags = SSAM_REQUEST_HAS_RESPONSE; 477 rqst.length = 0; 478 rqst.payload = NULL; 479 480 rsp.capacity = sizeof(*sources); 481 rsp.length = 0; 482 rsp.pointer = (u8 *)sources; 483 484 status = ssam_retry(ssam_request_do_sync_onstack, sw->sdev->ctrl, &rqst, &rsp, 0); 485 if (status) 486 return status; 487 488 /* We need at least the 'sources->count' field. */ 489 if (rsp.length < sizeof(__le32)) { 490 dev_err(&sw->sdev->dev, "received source list response is too small\n"); 491 return -EPROTO; 492 } 493 494 /* Make sure 'sources->count' matches with the response length. */ 495 if (get_unaligned_le32(&sources->count) * sizeof(__le32) + sizeof(__le32) != rsp.length) { 496 dev_err(&sw->sdev->dev, "mismatch between number of sources and response size\n"); 497 return -EPROTO; 498 } 499 500 return 0; 501 } 502 503 static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) 504 { 505 struct ssam_sources_list sources = {}; 506 int status; 507 508 status = ssam_pos_get_sources_list(sw, &sources); 509 if (status) 510 return status; 511 512 if (get_unaligned_le32(&sources.count) == 0) { 513 dev_err(&sw->sdev->dev, "no posture sources found\n"); 514 return -ENODEV; 515 } 516 517 /* 518 * We currently don't know what to do with more than one posture 519 * source. At the moment, only one source seems to be used/provided. 520 * The WARN_ON() here should hopefully let us know quickly once there 521 * is a device that provides multiple sources, at which point we can 522 * then try to figure out how to handle them. 523 */ 524 WARN_ON(get_unaligned_le32(&sources.count) > 1); 525 526 *source_id = get_unaligned_le32(&sources.id[0]); 527 return 0; 528 } 529 530 SSAM_DEFINE_SYNC_REQUEST_WR(__ssam_pos_get_posture_for_source, __le32, __le32, { 531 .target_category = SSAM_SSH_TC_POS, 532 .target_id = SSAM_SSH_TID_SAM, 533 .command_id = 0x02, 534 .instance_id = 0x00, 535 }); 536 537 static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw *sw, u32 source_id, u32 *posture) 538 { 539 __le32 source_le = cpu_to_le32(source_id); 540 __le32 rspval_le = 0; 541 int status; 542 543 status = ssam_retry(__ssam_pos_get_posture_for_source, sw->sdev->ctrl, 544 &source_le, &rspval_le); 545 if (status) 546 return status; 547 548 *posture = le32_to_cpu(rspval_le); 549 return 0; 550 } 551 552 static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state) 553 { 554 u32 source_id; 555 u32 source_state; 556 int status; 557 558 status = ssam_pos_get_source(sw, &source_id); 559 if (status) { 560 dev_err(&sw->sdev->dev, "failed to get posture source ID: %d\n", status); 561 return status; 562 } 563 564 status = ssam_pos_get_posture_for_source(sw, source_id, &source_state); 565 if (status) { 566 dev_err(&sw->sdev->dev, "failed to get posture value for source %u: %d\n", 567 source_id, status); 568 return status; 569 } 570 571 state->source = source_id; 572 state->state = source_state; 573 return 0; 574 } 575 576 static u32 ssam_pos_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) 577 { 578 struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); 579 580 if (event->command_id != SSAM_EVENT_POS_CID_POSTURE_CHANGED) 581 return 0; /* Return "unhandled". */ 582 583 if (event->length != sizeof(__le32) * 3) 584 dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); 585 586 schedule_work(&sw->update_work); 587 return SSAM_NOTIF_HANDLED; 588 } 589 590 static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { 591 .dev = { 592 .name = "Microsoft Surface POS Tablet Mode Switch", 593 .phys = "ssam/01:26:01:00:01/input0", 594 }, 595 .ops = { 596 .notify = ssam_pos_sw_notif, 597 .get_state = ssam_pos_get_posture, 598 .state_name = ssam_pos_state_name, 599 .state_is_tablet_mode = ssam_pos_state_is_tablet_mode, 600 }, 601 .event = { 602 .reg = SSAM_EVENT_REGISTRY_SAM, 603 .id = { 604 .target_category = SSAM_SSH_TC_POS, 605 .instance = 0, 606 }, 607 .mask = SSAM_EVENT_MASK_TARGET, 608 }, 609 }; 610 611 612 /* -- Driver registration. -------------------------------------------------- */ 613 614 static const struct ssam_device_id ssam_tablet_sw_match[] = { 615 { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, 616 { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, 617 { }, 618 }; 619 MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); 620 621 static struct ssam_device_driver ssam_tablet_sw_driver = { 622 .probe = ssam_tablet_sw_probe, 623 .remove = ssam_tablet_sw_remove, 624 .match_table = ssam_tablet_sw_match, 625 .driver = { 626 .name = "surface_aggregator_tablet_mode_switch", 627 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 628 .pm = &ssam_tablet_sw_pm_ops, 629 }, 630 }; 631 module_ssam_device_driver(ssam_tablet_sw_driver); 632 633 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 634 MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using the Surface Aggregator Module"); 635 MODULE_LICENSE("GPL"); 636