0001
0002
0003
0004
0005
0006
0007
0008
0009
0010 #include <asm/unaligned.h>
0011 #include <linux/hid.h>
0012 #include <linux/kernel.h>
0013 #include <linux/module.h>
0014 #include <linux/platform_device.h>
0015 #include <linux/types.h>
0016
0017 #include <linux/surface_aggregator/controller.h>
0018
0019 #include "surface_hid_core.h"
0020
0021
0022
0023
0024 #define KBD_FEATURE_REPORT_SIZE 7
0025
0026 enum surface_kbd_cid {
0027 SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00,
0028 SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01,
0029 SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03,
0030 SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04,
0031 SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b,
0032 };
0033
0034 static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
0035 {
0036 struct ssam_request rqst;
0037 struct ssam_response rsp;
0038 int status;
0039
0040 rqst.target_category = shid->uid.category;
0041 rqst.target_id = shid->uid.target;
0042 rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR;
0043 rqst.instance_id = shid->uid.instance;
0044 rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
0045 rqst.length = sizeof(entry);
0046 rqst.payload = &entry;
0047
0048 rsp.capacity = len;
0049 rsp.length = 0;
0050 rsp.pointer = buf;
0051
0052 status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
0053 if (status)
0054 return status;
0055
0056 if (rsp.length != len) {
0057 dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n",
0058 rsp.length, len);
0059 return -EPROTO;
0060 }
0061
0062 return 0;
0063 }
0064
0065 static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value)
0066 {
0067 struct ssam_request rqst;
0068 u8 value_u8 = value;
0069
0070 rqst.target_category = shid->uid.category;
0071 rqst.target_id = shid->uid.target;
0072 rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED;
0073 rqst.instance_id = shid->uid.instance;
0074 rqst.flags = 0;
0075 rqst.length = sizeof(value_u8);
0076 rqst.payload = &value_u8;
0077
0078 return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
0079 }
0080
0081 static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len)
0082 {
0083 struct ssam_request rqst;
0084 struct ssam_response rsp;
0085 u8 payload = 0;
0086 int status;
0087
0088 rqst.target_category = shid->uid.category;
0089 rqst.target_id = shid->uid.target;
0090 rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT;
0091 rqst.instance_id = shid->uid.instance;
0092 rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
0093 rqst.length = sizeof(payload);
0094 rqst.payload = &payload;
0095
0096 rsp.capacity = len;
0097 rsp.length = 0;
0098 rsp.pointer = buf;
0099
0100 status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
0101 if (status)
0102 return status;
0103
0104 if (rsp.length != len) {
0105 dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n",
0106 rsp.length, len);
0107 return -EPROTO;
0108 }
0109
0110 return 0;
0111 }
0112
0113 static bool ssam_kbd_is_input_event(const struct ssam_event *event)
0114 {
0115 if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC)
0116 return true;
0117
0118 if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS)
0119 return true;
0120
0121 return false;
0122 }
0123
0124 static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
0125 {
0126 struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
0127
0128
0129
0130
0131
0132
0133 if (shid->uid.category != event->target_category)
0134 return 0;
0135
0136 if (shid->uid.target != event->target_id)
0137 return 0;
0138
0139 if (shid->uid.instance != event->instance_id)
0140 return 0;
0141
0142 if (!ssam_kbd_is_input_event(event))
0143 return 0;
0144
0145 hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
0146 return SSAM_NOTIF_HANDLED;
0147 }
0148
0149
0150
0151
0152 static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len)
0153 {
0154 struct hid_field *field;
0155 unsigned int offset, size;
0156 int i;
0157
0158
0159 field = hidinput_get_led_field(hid);
0160 if (!field)
0161 return -ENOENT;
0162
0163
0164 if (len != hid_report_len(field->report))
0165 return -ENOENT;
0166
0167 if (rprt_id != field->report->id)
0168 return -ENOENT;
0169
0170
0171 for (i = 0; i < field->report_count; i++)
0172 if ((field->usage[i].hid & 0xffff) == 0x02)
0173 break;
0174
0175 if (i == field->report_count)
0176 return -ENOENT;
0177
0178
0179 size = field->report_size;
0180 offset = field->report_offset + i * size;
0181 return !!hid_field_extract(hid, buf + 1, size, offset);
0182 }
0183
0184 static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
0185 {
0186 int caps_led;
0187 int status;
0188
0189 caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len);
0190 if (caps_led < 0)
0191 return -EIO;
0192
0193 status = ssam_kbd_set_caps_led(shid, caps_led);
0194 if (status < 0)
0195 return status;
0196
0197 return len;
0198 }
0199
0200 static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
0201 {
0202 u8 report[KBD_FEATURE_REPORT_SIZE];
0203 int status;
0204
0205
0206
0207
0208
0209
0210
0211 if (len < ARRAY_SIZE(report))
0212 return -ENOSPC;
0213
0214 status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report));
0215 if (status < 0)
0216 return status;
0217
0218 if (rprt_id != report[0])
0219 return -ENOENT;
0220
0221 memcpy(buf, report, ARRAY_SIZE(report));
0222 return len;
0223 }
0224
0225 static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
0226 {
0227
0228 return -EIO;
0229 }
0230
0231
0232
0233
0234 static int surface_kbd_probe(struct platform_device *pdev)
0235 {
0236 struct ssam_controller *ctrl;
0237 struct surface_hid_device *shid;
0238
0239
0240 ctrl = ssam_client_bind(&pdev->dev);
0241 if (IS_ERR(ctrl))
0242 return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
0243
0244 shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL);
0245 if (!shid)
0246 return -ENOMEM;
0247
0248 shid->dev = &pdev->dev;
0249 shid->ctrl = ctrl;
0250
0251 shid->uid.domain = SSAM_DOMAIN_SERIALHUB;
0252 shid->uid.category = SSAM_SSH_TC_KBD;
0253 shid->uid.target = 2;
0254 shid->uid.instance = 0;
0255 shid->uid.function = 0;
0256
0257 shid->notif.base.priority = 1;
0258 shid->notif.base.fn = ssam_kbd_event_fn;
0259 shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
0260 shid->notif.event.id.target_category = shid->uid.category;
0261 shid->notif.event.id.instance = shid->uid.instance;
0262 shid->notif.event.mask = SSAM_EVENT_MASK_NONE;
0263 shid->notif.event.flags = 0;
0264
0265 shid->ops.get_descriptor = ssam_kbd_get_descriptor;
0266 shid->ops.output_report = skbd_output_report;
0267 shid->ops.get_feature_report = skbd_get_feature_report;
0268 shid->ops.set_feature_report = skbd_set_feature_report;
0269
0270 platform_set_drvdata(pdev, shid);
0271 return surface_hid_device_add(shid);
0272 }
0273
0274 static int surface_kbd_remove(struct platform_device *pdev)
0275 {
0276 surface_hid_device_destroy(platform_get_drvdata(pdev));
0277 return 0;
0278 }
0279
0280 static const struct acpi_device_id surface_kbd_match[] = {
0281 { "MSHW0096" },
0282 { },
0283 };
0284 MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
0285
0286 static struct platform_driver surface_kbd_driver = {
0287 .probe = surface_kbd_probe,
0288 .remove = surface_kbd_remove,
0289 .driver = {
0290 .name = "surface_keyboard",
0291 .acpi_match_table = surface_kbd_match,
0292 .pm = &surface_hid_pm_ops,
0293 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
0294 },
0295 };
0296 module_platform_driver(surface_kbd_driver);
0297
0298 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
0299 MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module");
0300 MODULE_LICENSE("GPL");