Skip to content

Commit 4521109

Browse files
Roderick ColenbranderJiri Kosina
authored andcommitted
HID: playstation: support DualShock4 lightbar.
Expose the lightbar LEDs in the same manner as hid-sony through individual LEDs for backwards compatibility reasons. There is a slight change in LED naming to use the input device name as opposed to the MAC address like hid-sony did. This is expected to not cause any issues and should make the naming more compliant. In addition set a default lightbar color based on player ID. Signed-off-by: Roderick Colenbrander <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent 316f57f commit 4521109

File tree

1 file changed

+138
-3
lines changed

1 file changed

+138
-3
lines changed

drivers/hid/hid-playstation.c

Lines changed: 138 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ struct dualsense_output_report {
311311

312312
/* Flags for DualShock4 output report. */
313313
#define DS4_OUTPUT_VALID_FLAG0_MOTOR 0x01
314+
#define DS4_OUTPUT_VALID_FLAG0_LED 0x02
315+
#define DS4_OUTPUT_VALID_FLAG0_LED_BLINK 0x04
314316

315317
/* DualShock4 hardware limits */
316318
#define DS4_ACC_RES_PER_G 8192
@@ -339,6 +341,14 @@ struct dualshock4 {
339341
uint8_t motor_left;
340342
uint8_t motor_right;
341343

344+
/* Lightbar leds */
345+
bool update_lightbar;
346+
bool lightbar_enabled; /* For use by global LED control. */
347+
uint8_t lightbar_red;
348+
uint8_t lightbar_green;
349+
uint8_t lightbar_blue;
350+
struct led_classdev lightbar_leds[4];
351+
342352
struct work_struct output_worker;
343353
bool output_worker_initialized;
344354
void *output_report_dmabuf;
@@ -697,8 +707,14 @@ static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led,
697707
{
698708
int ret;
699709

700-
led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
701-
"%s:%s:%s", ps_dev->input_dev_name, led_info->color, led_info->name);
710+
if (led_info->name) {
711+
led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
712+
"%s:%s:%s", ps_dev->input_dev_name, led_info->color, led_info->name);
713+
} else {
714+
/* Backwards compatible mode for hid-sony, but not compliant with LED class spec. */
715+
led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
716+
"%s:%s", ps_dev->input_dev_name, led_info->color);
717+
}
702718

703719
if (!led->name)
704720
return -ENOMEM;
@@ -1746,6 +1762,60 @@ static int dualshock4_get_mac_address(struct dualshock4 *ds4)
17461762
return ret;
17471763
}
17481764

1765+
static enum led_brightness dualshock4_led_get_brightness(struct led_classdev *led)
1766+
{
1767+
struct hid_device *hdev = to_hid_device(led->dev->parent);
1768+
struct dualshock4 *ds4 = hid_get_drvdata(hdev);
1769+
unsigned int led_index;
1770+
1771+
led_index = led - ds4->lightbar_leds;
1772+
switch (led_index) {
1773+
case 0:
1774+
return ds4->lightbar_red;
1775+
case 1:
1776+
return ds4->lightbar_green;
1777+
case 2:
1778+
return ds4->lightbar_blue;
1779+
case 3:
1780+
return ds4->lightbar_enabled;
1781+
}
1782+
1783+
return -1;
1784+
}
1785+
1786+
static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brightness value)
1787+
{
1788+
struct hid_device *hdev = to_hid_device(led->dev->parent);
1789+
struct dualshock4 *ds4 = hid_get_drvdata(hdev);
1790+
unsigned long flags;
1791+
unsigned int led_index;
1792+
1793+
spin_lock_irqsave(&ds4->base.lock, flags);
1794+
1795+
led_index = led - ds4->lightbar_leds;
1796+
switch (led_index) {
1797+
case 0:
1798+
ds4->lightbar_red = value;
1799+
break;
1800+
case 1:
1801+
ds4->lightbar_green = value;
1802+
break;
1803+
case 2:
1804+
ds4->lightbar_blue = value;
1805+
break;
1806+
case 3:
1807+
ds4->lightbar_enabled = !!value;
1808+
}
1809+
1810+
ds4->update_lightbar = true;
1811+
1812+
spin_unlock_irqrestore(&ds4->base.lock, flags);
1813+
1814+
dualshock4_schedule_work(ds4);
1815+
1816+
return 0;
1817+
}
1818+
17491819
static void dualshock4_init_output_report(struct dualshock4 *ds4,
17501820
struct dualshock4_output_report *rp, void *buf)
17511821
{
@@ -1784,6 +1854,18 @@ static void dualshock4_output_worker(struct work_struct *work)
17841854
ds4->update_rumble = false;
17851855
}
17861856

