diff options
Diffstat (limited to 'drivers/hid/hid-logitech-hidpp.c')
-rw-r--r-- | drivers/hid/hid-logitech-hidpp.c | 155 |
1 files changed, 147 insertions, 8 deletions
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index e748e45b5b2f..9561a1f9b42b 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -36,6 +36,9 @@ MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>"); #define HIDPP_QUIRK_CLASS_WTP BIT(0) +/* bits 1..20 are reserved for classes */ +#define HIDPP_QUIRK_DELAYED_INIT BIT(21) + /* * There are two hidpp protocols in use, the first version hidpp10 is known * as register access protocol or RAP, the second version hidpp20 is known as @@ -91,6 +94,11 @@ struct hidpp_device { void *private_data; + struct work_struct work; + struct kfifo delayed_work_fifo; + atomic_t connected; + struct input_dev *delayed_input; + unsigned long quirks; }; @@ -110,6 +118,8 @@ struct hidpp_device { #define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b #define HIDPP_ERROR_WRONG_PIN_CODE 0x0c +static void hidpp_connect_event(struct hidpp_device *hidpp_dev); + static int __hidpp_send_report(struct hid_device *hdev, struct hidpp_report *hidpp_report) { @@ -230,6 +240,13 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, return ret; } +static void delayed_work_cb(struct work_struct *work) +{ + struct hidpp_device *hidpp = container_of(work, struct hidpp_device, + work); + hidpp_connect_event(hidpp); +} + static inline bool hidpp_match_answer(struct hidpp_report *question, struct hidpp_report *answer) { @@ -245,6 +262,12 @@ static inline bool hidpp_match_error(struct hidpp_report *question, (answer->fap.params[0] == question->fap.funcindex_clientid); } +static inline bool hidpp_report_is_connect_event(struct hidpp_report *report) +{ + return (report->report_id == REPORT_ID_HIDPP_SHORT) && + (report->rap.sub_id == 0x41); +} + /* -------------------------------------------------------------------------- */ /* HIDP++ 1.0 commands */ /* -------------------------------------------------------------------------- */ @@ -535,12 +558,10 @@ static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi, return -1; } -static void wtp_input_configured(struct hid_device *hdev, - struct hid_input *hidinput) +static void wtp_populate_input(struct hidpp_device *hidpp, + struct input_dev *input_dev, bool origin_is_hid_core) { - struct hidpp_device *hidpp = hid_get_drvdata(hdev); struct wtp_data *wd = hidpp->private_data; - struct input_dev *input_dev = hidinput->input; __set_bit(EV_ABS, input_dev->evbit); __set_bit(EV_KEY, input_dev->evbit); @@ -716,13 +737,20 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi, return 0; } +static void hidpp_populate_input(struct hidpp_device *hidpp, + struct input_dev *input, bool origin_is_hid_core) +{ + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) + wtp_populate_input(hidpp, input, origin_is_hid_core); +} + static void hidpp_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct input_dev *input = hidinput->input; - if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) - wtp_input_configured(hdev, hidinput); + hidpp_populate_input(hidpp, input, true); } static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, @@ -756,6 +784,15 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, } } + if (unlikely(hidpp_report_is_connect_event(report))) { + atomic_set(&hidpp->connected, + !(report->rap.params[0] & (1 << 6))); + if ((hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) && + (schedule_work(&hidpp->work) == 0)) + dbg_hid("%s: connect event already queued\n", __func__); + return 1; + } + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) return wtp_raw_event(hidpp->hid_dev, data, size); @@ -814,11 +851,99 @@ static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying) kfree(name); } +static int hidpp_input_open(struct input_dev *dev) +{ + struct hid_device *hid = input_get_drvdata(dev); + + return hid_hw_open(hid); +} + +static void hidpp_input_close(struct input_dev *dev) +{ + struct hid_device *hid = input_get_drvdata(dev); + + hid_hw_close(hid); +} + +static struct input_dev *hidpp_allocate_input(struct hid_device *hdev) +{ + struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev); + + if (!input_dev) + return NULL; + + input_set_drvdata(input_dev, hdev); + input_dev->open = hidpp_input_open; + input_dev->close = hidpp_input_close; + + input_dev->name = hdev->name; + input_dev->phys = hdev->phys; + input_dev->uniq = hdev->uniq; + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + input_dev->dev.parent = &hdev->dev; + + return input_dev; +} + +static void hidpp_connect_event(struct hidpp_device *hidpp) +{ + struct hid_device *hdev = hidpp->hid_dev; + int ret = 0; + bool connected = atomic_read(&hidpp->connected); + struct input_dev *input; + char *name, *devm_name; + u8 name_length; + + if (!connected || hidpp->delayed_input) + return; + + if (!hidpp->protocol_major) { + ret = !hidpp_is_connected(hidpp); + if (ret) { + hid_err(hdev, "Can not get the protocol version.\n"); + return; + } + } + + /* the device is already connected, we can ask for its name and + * protocol */ + hid_info(hdev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + + input = hidpp_allocate_input(hdev); + if (!input) { + hid_err(hdev, "cannot allocate new input device: %d\n", ret); + return; + } + + name = hidpp_get_device_name(hidpp, &name_length); + if (!name) { + hid_err(hdev, "unable to retrieve the name of the device"); + } else { + devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name); + if (devm_name) + input->name = devm_name; + kfree(name); + } + + hidpp_populate_input(hidpp, input, false); + + ret = input_register_device(input); + if (ret) + input_free_device(input); + + hidpp->delayed_input = input; +} + static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct hidpp_device *hidpp; int ret; bool connected; + unsigned int connect_mask = HID_CONNECT_DEFAULT; hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device), GFP_KERNEL); @@ -836,6 +961,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } + INIT_WORK(&hidpp->work, delayed_work_cb); mutex_init(&hidpp->send_mutex); init_waitqueue_head(&hidpp->wait); @@ -860,8 +986,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) } hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE); + atomic_set(&hidpp->connected, connected); - if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { + if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) { ret = wtp_get_config(hidpp); if (ret) goto hid_parse_fail; @@ -870,16 +997,27 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* Block incoming packets */ hid_device_io_stop(hdev); - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) + connect_mask &= ~HID_CONNECT_HIDINPUT; + + ret = hid_hw_start(hdev, connect_mask); if (ret) { hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); goto hid_hw_start_fail; } + if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) { + /* Allow incoming packets */ + hid_device_io_start(hdev); + + hidpp_connect_event(hidpp); + } + return ret; hid_hw_start_fail: hid_parse_fail: + cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); hid_set_drvdata(hdev, NULL); return ret; @@ -889,6 +1027,7 @@ static void hidpp_remove(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); hid_hw_stop(hdev); } |