1 // SPDX-License-Identifier: GPL-2.0+
3 * Surface System Aggregator Module (SSAM) tablet mode switch driver.
5 * Copyright (C) 2022 Maximilian Luz <luzmaximilian@gmail.com>
8 #include <linux/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>
15 #include <linux/surface_aggregator/controller.h>
16 #include <linux/surface_aggregator/device.h>
19 /* -- SSAM generic tablet switch driver framework. -------------------------- */
21 struct ssam_tablet_sw
;
23 struct ssam_tablet_sw_state
{
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
);
36 struct ssam_tablet_sw
{
37 struct ssam_device
*sdev
;
39 struct ssam_tablet_sw_state state
;
40 struct work_struct update_work
;
41 struct input_dev
*mode_switch
;
43 struct ssam_tablet_sw_ops ops
;
44 struct ssam_event_notifier notif
;
47 struct ssam_tablet_sw_desc
{
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
);
63 struct ssam_event_registry reg
;
64 struct ssam_event_id id
;
65 enum ssam_event_mask mask
;
70 static ssize_t
state_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
)
72 struct ssam_tablet_sw
*sw
= dev_get_drvdata(dev
);
73 const char *state
= sw
->ops
.state_name(sw
, &sw
->state
);
75 return sysfs_emit(buf
, "%s\n", state
);
77 static DEVICE_ATTR_RO(state
);
79 static struct attribute
*ssam_tablet_sw_attrs
[] = {
84 static const struct attribute_group ssam_tablet_sw_group
= {
85 .attrs
= ssam_tablet_sw_attrs
,
88 static void ssam_tablet_sw_update_workfn(struct work_struct
*work
)
90 struct ssam_tablet_sw
*sw
= container_of(work
, struct ssam_tablet_sw
, update_work
);
91 struct ssam_tablet_sw_state state
;
94 status
= sw
->ops
.get_state(sw
, &state
);
98 if (sw
->state
.source
== state
.source
&& sw
->state
.state
== state
.state
)
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
);
108 static int __maybe_unused
ssam_tablet_sw_resume(struct device
*dev
)
110 struct ssam_tablet_sw
*sw
= dev_get_drvdata(dev
);
112 schedule_work(&sw
->update_work
);
115 static SIMPLE_DEV_PM_OPS(ssam_tablet_sw_pm_ops
, NULL
, ssam_tablet_sw_resume
);
117 static int ssam_tablet_sw_probe(struct ssam_device
*sdev
)
119 const struct ssam_tablet_sw_desc
*desc
;
120 struct ssam_tablet_sw
*sw
;
123 desc
= ssam_device_get_match_data(sdev
);
125 WARN(1, "no driver match data specified");
129 sw
= devm_kzalloc(&sdev
->dev
, sizeof(*sw
), GFP_KERNEL
);
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
;
139 INIT_WORK(&sw
->update_work
, ssam_tablet_sw_update_workfn
);
141 ssam_device_set_drvdata(sdev
, sw
);
143 /* Get initial state. */
144 status
= sw
->ops
.get_state(sw
, &sw
->state
);
148 /* Set up tablet mode switch. */
149 sw
->mode_switch
= devm_input_allocate_device(&sdev
->dev
);
150 if (!sw
->mode_switch
)
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
;
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
);
162 status
= input_register_device(sw
->mode_switch
);
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
;
174 status
= ssam_device_notifier_register(sdev
, &sw
->notif
);
178 status
= sysfs_create_group(&sdev
->dev
.kobj
, &ssam_tablet_sw_group
);
182 /* We might have missed events during setup, so check again. */
183 schedule_work(&sw
->update_work
);
187 ssam_device_notifier_unregister(sdev
, &sw
->notif
);
188 cancel_work_sync(&sw
->update_work
);
192 static void ssam_tablet_sw_remove(struct ssam_device
*sdev
)
194 struct ssam_tablet_sw
*sw
= ssam_device_get_drvdata(sdev
);
196 sysfs_remove_group(&sdev
->dev
.kobj
, &ssam_tablet_sw_group
);
198 ssam_device_notifier_unregister(sdev
, &sw
->notif
);
199 cancel_work_sync(&sw
->update_work
);
203 /* -- SSAM KIP tablet switch implementation. -------------------------------- */
205 #define SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED 0x1d
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,
216 static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw
*sw
,
217 const struct ssam_tablet_sw_state
*state
)
219 switch (state
->state
) {
220 case SSAM_KIP_COVER_STATE_DISCONNECTED
:
221 return "disconnected";
223 case SSAM_KIP_COVER_STATE_CLOSED
:
226 case SSAM_KIP_COVER_STATE_LAPTOP
:
229 case SSAM_KIP_COVER_STATE_FOLDED_CANVAS
:
230 return "folded-canvas";
232 case SSAM_KIP_COVER_STATE_FOLDED_BACK
:
233 return "folded-back";
235 case SSAM_KIP_COVER_STATE_BOOK
:
239 dev_warn(&sw
->sdev
->dev
, "unknown KIP cover state: %u\n", state
->state
);
244 static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw
*sw
,
245 const struct ssam_tablet_sw_state
*state
)
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
:
254 case SSAM_KIP_COVER_STATE_CLOSED
:
255 case SSAM_KIP_COVER_STATE_LAPTOP
:
259 dev_warn(&sw
->sdev
->dev
, "unknown KIP cover state: %d\n", state
->state
);
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
,
271 static int ssam_kip_get_cover_state(struct ssam_tablet_sw
*sw
, struct ssam_tablet_sw_state
*state
)
276 status
= ssam_retry(__ssam_kip_get_cover_state
, sw
->sdev
->ctrl
, &raw
);
278 dev_err(&sw
->sdev
->dev
, "failed to query KIP lid state: %d\n", status
);
282 state
->source
= 0; /* Unused for KIP switch. */
287 static u32
ssam_kip_sw_notif(struct ssam_event_notifier
*nf
, const struct ssam_event
*event
)
289 struct ssam_tablet_sw
*sw
= container_of(nf
, struct ssam_tablet_sw
, notif
);
291 if (event
->command_id
!= SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED
)
292 return 0; /* Return "unhandled". */
294 if (event
->length
< 1)
295 dev_warn(&sw
->sdev
->dev
, "unexpected payload size: %u\n", event
->length
);
297 schedule_work(&sw
->update_work
);
298 return SSAM_NOTIF_HANDLED
;
301 static const struct ssam_tablet_sw_desc ssam_kip_sw_desc
= {
303 .name
= "Microsoft Surface KIP Tablet Mode Switch",
304 .phys
= "ssam/01:0e:01:00:01/input0",
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
,
313 .reg
= SSAM_EVENT_REGISTRY_SAM
,
315 .target_category
= SSAM_SSH_TC_KIP
,
318 .mask
= SSAM_EVENT_MASK_TARGET
,
323 /* -- SSAM POS tablet switch implementation. -------------------------------- */
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'");
329 #define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03
330 #define SSAM_POS_MAX_SOURCES 4
332 enum ssam_pos_source_id
{
333 SSAM_POS_SOURCE_COVER
= 0x00,
334 SSAM_POS_SOURCE_SLS
= 0x03,
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,
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,
353 struct ssam_sources_list
{
355 __le32 id
[SSAM_POS_MAX_SOURCES
];
358 static const char *ssam_pos_state_name_cover(struct ssam_tablet_sw
*sw
, u32 state
)
361 case SSAM_POS_COVER_DISCONNECTED
:
362 return "disconnected";
364 case SSAM_POS_COVER_CLOSED
:
367 case SSAM_POS_COVER_LAPTOP
:
370 case SSAM_POS_COVER_FOLDED_CANVAS
:
371 return "folded-canvas";
373 case SSAM_POS_COVER_FOLDED_BACK
:
374 return "folded-back";
376 case SSAM_POS_COVER_BOOK
:
380 dev_warn(&sw
->sdev
->dev
, "unknown device posture for type-cover: %u\n", state
);
385 static const char *ssam_pos_state_name_sls(struct ssam_tablet_sw
*sw
, u32 state
)
388 case SSAM_POS_SLS_LID_CLOSED
:
391 case SSAM_POS_SLS_LAPTOP
:
394 case SSAM_POS_SLS_SLATE
:
397 case SSAM_POS_SLS_TABLET
:
401 dev_warn(&sw
->sdev
->dev
, "unknown device posture for SLS: %u\n", state
);
406 static const char *ssam_pos_state_name(struct ssam_tablet_sw
*sw
,
407 const struct ssam_tablet_sw_state
*state
)
409 switch (state
->source
) {
410 case SSAM_POS_SOURCE_COVER
:
411 return ssam_pos_state_name_cover(sw
, state
->state
);
413 case SSAM_POS_SOURCE_SLS
:
414 return ssam_pos_state_name_sls(sw
, state
->state
);
417 dev_warn(&sw
->sdev
->dev
, "unknown device posture source: %u\n", state
->source
);
422 static bool ssam_pos_state_is_tablet_mode_cover(struct ssam_tablet_sw
*sw
, u32 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
:
431 case SSAM_POS_COVER_CLOSED
:
432 case SSAM_POS_COVER_LAPTOP
:
436 dev_warn(&sw
->sdev
->dev
, "unknown device posture for type-cover: %u\n", state
);
441 static bool ssam_pos_state_is_tablet_mode_sls(struct ssam_tablet_sw
*sw
, u32 state
)
444 case SSAM_POS_SLS_LAPTOP
:
445 case SSAM_POS_SLS_LID_CLOSED
:
448 case SSAM_POS_SLS_SLATE
:
449 return tablet_mode_in_slate_state
;
451 case SSAM_POS_SLS_TABLET
:
455 dev_warn(&sw
->sdev
->dev
, "unknown device posture for SLS: %u\n", state
);
460 static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw
*sw
,
461 const struct ssam_tablet_sw_state
*state
)
463 switch (state
->source
) {
464 case SSAM_POS_SOURCE_COVER
:
465 return ssam_pos_state_is_tablet_mode_cover(sw
, state
->state
);
467 case SSAM_POS_SOURCE_SLS
:
468 return ssam_pos_state_is_tablet_mode_sls(sw
, state
->state
);
471 dev_warn(&sw
->sdev
->dev
, "unknown device posture source: %u\n", state
->source
);
476 static int ssam_pos_get_sources_list(struct ssam_tablet_sw
*sw
, struct ssam_sources_list
*sources
)
478 struct ssam_request rqst
;
479 struct ssam_response rsp
;
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
;
490 rsp
.capacity
= sizeof(*sources
);
492 rsp
.pointer
= (u8
*)sources
;
494 status
= ssam_retry(ssam_request_do_sync_onstack
, sw
->sdev
->ctrl
, &rqst
, &rsp
, 0);
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");
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");
513 static int ssam_pos_get_source(struct ssam_tablet_sw
*sw
, u32
*source_id
)
515 struct ssam_sources_list sources
= {};
518 status
= ssam_pos_get_sources_list(sw
, &sources
);
522 if (get_unaligned_le32(&sources
.count
) == 0) {
523 dev_err(&sw
->sdev
->dev
, "no posture sources found\n");
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.
534 WARN_ON(get_unaligned_le32(&sources
.count
) > 1);
536 *source_id
= get_unaligned_le32(&sources
.id
[0]);
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
,
547 static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw
*sw
, u32 source_id
, u32
*posture
)
549 __le32 source_le
= cpu_to_le32(source_id
);
550 __le32 rspval_le
= 0;
553 status
= ssam_retry(__ssam_pos_get_posture_for_source
, sw
->sdev
->ctrl
,
554 &source_le
, &rspval_le
);
558 *posture
= le32_to_cpu(rspval_le
);
562 static int ssam_pos_get_posture(struct ssam_tablet_sw
*sw
, struct ssam_tablet_sw_state
*state
)
568 status
= ssam_pos_get_source(sw
, &source_id
);
570 dev_err(&sw
->sdev
->dev
, "failed to get posture source ID: %d\n", status
);
574 status
= ssam_pos_get_posture_for_source(sw
, source_id
, &source_state
);
576 dev_err(&sw
->sdev
->dev
, "failed to get posture value for source %u: %d\n",
581 state
->source
= source_id
;
582 state
->state
= source_state
;
586 static u32
ssam_pos_sw_notif(struct ssam_event_notifier
*nf
, const struct ssam_event
*event
)
588 struct ssam_tablet_sw
*sw
= container_of(nf
, struct ssam_tablet_sw
, notif
);
590 if (event
->command_id
!= SSAM_EVENT_POS_CID_POSTURE_CHANGED
)
591 return 0; /* Return "unhandled". */
593 if (event
->length
!= sizeof(__le32
) * 3)
594 dev_warn(&sw
->sdev
->dev
, "unexpected payload size: %u\n", event
->length
);
596 schedule_work(&sw
->update_work
);
597 return SSAM_NOTIF_HANDLED
;
600 static const struct ssam_tablet_sw_desc ssam_pos_sw_desc
= {
602 .name
= "Microsoft Surface POS Tablet Mode Switch",
603 .phys
= "ssam/01:26:01:00:01/input0",
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
,
612 .reg
= SSAM_EVENT_REGISTRY_SAM
,
614 .target_category
= SSAM_SSH_TC_POS
,
617 .mask
= SSAM_EVENT_MASK_TARGET
,
622 /* -- Driver registration. -------------------------------------------------- */
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
},
629 MODULE_DEVICE_TABLE(ssam
, ssam_tablet_sw_match
);
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
,
636 .name
= "surface_aggregator_tablet_mode_switch",
637 .probe_type
= PROBE_PREFER_ASYNCHRONOUS
,
638 .pm
= &ssam_tablet_sw_pm_ops
,
641 module_ssam_device_driver(ssam_tablet_sw_driver
);
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");