1857+
if (ds4->update_lightbar) {
1858+
common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED;
1859+
/* Comptabile behavior with hid-sony, which used a dummy global LED to
1860+
* allow enabling/disabling the lightbar. The global LED maps to
1861+
* lightbar_enabled.
1862+
*/
1863+
common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0;
1864+
common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0;
1865+
common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0;
1866+
ds4->update_lightbar = false;
1867+
}
1868+
17871869
spin_unlock_irqrestore(&ds4->base.lock, flags);
17881870

17891871
hid_hw_output_report(ds4->base.hdev, report.data, report.len);
@@ -1998,12 +2080,52 @@ static inline void dualshock4_schedule_work(struct dualshock4 *ds4)
19982080
spin_unlock_irqrestore(&ds4->base.lock, flags);
19992081
}
20002082

2083+
/* Set default lightbar color based on player. */
2084+
static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4)
2085+
{
2086+
/* Use same player colors as PlayStation 4.
2087+
* Array of colors is in RGB.
2088+
*/
2089+
static const int player_colors[4][3] = {
2090+
{ 0x00, 0x00, 0x40 }, /* Blue */
2091+
{ 0x40, 0x00, 0x00 }, /* Red */
2092+
{ 0x00, 0x40, 0x00 }, /* Green */
2093+
{ 0x20, 0x00, 0x20 } /* Pink */
2094+
};
2095+
2096+
uint8_t player_id = ds4->base.player_id % ARRAY_SIZE(player_colors);
2097+
2098+
ds4->lightbar_enabled = true;
2099+
ds4->lightbar_red = player_colors[player_id][0];
2100+
ds4->lightbar_green = player_colors[player_id][1];
2101+
ds4->lightbar_blue = player_colors[player_id][2];
2102+
2103+
ds4->update_lightbar = true;
2104+
dualshock4_schedule_work(ds4);
2105+
}
2106+
20012107
static struct ps_device *dualshock4_create(struct hid_device *hdev)
20022108
{
20032109
struct dualshock4 *ds4;
20042110
struct ps_device *ps_dev;
20052111
uint8_t max_output_report_size;
2006-
int ret;
2112+
int i, ret;
2113+
2114+
/* The DualShock4 has an RGB lightbar, which the original hid-sony driver
2115+
* exposed as a set of 4 LEDs for the 3 color channels and a global control.
2116+
* Ideally this should have used the multi-color LED class, which didn't exist
2117+
* yet. In addition the driver used a naming scheme not compliant with the LED
2118+
* naming spec by using "<mac_address>:<color>", which contained many colons.
2119+
* We use a more compliant by using "<device_name>:<color>" name now. Ideally
2120+
* would have been "<device_name>:<color>:indicator", but that would break
2121+
* existing applications (e.g. Android). Nothing matches against MAC address.
2122+
*/
2123+
static const struct ps_led_info lightbar_leds_info[] = {
2124+
{ NULL, "red", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
2125+
{ NULL, "green", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
2126+
{ NULL, "blue", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
2127+
{ NULL, "global", 1, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
2128+
};
20072129

20082130
ds4 = devm_kzalloc(&hdev->dev, sizeof(*ds4), GFP_KERNEL);
20092131
if (!ds4)
@@ -2060,6 +2182,9 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
20602182
goto err;
20612183
}
20622184

2185+
/* Use gamepad input device name as primary device name for e.g. LEDs */
2186+
ps_dev->input_dev_name = dev_name(&ds4->gamepad->dev);
2187+
20632188
ds4->sensors = ps_sensors_create(hdev, DS4_ACC_RANGE, DS4_ACC_RES_PER_G,
20642189
DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S);
20652190
if (IS_ERR(ds4->sensors)) {
@@ -2077,12 +2202,22 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
20772202
if (ret)
20782203
goto err;
20792204

2205+
for (i = 0; i < ARRAY_SIZE(lightbar_leds_info); i++) {
2206+
const struct ps_led_info *led_info = &lightbar_leds_info[i];
2207+
2208+
ret = ps_led_register(ps_dev, &ds4->lightbar_leds[i], led_info);
2209+
if (ret < 0)
2210+
goto err;
2211+
}
2212+
20802213
ret = ps_device_set_player_id(ps_dev);
20812214
if (ret) {
20822215
hid_err(hdev, "Failed to assign player id for DualShock4: %d\n", ret);
20832216
goto err;
20842217
}
20852218

2219+
dualshock4_set_default_lightbar_colors(ds4);
2220+
20862221
/*
20872222
* Reporting hardware and firmware is important as there are frequent updates, which
20882223
* can change behavior.

0 commit comments

Comments
 (0)