summaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/hid-sony.c77
1 files changed, 50 insertions, 27 deletions
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 8020d10ce703..79e0d5808a92 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -40,7 +40,9 @@
#define PS3REMOTE BIT(4)
#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[] = {
0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C,
@@ -227,7 +229,7 @@ static const unsigned int buzz_keymap[] = {
struct sony_sc {
struct hid_device *hdev;
- struct led_classdev *leds[4];
+ struct led_classdev *leds[MAX_LEDS];
unsigned long quirks;
struct work_struct state_worker;
@@ -236,7 +238,8 @@ struct sony_sc {
__u8 right;
#endif
- __u8 led_state;
+ __u8 led_state[MAX_LEDS];
+ __u8 led_count;
};
static __u8 *ps3remote_fixup(struct hid_device *hdev, __u8 *rdesc,
@@ -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);
}
-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 =
&hdev->report_enum[HID_OUTPUT_REPORT].report_list;
@@ -456,23 +459,28 @@ static void buzz_set_leds(struct hid_device *hdev, int leds)
__s32 *value = report->field[0]->value;
value[0] = 0x00;
- value[1] = (leds & 1) ? 0xff : 0x00;
- value[2] = (leds & 2) ? 0xff : 0x00;
- value[3] = (leds & 4) ? 0xff : 0x00;
- value[4] = (leds & 8) ? 0xff : 0x00;
+ value[1] = leds[0] ? 0xff : 0x00;
+ value[2] = leds[1] ? 0xff : 0x00;
+ value[3] = leds[2] ? 0xff : 0x00;
+ value[4] = leds[3] ? 0xff : 0x00;
value[5] = 0x00;
value[6] = 0x00;
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);
+ 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);
- } else if (drv_data->quirks & SIXAXIS_CONTROLLER_USB) {
- drv_data->led_state = leds;
+ } else if ((drv_data->quirks & SIXAXIS_CONTROLLER_USB) ||
+ (drv_data->quirks & DUALSHOCK4_CONTROLLER)) {
+ for (n = 0; n < count; n++)
+ drv_data->led_state[n] = leds[n];
schedule_work(&drv_data->state_worker);
}
}
@@ -492,15 +500,11 @@ static void sony_led_set_brightness(struct led_classdev *led,
return;
}
- for (n = 0; n < 4; n++) {
+ for (n = 0; n < drv_data->led_count; n++) {
if (led == drv_data->leds[n]) {
- int on = !!(drv_data->led_state & (1 << n));
- if (value == LED_OFF && on) {
- drv_data->led_state &= ~(1 << n);
- 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);
+ if (value != drv_data->led_state[n]) {
+ drv_data->led_state[n] = value;
+ sony_set_leds(hdev, drv_data->led_state, drv_data->led_count);
}
break;
}
@@ -522,9 +526,9 @@ static enum led_brightness sony_led_get_brightness(struct led_classdev *led)
return LED_OFF;
}
- for (n = 0; n < 4; n++) {
+ for (n = 0; n < drv_data->led_count; n++) {
if (led == drv_data->leds[n]) {
- on = !!(drv_data->led_state & (1 << n));
+ on = !!(drv_data->led_state[n]);
break;
}
}
@@ -541,7 +545,7 @@ static void sony_leds_remove(struct hid_device *hdev)
drv_data = hid_get_drvdata(hdev);
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];
drv_data->leds[n] = NULL;
if (!led)
@@ -549,17 +553,21 @@ static void sony_leds_remove(struct hid_device *hdev)
led_classdev_unregister(led);
kfree(led);
}
+
+ drv_data->led_count = 0;
}
static int sony_leds_init(struct hid_device *hdev)
{
struct sony_sc *drv_data;
int n, ret = 0;
+ int max_brightness;
struct led_classdev *led;
size_t name_sz;
char *name;
size_t name_len;
const char *name_fmt;
+ static const __u8 initial_values[MAX_LEDS] = { 0x00, 0x00, 0x00, 0x00 };
drv_data = hid_get_drvdata(hdev);
BUG_ON(!(drv_data->quirks & SONY_LED_SUPPORT));
@@ -575,14 +583,22 @@ static int sony_leds_init(struct hid_device *hdev)
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
* only relevant if the driver is loaded after somebody actively set the
* 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;
- 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);
if (!led) {
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)
snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1);
led->name = name;
led->brightness = 0;
- led->max_brightness = 1;
+ led->max_brightness = max_brightness;
led->brightness_get = sony_led_get_brightness;
led->brightness_set = sony_led_set_brightness;
@@ -635,7 +651,10 @@ static void sony_state_worker(struct work_struct *work)
buf[5] = sc->left;
#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),
HID_OUTPUT_REPORT);
@@ -660,6 +679,10 @@ static void dualshock4_state_worker(struct work_struct *work)
buf[5] = sc->left;
#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),
HID_OUTPUT_REPORT);
}