diff options
Diffstat (limited to 'drivers/hid/hid-wiimote.c')
-rw-r--r-- | drivers/hid/hid-wiimote.c | 277 |
1 files changed, 183 insertions, 94 deletions
diff --git a/drivers/hid/hid-wiimote.c b/drivers/hid/hid-wiimote.c index a594383ce03..85a02e5f9fe 100644 --- a/drivers/hid/hid-wiimote.c +++ b/drivers/hid/hid-wiimote.c @@ -10,10 +10,10 @@ * any later version. */ -#include <linux/atomic.h> #include <linux/device.h> #include <linux/hid.h> #include <linux/input.h> +#include <linux/leds.h> #include <linux/module.h> #include <linux/spinlock.h> #include "hid-ids.h" @@ -33,9 +33,9 @@ struct wiimote_state { }; struct wiimote_data { - atomic_t ready; struct hid_device *hdev; struct input_dev *input; + struct led_classdev *leds[4]; spinlock_t qlock; __u8 head; @@ -53,8 +53,15 @@ struct wiimote_data { #define WIIPROTO_FLAGS_LEDS (WIIPROTO_FLAG_LED1 | WIIPROTO_FLAG_LED2 | \ WIIPROTO_FLAG_LED3 | WIIPROTO_FLAG_LED4) +/* return flag for led \num */ +#define WIIPROTO_FLAG_LED(num) (WIIPROTO_FLAG_LED1 << (num - 1)) + enum wiiproto_reqs { + WIIPROTO_REQ_NULL = 0x0, WIIPROTO_REQ_LED = 0x11, + WIIPROTO_REQ_DRM = 0x12, + WIIPROTO_REQ_STATUS = 0x20, + WIIPROTO_REQ_RETURN = 0x22, WIIPROTO_REQ_DRM_K = 0x30, }; @@ -87,9 +94,6 @@ static __u16 wiiproto_keymap[] = { BTN_MODE, /* WIIPROTO_KEY_HOME */ }; -#define dev_to_wii(pdev) hid_get_drvdata(container_of(pdev, struct hid_device, \ - dev)) - static ssize_t wiimote_hid_send(struct hid_device *hdev, __u8 *buffer, size_t count) { @@ -192,66 +196,96 @@ static void wiiproto_req_leds(struct wiimote_data *wdata, int leds) wiimote_queue(wdata, cmd, sizeof(cmd)); } -#define wiifs_led_show_set(num) \ -static ssize_t wiifs_led_show_##num(struct device *dev, \ - struct device_attribute *attr, char *buf) \ -{ \ - struct wiimote_data *wdata = dev_to_wii(dev); \ - unsigned long flags; \ - int state; \ - \ - if (!atomic_read(&wdata->ready)) \ - return -EBUSY; \ - \ - spin_lock_irqsave(&wdata->state.lock, flags); \ - state = !!(wdata->state.flags & WIIPROTO_FLAG_LED##num); \ - spin_unlock_irqrestore(&wdata->state.lock, flags); \ - \ - return sprintf(buf, "%d\n", state); \ -} \ -static ssize_t wiifs_led_set_##num(struct device *dev, \ - struct device_attribute *attr, const char *buf, size_t count) \ -{ \ - struct wiimote_data *wdata = dev_to_wii(dev); \ - int tmp = simple_strtoul(buf, NULL, 10); \ - unsigned long flags; \ - __u8 state; \ - \ - if (!atomic_read(&wdata->ready)) \ - return -EBUSY; \ - \ - spin_lock_irqsave(&wdata->state.lock, flags); \ - \ - state = wdata->state.flags; \ - \ - if (tmp) \ - wiiproto_req_leds(wdata, state | WIIPROTO_FLAG_LED##num);\ - else \ - wiiproto_req_leds(wdata, state & ~WIIPROTO_FLAG_LED##num);\ - \ - spin_unlock_irqrestore(&wdata->state.lock, flags); \ - \ - return count; \ -} \ -static DEVICE_ATTR(led##num, S_IRUGO | S_IWUSR, wiifs_led_show_##num, \ - wiifs_led_set_##num) - -wiifs_led_show_set(1); -wiifs_led_show_set(2); -wiifs_led_show_set(3); -wiifs_led_show_set(4); +/* + * Check what peripherals of the wiimote are currently + * active and select a proper DRM that supports all of + * the requested data inputs. + */ +static __u8 select_drm(struct wiimote_data *wdata) +{ + return WIIPROTO_REQ_DRM_K; +} + +static void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm) +{ + __u8 cmd[3]; + + if (drm == WIIPROTO_REQ_NULL) + drm = select_drm(wdata); + + cmd[0] = WIIPROTO_REQ_DRM; + cmd[1] = 0; + cmd[2] = drm; + + wiimote_queue(wdata, cmd, sizeof(cmd)); +} + +static enum led_brightness wiimote_leds_get(struct led_classdev *led_dev) +{ + struct wiimote_data *wdata; + struct device *dev = led_dev->dev->parent; + int i; + unsigned long flags; + bool value = false; + + wdata = hid_get_drvdata(container_of(dev, struct hid_device, dev)); + + for (i = 0; i < 4; ++i) { + if (wdata->leds[i] == led_dev) { + spin_lock_irqsave(&wdata->state.lock, flags); + value = wdata->state.flags & WIIPROTO_FLAG_LED(i + 1); + spin_unlock_irqrestore(&wdata->state.lock, flags); + break; + } + } + + return value ? LED_FULL : LED_OFF; +} + +static void wiimote_leds_set(struct led_classdev *led_dev, + enum led_brightness value) +{ + struct wiimote_data *wdata; + struct device *dev = led_dev->dev->parent; + int i; + unsigned long flags; + __u8 state, flag; + + wdata = hid_get_drvdata(container_of(dev, struct hid_device, dev)); + + for (i = 0; i < 4; ++i) { + if (wdata->leds[i] == led_dev) { + flag = WIIPROTO_FLAG_LED(i + 1); + spin_lock_irqsave(&wdata->state.lock, flags); + state = wdata->state.flags; + if (value == LED_OFF) + wiiproto_req_leds(wdata, state & ~flag); + else + wiiproto_req_leds(wdata, state | flag); + spin_unlock_irqrestore(&wdata->state.lock, flags); + break; + } + } +} static int wiimote_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { + return 0; +} + +static int wiimote_input_open(struct input_dev *dev) +{ struct wiimote_data *wdata = input_get_drvdata(dev); - if (!atomic_read(&wdata->ready)) - return -EBUSY; - /* smp_rmb: Make sure wdata->xy is available when wdata->ready is 1 */ - smp_rmb(); + return hid_hw_open(wdata->hdev); +} - return 0; +static void wiimote_input_close(struct input_dev *dev) +{ + struct wiimote_data *wdata = input_get_drvdata(dev); + + hid_hw_close(wdata->hdev); } static void handler_keys(struct wiimote_data *wdata, const __u8 *payload) @@ -281,6 +315,26 @@ static void handler_keys(struct wiimote_data *wdata, const __u8 *payload) input_sync(wdata->input); } +static void handler_status(struct wiimote_data *wdata, const __u8 *payload) +{ + handler_keys(wdata, payload); + + /* on status reports the drm is reset so we need to resend the drm */ + wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL); +} + +static void handler_return(struct wiimote_data *wdata, const __u8 *payload) +{ + __u8 err = payload[3]; + __u8 cmd = payload[2]; + + handler_keys(wdata, payload); + + if (err) + hid_warn(wdata->hdev, "Remote error %hhu on req %hhu\n", err, + cmd); +} + struct wiiproto_handler { __u8 id; size_t size; @@ -288,6 +342,8 @@ struct wiiproto_handler { }; static struct wiiproto_handler handlers[] = { + { .id = WIIPROTO_REQ_STATUS, .size = 6, .func = handler_status }, + { .id = WIIPROTO_REQ_RETURN, .size = 4, .func = handler_return }, { .id = WIIPROTO_REQ_DRM_K, .size = 2, .func = handler_keys }, { .id = 0 } }; @@ -300,11 +356,6 @@ static int wiimote_hid_event(struct hid_device *hdev, struct hid_report *report, int i; unsigned long flags; - if (!atomic_read(&wdata->ready)) - return -EBUSY; - /* smp_rmb: Make sure wdata->xy is available when wdata->ready is 1 */ - smp_rmb(); - if (size < 1) return -EINVAL; @@ -321,6 +372,58 @@ static int wiimote_hid_event(struct hid_device *hdev, struct hid_report *report, return 0; } +static void wiimote_leds_destroy(struct wiimote_data *wdata) +{ + int i; + struct led_classdev *led; + + for (i = 0; i < 4; ++i) { + if (wdata->leds[i]) { + led = wdata->leds[i]; + wdata->leds[i] = NULL; + led_classdev_unregister(led); + kfree(led); + } + } +} + +static int wiimote_leds_create(struct wiimote_data *wdata) +{ + int i, ret; + struct device *dev = &wdata->hdev->dev; + size_t namesz = strlen(dev_name(dev)) + 9; + struct led_classdev *led; + char *name; + + for (i = 0; i < 4; ++i) { + led = kzalloc(sizeof(struct led_classdev) + namesz, GFP_KERNEL); + if (!led) { + ret = -ENOMEM; + goto err; + } + name = (void*)&led[1]; + snprintf(name, namesz, "%s:blue:p%d", dev_name(dev), i); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = wiimote_leds_get; + led->brightness_set = wiimote_leds_set; + + ret = led_classdev_register(dev, led); + if (ret) { + kfree(led); + goto err; + } + wdata->leds[i] = led; + } + + return 0; + +err: + wiimote_leds_destroy(wdata); + return ret; +} + static struct wiimote_data *wiimote_create(struct hid_device *hdev) { struct wiimote_data *wdata; @@ -341,6 +444,8 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) input_set_drvdata(wdata->input, wdata); wdata->input->event = wiimote_input_event; + wdata->input->open = wiimote_input_open; + wdata->input->close = wiimote_input_close; wdata->input->dev.parent = &wdata->hdev->dev; wdata->input->id.bustype = wdata->hdev->bus; wdata->input->id.vendor = wdata->hdev->vendor; @@ -362,6 +467,12 @@ static struct wiimote_data *wiimote_create(struct hid_device *hdev) static void wiimote_destroy(struct wiimote_data *wdata) { + wiimote_leds_destroy(wdata); + + input_unregister_device(wdata->input); + cancel_work_sync(&wdata->worker); + hid_hw_stop(wdata->hdev); + kfree(wdata); } @@ -377,19 +488,6 @@ static int wiimote_hid_probe(struct hid_device *hdev, return -ENOMEM; } - ret = device_create_file(&hdev->dev, &dev_attr_led1); - if (ret) - goto err; - ret = device_create_file(&hdev->dev, &dev_attr_led2); - if (ret) - goto err; - ret = device_create_file(&hdev->dev, &dev_attr_led3); - if (ret) - goto err; - ret = device_create_file(&hdev->dev, &dev_attr_led4); - if (ret) - goto err; - ret = hid_parse(hdev); if (ret) { hid_err(hdev, "HID parse failed\n"); @@ -408,9 +506,10 @@ static int wiimote_hid_probe(struct hid_device *hdev, goto err_stop; } - /* smp_wmb: Write wdata->xy first before wdata->ready is set to 1 */ - smp_wmb(); - atomic_set(&wdata->ready, 1); + ret = wiimote_leds_create(wdata); + if (ret) + goto err_free; + hid_info(hdev, "New device registered\n"); /* by default set led1 after device initialization */ @@ -420,15 +519,15 @@ static int wiimote_hid_probe(struct hid_device *hdev, return 0; +err_free: + wiimote_destroy(wdata); + return ret; + err_stop: hid_hw_stop(hdev); err: input_free_device(wdata->input); - device_remove_file(&hdev->dev, &dev_attr_led1); - device_remove_file(&hdev->dev, &dev_attr_led2); - device_remove_file(&hdev->dev, &dev_attr_led3); - device_remove_file(&hdev->dev, &dev_attr_led4); - wiimote_destroy(wdata); + kfree(wdata); return ret; } @@ -437,16 +536,6 @@ static void wiimote_hid_remove(struct hid_device *hdev) struct wiimote_data *wdata = hid_get_drvdata(hdev); hid_info(hdev, "Device removed\n"); - - device_remove_file(&hdev->dev, &dev_attr_led1); - device_remove_file(&hdev->dev, &dev_attr_led2); - device_remove_file(&hdev->dev, &dev_attr_led3); - device_remove_file(&hdev->dev, &dev_attr_led4); - - hid_hw_stop(hdev); - input_unregister_device(wdata->input); - - cancel_work_sync(&wdata->worker); wiimote_destroy(wdata); } |