diff options
Diffstat (limited to 'drivers/hid/hid-bigbenff.c')
| -rw-r--r-- | drivers/hid/hid-bigbenff.c | 118 |
1 files changed, 99 insertions, 19 deletions
diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c index 3f6abd190df4..9f05465358d9 100644 --- a/drivers/hid/hid-bigbenff.c +++ b/drivers/hid/hid-bigbenff.c @@ -99,7 +99,7 @@ * - map previously unused analog trigger data to Z/RZ * - simplify feature and output descriptor */ -static __u8 pid0902_rdesc_fixed[] = { +static const __u8 pid0902_rdesc_fixed[] = { 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ 0x09, 0x05, /* Usage (Game Pad) */ 0xA1, 0x01, /* Collection (Application) */ @@ -174,6 +174,8 @@ static __u8 pid0902_rdesc_fixed[] = { struct bigben_device { struct hid_device *hid; struct hid_report *report; + spinlock_t lock; + bool removed; u8 led_state; /* LED1 = 1 .. LED4 = 8 */ u8 right_motor_on; /* right motor off/on 0/1 */ u8 left_motor_force; /* left motor force 0-255 */ @@ -183,15 +185,39 @@ struct bigben_device { struct work_struct worker; }; +static inline void bigben_schedule_work(struct bigben_device *bigben) +{ + unsigned long flags; + + spin_lock_irqsave(&bigben->lock, flags); + if (!bigben->removed) + schedule_work(&bigben->worker); + spin_unlock_irqrestore(&bigben->lock, flags); +} static void bigben_worker(struct work_struct *work) { struct bigben_device *bigben = container_of(work, struct bigben_device, worker); struct hid_field *report_field = bigben->report->field[0]; + bool do_work_led = false; + bool do_work_ff = false; + u8 *buf; + u32 len; + unsigned long flags; + + buf = hid_alloc_report_buf(bigben->report, GFP_KERNEL); + if (!buf) + return; + + len = hid_report_len(bigben->report); + + /* LED work */ + spin_lock_irqsave(&bigben->lock, flags); if (bigben->work_led) { bigben->work_led = false; + do_work_led = true; report_field->value[0] = 0x01; /* 1 = led message */ report_field->value[1] = 0x08; /* reserved value, always 8 */ report_field->value[2] = bigben->led_state; @@ -200,11 +226,22 @@ static void bigben_worker(struct work_struct *work) report_field->value[5] = 0x00; /* padding */ report_field->value[6] = 0x00; /* padding */ report_field->value[7] = 0x00; /* padding */ - hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); + hid_output_report(bigben->report, buf); + } + + spin_unlock_irqrestore(&bigben->lock, flags); + + if (do_work_led) { + hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len, + bigben->report->type, HID_REQ_SET_REPORT); } + /* FF work */ + spin_lock_irqsave(&bigben->lock, flags); + if (bigben->work_ff) { bigben->work_ff = false; + do_work_ff = true; report_field->value[0] = 0x02; /* 2 = rumble effect message */ report_field->value[1] = 0x08; /* reserved value, always 8 */ report_field->value[2] = bigben->right_motor_on; @@ -213,16 +250,32 @@ static void bigben_worker(struct work_struct *work) report_field->value[5] = 0x00; /* padding */ report_field->value[6] = 0x00; /* padding */ report_field->value[7] = 0x00; /* padding */ - hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); + hid_output_report(bigben->report, buf); + } + + spin_unlock_irqrestore(&bigben->lock, flags); + + if (do_work_ff) { + hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len, + bigben->report->type, HID_REQ_SET_REPORT); } + + kfree(buf); } static int hid_bigben_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect) { - struct bigben_device *bigben = data; + struct hid_device *hid = input_get_drvdata(dev); + struct bigben_device *bigben = hid_get_drvdata(hid); u8 right_motor_on; u8 left_motor_force; + unsigned long flags; + + if (!bigben) { + hid_err(hid, "no device data\n"); + return 0; + } if (effect->type != FF_RUMBLE) return 0; @@ -232,10 +285,13 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data, if (right_motor_on != bigben->right_motor_on || left_motor_force != bigben->left_motor_force) { + spin_lock_irqsave(&bigben->lock, flags); bigben->right_motor_on = right_motor_on; bigben->left_motor_force = left_motor_force; bigben->work_ff = true; - schedule_work(&bigben->worker); + spin_unlock_irqrestore(&bigben->lock, flags); + + bigben_schedule_work(bigben); } return 0; @@ -249,6 +305,7 @@ static void bigben_set_led(struct led_classdev *led, struct bigben_device *bigben = hid_get_drvdata(hid); int n; bool work; + unsigned long flags; if (!bigben) { hid_err(hid, "no device data\n"); @@ -257,6 +314,7 @@ static void bigben_set_led(struct led_classdev *led, for (n = 0; n < NUM_LEDS; n++) { if (led == bigben->leds[n]) { + spin_lock_irqsave(&bigben->lock, flags); if (value == LED_OFF) { work = (bigben->led_state & BIT(n)); bigben->led_state &= ~BIT(n); @@ -264,10 +322,11 @@ static void bigben_set_led(struct led_classdev *led, work = !(bigben->led_state & BIT(n)); bigben->led_state |= BIT(n); } + spin_unlock_irqrestore(&bigben->lock, flags); if (work) { bigben->work_led = true; - schedule_work(&bigben->worker); + bigben_schedule_work(bigben); } return; } @@ -297,9 +356,13 @@ static enum led_brightness bigben_get_led(struct led_classdev *led) static void bigben_remove(struct hid_device *hid) { struct bigben_device *bigben = hid_get_drvdata(hid); + unsigned long flags; + + spin_lock_irqsave(&bigben->lock, flags); + bigben->removed = true; + spin_unlock_irqrestore(&bigben->lock, flags); cancel_work_sync(&bigben->worker); - hid_hw_close(hid); hid_hw_stop(hid); } @@ -308,7 +371,6 @@ static int bigben_probe(struct hid_device *hid, { struct bigben_device *bigben; struct hid_input *hidinput; - struct list_head *report_list; struct led_classdev *led; char *name; size_t name_sz; @@ -319,6 +381,7 @@ static int bigben_probe(struct hid_device *hid, return -ENOMEM; hid_set_drvdata(hid, bigben); bigben->hid = hid; + bigben->removed = false; error = hid_parse(hid); if (error) { @@ -332,19 +395,29 @@ static int bigben_probe(struct hid_device *hid, return error; } - report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; - bigben->report = list_entry(report_list->next, - struct hid_report, list); + bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8); + if (!bigben->report) { + hid_err(hid, "no output report found\n"); + error = -ENODEV; + goto error_hw_stop; + } + + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + error = -ENODEV; + goto error_hw_stop; + } hidinput = list_first_entry(&hid->inputs, struct hid_input, list); set_bit(FF_RUMBLE, hidinput->input->ffbit); INIT_WORK(&bigben->worker, bigben_worker); + spin_lock_init(&bigben->lock); - error = input_ff_create_memless(hidinput->input, bigben, + error = input_ff_create_memless(hidinput->input, NULL, hid_bigben_play_effect); if (error) - return error; + goto error_hw_stop; name_sz = strlen(dev_name(&hid->dev)) + strlen(":red:bigben#") + 1; @@ -354,8 +427,10 @@ static int bigben_probe(struct hid_device *hid, sizeof(struct led_classdev) + name_sz, GFP_KERNEL ); - if (!led) - return -ENOMEM; + if (!led) { + error = -ENOMEM; + goto error_hw_stop; + } name = (void *)(&led[1]); snprintf(name, name_sz, "%s:red:bigben%d", @@ -369,7 +444,7 @@ static int bigben_probe(struct hid_device *hid, bigben->leds[n] = led; error = devm_led_classdev_register(&hid->dev, led); if (error) - return error; + goto error_hw_stop; } /* initial state: LED1 is on, no rumble effect */ @@ -378,19 +453,23 @@ static int bigben_probe(struct hid_device *hid, bigben->left_motor_force = 0; bigben->work_led = true; bigben->work_ff = true; - schedule_work(&bigben->worker); + bigben_schedule_work(bigben); hid_info(hid, "LED and force feedback support for BigBen gamepad\n"); return 0; + +error_hw_stop: + hid_hw_stop(hid); + return error; } -static __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc, +static const __u8 *bigben_report_fixup(struct hid_device *hid, __u8 *rdesc, unsigned int *rsize) { if (*rsize == PID0902_RDESC_ORIG_SIZE) { - rdesc = pid0902_rdesc_fixed; *rsize = sizeof(pid0902_rdesc_fixed); + return pid0902_rdesc_fixed; } else hid_warn(hid, "unexpected rdesc, please submit for review\n"); return rdesc; @@ -411,4 +490,5 @@ static struct hid_driver bigben_driver = { }; module_hid_driver(bigben_driver); +MODULE_DESCRIPTION("LED & force feedback support for BigBen Interactive"); MODULE_LICENSE("GPL"); |
