1
2
3
4
5
6
7
8
9
10#include <asm/unaligned.h>
11#include <linux/hid.h>
12#include <linux/kernel.h>
13#include <linux/module.h>
14#include <linux/types.h>
15#include <linux/usb/ch9.h>
16
17#include <linux/surface_aggregator/controller.h>
18
19#include "surface_hid_core.h"
20
21
22
23
24static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
25{
26 int status;
27
28 status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
29 (u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
30 if (status)
31 return status;
32
33 if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) {
34 dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n",
35 shid->hid_desc.desc_len, sizeof(shid->hid_desc));
36 return -EPROTO;
37 }
38
39 if (shid->hid_desc.desc_type != HID_DT_HID) {
40 dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n",
41 shid->hid_desc.desc_type, HID_DT_HID);
42 return -EPROTO;
43 }
44
45 if (shid->hid_desc.num_descriptors != 1) {
46 dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n",
47 shid->hid_desc.num_descriptors);
48 return -EPROTO;
49 }
50
51 if (shid->hid_desc.report_desc_type != HID_DT_REPORT) {
52 dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n",
53 shid->hid_desc.report_desc_type, HID_DT_REPORT);
54 return -EPROTO;
55 }
56
57 return 0;
58}
59
60static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
61{
62 int status;
63
64 status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
65 (u8 *)&shid->attrs, sizeof(shid->attrs));
66 if (status)
67 return status;
68
69 if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) {
70 dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n",
71 get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs));
72 return -EPROTO;
73 }
74
75 return 0;
76}
77
78
79
80
81static int surface_hid_start(struct hid_device *hid)
82{
83 struct surface_hid_device *shid = hid->driver_data;
84
85 return ssam_notifier_register(shid->ctrl, &shid->notif);
86}
87
88static void surface_hid_stop(struct hid_device *hid)
89{
90 struct surface_hid_device *shid = hid->driver_data;
91
92
93 ssam_notifier_unregister(shid->ctrl, &shid->notif);
94}
95
96static int surface_hid_open(struct hid_device *hid)
97{
98 return 0;
99}
100
101static void surface_hid_close(struct hid_device *hid)
102{
103}
104
105static int surface_hid_parse(struct hid_device *hid)
106{
107 struct surface_hid_device *shid = hid->driver_data;
108 size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len);
109 u8 *buf;
110 int status;
111
112 buf = kzalloc(len, GFP_KERNEL);
113 if (!buf)
114 return -ENOMEM;
115
116 status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len);
117 if (!status)
118 status = hid_parse_report(hid, buf, len);
119
120 kfree(buf);
121 return status;
122}
123
124static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf,
125 size_t len, unsigned char rtype, int reqtype)
126{
127 struct surface_hid_device *shid = hid->driver_data;
128
129 if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
130 return shid->ops.output_report(shid, reportnum, buf, len);
131
132 else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT)
133 return shid->ops.get_feature_report(shid, reportnum, buf, len);
134
135 else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT)
136 return shid->ops.set_feature_report(shid, reportnum, buf, len);
137
138 return -EIO;
139}
140
141static struct hid_ll_driver surface_hid_ll_driver = {
142 .start = surface_hid_start,
143 .stop = surface_hid_stop,
144 .open = surface_hid_open,
145 .close = surface_hid_close,
146 .parse = surface_hid_parse,
147 .raw_request = surface_hid_raw_request,
148};
149
150
151
152
153int surface_hid_device_add(struct surface_hid_device *shid)
154{
155 int status;
156
157 status = surface_hid_load_hid_descriptor(shid);
158 if (status)
159 return status;
160
161 status = surface_hid_load_device_attributes(shid);
162 if (status)
163 return status;
164
165 shid->hid = hid_allocate_device();
166 if (IS_ERR(shid->hid))
167 return PTR_ERR(shid->hid);
168
169 shid->hid->dev.parent = shid->dev;
170 shid->hid->bus = BUS_HOST;
171 shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor);
172 shid->hid->product = get_unaligned_le16(&shid->attrs.product);
173 shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version);
174 shid->hid->country = shid->hid_desc.country_code;
175
176 snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
177 shid->hid->vendor, shid->hid->product);
178
179 strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys));
180
181 shid->hid->driver_data = shid;
182 shid->hid->ll_driver = &surface_hid_ll_driver;
183
184 status = hid_add_device(shid->hid);
185 if (status)
186 hid_destroy_device(shid->hid);
187
188 return status;
189}
190EXPORT_SYMBOL_GPL(surface_hid_device_add);
191
192void surface_hid_device_destroy(struct surface_hid_device *shid)
193{
194 hid_destroy_device(shid->hid);
195}
196EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
197
198
199
200
201#ifdef CONFIG_PM_SLEEP
202
203static int surface_hid_suspend(struct device *dev)
204{
205 struct surface_hid_device *d = dev_get_drvdata(dev);
206
207 if (d->hid->driver && d->hid->driver->suspend)
208 return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
209
210 return 0;
211}
212
213static int surface_hid_resume(struct device *dev)
214{
215 struct surface_hid_device *d = dev_get_drvdata(dev);
216
217 if (d->hid->driver && d->hid->driver->resume)
218 return d->hid->driver->resume(d->hid);
219
220 return 0;
221}
222
223static int surface_hid_freeze(struct device *dev)
224{
225 struct surface_hid_device *d = dev_get_drvdata(dev);
226
227 if (d->hid->driver && d->hid->driver->suspend)
228 return d->hid->driver->suspend(d->hid, PMSG_FREEZE);
229
230 return 0;
231}
232
233static int surface_hid_poweroff(struct device *dev)
234{
235 struct surface_hid_device *d = dev_get_drvdata(dev);
236
237 if (d->hid->driver && d->hid->driver->suspend)
238 return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE);
239
240 return 0;
241}
242
243static int surface_hid_restore(struct device *dev)
244{
245 struct surface_hid_device *d = dev_get_drvdata(dev);
246
247 if (d->hid->driver && d->hid->driver->reset_resume)
248 return d->hid->driver->reset_resume(d->hid);
249
250 return 0;
251}
252
253const struct dev_pm_ops surface_hid_pm_ops = {
254 .freeze = surface_hid_freeze,
255 .thaw = surface_hid_resume,
256 .suspend = surface_hid_suspend,
257 .resume = surface_hid_resume,
258 .poweroff = surface_hid_poweroff,
259 .restore = surface_hid_restore,
260};
261EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
262
263#else
264
265const struct dev_pm_ops surface_hid_pm_ops = { };
266EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
267
268#endif
269
270MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
271MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
272MODULE_LICENSE("GPL");
273