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 SSAM_KIP_COVER_STATE_BOOK = 0x06, 214 }; 215 216 static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, 217 const struct ssam_tablet_sw_state *state) 218 { 219 switch (state->state) { 220 case SSAM_KIP_COVER_STATE_DISCONNECTED: 221 return "disconnected"; 222 223 case SSAM_KIP_COVER_STATE_CLOSED: 224 return "closed"; 225 226 case SSAM_KIP_COVER_STATE_LAPTOP: 227 return "laptop"; 228 229 case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: 230 return "folded-canvas"; 231 232 case SSAM_KIP_COVER_STATE_FOLDED_BACK: 233 return "folded-back"; 234 235 case SSAM_KIP_COVER_STATE_BOOK: 236 return "book"; 237 238 default: 239 dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state->state); 240 return "<unknown>"; 241 } 242 } 243 244 static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, 245 const struct ssam_tablet_sw_state *state) 246 { 247 switch (state->state) { 248 case SSAM_KIP_COVER_STATE_DISCONNECTED: 249 case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: 250 case SSAM_KIP_COVER_STATE_FOLDED_BACK: 251 case SSAM_KIP_COVER_STATE_BOOK: 252 return true; 253 254 case SSAM_KIP_COVER_STATE_CLOSED: 255 case SSAM_KIP_COVER_STATE_LAPTOP: 256 return false; 257 258 default: 259 dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", state->state); 260 return true; 261 } 262 } 263 264 SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, { 265 .target_category = SSAM_SSH_TC_KIP, 266 .target_id = SSAM_SSH_TID_SAM, 267 .command_id = 0x1d, 268 .instance_id = 0x00, 269 }); 270 271 static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state) 272 { 273 int status; 274 u8 raw; 275 276 status = ssam_retry(__ssam_kip_get_cover_state, sw->sdev->ctrl, &raw); 277 if (status < 0) { 278 dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); 279 return status; 280 } 281 282 state->source = 0; /* Unused for KIP switch. */ 283 state->state = raw; 284 return 0; 285 } 286 287 static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) 288 { 289 struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); 290 291 if (event->command_id != SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED) 292 return 0; /* Return "unhandled". */ 293 294 if (event->length < 1) 295 dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); 296 297 schedule_work(&sw->update_work); 298 return SSAM_NOTIF_HANDLED; 299 } 300 301 static const struct ssam_tablet_sw_desc ssam_kip_sw_desc = { 302 .dev = { 303 .name = "Microsoft Surface KIP Tablet Mode Switch", 304 .phys = "ssam/01:0e:01:00:01/input0", 305 }, 306 .ops = { 307 .notify = ssam_kip_sw_notif, 308 .get_state = ssam_kip_get_cover_state, 309 .state_name = ssam_kip_cover_state_name, 310 .state_is_tablet_mode = ssam_kip_cover_state_is_tablet_mode, 311 }, 312 .event = { 313 .reg = SSAM_EVENT_REGISTRY_SAM, 314 .id = { 315 .target_category = SSAM_SSH_TC_KIP, 316 .instance = 0, 317 }, 318 .mask = SSAM_EVENT_MASK_TARGET, 319 }, 320 }; 321 322 323 /* -- SSAM POS tablet switch implementation. -------------------------------- */ 324 325 static bool tablet_mode_in_slate_state = true; 326 module_param(tablet_mode_in_slate_state, bool, 0644); 327 MODULE_PARM_DESC(tablet_mode_in_slate_state, "Enable tablet mode in slate device posture, default is 'true'"); 328 329 #define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03 330 #define SSAM_POS_MAX_SOURCES 4 331 332 enum ssam_pos_source_id { 333 SSAM_POS_SOURCE_COVER = 0x00, 334 SSAM_POS_SOURCE_SLS = 0x03, 335 }; 336 337 enum ssam_pos_state_cover { 338 SSAM_POS_COVER_DISCONNECTED = 0x01, 339 SSAM_POS_COVER_CLOSED = 0x02, 340 SSAM_POS_COVER_LAPTOP = 0x03, 341 SSAM_POS_COVER_FOLDED_CANVAS = 0x04, 342 SSAM_POS_COVER_FOLDED_BACK = 0x05, 343 SSAM_POS_COVER_BOOK = 0x06, 344 }; 345 346 enum ssam_pos_state_sls { 347 SSAM_POS_SLS_LID_CLOSED = 0x00, 348 SSAM_POS_SLS_LAPTOP = 0x01, 349 SSAM_POS_SLS_SLATE = 0x02, 350 SSAM_POS_SLS_TABLET = 0x03, 351 }; 352 353 struct ssam_sources_list { 354 __le32 count; 355 __le32 id[SSAM_POS_MAX_SOURCES]; 356 } __packed; 357 358 static const char *ssam_pos_state_name_cover(struct ssam_tablet_sw *sw, u32 state) 359 { 360 switch (state) { 361 case SSAM_POS_COVER_DISCONNECTED: 362 return "disconnected"; 363 364 case SSAM_POS_COVER_CLOSED: 365 return "closed"; 366 367 case SSAM_POS_COVER_LAPTOP: 368 return "laptop"; 369 370 case SSAM_POS_COVER_FOLDED_CANVAS: 371 return "folded-canvas"; 372 373 case SSAM_POS_COVER_FOLDED_BACK: 374 return "folded-back"; 375 376 case SSAM_POS_COVER_BOOK: 377 return "book"; 378 379 default: 380 dev_warn(&sw->sdev->dev, "unknown device posture for type-cover: %u\n", state); 381 return "<unknown>"; 382 } 383 } 384 385 static const char *ssam_pos_state_name_sls(struct ssam_tablet_sw *sw, u32 state) 386 { 387 switch (state) { 388 case SSAM_POS_SLS_LID_CLOSED: 389 return "closed"; 390 391 case SSAM_POS_SLS_LAPTOP: 392 return "laptop"; 393 394 case SSAM_POS_SLS_SLATE: 395 return "slate"; 396 397 case SSAM_POS_SLS_TABLET: 398 return "tablet"; 399 400 default: 401 dev_warn(&sw->sdev->dev, "unknown device posture for SLS: %u\n", state); 402 return "<unknown>"; 403 } 404 } 405 406 static const char *ssam_pos_state_name(struct ssam_tablet_sw *sw, 407 const struct ssam_tablet_sw_state *state) 408 { 409 switch (state->source) { 410 case SSAM_POS_SOURCE_COVER: 411 return ssam_pos_state_name_cover(sw, state->state); 412 413 case SSAM_POS_SOURCE_SLS: 414 return ssam_pos_state_name_sls(sw, state->state); 415 416 default: 417 dev_warn(&sw->sdev->dev, "unknown device posture source: %u\n", state->source); 418 return "<unknown>"; 419 } 420 } 421 422 static bool ssam_pos_state_is_tablet_mode_cover(struct ssam_tablet_sw *sw, u32 state) 423 { 424 switch (state) { 425 case SSAM_POS_COVER_DISCONNECTED: 426 case SSAM_POS_COVER_FOLDED_CANVAS: 427 case SSAM_POS_COVER_FOLDED_BACK: 428 case SSAM_POS_COVER_BOOK: 429 return true; 430 431 case SSAM_POS_COVER_CLOSED: 432 case SSAM_POS_COVER_LAPTOP: 433 return false; 434 435 default: 436 dev_warn(&sw->sdev->dev, "unknown device posture for type-cover: %u\n", state); 437 return true; 438 } 439 } 440 441 static bool ssam_pos_state_is_tablet_mode_sls(struct ssam_tablet_sw *sw, u32 state) 442 { 443 switch (state) { 444 case SSAM_POS_SLS_LAPTOP: 445 case SSAM_POS_SLS_LID_CLOSED: 446 return false; 447 448 case SSAM_POS_SLS_SLATE: 449 return tablet_mode_in_slate_state; 450 451 case SSAM_POS_SLS_TABLET: 452 return true; 453 454 default: 455 dev_warn(&sw->sdev->dev, "unknown device posture for SLS: %u\n", state); 456 return true; 457 } 458 } 459 460 static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw, 461 const struct ssam_tablet_sw_state *state) 462 { 463 switch (state->source) { 464 case SSAM_POS_SOURCE_COVER: 465 return ssam_pos_state_is_tablet_mode_cover(sw, state->state); 466 467 case SSAM_POS_SOURCE_SLS: 468 return ssam_pos_state_is_tablet_mode_sls(sw, state->state); 469 470 default: 471 dev_warn(&sw->sdev->dev, "unknown device posture source: %u\n", state->source); 472 return true; 473 } 474 } 475 476 static int ssam_pos_get_sources_list(struct ssam_tablet_sw *sw, struct ssam_sources_list *sources) 477 { 478 struct ssam_request rqst; 479 struct ssam_response rsp; 480 int status; 481 482 rqst.target_category = SSAM_SSH_TC_POS; 483 rqst.target_id = SSAM_SSH_TID_SAM; 484 rqst.command_id = 0x01; 485 rqst.instance_id = 0x00; 486 rqst.flags = SSAM_REQUEST_HAS_RESPONSE; 487 rqst.length = 0; 488 rqst.payload = NULL; 489 490 rsp.capacity = sizeof(*sources); 491 rsp.length = 0; 492 rsp.pointer = (u8 *)sources; 493 494 status = ssam_retry(ssam_request_do_sync_onstack, sw->sdev->ctrl, &rqst, &rsp, 0); 495 if (status) 496 return status; 497 498 /* We need at least the 'sources->count' field. */ 499 if (rsp.length < sizeof(__le32)) { 500 dev_err(&sw->sdev->dev, "received source list response is too small\n"); 501 return -EPROTO; 502 } 503 504 /* Make sure 'sources->count' matches with the response length. */ 505 if (get_unaligned_le32(&sources->count) * sizeof(__le32) + sizeof(__le32) != rsp.length) { 506 dev_err(&sw->sdev->dev, "mismatch between number of sources and response size\n"); 507 return -EPROTO; 508 } 509 510 return 0; 511 } 512 513 static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) 514 { 515 struct ssam_sources_list sources = {}; 516 int status; 517 518 status = ssam_pos_get_sources_list(sw, &sources); 519 if (status) 520 return status; 521 522 if (get_unaligned_le32(&sources.count) == 0) { 523 dev_err(&sw->sdev->dev, "no posture sources found\n"); 524 return -ENODEV; 525 } 526 527 /* 528 * We currently don't know what to do with more than one posture 529 * source. At the moment, only one source seems to be used/provided. 530 * The WARN_ON() here should hopefully let us know quickly once there 531 * is a device that provides multiple sources, at which point we can 532 * then try to figure out how to handle them. 533 */ 534 WARN_ON(get_unaligned_le32(&sources.count) > 1); 535 536 *source_id = get_unaligned_le32(&sources.id[0]); 537 return 0; 538 } 539 540 SSAM_DEFINE_SYNC_REQUEST_WR(__ssam_pos_get_posture_for_source, __le32, __le32, { 541 .target_category = SSAM_SSH_TC_POS, 542 .target_id = SSAM_SSH_TID_SAM, 543 .command_id = 0x02, 544 .instance_id = 0x00, 545 }); 546 547 static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw *sw, u32 source_id, u32 *posture) 548 { 549 __le32 source_le = cpu_to_le32(source_id); 550 __le32 rspval_le = 0; 551 int status; 552 553 status = ssam_retry(__ssam_pos_get_posture_for_source, sw->sdev->ctrl, 554 &source_le, &rspval_le); 555 if (status) 556 return status; 557 558 *posture = le32_to_cpu(rspval_le); 559 return 0; 560 } 561 562 static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, struct ssam_tablet_sw_state *state) 563 { 564 u32 source_id; 565 u32 source_state; 566 int status; 567 568 status = ssam_pos_get_source(sw, &source_id); 569 if (status) { 570 dev_err(&sw->sdev->dev, "failed to get posture source ID: %d\n", status); 571 return status; 572 } 573 574 status = ssam_pos_get_posture_for_source(sw, source_id, &source_state); 575 if (status) { 576 dev_err(&sw->sdev->dev, "failed to get posture value for source %u: %d\n", 577 source_id, status); 578 return status; 579 } 580 581 state->source = source_id; 582 state->state = source_state; 583 return 0; 584 } 585 586 static u32 ssam_pos_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) 587 { 588 struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); 589 590 if (event->command_id != SSAM_EVENT_POS_CID_POSTURE_CHANGED) 591 return 0; /* Return "unhandled". */ 592 593 if (event->length != sizeof(__le32) * 3) 594 dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); 595 596 schedule_work(&sw->update_work); 597 return SSAM_NOTIF_HANDLED; 598 } 599 600 static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { 601 .dev = { 602 .name = "Microsoft Surface POS Tablet Mode Switch", 603 .phys = "ssam/01:26:01:00:01/input0", 604 }, 605 .ops = { 606 .notify = ssam_pos_sw_notif, 607 .get_state = ssam_pos_get_posture, 608 .state_name = ssam_pos_state_name, 609 .state_is_tablet_mode = ssam_pos_state_is_tablet_mode, 610 }, 611 .event = { 612 .reg = SSAM_EVENT_REGISTRY_SAM, 613 .id = { 614 .target_category = SSAM_SSH_TC_POS, 615 .instance = 0, 616 }, 617 .mask = SSAM_EVENT_MASK_TARGET, 618 }, 619 }; 620 621 622 /* -- Driver registration. -------------------------------------------------- */ 623 624 static const struct ssam_device_id ssam_tablet_sw_match[] = { 625 { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, 626 { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, 627 { }, 628 }; 629 MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); 630 631 static struct ssam_device_driver ssam_tablet_sw_driver = { 632 .probe = ssam_tablet_sw_probe, 633 .remove = ssam_tablet_sw_remove, 634 .match_table = ssam_tablet_sw_match, 635 .driver = { 636 .name = "surface_aggregator_tablet_mode_switch", 637 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 638 .pm = &ssam_tablet_sw_pm_ops, 639 }, 640 }; 641 module_ssam_device_driver(ssam_tablet_sw_driver); 642 643 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 644 MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using the Surface Aggregator Module"); 645 MODULE_LICENSE("GPL"); 646