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