git: d8e315ba9751 - main - hid: Add Sony DualSense gamepad driver

From: Vladimir Kondratyev <wulf_at_FreeBSD.org>
Date: Fri, 11 Sep 2026 15:50:54 UTC
The branch main has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=d8e315ba975163a2050dd3d3e193f2472dbc2478

commit d8e315ba975163a2050dd3d3e193f2472dbc2478
Author:     Christos Longros <chris.longros@gmail.com>
AuthorDate: 2026-09-11 15:49:22 +0000
Commit:     Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2026-09-11 15:49:22 +0000

    hid: Add Sony DualSense gamepad driver
    
    Add a driver for the Sony PS5 DualSense controller (054c:0ce6)
    providing gamepad input via evdev, lightbar RGB LED control, and
    player indicator LEDs through sysctl.
    
    Signed-off-by:  Christos Longros <chris.longros@gmail.com>
    Reviewed by:    ziaee, wulf
    MFC after:      1 month
    Differential Revision:  https://reviews.freebsd.org/D56345
---
 share/man/man4/Makefile            |   1 +
 share/man/man4/ps5dsense.4         | 145 ++++++++++++
 sys/conf/NOTES                     |   1 +
 sys/conf/files                     |   1 +
 sys/dev/hid/ps5dsense.c            | 472 +++++++++++++++++++++++++++++++++++++
 sys/modules/hid/Makefile           |   1 +
 sys/modules/hid/ps5dsense/Makefile |   8 +
 7 files changed, 629 insertions(+)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 305597d8bedb..75b9b7cefd92 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -496,6 +496,7 @@ MAN=	aac.4 \
 	procfs.4 \
 	proto.4 \
 	ps4dshock.4 \
+	ps5dsense.4 \
 	psm.4 \
 	pst.4 \
 	pt.4 \
diff --git a/share/man/man4/ps5dsense.4 b/share/man/man4/ps5dsense.4
new file mode 100644
index 000000000000..8e412b8bde9a
--- /dev/null
+++ b/share/man/man4/ps5dsense.4
@@ -0,0 +1,145 @@
+.\"
+.\" Copyright (c) 2026 Christos Longros <chris.longros@gmail.com>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd August 29, 2026
+.Dt PS5DSENSE 4
+.Os
+.Sh NAME
+.Nm ps5dsense
+.Nd Sony PlayStation 5 DualSense gamepad driver
+.Sh SYNOPSIS
+.Cd device ps5dsense
+.Cd device hid
+.Cd device hidbus
+.Cd device hidmap
+.Cd device evdev
+.Pp
+In
+.Xr sysctl.conf 5 :
+.Cd dev.ps5dsense.%d.led_state
+.Cd dev.ps5dsense.%d.led_color_r
+.Cd dev.ps5dsense.%d.led_color_g
+.Cd dev.ps5dsense.%d.led_color_b
+.Cd dev.ps5dsense.%d.player_leds
+.Pp
+In
+.Xr loader.conf 5 :
+.Cd hw.hid.ps5dsense.debug
+.Cd ps5dsense_load
+.Sh DESCRIPTION
+The
+.Nm
+driver supports the Sony PlayStation 5 DualSense gamepad
+connected over USB,
+and presents it to applications over the
+.Sy evdev
+interface.
+.Pp
+If the appropriate hardware is detected,
+the driver will be loaded automatically by
+.Xr devmatch 8 .
+To load the driver manually at boot time, set the
+.Va ps5dsense_load
+variable to
+.Ar YES
+at the
+.Xr loader 8
+prompt, or add it to
+.Xr loader.conf 5 .
+.Pp
+To give user applications access to the game controller,
+allow user access to the
+.Pa /dev/input/event*
+nodes with inclusion of user in the
+.Em games
+group,
+or with a
+.Xr devfs.rules 5
+ruleset.
+.Pp
+The microphone mute button is reported as
+.Dv KEY_MICMUTE ;
+unlike the vendor driver on other systems,
+.Nm
+does not mute the built-in microphone itself.
+.Sh HARDWARE
+The
+.Nm
+driver supports the following devices:
+.Pp
+.Bl -bullet -compact
+.It
+Sony DualSense wireless controller
+.Pq USB product ID 0x0ce6
+.El
+.Sh SYSCTL VARIABLES
+The following variables are available as
+.Xr sysctl 8
+variables:
+.Bl -tag -width indent
+.It Va dev.ps5dsense.%d.led_state
+Lightbar state,
+where 0 is off and
+1 is on.
+.It Va dev.ps5dsense.%d.led_color_r
+Lightbar color,
+red component.
+.It Va dev.ps5dsense.%d.led_color_g
+Lightbar color,
+green component.
+.It Va dev.ps5dsense.%d.led_color_b
+Lightbar color,
+blue component.
+.It Va dev.ps5dsense.%d.player_leds
+Player indicator LEDs,
+where -1 is off and
+0 to 4 select the player number.
+.El
+.Pp
+The following variable is available as both
+.Xr sysctl 8
+variable and
+.Xr loader 8
+tunable:
+.Bl -tag -width indent
+.It Va hw.hid.ps5dsense.debug
+Debug output level,
+where 0 is debugging disabled and
+larger values increase debug message verbosity.
+Default is 0.
+.El
+.Sh FILES
+.Bl -tag -width "/dev/input/event*" -compact
+.It Pa /dev/input/event*
+input event device
+.Pq Sy evdev
+node
+.El
+.Sh SEE ALSO
+.Xr hgame 4 ,
+.Xr ps4dshock 4 ,
+.Xr usbhid 4 ,
+.Xr devfs.rules 5
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Christos Longros Aq Mt chris.longros@gmail.com .
+.Sh BUGS
+The
+.Nm
+driver does not support force-feedback events, haptic feedback or the
+adaptive triggers.
+.Pp
+The touchpad and the motion sensors are not decoded.
+The touchpad click is therefore not reported at all.
+.Pp
+Bluetooth operation is not supported.
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
index 11af716a8e08..d405de62bed7 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -2441,6 +2441,7 @@ device		hpen		# Generic pen driver
 device		hrfkill		# RFKILL driver
 device		hsctrl		# System controls
 device		ps4dshock	# Sony PS4 DualShock 4 gamepad driver
