1*35a927f2SMaximilian Luz // SPDX-License-Identifier: GPL-2.0+ 2*35a927f2SMaximilian Luz /* 3*35a927f2SMaximilian Luz * Surface System Aggregator Module (SSAM) HID transport driver for the legacy 4*35a927f2SMaximilian Luz * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the 5*35a927f2SMaximilian Luz * integrated HID keyboard on Surface Laptops 1 and 2. 6*35a927f2SMaximilian Luz * 7*35a927f2SMaximilian Luz * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> 8*35a927f2SMaximilian Luz */ 9*35a927f2SMaximilian Luz 10*35a927f2SMaximilian Luz #include <asm/unaligned.h> 11*35a927f2SMaximilian Luz #include <linux/hid.h> 12*35a927f2SMaximilian Luz #include <linux/kernel.h> 13*35a927f2SMaximilian Luz #include <linux/module.h> 14*35a927f2SMaximilian Luz #include <linux/platform_device.h> 15*35a927f2SMaximilian Luz #include <linux/types.h> 16*35a927f2SMaximilian Luz 17*35a927f2SMaximilian Luz #include <linux/surface_aggregator/controller.h> 18*35a927f2SMaximilian Luz 19*35a927f2SMaximilian Luz #include "surface_hid_core.h" 20*35a927f2SMaximilian Luz 21*35a927f2SMaximilian Luz 22*35a927f2SMaximilian Luz /* -- SAM interface (KBD). -------------------------------------------------- */ 23*35a927f2SMaximilian Luz 24*35a927f2SMaximilian Luz #define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */ 25*35a927f2SMaximilian Luz 26*35a927f2SMaximilian Luz enum surface_kbd_cid { 27*35a927f2SMaximilian Luz SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00, 28*35a927f2SMaximilian Luz SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01, 29*35a927f2SMaximilian Luz SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03, 30*35a927f2SMaximilian Luz SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04, 31*35a927f2SMaximilian Luz SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b, 32*35a927f2SMaximilian Luz }; 33*35a927f2SMaximilian Luz 34*35a927f2SMaximilian Luz static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) 35*35a927f2SMaximilian Luz { 36*35a927f2SMaximilian Luz struct ssam_request rqst; 37*35a927f2SMaximilian Luz struct ssam_response rsp; 38*35a927f2SMaximilian Luz int status; 39*35a927f2SMaximilian Luz 40*35a927f2SMaximilian Luz rqst.target_category = shid->uid.category; 41*35a927f2SMaximilian Luz rqst.target_id = shid->uid.target; 42*35a927f2SMaximilian Luz rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; 43*35a927f2SMaximilian Luz rqst.instance_id = shid->uid.instance; 44*35a927f2SMaximilian Luz rqst.flags = SSAM_REQUEST_HAS_RESPONSE; 45*35a927f2SMaximilian Luz rqst.length = sizeof(entry); 46*35a927f2SMaximilian Luz rqst.payload = &entry; 47*35a927f2SMaximilian Luz 48*35a927f2SMaximilian Luz rsp.capacity = len; 49*35a927f2SMaximilian Luz rsp.length = 0; 50*35a927f2SMaximilian Luz rsp.pointer = buf; 51*35a927f2SMaximilian Luz 52*35a927f2SMaximilian Luz status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry)); 53*35a927f2SMaximilian Luz if (status) 54*35a927f2SMaximilian Luz return status; 55*35a927f2SMaximilian Luz 56*35a927f2SMaximilian Luz if (rsp.length != len) { 57*35a927f2SMaximilian Luz dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n", 58*35a927f2SMaximilian Luz rsp.length, len); 59*35a927f2SMaximilian Luz return -EPROTO; 60*35a927f2SMaximilian Luz } 61*35a927f2SMaximilian Luz 62*35a927f2SMaximilian Luz return 0; 63*35a927f2SMaximilian Luz } 64*35a927f2SMaximilian Luz 65*35a927f2SMaximilian Luz static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value) 66*35a927f2SMaximilian Luz { 67*35a927f2SMaximilian Luz struct ssam_request rqst; 68*35a927f2SMaximilian Luz u8 value_u8 = value; 69*35a927f2SMaximilian Luz 70*35a927f2SMaximilian Luz rqst.target_category = shid->uid.category; 71*35a927f2SMaximilian Luz rqst.target_id = shid->uid.target; 72*35a927f2SMaximilian Luz rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED; 73*35a927f2SMaximilian Luz rqst.instance_id = shid->uid.instance; 74*35a927f2SMaximilian Luz rqst.flags = 0; 75*35a927f2SMaximilian Luz rqst.length = sizeof(value_u8); 76*35a927f2SMaximilian Luz rqst.payload = &value_u8; 77*35a927f2SMaximilian Luz 78*35a927f2SMaximilian Luz return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8)); 79*35a927f2SMaximilian Luz } 80*35a927f2SMaximilian Luz 81*35a927f2SMaximilian Luz static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len) 82*35a927f2SMaximilian Luz { 83*35a927f2SMaximilian Luz struct ssam_request rqst; 84*35a927f2SMaximilian Luz struct ssam_response rsp; 85*35a927f2SMaximilian Luz u8 payload = 0; 86*35a927f2SMaximilian Luz int status; 87*35a927f2SMaximilian Luz 88*35a927f2SMaximilian Luz rqst.target_category = shid->uid.category; 89*35a927f2SMaximilian Luz rqst.target_id = shid->uid.target; 90*35a927f2SMaximilian Luz rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT; 91*35a927f2SMaximilian Luz rqst.instance_id = shid->uid.instance; 92*35a927f2SMaximilian Luz rqst.flags = SSAM_REQUEST_HAS_RESPONSE; 93*35a927f2SMaximilian Luz rqst.length = sizeof(payload); 94*35a927f2SMaximilian Luz rqst.payload = &payload; 95*35a927f2SMaximilian Luz 96*35a927f2SMaximilian Luz rsp.capacity = len; 97*35a927f2SMaximilian Luz rsp.length = 0; 98*35a927f2SMaximilian Luz rsp.pointer = buf; 99*35a927f2SMaximilian Luz 100*35a927f2SMaximilian Luz status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload)); 101*35a927f2SMaximilian Luz if (status) 102*35a927f2SMaximilian Luz return status; 103*35a927f2SMaximilian Luz 104*35a927f2SMaximilian Luz if (rsp.length != len) { 105*35a927f2SMaximilian Luz dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n", 106*35a927f2SMaximilian Luz rsp.length, len); 107*35a927f2SMaximilian Luz return -EPROTO; 108*35a927f2SMaximilian Luz } 109*35a927f2SMaximilian Luz 110*35a927f2SMaximilian Luz return 0; 111*35a927f2SMaximilian Luz } 112*35a927f2SMaximilian Luz 113*35a927f2SMaximilian Luz static bool ssam_kbd_is_input_event(const struct ssam_event *event) 114*35a927f2SMaximilian Luz { 115*35a927f2SMaximilian Luz if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC) 116*35a927f2SMaximilian Luz return true; 117*35a927f2SMaximilian Luz 118*35a927f2SMaximilian Luz if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS) 119*35a927f2SMaximilian Luz return true; 120*35a927f2SMaximilian Luz 121*35a927f2SMaximilian Luz return false; 122*35a927f2SMaximilian Luz } 123*35a927f2SMaximilian Luz 124*35a927f2SMaximilian Luz static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) 125*35a927f2SMaximilian Luz { 126*35a927f2SMaximilian Luz struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); 127*35a927f2SMaximilian Luz 128*35a927f2SMaximilian Luz /* 129*35a927f2SMaximilian Luz * Check against device UID manually, as registry and device target 130*35a927f2SMaximilian Luz * category doesn't line up. 131*35a927f2SMaximilian Luz */ 132*35a927f2SMaximilian Luz 133*35a927f2SMaximilian Luz if (shid->uid.category != event->target_category) 134*35a927f2SMaximilian Luz return 0; 135*35a927f2SMaximilian Luz 136*35a927f2SMaximilian Luz if (shid->uid.target != event->target_id) 137*35a927f2SMaximilian Luz return 0; 138*35a927f2SMaximilian Luz 139*35a927f2SMaximilian Luz if (shid->uid.instance != event->instance_id) 140*35a927f2SMaximilian Luz return 0; 141*35a927f2SMaximilian Luz 142*35a927f2SMaximilian Luz if (!ssam_kbd_is_input_event(event)) 143*35a927f2SMaximilian Luz return 0; 144*35a927f2SMaximilian Luz 145*35a927f2SMaximilian Luz hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); 146*35a927f2SMaximilian Luz return SSAM_NOTIF_HANDLED; 147*35a927f2SMaximilian Luz } 148*35a927f2SMaximilian Luz 149*35a927f2SMaximilian Luz 150*35a927f2SMaximilian Luz /* -- Transport driver (KBD). ----------------------------------------------- */ 151*35a927f2SMaximilian Luz 152*35a927f2SMaximilian Luz static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len) 153*35a927f2SMaximilian Luz { 154*35a927f2SMaximilian Luz struct hid_field *field; 155*35a927f2SMaximilian Luz unsigned int offset, size; 156*35a927f2SMaximilian Luz int i; 157*35a927f2SMaximilian Luz 158*35a927f2SMaximilian Luz /* Get LED field. */ 159*35a927f2SMaximilian Luz field = hidinput_get_led_field(hid); 160*35a927f2SMaximilian Luz if (!field) 161*35a927f2SMaximilian Luz return -ENOENT; 162*35a927f2SMaximilian Luz 163*35a927f2SMaximilian Luz /* Check if we got the correct report. */ 164*35a927f2SMaximilian Luz if (len != hid_report_len(field->report)) 165*35a927f2SMaximilian Luz return -ENOENT; 166*35a927f2SMaximilian Luz 167*35a927f2SMaximilian Luz if (rprt_id != field->report->id) 168*35a927f2SMaximilian Luz return -ENOENT; 169*35a927f2SMaximilian Luz 170*35a927f2SMaximilian Luz /* Get caps lock LED index. */ 171*35a927f2SMaximilian Luz for (i = 0; i < field->report_count; i++) 172*35a927f2SMaximilian Luz if ((field->usage[i].hid & 0xffff) == 0x02) 173*35a927f2SMaximilian Luz break; 174*35a927f2SMaximilian Luz 175*35a927f2SMaximilian Luz if (i == field->report_count) 176*35a927f2SMaximilian Luz return -ENOENT; 177*35a927f2SMaximilian Luz 178*35a927f2SMaximilian Luz /* Extract value. */ 179*35a927f2SMaximilian Luz size = field->report_size; 180*35a927f2SMaximilian Luz offset = field->report_offset + i * size; 181*35a927f2SMaximilian Luz return !!hid_field_extract(hid, buf + 1, size, offset); 182*35a927f2SMaximilian Luz } 183*35a927f2SMaximilian Luz 184*35a927f2SMaximilian Luz static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) 185*35a927f2SMaximilian Luz { 186*35a927f2SMaximilian Luz int caps_led; 187*35a927f2SMaximilian Luz int status; 188*35a927f2SMaximilian Luz 189*35a927f2SMaximilian Luz caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len); 190*35a927f2SMaximilian Luz if (caps_led < 0) 191*35a927f2SMaximilian Luz return -EIO; /* Only caps LED output reports are supported. */ 192*35a927f2SMaximilian Luz 193*35a927f2SMaximilian Luz status = ssam_kbd_set_caps_led(shid, caps_led); 194*35a927f2SMaximilian Luz if (status < 0) 195*35a927f2SMaximilian Luz return status; 196*35a927f2SMaximilian Luz 197*35a927f2SMaximilian Luz return len; 198*35a927f2SMaximilian Luz } 199*35a927f2SMaximilian Luz 200*35a927f2SMaximilian Luz static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) 201*35a927f2SMaximilian Luz { 202*35a927f2SMaximilian Luz u8 report[KBD_FEATURE_REPORT_SIZE]; 203*35a927f2SMaximilian Luz int status; 204*35a927f2SMaximilian Luz 205*35a927f2SMaximilian Luz /* 206*35a927f2SMaximilian Luz * The keyboard only has a single hard-coded read-only feature report 207*35a927f2SMaximilian Luz * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its 208*35a927f2SMaximilian Luz * report ID against the requested one. 209*35a927f2SMaximilian Luz */ 210*35a927f2SMaximilian Luz 211*35a927f2SMaximilian Luz if (len < ARRAY_SIZE(report)) 212*35a927f2SMaximilian Luz return -ENOSPC; 213*35a927f2SMaximilian Luz 214*35a927f2SMaximilian Luz status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report)); 215*35a927f2SMaximilian Luz if (status < 0) 216*35a927f2SMaximilian Luz return status; 217*35a927f2SMaximilian Luz 218*35a927f2SMaximilian Luz if (rprt_id != report[0]) 219*35a927f2SMaximilian Luz return -ENOENT; 220*35a927f2SMaximilian Luz 221*35a927f2SMaximilian Luz memcpy(buf, report, ARRAY_SIZE(report)); 222*35a927f2SMaximilian Luz return len; 223*35a927f2SMaximilian Luz } 224*35a927f2SMaximilian Luz 225*35a927f2SMaximilian Luz static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) 226*35a927f2SMaximilian Luz { 227*35a927f2SMaximilian Luz /* Not supported. See skbd_get_feature_report() for details. */ 228*35a927f2SMaximilian Luz return -EIO; 229*35a927f2SMaximilian Luz } 230*35a927f2SMaximilian Luz 231*35a927f2SMaximilian Luz 232*35a927f2SMaximilian Luz /* -- Driver setup. --------------------------------------------------------- */ 233*35a927f2SMaximilian Luz 234*35a927f2SMaximilian Luz static int surface_kbd_probe(struct platform_device *pdev) 235*35a927f2SMaximilian Luz { 236*35a927f2SMaximilian Luz struct ssam_controller *ctrl; 237*35a927f2SMaximilian Luz struct surface_hid_device *shid; 238*35a927f2SMaximilian Luz 239*35a927f2SMaximilian Luz /* Add device link to EC. */ 240*35a927f2SMaximilian Luz ctrl = ssam_client_bind(&pdev->dev); 241*35a927f2SMaximilian Luz if (IS_ERR(ctrl)) 242*35a927f2SMaximilian Luz return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); 243*35a927f2SMaximilian Luz 244*35a927f2SMaximilian Luz shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); 245*35a927f2SMaximilian Luz if (!shid) 246*35a927f2SMaximilian Luz return -ENOMEM; 247*35a927f2SMaximilian Luz 248*35a927f2SMaximilian Luz shid->dev = &pdev->dev; 249*35a927f2SMaximilian Luz shid->ctrl = ctrl; 250*35a927f2SMaximilian Luz 251*35a927f2SMaximilian Luz shid->uid.domain = SSAM_DOMAIN_SERIALHUB; 252*35a927f2SMaximilian Luz shid->uid.category = SSAM_SSH_TC_KBD; 253*35a927f2SMaximilian Luz shid->uid.target = 2; 254*35a927f2SMaximilian Luz shid->uid.instance = 0; 255*35a927f2SMaximilian Luz shid->uid.function = 0; 256*35a927f2SMaximilian Luz 257*35a927f2SMaximilian Luz shid->notif.base.priority = 1; 258*35a927f2SMaximilian Luz shid->notif.base.fn = ssam_kbd_event_fn; 259*35a927f2SMaximilian Luz shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; 260*35a927f2SMaximilian Luz shid->notif.event.id.target_category = shid->uid.category; 261*35a927f2SMaximilian Luz shid->notif.event.id.instance = shid->uid.instance; 262*35a927f2SMaximilian Luz shid->notif.event.mask = SSAM_EVENT_MASK_NONE; 263*35a927f2SMaximilian Luz shid->notif.event.flags = 0; 264*35a927f2SMaximilian Luz 265*35a927f2SMaximilian Luz shid->ops.get_descriptor = ssam_kbd_get_descriptor; 266*35a927f2SMaximilian Luz shid->ops.output_report = skbd_output_report; 267*35a927f2SMaximilian Luz shid->ops.get_feature_report = skbd_get_feature_report; 268*35a927f2SMaximilian Luz shid->ops.set_feature_report = skbd_set_feature_report; 269*35a927f2SMaximilian Luz 270*35a927f2SMaximilian Luz platform_set_drvdata(pdev, shid); 271*35a927f2SMaximilian Luz return surface_hid_device_add(shid); 272*35a927f2SMaximilian Luz } 273*35a927f2SMaximilian Luz 274*35a927f2SMaximilian Luz static int surface_kbd_remove(struct platform_device *pdev) 275*35a927f2SMaximilian Luz { 276*35a927f2SMaximilian Luz surface_hid_device_destroy(platform_get_drvdata(pdev)); 277*35a927f2SMaximilian Luz return 0; 278*35a927f2SMaximilian Luz } 279*35a927f2SMaximilian Luz 280*35a927f2SMaximilian Luz static const struct acpi_device_id surface_kbd_match[] = { 281*35a927f2SMaximilian Luz { "MSHW0096" }, 282*35a927f2SMaximilian Luz { }, 283*35a927f2SMaximilian Luz }; 284*35a927f2SMaximilian Luz MODULE_DEVICE_TABLE(acpi, surface_kbd_match); 285*35a927f2SMaximilian Luz 286*35a927f2SMaximilian Luz static struct platform_driver surface_kbd_driver = { 287*35a927f2SMaximilian Luz .probe = surface_kbd_probe, 288*35a927f2SMaximilian Luz .remove = surface_kbd_remove, 289*35a927f2SMaximilian Luz .driver = { 290*35a927f2SMaximilian Luz .name = "surface_keyboard", 291*35a927f2SMaximilian Luz .acpi_match_table = surface_kbd_match, 292*35a927f2SMaximilian Luz .pm = &surface_hid_pm_ops, 293*35a927f2SMaximilian Luz .probe_type = PROBE_PREFER_ASYNCHRONOUS, 294*35a927f2SMaximilian Luz }, 295*35a927f2SMaximilian Luz }; 296*35a927f2SMaximilian Luz module_platform_driver(surface_kbd_driver); 297*35a927f2SMaximilian Luz 298*35a927f2SMaximilian Luz MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 299*35a927f2SMaximilian Luz MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module"); 300*35a927f2SMaximilian Luz MODULE_LICENSE("GPL"); 301