|
|
|
@ -46,6 +46,7 @@ struct ps_device {
|
|
|
|
|
uint32_t fw_version;
|
|
|
|
|
|
|
|
|
|
int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size);
|
|
|
|
|
void (*remove)(struct ps_device *dev);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Calibration data for playstation motion sensors. */
|
|
|
|
@ -107,6 +108,9 @@ struct ps_led_info {
|
|
|
|
|
#define DS_STATUS_CHARGING GENMASK(7, 4)
|
|
|
|
|
#define DS_STATUS_CHARGING_SHIFT 4
|
|
|
|
|
|
|
|
|
|
/* Feature version from DualSense Firmware Info report. */
|
|
|
|
|
#define DS_FEATURE_VERSION(major, minor) ((major & 0xff) << 8 | (minor & 0xff))
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Status of a DualSense touch point contact.
|
|
|
|
|
* Contact IDs, with highest bit set are 'inactive'
|
|
|
|
@ -125,6 +129,7 @@ struct ps_led_info {
|
|
|
|
|
#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3)
|
|
|
|
|
#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4)
|
|
|
|
|
#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1)
|
|
|
|
|
#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2)
|
|
|
|
|
#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4)
|
|
|
|
|
#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1)
|
|
|
|
|
|
|
|
|
@ -142,6 +147,9 @@ struct dualsense {
|
|
|
|
|
struct input_dev *sensors;
|
|
|
|
|
struct input_dev *touchpad;
|
|
|
|
|
|
|
|
|
|
/* Update version is used as a feature/capability version. */
|
|
|
|
|
uint16_t update_version;
|
|
|
|
|
|
|
|
|
|
/* Calibration data for accelerometer and gyroscope. */
|
|
|
|
|
struct ps_calibration_data accel_calib_data[3];
|
|
|
|
|
struct ps_calibration_data gyro_calib_data[3];
|
|
|
|
@ -152,6 +160,7 @@ struct dualsense {
|
|
|
|
|
uint32_t sensor_timestamp_us;
|
|
|
|
|
|
|
|
|
|
/* Compatible rumble state */
|
|
|
|
|
bool use_vibration_v2;
|
|
|
|
|
bool update_rumble;
|
|
|
|
|
uint8_t motor_left;
|
|
|
|
|
uint8_t motor_right;
|
|
|
|
@ -174,6 +183,7 @@ struct dualsense {
|
|
|
|
|
struct led_classdev player_leds[5];
|
|
|
|
|
|
|
|
|
|
struct work_struct output_worker;
|
|
|
|
|
bool output_worker_initialized;
|
|
|
|
|
void *output_report_dmabuf;
|
|
|
|
|
uint8_t output_seq; /* Sequence number for output report. */
|
|
|
|
|
};
|
|
|
|
@ -299,6 +309,7 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = {
|
|
|
|
|
{0, 0},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static inline void dualsense_schedule_work(struct dualsense *ds);
|
|
|
|
|
static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
@ -789,6 +800,7 @@ err_free:
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int dualsense_get_firmware_info(struct dualsense *ds)
|
|
|
|
|
{
|
|
|
|
|
uint8_t *buf;
|
|
|
|
@ -808,6 +820,15 @@ static int dualsense_get_firmware_info(struct dualsense *ds)
|
|
|
|
|
ds->base.hw_version = get_unaligned_le32(&buf[24]);
|
|
|
|
|
ds->base.fw_version = get_unaligned_le32(&buf[28]);
|
|
|
|
|
|
|
|
|
|
/* Update version is some kind of feature version. It is distinct from
|
|
|
|
|
* the firmware version as there can be many different variations of a
|
|
|
|
|
* controller over time with the same physical shell, but with different
|
|
|
|
|
* PCBs and other internal changes. The update version (internal name) is
|
|
|
|
|
* used as a means to detect what features are available and change behavior.
|
|
|
|
|
* Note: the version is different between DualSense and DualSense Edge.
|
|
|
|
|
*/
|
|
|
|
|
ds->update_version = get_unaligned_le16(&buf[44]);
|
|
|
|
|
|
|
|
|
|
err_free:
|
|
|
|
|
kfree(buf);
|
|
|
|
|
return ret;
|
|
|
|
@ -878,7 +899,7 @@ static int dualsense_player_led_set_brightness(struct led_classdev *led, enum le
|
|
|
|
|
ds->update_player_leds = true;
|
|
|
|
|
spin_unlock_irqrestore(&ds->base.lock, flags);
|
|
|
|
|
|
|
|
|
|
schedule_work(&ds->output_worker);
|
|
|
|
|
dualsense_schedule_work(ds);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
@ -922,6 +943,16 @@ static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void dualsense_schedule_work(struct dualsense *ds)
|
|
|
|
|
{
|
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
|
|
spin_lock_irqsave(&ds->base.lock, flags);
|
|
|
|
|
if (ds->output_worker_initialized)
|
|
|
|
|
schedule_work(&ds->output_worker);
|
|
|
|
|
spin_unlock_irqrestore(&ds->base.lock, flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Helper function to send DualSense output reports. Applies a CRC at the end of a report
|
|
|
|
|
* for Bluetooth reports.
|
|
|
|
@ -960,7 +991,10 @@ static void dualsense_output_worker(struct work_struct *work)
|
|
|
|
|
if (ds->update_rumble) {
|
|
|
|
|
/* Select classic rumble style haptics and enable it. */
|
|
|
|
|
common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT;
|
|
|
|
|
common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION;
|
|
|
|
|
if (ds->use_vibration_v2)
|
|
|
|
|
common->valid_flag2 |= DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2;
|
|
|
|
|
else
|
|
|
|
|
common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION;
|
|
|
|
|
common->motor_left = ds->motor_left;
|
|
|
|
|
common->motor_right = ds->motor_right;
|
|
|
|
|
ds->update_rumble = false;
|
|
|
|
@ -1082,7 +1116,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
|
|
|
|
|
spin_unlock_irqrestore(&ps_dev->lock, flags);
|
|
|
|
|
|
|
|
|
|
/* Schedule updating of microphone state at hardware level. */
|
|
|
|
|
schedule_work(&ds->output_worker);
|
|
|
|
|
dualsense_schedule_work(ds);
|
|
|
|
|
}
|
|
|
|
|
ds->last_btn_mic_state = btn_mic_state;
|
|
|
|
|
|
|
|
|
@ -1197,10 +1231,22 @@ static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_ef
|
|
|
|
|
ds->motor_right = effect->u.rumble.weak_magnitude / 256;
|
|
|
|
|
spin_unlock_irqrestore(&ds->base.lock, flags);
|
|
|
|
|
|
|
|
|
|
schedule_work(&ds->output_worker);
|
|
|
|
|
dualsense_schedule_work(ds);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void dualsense_remove(struct ps_device *ps_dev)
|
|
|
|
|
{
|
|
|
|
|
struct dualsense *ds = container_of(ps_dev, struct dualsense, base);
|
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
|
|
spin_lock_irqsave(&ds->base.lock, flags);
|
|
|
|
|
ds->output_worker_initialized = false;
|
|
|
|
|
spin_unlock_irqrestore(&ds->base.lock, flags);
|
|
|
|
|
|
|
|
|
|
cancel_work_sync(&ds->output_worker);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int dualsense_reset_leds(struct dualsense *ds)
|
|
|
|
|
{
|
|
|
|
|
struct dualsense_output_report report;
|
|
|
|
@ -1237,7 +1283,7 @@ static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t gr
|
|
|
|
|
ds->lightbar_blue = blue;
|
|
|
|
|
spin_unlock_irqrestore(&ds->base.lock, flags);
|
|
|
|
|
|
|
|
|
|
schedule_work(&ds->output_worker);
|
|
|
|
|
dualsense_schedule_work(ds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void dualsense_set_player_leds(struct dualsense *ds)
|
|
|
|
@ -1260,7 +1306,7 @@ static void dualsense_set_player_leds(struct dualsense *ds)
|
|
|
|
|
|
|
|
|
|
ds->update_player_leds = true;
|
|
|
|
|
ds->player_leds_state = player_ids[player_id];
|
|
|
|
|
schedule_work(&ds->output_worker);
|
|
|
|
|
dualsense_schedule_work(ds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct ps_device *dualsense_create(struct hid_device *hdev)
|
|
|
|
@ -1299,7 +1345,9 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
|
|
|
|
|
ps_dev->battery_capacity = 100; /* initial value until parse_report. */
|
|
|
|
|
ps_dev->battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
|
|
|
ps_dev->parse_report = dualsense_parse_report;
|
|
|
|
|
ps_dev->remove = dualsense_remove;
|
|
|
|
|
INIT_WORK(&ds->output_worker, dualsense_output_worker);
|
|
|
|
|
ds->output_worker_initialized = true;
|
|
|
|
|
hid_set_drvdata(hdev, ds);
|
|
|
|
|
|
|
|
|
|
max_output_report_size = sizeof(struct dualsense_output_report_bt);
|
|
|
|
@ -1320,6 +1368,21 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
|
|
|
|
|
return ERR_PTR(ret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Original DualSense firmware simulated classic controller rumble through
|
|
|
|
|
* its new haptics hardware. It felt different from classic rumble users
|
|
|
|
|
* were used to. Since then new firmwares were introduced to change behavior
|
|
|
|
|
* and make this new 'v2' behavior default on PlayStation and other platforms.
|
|
|
|
|
* The original DualSense requires a new enough firmware as bundled with PS5
|
|
|
|
|
* software released in 2021. DualSense edge supports it out of the box.
|
|
|
|
|
* Both devices also support the old mode, but it is not really used.
|
|
|
|
|
*/
|
|
|
|
|
if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER) {
|
|
|
|
|
/* Feature version 2.21 introduced new vibration method. */
|
|
|
|
|
ds->use_vibration_v2 = ds->update_version >= DS_FEATURE_VERSION(2, 21);
|
|
|
|
|
} else if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) {
|
|
|
|
|
ds->use_vibration_v2 = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = ps_devices_list_add(ps_dev);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ERR_PTR(ret);
|
|
|
|
@ -1436,7 +1499,8 @@ static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|
|
|
|
goto err_stop;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER) {
|
|
|
|
|
if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER ||
|
|
|
|
|
hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) {
|
|
|
|
|
dev = dualsense_create(hdev);
|
|
|
|
|
if (IS_ERR(dev)) {
|
|
|
|
|
hid_err(hdev, "Failed to create dualsense.\n");
|
|
|
|
@ -1461,6 +1525,9 @@ static void ps_remove(struct hid_device *hdev)
|
|
|
|
|
ps_devices_list_remove(dev);
|
|
|
|
|
ps_device_release_player_id(dev);
|
|
|
|
|
|
|
|
|
|
if (dev->remove)
|
|
|
|
|
dev->remove(dev);
|
|
|
|
|
|
|
|
|
|
hid_hw_close(hdev);
|
|
|
|
|
hid_hw_stop(hdev);
|
|
|
|
|
}
|
|
|
|
@ -1468,6 +1535,8 @@ static void ps_remove(struct hid_device *hdev)
|
|
|
|
|
static const struct hid_device_id ps_devices[] = {
|
|
|
|
|
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
|
|
|
|
|
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
|
|
|
|
|
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) },
|
|
|
|
|
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) },
|
|
|
|
|
{ }
|
|
|
|
|
};
|
|
|
|
|
MODULE_DEVICE_TABLE(hid, ps_devices);
|
|
|
|
|