+device		ps5dsense	# Sony PS5 DualSense gamepad driver
 device		u2f		# FIDO/U2F authenticator
 options 	U2F_DROP_UHID_ALIAS	# Do not install /dev/uhid alias for
 				# /dev/u2f/ and rename driver from uhid to u2f
diff --git a/sys/conf/files b/sys/conf/files
index c6655459187f..a195d93cb169 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1816,6 +1816,7 @@ dev/hid/hrfkill.c		optional hrfkill
 dev/hid/hsctrl.c		optional hsctrl
 dev/hid/ietp.c			optional ietp
 dev/hid/ps4dshock.c		optional ps4dshock
+dev/hid/ps5dsense.c		optional ps5dsense
 dev/hid/u2f.c			optional u2f
 dev/hid/xb360gp.c		optional xb360gp
 dev/hptiop/hptiop.c		optional hptiop scbus
diff --git a/sys/dev/hid/ps5dsense.c b/sys/dev/hid/ps5dsense.c
new file mode 100644
index 000000000000..75933debe223
--- /dev/null
+++ b/sys/dev/hid/ps5dsense.c
@@ -0,0 +1,472 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
+ * Copyright (c) 2026 Christos Longros <chris.longros@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Sony PS5 DualSense driver
+ * https://controllers.fandom.com/wiki/Sony_DualSense
+ * https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c
+ *
+ * Touchpad and motion sensors are not supported yet.
+ */
+
+#include "opt_hid.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/stat.h>
+#include <sys/sx.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#define	HID_DEBUG_VAR	ps5ds_debug
+#include <dev/hid/hgame.h>
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidquirk.h>
+#include <dev/hid/hidmap.h>
+#include "usbdevs.h"
+
+#ifdef HID_DEBUG
+static int ps5ds_debug = 1;
+
+static SYSCTL_NODE(_hw_hid, OID_AUTO, ps5dsense, CTLFLAG_RW, 0,
+		"Sony PS5 DualSense Gamepad");
+SYSCTL_INT(_hw_hid_ps5dsense, OID_AUTO, debug, CTLFLAG_RWTUN,
+		&ps5ds_debug, 0, "Debug level");
+#endif
+
+/*
+ * Fixed HID report descriptor.  Output is 62 bytes, not 47: shorter reports
+ * are ignored.
+ */
+static const uint8_t	ps5ds_rdesc[] = {
+	0x05, 0x01,		/* Usage Page (Generic Desktop Ctrls)	*/
+	0x09, 0x05,		/* Usage (Game Pad)			*/
+	0xA1, 0x01,		/* Collection (Application)		*/
+	0x85, 0x01,		/*   Report ID (1)			*/
+	0x09, 0x30,		/*   Usage (X)  - left stick X		*/
+	0x09, 0x31,		/*   Usage (Y)  - left stick Y		*/
+	0x09, 0x33,		/*   Usage (Rx) - right stick X		*/
+	0x09, 0x34,		/*   Usage (Ry) - right stick Y		*/
+	0x15, 0x00,		/*   Logical Minimum (0)		*/
+	0x26, 0xFF, 0x00,	/*   Logical Maximum (255)		*/
+	0x75, 0x08,		/*   Report Size (8)			*/
+	0x95, 0x04,		/*   Report Count (4)			*/
+	0x81, 0x02,		/*   Input (Data,Var,Abs)		*/
+	0x09, 0x32,		/*   Usage (Z)  - L2 trigger		*/
+	0x09, 0x35,		/*   Usage (Rz) - R2 trigger		*/
+	0x15, 0x00,		/*   Logical Minimum (0)		*/
+	0x26, 0xFF, 0x00,	/*   Logical Maximum (255)		*/
+	0x75, 0x08,		/*   Report Size (8)			*/
+	0x95, 0x02,		/*   Report Count (2)			*/
+	0x81, 0x02,		/*   Input (Data,Var,Abs)		*/
+	0x06, 0x00, 0xFF,	/*   Usage Page (Vendor Defined 0xFF00)	*/
+	0x09, 0x20,		/*   Usage (0x20)			*/
+	0x15, 0x00,		/*   Logical Minimum (0)		*/
+	0x26, 0xFF, 0x00,	/*   Logical Maximum (255)		*/
+	0x75, 0x08,		/*   Report Size (8)			*/
+	0x95, 0x01,		/*   Report Count (1)			*/
+	0x81, 0x02,		/*   Input (Data,Var,Abs) - counter	*/
+	0x05, 0x01,		/*   Usage Page (Generic Desktop)	*/
+	0x09, 0x39,		/*   Usage (Hat switch)			*/
+	0x15, 0x00,		/*   Logical Minimum (0)		*/
+	0x25, 0x07,		/*   Logical Maximum (7)		*/
+	0x35, 0x00,		/*   Physical Minimum (0)		*/
+	0x46, 0x3B, 0x01,	/*   Physical Maximum (315)		*/
+	0x65, 0x14,		/*   Unit (Eng Rot: Degree)		*/
+	0x75, 0x04,		/*   Report Size (4)			*/
+	0x95, 0x01,		/*   Report Count (1)			*/
+	0x81, 0x42,		/*   Input (Data,Var,Abs,Null State)	*/
+	0x65, 0x00,		/*   Unit (None)			*/
+	0x45, 0x00,		/*   Physical Maximum (0)		*/
+	0x05, 0x09,		/*   Usage Page (Button)		*/
+	0x19, 0x01,		/*   Usage Minimum (0x01)		*/
+	0x29, 0x0F,		/*   Usage Maximum (0x0F)		*/
+	0x15, 0x00,		/*   Logical Minimum (0)		*/
+	0x25, 0x01,		/*   Logical Maximum (1)		*/
+	0x75, 0x01,		/*   Report Size (1)			*/
+	0x95, 0x0F,		/*   Report Count (15)			*/
+	0x81, 0x02,		/*   Input (Data,Var,Abs)		*/
+	0x06, 0x00, 0xFF,	/*   Usage Page (Vendor Defined 0xFF00)	*/
+	0x09, 0x21,		/*   Usage (0x21)			*/
+	0x75, 0x01,		/*   Report Size (1)			*/
+	0x95, 0x0D,		/*   Report Count (13)			*/
+	0x81, 0x03,		/*   Input (Const) - unused button bits	*/
+	0x09, 0x22,		/*   Usage (0x22)			*/
+	0x15, 0x00,		/*   Logical Minimum (0)		*/
+	0x26, 0xFF, 0x00,	/*   Logical Maximum (255)		*/
+	0x75, 0x08,		/*   Report Size (8)			*/
+	0x95, 0x34,		/*   Report Count (52)			*/
+	0x81, 0x03,		/*   Input (Const) - sensors, touchpad	*/
+	/* Output report (63 bytes total) for LED and rumble control */
+	0x85, 0x02,		/*   Report ID (2)			*/
+	0x09, 0x23,		/*   Usage (0x23)			*/
+	0x75, 0x08,		/*   Report Size (8)			*/
+	0x95, 0x3E,		/*   Report Count (62)			*/
+	0x91, 0x02,		/*   Output (Data,Var,Abs)		*/
+	0xC0,			/* End Collection			*/
+};
+
+#define	PS5DS_OUTPUT_REPORT2_SIZE	63
+
+struct ps5ds_out2 {
+	uint8_t	valid_flag0;
+	uint8_t	valid_flag1;
+	uint8_t	rumble_right;
+	uint8_t	rumble_left;
+	uint8_t	reserved1[4];
+	uint8_t	mute_button_led;
+	uint8_t	power_save_control;
+	uint8_t	reserved2[28];
+	uint8_t	valid_flag2;
+	uint8_t	reserved3[2];
+	uint8_t	lightbar_setup;
+	uint8_t	led_brightness;
+	uint8_t	player_leds;
+	uint8_t	led_color_r;
+	uint8_t	led_color_g;
+	uint8_t	led_color_b;
+} __attribute__((packed));
+
+#define	PS5DS_FLAG1_LIGHTBAR		0x04
+#define	PS5DS_FLAG1_PLAYER_LEDS		0x10
+#define	PS5DS_FLAG2_LIGHTBAR_SETUP	0x02
+#define	PS5DS_LIGHTBAR_FADE_OUT		0x02
+
+static const uint8_t ps5ds_player_leds[] = {
+	0x04,	/* Player 1: --x-- */
+	0x0A,	/* Player 2: -x-x- */
+	0x15,	/* Player 3: x-x-x */
+	0x1B,	/* Player 4: xx-xx */
+	0x1F,	/* Player 5: xxxxx */
+};
+
+static const struct ps5ds_led {
+	int	r;
+	int	g;
+	int	b;
+} ps5ds_leds[] = {
+	/* The first 4 entries match the PS4, other from Linux driver */
+	{ 0x00, 0x00, 0x40 },	/* Blue   */
+	{ 0x40, 0x00, 0x00 },	/* Red    */
+	{ 0x00, 0x40, 0x00 },	/* Green  */
+	{ 0x20, 0x00, 0x20 },	/* Pink   */
+	{ 0x02, 0x01, 0x00 },	/* Orange */
+	{ 0x00, 0x01, 0x01 },	/* Teal   */
+	{ 0x01, 0x01, 0x01 }	/* White  */
+};
+
+enum ps5ds_led_state {
+	PS5DS_LED_OFF,
+	PS5DS_LED_ON,
+	PS5DS_LED_CNT,
+};
+
+struct ps5ds_softc {
+	struct hidmap	hm;
+
+	struct sx	lock;
+	enum ps5ds_led_state	led_state;
+	struct ps5ds_led	led_color;
+	int		player_leds;	/* 0-4: player number, -1: off */
+};
+
+#define	PS5DS_OFFSET(field) offsetof(struct ps5ds_softc, field)
+enum {
+	PS5DS_SYSCTL_LED_STATE =	PS5DS_OFFSET(led_state),
+	PS5DS_SYSCTL_LED_COLOR_R =	PS5DS_OFFSET(led_color.r),
+	PS5DS_SYSCTL_LED_COLOR_G =	PS5DS_OFFSET(led_color.g),
+	PS5DS_SYSCTL_LED_COLOR_B =	PS5DS_OFFSET(led_color.b),
+	PS5DS_SYSCTL_PLAYER_LEDS =	PS5DS_OFFSET(player_leds),
+#define	PS5DS_SYSCTL_LAST		PS5DS_SYSCTL_PLAYER_LEDS
+};
+
+#define PS5DS_MAP_BTN(number, code)		\
+	{ HIDMAP_KEY(HUP_BUTTON, number, code) }
+#define PS5DS_MAP_ABS(usage, code)		\
+	{ HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) }
+#define PS5DS_MAP_FLT(usage, code)		\
+	{ HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code), .flat = 15 }
+#define	PS5DS_MAP_GCB(usage, callback)		\
+	{ HIDMAP_ANY_CB(HUP_GENERIC_DESKTOP, HUG_##usage, callback) }
+#define	PS5DS_FINALCB(cb)			\
+	{ HIDMAP_FINAL_CB(&cb) }
+
+static hidmap_cb_t	ps5ds_final_cb;
+
+static const struct hidmap_item ps5ds_map[] = {
+	PS5DS_MAP_FLT(X,		ABS_X),
+	PS5DS_MAP_FLT(Y,		ABS_Y),
+	PS5DS_MAP_ABS(Z,		ABS_Z),
+	PS5DS_MAP_ABS(RZ,		ABS_RZ),
+	PS5DS_MAP_FLT(RX,		ABS_RX),
+	PS5DS_MAP_FLT(RY,		ABS_RY),
+	PS5DS_MAP_BTN(1,		BTN_WEST),
+	PS5DS_MAP_BTN(2,		BTN_SOUTH),
+	PS5DS_MAP_BTN(3,		BTN_EAST),
+	PS5DS_MAP_BTN(4,		BTN_NORTH),
+	PS5DS_MAP_BTN(5,		BTN_TL),
+	PS5DS_MAP_BTN(6,		BTN_TR),
+	PS5DS_MAP_BTN(7,		BTN_TL2),
+	PS5DS_MAP_BTN(8,		BTN_TR2),
+	PS5DS_MAP_BTN(9,		BTN_SELECT),
+	PS5DS_MAP_BTN(10,		BTN_START),
+	PS5DS_MAP_BTN(11,		BTN_THUMBL),
+	PS5DS_MAP_BTN(12,		BTN_THUMBR),
+	PS5DS_MAP_BTN(13,		BTN_MODE),
+	/* Click button is handled by touchpad driver */
+	/* PS5DS_MAP_BTN(14,	BTN_LEFT), */
+	/* Linux mutes the microphone in hardware and emits no event */
+	PS5DS_MAP_BTN(15,		KEY_MICMUTE),
+	PS5DS_MAP_GCB(HAT_SWITCH,	hgame_hat_switch_cb),
+	PS5DS_FINALCB(			ps5ds_final_cb),
+};
+
+static const struct hid_device_id ps5ds_devs[] = {
+	{ HID_BVP(BUS_USB, USB_VENDOR_SONY, 0x0ce6),
+	  HID_TLC(HUP_GENERIC_DESKTOP, HUG_GAME_PAD) },
+};
+
+static int
+ps5ds_final_cb(HIDMAP_CB_ARGS)
+{
+	struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV();
+
+	if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) {
+		evdev_support_prop(evdev, INPUT_PROP_DIRECT);
+		evdev_set_cdev_mode(evdev, UID_ROOT, GID_GAMES,
+		    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+	}
+
+	/* Do not execute callback at interrupt handler and detach */
+	return (ENOSYS);
+}
+
+static int
+ps5ds_write(struct ps5ds_softc *sc)
+{
+	uint8_t buf[PS5DS_OUTPUT_REPORT2_SIZE];
+	bool led_on;
+
+	sx_assert(&sc->lock, SA_XLOCKED);
+
+	memset(buf, 0, sizeof(buf));
+	buf[0] = 0x02;
+	led_on = sc->led_state != PS5DS_LED_OFF;
+	*(struct ps5ds_out2 *)(buf + 1) = (struct ps5ds_out2) {
+		.valid_flag1 = PS5DS_FLAG1_LIGHTBAR | PS5DS_FLAG1_PLAYER_LEDS,
+		.player_leds = sc->player_leds < 0 ?
+		    0 : ps5ds_player_leds[sc->player_leds],
+		.led_color_r = led_on ? sc->led_color.r : 0,
+		.led_color_g = led_on ? sc->led_color.g : 0,
+		.led_color_b = led_on ? sc->led_color.b : 0,
+	};
+
+	return (hid_write(sc->hm.dev, buf, sizeof(buf)));
+}
+
+static int
+ps5ds_blank(struct ps5ds_softc *sc)
+{
+	uint8_t buf[PS5DS_OUTPUT_REPORT2_SIZE];
+
+	memset(buf, 0, sizeof(buf));
+	buf[0] = 0x02;
+	*(struct ps5ds_out2 *)(buf + 1) = (struct ps5ds_out2) {
+		.valid_flag1 = PS5DS_FLAG1_LIGHTBAR | PS5DS_FLAG1_PLAYER_LEDS,
+		.valid_flag2 = PS5DS_FLAG2_LIGHTBAR_SETUP,
+		.lightbar_setup = PS5DS_LIGHTBAR_FADE_OUT,
+	};
+
+	return (hid_write(sc->hm.dev, buf, sizeof(buf)));
+}
+
+static int
+ps5ds_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct ps5ds_softc *sc;
+	int error, arg;
+
+	if (oidp->oid_arg1 == NULL || oidp->oid_arg2 < 0 ||
+	    oidp->oid_arg2 > PS5DS_SYSCTL_LAST)
+		return (EINVAL);
+
+	sc = oidp->oid_arg1;
+	sx_xlock(&sc->lock);
+
+	arg = *(int *)((char *)sc + oidp->oid_arg2);
+	error = sysctl_handle_int(oidp, &arg, 0, req);
+
+	if (error || !req->newptr)
+		goto unlock;
+
+	switch (oidp->oid_arg2) {
+	case PS5DS_SYSCTL_LED_STATE:
+		if (arg < 0 || arg >= PS5DS_LED_CNT)
+			error = EINVAL;
+		break;
+	case PS5DS_SYSCTL_LED_COLOR_R:
+	case PS5DS_SYSCTL_LED_COLOR_G:
+	case PS5DS_SYSCTL_LED_COLOR_B:
+		if (arg < 0 || arg > UINT8_MAX)
+			error = EINVAL;
+		break;
+	case PS5DS_SYSCTL_PLAYER_LEDS:
+		if (arg < -1 || arg >= (int)nitems(ps5ds_player_leds))
+			error = EINVAL;
+		break;
+	default:
+		error = EINVAL;
+	}
+
+	if (error == 0) {
+		*(int *)((char *)sc + oidp->oid_arg2) = arg;
+		ps5ds_write(sc);
+	}
+unlock:
+	sx_unlock(&sc->lock);
+
+	return (error);
+}
+
+static void
+ps5ds_identify(driver_t *driver, device_t parent)
+{
+
+	/* Replace the gamepad's rudimentary report descriptor */
+	if (HIDBUS_LOOKUP_ID(parent, ps5ds_devs) != NULL)
+		hid_set_report_descr(parent, ps5ds_rdesc,
+		    sizeof(ps5ds_rdesc));
+}
+
+static int
+ps5ds_probe(device_t dev)
+{
+	struct ps5ds_softc *sc = device_get_softc(dev);
+
+	hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR);
+	return (
+	    HIDMAP_PROBE(&sc->hm, dev, ps5ds_devs, ps5ds_map, NULL)
+	);
+}
+
+static int
+ps5ds_attach(device_t dev)
+{
+	struct ps5ds_softc *sc = device_get_softc(dev);
+	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
+	struct sysctl_oid *tree = device_get_sysctl_tree(dev);
+	int error, unit;
+
+	sx_init(&sc->lock, "ps5dsense");
+
+	unit = device_get_unit(dev);
+	sc->led_state = PS5DS_LED_ON;
+	sc->led_color = ps5ds_leds[unit % nitems(ps5ds_leds)];
+	sc->player_leds = unit < (int)nitems(ps5ds_player_leds) ? unit : -1;
+
+	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+	    "led_state", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
+	    PS5DS_SYSCTL_LED_STATE, ps5ds_sysctl, "I",
+	    "LED state: 0 - off, 1 - on.");
+
+	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+	    "led_color_r", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
+	    PS5DS_SYSCTL_LED_COLOR_R, ps5ds_sysctl, "I",
+	    "Lightbar color. Red component.");
+
+	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+	    "led_color_g", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
+	    PS5DS_SYSCTL_LED_COLOR_G, ps5ds_sysctl, "I",
+	    "Lightbar color. Green component.");
+
+	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+	    "led_color_b", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
+	    PS5DS_SYSCTL_LED_COLOR_B, ps5ds_sysctl, "I",
+	    "Lightbar color. Blue component.");
+
+	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+	    "player_leds", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc,
+	    PS5DS_SYSCTL_PLAYER_LEDS, ps5ds_sysctl, "I",
+	    "Player indicator LEDs: -1 - off, 0-4 - player number.");
+
+	error = hidmap_attach(&sc->hm);
+	if (error) {
+		sx_destroy(&sc->lock);
+		return (error);
+	}
+
+	sx_xlock(&sc->lock);
+	ps5ds_write(sc);
+	sx_xunlock(&sc->lock);
+
+	return (0);
+}
+
+static int
+ps5ds_detach(device_t dev)
+{
+	struct ps5ds_softc *sc = device_get_softc(dev);
+	int error;
+
+	error = hidmap_detach(&sc->hm);
+	if (error)
+		return (error);
+
+	ps5ds_blank(sc);
+	sx_destroy(&sc->lock);
+
+	return (0);
+}
+
+static device_method_t ps5ds_methods[] = {
+	DEVMETHOD(device_identify,	ps5ds_identify),
+	DEVMETHOD(device_probe,		ps5ds_probe),
+	DEVMETHOD(device_attach,	ps5ds_attach),
+	DEVMETHOD(device_detach,	ps5ds_detach),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(ps5dsense, ps5ds_driver, ps5ds_methods,
+    sizeof(struct ps5ds_softc));
+DRIVER_MODULE(ps5dsense, hidbus, ps5ds_driver, NULL, NULL);
+
+MODULE_DEPEND(ps5dsense, hid, 1, 1, 1);
+MODULE_DEPEND(ps5dsense, hidbus, 1, 1, 1);
+MODULE_DEPEND(ps5dsense, hidmap, 1, 1, 1);
+MODULE_DEPEND(ps5dsense, hgame, 1, 1, 1);
+MODULE_DEPEND(ps5dsense, evdev, 1, 1, 1);
+MODULE_VERSION(ps5dsense, 1);
+HID_PNP_INFO(ps5ds_devs);
diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile
index 100af866555a..ae4ad2c32133 100644
--- a/sys/modules/hid/Makefile
+++ b/sys/modules/hid/Makefile
@@ -20,6 +20,7 @@ SUBDIR += \
 	hsctrl \
 	ietp \
 	ps4dshock \
+	ps5dsense \
 	u2f \
 	xb360gp
 
diff --git a/sys/modules/hid/ps5dsense/Makefile b/sys/modules/hid/ps5dsense/Makefile
new file mode 100644
index 000000000000..d9cf1e179602
--- /dev/null
+++ b/sys/modules/hid/ps5dsense/Makefile
@@ -0,0 +1,8 @@
+.PATH: ${SRCTOP}/sys/dev/hid
+
+KMOD=	ps5dsense
+SRCS=	ps5dsense.c
+SRCS+=	opt_hid.h opt_kbd.h
+SRCS+=	bus_if.h device_if.h usbdevs.h
+
+.include <bsd.kmod.mk>