Commit 60781cf4 authored by Frank Praznik's avatar Frank Praznik Committed by Jiri Kosina

HID: sony: Add LED controls for the Dualshock 4

Add LED lightbar controls for the Dualshock 4.

The Dualshock 4 light bar has 3 separate RGB LEDs that can range in
brightness from 0 to 255 so a full byte is now needed to store each LED's
state

Changed the module to support an arbitrary number of LEDs instead of being
hardcoded to 4.
Signed-off-by: default avatarFrank Praznik <frank.praznik@oh.rr.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 0bd88dd3
...@@ -40,7 +40,9 @@ ...@@ -40,7 +40,9 @@
#define PS3REMOTE BIT(4) #define PS3REMOTE BIT(4)
#define DUALSHOCK4_CONTROLLER BIT(5) #define DUALSHOCK4_CONTROLLER BIT(5)
#define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER_USB | BUZZ_CONTROLLER) #define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER_USB | BUZZ_CONTROLLER | DUALSHOCK4_CONTROLLER)
#define MAX_LEDS 4
static const u8 sixaxis_rdesc_fixup[] = { static const u8 sixaxis_rdesc_fixup[] = {
0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C, 0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C,
...@@ -227,7 +229,7 @@ static const unsigned int buzz_keymap[] = { ...@@ -227,7 +229,7 @@ static const unsigned int buzz_keymap[] = {
struct sony_sc { struct sony_sc {
struct hid_device *hdev; struct hid_device *hdev;
struct led_classdev *leds[4]; struct led_classdev *leds[MAX_LEDS];
unsigned long quirks; unsigned long quirks;
struct work_struct state_worker; struct work_struct state_worker;
...@@ -236,7 +238,8 @@ struct sony_sc { ...@@ -236,7 +238,8 @@ struct sony_sc {
__u8 right; __u8 right;
#endif #endif
__u8 led_state; __u8 led_state[MAX_LEDS];
__u8 led_count;
}; };
static __u8 *ps3remote_fixup(struct hid_device *hdev, __u8 *rdesc, static __u8 *ps3remote_fixup(struct hid_device *hdev, __u8 *rdesc,
...@@ -447,7 +450,7 @@ static int sixaxis_set_operational_bt(struct hid_device *hdev) ...@@ -447,7 +450,7 @@ static int sixaxis_set_operational_bt(struct hid_device *hdev)
return hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT); return hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT);
} }
static void buzz_set_leds(struct hid_device *hdev, int leds) static void buzz_set_leds(struct hid_device *hdev, const __u8 *leds)
{ {
struct list_head *report_list = struct list_head *report_list =
&hdev->report_enum[HID_OUTPUT_REPORT].report_list; &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
...@@ -456,23 +459,28 @@ static void buzz_set_leds(struct hid_device *hdev, int leds) ...@@ -456,23 +459,28 @@ static void buzz_set_leds(struct hid_device *hdev, int leds)
__s32 *value = report->field[0]->value; __s32 *value = report->field[0]->value;
value[0] = 0x00; value[0] = 0x00;
value[1] = (leds & 1) ? 0xff : 0x00; value[1] = leds[0] ? 0xff : 0x00;
value[2] = (leds & 2) ? 0xff : 0x00; value[2] = leds[1] ? 0xff : 0x00;
value[3] = (leds & 4) ? 0xff : 0x00; value[3] = leds[2] ? 0xff : 0x00;
value[4] = (leds & 8) ? 0xff : 0x00; value[4] = leds[3] ? 0xff : 0x00;
value[5] = 0x00; value[5] = 0x00;
value[6] = 0x00; value[6] = 0x00;
hid_hw_request(hdev, report, HID_REQ_SET_REPORT); hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
} }
static void sony_set_leds(struct hid_device *hdev, __u8 leds) static void sony_set_leds(struct hid_device *hdev, const __u8 *leds, int count)
{ {
struct sony_sc *drv_data = hid_get_drvdata(hdev); struct sony_sc *drv_data = hid_get_drvdata(hdev);
int n;
if (drv_data->quirks & BUZZ_CONTROLLER) { BUG_ON(count > MAX_LEDS);
if (drv_data->quirks & BUZZ_CONTROLLER && count == 4) {
buzz_set_leds(hdev, leds); buzz_set_leds(hdev, leds);
} else if (drv_data->quirks & SIXAXIS_CONTROLLER_USB) { } else if ((drv_data->quirks & SIXAXIS_CONTROLLER_USB) ||
drv_data->led_state = leds; (drv_data->quirks & DUALSHOCK4_CONTROLLER)) {
for (n = 0; n < count; n++)
drv_data->led_state[n] = leds[n];
schedule_work(&drv_data->state_worker); schedule_work(&drv_data->state_worker);
} }
} }
...@@ -492,15 +500,11 @@ static void sony_led_set_brightness(struct led_classdev *led, ...@@ -492,15 +500,11 @@ static void sony_led_set_brightness(struct led_classdev *led,
return; return;
} }
for (n = 0; n < 4; n++) { for (n = 0; n < drv_data->led_count; n++) {
if (led == drv_data->leds[n]) { if (led == drv_data->leds[n]) {
int on = !!(drv_data->led_state & (1 << n)); if (value != drv_data->led_state[n]) {
if (value == LED_OFF && on) { drv_data->led_state[n] = value;
drv_data->led_state &= ~(1 << n); sony_set_leds(hdev, drv_data->led_state, drv_data->led_count);
sony_set_leds(hdev, drv_data->led_state);
} else if (value != LED_OFF && !on) {
drv_data->led_state |= (1 << n);
sony_set_leds(hdev, drv_data->led_state);
} }
break; break;
} }
...@@ -522,9 +526,9 @@ static enum led_brightness sony_led_get_brightness(struct led_classdev *led) ...@@ -522,9 +526,9 @@ static enum led_brightness sony_led_get_brightness(struct led_classdev *led)
return LED_OFF; return LED_OFF;
} }
for (n = 0; n < 4; n++) { for (n = 0; n < drv_data->led_count; n++) {
if (led == drv_data->leds[n]) { if (led == drv_data->leds[n]) {
on = !!(drv_data->led_state & (1 << n)); on = !!(drv_data->led_state[n]);
break; break;
} }
} }
...@@ -541,7 +545,7 @@ static void sony_leds_remove(struct hid_device *hdev) ...@@ -541,7 +545,7 @@ static void sony_leds_remove(struct hid_device *hdev)
drv_data = hid_get_drvdata(hdev); drv_data = hid_get_drvdata(hdev);
BUG_ON(!(drv_data->quirks & SONY_LED_SUPPORT)); BUG_ON(!(drv_data->quirks & SONY_LED_SUPPORT));
for (n = 0; n < 4; n++) { for (n = 0; n < drv_data->led_count; n++) {
led = drv_data->leds[n]; led = drv_data->leds[n];
drv_data->leds[n] = NULL; drv_data->leds[n] = NULL;
if (!led) if (!led)
...@@ -549,17 +553,21 @@ static void sony_leds_remove(struct hid_device *hdev) ...@@ -549,17 +553,21 @@ static void sony_leds_remove(struct hid_device *hdev)
led_classdev_unregister(led); led_classdev_unregister(led);
kfree(led); kfree(led);
} }
drv_data->led_count = 0;
} }
static int sony_leds_init(struct hid_device *hdev) static int sony_leds_init(struct hid_device *hdev)
{ {
struct sony_sc *drv_data; struct sony_sc *drv_data;
int n, ret = 0; int n, ret = 0;
int max_brightness;
struct led_classdev *led; struct led_classdev *led;
size_t name_sz; size_t name_sz;
char *name; char *name;
size_t name_len; size_t name_len;
const char *name_fmt; const char *name_fmt;
static const __u8 initial_values[MAX_LEDS] = { 0x00, 0x00, 0x00, 0x00 };
drv_data = hid_get_drvdata(hdev); drv_data = hid_get_drvdata(hdev);
BUG_ON(!(drv_data->quirks & SONY_LED_SUPPORT)); BUG_ON(!(drv_data->quirks & SONY_LED_SUPPORT));
...@@ -575,14 +583,22 @@ static int sony_leds_init(struct hid_device *hdev) ...@@ -575,14 +583,22 @@ static int sony_leds_init(struct hid_device *hdev)
name_fmt = "%s::sony%d"; name_fmt = "%s::sony%d";
} }
if (drv_data->quirks & DUALSHOCK4_CONTROLLER) {
drv_data->led_count = 3;
max_brightness = 255;
} else {
drv_data->led_count = 4;
max_brightness = 1;
}
/* Clear LEDs as we have no way of reading their initial state. This is /* Clear LEDs as we have no way of reading their initial state. This is
* only relevant if the driver is loaded after somebody actively set the * only relevant if the driver is loaded after somebody actively set the
* LEDs to on */ * LEDs to on */
sony_set_leds(hdev, 0x00); sony_set_leds(hdev, initial_values, drv_data->led_count);
name_sz = strlen(dev_name(&hdev->dev)) + name_len + 1; name_sz = strlen(dev_name(&hdev->dev)) + name_len + 1;
for (n = 0; n < 4; n++) { for (n = 0; n < drv_data->led_count; n++) {
led = kzalloc(sizeof(struct led_classdev) + name_sz, GFP_KERNEL); led = kzalloc(sizeof(struct led_classdev) + name_sz, GFP_KERNEL);
if (!led) { if (!led) {
hid_err(hdev, "Couldn't allocate memory for LED %d\n", n); hid_err(hdev, "Couldn't allocate memory for LED %d\n", n);
...@@ -594,7 +610,7 @@ static int sony_leds_init(struct hid_device *hdev) ...@@ -594,7 +610,7 @@ static int sony_leds_init(struct hid_device *hdev)
snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1); snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1);
led->name = name; led->name = name;
led->brightness = 0; led->brightness = 0;
led->max_brightness = 1; led->max_brightness = max_brightness;
led->brightness_get = sony_led_get_brightness; led->brightness_get = sony_led_get_brightness;
led->brightness_set = sony_led_set_brightness; led->brightness_set = sony_led_set_brightness;
...@@ -635,7 +651,10 @@ static void sony_state_worker(struct work_struct *work) ...@@ -635,7 +651,10 @@ static void sony_state_worker(struct work_struct *work)
buf[5] = sc->left; buf[5] = sc->left;
#endif #endif
buf[10] |= (sc->led_state & 0xf) << 1; buf[10] |= sc->led_state[0] << 1;
buf[10] |= sc->led_state[1] << 2;
buf[10] |= sc->led_state[2] << 3;
buf[10] |= sc->led_state[3] << 4;
sc->hdev->hid_output_raw_report(sc->hdev, buf, sizeof(buf), sc->hdev->hid_output_raw_report(sc->hdev, buf, sizeof(buf),
HID_OUTPUT_REPORT); HID_OUTPUT_REPORT);
...@@ -660,6 +679,10 @@ static void dualshock4_state_worker(struct work_struct *work) ...@@ -660,6 +679,10 @@ static void dualshock4_state_worker(struct work_struct *work)
buf[5] = sc->left; buf[5] = sc->left;
#endif #endif
buf[6] = sc->led_state[0];
buf[7] = sc->led_state[1];
buf[8] = sc->led_state[2];
sc->hdev->hid_output_raw_report(sc->hdev, buf, sizeof(buf), sc->hdev->hid_output_raw_report(sc->hdev, buf, sizeof(buf),
HID_OUTPUT_REPORT); HID_OUTPUT_REPORT);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment