1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
5 * Copyright (c) 2026 Christos Longros <chris.longros@gmail.com>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 /*
30 * Sony PS5 DualSense driver
31 * https://controllers.fandom.com/wiki/Sony_DualSense
32 * https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c
33 *
34 * Touchpad and motion sensors are not supported yet.
35 */
36
37 #include "opt_hid.h"
38
39 #include <sys/param.h>
40 #include <sys/bus.h>
41 #include <sys/conf.h>
42 #include <sys/kernel.h>
43 #include <sys/lock.h>
44 #include <sys/module.h>
45 #include <sys/stat.h>
46 #include <sys/sx.h>
47 #include <sys/sysctl.h>
48
49 #include <dev/evdev/input.h>
50 #include <dev/evdev/evdev.h>
51
52 #define HID_DEBUG_VAR ps5ds_debug
53 #include <dev/hid/hgame.h>
54 #include <dev/hid/hid.h>
55 #include <dev/hid/hidbus.h>
56 #include <dev/hid/hidquirk.h>
57 #include <dev/hid/hidmap.h>
58 #include "usbdevs.h"
59
60 #ifdef HID_DEBUG
61 static int ps5ds_debug = 1;
62
63 static SYSCTL_NODE(_hw_hid, OID_AUTO, ps5dsense, CTLFLAG_RW, 0,
64 "Sony PS5 DualSense Gamepad");
65 SYSCTL_INT(_hw_hid_ps5dsense, OID_AUTO, debug, CTLFLAG_RWTUN,
66 &ps5ds_debug, 0, "Debug level");
67 #endif
68
69 /*
70 * Fixed HID report descriptor. Output is 62 bytes, not 47: shorter reports
71 * are ignored.
72 */
73 static const uint8_t ps5ds_rdesc[] = {
74 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
75 0x09, 0x05, /* Usage (Game Pad) */
76 0xA1, 0x01, /* Collection (Application) */
77 0x85, 0x01, /* Report ID (1) */
78 0x09, 0x30, /* Usage (X) - left stick X */
79 0x09, 0x31, /* Usage (Y) - left stick Y */
80 0x09, 0x33, /* Usage (Rx) - right stick X */
81 0x09, 0x34, /* Usage (Ry) - right stick Y */
82 0x15, 0x00, /* Logical Minimum (0) */
83 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
84 0x75, 0x08, /* Report Size (8) */
85 0x95, 0x04, /* Report Count (4) */
86 0x81, 0x02, /* Input (Data,Var,Abs) */
87 0x09, 0x32, /* Usage (Z) - L2 trigger */
88 0x09, 0x35, /* Usage (Rz) - R2 trigger */
89 0x15, 0x00, /* Logical Minimum (0) */
90 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
91 0x75, 0x08, /* Report Size (8) */
92 0x95, 0x02, /* Report Count (2) */
93 0x81, 0x02, /* Input (Data,Var,Abs) */
94 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */
95 0x09, 0x20, /* Usage (0x20) */
96 0x15, 0x00, /* Logical Minimum (0) */
97 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
98 0x75, 0x08, /* Report Size (8) */
99 0x95, 0x01, /* Report Count (1) */
100 0x81, 0x02, /* Input (Data,Var,Abs) - counter */
101 0x05, 0x01, /* Usage Page (Generic Desktop) */
102 0x09, 0x39, /* Usage (Hat switch) */
103 0x15, 0x00, /* Logical Minimum (0) */
104 0x25, 0x07, /* Logical Maximum (7) */
105 0x35, 0x00, /* Physical Minimum (0) */
106 0x46, 0x3B, 0x01, /* Physical Maximum (315) */
107 0x65, 0x14, /* Unit (Eng Rot: Degree) */
108 0x75, 0x04, /* Report Size (4) */
109 0x95, 0x01, /* Report Count (1) */
110 0x81, 0x42, /* Input (Data,Var,Abs,Null State) */
111 0x65, 0x00, /* Unit (None) */
112 0x45, 0x00, /* Physical Maximum (0) */
113 0x05, 0x09, /* Usage Page (Button) */
114 0x19, 0x01, /* Usage Minimum (0x01) */
115 0x29, 0x0F, /* Usage Maximum (0x0F) */
116 0x15, 0x00, /* Logical Minimum (0) */
117 0x25, 0x01, /* Logical Maximum (1) */
118 0x75, 0x01, /* Report Size (1) */
119 0x95, 0x0F, /* Report Count (15) */
120 0x81, 0x02, /* Input (Data,Var,Abs) */
121 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */
122 0x09, 0x21, /* Usage (0x21) */
123 0x75, 0x01, /* Report Size (1) */
124 0x95, 0x0D, /* Report Count (13) */
125 0x81, 0x03, /* Input (Const) - unused button bits */
126 0x09, 0x22, /* Usage (0x22) */
127 0x15, 0x00, /* Logical Minimum (0) */
128 0x26, 0xFF, 0x00, /* Logical Maximum (255) */
129 0x75, 0x08, /* Report Size (8) */
130 0x95, 0x34, /* Report Count (52) */
131 0x81, 0x03, /* Input (Const) - sensors, touchpad */
132 /* Output report (63 bytes total) for LED and rumble control */
133 0x85, 0x02, /* Report ID (2) */
134 0x09, 0x23, /* Usage (0x23) */
135 0x75, 0x08, /* Report Size (8) */
136 0x95, 0x3E, /* Report Count (62) */
137 0x91, 0x02, /* Output (Data,Var,Abs) */
138 0xC0, /* End Collection */
139 };
140
141 #define PS5DS_OUTPUT_REPORT2_SIZE 63
142
143 struct ps5ds_out2 {
144 uint8_t valid_flag0;
145 uint8_t valid_flag1;
146 uint8_t rumble_right;
147 uint8_t rumble_left;
148 uint8_t reserved1[4];
149 uint8_t mute_button_led;
150 uint8_t power_save_control;
151 uint8_t reserved2[28];
152 uint8_t valid_flag2;
153 uint8_t reserved3[2];
154 uint8_t lightbar_setup;
155 uint8_t led_brightness;
156 uint8_t player_leds;
157 uint8_t led_color_r;
158 uint8_t led_color_g;
159 uint8_t led_color_b;
160 } __attribute__((packed));
161
162 #define PS5DS_FLAG1_LIGHTBAR 0x04
163 #define PS5DS_FLAG1_PLAYER_LEDS 0x10
164 #define PS5DS_FLAG2_LIGHTBAR_SETUP 0x02
165 #define PS5DS_LIGHTBAR_FADE_OUT 0x02
166
167 static const uint8_t ps5ds_player_leds[] = {
168 0x04, /* Player 1: --x-- */
169 0x0A, /* Player 2: -x-x- */
170 0x15, /* Player 3: x-x-x */
171 0x1B, /* Player 4: xx-xx */
172 0x1F, /* Player 5: xxxxx */
173 };
174
175 static const struct ps5ds_led {
176 int r;
177 int g;
178 int b;
179 } ps5ds_leds[] = {
180 /* The first 4 entries match the PS4, other from Linux driver */
181 { 0x00, 0x00, 0x40 }, /* Blue */
182 { 0x40, 0x00, 0x00 }, /* Red */
183 { 0x00, 0x40, 0x00 }, /* Green */
184 { 0x20, 0x00, 0x20 }, /* Pink */
185 { 0x02, 0x01, 0x00 }, /* Orange */
186 { 0x00, 0x01, 0x01 }, /* Teal */
187 { 0x01, 0x01, 0x01 } /* White */
188 };
189
190 enum ps5ds_led_state {
191 PS5DS_LED_OFF,
192 PS5DS_LED_ON,
193 PS5DS_LED_CNT,
194 };
195
196 struct ps5ds_softc {
197 struct hidmap hm;
198
199 struct sx lock;
200 enum ps5ds_led_state led_state;
201 struct ps5ds_led led_color;
202 int player_leds; /* 0-4: player number, -1: off */
203 };
204
205 #define PS5DS_OFFSET(field) offsetof(struct ps5ds_softc, field)
206 enum {
207 PS5DS_SYSCTL_LED_STATE = PS5DS_OFFSET(led_state),
208 PS5DS_SYSCTL_LED_COLOR_R = PS5DS_OFFSET(led_color.r),
209 PS5DS_SYSCTL_LED_COLOR_G = PS5DS_OFFSET(led_color.g),
210 PS5DS_SYSCTL_LED_COLOR_B = PS5DS_OFFSET(led_color.b),
211 PS5DS_SYSCTL_PLAYER_LEDS = PS5DS_OFFSET(player_leds),
212 #define PS5DS_SYSCTL_LAST PS5DS_SYSCTL_PLAYER_LEDS
213 };
214
215 #define PS5DS_MAP_BTN(number, code) \
216 { HIDMAP_KEY(HUP_BUTTON, number, code) }
217 #define PS5DS_MAP_ABS(usage, code) \
218 { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) }
219 #define PS5DS_MAP_FLT(usage, code) \
220 { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code), .flat = 15 }
221 #define PS5DS_MAP_GCB(usage, callback) \
222 { HIDMAP_ANY_CB(HUP_GENERIC_DESKTOP, HUG_##usage, callback) }
223 #define PS5DS_FINALCB(cb) \
224 { HIDMAP_FINAL_CB(&cb) }
225
226 static hidmap_cb_t ps5ds_final_cb;
227
228 static const struct hidmap_item ps5ds_map[] = {
229 PS5DS_MAP_FLT(X, ABS_X),
230 PS5DS_MAP_FLT(Y, ABS_Y),
231 PS5DS_MAP_ABS(Z, ABS_Z),
232 PS5DS_MAP_ABS(RZ, ABS_RZ),
233 PS5DS_MAP_FLT(RX, ABS_RX),
234 PS5DS_MAP_FLT(RY, ABS_RY),
235 PS5DS_MAP_BTN(1, BTN_WEST),
236 PS5DS_MAP_BTN(2, BTN_SOUTH),
237 PS5DS_MAP_BTN(3, BTN_EAST),
238 PS5DS_MAP_BTN(4, BTN_NORTH),
239 PS5DS_MAP_BTN(5, BTN_TL),
240 PS5DS_MAP_BTN(6, BTN_TR),
241 PS5DS_MAP_BTN(7, BTN_TL2),
242 PS5DS_MAP_BTN(8, BTN_TR2),
243 PS5DS_MAP_BTN(9, BTN_SELECT),
244 PS5DS_MAP_BTN(10, BTN_START),
245 PS5DS_MAP_BTN(11, BTN_THUMBL),
246 PS5DS_MAP_BTN(12, BTN_THUMBR),
247 PS5DS_MAP_BTN(13, BTN_MODE),
248 /* Click button is handled by touchpad driver */
249 /* PS5DS_MAP_BTN(14, BTN_LEFT), */
250 /* Linux mutes the microphone in hardware and emits no event */
251 PS5DS_MAP_BTN(15, KEY_MICMUTE),
252 PS5DS_MAP_GCB(HAT_SWITCH, hgame_hat_switch_cb),
253 PS5DS_FINALCB( ps5ds_final_cb),
254 };
255
256 static const struct hid_device_id ps5ds_devs[] = {
257 { HID_BVP(BUS_USB, USB_VENDOR_SONY, 0x0ce6),
258 HID_TLC(HUP_GENERIC_DESKTOP, HUG_GAME_PAD) },
259 };
260
261 static int
ps5ds_final_cb(HIDMAP_CB_ARGS)262 ps5ds_final_cb(HIDMAP_CB_ARGS)
263 {
264 struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV();
265
266 if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) {
267 evdev_support_prop(evdev, INPUT_PROP_DIRECT);
268 evdev_set_cdev_mode(evdev, UID_ROOT, GID_GAMES,
269 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
270 }
271
272 /* Do not execute callback at interrupt handler and detach */
273 return (ENOSYS);
274 }
275
276 static int
ps5ds_write(struct ps5ds_softc * sc)277 ps5ds_write(struct ps5ds_softc *sc)
278 {
279 uint8_t buf[PS5DS_OUTPUT_REPORT2_SIZE];
280 bool led_on;
281
282 sx_assert(&sc->lock, SA_XLOCKED);
283
284 memset(buf, 0, sizeof(buf));
285 buf[0] = 0x02;
286 led_on = sc->led_state != PS5DS_LED_OFF;
287 *(struct ps5ds_out2 *)(buf + 1) = (struct ps5ds_out2) {
288 .valid_flag1 = PS5DS_FLAG1_LIGHTBAR | PS5DS_FLAG1_PLAYER_LEDS,
289 .player_leds = sc->player_leds < 0 ?
290 0 : ps5ds_player_leds[sc->player_leds],
291 .led_color_r = led_on ? sc->led_color.r : 0,
292 .led_color_g = led_on ? sc->led_color.g : 0,
293 .led_color_b = led_on ? sc->led_color.b : 0,
294 };
295
296 return (hid_write(sc->hm.dev, buf, sizeof(buf)));
297 }
298
299 static int
ps5ds_blank(struct ps5ds_softc * sc)300 ps5ds_blank(struct ps5ds_softc *sc)
301 {
302 uint8_t buf[PS5DS_OUTPUT_REPORT2_SIZE];
303
304 memset(buf, 0, sizeof(buf));
305 buf[0] = 0x02;
306 *(struct ps5ds_out2 *)(buf + 1) = (struct ps5ds_out2) {
307 .valid_flag1 = PS5DS_FLAG1_LIGHTBAR | PS5DS_FLAG1_PLAYER_LEDS,
308 .valid_flag2 = PS5DS_FLAG2_LIGHTBAR_SETUP,
309 .lightbar_setup = PS5DS_LIGHTBAR_FADE_OUT,
310 };
311
312 return (hid_write(sc->hm.dev, buf, sizeof(buf)));
313 }
314
315 static int
ps5ds_sysctl(SYSCTL_HANDLER_ARGS)316 ps5ds_sysctl(SYSCTL_HANDLER_ARGS)
317 {
318 struct ps5ds_softc *sc;
319 int error, arg;
320
321 if (oidp->oid_arg1 == NULL || oidp->oid_arg2 < 0 ||
322 oidp->oid_arg2 > PS5DS_SYSCTL_LAST)
323 return (EINVAL);
324
325 sc = oidp->oid_arg1;
326 sx_xlock(&sc->lock);
327
328 arg = *(int *)((char *)sc + oidp->oid_arg2);
329 error = sysctl_handle_int(oidp, &arg, 0, req);
330
331 if (error || !req->newptr)
332 goto unlock;
333
334 switch (oidp->oid_arg2) {
335 case PS5DS_SYSCTL_LED_STATE:
336 if (arg < 0 || arg >= PS5DS_LED_CNT)
337 error = EINVAL;
338 break;
339 case PS5DS_SYSCTL_LED_COLOR_R:
340 case PS5DS_SYSCTL_LED_COLOR_G:
341 case PS5DS_SYSCTL_LED_COLOR_B:
342 if (arg < 0 || arg > UINT8_MAX)
343 error = EINVAL;
344 break;
345 case PS5DS_SYSCTL_PLAYER_LEDS:
346 if (arg < -1 || arg >= (int)nitems(ps5ds_player_leds))
347 error = EINVAL;
348 break;
349 default:
350 error = EINVAL;
351 }
352
353 if (error == 0) {
354 *(int *)((char *)sc + oidp->oid_arg2) = arg;
355 ps5ds_write(sc);
356 }
357 unlock:
358 sx_unlock(&sc->lock);
359
360 return (error);
361 }
362
363 static void
ps5ds_identify(driver_t * driver,device_t parent)364 ps5ds_identify(driver_t *driver, device_t parent)
365 {
366
367 /* Replace the gamepad's rudimentary report descriptor */
368 if (HIDBUS_LOOKUP_ID(parent, ps5ds_devs) != NULL)
369 hid_set_report_descr(parent, ps5ds_rdesc,
370 sizeof(ps5ds_rdesc));
371 }
372
373 static int
ps5ds_probe(device_t dev)374 ps5ds_probe(device_t dev)
375 {
376 struct ps5ds_softc *sc = device_get_softc(dev);
377
378 hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR);
379 return (
380 HIDMAP_PROBE(&sc->hm, dev, ps5ds_devs, ps5ds_map, NULL)
381 );
382 }
383
384 static int
ps5ds_attach(device_t dev)385 ps5ds_attach(device_t dev)
386 {
387 struct ps5ds_softc *sc = device_get_softc(dev);
388 struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
389 struct sysctl_oid *tree = device_get_sysctl_tree(dev);
390 int error, unit;
391
392 sx_init(&sc->lock, "ps5dsense");
393
394 unit = device_get_unit(dev);
395 sc->led_state = PS5DS_LED_ON;
396 sc->led_color = ps5ds_leds[unit % nitems(ps5ds_leds)];
397 sc->player_leds = unit < (int)nitems(ps5ds_player_leds) ? unit : -1;
398
399 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
400 "led_state", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
401 PS5DS_SYSCTL_LED_STATE, ps5ds_sysctl, "I",
402 "LED state: 0 - off, 1 - on.");
403
404 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
405 "led_color_r", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
406 PS5DS_SYSCTL_LED_COLOR_R, ps5ds_sysctl, "I",
407 "Lightbar color. Red component.");
408
409 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
410 "led_color_g", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
411 PS5DS_SYSCTL_LED_COLOR_G, ps5ds_sysctl, "I",
412 "Lightbar color. Green component.");
413
414 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
415 "led_color_b", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
416 PS5DS_SYSCTL_LED_COLOR_B, ps5ds_sysctl, "I",
417 "Lightbar color. Blue component.");
418
419 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
420 "player_leds", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
421 PS5DS_SYSCTL_PLAYER_LEDS, ps5ds_sysctl, "I",
422 "Player indicator LEDs: -1 - off, 0-4 - player number.");
423
424 error = hidmap_attach(&sc->hm);
425 if (error) {
426 sx_destroy(&sc->lock);
427 return (error);
428 }
429
430 sx_xlock(&sc->lock);
431 ps5ds_write(sc);
432 sx_xunlock(&sc->lock);
433
434 return (0);
435 }
436
437 static int
ps5ds_detach(device_t dev)438 ps5ds_detach(device_t dev)
439 {
440 struct ps5ds_softc *sc = device_get_softc(dev);
441 int error;
442
443 error = hidmap_detach(&sc->hm);
444 if (error)
445 return (error);
446
447 ps5ds_blank(sc);
448 sx_destroy(&sc->lock);
449
450 return (0);
451 }
452
453 static device_method_t ps5ds_methods[] = {
454 DEVMETHOD(device_identify, ps5ds_identify),
455 DEVMETHOD(device_probe, ps5ds_probe),
456 DEVMETHOD(device_attach, ps5ds_attach),
457 DEVMETHOD(device_detach, ps5ds_detach),
458
459 DEVMETHOD_END
460 };
461
462 DEFINE_CLASS_0(ps5dsense, ps5ds_driver, ps5ds_methods,
463 sizeof(struct ps5ds_softc));
464 DRIVER_MODULE(ps5dsense, hidbus, ps5ds_driver, NULL, NULL);
465
466 MODULE_DEPEND(ps5dsense, hid, 1, 1, 1);
467 MODULE_DEPEND(ps5dsense, hidbus, 1, 1, 1);
468 MODULE_DEPEND(ps5dsense, hidmap, 1, 1, 1);
469 MODULE_DEPEND(ps5dsense, hgame, 1, 1, 1);
470 MODULE_DEPEND(ps5dsense, evdev, 1, 1, 1);
471 MODULE_VERSION(ps5dsense, 1);
472 HID_PNP_INFO(ps5ds_devs);
473