From 20d11305b7c441a37ddb5f4676572715fdf8b867 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Thu, 21 Jul 2016 11:25:54 -0400 Subject: HID: use IS_ENABLED() instead of checking for built-in or module The IS_ENABLED() macro checks if a Kconfig symbol has been enabled either built-in or as a module, use that macro instead of open coding the same. Using the macro makes the code more readable by helping abstract away some of the Kconfig built-in and module enable details. Signed-off-by: Javier Martinez Canillas Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 08f53c7fd513..386f31481c06 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2480,7 +2480,7 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_PANJIT, 0x0004) }, { HID_USB_DEVICE(USB_VENDOR_ID_PHILIPS, USB_DEVICE_ID_PHILIPS_IEEE802154_DONGLE) }, { HID_USB_DEVICE(USB_VENDOR_ID_POWERCOM, USB_DEVICE_ID_POWERCOM_UPS) }, -#if defined(CONFIG_MOUSE_SYNAPTICS_USB) || defined(CONFIG_MOUSE_SYNAPTICS_USB_MODULE) +#if IS_ENABLED(CONFIG_MOUSE_SYNAPTICS_USB) { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP) }, { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_INT_TP) }, { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_CPAD) }, -- cgit v1.2.3 From d453b87612a0aba6ca6ec85447eeefa220cadec5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:48 +0200 Subject: HID: wacom: actually report the battery level for wireless connected Since fd5f92b ("HID: wacom: reuse wacom_parse_and_register() in wireless_work"), wacom->shared->type is not set. Send the information of the battery if we have one. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 1eae13cdc502..d6746caf71cb 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -2125,6 +2125,7 @@ static int wacom_bamboo_pad_irq(struct wacom_wac *wacom, size_t len) static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len) { + struct wacom *w = container_of(wacom, struct wacom, wacom_wac); unsigned char *data = wacom->data; int connected; @@ -2152,7 +2153,7 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len) wacom_schedule_work(wacom); } - if (wacom->shared->type) + if (w->battery) wacom_notify_battery(wacom, battery, charging, 1, 0); } else if (wacom->pid != 0) { -- cgit v1.2.3 From 97f9afa4f9cc5e88e092e47ebd17ebf3f764a582 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:49 +0200 Subject: HID: wacom: store the type in wacom->shared for INTUOSHT and INTUOSHT2 The type is never set but we check for it in wacom_wireless_irq(). It looks like this is a big hack from the beginning, so fill in the gap only. Untested. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 499cc8213cfe..9e283aaa6bad 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1720,9 +1720,10 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) error = hid_hw_open(hdev); if ((wacom_wac->features.type == INTUOSHT || - wacom_wac->features.type == INTUOSHT2) && + wacom_wac->features.type == INTUOSHT2) && (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH)) { - wacom_wac->shared->touch_input = wacom_wac->touch_input; + wacom_wac->shared->type = wacom_wac->features.type; + wacom_wac->shared->touch_input = wacom_wac->touch_input; } return 0; -- cgit v1.2.3 From b62f6465ce1fcbfe4459eb9fd67566ced2a27994 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:50 +0200 Subject: HID: wacom: remove cleanup of wacom->remote_dir from wacom_clean_inputs() wacom->remote_dir has nothing to do with inputs, so better not magically removing it when cleaning inputs. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 9e283aaa6bad..5dd2640c7f21 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1397,7 +1397,6 @@ static void wacom_clean_inputs(struct wacom *wacom) else input_free_device(wacom->wacom_wac.pad_input); } - kobject_put(wacom->remote_dir); wacom->wacom_wac.pen_input = NULL; wacom->wacom_wac.touch_input = NULL; wacom->wacom_wac.pad_input = NULL; @@ -1480,16 +1479,10 @@ static int wacom_register_inputs(struct wacom *wacom) error = wacom_initialize_leds(wacom); if (error) goto fail_leds; - - error = wacom_initialize_remote(wacom); - if (error) - goto fail_remote; } return 0; -fail_remote: - wacom_destroy_leds(wacom); fail_leds: input_unregister_device(pad_input_dev); pad_input_dev = NULL; @@ -1686,6 +1679,12 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) if (error) goto fail_register_inputs; + if (wacom->wacom_wac.features.device_type & WACOM_DEVICETYPE_PAD) { + error = wacom_initialize_remote(wacom); + if (error) + goto fail_remote; + } + if (features->type == HID_GENERIC) connect_mask |= HID_CONNECT_DRIVER; @@ -1705,7 +1704,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) if ((features->type == BAMBOO_TOUCH) && (features->device_type & WACOM_DEVICETYPE_PEN)) { error = -ENODEV; - goto fail_hw_start; + goto fail_quirks; } /* pen only Bamboo neither support touch nor pad */ @@ -1713,7 +1712,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) ((features->device_type & WACOM_DEVICETYPE_TOUCH) || (features->device_type & WACOM_DEVICETYPE_PAD))) { error = -ENODEV; - goto fail_hw_start; + goto fail_quirks; } if (features->device_type & WACOM_DEVICETYPE_WL_MONITOR) @@ -1728,10 +1727,13 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) return 0; -fail_hw_start: +fail_quirks: hid_hw_stop(hdev); -fail_register_inputs: +fail_hw_start: + kobject_put(wacom->remote_dir); +fail_remote: wacom_clean_inputs(wacom); +fail_register_inputs: wacom_destroy_battery(wacom); fail_battery: wacom_remove_shared_data(wacom); @@ -1910,6 +1912,7 @@ static void wacom_remove(struct hid_device *hdev) hid_hw_stop(hdev); cancel_work_sync(&wacom->work); + kobject_put(wacom->remote_dir); wacom_clean_inputs(wacom); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); -- cgit v1.2.3 From 85d2c77b78acf6c30d6d4f7263fb5d30fec1f5c3 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:51 +0200 Subject: HID: wacom: untie leds from inputs Like remotes, LEDs should be handled by themself, not magically behind the inputs as they have a complete different life. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 5dd2640c7f21..af2801df14ed 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1403,7 +1403,6 @@ static void wacom_clean_inputs(struct wacom *wacom) wacom->wacom_wac.pen_registered = false; wacom->wacom_wac.touch_registered = false; wacom->wacom_wac.pad_registered = false; - wacom_destroy_leds(wacom); } static int wacom_allocate_inputs(struct wacom *wacom) @@ -1475,18 +1474,10 @@ static int wacom_register_inputs(struct wacom *wacom) if (error) goto fail_register_pad_input; wacom_wac->pad_registered = true; - - error = wacom_initialize_leds(wacom); - if (error) - goto fail_leds; } return 0; -fail_leds: - input_unregister_device(pad_input_dev); - pad_input_dev = NULL; - wacom_wac->pad_registered = false; fail_register_pad_input: if (touch_input_dev) input_unregister_device(touch_input_dev); @@ -1680,6 +1671,10 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) goto fail_register_inputs; if (wacom->wacom_wac.features.device_type & WACOM_DEVICETYPE_PAD) { + error = wacom_initialize_leds(wacom); + if (error) + goto fail_leds; + error = wacom_initialize_remote(wacom); if (error) goto fail_remote; @@ -1732,6 +1727,8 @@ fail_quirks: fail_hw_start: kobject_put(wacom->remote_dir); fail_remote: + wacom_destroy_leds(wacom); +fail_leds: wacom_clean_inputs(wacom); fail_register_inputs: wacom_destroy_battery(wacom); @@ -1765,12 +1762,14 @@ static void wacom_wireless_work(struct work_struct *work) hdev1 = usb_get_intfdata(usbdev->config->interface[1]); wacom1 = hid_get_drvdata(hdev1); wacom_wac1 = &(wacom1->wacom_wac); + wacom_destroy_leds(wacom1); wacom_clean_inputs(wacom1); /* Touch interface */ hdev2 = usb_get_intfdata(usbdev->config->interface[2]); wacom2 = hid_get_drvdata(hdev2); wacom_wac2 = &(wacom2->wacom_wac); + wacom_destroy_leds(wacom2); wacom_clean_inputs(wacom2); if (wacom_wac->pid == 0) { @@ -1825,7 +1824,9 @@ static void wacom_wireless_work(struct work_struct *work) return; fail: + wacom_destroy_leds(wacom1); wacom_clean_inputs(wacom1); + wacom_destroy_leds(wacom2); wacom_clean_inputs(wacom2); return; } @@ -1913,6 +1914,7 @@ static void wacom_remove(struct hid_device *hdev) cancel_work_sync(&wacom->work); kobject_put(wacom->remote_dir); + wacom_destroy_leds(wacom); wacom_clean_inputs(wacom); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); -- cgit v1.2.3 From d17d1f171158cc0663b072d91265cc8a6b477c77 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:52 +0200 Subject: HID: wacom: use one work queue per task Looks like the battery hijacked the wireless worker. That's not fair so use a work queue per task. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 21 ++++++++++++++++++--- drivers/hid/wacom_sys.c | 10 ++++++---- drivers/hid/wacom_wac.c | 13 +++++-------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 4681a65a4579..a968fbbbae77 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -105,13 +105,19 @@ #define USB_VENDOR_ID_WACOM 0x056a #define USB_VENDOR_ID_LENOVO 0x17ef +enum wacom_worker { + WACOM_WORKER_WIRELESS, + WACOM_WORKER_BATTERY, +}; + struct wacom { struct usb_device *usbdev; struct usb_interface *intf; struct wacom_wac wacom_wac; struct hid_device *hdev; struct mutex lock; - struct work_struct work; + struct work_struct wireless_work; + struct work_struct battery_work; struct wacom_led { u8 select[5]; /* status led selector (0..3) */ u8 llv; /* status led brightness no button (1..127) */ @@ -127,10 +133,19 @@ struct wacom { struct attribute_group remote_group[5]; }; -static inline void wacom_schedule_work(struct wacom_wac *wacom_wac) +static inline void wacom_schedule_work(struct wacom_wac *wacom_wac, + enum wacom_worker which) { struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); - schedule_work(&wacom->work); + + switch (which) { + case WACOM_WORKER_WIRELESS: + schedule_work(&wacom->wireless_work); + break; + case WACOM_WORKER_BATTERY: + schedule_work(&wacom->battery_work); + break; + } } extern const struct hid_device_id wacom_ids[]; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index af2801df14ed..54f026049812 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1527,7 +1527,7 @@ static void wacom_calculate_res(struct wacom_features *features) void wacom_battery_work(struct work_struct *work) { - struct wacom *wacom = container_of(work, struct wacom, work); + struct wacom *wacom = container_of(work, struct wacom, battery_work); if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) && !wacom->battery) { @@ -1743,7 +1743,7 @@ fail_allocate_inputs: static void wacom_wireless_work(struct work_struct *work) { - struct wacom *wacom = container_of(work, struct wacom, work); + struct wacom *wacom = container_of(work, struct wacom, wireless_work); struct usb_device *usbdev = wacom->usbdev; struct wacom_wac *wacom_wac = &wacom->wacom_wac; struct hid_device *hdev1, *hdev2; @@ -1871,7 +1871,8 @@ static int wacom_probe(struct hid_device *hdev, wacom->usbdev = dev; wacom->intf = intf; mutex_init(&wacom->lock); - INIT_WORK(&wacom->work, wacom_wireless_work); + INIT_WORK(&wacom->wireless_work, wacom_wireless_work); + INIT_WORK(&wacom->battery_work, wacom_battery_work); /* ask for the report descriptor to be loaded by HID */ error = hid_parse(hdev); @@ -1912,7 +1913,8 @@ static void wacom_remove(struct hid_device *hdev) hid_hw_stop(hdev); - cancel_work_sync(&wacom->work); + cancel_work_sync(&wacom->wireless_work); + cancel_work_sync(&wacom->battery_work); kobject_put(wacom->remote_dir); wacom_destroy_leds(wacom); wacom_clean_inputs(wacom); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index d6746caf71cb..c7707b29eeec 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -814,8 +814,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) if (!wacom->battery && !(features->quirks & WACOM_QUIRK_BATTERY)) { features->quirks |= WACOM_QUIRK_BATTERY; - INIT_WORK(&wacom->work, wacom_battery_work); - wacom_schedule_work(wacom_wac); + wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY); } wacom_notify_battery(wacom_wac, bat_percent, bat_charging, 1, @@ -2150,7 +2149,7 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len) charging = !!(data[5] & 0x80); if (wacom->pid != pid) { wacom->pid = pid; - wacom_schedule_work(wacom); + wacom_schedule_work(wacom, WACOM_WORKER_WIRELESS); } if (w->battery) @@ -2159,7 +2158,7 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len) } else if (wacom->pid != 0) { /* disconnected while previously connected */ wacom->pid = 0; - wacom_schedule_work(wacom); + wacom_schedule_work(wacom, WACOM_WORKER_WIRELESS); wacom_notify_battery(wacom, 0, 0, 0, 0); } @@ -2194,15 +2193,13 @@ static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len) if (!wacom->battery && !(features->quirks & WACOM_QUIRK_BATTERY)) { features->quirks |= WACOM_QUIRK_BATTERY; - INIT_WORK(&wacom->work, wacom_battery_work); - wacom_schedule_work(wacom_wac); + wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY); } } else if ((features->quirks & WACOM_QUIRK_BATTERY) && wacom->battery) { features->quirks &= ~WACOM_QUIRK_BATTERY; - INIT_WORK(&wacom->work, wacom_battery_work); - wacom_schedule_work(wacom_wac); + wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY); wacom_notify_battery(wacom_wac, 0, 0, 0, 0); } return 0; -- cgit v1.2.3 From b189da901536cf7e9c95c17dd159cfbaddcc0384 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:53 +0200 Subject: HID: wacom: switch battery to devres Simplifying the error code paths. We need to keep wacom_destroy_battery() around for now as the wireless module and the remotes are using it to dynamically remove the battery supply on disconnect. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 54f026049812..61dcb87e3236 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1082,11 +1082,16 @@ static int wacom_ac_get_property(struct power_supply *psy, static int wacom_initialize_battery(struct wacom *wacom) { static atomic_t battery_no = ATOMIC_INIT(0); + struct device *dev = &wacom->hdev->dev; struct power_supply_config psy_cfg = { .drv_data = wacom, }; + struct power_supply_desc *bat_desc = &wacom->battery_desc; unsigned long n; + int error; + + if (!devres_open_group(dev, bat_desc, GFP_KERNEL)) + return -ENOMEM; if (wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) { - struct power_supply_desc *bat_desc = &wacom->battery_desc; struct power_supply_desc *ac_desc = &wacom->ac_desc; n = atomic_inc_return(&battery_no) - 1; @@ -1106,33 +1111,40 @@ static int wacom_initialize_battery(struct wacom *wacom) ac_desc->type = POWER_SUPPLY_TYPE_MAINS; ac_desc->use_for_apm = 0; - wacom->battery = power_supply_register(&wacom->hdev->dev, - &wacom->battery_desc, &psy_cfg); - if (IS_ERR(wacom->battery)) - return PTR_ERR(wacom->battery); + wacom->battery = devm_power_supply_register(dev, + &wacom->battery_desc, + &psy_cfg); + if (IS_ERR(wacom->battery)) { + error = PTR_ERR(wacom->battery); + goto err; + } power_supply_powers(wacom->battery, &wacom->hdev->dev); - wacom->ac = power_supply_register(&wacom->hdev->dev, - &wacom->ac_desc, - &psy_cfg); + wacom->ac = devm_power_supply_register(dev, + &wacom->ac_desc, + &psy_cfg); if (IS_ERR(wacom->ac)) { - power_supply_unregister(wacom->battery); - return PTR_ERR(wacom->ac); + error = PTR_ERR(wacom->ac); + goto err; } power_supply_powers(wacom->ac, &wacom->hdev->dev); } + devres_close_group(dev, bat_desc); return 0; + +err: + devres_release_group(dev, bat_desc); + return error; } static void wacom_destroy_battery(struct wacom *wacom) { if (wacom->battery) { - power_supply_unregister(wacom->battery); + devres_release_group(&wacom->hdev->dev, &wacom->battery_desc); wacom->battery = NULL; - power_supply_unregister(wacom->ac); wacom->ac = NULL; } } @@ -1731,7 +1743,6 @@ fail_remote: fail_leds: wacom_clean_inputs(wacom); fail_register_inputs: - wacom_destroy_battery(wacom); fail_battery: wacom_remove_shared_data(wacom); fail_shared_data: @@ -1920,7 +1931,6 @@ static void wacom_remove(struct hid_device *hdev) wacom_clean_inputs(wacom); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); - wacom_destroy_battery(wacom); wacom_remove_shared_data(wacom); hid_set_drvdata(hdev, NULL); -- cgit v1.2.3 From 3dad188e6a1c6a6213a21dee8b609ceab45d6258 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:54 +0200 Subject: HID: wacom: switch inputs to devres Simplifying the error code paths. We need to keep wacom_clean_inputs() around for now as the wireless module is using it to dynamically remove the inputs on disconnect. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 61dcb87e3236..da2fccf5fa15 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -91,7 +91,12 @@ static void wacom_close(struct input_dev *dev) { struct wacom *wacom = input_get_drvdata(dev); - hid_hw_close(wacom->hdev); + /* + * wacom->hdev should never be null, but surprisingly, I had the case + * once while unplugging the Wacom Wireless Receiver. + */ + if (wacom->hdev) + hid_hw_close(wacom->hdev); } /* @@ -1370,7 +1375,7 @@ static struct input_dev *wacom_allocate_input(struct wacom *wacom) struct hid_device *hdev = wacom->hdev; struct wacom_wac *wacom_wac = &(wacom->wacom_wac); - input_dev = input_allocate_device(); + input_dev = devm_input_allocate_device(&hdev->dev); if (!input_dev) return NULL; @@ -1424,10 +1429,10 @@ static int wacom_allocate_inputs(struct wacom *wacom) wacom_wac->pen_input = wacom_allocate_input(wacom); wacom_wac->touch_input = wacom_allocate_input(wacom); wacom_wac->pad_input = wacom_allocate_input(wacom); - if (!wacom_wac->pen_input || !wacom_wac->touch_input || !wacom_wac->pad_input) { - wacom_clean_inputs(wacom); + if (!wacom_wac->pen_input || + !wacom_wac->touch_input || + !wacom_wac->pad_input) return -ENOMEM; - } wacom_wac->pen_input->name = wacom_wac->pen_name; wacom_wac->touch_input->name = wacom_wac->touch_name; @@ -1458,7 +1463,7 @@ static int wacom_register_inputs(struct wacom *wacom) } else { error = input_register_device(pen_input_dev); if (error) - goto fail_register_pen_input; + goto fail; wacom_wac->pen_registered = true; } @@ -1471,7 +1476,7 @@ static int wacom_register_inputs(struct wacom *wacom) } else { error = input_register_device(touch_input_dev); if (error) - goto fail_register_touch_input; + goto fail; wacom_wac->touch_registered = true; } @@ -1484,23 +1489,19 @@ static int wacom_register_inputs(struct wacom *wacom) } else { error = input_register_device(pad_input_dev); if (error) - goto fail_register_pad_input; + goto fail; wacom_wac->pad_registered = true; } return 0; -fail_register_pad_input: - if (touch_input_dev) - input_unregister_device(touch_input_dev); +fail: + wacom_wac->pad_input = NULL; + wacom_wac->pad_registered = false; wacom_wac->touch_input = NULL; wacom_wac->touch_registered = false; -fail_register_touch_input: - if (pen_input_dev) - input_unregister_device(pen_input_dev); wacom_wac->pen_input = NULL; wacom_wac->pen_registered = false; -fail_register_pen_input: return error; } @@ -1741,14 +1742,12 @@ fail_hw_start: fail_remote: wacom_destroy_leds(wacom); fail_leds: - wacom_clean_inputs(wacom); fail_register_inputs: fail_battery: wacom_remove_shared_data(wacom); fail_shared_data: fail_parsed: fail_allocate_inputs: - wacom_clean_inputs(wacom); return error; } @@ -1928,7 +1927,6 @@ static void wacom_remove(struct hid_device *hdev) cancel_work_sync(&wacom->battery_work); kobject_put(wacom->remote_dir); wacom_destroy_leds(wacom); - wacom_clean_inputs(wacom); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); wacom_remove_shared_data(wacom); -- cgit v1.2.3 From 84dfbd7f2a58c8c79ba7947159fc5f2c521348f6 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:55 +0200 Subject: HID: wacom: put the managed resources in a group We currently have a complex clean_inputs() function while this can be handled all by devres. Set a group that we can destroy in wireless_work(). Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 1 + drivers/hid/wacom_sys.c | 69 +++++++++++++++++++++---------------------------- drivers/hid/wacom_wac.c | 2 +- drivers/hid/wacom_wac.h | 3 --- 4 files changed, 32 insertions(+), 43 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index a968fbbbae77..6f5555fa1eab 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -131,6 +131,7 @@ struct wacom { struct power_supply_desc ac_desc; struct kobject *remote_dir; struct attribute_group remote_group[5]; + bool resources; }; static inline void wacom_schedule_work(struct wacom_wac *wacom_wac, diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index da2fccf5fa15..08f6d1f29c73 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1394,34 +1394,6 @@ static struct input_dev *wacom_allocate_input(struct wacom *wacom) return input_dev; } -static void wacom_clean_inputs(struct wacom *wacom) -{ - if (wacom->wacom_wac.pen_input) { - if (wacom->wacom_wac.pen_registered) - input_unregister_device(wacom->wacom_wac.pen_input); - else - input_free_device(wacom->wacom_wac.pen_input); - } - if (wacom->wacom_wac.touch_input) { - if (wacom->wacom_wac.touch_registered) - input_unregister_device(wacom->wacom_wac.touch_input); - else - input_free_device(wacom->wacom_wac.touch_input); - } - if (wacom->wacom_wac.pad_input) { - if (wacom->wacom_wac.pad_registered) - input_unregister_device(wacom->wacom_wac.pad_input); - else - input_free_device(wacom->wacom_wac.pad_input); - } - wacom->wacom_wac.pen_input = NULL; - wacom->wacom_wac.touch_input = NULL; - wacom->wacom_wac.pad_input = NULL; - wacom->wacom_wac.pen_registered = false; - wacom->wacom_wac.touch_registered = false; - wacom->wacom_wac.pad_registered = false; -} - static int wacom_allocate_inputs(struct wacom *wacom) { struct wacom_wac *wacom_wac = &(wacom->wacom_wac); @@ -1464,7 +1436,6 @@ static int wacom_register_inputs(struct wacom *wacom) error = input_register_device(pen_input_dev); if (error) goto fail; - wacom_wac->pen_registered = true; } error = wacom_setup_touch_input_capabilities(touch_input_dev, wacom_wac); @@ -1477,7 +1448,6 @@ static int wacom_register_inputs(struct wacom *wacom) error = input_register_device(touch_input_dev); if (error) goto fail; - wacom_wac->touch_registered = true; } error = wacom_setup_pad_input_capabilities(pad_input_dev, wacom_wac); @@ -1490,18 +1460,14 @@ static int wacom_register_inputs(struct wacom *wacom) error = input_register_device(pad_input_dev); if (error) goto fail; - wacom_wac->pad_registered = true; } return 0; fail: wacom_wac->pad_input = NULL; - wacom_wac->pad_registered = false; wacom_wac->touch_input = NULL; - wacom_wac->touch_registered = false; wacom_wac->pen_input = NULL; - wacom_wac->pen_registered = false; return error; } @@ -1612,6 +1578,22 @@ static void wacom_update_name(struct wacom *wacom, const char *suffix) "%s%s Pad", name, suffix); } +static void wacom_release_resources(struct wacom *wacom) +{ + struct hid_device *hdev = wacom->hdev; + + if (!wacom->resources) + return; + + devres_release_group(&hdev->dev, wacom); + + wacom->resources = false; + + wacom->wacom_wac.pen_input = NULL; + wacom->wacom_wac.touch_input = NULL; + wacom->wacom_wac.pad_input = NULL; +} + static int wacom_parse_and_register(struct wacom *wacom, bool wireless) { struct wacom_wac *wacom_wac = &wacom->wacom_wac; @@ -1624,9 +1606,14 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) if (features->pktlen > WACOM_PKGLEN_MAX) return -EINVAL; + if (!devres_open_group(&hdev->dev, wacom, GFP_KERNEL)) + return -ENOMEM; + + wacom->resources = true; + error = wacom_allocate_inputs(wacom); if (error) - return error; + goto fail_open_group; /* * Bamboo Pad has a generic hid handling for the Pen, and we switch it @@ -1733,6 +1720,8 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) wacom_wac->shared->touch_input = wacom_wac->touch_input; } + devres_close_group(&hdev->dev, wacom); + return 0; fail_quirks: @@ -1748,6 +1737,8 @@ fail_battery: fail_shared_data: fail_parsed: fail_allocate_inputs: +fail_open_group: + wacom_release_resources(wacom); return error; } @@ -1773,14 +1764,14 @@ static void wacom_wireless_work(struct work_struct *work) wacom1 = hid_get_drvdata(hdev1); wacom_wac1 = &(wacom1->wacom_wac); wacom_destroy_leds(wacom1); - wacom_clean_inputs(wacom1); + wacom_release_resources(wacom1); /* Touch interface */ hdev2 = usb_get_intfdata(usbdev->config->interface[2]); wacom2 = hid_get_drvdata(hdev2); wacom_wac2 = &(wacom2->wacom_wac); wacom_destroy_leds(wacom2); - wacom_clean_inputs(wacom2); + wacom_release_resources(wacom2); if (wacom_wac->pid == 0) { hid_info(wacom->hdev, "wireless tablet disconnected\n"); @@ -1835,9 +1826,9 @@ static void wacom_wireless_work(struct work_struct *work) fail: wacom_destroy_leds(wacom1); - wacom_clean_inputs(wacom1); + wacom_release_resources(wacom1); wacom_destroy_leds(wacom2); - wacom_clean_inputs(wacom2); + wacom_release_resources(wacom2); return; } diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index c7707b29eeec..3e2c90323326 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1941,7 +1941,7 @@ static int wacom_bpt3_touch(struct wacom_wac *wacom) } /* only update touch if we actually have a touchpad and touch data changed */ - if (wacom->touch_registered && touch_changed) { + if (wacom->touch_input && touch_changed) { input_mt_sync_frame(wacom->touch_input); wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); } diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 53d16537fd2a..d5b973d0567c 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -234,9 +234,6 @@ struct wacom_wac { struct input_dev *pen_input; struct input_dev *touch_input; struct input_dev *pad_input; - bool pen_registered; - bool touch_registered; - bool pad_registered; int pid; int battery_capacity; int num_contacts_left; -- cgit v1.2.3 From 2df68a8864883ff006b76f50dfc32fd230247ef9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:56 +0200 Subject: HID: wacom: convert LEDs to devres Use our own wacom_devm_sysfs_create_group() as there is currently no generic one. It has been requested at least twice [1][2] but has been always rejected. However, in the Wacom case, for the wirelessly connected devices, we need to be able to release the created sysfs files without removing the parent kobject. [1] https://patchwork.kernel.org/patch/7526551/ [2] https://lkml.org/lkml/2013/3/14/728 Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 1 - drivers/hid/wacom_sys.c | 95 +++++++++++++++++++++++-------------------------- 2 files changed, 45 insertions(+), 51 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 6f5555fa1eab..bcfeb517221e 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -124,7 +124,6 @@ struct wacom { u8 hlv; /* status led brightness button pressed (1..127) */ u8 img_lum; /* OLED matrix display brightness */ } led; - bool led_initialized; struct power_supply *battery; struct power_supply *ac; struct power_supply_desc battery_desc; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 08f6d1f29c73..c08a7522fdb9 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -909,6 +909,45 @@ static struct attribute_group intuos5_led_attr_group = { .attrs = intuos5_led_attrs, }; +struct wacom_sysfs_group_devres { + struct attribute_group *group; + struct kobject *root; +}; + +static void wacom_devm_sysfs_group_release(struct device *dev, void *res) +{ + struct wacom_sysfs_group_devres *devres = res; + struct kobject *kobj = devres->root; + + dev_dbg(dev, "%s: dropping reference to %s\n", + __func__, devres->group->name); + sysfs_remove_group(kobj, devres->group); +} + +static int wacom_devm_sysfs_create_group(struct wacom *wacom, + struct attribute_group *group) +{ + struct wacom_sysfs_group_devres *devres; + int error; + + devres = devres_alloc(wacom_devm_sysfs_group_release, + sizeof(struct wacom_sysfs_group_devres), + GFP_KERNEL); + if (!devres) + return -ENOMEM; + + devres->group = group; + devres->root = &wacom->hdev->dev.kobj; + + error = sysfs_create_group(devres->root, group); + if (error) + return error; + + devres_add(&wacom->hdev->dev, devres); + + return 0; +} + static int wacom_initialize_leds(struct wacom *wacom) { int error; @@ -927,8 +966,8 @@ static int wacom_initialize_leds(struct wacom *wacom) wacom->led.llv = 10; wacom->led.hlv = 20; wacom->led.img_lum = 10; - error = sysfs_create_group(&wacom->hdev->dev.kobj, - &intuos4_led_attr_group); + error = wacom_devm_sysfs_create_group(wacom, + &intuos4_led_attr_group); break; case WACOM_24HD: @@ -939,8 +978,8 @@ static int wacom_initialize_leds(struct wacom *wacom) wacom->led.hlv = 0; wacom->led.img_lum = 0; - error = sysfs_create_group(&wacom->hdev->dev.kobj, - &cintiq_led_attr_group); + error = wacom_devm_sysfs_create_group(wacom, + &cintiq_led_attr_group); break; case INTUOS5S: @@ -955,8 +994,8 @@ static int wacom_initialize_leds(struct wacom *wacom) wacom->led.hlv = 0; wacom->led.img_lum = 0; - error = sysfs_create_group(&wacom->hdev->dev.kobj, - &intuos5_led_attr_group); + error = wacom_devm_sysfs_create_group(wacom, + &intuos5_led_attr_group); break; default: @@ -969,48 +1008,10 @@ static int wacom_initialize_leds(struct wacom *wacom) return error; } wacom_led_control(wacom); - wacom->led_initialized = true; return 0; } -static void wacom_destroy_leds(struct wacom *wacom) -{ - if (!wacom->led_initialized) - return; - - if (!(wacom->wacom_wac.features.device_type & WACOM_DEVICETYPE_PAD)) - return; - - wacom->led_initialized = false; - - switch (wacom->wacom_wac.features.type) { - case INTUOS4S: - case INTUOS4: - case INTUOS4WL: - case INTUOS4L: - sysfs_remove_group(&wacom->hdev->dev.kobj, - &intuos4_led_attr_group); - break; - - case WACOM_24HD: - case WACOM_21UX2: - sysfs_remove_group(&wacom->hdev->dev.kobj, - &cintiq_led_attr_group); - break; - - case INTUOS5S: - case INTUOS5: - case INTUOS5L: - case INTUOSPS: - case INTUOSPM: - case INTUOSPL: - sysfs_remove_group(&wacom->hdev->dev.kobj, - &intuos5_led_attr_group); - break; - } -} - static enum power_supply_property wacom_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_STATUS, @@ -1729,7 +1730,6 @@ fail_quirks: fail_hw_start: kobject_put(wacom->remote_dir); fail_remote: - wacom_destroy_leds(wacom); fail_leds: fail_register_inputs: fail_battery: @@ -1763,14 +1763,12 @@ static void wacom_wireless_work(struct work_struct *work) hdev1 = usb_get_intfdata(usbdev->config->interface[1]); wacom1 = hid_get_drvdata(hdev1); wacom_wac1 = &(wacom1->wacom_wac); - wacom_destroy_leds(wacom1); wacom_release_resources(wacom1); /* Touch interface */ hdev2 = usb_get_intfdata(usbdev->config->interface[2]); wacom2 = hid_get_drvdata(hdev2); wacom_wac2 = &(wacom2->wacom_wac); - wacom_destroy_leds(wacom2); wacom_release_resources(wacom2); if (wacom_wac->pid == 0) { @@ -1825,9 +1823,7 @@ static void wacom_wireless_work(struct work_struct *work) return; fail: - wacom_destroy_leds(wacom1); wacom_release_resources(wacom1); - wacom_destroy_leds(wacom2); wacom_release_resources(wacom2); return; } @@ -1917,7 +1913,6 @@ static void wacom_remove(struct hid_device *hdev) cancel_work_sync(&wacom->wireless_work); cancel_work_sync(&wacom->battery_work); kobject_put(wacom->remote_dir); - wacom_destroy_leds(wacom); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); wacom_remove_shared_data(wacom); -- cgit v1.2.3 From c1f5409b74c14ac021ef847e034356c904bc7882 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:57 +0200 Subject: HID: wacom: use devm_kasprintf for allocating the name of the remote The sysfs group was indeed removed by kobject_put(wacom->remote_dir) in wacom_remove(), but the name of the group was never freed. Also remove the misplaced kobject_put(wacom->remote_dir) in the error path of wacom_remote_create_attr_group(). Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index c08a7522fdb9..ec088c1a4868 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -25,7 +25,6 @@ #define WAC_CMD_RETRIES 10 #define WAC_CMD_DELETE_PAIRING 0x20 #define WAC_CMD_UNPAIR_ALL 0xFF -#define WAC_REMOTE_SERIAL_MAX_STRLEN 9 #define DEV_ATTR_RW_PERM (S_IRUGO | S_IWUSR | S_IWGRP) #define DEV_ATTR_WO_PERM (S_IWUSR | S_IWGRP) @@ -1233,23 +1232,21 @@ DEVICE_EKR_ATTR_GROUP(4); int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, int index) { int error = 0; - char *buf; struct wacom_wac *wacom_wac = &wacom->wacom_wac; wacom_wac->serial[index] = serial; - buf = kzalloc(WAC_REMOTE_SERIAL_MAX_STRLEN, GFP_KERNEL); - if (!buf) + wacom->remote_group[index].name = devm_kasprintf(&wacom->hdev->dev, + GFP_KERNEL, + "%d", serial); + if (!wacom->remote_group[index].name) return -ENOMEM; - snprintf(buf, WAC_REMOTE_SERIAL_MAX_STRLEN, "%d", serial); - wacom->remote_group[index].name = buf; error = sysfs_create_group(wacom->remote_dir, &wacom->remote_group[index]); if (error) { hid_err(wacom->hdev, "cannot create sysfs group err: %d\n", error); - kobject_put(wacom->remote_dir); return error; } @@ -1271,7 +1268,8 @@ void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial) if (wacom->remote_group[i].name) { sysfs_remove_group(wacom->remote_dir, &wacom->remote_group[i]); - kfree(wacom->remote_group[i].name); + devm_kfree(&wacom->hdev->dev, + (char *)wacom->remote_group[i].name); wacom->remote_group[i].name = NULL; } } -- cgit v1.2.3 From 19b643300181ccf2bd83cd751283508b9ae179f5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:58 +0200 Subject: HID: wacom: use devres to allocate driver data We started switching the driver to devres, so we should use it as much as possible. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index ec088c1a4868..56d62e8a400b 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1844,7 +1844,7 @@ static int wacom_probe(struct hid_device *hdev, /* hid-core sets this quirk for the boot interface */ hdev->quirks &= ~HID_QUIRK_NOGET; - wacom = kzalloc(sizeof(struct wacom), GFP_KERNEL); + wacom = devm_kzalloc(&hdev->dev, sizeof(struct wacom), GFP_KERNEL); if (!wacom) return -ENOMEM; @@ -1892,7 +1892,6 @@ static int wacom_probe(struct hid_device *hdev, fail_type: fail_parse: - kfree(wacom); hid_set_drvdata(hdev, NULL); return error; } @@ -1916,7 +1915,6 @@ static void wacom_remove(struct hid_device *hdev) wacom_remove_shared_data(wacom); hid_set_drvdata(hdev, NULL); - kfree(wacom); } #ifdef CONFIG_PM -- cgit v1.2.3 From 1c817c83e8ccea5f710d3c0d38edcfd362654ba2 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:05:59 +0200 Subject: HID: wacom: devres manage the shared data too wacom_release_shared_data() and wacom_remove_shared_data() are moved up so they can be referenced in wacom_add_shared_data(). There is no point in explicitly setting wacom_wac1->shared->type to 0 in wacom_wireless_work() (plus this would give an oops). Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 73 ++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 56d62e8a400b..d0f57647cd5e 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -567,6 +567,38 @@ static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev) return NULL; } +static void wacom_release_shared_data(struct kref *kref) +{ + struct wacom_hdev_data *data = + container_of(kref, struct wacom_hdev_data, kref); + + mutex_lock(&wacom_udev_list_lock); + list_del(&data->list); + mutex_unlock(&wacom_udev_list_lock); + + kfree(data); +} + +static void wacom_remove_shared_data(void *res) +{ + struct wacom *wacom = res; + struct wacom_hdev_data *data; + struct wacom_wac *wacom_wac = &wacom->wacom_wac; + + if (wacom_wac->shared) { + data = container_of(wacom_wac->shared, struct wacom_hdev_data, + shared); + + if (wacom_wac->shared->touch == wacom->hdev) + wacom_wac->shared->touch = NULL; + else if (wacom_wac->shared->pen == wacom->hdev) + wacom_wac->shared->pen = NULL; + + kref_put(&data->kref, wacom_release_shared_data); + wacom_wac->shared = NULL; + } +} + static int wacom_add_shared_data(struct hid_device *hdev) { struct wacom *wacom = hid_get_drvdata(hdev); @@ -591,6 +623,13 @@ static int wacom_add_shared_data(struct hid_device *hdev) wacom_wac->shared = &data->shared; + retval = devm_add_action(&hdev->dev, wacom_remove_shared_data, wacom); + if (retval) { + mutex_unlock(&wacom_udev_list_lock); + wacom_remove_shared_data(wacom); + return retval; + } + if (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH) wacom_wac->shared->touch = hdev; else if (wacom_wac->features.device_type & WACOM_DEVICETYPE_PEN) @@ -601,37 +640,6 @@ out: return retval; } -static void wacom_release_shared_data(struct kref *kref) -{ - struct wacom_hdev_data *data = - container_of(kref, struct wacom_hdev_data, kref); - - mutex_lock(&wacom_udev_list_lock); - list_del(&data->list); - mutex_unlock(&wacom_udev_list_lock); - - kfree(data); -} - -static void wacom_remove_shared_data(struct wacom *wacom) -{ - struct wacom_hdev_data *data; - struct wacom_wac *wacom_wac = &wacom->wacom_wac; - - if (wacom_wac->shared) { - data = container_of(wacom_wac->shared, struct wacom_hdev_data, - shared); - - if (wacom_wac->shared->touch == wacom->hdev) - wacom_wac->shared->touch = NULL; - else if (wacom_wac->shared->pen == wacom->hdev) - wacom_wac->shared->pen = NULL; - - kref_put(&data->kref, wacom_release_shared_data); - wacom_wac->shared = NULL; - } -} - static int wacom_led_control(struct wacom *wacom) { unsigned char *buf; @@ -1731,7 +1739,6 @@ fail_remote: fail_leds: fail_register_inputs: fail_battery: - wacom_remove_shared_data(wacom); fail_shared_data: fail_parsed: fail_allocate_inputs: @@ -1771,7 +1778,6 @@ static void wacom_wireless_work(struct work_struct *work) if (wacom_wac->pid == 0) { hid_info(wacom->hdev, "wireless tablet disconnected\n"); - wacom_wac1->shared->type = 0; } else { const struct hid_device_id *id = wacom_ids; @@ -1912,7 +1918,6 @@ static void wacom_remove(struct hid_device *hdev) kobject_put(wacom->remote_dir); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); - wacom_remove_shared_data(wacom); hid_set_drvdata(hdev, NULL); } -- cgit v1.2.3 From a50aac7193f18a5a997588556f1212b0d1ba029b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:00 +0200 Subject: HID: wacom: leds: dynamically allocate LED groups We need to add an action to ensure wacom->led.groups is null when wacom_led_control() gets called after the resources has been freed. This also prevents to send a LED command when there is no support from the device. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 8 +++-- drivers/hid/wacom_sys.c | 85 +++++++++++++++++++++++++++++++++++++++++-------- drivers/hid/wacom_wac.c | 2 +- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index bcfeb517221e..8ac1eb8a4382 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -110,6 +110,10 @@ enum wacom_worker { WACOM_WORKER_BATTERY, }; +struct wacom_group_leds { + u8 select; /* status led selector (0..3) */ +}; + struct wacom { struct usb_device *usbdev; struct usb_interface *intf; @@ -118,8 +122,8 @@ struct wacom { struct mutex lock; struct work_struct wireless_work; struct work_struct battery_work; - struct wacom_led { - u8 select[5]; /* status led selector (0..3) */ + struct wacom_leds { + struct wacom_group_leds *groups; u8 llv; /* status led brightness no button (1..127) */ u8 hlv; /* status led brightness button pressed (1..127) */ u8 img_lum; /* OLED matrix display brightness */ diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index d0f57647cd5e..4d4c737593df 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -647,6 +647,9 @@ static int wacom_led_control(struct wacom *wacom) unsigned char report_id = WAC_CMD_LED_CONTROL; int buf_size = 9; + if (!wacom->led.groups) + return -ENOTSUPP; + if (wacom->wacom_wac.pid) { /* wireless connected */ report_id = WAC_CMD_WL_LED_CONTROL; buf_size = 13; @@ -662,7 +665,7 @@ static int wacom_led_control(struct wacom *wacom) * one of four values: * 0 = Low; 1 = Medium; 2 = High; 3 = Off */ - int ring_led = wacom->led.select[0] & 0x03; + int ring_led = wacom->led.groups[0].select & 0x03; int ring_lum = (((wacom->led.llv & 0x60) >> 5) - 1) & 0x03; int crop_lum = 0; unsigned char led_bits = (crop_lum << 4) | (ring_lum << 2) | (ring_led); @@ -677,11 +680,11 @@ static int wacom_led_control(struct wacom *wacom) buf[1] = led_bits; } else { - int led = wacom->led.select[0] | 0x4; + int led = wacom->led.groups[0].select | 0x4; if (wacom->wacom_wac.features.type == WACOM_21UX2 || wacom->wacom_wac.features.type == WACOM_24HD) - led |= (wacom->led.select[1] << 4) | 0x40; + led |= (wacom->led.groups[1].select << 4) | 0x40; buf[0] = report_id; buf[1] = led; @@ -753,7 +756,7 @@ static ssize_t wacom_led_select_store(struct device *dev, int set_id, mutex_lock(&wacom->lock); - wacom->led.select[set_id] = id & 0x3; + wacom->led.groups[set_id].select = id & 0x3; err = wacom_led_control(wacom); mutex_unlock(&wacom->lock); @@ -773,7 +776,7 @@ static ssize_t wacom_led##SET_ID##_select_show(struct device *dev, \ struct hid_device *hdev = to_hid_device(dev);\ struct wacom *wacom = hid_get_drvdata(hdev); \ return scnprintf(buf, PAGE_SIZE, "%d\n", \ - wacom->led.select[SET_ID]); \ + wacom->led.groups[SET_ID].select); \ } \ static DEVICE_ATTR(status_led##SET_ID##_select, DEV_ATTR_RW_PERM, \ wacom_led##SET_ID##_select_show, \ @@ -955,6 +958,35 @@ static int wacom_devm_sysfs_create_group(struct wacom *wacom, return 0; } +static void wacom_led_groups_release(void *data) +{ + struct wacom *wacom = data; + + wacom->led.groups = NULL; +} + +static int wacom_led_groups_allocate(struct wacom *wacom, int count) +{ + struct wacom_group_leds *groups; + int error; + + groups = devm_kzalloc(&wacom->hdev->dev, + sizeof(struct wacom_group_leds) * count, + GFP_KERNEL); + if (!groups) + return -ENOMEM; + + error = devm_add_action_or_reset(&wacom->hdev->dev, + wacom_led_groups_release, + wacom); + if (error) + return error; + + wacom->led.groups = groups; + + return 0; +} + static int wacom_initialize_leds(struct wacom *wacom) { int error; @@ -968,23 +1000,34 @@ static int wacom_initialize_leds(struct wacom *wacom) case INTUOS4: case INTUOS4WL: case INTUOS4L: - wacom->led.select[0] = 0; - wacom->led.select[1] = 0; wacom->led.llv = 10; wacom->led.hlv = 20; wacom->led.img_lum = 10; + + error = wacom_led_groups_allocate(wacom, 1); + if (error) { + hid_err(wacom->hdev, + "cannot create leds err: %d\n", error); + return error; + } + error = wacom_devm_sysfs_create_group(wacom, &intuos4_led_attr_group); break; case WACOM_24HD: case WACOM_21UX2: - wacom->led.select[0] = 0; - wacom->led.select[1] = 0; wacom->led.llv = 0; wacom->led.hlv = 0; wacom->led.img_lum = 0; + error = wacom_led_groups_allocate(wacom, 2); + if (error) { + hid_err(wacom->hdev, + "cannot create leds err: %d\n", error); + return error; + } + error = wacom_devm_sysfs_create_group(wacom, &cintiq_led_attr_group); break; @@ -995,16 +1038,30 @@ static int wacom_initialize_leds(struct wacom *wacom) case INTUOSPS: case INTUOSPM: case INTUOSPL: - wacom->led.select[0] = 0; - wacom->led.select[1] = 0; wacom->led.llv = 32; wacom->led.hlv = 0; wacom->led.img_lum = 0; + error = wacom_led_groups_allocate(wacom, 1); + if (error) { + hid_err(wacom->hdev, + "cannot create leds err: %d\n", error); + return error; + } + error = wacom_devm_sysfs_create_group(wacom, &intuos5_led_attr_group); break; + case REMOTE: + error = wacom_led_groups_allocate(wacom, 5); + if (error) { + hid_err(wacom->hdev, + "cannot create leds err: %d\n", error); + return error; + } + return 0; + default: return 0; } @@ -1204,7 +1261,7 @@ static ssize_t wacom_show_remote_mode(struct kobject *kobj, struct wacom *wacom = hid_get_drvdata(hdev); u8 mode; - mode = wacom->led.select[index]; + mode = wacom->led.groups[index].select; if (mode >= 0 && mode < 3) return snprintf(buf, PAGE_SIZE, "%d\n", mode); else @@ -1272,7 +1329,7 @@ void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial) for (i = 0; i < WACOM_MAX_REMOTES; i++) { if (wacom_wac->serial[i] == serial) { wacom_wac->serial[i] = 0; - wacom->led.select[i] = WACOM_STATUS_UNKNOWN; + wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; if (wacom->remote_group[i].name) { sysfs_remove_group(wacom->remote_dir, &wacom->remote_group[i]); @@ -1369,7 +1426,7 @@ static int wacom_initialize_remote(struct wacom *wacom) } for (i = 0; i < WACOM_MAX_REMOTES; i++) { - wacom->led.select[i] = WACOM_STATUS_UNKNOWN; + wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; wacom_wac->serial[i] = 0; } diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 3e2c90323326..932d3eec933b 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -808,7 +808,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) for (i = 0; i < WACOM_MAX_REMOTES; i++) { if (wacom_wac->serial[i] == serial) - wacom->led.select[i] = touch_ring_mode; + wacom->led.groups[i].select = touch_ring_mode; } if (!wacom->battery && -- cgit v1.2.3 From e6f2813a6d3fd924a399f8b19bcd8b11487155bc Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:01 +0200 Subject: HID: wacom: EKR: add a worker to add/remove resources on addition/removal wacom_remote_status_irq() sends information of addition/removal of EKR. We want to allocate one input node per remote, so better having this in a separate worker, not handled in the IRQ directly. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 11 +++++-- drivers/hid/wacom_sys.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++-- drivers/hid/wacom_wac.c | 53 +++++++++++++------------------- drivers/hid/wacom_wac.h | 7 +++++ 4 files changed, 113 insertions(+), 38 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 8ac1eb8a4382..1797e4b3662a 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -90,6 +90,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,7 @@ enum wacom_worker { WACOM_WORKER_WIRELESS, WACOM_WORKER_BATTERY, + WACOM_WORKER_REMOTE, }; struct wacom_group_leds { @@ -122,6 +124,9 @@ struct wacom { struct mutex lock; struct work_struct wireless_work; struct work_struct battery_work; + struct work_struct remote_work; + spinlock_t remote_lock; + struct kfifo remote_fifo; struct wacom_leds { struct wacom_group_leds *groups; u8 llv; /* status led brightness no button (1..127) */ @@ -149,6 +154,9 @@ static inline void wacom_schedule_work(struct wacom_wac *wacom_wac, case WACOM_WORKER_BATTERY: schedule_work(&wacom->battery_work); break; + case WACOM_WORKER_REMOTE: + schedule_work(&wacom->remote_work); + break; } } @@ -168,7 +176,4 @@ int wacom_wac_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value); void wacom_wac_report(struct hid_device *hdev, struct hid_report *report); void wacom_battery_work(struct work_struct *work); -int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, - int index); -void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial); #endif diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 4d4c737593df..7e01117433dd 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1294,7 +1294,8 @@ DEVICE_EKR_ATTR_GROUP(2); DEVICE_EKR_ATTR_GROUP(3); DEVICE_EKR_ATTR_GROUP(4); -int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, int index) +static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, + int index) { int error = 0; struct wacom_wac *wacom_wac = &wacom->wacom_wac; @@ -1318,7 +1319,7 @@ int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, int index) return 0; } -void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial) +static void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial) { struct wacom_wac *wacom_wac = &wacom->wacom_wac; int i; @@ -1889,6 +1890,65 @@ fail: return; } +static void wacom_remote_work(struct work_struct *work) +{ + struct wacom *wacom = container_of(work, struct wacom, remote_work); + struct wacom_wac *wacom_wac = &wacom->wacom_wac; + struct wacom_remote_data data; + unsigned long flags; + unsigned int count; + u32 serial; + int i, k; + + spin_lock_irqsave(&wacom->remote_lock, flags); + + count = kfifo_out(&wacom->remote_fifo, &data, sizeof(data)); + + if (count != sizeof(data)) { + hid_err(wacom->hdev, + "workitem triggered without status available\n"); + spin_unlock_irqrestore(&wacom->remote_lock, flags); + return; + } + + if (!kfifo_is_empty(&wacom->remote_fifo)) + wacom_schedule_work(&wacom->wacom_wac, WACOM_WORKER_REMOTE); + + spin_unlock_irqrestore(&wacom->remote_lock, flags); + + for (i = 0; i < WACOM_MAX_REMOTES; i++) { + serial = data.remote[i].serial; + if (data.remote[i].connected) { + + if (wacom_wac->serial[i] == serial) + continue; + + if (wacom_wac->serial[i]) { + wacom_remote_destroy_attr_group(wacom, + wacom_wac->serial[i]); + } + + /* A remote can pair more than once with an EKR, + * check to make sure this serial isn't already paired. + */ + for (k = 0; k < WACOM_MAX_REMOTES; k++) { + if (wacom_wac->serial[k] == serial) + break; + } + + if (k < WACOM_MAX_REMOTES) { + wacom_wac->serial[i] = serial; + continue; + } + wacom_remote_create_attr_group(wacom, serial, i); + + } else if (wacom_wac->serial[i]) { + wacom_remote_destroy_attr_group(wacom, + wacom_wac->serial[i]); + } + } +} + static int wacom_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -1931,6 +1991,17 @@ static int wacom_probe(struct hid_device *hdev, mutex_init(&wacom->lock); INIT_WORK(&wacom->wireless_work, wacom_wireless_work); INIT_WORK(&wacom->battery_work, wacom_battery_work); + INIT_WORK(&wacom->remote_work, wacom_remote_work); + spin_lock_init(&wacom->remote_lock); + + if (kfifo_alloc(&wacom->remote_fifo, + 5 * sizeof(struct wacom_remote_data), + GFP_KERNEL)) { + dev_err(&hdev->dev, + "%s:failed allocating remote_fifo\n", __func__); + error = -ENOMEM; + goto fail_type; + } /* ask for the report descriptor to be loaded by HID */ error = hid_parse(hdev); @@ -1953,8 +2024,9 @@ static int wacom_probe(struct hid_device *hdev, return 0; -fail_type: fail_parse: + kfifo_free(&wacom->remote_fifo); +fail_type: hid_set_drvdata(hdev, NULL); return error; } @@ -1972,6 +2044,8 @@ static void wacom_remove(struct hid_device *hdev) cancel_work_sync(&wacom->wireless_work); cancel_work_sync(&wacom->battery_work); + cancel_work_sync(&wacom->remote_work); + kfifo_free(&wacom->remote_fifo); kobject_put(wacom->remote_dir); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 932d3eec933b..d54992967391 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -823,52 +823,40 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) return 1; } -static int wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) +static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) { struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); unsigned char *data = wacom_wac->data; - int i; + struct wacom_remote_data remote_data; + unsigned long flags; + int i, ret; if (data[0] != WACOM_REPORT_DEVICE_LIST) - return 0; + return; + + memset(&remote_data, 0, sizeof(struct wacom_remote_data)); for (i = 0; i < WACOM_MAX_REMOTES; i++) { int j = i * 6; int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4]; bool connected = data[j+2]; - if (connected) { - int k; - - if (wacom_wac->serial[i] == serial) - continue; - - if (wacom_wac->serial[i]) { - wacom_remote_destroy_attr_group(wacom, - wacom_wac->serial[i]); - } - - /* A remote can pair more than once with an EKR, - * check to make sure this serial isn't already paired. - */ - for (k = 0; k < WACOM_MAX_REMOTES; k++) { - if (wacom_wac->serial[k] == serial) - break; - } + remote_data.remote[i].serial = serial; + remote_data.remote[i].connected = connected; + } - if (k < WACOM_MAX_REMOTES) { - wacom_wac->serial[i] = serial; - continue; - } - wacom_remote_create_attr_group(wacom, serial, i); + spin_lock_irqsave(&wacom->remote_lock, flags); - } else if (wacom_wac->serial[i]) { - wacom_remote_destroy_attr_group(wacom, - wacom_wac->serial[i]); - } + ret = kfifo_in(&wacom->remote_fifo, &remote_data, sizeof(remote_data)); + if (ret != sizeof(remote_data)) { + spin_unlock_irqrestore(&wacom->remote_lock, flags); + hid_err(wacom->hdev, "Can't queue Remote status event.\n"); + return; } - return 0; + spin_unlock_irqrestore(&wacom->remote_lock, flags); + + wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE); } static int wacom_intuos_general(struct wacom_wac *wacom) @@ -2310,8 +2298,9 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len) break; case REMOTE: + sync = false; if (wacom_wac->data[0] == WACOM_REPORT_DEVICE_LIST) - sync = wacom_remote_status_irq(wacom_wac, len); + wacom_remote_status_irq(wacom_wac, len); else sync = wacom_remote_irq(wacom_wac, len); break; diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index d5b973d0567c..c2c65b8c71c1 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -218,6 +218,13 @@ struct hid_data { int num_received; }; +struct wacom_remote_data { + struct { + u32 serial; + bool connected; + } remote[WACOM_MAX_REMOTES]; +}; + struct wacom_wac { char pen_name[WACOM_NAME_MAX]; char touch_name[WACOM_NAME_MAX]; -- cgit v1.2.3 From 83e6b40e2de6f8416ce6c8c3ca6c4bc8acfa8b32 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:02 +0200 Subject: HID: wacom: EKR: have the wacom resources dynamically allocated If we want to have one input device per remote, it's better to have our own struct wacom_remote which is dynamically allocated. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 13 +++-- drivers/hid/wacom_sys.c | 133 ++++++++++++++++++++++++++++-------------------- drivers/hid/wacom_wac.c | 12 +++-- drivers/hid/wacom_wac.h | 2 +- 4 files changed, 94 insertions(+), 66 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 1797e4b3662a..110ea6793b99 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -116,6 +116,14 @@ struct wacom_group_leds { u8 select; /* status led selector (0..3) */ }; +struct wacom_remote { + spinlock_t remote_lock; + struct kfifo remote_fifo; + struct kobject *remote_dir; + struct attribute_group remote_group[WACOM_MAX_REMOTES]; + __u32 serial[WACOM_MAX_REMOTES]; +}; + struct wacom { struct usb_device *usbdev; struct usb_interface *intf; @@ -125,8 +133,7 @@ struct wacom { struct work_struct wireless_work; struct work_struct battery_work; struct work_struct remote_work; - spinlock_t remote_lock; - struct kfifo remote_fifo; + struct wacom_remote *remote; struct wacom_leds { struct wacom_group_leds *groups; u8 llv; /* status led brightness no button (1..127) */ @@ -137,8 +144,6 @@ struct wacom { struct power_supply *ac; struct power_supply_desc battery_desc; struct power_supply_desc ac_desc; - struct kobject *remote_dir; - struct attribute_group remote_group[5]; bool resources; }; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 7e01117433dd..64acb71adde4 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1298,18 +1298,18 @@ static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, int index) { int error = 0; - struct wacom_wac *wacom_wac = &wacom->wacom_wac; + struct wacom_remote *remote = wacom->remote; - wacom_wac->serial[index] = serial; + remote->serial[index] = serial; - wacom->remote_group[index].name = devm_kasprintf(&wacom->hdev->dev, - GFP_KERNEL, - "%d", serial); - if (!wacom->remote_group[index].name) + remote->remote_group[index].name = devm_kasprintf(&wacom->hdev->dev, + GFP_KERNEL, + "%d", serial); + if (!remote->remote_group[index].name) return -ENOMEM; - error = sysfs_create_group(wacom->remote_dir, - &wacom->remote_group[index]); + error = sysfs_create_group(remote->remote_dir, + &remote->remote_group[index]); if (error) { hid_err(wacom->hdev, "cannot create sysfs group err: %d\n", error); @@ -1321,22 +1321,22 @@ static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, static void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial) { - struct wacom_wac *wacom_wac = &wacom->wacom_wac; + struct wacom_remote *remote = wacom->remote; int i; if (!serial) return; for (i = 0; i < WACOM_MAX_REMOTES; i++) { - if (wacom_wac->serial[i] == serial) { - wacom_wac->serial[i] = 0; + if (remote->serial[i] == serial) { + remote->serial[i] = 0; wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; - if (wacom->remote_group[i].name) { - sysfs_remove_group(wacom->remote_dir, - &wacom->remote_group[i]); + if (remote->remote_group[i].name) { + sysfs_remove_group(remote->remote_dir, + &remote->remote_group[i]); devm_kfree(&wacom->hdev->dev, - (char *)wacom->remote_group[i].name); - wacom->remote_group[i].name = NULL; + (char *)remote->remote_group[i].name); + remote->remote_group[i].name = NULL; } } } @@ -1398,27 +1398,57 @@ static const struct attribute *remote_unpair_attrs[] = { NULL }; -static int wacom_initialize_remote(struct wacom *wacom) +static void wacom_remotes_destroy(void *data) +{ + struct wacom *wacom = data; + struct wacom_remote *remote = wacom->remote; + + if (!remote) + return; + + kobject_put(remote->remote_dir); + kfifo_free(&remote->remote_fifo); + wacom->remote = NULL; +} + +static int wacom_initialize_remotes(struct wacom *wacom) { int error = 0; - struct wacom_wac *wacom_wac = &(wacom->wacom_wac); + struct wacom_remote *remote; int i; if (wacom->wacom_wac.features.type != REMOTE) return 0; - wacom->remote_group[0] = remote0_serial_group; - wacom->remote_group[1] = remote1_serial_group; - wacom->remote_group[2] = remote2_serial_group; - wacom->remote_group[3] = remote3_serial_group; - wacom->remote_group[4] = remote4_serial_group; + remote = devm_kzalloc(&wacom->hdev->dev, sizeof(*wacom->remote), + GFP_KERNEL); + if (!remote) + return -ENOMEM; - wacom->remote_dir = kobject_create_and_add("wacom_remote", - &wacom->hdev->dev.kobj); - if (!wacom->remote_dir) + wacom->remote = remote; + + spin_lock_init(&remote->remote_lock); + + error = kfifo_alloc(&remote->remote_fifo, + 5 * sizeof(struct wacom_remote_data), + GFP_KERNEL); + if (error) { + hid_err(wacom->hdev, "failed allocating remote_fifo\n"); return -ENOMEM; + } - error = sysfs_create_files(wacom->remote_dir, remote_unpair_attrs); + remote->remote_group[0] = remote0_serial_group; + remote->remote_group[1] = remote1_serial_group; + remote->remote_group[2] = remote2_serial_group; + remote->remote_group[3] = remote3_serial_group; + remote->remote_group[4] = remote4_serial_group; + + remote->remote_dir = kobject_create_and_add("wacom_remote", + &wacom->hdev->dev.kobj); + if (!remote->remote_dir) + return -ENOMEM; + + error = sysfs_create_files(remote->remote_dir, remote_unpair_attrs); if (error) { hid_err(wacom->hdev, @@ -1428,9 +1458,14 @@ static int wacom_initialize_remote(struct wacom *wacom) for (i = 0; i < WACOM_MAX_REMOTES; i++) { wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; - wacom_wac->serial[i] = 0; + remote->serial[i] = 0; } + error = devm_add_action_or_reset(&wacom->hdev->dev, + wacom_remotes_destroy, wacom); + if (error) + return error; + return 0; } @@ -1740,7 +1775,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) if (error) goto fail_leds; - error = wacom_initialize_remote(wacom); + error = wacom_initialize_remotes(wacom); if (error) goto fail_remote; } @@ -1792,7 +1827,6 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) fail_quirks: hid_hw_stop(hdev); fail_hw_start: - kobject_put(wacom->remote_dir); fail_remote: fail_leds: fail_register_inputs: @@ -1893,58 +1927,58 @@ fail: static void wacom_remote_work(struct work_struct *work) { struct wacom *wacom = container_of(work, struct wacom, remote_work); - struct wacom_wac *wacom_wac = &wacom->wacom_wac; + struct wacom_remote *remote = wacom->remote; struct wacom_remote_data data; unsigned long flags; unsigned int count; u32 serial; int i, k; - spin_lock_irqsave(&wacom->remote_lock, flags); + spin_lock_irqsave(&remote->remote_lock, flags); - count = kfifo_out(&wacom->remote_fifo, &data, sizeof(data)); + count = kfifo_out(&remote->remote_fifo, &data, sizeof(data)); if (count != sizeof(data)) { hid_err(wacom->hdev, "workitem triggered without status available\n"); - spin_unlock_irqrestore(&wacom->remote_lock, flags); + spin_unlock_irqrestore(&remote->remote_lock, flags); return; } - if (!kfifo_is_empty(&wacom->remote_fifo)) + if (!kfifo_is_empty(&remote->remote_fifo)) wacom_schedule_work(&wacom->wacom_wac, WACOM_WORKER_REMOTE); - spin_unlock_irqrestore(&wacom->remote_lock, flags); + spin_unlock_irqrestore(&remote->remote_lock, flags); for (i = 0; i < WACOM_MAX_REMOTES; i++) { serial = data.remote[i].serial; if (data.remote[i].connected) { - if (wacom_wac->serial[i] == serial) + if (remote->serial[i] == serial) continue; - if (wacom_wac->serial[i]) { + if (remote->serial[i]) { wacom_remote_destroy_attr_group(wacom, - wacom_wac->serial[i]); + remote->serial[i]); } /* A remote can pair more than once with an EKR, * check to make sure this serial isn't already paired. */ for (k = 0; k < WACOM_MAX_REMOTES; k++) { - if (wacom_wac->serial[k] == serial) + if (remote->serial[k] == serial) break; } if (k < WACOM_MAX_REMOTES) { - wacom_wac->serial[i] = serial; + remote->serial[i] = serial; continue; } wacom_remote_create_attr_group(wacom, serial, i); - } else if (wacom_wac->serial[i]) { + } else if (remote->serial[i]) { wacom_remote_destroy_attr_group(wacom, - wacom_wac->serial[i]); + remote->serial[i]); } } } @@ -1992,16 +2026,6 @@ static int wacom_probe(struct hid_device *hdev, INIT_WORK(&wacom->wireless_work, wacom_wireless_work); INIT_WORK(&wacom->battery_work, wacom_battery_work); INIT_WORK(&wacom->remote_work, wacom_remote_work); - spin_lock_init(&wacom->remote_lock); - - if (kfifo_alloc(&wacom->remote_fifo, - 5 * sizeof(struct wacom_remote_data), - GFP_KERNEL)) { - dev_err(&hdev->dev, - "%s:failed allocating remote_fifo\n", __func__); - error = -ENOMEM; - goto fail_type; - } /* ask for the report descriptor to be loaded by HID */ error = hid_parse(hdev); @@ -2025,7 +2049,6 @@ static int wacom_probe(struct hid_device *hdev, return 0; fail_parse: - kfifo_free(&wacom->remote_fifo); fail_type: hid_set_drvdata(hdev, NULL); return error; @@ -2045,8 +2068,6 @@ static void wacom_remove(struct hid_device *hdev) cancel_work_sync(&wacom->wireless_work); cancel_work_sync(&wacom->battery_work); cancel_work_sync(&wacom->remote_work); - kfifo_free(&wacom->remote_fifo); - kobject_put(wacom->remote_dir); if (hdev->bus == BUS_BLUETOOTH) device_remove_file(&hdev->dev, &dev_attr_speed); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index d54992967391..ce1089f1142d 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -753,6 +753,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) unsigned char *data = wacom_wac->data; struct input_dev *input = wacom_wac->pad_input; struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); + struct wacom_remote *remote = wacom->remote; struct wacom_features *features = &wacom_wac->features; int bat_charging, bat_percent, touch_ring_mode; __u32 serial; @@ -807,7 +808,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) touch_ring_mode = (data[11] & 0xC0) >> 6; for (i = 0; i < WACOM_MAX_REMOTES; i++) { - if (wacom_wac->serial[i] == serial) + if (remote->serial[i] == serial) wacom->led.groups[i].select = touch_ring_mode; } @@ -827,6 +828,7 @@ static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) { struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); unsigned char *data = wacom_wac->data; + struct wacom_remote *remote = wacom->remote; struct wacom_remote_data remote_data; unsigned long flags; int i, ret; @@ -845,16 +847,16 @@ static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) remote_data.remote[i].connected = connected; } - spin_lock_irqsave(&wacom->remote_lock, flags); + spin_lock_irqsave(&remote->remote_lock, flags); - ret = kfifo_in(&wacom->remote_fifo, &remote_data, sizeof(remote_data)); + ret = kfifo_in(&remote->remote_fifo, &remote_data, sizeof(remote_data)); if (ret != sizeof(remote_data)) { - spin_unlock_irqrestore(&wacom->remote_lock, flags); + spin_unlock_irqrestore(&remote->remote_lock, flags); hid_err(wacom->hdev, "Can't queue Remote status event.\n"); return; } - spin_unlock_irqrestore(&wacom->remote_lock, flags); + spin_unlock_irqrestore(&remote->remote_lock, flags); wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE); } diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index c2c65b8c71c1..6be6cae0d093 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -234,7 +234,7 @@ struct wacom_wac { unsigned char data[WACOM_PKGLEN_MAX]; int tool[2]; int id[2]; - __u32 serial[5]; + __u32 serial[2]; bool reporting_data; struct wacom_features features; struct wacom_shared *shared; -- cgit v1.2.3 From 3888b0d53dfbbc9ebc678a67b2ccebb053ba4aac Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:03 +0200 Subject: HID: wacom: rework fail path in probe() and parse_and_register() Thanks to devres management, we don't need to remember a lot of failure path. One or two is enough. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 64acb71adde4..241aceee631e 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1713,7 +1713,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) error = wacom_allocate_inputs(wacom); if (error) - goto fail_open_group; + goto fail; /* * Bamboo Pad has a generic hid handling for the Pen, and we switch it @@ -1726,7 +1726,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) } else if ((features->pktlen != WACOM_PKGLEN_BPAD_TOUCH) && (features->pktlen != WACOM_PKGLEN_BPAD_TOUCH_USB)) { error = -ENODEV; - goto fail_allocate_inputs; + goto fail; } } @@ -1746,7 +1746,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) error ? "Ignoring" : "Assuming pen"); if (error) - goto fail_parsed; + goto fail; features->device_type |= WACOM_DEVICETYPE_PEN; } @@ -1757,27 +1757,27 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) error = wacom_add_shared_data(hdev); if (error) - goto fail_shared_data; + goto fail; if (!(features->device_type & WACOM_DEVICETYPE_WL_MONITOR) && (features->quirks & WACOM_QUIRK_BATTERY)) { error = wacom_initialize_battery(wacom); if (error) - goto fail_battery; + goto fail; } error = wacom_register_inputs(wacom); if (error) - goto fail_register_inputs; + goto fail; if (wacom->wacom_wac.features.device_type & WACOM_DEVICETYPE_PAD) { error = wacom_initialize_leds(wacom); if (error) - goto fail_leds; + goto fail; error = wacom_initialize_remotes(wacom); if (error) - goto fail_remote; + goto fail; } if (features->type == HID_GENERIC) @@ -1787,7 +1787,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) error = hid_hw_start(hdev, connect_mask); if (error) { hid_err(hdev, "hw start failed\n"); - goto fail_hw_start; + goto fail; } if (!wireless) { @@ -1826,15 +1826,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless) fail_quirks: hid_hw_stop(hdev); -fail_hw_start: -fail_remote: -fail_leds: -fail_register_inputs: -fail_battery: -fail_shared_data: -fail_parsed: -fail_allocate_inputs: -fail_open_group: +fail: wacom_release_resources(wacom); return error; } @@ -2014,7 +2006,7 @@ static int wacom_probe(struct hid_device *hdev, if (features->check_for_hid_type && features->hid_type != hdev->type) { error = -ENODEV; - goto fail_type; + goto fail; } wacom_wac->hid_data.inputmode = -1; @@ -2031,12 +2023,12 @@ static int wacom_probe(struct hid_device *hdev, error = hid_parse(hdev); if (error) { hid_err(hdev, "parse failed\n"); - goto fail_parse; + goto fail; } error = wacom_parse_and_register(wacom, false); if (error) - goto fail_parse; + goto fail; if (hdev->bus == BUS_BLUETOOTH) { error = device_create_file(&hdev->dev, &dev_attr_speed); @@ -2048,8 +2040,7 @@ static int wacom_probe(struct hid_device *hdev, return 0; -fail_parse: -fail_type: +fail: hid_set_drvdata(hdev, NULL); return error; } -- cgit v1.2.3 From 04bfa27b429fc45197fbb1a3dfe01585b931824b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:04 +0200 Subject: HID: wacom: EKR: have proper allocator and destructor The wacom_remote_create_attr_group() and wacom_remote_destroy_attr_group() functions were both allocating/destroying the sysfs groups but also initializing the parameters for the remotes. Have proper functions that can be called and extended. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 93 ++++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 241aceee631e..27ca1473940f 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1300,8 +1300,6 @@ static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, int error = 0; struct wacom_remote *remote = wacom->remote; - remote->serial[index] = serial; - remote->remote_group[index].name = devm_kasprintf(&wacom->hdev->dev, GFP_KERNEL, "%d", serial); @@ -1319,27 +1317,13 @@ static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, return 0; } -static void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial) +static void wacom_remote_destroy_attr_group(struct wacom *wacom, unsigned int i) { struct wacom_remote *remote = wacom->remote; - int i; - if (!serial) - return; - - for (i = 0; i < WACOM_MAX_REMOTES; i++) { - if (remote->serial[i] == serial) { - remote->serial[i] = 0; - wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; - if (remote->remote_group[i].name) { - sysfs_remove_group(remote->remote_dir, - &remote->remote_group[i]); - devm_kfree(&wacom->hdev->dev, - (char *)remote->remote_group[i].name); - remote->remote_group[i].name = NULL; - } - } - } + sysfs_remove_group(remote->remote_dir, &remote->remote_group[i]); + devm_kfree(&wacom->hdev->dev, (char *)remote->remote_group[i].name); + remote->remote_group[i].name = NULL; } static int wacom_cmd_unpair_remote(struct wacom *wacom, unsigned char selector) @@ -1916,6 +1900,50 @@ fail: return; } +static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) +{ + struct wacom_remote *remote = wacom->remote; + u32 serial = remote->serial[index]; + int i; + + wacom_remote_destroy_attr_group(wacom, index); + + for (i = 0; i < WACOM_MAX_REMOTES; i++) { + if (remote->serial[i] == serial) { + remote->serial[i] = 0; + wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; + } + } +} + +static int wacom_remote_create_one(struct wacom *wacom, u32 serial, + unsigned int index) +{ + struct wacom_remote *remote = wacom->remote; + int error, k; + + /* A remote can pair more than once with an EKR, + * check to make sure this serial isn't already paired. + */ + for (k = 0; k < WACOM_MAX_REMOTES; k++) { + if (remote->serial[k] == serial) + break; + } + + if (k < WACOM_MAX_REMOTES) { + remote->serial[index] = serial; + return 0; + } + + error = wacom_remote_create_attr_group(wacom, serial, index); + if (error) + return error; + + remote->serial[index] = serial; + + return 0; +} + static void wacom_remote_work(struct work_struct *work) { struct wacom *wacom = container_of(work, struct wacom, remote_work); @@ -1924,7 +1952,7 @@ static void wacom_remote_work(struct work_struct *work) unsigned long flags; unsigned int count; u32 serial; - int i, k; + int i; spin_lock_irqsave(&remote->remote_lock, flags); @@ -1949,28 +1977,13 @@ static void wacom_remote_work(struct work_struct *work) if (remote->serial[i] == serial) continue; - if (remote->serial[i]) { - wacom_remote_destroy_attr_group(wacom, - remote->serial[i]); - } - - /* A remote can pair more than once with an EKR, - * check to make sure this serial isn't already paired. - */ - for (k = 0; k < WACOM_MAX_REMOTES; k++) { - if (remote->serial[k] == serial) - break; - } + if (remote->serial[i]) + wacom_remote_destroy_one(wacom, i); - if (k < WACOM_MAX_REMOTES) { - remote->serial[i] = serial; - continue; - } - wacom_remote_create_attr_group(wacom, serial, i); + wacom_remote_create_one(wacom, serial, i); } else if (remote->serial[i]) { - wacom_remote_destroy_attr_group(wacom, - remote->serial[i]); + wacom_remote_destroy_one(wacom, i); } } } -- cgit v1.2.3 From f9036bd43602562f549ace170391c932f28446cd Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:05 +0200 Subject: HID: wacom: EKR: use devres groups to manage resources This will be useful when each remote will be assigned its own input device. We won't need to unregister each input and sysfs and other elements one at a time. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 27ca1473940f..9121e72c1b40 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -934,8 +934,9 @@ static void wacom_devm_sysfs_group_release(struct device *dev, void *res) sysfs_remove_group(kobj, devres->group); } -static int wacom_devm_sysfs_create_group(struct wacom *wacom, - struct attribute_group *group) +static int __wacom_devm_sysfs_create_group(struct wacom *wacom, + struct kobject *root, + struct attribute_group *group) { struct wacom_sysfs_group_devres *devres; int error; @@ -947,7 +948,7 @@ static int wacom_devm_sysfs_create_group(struct wacom *wacom, return -ENOMEM; devres->group = group; - devres->root = &wacom->hdev->dev.kobj; + devres->root = root; error = sysfs_create_group(devres->root, group); if (error) @@ -958,6 +959,13 @@ static int wacom_devm_sysfs_create_group(struct wacom *wacom, return 0; } +static int wacom_devm_sysfs_create_group(struct wacom *wacom, + struct attribute_group *group) +{ + return __wacom_devm_sysfs_create_group(wacom, &wacom->hdev->dev.kobj, + group); +} + static void wacom_led_groups_release(void *data) { struct wacom *wacom = data; @@ -1306,9 +1314,10 @@ static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, if (!remote->remote_group[index].name) return -ENOMEM; - error = sysfs_create_group(remote->remote_dir, - &remote->remote_group[index]); + error = __wacom_devm_sysfs_create_group(wacom, remote->remote_dir, + &remote->remote_group[index]); if (error) { + remote->remote_group[index].name = NULL; hid_err(wacom->hdev, "cannot create sysfs group err: %d\n", error); return error; @@ -1317,15 +1326,6 @@ static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, return 0; } -static void wacom_remote_destroy_attr_group(struct wacom *wacom, unsigned int i) -{ - struct wacom_remote *remote = wacom->remote; - - sysfs_remove_group(remote->remote_dir, &remote->remote_group[i]); - devm_kfree(&wacom->hdev->dev, (char *)remote->remote_group[i].name); - remote->remote_group[i].name = NULL; -} - static int wacom_cmd_unpair_remote(struct wacom *wacom, unsigned char selector) { const size_t buf_size = 2; @@ -1906,11 +1906,13 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) u32 serial = remote->serial[index]; int i; - wacom_remote_destroy_attr_group(wacom, index); + if (remote->remote_group[index].name) + devres_release_group(&wacom->hdev->dev, &remote->serial[index]); for (i = 0; i < WACOM_MAX_REMOTES; i++) { if (remote->serial[i] == serial) { remote->serial[i] = 0; + remote->remote_group[i].name = NULL; wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; } } @@ -1920,6 +1922,7 @@ static int wacom_remote_create_one(struct wacom *wacom, u32 serial, unsigned int index) { struct wacom_remote *remote = wacom->remote; + struct device *dev = &wacom->hdev->dev; int error, k; /* A remote can pair more than once with an EKR, @@ -1935,13 +1938,22 @@ static int wacom_remote_create_one(struct wacom *wacom, u32 serial, return 0; } + if (!devres_open_group(dev, &remote->serial[index], GFP_KERNEL)) + return -ENOMEM; + error = wacom_remote_create_attr_group(wacom, serial, index); if (error) - return error; + goto fail; remote->serial[index] = serial; + devres_close_group(dev, &remote->serial[index]); return 0; + +fail: + devres_release_group(dev, &remote->serial[index]); + remote->serial[index] = 0; + return error; } static void wacom_remote_work(struct work_struct *work) -- cgit v1.2.3 From e7749f6e5f9d33da189f7bc7d757349ad4716f00 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:06 +0200 Subject: HID: wacom: EKR: have one array of struct remotes instead of many arrays No functional changes, just a prep patch for the one after. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 6 ++++-- drivers/hid/wacom_sys.c | 53 +++++++++++++++++++++++++------------------------ drivers/hid/wacom_wac.c | 2 +- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 110ea6793b99..6b8df67b3653 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -120,8 +120,10 @@ struct wacom_remote { spinlock_t remote_lock; struct kfifo remote_fifo; struct kobject *remote_dir; - struct attribute_group remote_group[WACOM_MAX_REMOTES]; - __u32 serial[WACOM_MAX_REMOTES]; + struct { + struct attribute_group group; + u32 serial; + } remotes[WACOM_MAX_REMOTES]; }; struct wacom { diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 9121e72c1b40..c3b269237af5 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1308,16 +1308,16 @@ static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, int error = 0; struct wacom_remote *remote = wacom->remote; - remote->remote_group[index].name = devm_kasprintf(&wacom->hdev->dev, + remote->remotes[index].group.name = devm_kasprintf(&wacom->hdev->dev, GFP_KERNEL, "%d", serial); - if (!remote->remote_group[index].name) + if (!remote->remotes[index].group.name) return -ENOMEM; error = __wacom_devm_sysfs_create_group(wacom, remote->remote_dir, - &remote->remote_group[index]); + &remote->remotes[index].group); if (error) { - remote->remote_group[index].name = NULL; + remote->remotes[index].group.name = NULL; hid_err(wacom->hdev, "cannot create sysfs group err: %d\n", error); return error; @@ -1421,11 +1421,11 @@ static int wacom_initialize_remotes(struct wacom *wacom) return -ENOMEM; } - remote->remote_group[0] = remote0_serial_group; - remote->remote_group[1] = remote1_serial_group; - remote->remote_group[2] = remote2_serial_group; - remote->remote_group[3] = remote3_serial_group; - remote->remote_group[4] = remote4_serial_group; + remote->remotes[0].group = remote0_serial_group; + remote->remotes[1].group = remote1_serial_group; + remote->remotes[2].group = remote2_serial_group; + remote->remotes[3].group = remote3_serial_group; + remote->remotes[4].group = remote4_serial_group; remote->remote_dir = kobject_create_and_add("wacom_remote", &wacom->hdev->dev.kobj); @@ -1442,7 +1442,7 @@ static int wacom_initialize_remotes(struct wacom *wacom) for (i = 0; i < WACOM_MAX_REMOTES; i++) { wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; - remote->serial[i] = 0; + remote->remotes[i].serial = 0; } error = devm_add_action_or_reset(&wacom->hdev->dev, @@ -1903,16 +1903,17 @@ fail: static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) { struct wacom_remote *remote = wacom->remote; - u32 serial = remote->serial[index]; + u32 serial = remote->remotes[index].serial; int i; - if (remote->remote_group[index].name) - devres_release_group(&wacom->hdev->dev, &remote->serial[index]); + if (remote->remotes[index].group.name) + devres_release_group(&wacom->hdev->dev, + &remote->remotes[index]); for (i = 0; i < WACOM_MAX_REMOTES; i++) { - if (remote->serial[i] == serial) { - remote->serial[i] = 0; - remote->remote_group[i].name = NULL; + if (remote->remotes[i].serial == serial) { + remote->remotes[i].serial = 0; + remote->remotes[i].group.name = NULL; wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; } } @@ -1929,30 +1930,30 @@ static int wacom_remote_create_one(struct wacom *wacom, u32 serial, * check to make sure this serial isn't already paired. */ for (k = 0; k < WACOM_MAX_REMOTES; k++) { - if (remote->serial[k] == serial) + if (remote->remotes[k].serial == serial) break; } if (k < WACOM_MAX_REMOTES) { - remote->serial[index] = serial; + remote->remotes[index].serial = serial; return 0; } - if (!devres_open_group(dev, &remote->serial[index], GFP_KERNEL)) + if (!devres_open_group(dev, &remote->remotes[index], GFP_KERNEL)) return -ENOMEM; error = wacom_remote_create_attr_group(wacom, serial, index); if (error) goto fail; - remote->serial[index] = serial; + remote->remotes[index].serial = serial; - devres_close_group(dev, &remote->serial[index]); + devres_close_group(dev, &remote->remotes[index]); return 0; fail: - devres_release_group(dev, &remote->serial[index]); - remote->serial[index] = 0; + devres_release_group(dev, &remote->remotes[index]); + remote->remotes[index].serial = 0; return error; } @@ -1986,15 +1987,15 @@ static void wacom_remote_work(struct work_struct *work) serial = data.remote[i].serial; if (data.remote[i].connected) { - if (remote->serial[i] == serial) + if (remote->remotes[i].serial == serial) continue; - if (remote->serial[i]) + if (remote->remotes[i].serial) wacom_remote_destroy_one(wacom, i); wacom_remote_create_one(wacom, serial, i); - } else if (remote->serial[i]) { + } else if (remote->remotes[i].serial) { wacom_remote_destroy_one(wacom, i); } } diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index ce1089f1142d..aee07613040d 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -808,7 +808,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) touch_ring_mode = (data[11] & 0xC0) >> 6; for (i = 0; i < WACOM_MAX_REMOTES; i++) { - if (remote->serial[i] == serial) + if (remote->remotes[i].serial == serial) wacom->led.groups[i].select = touch_ring_mode; } -- cgit v1.2.3 From 7c35dc3cd4d114019ed4b26cab313e253396d7c9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:07 +0200 Subject: HID: wacom: EKR: allocate one input node per remote Thanks to devres, we can now afford to create more than one input node without having to overload the remove/failure paths. Having one input node per remote is something which should have been implemented from start but the probability of having users with several remotes is quite low. Anyway, still, better looking at the future and implement things properly. Remote input nodes will be freed/unregistered magically as they are created in the devres group &remote->remotes[index]. We need to open the hid node now that the remotes are dynamically allocated. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 2 ++ drivers/hid/wacom_sys.c | 30 ++++++++++++++++++++++++++++++ drivers/hid/wacom_wac.c | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 6b8df67b3653..393b5af871d9 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -123,6 +123,8 @@ struct wacom_remote { struct { struct attribute_group group; u32 serial; + struct input_dev *input; + bool registered; } remotes[WACOM_MAX_REMOTES]; }; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index c3b269237af5..f2f5b4b248cb 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1905,6 +1905,11 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) struct wacom_remote *remote = wacom->remote; u32 serial = remote->remotes[index].serial; int i; + unsigned long flags; + + spin_lock_irqsave(&remote->remote_lock, flags); + remote->remotes[index].registered = false; + spin_unlock_irqrestore(&remote->remote_lock, flags); if (remote->remotes[index].group.name) devres_release_group(&wacom->hdev->dev, @@ -1914,6 +1919,7 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) if (remote->remotes[i].serial == serial) { remote->remotes[i].serial = 0; remote->remotes[i].group.name = NULL; + remote->remotes[i].registered = false; wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; } } @@ -1946,8 +1952,32 @@ static int wacom_remote_create_one(struct wacom *wacom, u32 serial, if (error) goto fail; + remote->remotes[index].input = wacom_allocate_input(wacom); + if (!remote->remotes[index].input) { + error = -ENOMEM; + goto fail; + } + remote->remotes[index].input->uniq = remote->remotes[index].group.name; + remote->remotes[index].input->name = wacom->wacom_wac.pad_name; + + if (!remote->remotes[index].input->name) { + error = -EINVAL; + goto fail; + } + + error = wacom_setup_pad_input_capabilities(remote->remotes[index].input, + &wacom->wacom_wac); + if (error) + goto fail; + remote->remotes[index].serial = serial; + error = input_register_device(remote->remotes[index].input); + if (error) + goto fail; + + remote->remotes[index].registered = true; + devres_close_group(dev, &remote->remotes[index]); return 0; diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index aee07613040d..99d688a3ee71 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -751,23 +751,38 @@ static int wacom_intuos_inout(struct wacom_wac *wacom) static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) { unsigned char *data = wacom_wac->data; - struct input_dev *input = wacom_wac->pad_input; + struct input_dev *input; struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); struct wacom_remote *remote = wacom->remote; struct wacom_features *features = &wacom_wac->features; int bat_charging, bat_percent, touch_ring_mode; __u32 serial; - int i; + int i, index = -1; + unsigned long flags; if (data[0] != WACOM_REPORT_REMOTE) { - dev_dbg(input->dev.parent, - "%s: received unknown report #%d", __func__, data[0]); + hid_dbg(wacom->hdev, "%s: received unknown report #%d", + __func__, data[0]); return 0; } serial = data[3] + (data[4] << 8) + (data[5] << 16); wacom_wac->id[0] = PAD_DEVICE_ID; + spin_lock_irqsave(&remote->remote_lock, flags); + + for (i = 0; i < WACOM_MAX_REMOTES; i++) { + if (remote->remotes[i].serial == serial) { + index = i; + break; + } + } + + if (index < 0 || !remote->remotes[index].registered) + goto out; + + input = remote->remotes[index].input; + input_report_key(input, BTN_0, (data[9] & 0x01)); input_report_key(input, BTN_1, (data[9] & 0x02)); input_report_key(input, BTN_2, (data[9] & 0x04)); @@ -804,6 +819,8 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) input_event(input, EV_MSC, MSC_SERIAL, serial); + input_sync(input); + /*Which mode select (LED light) is currently on?*/ touch_ring_mode = (data[11] & 0xC0) >> 6; @@ -821,7 +838,9 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) wacom_notify_battery(wacom_wac, bat_percent, bat_charging, 1, bat_charging); - return 1; +out: + spin_unlock_irqrestore(&remote->remote_lock, flags); + return 0; } static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) @@ -2458,6 +2477,9 @@ void wacom_setup_device_quirks(struct wacom *wacom) features->quirks |= WACOM_QUIRK_BATTERY; } } + + if (features->type == REMOTE) + features->device_type |= WACOM_DEVICETYPE_WL_MONITOR; } int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, @@ -2762,6 +2784,9 @@ int wacom_setup_pad_input_capabilities(struct input_dev *input_dev, if (!(features->device_type & WACOM_DEVICETYPE_PAD)) return -ENODEV; + if (features->type == REMOTE && input_dev == wacom_wac->pad_input) + return -ENODEV; + input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); /* kept for making legacy xf86-input-wacom working with the wheels */ -- cgit v1.2.3 From 59d69bc8215478af6b89d4ae9ab0baf6d91eb870 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:08 +0200 Subject: HID: wacom: EKR: have one power_supply per remote Previously, all the remotes attached to the same receiver would share the same power_supply. That's not good as the remotes will constantly change the battery information according to their own state. To have something generic enough, we introduce struct wacom_battery which regroups all the information we need for a battery. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 19 ++++++-- drivers/hid/wacom_sys.c | 123 ++++++++++++++++++++++++++---------------------- drivers/hid/wacom_wac.c | 54 ++++++++++----------- drivers/hid/wacom_wac.h | 6 --- 4 files changed, 109 insertions(+), 93 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 393b5af871d9..768d69602e7a 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -116,6 +116,19 @@ struct wacom_group_leds { u8 select; /* status led selector (0..3) */ }; +struct wacom_battery { + struct power_supply_desc bat_desc; + struct power_supply_desc ac_desc; + struct power_supply *battery; + struct power_supply *ac; + char bat_name[WACOM_NAME_MAX]; + char ac_name[WACOM_NAME_MAX]; + int battery_capacity; + int bat_charging; + int bat_connected; + int ps_connected; +}; + struct wacom_remote { spinlock_t remote_lock; struct kfifo remote_fifo; @@ -125,6 +138,7 @@ struct wacom_remote { u32 serial; struct input_dev *input; bool registered; + struct wacom_battery battery; } remotes[WACOM_MAX_REMOTES]; }; @@ -144,10 +158,7 @@ struct wacom { u8 hlv; /* status led brightness button pressed (1..127) */ u8 img_lum; /* OLED matrix display brightness */ } led; - struct power_supply *battery; - struct power_supply *ac; - struct power_supply_desc battery_desc; - struct power_supply_desc ac_desc; + struct wacom_battery battery; bool resources; }; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index f2f5b4b248cb..04f5c753c868 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1101,27 +1101,26 @@ static int wacom_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct wacom *wacom = power_supply_get_drvdata(psy); + struct wacom_battery *battery = power_supply_get_drvdata(psy); int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: - val->intval = wacom->wacom_wac.bat_connected; + val->intval = battery->bat_connected; break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; break; case POWER_SUPPLY_PROP_CAPACITY: - val->intval = - wacom->wacom_wac.battery_capacity; + val->intval = battery->battery_capacity; break; case POWER_SUPPLY_PROP_STATUS: - if (wacom->wacom_wac.bat_charging) + if (battery->bat_charging) val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (wacom->wacom_wac.battery_capacity == 100 && - wacom->wacom_wac.ps_connected) + else if (battery->battery_capacity == 100 && + battery->ps_connected) val->intval = POWER_SUPPLY_STATUS_FULL; - else if (wacom->wacom_wac.ps_connected) + else if (battery->ps_connected) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; else val->intval = POWER_SUPPLY_STATUS_DISCHARGING; @@ -1138,14 +1137,14 @@ static int wacom_ac_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - struct wacom *wacom = power_supply_get_drvdata(psy); + struct wacom_battery *battery = power_supply_get_drvdata(psy); int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: /* fall through */ case POWER_SUPPLY_PROP_ONLINE: - val->intval = wacom->wacom_wac.ps_connected; + val->intval = battery->ps_connected; break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; @@ -1157,58 +1156,56 @@ static int wacom_ac_get_property(struct power_supply *psy, return ret; } -static int wacom_initialize_battery(struct wacom *wacom) +static int __wacom_initialize_battery(struct wacom *wacom, + struct wacom_battery *battery) { static atomic_t battery_no = ATOMIC_INIT(0); struct device *dev = &wacom->hdev->dev; - struct power_supply_config psy_cfg = { .drv_data = wacom, }; - struct power_supply_desc *bat_desc = &wacom->battery_desc; + struct power_supply_config psy_cfg = { .drv_data = battery, }; + struct power_supply *ps_bat, *ps_ac; + struct power_supply_desc *bat_desc = &battery->bat_desc; + struct power_supply_desc *ac_desc = &battery->ac_desc; unsigned long n; int error; if (!devres_open_group(dev, bat_desc, GFP_KERNEL)) return -ENOMEM; - if (wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) { - struct power_supply_desc *ac_desc = &wacom->ac_desc; - n = atomic_inc_return(&battery_no) - 1; - - bat_desc->properties = wacom_battery_props; - bat_desc->num_properties = ARRAY_SIZE(wacom_battery_props); - bat_desc->get_property = wacom_battery_get_property; - sprintf(wacom->wacom_wac.bat_name, "wacom_battery_%ld", n); - bat_desc->name = wacom->wacom_wac.bat_name; - bat_desc->type = POWER_SUPPLY_TYPE_BATTERY; - bat_desc->use_for_apm = 0; - - ac_desc->properties = wacom_ac_props; - ac_desc->num_properties = ARRAY_SIZE(wacom_ac_props); - ac_desc->get_property = wacom_ac_get_property; - sprintf(wacom->wacom_wac.ac_name, "wacom_ac_%ld", n); - ac_desc->name = wacom->wacom_wac.ac_name; - ac_desc->type = POWER_SUPPLY_TYPE_MAINS; - ac_desc->use_for_apm = 0; - - wacom->battery = devm_power_supply_register(dev, - &wacom->battery_desc, - &psy_cfg); - if (IS_ERR(wacom->battery)) { - error = PTR_ERR(wacom->battery); - goto err; - } + n = atomic_inc_return(&battery_no) - 1; + + bat_desc->properties = wacom_battery_props; + bat_desc->num_properties = ARRAY_SIZE(wacom_battery_props); + bat_desc->get_property = wacom_battery_get_property; + sprintf(battery->bat_name, "wacom_battery_%ld", n); + bat_desc->name = battery->bat_name; + bat_desc->type = POWER_SUPPLY_TYPE_BATTERY; + bat_desc->use_for_apm = 0; + + ac_desc->properties = wacom_ac_props; + ac_desc->num_properties = ARRAY_SIZE(wacom_ac_props); + ac_desc->get_property = wacom_ac_get_property; + sprintf(battery->ac_name, "wacom_ac_%ld", n); + ac_desc->name = battery->ac_name; + ac_desc->type = POWER_SUPPLY_TYPE_MAINS; + ac_desc->use_for_apm = 0; + + ps_bat = devm_power_supply_register(dev, bat_desc, &psy_cfg); + if (IS_ERR(ps_bat)) { + error = PTR_ERR(ps_bat); + goto err; + } - power_supply_powers(wacom->battery, &wacom->hdev->dev); + ps_ac = devm_power_supply_register(dev, ac_desc, &psy_cfg); + if (IS_ERR(ps_ac)) { + error = PTR_ERR(ps_ac); + goto err; + } - wacom->ac = devm_power_supply_register(dev, - &wacom->ac_desc, - &psy_cfg); - if (IS_ERR(wacom->ac)) { - error = PTR_ERR(wacom->ac); - goto err; - } + power_supply_powers(ps_bat, &wacom->hdev->dev); + power_supply_powers(ps_ac, &wacom->hdev->dev); - power_supply_powers(wacom->ac, &wacom->hdev->dev); - } + battery->battery = ps_bat; + battery->ac = ps_ac; devres_close_group(dev, bat_desc); return 0; @@ -1218,12 +1215,21 @@ err: return error; } +static int wacom_initialize_battery(struct wacom *wacom) +{ + if (wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) + return __wacom_initialize_battery(wacom, &wacom->battery); + + return 0; +} + static void wacom_destroy_battery(struct wacom *wacom) { - if (wacom->battery) { - devres_release_group(&wacom->hdev->dev, &wacom->battery_desc); - wacom->battery = NULL; - wacom->ac = NULL; + if (wacom->battery.battery) { + devres_release_group(&wacom->hdev->dev, + &wacom->battery.bat_desc); + wacom->battery.battery = NULL; + wacom->battery.ac = NULL; } } @@ -1593,11 +1599,11 @@ void wacom_battery_work(struct work_struct *work) struct wacom *wacom = container_of(work, struct wacom, battery_work); if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) && - !wacom->battery) { + !wacom->battery.battery) { wacom_initialize_battery(wacom); } else if (!(wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) && - wacom->battery) { + wacom->battery.battery) { wacom_destroy_battery(wacom); } } @@ -1976,6 +1982,11 @@ static int wacom_remote_create_one(struct wacom *wacom, u32 serial, if (error) goto fail; + error = __wacom_initialize_battery(wacom, + &remote->remotes[index].battery); + if (error) + goto fail; + remote->remotes[index].registered = true; devres_close_group(dev, &remote->remotes[index]); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 99d688a3ee71..1c882bb70f14 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -48,25 +48,34 @@ static unsigned short batcap_gr[8] = { 1, 15, 25, 35, 50, 70, 100, 100 }; */ static unsigned short batcap_i4[8] = { 1, 15, 30, 45, 60, 70, 85, 100 }; +static void __wacom_notify_battery(struct wacom_battery *battery, + int bat_capacity, bool bat_charging, + bool bat_connected, bool ps_connected) +{ + bool changed = battery->battery_capacity != bat_capacity || + battery->bat_charging != bat_charging || + battery->bat_connected != bat_connected || + battery->ps_connected != ps_connected; + + if (changed) { + battery->battery_capacity = bat_capacity; + battery->bat_charging = bat_charging; + battery->bat_connected = bat_connected; + battery->ps_connected = ps_connected; + + if (battery->battery) + power_supply_changed(battery->battery); + } +} + static void wacom_notify_battery(struct wacom_wac *wacom_wac, int bat_capacity, bool bat_charging, bool bat_connected, bool ps_connected) { struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); - bool changed = wacom_wac->battery_capacity != bat_capacity || - wacom_wac->bat_charging != bat_charging || - wacom_wac->bat_connected != bat_connected || - wacom_wac->ps_connected != ps_connected; - if (changed) { - wacom_wac->battery_capacity = bat_capacity; - wacom_wac->bat_charging = bat_charging; - wacom_wac->bat_connected = bat_connected; - wacom_wac->ps_connected = ps_connected; - - if (wacom->battery) - power_supply_changed(wacom->battery); - } + __wacom_notify_battery(&wacom->battery, bat_capacity, bat_charging, + bat_connected, ps_connected); } static int wacom_penpartner_irq(struct wacom_wac *wacom) @@ -754,7 +763,6 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) struct input_dev *input; struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); struct wacom_remote *remote = wacom->remote; - struct wacom_features *features = &wacom_wac->features; int bat_charging, bat_percent, touch_ring_mode; __u32 serial; int i, index = -1; @@ -829,14 +837,8 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) wacom->led.groups[i].select = touch_ring_mode; } - if (!wacom->battery && - !(features->quirks & WACOM_QUIRK_BATTERY)) { - features->quirks |= WACOM_QUIRK_BATTERY; - wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY); - } - - wacom_notify_battery(wacom_wac, bat_percent, bat_charging, 1, - bat_charging); + __wacom_notify_battery(&remote->remotes[index].battery, bat_percent, + bat_charging, 1, bat_charging); out: spin_unlock_irqrestore(&remote->remote_lock, flags); @@ -2133,7 +2135,6 @@ static int wacom_bamboo_pad_irq(struct wacom_wac *wacom, size_t len) static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len) { - struct wacom *w = container_of(wacom, struct wacom, wacom_wac); unsigned char *data = wacom->data; int connected; @@ -2161,8 +2162,7 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len) wacom_schedule_work(wacom, WACOM_WORKER_WIRELESS); } - if (w->battery) - wacom_notify_battery(wacom, battery, charging, 1, 0); + wacom_notify_battery(wacom, battery, charging, 1, 0); } else if (wacom->pid != 0) { /* disconnected while previously connected */ @@ -2199,14 +2199,14 @@ static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len) wacom_notify_battery(wacom_wac, battery, charging, battery || charging, 1); - if (!wacom->battery && + if (!wacom->battery.battery && !(features->quirks & WACOM_QUIRK_BATTERY)) { features->quirks |= WACOM_QUIRK_BATTERY; wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY); } } else if ((features->quirks & WACOM_QUIRK_BATTERY) && - wacom->battery) { + wacom->battery.battery) { features->quirks &= ~WACOM_QUIRK_BATTERY; wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY); wacom_notify_battery(wacom_wac, 0, 0, 0, 0); diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 6be6cae0d093..8a8974c901cb 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -229,8 +229,6 @@ struct wacom_wac { char pen_name[WACOM_NAME_MAX]; char touch_name[WACOM_NAME_MAX]; char pad_name[WACOM_NAME_MAX]; - char bat_name[WACOM_NAME_MAX]; - char ac_name[WACOM_NAME_MAX]; unsigned char data[WACOM_PKGLEN_MAX]; int tool[2]; int id[2]; @@ -242,11 +240,7 @@ struct wacom_wac { struct input_dev *touch_input; struct input_dev *pad_input; int pid; - int battery_capacity; int num_contacts_left; - int bat_charging; - int bat_connected; - int ps_connected; u8 bt_features; u8 bt_high_speed; int mode_report; -- cgit v1.2.3 From 9f1015d45f62d3b1c6719a1ccffaded89b157e10 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:09 +0200 Subject: HID: wacom: EKR: attach the power_supply on first connection Or Gnome complains about an empty battery. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 04f5c753c868..1d79215a7968 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1917,6 +1917,10 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) remote->remotes[index].registered = false; spin_unlock_irqrestore(&remote->remote_lock, flags); + if (remote->remotes[index].battery.battery) + devres_release_group(&wacom->hdev->dev, + &remote->remotes[index].battery.bat_desc); + if (remote->remotes[index].group.name) devres_release_group(&wacom->hdev->dev, &remote->remotes[index]); @@ -1926,6 +1930,7 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) remote->remotes[i].serial = 0; remote->remotes[i].group.name = NULL; remote->remotes[i].registered = false; + remote->remotes[i].battery.battery = NULL; wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; } } @@ -1982,11 +1987,6 @@ static int wacom_remote_create_one(struct wacom *wacom, u32 serial, if (error) goto fail; - error = __wacom_initialize_battery(wacom, - &remote->remotes[index].battery); - if (error) - goto fail; - remote->remotes[index].registered = true; devres_close_group(dev, &remote->remotes[index]); @@ -1998,6 +1998,28 @@ fail: return error; } +static int wacom_remote_attach_battery(struct wacom *wacom, int index) +{ + struct wacom_remote *remote = wacom->remote; + int error; + + if (!remote->remotes[index].registered) + return 0; + + if (remote->remotes[index].battery.battery) + return 0; + + if (wacom->led.groups[index].select == WACOM_STATUS_UNKNOWN) + return 0; + + error = __wacom_initialize_battery(wacom, + &wacom->remote->remotes[index].battery); + if (error) + return error; + + return 0; +} + static void wacom_remote_work(struct work_struct *work) { struct wacom *wacom = container_of(work, struct wacom, remote_work); @@ -2028,8 +2050,10 @@ static void wacom_remote_work(struct work_struct *work) serial = data.remote[i].serial; if (data.remote[i].connected) { - if (remote->remotes[i].serial == serial) + if (remote->remotes[i].serial == serial) { + wacom_remote_attach_battery(wacom, i); continue; + } if (remote->remotes[i].serial) wacom_remote_destroy_one(wacom, i); -- cgit v1.2.3 From 97f5541fc0c7ed107103e6f87a6522f5327ab4b0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:10 +0200 Subject: HID: wacom: leds: use the ledclass instead of custom made sysfs files The now obsolete sysfs files for LEDs and EKRemote are kept for backward compatibility. Both the EKR (read-only) and the regular Cintiqs and Intuos are now sharing the same led API. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- Documentation/ABI/testing/sysfs-driver-wacom | 5 + drivers/hid/wacom.h | 19 +++ drivers/hid/wacom_sys.c | 195 +++++++++++++++++++++++++-- 3 files changed, 209 insertions(+), 10 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-driver-wacom b/Documentation/ABI/testing/sysfs-driver-wacom index dca429340772..2aa5503ee200 100644 --- a/Documentation/ABI/testing/sysfs-driver-wacom +++ b/Documentation/ABI/testing/sysfs-driver-wacom @@ -24,6 +24,7 @@ What: /sys/bus/hid/devices/::./wacom_led/status0_luminance Date: August 2014 Contact: linux-input@vger.kernel.org Description: + Writing to this file sets the status LED luminance (1..127) when the stylus does not touch the tablet surface, and no button is pressed on the stylus. This luminance level is @@ -33,6 +34,7 @@ What: /sys/bus/hid/devices/::./wacom_led/status1_luminance Date: August 2014 Contact: linux-input@vger.kernel.org Description: + Writing to this file sets the status LED luminance (1..127) when the stylus touches the tablet surface, or any button is pressed on the stylus. @@ -41,6 +43,7 @@ What: /sys/bus/hid/devices/::./wacom_led/status_led0_select Date: August 2014 Contact: linux-input@vger.kernel.org Description: + Writing to this file sets which one of the four (for Intuos 4 and Intuos 5) or of the right four (for Cintiq 21UX2 and Cintiq 24HD) status LEDs is active (0..3). The other three LEDs on the @@ -50,6 +53,7 @@ What: /sys/bus/hid/devices/::./wacom_led/status_led1_select Date: August 2014 Contact: linux-input@vger.kernel.org Description: + Writing to this file sets which one of the left four (for Cintiq 21UX2 and Cintiq 24HD) status LEDs is active (0..3). The other three LEDs on the left are always inactive. @@ -91,6 +95,7 @@ What: /sys/bus/hid/devices/::./wacom_remote//r Date: July 2015 Contact: linux-input@vger.kernel.org Description: + Reading from this file reports the mode status of the remote as indicated by the LED lights on the device. If no reports have been received from the paired device, reading diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 768d69602e7a..0c498c46de5f 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -91,6 +91,7 @@ #include #include #include +#include #include #include #include @@ -112,8 +113,23 @@ enum wacom_worker { WACOM_WORKER_REMOTE, }; +struct wacom; + +struct wacom_led { + struct led_classdev cdev; + struct led_trigger trigger; + struct wacom *wacom; + unsigned int group; + unsigned int id; + u8 llv; + u8 hlv; + bool held; +}; + struct wacom_group_leds { u8 select; /* status led selector (0..3) */ + struct wacom_led *leds; + unsigned int count; }; struct wacom_battery { @@ -154,9 +170,12 @@ struct wacom { struct wacom_remote *remote; struct wacom_leds { struct wacom_group_leds *groups; + unsigned int count; u8 llv; /* status led brightness no button (1..127) */ u8 hlv; /* status led brightness button pressed (1..127) */ u8 img_lum; /* OLED matrix display brightness */ + u8 max_llv; /* maximum brightness of LED (llv) */ + u8 max_hlv; /* maximum brightness of LED (hlv) */ } led; struct wacom_battery battery; bool resources; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 1d79215a7968..c5d518da0d0f 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -647,6 +647,9 @@ static int wacom_led_control(struct wacom *wacom) unsigned char report_id = WAC_CMD_LED_CONTROL; int buf_size = 9; + if (!hid_get_drvdata(wacom->hdev)) + return -ENODEV; + if (!wacom->led.groups) return -ENOTSUPP; @@ -966,31 +969,194 @@ static int wacom_devm_sysfs_create_group(struct wacom *wacom, group); } +static enum led_brightness wacom_leds_brightness_get(struct wacom_led *led) +{ + struct wacom *wacom = led->wacom; + + if (wacom->led.max_hlv) + return led->hlv * LED_FULL / wacom->led.max_hlv; + + if (wacom->led.max_llv) + return led->llv * LED_FULL / wacom->led.max_llv; + + /* device doesn't support brightness tuning */ + return LED_FULL; +} + +static enum led_brightness __wacom_led_brightness_get(struct led_classdev *cdev) +{ + struct wacom_led *led = container_of(cdev, struct wacom_led, cdev); + struct wacom *wacom = led->wacom; + + if (wacom->led.groups[led->group].select != led->id) + return LED_OFF; + + return wacom_leds_brightness_get(led); +} + +static int wacom_led_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct wacom_led *led = container_of(cdev, struct wacom_led, cdev); + struct wacom *wacom = led->wacom; + int error; + + mutex_lock(&wacom->lock); + + if (!wacom->led.groups || (brightness == LED_OFF && + wacom->led.groups[led->group].select != led->id)) { + error = 0; + goto out; + } + + led->llv = wacom->led.llv = wacom->led.max_llv * brightness / LED_FULL; + led->hlv = wacom->led.hlv = wacom->led.max_hlv * brightness / LED_FULL; + + wacom->led.groups[led->group].select = led->id; + + error = wacom_led_control(wacom); + +out: + mutex_unlock(&wacom->lock); + + return error; +} + +static void wacom_led_readonly_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ +} + +static int wacom_led_register_one(struct device *dev, struct wacom *wacom, + struct wacom_led *led, unsigned int group, + unsigned int id, bool read_only) +{ + int error; + char *name; + + name = devm_kasprintf(dev, GFP_KERNEL, + "%s::wacom-%d.%d", + dev_name(dev), + group, + id); + if (!name) + return -ENOMEM; + + led->group = group; + led->id = id; + led->wacom = wacom; + led->llv = wacom->led.llv; + led->hlv = wacom->led.hlv; + led->cdev.name = name; + led->cdev.max_brightness = LED_FULL; + led->cdev.flags = LED_HW_PLUGGABLE; + led->cdev.brightness_get = __wacom_led_brightness_get; + if (!read_only) + led->cdev.brightness_set_blocking = wacom_led_brightness_set; + else + led->cdev.brightness_set = wacom_led_readonly_brightness_set; + + error = devm_led_classdev_register(dev, &led->cdev); + if (error) { + hid_err(wacom->hdev, + "failed to register LED %s: %d\n", + led->cdev.name, error); + led->cdev.name = NULL; + return error; + } + + return 0; +} + +static int wacom_led_groups_alloc_and_register_one(struct device *dev, + struct wacom *wacom, + int group_id, int count, + bool read_only) +{ + struct wacom_led *leds; + int i, error; + + if (group_id >= wacom->led.count || count <= 0) + return -EINVAL; + + if (!devres_open_group(dev, &wacom->led.groups[group_id], GFP_KERNEL)) + return -ENOMEM; + + leds = devm_kzalloc(dev, sizeof(struct wacom_led) * count, GFP_KERNEL); + if (!leds) { + error = -ENOMEM; + goto err; + } + + wacom->led.groups[group_id].leds = leds; + wacom->led.groups[group_id].count = count; + + for (i = 0; i < count; i++) { + error = wacom_led_register_one(dev, wacom, &leds[i], + group_id, i, read_only); + if (error) + goto err; + } + + devres_remove_group(dev, &wacom->led.groups[group_id]); + return 0; + +err: + devres_release_group(dev, &wacom->led.groups[group_id]); + return error; +} + static void wacom_led_groups_release(void *data) { struct wacom *wacom = data; wacom->led.groups = NULL; + wacom->led.count = 0; } static int wacom_led_groups_allocate(struct wacom *wacom, int count) { + struct device *dev = &wacom->hdev->dev; struct wacom_group_leds *groups; int error; - groups = devm_kzalloc(&wacom->hdev->dev, - sizeof(struct wacom_group_leds) * count, + groups = devm_kzalloc(dev, sizeof(struct wacom_group_leds) * count, GFP_KERNEL); if (!groups) return -ENOMEM; - error = devm_add_action_or_reset(&wacom->hdev->dev, - wacom_led_groups_release, - wacom); + error = devm_add_action_or_reset(dev, wacom_led_groups_release, wacom); if (error) return error; wacom->led.groups = groups; + wacom->led.count = count; + + return 0; +} + +static int wacom_leds_alloc_and_register(struct wacom *wacom, int group_count, + int led_per_group, bool read_only) +{ + struct device *dev; + int i, error; + + if (!wacom->wacom_wac.pad_input) + return -EINVAL; + + dev = &wacom->wacom_wac.pad_input->dev; + + error = wacom_led_groups_allocate(wacom, group_count); + if (error) + return error; + + for (i = 0; i < group_count; i++) { + error = wacom_led_groups_alloc_and_register_one(dev, wacom, i, + led_per_group, + read_only); + if (error) + return error; + } return 0; } @@ -1010,9 +1176,11 @@ static int wacom_initialize_leds(struct wacom *wacom) case INTUOS4L: wacom->led.llv = 10; wacom->led.hlv = 20; + wacom->led.max_llv = 127; + wacom->led.max_hlv = 127; wacom->led.img_lum = 10; - error = wacom_led_groups_allocate(wacom, 1); + error = wacom_leds_alloc_and_register(wacom, 1, 4, false); if (error) { hid_err(wacom->hdev, "cannot create leds err: %d\n", error); @@ -1029,7 +1197,7 @@ static int wacom_initialize_leds(struct wacom *wacom) wacom->led.hlv = 0; wacom->led.img_lum = 0; - error = wacom_led_groups_allocate(wacom, 2); + error = wacom_leds_alloc_and_register(wacom, 2, 4, false); if (error) { hid_err(wacom->hdev, "cannot create leds err: %d\n", error); @@ -1047,10 +1215,9 @@ static int wacom_initialize_leds(struct wacom *wacom) case INTUOSPM: case INTUOSPL: wacom->led.llv = 32; - wacom->led.hlv = 0; - wacom->led.img_lum = 0; + wacom->led.max_llv = 96; - error = wacom_led_groups_allocate(wacom, 1); + error = wacom_leds_alloc_and_register(wacom, 1, 4, false); if (error) { hid_err(wacom->hdev, "cannot create leds err: %d\n", error); @@ -1062,6 +1229,8 @@ static int wacom_initialize_leds(struct wacom *wacom) break; case REMOTE: + wacom->led.llv = 255; + wacom->led.max_llv = 255; error = wacom_led_groups_allocate(wacom, 5); if (error) { hid_err(wacom->hdev, @@ -1987,6 +2156,12 @@ static int wacom_remote_create_one(struct wacom *wacom, u32 serial, if (error) goto fail; + error = wacom_led_groups_alloc_and_register_one( + &remote->remotes[index].input->dev, + wacom, index, 3, true); + if (error) + goto fail; + remote->remotes[index].registered = true; devres_close_group(dev, &remote->remotes[index]); -- cgit v1.2.3 From 589e5060206b97a9602534d19d53264d35cd969f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:11 +0200 Subject: HID: wacom: leds: actually release the LEDs on disconnect There is a bug (?) in devm_led_classdev_register() in which its increments the refcount of the parent. If the parent is an input device, that means the ref count never reaches 0 when devm_input_device_release() gets called. This means that the LEDs and all the devres resources attached to the input device are not released. Manually force the release of the group so that the leds are released once we are done using them. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 1 + drivers/hid/wacom_sys.c | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 0c498c46de5f..9159dd3e4b90 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -130,6 +130,7 @@ struct wacom_group_leds { u8 select; /* status led selector (0..3) */ struct wacom_led *leds; unsigned int count; + struct device *dev; }; struct wacom_battery { diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index c5d518da0d0f..3a651e288776 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1068,6 +1068,13 @@ static int wacom_led_register_one(struct device *dev, struct wacom *wacom, return 0; } +static void wacom_led_groups_release_one(void *data) +{ + struct wacom_group_leds *group = data; + + devres_release_group(group->dev, group); +} + static int wacom_led_groups_alloc_and_register_one(struct device *dev, struct wacom *wacom, int group_id, int count, @@ -1098,7 +1105,25 @@ static int wacom_led_groups_alloc_and_register_one(struct device *dev, goto err; } - devres_remove_group(dev, &wacom->led.groups[group_id]); + wacom->led.groups[group_id].dev = dev; + + devres_close_group(dev, &wacom->led.groups[group_id]); + + /* + * There is a bug (?) in devm_led_classdev_register() in which its + * increments the refcount of the parent. If the parent is an input + * device, that means the ref count never reaches 0 when + * devm_input_device_release() gets called. + * This means that the LEDs are still there after disconnect. + * Manually force the release of the group so that the leds are released + * once we are done using them. + */ + error = devm_add_action_or_reset(&wacom->hdev->dev, + wacom_led_groups_release_one, + &wacom->led.groups[group_id]); + if (error) + return error; + return 0; err: -- cgit v1.2.3 From 34736aa96e313d304ffe0746519833167e66c978 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:12 +0200 Subject: HID: wacom: leds: handle the switch of the LEDs directly in the kernel The EKR switches the LED directly, and there is no point in having userspace handling the switch it self when it's easy enough to do in the kernel. The other benefit is that now userspace does not need to have root access to the LED but need only to read them with user privileges. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + drivers/hid/wacom.h | 4 ++++ drivers/hid/wacom_sys.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++--- drivers/hid/wacom_wac.c | 53 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 78ac4811bd3c..bdacb552f0a3 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -862,6 +862,7 @@ config HID_WACOM select POWER_SUPPLY select NEW_LEDS select LEDS_CLASS + select LEDS_TRIGGERS help Say Y here if you want to use the USB or BT version of the Wacom Intuos or Graphire tablet. diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 9159dd3e4b90..8f8a16243f07 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -216,4 +216,8 @@ int wacom_wac_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value); void wacom_wac_report(struct hid_device *hdev, struct hid_report *report); void wacom_battery_work(struct work_struct *work); +enum led_brightness wacom_leds_brightness_get(struct wacom_led *led); +struct wacom_led *wacom_led_find(struct wacom *wacom, unsigned int group, + unsigned int id); +struct wacom_led *wacom_led_next(struct wacom *wacom, struct wacom_led *cur); #endif diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 3a651e288776..7e83352adbde 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -969,7 +969,7 @@ static int wacom_devm_sysfs_create_group(struct wacom *wacom, group); } -static enum led_brightness wacom_leds_brightness_get(struct wacom_led *led) +enum led_brightness wacom_leds_brightness_get(struct wacom_led *led) { struct wacom *wacom = led->wacom; @@ -1042,6 +1042,17 @@ static int wacom_led_register_one(struct device *dev, struct wacom *wacom, if (!name) return -ENOMEM; + if (!read_only) { + led->trigger.name = name; + error = devm_led_trigger_register(dev, &led->trigger); + if (error) { + hid_err(wacom->hdev, + "failed to register LED trigger %s: %d\n", + led->cdev.name, error); + return error; + } + } + led->group = group; led->id = id; led->wacom = wacom; @@ -1051,10 +1062,12 @@ static int wacom_led_register_one(struct device *dev, struct wacom *wacom, led->cdev.max_brightness = LED_FULL; led->cdev.flags = LED_HW_PLUGGABLE; led->cdev.brightness_get = __wacom_led_brightness_get; - if (!read_only) + if (!read_only) { led->cdev.brightness_set_blocking = wacom_led_brightness_set; - else + led->cdev.default_trigger = led->cdev.name; + } else { led->cdev.brightness_set = wacom_led_readonly_brightness_set; + } error = devm_led_classdev_register(dev, &led->cdev); if (error) { @@ -1131,6 +1144,50 @@ err: return error; } +struct wacom_led *wacom_led_find(struct wacom *wacom, unsigned int group_id, + unsigned int id) +{ + struct wacom_group_leds *group; + + if (group_id >= wacom->led.count) + return NULL; + + group = &wacom->led.groups[group_id]; + + if (!group->leds) + return NULL; + + id %= group->count; + + return &group->leds[id]; +} + +/** + * wacom_led_next: gives the next available led with a wacom trigger. + * + * returns the next available struct wacom_led which has its default trigger + * or the current one if none is available. + */ +struct wacom_led *wacom_led_next(struct wacom *wacom, struct wacom_led *cur) +{ + struct wacom_led *next_led; + int group, next; + + if (!wacom || !cur) + return NULL; + + group = cur->group; + next = cur->id; + + do { + next_led = wacom_led_find(wacom, group, ++next); + if (!next_led || next_led == cur) + return next_led; + } while (next_led->cdev.trigger != &next_led->trigger); + + return next_led; +} + static void wacom_led_groups_release(void *data) { struct wacom *wacom = data; diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 1c882bb70f14..00e0c80be6d5 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -2763,11 +2763,64 @@ static void wacom_setup_numbered_buttons(struct input_dev *input_dev, __set_bit(BTN_BASE + (i-16), input_dev->keybit); } +static bool wacom_is_led_toggled(struct wacom *wacom, int button_count, + int mask, int group) +{ + int button_per_group; + + button_per_group = button_count/wacom->led.count; + + return mask & (1 << (group * button_per_group)); +} + +static void wacom_update_led(struct wacom *wacom, int button_count, int mask, + int group) +{ + struct wacom_led *led, *next_led; + int cur; + bool pressed; + + pressed = wacom_is_led_toggled(wacom, button_count, mask, group); + cur = wacom->led.groups[group].select; + + led = wacom_led_find(wacom, group, cur); + if (!led) { + hid_err(wacom->hdev, "can't find current LED %d in group %d\n", + cur, group); + return; + } + + if (!pressed) { + led->held = false; + return; + } + + if (led->held && pressed) + return; + + next_led = wacom_led_next(wacom, led); + if (!next_led) { + hid_err(wacom->hdev, "can't find next LED in group %d\n", + group); + return; + } + if (next_led == led) + return; + + next_led->held = true; + led_trigger_event(&next_led->trigger, + wacom_leds_brightness_get(next_led)); +} + static void wacom_report_numbered_buttons(struct input_dev *input_dev, int button_count, int mask) { + struct wacom *wacom = input_get_drvdata(input_dev); int i; + for (i = 0; i < wacom->led.count; i++) + wacom_update_led(wacom, button_count, mask, i); + for (i = 0; i < button_count && i < 10; i++) input_report_key(input_dev, BTN_0 + i, mask & (1 << i)); for (i = 10; i < button_count && i < 16; i++) -- cgit v1.2.3 From 5a0fe8abd169be4f2e1f73ad851419d502b5a832 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:13 +0200 Subject: HID: wacom: leds: make sure Cintiq 21UX2 and 24HD control the right LEDs The code for 21UX2 and 24HD makes the LED group 1 on the left, and the group 0 on the right. The buttons are ordered in the other way, but libwacom already exports those that way. So we simply can't reassign LED group 0 to the left buttons, and have to quirk the incoming data... Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 00e0c80be6d5..a9693d5d2e93 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -2768,6 +2768,15 @@ static bool wacom_is_led_toggled(struct wacom *wacom, int button_count, { int button_per_group; + /* + * 24HD and 21UX2 have LED group 1 to the left and LED group 0 + * to the right. We need to reverse the group to match this + * historical behavior. + */ + if (wacom->wacom_wac.features.type == WACOM_24HD || + wacom->wacom_wac.features.type == WACOM_21UX2) + group = 1 - group; + button_per_group = button_count/wacom->led.count; return mask & (1 << (group * button_per_group)); -- cgit v1.2.3 From 6a06281e2bfb6f59aa3290c459981471b4940a39 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:14 +0200 Subject: HID: wacom: leds: handle Cintiq 24HD leds buttons The 24HD has 1 button per LED (first three buttons of each group). We need a special treatment for them as it's not a uniq button that switches between the LEDs. Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index a9693d5d2e93..0914667585ce 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -2763,18 +2763,47 @@ static void wacom_setup_numbered_buttons(struct input_dev *input_dev, __set_bit(BTN_BASE + (i-16), input_dev->keybit); } +static void wacom_24hd_update_leds(struct wacom *wacom, int mask, int group) +{ + struct wacom_led *led; + int i; + bool updated = false; + + /* + * 24HD has LED group 1 to the left and LED group 0 to the right. + * So group 0 matches the second half of the buttons and thus the mask + * needs to be shifted. + */ + if (group == 0) + mask >>= 8; + + for (i = 0; i < 3; i++) { + led = wacom_led_find(wacom, group, i); + if (!led) { + hid_err(wacom->hdev, "can't find LED %d in group %d\n", + i, group); + continue; + } + if (!updated && mask & BIT(i)) { + led->held = true; + led_trigger_event(&led->trigger, LED_FULL); + } else { + led->held = false; + } + } +} + static bool wacom_is_led_toggled(struct wacom *wacom, int button_count, int mask, int group) { int button_per_group; /* - * 24HD and 21UX2 have LED group 1 to the left and LED group 0 + * 21UX2 has LED group 1 to the left and LED group 0 * to the right. We need to reverse the group to match this * historical behavior. */ - if (wacom->wacom_wac.features.type == WACOM_24HD || - wacom->wacom_wac.features.type == WACOM_21UX2) + if (wacom->wacom_wac.features.type == WACOM_21UX2) group = 1 - group; button_per_group = button_count/wacom->led.count; @@ -2789,6 +2818,9 @@ static void wacom_update_led(struct wacom *wacom, int button_count, int mask, int cur; bool pressed; + if (wacom->wacom_wac.features.type == WACOM_24HD) + return wacom_24hd_update_leds(wacom, mask, group); + pressed = wacom_is_led_toggled(wacom, button_count, mask, group); cur = wacom->led.groups[group].select; -- cgit v1.2.3 From 96983296281507f049425b84f0d244c40d506eba Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:15 +0200 Subject: HID: wacom: power_supply: mark the type as USB When upowerd detects a new device, it tries to map this new device to an input to guess its kind. It works OK for wired tablets when the wireless module and its battery are attached, but not so well when connected over wireless. In that case, the battery is attached to the wireless HID node, not the Pen or Pad HID node. So there is no input node as a parent of the reported battery, which means it will be showed as a computer battery in gnome-control-center. If we set the power supply type to USB, upowerd has a heuristic that detects "wacom_" in the name of the power_supply, and set the type to tablet. So it's now clear that the reported battery of from a tablet. (see https://cgit.freedesktop.org/upower/tree/src/linux/up-device-supply.c) Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 7e83352adbde..792708b932bc 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1429,7 +1429,7 @@ static int __wacom_initialize_battery(struct wacom *wacom, bat_desc->get_property = wacom_battery_get_property; sprintf(battery->bat_name, "wacom_battery_%ld", n); bat_desc->name = battery->bat_name; - bat_desc->type = POWER_SUPPLY_TYPE_BATTERY; + bat_desc->type = POWER_SUPPLY_TYPE_USB; bat_desc->use_for_apm = 0; ac_desc->properties = wacom_ac_props; -- cgit v1.2.3 From 136ae5e9bacebbd8f2a94e8dea69f0dc22fbc8c7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:16 +0200 Subject: HID: wacom: power_supply: remove ac information Looks like upowerd is ignoring this since October 2013, so there is no need to keep this around in the kernel. And as mentioned in 8aaa592 (linux: Ignore ACs coming from devices) in the upower tree, "We already have enough information on the device battery". Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 3 --- drivers/hid/wacom_sys.c | 49 +------------------------------------------------ 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 8f8a16243f07..26a8a057b624 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -135,11 +135,8 @@ struct wacom_group_leds { struct wacom_battery { struct power_supply_desc bat_desc; - struct power_supply_desc ac_desc; struct power_supply *battery; - struct power_supply *ac; char bat_name[WACOM_NAME_MAX]; - char ac_name[WACOM_NAME_MAX]; int battery_capacity; int bat_charging; int bat_connected; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 792708b932bc..d8f3d3dc2c88 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1342,12 +1342,6 @@ static enum power_supply_property wacom_battery_props[] = { POWER_SUPPLY_PROP_CAPACITY }; -static enum power_supply_property wacom_ac_props[] = { - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_SCOPE, -}; - static int wacom_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -1384,38 +1378,14 @@ static int wacom_battery_get_property(struct power_supply *psy, return ret; } -static int wacom_ac_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct wacom_battery *battery = power_supply_get_drvdata(psy); - int ret = 0; - - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - /* fall through */ - case POWER_SUPPLY_PROP_ONLINE: - val->intval = battery->ps_connected; - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_DEVICE; - break; - default: - ret = -EINVAL; - break; - } - return ret; -} - static int __wacom_initialize_battery(struct wacom *wacom, struct wacom_battery *battery) { static atomic_t battery_no = ATOMIC_INIT(0); struct device *dev = &wacom->hdev->dev; struct power_supply_config psy_cfg = { .drv_data = battery, }; - struct power_supply *ps_bat, *ps_ac; + struct power_supply *ps_bat; struct power_supply_desc *bat_desc = &battery->bat_desc; - struct power_supply_desc *ac_desc = &battery->ac_desc; unsigned long n; int error; @@ -1432,31 +1402,15 @@ static int __wacom_initialize_battery(struct wacom *wacom, bat_desc->type = POWER_SUPPLY_TYPE_USB; bat_desc->use_for_apm = 0; - ac_desc->properties = wacom_ac_props; - ac_desc->num_properties = ARRAY_SIZE(wacom_ac_props); - ac_desc->get_property = wacom_ac_get_property; - sprintf(battery->ac_name, "wacom_ac_%ld", n); - ac_desc->name = battery->ac_name; - ac_desc->type = POWER_SUPPLY_TYPE_MAINS; - ac_desc->use_for_apm = 0; - ps_bat = devm_power_supply_register(dev, bat_desc, &psy_cfg); if (IS_ERR(ps_bat)) { error = PTR_ERR(ps_bat); goto err; } - ps_ac = devm_power_supply_register(dev, ac_desc, &psy_cfg); - if (IS_ERR(ps_ac)) { - error = PTR_ERR(ps_ac); - goto err; - } - power_supply_powers(ps_bat, &wacom->hdev->dev); - power_supply_powers(ps_ac, &wacom->hdev->dev); battery->battery = ps_bat; - battery->ac = ps_ac; devres_close_group(dev, bat_desc); return 0; @@ -1480,7 +1434,6 @@ static void wacom_destroy_battery(struct wacom *wacom) devres_release_group(&wacom->hdev->dev, &wacom->battery.bat_desc); wacom->battery.battery = NULL; - wacom->battery.ac = NULL; } } -- cgit v1.2.3 From 9956953ec082e9353ee0828538ff37b149cc91c9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 13 Jul 2016 18:06:17 +0200 Subject: HID: wacom: power_supply: provide the actual model_name Instead of displaying a generic "tablet", now g-c-c shows a pretty "Wacom Intuos Pro S (WL)". Signed-off-by: Benjamin Tissoires Acked-by: Ping Cheng Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 1 + drivers/hid/wacom_sys.c | 11 +++++++++++ drivers/hid/wacom_wac.h | 1 + 3 files changed, 13 insertions(+) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 26a8a057b624..b4800ea891cb 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -134,6 +134,7 @@ struct wacom_group_leds { }; struct wacom_battery { + struct wacom *wacom; struct power_supply_desc bat_desc; struct power_supply *battery; char bat_name[WACOM_NAME_MAX]; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index d8f3d3dc2c88..edde8819e506 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1336,6 +1336,7 @@ static int wacom_initialize_leds(struct wacom *wacom) } static enum power_supply_property wacom_battery_props[] = { + POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_SCOPE, @@ -1350,6 +1351,9 @@ static int wacom_battery_get_property(struct power_supply *psy, int ret = 0; switch (psp) { + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = battery->wacom->wacom_wac.name; + break; case POWER_SUPPLY_PROP_PRESENT: val->intval = battery->bat_connected; break; @@ -1392,6 +1396,8 @@ static int __wacom_initialize_battery(struct wacom *wacom, if (!devres_open_group(dev, bat_desc, GFP_KERNEL)) return -ENOMEM; + battery->wacom = wacom; + n = atomic_inc_return(&battery_no) - 1; bat_desc->properties = wacom_battery_props; @@ -1863,6 +1869,9 @@ static void wacom_update_name(struct wacom *wacom, const char *suffix) strlcpy(name, features->name, sizeof(name)); } + snprintf(wacom_wac->name, sizeof(wacom_wac->name), "%s%s", + name, suffix); + /* Append the device type to the name */ snprintf(wacom_wac->pen_name, sizeof(wacom_wac->pen_name), "%s%s Pen", name, suffix); @@ -2097,6 +2106,8 @@ static void wacom_wireless_work(struct work_struct *work) goto fail; } + strlcpy(wacom_wac->name, wacom_wac1->name, + sizeof(wacom_wac->name)); error = wacom_initialize_battery(wacom); if (error) goto fail; diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 8a8974c901cb..745e2c91cb55 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -226,6 +226,7 @@ struct wacom_remote_data { }; struct wacom_wac { + char name[WACOM_NAME_MAX]; char pen_name[WACOM_NAME_MAX]; char touch_name[WACOM_NAME_MAX]; char pad_name[WACOM_NAME_MAX]; -- cgit v1.2.3 From e5bc8eb1b02244be4e97e9654d394275e615923a Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Mon, 8 Aug 2016 12:06:29 -0700 Subject: HID: wacom: Add WACOM_DEVICETYPE_DIRECT for Cintiqs and similar "Direct" input devices like Cintiqs and Tablet PCs set the INPUT_PROP_DIRECT property to notify userspace that the sensor and screen are overlaid. This information can also be useful elsewhere within the kernel driver, however, so we introduce a new WACOM_DEVICETYPE_DIRECT that signals this to other kernel code. Signed-off-by: Jason Gerecke Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 58 +++++++++++++++++++++++++++++++++---------------- drivers/hid/wacom_wac.h | 1 + 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 0914667585ce..a545c2451156 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1750,10 +1750,10 @@ void wacom_wac_usage_mapping(struct hid_device *hdev, { struct wacom *wacom = hid_get_drvdata(hdev); struct wacom_wac *wacom_wac = &wacom->wacom_wac; + struct wacom_features *features = &wacom_wac->features; /* currently, only direct devices have proper hid report descriptors */ - __set_bit(INPUT_PROP_DIRECT, wacom_wac->pen_input->propbit); - __set_bit(INPUT_PROP_DIRECT, wacom_wac->touch_input->propbit); + features->device_type |= WACOM_DEVICETYPE_DIRECT; if (WACOM_PEN_FIELD(field)) return wacom_wac_pen_usage_mapping(hdev, field, usage); @@ -2459,6 +2459,33 @@ void wacom_setup_device_quirks(struct wacom *wacom) if (features->type == REMOTE) features->device_type = WACOM_DEVICETYPE_PAD; + switch (features->type) { + case PL: + case DTU: + case DTUS: + case DTUSX: + case WACOM_21UX2: + case WACOM_22HD: + case DTK: + case WACOM_24HD: + case WACOM_27QHD: + case CINTIQ_HYBRID: + case CINTIQ_COMPANION_2: + case CINTIQ: + case WACOM_BEE: + case WACOM_13HD: + case WACOM_24HDT: + case WACOM_27QHDT: + case TABLETPC: + case TABLETPCE: + case TABLETPC2FG: + case MTSCREEN: + case MTTPC: + case MTTPC_B: + features->device_type |= WACOM_DEVICETYPE_DIRECT; + break; + } + if (wacom->hdev->bus == BUS_BLUETOOTH) features->quirks |= WACOM_QUIRK_BATTERY; @@ -2492,6 +2519,11 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, if (!(features->device_type & WACOM_DEVICETYPE_PEN)) return -ENODEV; + if (features->device_type & WACOM_DEVICETYPE_DIRECT) + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + else + __set_bit(INPUT_PROP_POINTER, input_dev->propbit); + if (features->type == HID_GENERIC) /* setup has already been done */ return 0; @@ -2510,7 +2542,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, input_abs_set_res(input_dev, ABS_X, features->x_resolution); input_abs_set_res(input_dev, ABS_Y, features->y_resolution); - switch (features->type) { case GRAPHIRE_BT: __clear_bit(ABS_MISC, input_dev->absbit); @@ -2534,8 +2565,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, __set_bit(BTN_TOOL_MOUSE, input_dev->keybit); __set_bit(BTN_STYLUS, input_dev->keybit); __set_bit(BTN_STYLUS2, input_dev->keybit); - - __set_bit(INPUT_PROP_POINTER, input_dev->propbit); break; case WACOM_27QHD: @@ -2550,7 +2579,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, case CINTIQ_COMPANION_2: input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0); input_abs_set_res(input_dev, ABS_Z, 287); - __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); wacom_setup_cintiq(wacom_wac); break; @@ -2566,8 +2594,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, /* fall through */ case INTUOS: - __set_bit(INPUT_PROP_POINTER, input_dev->propbit); - wacom_setup_intuos(wacom_wac); break; @@ -2577,8 +2603,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, case INTUOSPL: case INTUOS5S: case INTUOSPS: - __set_bit(INPUT_PROP_POINTER, input_dev->propbit); - input_set_abs_params(input_dev, ABS_DISTANCE, 0, features->distance_max, features->distance_fuzz, 0); @@ -2608,8 +2632,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, __set_bit(BTN_TOOL_RUBBER, input_dev->keybit); __set_bit(BTN_STYLUS, input_dev->keybit); __set_bit(BTN_STYLUS2, input_dev->keybit); - - __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); break; case PTU: @@ -2620,16 +2642,12 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, __set_bit(BTN_TOOL_PEN, input_dev->keybit); __set_bit(BTN_TOOL_RUBBER, input_dev->keybit); __set_bit(BTN_STYLUS, input_dev->keybit); - - __set_bit(INPUT_PROP_POINTER, input_dev->propbit); break; case INTUOSHT: case BAMBOO_PT: case BAMBOO_PEN: case INTUOSHT2: - __set_bit(INPUT_PROP_POINTER, input_dev->propbit); - if (features->type == INTUOSHT2) { wacom_setup_basic_pro_pen(wacom_wac); } else { @@ -2660,6 +2678,11 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev, if (!(features->device_type & WACOM_DEVICETYPE_TOUCH)) return -ENODEV; + if (features->device_type & WACOM_DEVICETYPE_DIRECT) + __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + else + __set_bit(INPUT_PROP_POINTER, input_dev->propbit); + if (features->type == HID_GENERIC) /* setup has already been done */ return 0; @@ -2694,8 +2717,6 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev, case INTUOSPL: case INTUOS5S: case INTUOSPS: - __set_bit(INPUT_PROP_POINTER, input_dev->propbit); - input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, features->x_max, 0, 0); input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, features->y_max, 0, 0); input_mt_init_slots(input_dev, features->touch_max, INPUT_MT_POINTER); @@ -2718,7 +2739,6 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev, case TABLETPC: case TABLETPCE: - __set_bit(INPUT_PROP_DIRECT, input_dev->propbit); break; case INTUOSHT: diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 745e2c91cb55..de5d1adafc6b 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -82,6 +82,7 @@ #define WACOM_DEVICETYPE_TOUCH 0x0002 #define WACOM_DEVICETYPE_PAD 0x0004 #define WACOM_DEVICETYPE_WL_MONITOR 0x0008 +#define WACOM_DEVICETYPE_DIRECT 0x0010 #define WACOM_VENDORDEFINED_PEN 0xff0d0001 #define WACOM_G9_PAGE 0xff090000 -- cgit v1.2.3 From 41372d5d40e73ef4d8574e53a39c7faabff20260 Mon Sep 17 00:00:00 2001 From: Jason Gerecke Date: Mon, 8 Aug 2016 12:06:30 -0700 Subject: HID: wacom: Augment 'oVid' and 'oPid' with heuristics for HID_GENERIC The 'oVid' and 'oPid' variables used by wacom_are_sibling are a hacky solution to the problem of the driver historically having few good heuristics to use in determining if two devices should be considered siblings or not. While it works well enough for explicitly supported devices, it offers no help for HID_GENERIC devices. Now that we have a bit more information (e.g. direct/indirect) available to us though, we should make use of it it to improve the pairing of such devices. Signed-off-by: Jason Gerecke Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 83 ++++++++++++++++++++++++++++++++++++++++++------- drivers/hid/wacom_wac.c | 2 +- 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index edde8819e506..5e7a5648e708 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -527,36 +527,95 @@ struct wacom_hdev_data { static LIST_HEAD(wacom_udev_list); static DEFINE_MUTEX(wacom_udev_list_lock); +static bool compare_device_paths(struct hid_device *hdev_a, + struct hid_device *hdev_b, char separator) +{ + int n1 = strrchr(hdev_a->phys, separator) - hdev_a->phys; + int n2 = strrchr(hdev_b->phys, separator) - hdev_b->phys; + + if (n1 != n2 || n1 <= 0 || n2 <= 0) + return false; + + return !strncmp(hdev_a->phys, hdev_b->phys, n1); +} + static bool wacom_are_sibling(struct hid_device *hdev, struct hid_device *sibling) { struct wacom *wacom = hid_get_drvdata(hdev); struct wacom_features *features = &wacom->wacom_wac.features; - int vid = features->oVid; - int pid = features->oPid; - int n1,n2; + struct wacom *sibling_wacom = hid_get_drvdata(sibling); + struct wacom_features *sibling_features = &sibling_wacom->wacom_wac.features; + __u32 oVid = features->oVid ? features->oVid : hdev->vendor; + __u32 oPid = features->oPid ? features->oPid : hdev->product; - if (vid == 0 && pid == 0) { - vid = hdev->vendor; - pid = hdev->product; + /* The defined oVid/oPid must match that of the sibling */ + if (features->oVid != HID_ANY_ID && sibling->vendor != oVid) + return false; + if (features->oPid != HID_ANY_ID && sibling->product != oPid) + return false; + + /* + * Devices with the same VID/PID must share the same physical + * device path, while those with different VID/PID must share + * the same physical parent device path. + */ + if (hdev->vendor == sibling->vendor && hdev->product == sibling->product) { + if (!compare_device_paths(hdev, sibling, '/')) + return false; + } else { + if (!compare_device_paths(hdev, sibling, '.')) + return false; } - if (vid != sibling->vendor || pid != sibling->product) + /* Skip the remaining heuristics unless you are a HID_GENERIC device */ + if (features->type != HID_GENERIC) + return true; + + /* + * Direct-input devices may not be siblings of indirect-input + * devices. + */ + if ((features->device_type & WACOM_DEVICETYPE_DIRECT) && + !(sibling_features->device_type & WACOM_DEVICETYPE_DIRECT)) return false; - /* Compare the physical path. */ - n1 = strrchr(hdev->phys, '.') - hdev->phys; - n2 = strrchr(sibling->phys, '.') - sibling->phys; - if (n1 != n2 || n1 <= 0 || n2 <= 0) + /* + * Indirect-input devices may not be siblings of direct-input + * devices. + */ + if (!(features->device_type & WACOM_DEVICETYPE_DIRECT) && + (sibling_features->device_type & WACOM_DEVICETYPE_DIRECT)) + return false; + + /* Pen devices may only be siblings of touch devices */ + if ((features->device_type & WACOM_DEVICETYPE_PEN) && + !(sibling_features->device_type & WACOM_DEVICETYPE_TOUCH)) + return false; + + /* Touch devices may only be siblings of pen devices */ + if ((features->device_type & WACOM_DEVICETYPE_TOUCH) && + !(sibling_features->device_type & WACOM_DEVICETYPE_PEN)) return false; - return !strncmp(hdev->phys, sibling->phys, n1); + /* + * No reason could be found for these two devices to NOT be + * siblings, so there's a good chance they ARE siblings + */ + return true; } static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev) { struct wacom_hdev_data *data; + /* Try to find an already-probed interface from the same device */ + list_for_each_entry(data, &wacom_udev_list, list) { + if (compare_device_paths(hdev, data->dev, '/')) + return data; + } + + /* Fallback to finding devices that appear to be "siblings" */ list_for_each_entry(data, &wacom_udev_list, list) { if (wacom_are_sibling(hdev, data->dev)) { kref_get(&data->kref); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index a545c2451156..0c4eb31c1c71 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -3531,7 +3531,7 @@ static const struct wacom_features wacom_features_0x343 = WACOM_DTU_OFFSET, WACOM_DTU_OFFSET }; static const struct wacom_features wacom_features_HID_ANY_ID = - { "Wacom HID", .type = HID_GENERIC }; + { "Wacom HID", .type = HID_GENERIC, .oVid = HID_ANY_ID, .oPid = HID_ANY_ID }; #define USB_DEVICE_WACOM(prod) \ HID_DEVICE(BUS_USB, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\ -- cgit v1.2.3 From 1924e05e6014917b23b7648302be39cfee03d047 Mon Sep 17 00:00:00 2001 From: Ping Cheng Date: Tue, 9 Aug 2016 14:38:56 -0700 Subject: HID: wacom - add touch_arbitration parameter to wacom module Touch arbitration is always on in wacom.ko. However, there are touch enabled applications use both pen and touch simultaneously. We should provide an option for userland to decide if they want arbitration on or off. This patch sets default touch_arbitration to on since most userland apps are not ready to process pen and touch events simultaneously. In the future, when userland is ready to accept pen and touch events together, we will switch default touch_arbitration to off. Tested-by: Peter Hutterer Signed-off-by: Ping Cheng Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/wacom_wac.c | 63 ++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 0c4eb31c1c71..a48ed4ebd5a9 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -34,6 +34,10 @@ */ #define WACOM_CONTACT_AREA_SCALE 2607 +static bool touch_arbitration = 1; +module_param(touch_arbitration, bool, 0644); +MODULE_PARM_DESC(touch_arbitration, " on (Y) off (N)"); + static void wacom_report_numbered_buttons(struct input_dev *input_dev, int button_count, int mask); @@ -882,6 +886,16 @@ static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE); } +static inline bool report_touch_events(struct wacom_wac *wacom) +{ + return (touch_arbitration ? !wacom->shared->stylus_in_proximity : 1); +} + +static inline bool delay_pen_events(struct wacom_wac *wacom) +{ + return (wacom->shared->touch_down && touch_arbitration); +} + static int wacom_intuos_general(struct wacom_wac *wacom) { struct wacom_features *features = &wacom->features; @@ -895,7 +909,7 @@ static int wacom_intuos_general(struct wacom_wac *wacom) data[0] != WACOM_REPORT_INTUOS_PEN) return 0; - if (wacom->shared->touch_down) + if (delay_pen_events(wacom)) return 1; /* don't report events if we don't know the tool ID */ @@ -1155,7 +1169,7 @@ static int wacom_wac_finger_count_touches(struct wacom_wac *wacom) if (touch_max == 1) return test_bit(BTN_TOUCH, input->key) && - !wacom->shared->stylus_in_proximity; + report_touch_events(wacom); for (i = 0; i < input->mt->num_slots; i++) { struct input_mt_slot *ps = &input->mt->slots[i]; @@ -1196,7 +1210,7 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom) for (i = 0; i < contacts_to_send; i++) { int offset = (byte_per_packet * i) + 1; - bool touch = (data[offset] & 0x1) && !wacom->shared->stylus_in_proximity; + bool touch = (data[offset] & 0x1) && report_touch_events(wacom); int slot = input_mt_get_slot_by_key(input, data[offset + 1]); if (slot < 0) @@ -1260,7 +1274,7 @@ static int wacom_mt_touch(struct wacom_wac *wacom) for (i = 0; i < contacts_to_send; i++) { int offset = (WACOM_BYTES_PER_MT_PACKET + x_offset) * i + 3; - bool touch = (data[offset] & 0x1) && !wacom->shared->stylus_in_proximity; + bool touch = (data[offset] & 0x1) && report_touch_events(wacom); int id = get_unaligned_le16(&data[offset + 1]); int slot = input_mt_get_slot_by_key(input, id); @@ -1294,7 +1308,7 @@ static int wacom_tpc_mt_touch(struct wacom_wac *wacom) for (i = 0; i < 2; i++) { int p = data[1] & (1 << i); - bool touch = p && !wacom->shared->stylus_in_proximity; + bool touch = p && report_touch_events(wacom); input_mt_slot(input, i); input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); @@ -1318,7 +1332,7 @@ static int wacom_tpc_single_touch(struct wacom_wac *wacom, size_t len) { unsigned char *data = wacom->data; struct input_dev *input = wacom->touch_input; - bool prox = !wacom->shared->stylus_in_proximity; + bool prox = report_touch_events(wacom); int x = 0, y = 0; if (wacom->features.touch_max > 1 || len > WACOM_PKGLEN_TPC2FG) @@ -1363,8 +1377,10 @@ static int wacom_tpc_pen(struct wacom_wac *wacom) /* keep pen state for touch events */ wacom->shared->stylus_in_proximity = prox; - /* send pen events only when touch is up or forced out */ - if (!wacom->shared->touch_down) { + /* send pen events only when touch is up or forced out + * or touch arbitration is off + */ + if (!delay_pen_events(wacom)) { input_report_key(input, BTN_STYLUS, data[1] & 0x02); input_report_key(input, BTN_STYLUS2, data[1] & 0x10); input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2])); @@ -1506,8 +1522,10 @@ static int wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field, return 0; } - /* send pen events only when touch is up or forced out */ - if (!usage->type || wacom_wac->shared->touch_down) + /* send pen events only when touch is up or forced out + * or touch arbitration is off + */ + if (!usage->type || delay_pen_events(wacom_wac)) return 0; input_event(input, usage->type, usage->code, value); @@ -1537,8 +1555,7 @@ static void wacom_wac_pen_report(struct hid_device *hdev, /* keep pen state for touch events */ wacom_wac->shared->stylus_in_proximity = prox; - /* send pen events only when touch is up or forced out */ - if (!wacom_wac->shared->touch_down) { + if (!delay_pen_events(wacom_wac)) { input_report_key(input, BTN_TOUCH, wacom_wac->hid_data.tipswitch); input_report_key(input, wacom_wac->tool[0], prox); @@ -1609,7 +1626,7 @@ static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac, struct hid_data *hid_data = &wacom_wac->hid_data; bool mt = wacom_wac->features.touch_max > 1; bool prox = hid_data->tipswitch && - !wacom_wac->shared->stylus_in_proximity; + report_touch_events(wacom_wac); wacom_wac->hid_data.num_received++; if (wacom_wac->hid_data.num_received > wacom_wac->hid_data.num_expected) @@ -1835,15 +1852,8 @@ static int wacom_bpt_touch(struct wacom_wac *wacom) for (i = 0; i < 2; i++) { int offset = (data[1] & 0x80) ? (8 * i) : (9 * i); - bool touch = data[offset + 3] & 0x80; - - /* - * Touch events need to be disabled while stylus is - * in proximity because user's hand is resting on touchpad - * and sending unwanted events. User expects tablet buttons - * to continue working though. - */ - touch = touch && !wacom->shared->stylus_in_proximity; + bool touch = report_touch_events(wacom) + && (data[offset + 3] & 0x80); input_mt_slot(input, i); input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); @@ -1880,7 +1890,7 @@ static void wacom_bpt3_touch_msg(struct wacom_wac *wacom, unsigned char *data) if (slot < 0) return; - touch = touch && !wacom->shared->stylus_in_proximity; + touch = touch && report_touch_events(wacom); input_mt_slot(input, slot); input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); @@ -1993,7 +2003,7 @@ static int wacom_bpt_pen(struct wacom_wac *wacom) } wacom->shared->stylus_in_proximity = prox; - if (wacom->shared->touch_down) + if (delay_pen_events(wacom)) return 0; if (prox) { @@ -2087,7 +2097,7 @@ static int wacom_bamboo_pad_touch_event(struct wacom_wac *wacom, for (id = 0; id < wacom->features.touch_max; id++) { valid = !!(prefix & BIT(id)) && - !wacom->shared->stylus_in_proximity; + report_touch_events(wacom); input_mt_slot(input, id); input_mt_report_slot_state(input, MT_TOOL_FINGER, valid); @@ -2109,8 +2119,7 @@ static int wacom_bamboo_pad_touch_event(struct wacom_wac *wacom, input_report_key(input, BTN_RIGHT, prefix & 0x80); /* keep touch state for pen event */ - wacom->shared->touch_down = !!prefix && - !wacom->shared->stylus_in_proximity; + wacom->shared->touch_down = !!prefix && report_touch_events(wacom); return 1; } -- cgit v1.2.3 From 17e2adf2a7d755e17fa75495d29cb98d553d4a66 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 7 Aug 2016 02:25:33 -0700 Subject: Documentation: HID: Intel ISH HID document Document explaining ISH HID operation and implementation. Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- Documentation/hid/intel-ish-hid.txt | 454 ++++++++++++++++++++++++++++++++++++ 1 file changed, 454 insertions(+) create mode 100644 Documentation/hid/intel-ish-hid.txt diff --git a/Documentation/hid/intel-ish-hid.txt b/Documentation/hid/intel-ish-hid.txt new file mode 100644 index 000000000000..d48b21c71ddd --- /dev/null +++ b/Documentation/hid/intel-ish-hid.txt @@ -0,0 +1,454 @@ +Intel Integrated Sensor Hub (ISH) +=============================== + +A sensor hub enables the ability to offload sensor polling and algorithm +processing to a dedicated low power co-processor. This allows the core +processor to go into low power modes more often, resulting in the increased +battery life. + +There are many vendors providing external sensor hubs confirming to HID +Sensor usage tables, and used in several tablets, 2 in 1 convertible laptops +and embedded products. Linux had this support since Linux 3.9. + +Intel® introduced integrated sensor hubs as a part of the SoC starting from +Cherry Trail and now supported on multiple generations of CPU packages. There +are many commercial devices already shipped with Integrated Sensor Hubs (ISH). +These ISH also comply to HID sensor specification, but the difference is the +transport protocol used for communication. The current external sensor hubs +mainly use HID over i2C or USB. But ISH doesn't use either i2c or USB. + +1. Overview + +Using a analogy with a usbhid implementation, the ISH follows a similar model +for a very high speed communication: + + ----------------- ---------------------- + | USB HID | --> | ISH HID | + ----------------- ---------------------- + ----------------- ---------------------- + | USB protocol | --> | ISH Transport | + ----------------- ---------------------- + ----------------- ---------------------- + | EHCI/XHCI | --> | ISH IPC | + ----------------- ---------------------- + PCI PCI + ----------------- ---------------------- + |Host controller| --> | ISH processor | + ----------------- ---------------------- + USB Link + ----------------- ---------------------- + | USB End points| --> | ISH Clients | + ----------------- ---------------------- + +Like USB protocol provides a method for device enumeration, link management +and user data encapsulation, the ISH also provides similar services. But it is +very light weight tailored to manage and communicate with ISH client +applications implemented in the firmware. + +The ISH allows multiple sensor management applications executing in the +firmware. Like USB endpoints the messaging can be to/from a client. As part of +enumeration process, these clients are identified. These clients can be simple +HID sensor applications, sensor calibration application or senor firmware +update application. + +The implementation model is similar, like USB bus, ISH transport is also +implemented as a bus. Each client application executing in the ISH processor +is registered as a device on this bus. The driver, which binds each device +(ISH HID driver) identifies the device type and registers with the hid core. + +2. ISH Implementation: Block Diagram + + --------------------------- + | User Space Applications | + --------------------------- + +----------------IIO ABI---------------- + -------------------------- + | IIO Sensor Drivers | + -------------------------- + -------------------------- + | IIO core | + -------------------------- + -------------------------- + | HID Sensor Hub MFD | + -------------------------- + -------------------------- + | HID Core | + -------------------------- + -------------------------- + | HID over ISH Client | + -------------------------- + -------------------------- + | ISH Transport (ISHTP) | + -------------------------- + -------------------------- + | IPC Drivers | + -------------------------- +OS +---------------- PCI ----------------- +Hardware + Firmware + ---------------------------- + | ISH Hardware/Firmware(FW) | + ---------------------------- + +3. High level processing in above blocks + +3.1 Hardware Interface + +The ISH is exposed as "Non-VGA unclassified PCI device" to the host. The PCI +product and vendor IDs are changed from different generations of processors. So +the source code which enumerate drivers needs to update from generation to +generation. + +3.2 Inter Processor Communication (IPC) driver +Location: drivers/hid/intel-ish-hid/ipc + +The IPC message used memory mapped I/O. The registers are defined in +hw-ish-regs.h. + +3.2.1 IPC/FW message types + +There are two types of messages, one for management of link and other messages +are to and from transport layers. + +TX and RX of Transport messages + +A set of memory mapped register offers support of multi byte messages TX and +RX (E.g.IPC_REG_ISH2HOST_MSG, IPC_REG_HOST2ISH_MSG). The IPC layer maintains +internal queues to sequence messages and send them in order to the FW. +Optionally the caller can register handler to get notification of completion. +A door bell mechanism is used in messaging to trigger processing in host and +client firmware side. When ISH interrupt handler is called, the ISH2HOST +doorbell register is used by host drivers to determine that the interrupt +is for ISH. + +Each side has 32 32-bit message registers and a 32-bit doorbell. Doorbell +register has the following format: +Bits 0..6: fragment length (7 bits are used) +Bits 10..13: encapsulated protocol +Bits 16..19: management command (for IPC management protocol) +Bit 31: doorbell trigger (signal H/W interrupt to the other side) +Other bits are reserved, should be 0. + +3.2.2 Transport layer interface + +To abstract HW level IPC communication, a set of callbacks are registered. +The transport layer uses them to send and receive messages. +Refer to struct ishtp_hw_ops for callbacks. + +3.3 ISH Transport layer +Location: drivers/hid/intel-ish-hid/ishtp/ + +3.3.1 A Generic Transport Layer + +The transport layer is a bi-directional protocol, which defines: +- Set of commands to start, stop, connect, disconnect and flow control +(ishtp/hbm.h) for details +- A flow control mechanism to avoid buffer overflows + +This protocol resembles bus messages described in the following document: +http://www.intel.com/content/dam/www/public/us/en/documents/technical-\ +specifications/dcmi-hi-1-0-spec.pdf "Chapter 7: Bus Message Layer" + +3.3.2 Connection and Flow Control Mechanism + +Each FW client and a protocol is identified by an UUID. In order to communicate +to a FW client, a connection must be established using connect request and +response bus messages. If successful, a pair (host_client_id and fw_client_id) +will identify the connection. + +Once connection is established, peers send each other flow control bus messages +independently. Every peer may send a message only if it has received a +flow-control credit before. Once it sent a message, it may not send another one +before receiving the next flow control credit. +Either side can send disconnect request bus message to end communication. Also +the link will be dropped if major FW reset occurs. + +3.3.3 Peer to Peer data transfer + +Peer to Peer data transfer can happen with or without using DMA. Depending on +the sensor bandwidth requirement DMA can be enabled by using module parameter +ishtp_use_dma under intel_ishtp. + +Each side (host and FW) manages its DMA transfer memory independently. When an +ISHTP client from either host or FW side wants to send something, it decides +whether to send over IPC or over DMA; for each transfer the decision is +independent. The sending side sends DMA_XFER message when the message is in +the respective host buffer (TX when host client sends, RX when FW client +sends). The recipient of DMA message responds with DMA_XFER_ACK, indicating +the sender that the memory region for that message may be reused. + +DMA initialization is started with host sending DMA_ALLOC_NOTIFY bus message +(that includes RX buffer) and FW responds with DMA_ALLOC_NOTIFY_ACK. +Additionally to DMA address communication, this sequence checks capabilities: +if thw host doesn't support DMA, then it won't send DMA allocation, so FW can't +send DMA; if FW doesn't support DMA then it won't respond with +DMA_ALLOC_NOTIFY_ACK, in which case host will not use DMA transfers. +Here ISH acts as busmaster DMA controller. Hence when host sends DMA_XFER, +it's request to do host->ISH DMA transfer; when FW sends DMA_XFER, it means +that it already did DMA and the message resides at host. Thus, DMA_XFER +and DMA_XFER_ACK act as ownership indicators. + +At initial state all outgoing memory belongs to the sender (TX to host, RX to +FW), DMA_XFER transfers ownership on the region that contains ISHTP message to +the receiving side, DMA_XFER_ACK returns ownership to the sender. A sender +needs not wait for previous DMA_XFER to be ack'ed, and may send another message +as long as remaining continuous memory in its ownership is enough. +In principle, multiple DMA_XFER and DMA_XFER_ACK messages may be sent at once +(up to IPC MTU), thus allowing for interrupt throttling. +Currently, ISH FW decides to send over DMA if ISHTP message is more than 3 IPC +fragments and via IPC otherwise. + +3.3.4 Ring Buffers + +When a client initiate a connection, a ring or RX and TX buffers are allocated. +The size of ring can be specified by the client. HID client set 16 and 32 for +TX and RX buffers respectively. On send request from client, the data to be +sent is copied to one of the send ring buffer and scheduled to be sent using +bus message protocol. These buffers are required because the FW may have not +have processed the last message and may not have enough flow control credits +to send. Same thing holds true on receive side and flow control is required. + +3.3.5 Host Enumeration + +The host enumeration bus command allow discovery of clients present in the FW. +There can be multiple sensor clients and clients for calibration function. + +To ease in implantation and allow independent driver handle each client +this transport layer takes advantage of Linux Bus driver model. Each +client is registered as device on the the transport bus (ishtp bus). + +Enumeration sequence of messages: +- Host sends HOST_START_REQ_CMD, indicating that host ISHTP layer is up. +- FW responds with HOST_START_RES_CMD +- Host sends HOST_ENUM_REQ_CMD (enumerate FW clients) +- FW responds with HOST_ENUM_RES_CMD that includes bitmap of available FW +client IDs +- For each FW ID found in that bitmap host sends +HOST_CLIENT_PROPERTIES_REQ_CMD +- FW responds with HOST_CLIENT_PROPERTIES_RES_CMD. Properties include UUID, +max ISHTP message size, etc. +- Once host received properties for that last discovered client, it considers +ISHTP device fully functional (and allocates DMA buffers) + +3.4 HID over ISH Client +Location: drivers/hid/intel-ish-hid + +The ISHTP client driver is responsible for: +- enumerate HID devices under FW ISH client +- Get Report descriptor +- Register with HID core as a LL driver +- Process Get/Set feature request +- Get input reports + +3.5 HID Sensor Hub MFD and IIO sensor drivers + +The functionality in these drivers is the same as an external sensor hub. +Refer to +Documentation/hid/hid-sensor.txt for HID sensor +Documentation/ABI/testing/sysfs-bus-iio for IIO ABIs to user space + +3.6 End to End HID transport Sequence Diagram + +HID-ISH-CLN ISHTP IPC HW + | | | | + | | |-----WAKE UP------------------>| + | | | | + | | |-----HOST READY--------------->| + | | | | + | | |<----MNG_RESET_NOTIFY_ACK----- | + | | | | + | |<----ISHTP_START------ | | + | | | | + | |<-----------------HOST_START_RES_CMD-------------------| + | | | | + | |------------------QUERY_SUBSCRIBER-------------------->| + | | | | + | |------------------HOST_ENUM_REQ_CMD------------------->| + | | | | + | |<-----------------HOST_ENUM_RES_CMD--------------------| + | | | | + | |------------------HOST_CLIENT_PROPERTIES_REQ_CMD------>| + | | | | + | |<-----------------HOST_CLIENT_PROPERTIES_RES_CMD-------| + | Create new device on in ishtp bus | | + | | | | + | |------------------HOST_CLIENT_PROPERTIES_REQ_CMD------>| + | | | | + | |<-----------------HOST_CLIENT_PROPERTIES_RES_CMD-------| + | Create new device on in ishtp bus | | + | | | | + | |--Repeat HOST_CLIENT_PROPERTIES_REQ_CMD-till last one--| + | | | | + probed() + |----ishtp_cl_connect-->|----------------- CLIENT_CONNECT_REQ_CMD-------------->| + | | | | + | |<----------------CLIENT_CONNECT_RES_CMD----------------| + | | | | + |register event callback| | | + | | | | + |ishtp_cl_send( + HOSTIF_DM_ENUM_DEVICES) |----------fill ishtp_msg_hdr struct write to HW----- >| + | | | | + | | |<-----IRQ(IPC_PROTOCOL_ISHTP---| + | | | | + |<--ENUM_DEVICE RSP-----| | | + | | | | +for each enumerated device + |ishtp_cl_send( + HOSTIF_GET_HID_DESCRIPTOR |----------fill ishtp_msg_hdr struct write to HW--- >| + | | | | + ...Response + | | | | +for each enumerated device + |ishtp_cl_send( + HOSTIF_GET_REPORT_DESCRIPTOR |----------fill ishtp_msg_hdr struct write to HW- >| + | | | | + | | | | + hid_allocate_device + | | | | + hid_add_device | | | + | | | | + + +3.7 ISH Debugging + +To debug ISH, event tracing mechanism is used. To enable debug logs +echo 1 > /sys/kernel/debug/tracing/events/intel_ish/enable +cat sys/kernel/debug/tracing/trace + +3.8 ISH IIO sysfs Example on Lenovo thinkpad Yoga 260 + +root@otcpl-ThinkPad-Yoga-260:~# tree -l /sys/bus/iio/devices/ +/sys/bus/iio/devices/ +├── iio:device0 -> ../../../devices/0044:8086:22D8.0001/HID-SENSOR-200073.9.auto/iio:device0 +│   ├── buffer +│   │   ├── enable +│   │   ├── length +│   │   └── watermark +... +│   ├── in_accel_hysteresis +│   ├── in_accel_offset +│   ├── in_accel_sampling_frequency +│   ├── in_accel_scale +│   ├── in_accel_x_raw +│   ├── in_accel_y_raw +│   ├── in_accel_z_raw +│   ├── name +│   ├── scan_elements +│   │   ├── in_accel_x_en +│   │   ├── in_accel_x_index +│   │   ├── in_accel_x_type +│   │   ├── in_accel_y_en +│   │   ├── in_accel_y_index +│   │   ├── in_accel_y_type +│   │   ├── in_accel_z_en +│   │   ├── in_accel_z_index +│   │   └── in_accel_z_type +... +│   │   ├── devices +│   │   │   │   ├── buffer +│   │   │   │   │   ├── enable +│   │   │   │   │   ├── length +│   │   │   │   │   └── watermark +│   │   │   │   ├── dev +│   │   │   │   ├── in_intensity_both_raw +│   │   │   │   ├── in_intensity_hysteresis +│   │   │   │   ├── in_intensity_offset +│   │   │   │   ├── in_intensity_sampling_frequency +│   │   │   │   ├── in_intensity_scale +│   │   │   │   ├── name +│   │   │   │   ├── scan_elements +│   │   │   │   │   ├── in_intensity_both_en +│   │   │   │   │   ├── in_intensity_both_index +│   │   │   │   │   └── in_intensity_both_type +│   │   │   │   ├── trigger +│   │   │   │   │   └── current_trigger +... +│   │   │   │   ├── buffer +│   │   │   │   │   ├── enable +│   │   │   │   │   ├── length +│   │   │   │   │   └── watermark +│   │   │   │   ├── dev +│   │   │   │   ├── in_magn_hysteresis +│   │   │   │   ├── in_magn_offset +│   │   │   │   ├── in_magn_sampling_frequency +│   │   │   │   ├── in_magn_scale +│   │   │   │   ├── in_magn_x_raw +│   │   │   │   ├── in_magn_y_raw +│   │   │   │   ├── in_magn_z_raw +│   │   │   │   ├── in_rot_from_north_magnetic_tilt_comp_raw +│   │   │   │   ├── in_rot_hysteresis +│   │   │   │   ├── in_rot_offset +│   │   │   │   ├── in_rot_sampling_frequency +│   │   │   │   ├── in_rot_scale +│   │   │   │   ├── name +... +│   │   │   │   ├── scan_elements +│   │   │   │   │   ├── in_magn_x_en +│   │   │   │   │   ├── in_magn_x_index +│   │   │   │   │   ├── in_magn_x_type +│   │   │   │   │   ├── in_magn_y_en +│   │   │   │   │   ├── in_magn_y_index +│   │   │   │   │   ├── in_magn_y_type +│   │   │   │   │   ├── in_magn_z_en +│   │   │   │   │   ├── in_magn_z_index +│   │   │   │   │   ├── in_magn_z_type +│   │   │   │   │   ├── in_rot_from_north_magnetic_tilt_comp_en +│   │   │   │   │   ├── in_rot_from_north_magnetic_tilt_comp_index +│   │   │   │   │   └── in_rot_from_north_magnetic_tilt_comp_type +│   │   │   │   ├── trigger +│   │   │   │   │   └── current_trigger +... +│   │   │   │   ├── buffer +│   │   │   │   │   ├── enable +│   │   │   │   │   ├── length +│   │   │   │   │   └── watermark +│   │   │   │   ├── dev +│   │   │   │   ├── in_anglvel_hysteresis +│   │   │   │   ├── in_anglvel_offset +│   │   │   │   ├── in_anglvel_sampling_frequency +│   │   │   │   ├── in_anglvel_scale +│   │   │   │   ├── in_anglvel_x_raw +│   │   │   │   ├── in_anglvel_y_raw +│   │   │   │   ├── in_anglvel_z_raw +│   │   │   │   ├── name +│   │   │   │   ├── scan_elements +│   │   │   │   │   ├── in_anglvel_x_en +│   │   │   │   │   ├── in_anglvel_x_index +│   │   │   │   │   ├── in_anglvel_x_type +│   │   │   │   │   ├── in_anglvel_y_en +│   │   │   │   │   ├── in_anglvel_y_index +│   │   │   │   │   ├── in_anglvel_y_type +│   │   │   │   │   ├── in_anglvel_z_en +│   │   │   │   │   ├── in_anglvel_z_index +│   │   │   │   │   └── in_anglvel_z_type +│   │   │   │   ├── trigger +│   │   │   │   │   └── current_trigger +... +│   │   │   │   ├── buffer +│   │   │   │   │   ├── enable +│   │   │   │   │   ├── length +│   │   │   │   │   └── watermark +│   │   │   │   ├── dev +│   │   │   │   ├── in_anglvel_hysteresis +│   │   │   │   ├── in_anglvel_offset +│   │   │   │   ├── in_anglvel_sampling_frequency +│   │   │   │   ├── in_anglvel_scale +│   │   │   │   ├── in_anglvel_x_raw +│   │   │   │   ├── in_anglvel_y_raw +│   │   │   │   ├── in_anglvel_z_raw +│   │   │   │   ├── name +│   │   │   │   ├── scan_elements +│   │   │   │   │   ├── in_anglvel_x_en +│   │   │   │   │   ├── in_anglvel_x_index +│   │   │   │   │   ├── in_anglvel_x_type +│   │   │   │   │   ├── in_anglvel_y_en +│   │   │   │   │   ├── in_anglvel_y_index +│   │   │   │   │   ├── in_anglvel_y_type +│   │   │   │   │   ├── in_anglvel_z_en +│   │   │   │   │   ├── in_anglvel_z_index +│   │   │   │   │   └── in_anglvel_z_type +│   │   │   │   ├── trigger +│   │   │   │   │   └── current_trigger +... -- cgit v1.2.3 From 3703f53b99e4a7c373ce3568dd3f91f175ebb626 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 7 Aug 2016 02:25:34 -0700 Subject: HID: intel_ish-hid: ISH Transport layer The ISH transport layer (ishtp) is a bi-directional protocol implemented on the top of PCI based inter processor communication layer. This layer offers: - Connection management - Flow control with the firmware - Multiple client sessions - Client message transfer - Client message reception - DMA for RX and TX for fast data transfer Refer to Documentation/hid/intel-ish-hid.txt for overview of the functionality implemented in this layer. Original-author: Daniel Drubin Reviewed-and-tested-by: Ooi, Joyce Tested-by: Grant Likely Tested-by: Rann Bar-On Tested-by: Atri Bhattacharya Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 + drivers/hid/Makefile | 2 + drivers/hid/intel-ish-hid/Kconfig | 17 + drivers/hid/intel-ish-hid/Makefile | 12 + drivers/hid/intel-ish-hid/ishtp/bus.c | 791 ++++++++++++++++ drivers/hid/intel-ish-hid/ishtp/bus.h | 114 +++ drivers/hid/intel-ish-hid/ishtp/client-buffers.c | 258 ++++++ drivers/hid/intel-ish-hid/ishtp/client.c | 1054 ++++++++++++++++++++++ drivers/hid/intel-ish-hid/ishtp/client.h | 182 ++++ drivers/hid/intel-ish-hid/ishtp/dma-if.c | 175 ++++ drivers/hid/intel-ish-hid/ishtp/hbm.c | 1032 +++++++++++++++++++++ drivers/hid/intel-ish-hid/ishtp/hbm.h | 321 +++++++ drivers/hid/intel-ish-hid/ishtp/init.c | 115 +++ drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h | 277 ++++++ 14 files changed, 4352 insertions(+) create mode 100644 drivers/hid/intel-ish-hid/Kconfig create mode 100644 drivers/hid/intel-ish-hid/Makefile create mode 100644 drivers/hid/intel-ish-hid/ishtp/bus.c create mode 100644 drivers/hid/intel-ish-hid/ishtp/bus.h create mode 100644 drivers/hid/intel-ish-hid/ishtp/client-buffers.c create mode 100644 drivers/hid/intel-ish-hid/ishtp/client.c create mode 100644 drivers/hid/intel-ish-hid/ishtp/client.h create mode 100644 drivers/hid/intel-ish-hid/ishtp/dma-if.c create mode 100644 drivers/hid/intel-ish-hid/ishtp/hbm.c create mode 100644 drivers/hid/intel-ish-hid/ishtp/hbm.h create mode 100644 drivers/hid/intel-ish-hid/ishtp/init.c create mode 100644 drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 78ac4811bd3c..5f590e16e41b 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -967,4 +967,6 @@ source "drivers/hid/usbhid/Kconfig" source "drivers/hid/i2c-hid/Kconfig" +source "drivers/hid/intel-ish-hid/Kconfig" + endmenu diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index fc4b2aa47f2e..86b2b5785fd2 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -113,3 +113,5 @@ obj-$(CONFIG_USB_MOUSE) += usbhid/ obj-$(CONFIG_USB_KBD) += usbhid/ obj-$(CONFIG_I2C_HID) += i2c-hid/ + +obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ diff --git a/drivers/hid/intel-ish-hid/Kconfig b/drivers/hid/intel-ish-hid/Kconfig new file mode 100644 index 000000000000..ea065b3684a2 --- /dev/null +++ b/drivers/hid/intel-ish-hid/Kconfig @@ -0,0 +1,17 @@ +menu "Intel ISH HID support" + depends on X86_64 && PCI + +config INTEL_ISH_HID + tristate "Intel Integrated Sensor Hub" + default n + select HID + help + The Integrated Sensor Hub (ISH) enables the ability to offload + sensor polling and algorithm processing to a dedicated low power + processor in the chipset. This allows the core processor to go into + low power modes more often, resulting in the increased battery life. + The current processors that support ISH are: Cherrytrail, Skylake, + Broxton and Kaby Lake. + + Say Y here if you want to support Intel ISH. If unsure, say N. +endmenu diff --git a/drivers/hid/intel-ish-hid/Makefile b/drivers/hid/intel-ish-hid/Makefile new file mode 100644 index 000000000000..7b32d49624ae --- /dev/null +++ b/drivers/hid/intel-ish-hid/Makefile @@ -0,0 +1,12 @@ +# +# Makefile - Intel ISH HID drivers +# Copyright (c) 2014-2016, Intel Corporation. +# +# +obj-$(CONFIG_INTEL_ISH_HID) += intel-ishtp.o +intel-ishtp-objs := ishtp/init.o +intel-ishtp-objs += ishtp/hbm.o +intel-ishtp-objs += ishtp/client.o +intel-ishtp-objs += ishtp/bus.o +intel-ishtp-objs += ishtp/dma-if.o +intel-ishtp-objs += ishtp/client-buffers.o diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c new file mode 100644 index 000000000000..0183eacaf6c9 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -0,0 +1,791 @@ +/* + * ISHTP bus driver + * + * Copyright (c) 2012-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "bus.h" +#include "ishtp-dev.h" +#include "client.h" +#include "hbm.h" + +static int ishtp_use_dma; +module_param_named(ishtp_use_dma, ishtp_use_dma, int, 0600); +MODULE_PARM_DESC(ishtp_use_dma, "Use DMA to send messages"); + +#define to_ishtp_cl_driver(d) container_of(d, struct ishtp_cl_driver, driver) +#define to_ishtp_cl_device(d) container_of(d, struct ishtp_cl_device, dev) +static bool ishtp_device_ready; + +/** + * ishtp_recv() - process ishtp message + * @dev: ishtp device + * + * If a message with valid header and size is received, then + * this function calls appropriate handler. The host or firmware + * address is zero, then they are host bus management message, + * otherwise they are message fo clients. + */ +void ishtp_recv(struct ishtp_device *dev) +{ + uint32_t msg_hdr; + struct ishtp_msg_hdr *ishtp_hdr; + + /* Read ISHTP header dword */ + msg_hdr = dev->ops->ishtp_read_hdr(dev); + if (!msg_hdr) + return; + + dev->ops->sync_fw_clock(dev); + + ishtp_hdr = (struct ishtp_msg_hdr *)&msg_hdr; + dev->ishtp_msg_hdr = msg_hdr; + + /* Sanity check: ISHTP frag. length in header */ + if (ishtp_hdr->length > dev->mtu) { + dev_err(dev->devc, + "ISHTP hdr - bad length: %u; dropped [%08X]\n", + (unsigned int)ishtp_hdr->length, msg_hdr); + return; + } + + /* ISHTP bus message */ + if (!ishtp_hdr->host_addr && !ishtp_hdr->fw_addr) + recv_hbm(dev, ishtp_hdr); + /* ISHTP fixed-client message */ + else if (!ishtp_hdr->host_addr) + recv_fixed_cl_msg(dev, ishtp_hdr); + else + /* ISHTP client message */ + recv_ishtp_cl_msg(dev, ishtp_hdr); +} +EXPORT_SYMBOL(ishtp_recv); + +/** + * ishtp_send_msg() - Send ishtp message + * @dev: ishtp device + * @hdr: Message header + * @msg: Message contents + * @ipc_send_compl: completion callback + * @ipc_send_compl_prm: completion callback parameter + * + * Send a multi fragment message via IPC. After sending the first fragment + * the completion callback is called to schedule transmit of next fragment. + * + * Return: This returns IPC send message status. + */ +int ishtp_send_msg(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr, + void *msg, void(*ipc_send_compl)(void *), + void *ipc_send_compl_prm) +{ + unsigned char ipc_msg[IPC_FULL_MSG_SIZE]; + uint32_t drbl_val; + + drbl_val = dev->ops->ipc_get_header(dev, hdr->length + + sizeof(struct ishtp_msg_hdr), + 1); + + memcpy(ipc_msg, &drbl_val, sizeof(uint32_t)); + memcpy(ipc_msg + sizeof(uint32_t), hdr, sizeof(uint32_t)); + memcpy(ipc_msg + 2 * sizeof(uint32_t), msg, hdr->length); + return dev->ops->write(dev, ipc_send_compl, ipc_send_compl_prm, + ipc_msg, 2 * sizeof(uint32_t) + hdr->length); +} + +/** + * ishtp_write_message() - Send ishtp single fragment message + * @dev: ishtp device + * @hdr: Message header + * @buf: message data + * + * Send a single fragment message via IPC. This returns IPC send message + * status. + * + * Return: This returns IPC send message status. + */ +int ishtp_write_message(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr, + unsigned char *buf) +{ + return ishtp_send_msg(dev, hdr, buf, NULL, NULL); +} + +/** + * ishtp_fw_cl_by_uuid() - locate index of fw client + * @dev: ishtp device + * @uuid: uuid of the client to search + * + * Search firmware client using UUID. + * + * Return: fw client index or -ENOENT if not found + */ +int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const uuid_le *uuid) +{ + int i, res = -ENOENT; + + for (i = 0; i < dev->fw_clients_num; ++i) { + if (uuid_le_cmp(*uuid, dev->fw_clients[i].props.protocol_name) + == 0) { + res = i; + break; + } + } + return res; +} +EXPORT_SYMBOL(ishtp_fw_cl_by_uuid); + +/** + * ishtp_fw_cl_by_id() - return index to fw_clients for client_id + * @dev: the ishtp device structure + * @client_id: fw client id to search + * + * Search firmware client using client id. + * + * Return: index on success, -ENOENT on failure. + */ +int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id) +{ + int i, res = -ENOENT; + unsigned long flags; + + spin_lock_irqsave(&dev->fw_clients_lock, flags); + for (i = 0; i < dev->fw_clients_num; i++) { + if (dev->fw_clients[i].client_id == client_id) { + res = i; + break; + } + } + spin_unlock_irqrestore(&dev->fw_clients_lock, flags); + + return res; +} + +/** + * ishtp_cl_device_probe() - Bus probe() callback + * @dev: the device structure + * + * This is a bus probe callback and calls the drive probe function. + * + * Return: Return value from driver probe() call. + */ +static int ishtp_cl_device_probe(struct device *dev) +{ + struct ishtp_cl_device *device = to_ishtp_cl_device(dev); + struct ishtp_cl_driver *driver; + + if (!device) + return 0; + + driver = to_ishtp_cl_driver(dev->driver); + if (!driver || !driver->probe) + return -ENODEV; + + return driver->probe(device); +} + +/** + * ishtp_cl_device_remove() - Bus remove() callback + * @dev: the device structure + * + * This is a bus remove callback and calls the drive remove function. + * Since the ISH driver model supports only built in, this is + * primarily can be called during pci driver init failure. + * + * Return: Return value from driver remove() call. + */ +static int ishtp_cl_device_remove(struct device *dev) +{ + struct ishtp_cl_device *device = to_ishtp_cl_device(dev); + struct ishtp_cl_driver *driver; + + if (!device || !dev->driver) + return 0; + + if (device->event_cb) { + device->event_cb = NULL; + cancel_work_sync(&device->event_work); + } + + driver = to_ishtp_cl_driver(dev->driver); + if (!driver->remove) { + dev->driver = NULL; + + return 0; + } + + return driver->remove(device); +} + +/** + * ishtp_cl_device_suspend() - Bus suspend callback + * @dev: device + * + * Called during device suspend process. + * + * Return: Return value from driver suspend() call. + */ +static int ishtp_cl_device_suspend(struct device *dev) +{ + struct ishtp_cl_device *device = to_ishtp_cl_device(dev); + struct ishtp_cl_driver *driver; + int ret = 0; + + if (!device) + return 0; + + driver = to_ishtp_cl_driver(dev->driver); + if (driver && driver->driver.pm) { + if (driver->driver.pm->suspend) + ret = driver->driver.pm->suspend(dev); + } + + return ret; +} + +/** + * ishtp_cl_device_resume() - Bus resume callback + * @dev: device + * + * Called during device resume process. + * + * Return: Return value from driver resume() call. + */ +static int ishtp_cl_device_resume(struct device *dev) +{ + struct ishtp_cl_device *device = to_ishtp_cl_device(dev); + struct ishtp_cl_driver *driver; + int ret = 0; + + if (!device) + return 0; + + /* + * When ISH needs hard reset, it is done asynchrnously, hence bus + * resume will be called before full ISH resume + */ + if (device->ishtp_dev->resume_flag) + return 0; + + driver = to_ishtp_cl_driver(dev->driver); + if (driver && driver->driver.pm) { + if (driver->driver.pm->resume) + ret = driver->driver.pm->resume(dev); + } + + return ret; +} + +/** + * ishtp_cl_device_reset() - Reset callback + * @device: ishtp client device instance + * + * This is a callback when HW reset is done and the device need + * reinit. + * + * Return: Return value from driver reset() call. + */ +static int ishtp_cl_device_reset(struct ishtp_cl_device *device) +{ + struct ishtp_cl_driver *driver; + int ret = 0; + + device->event_cb = NULL; + cancel_work_sync(&device->event_work); + + driver = to_ishtp_cl_driver(device->dev.driver); + if (driver && driver->reset) + ret = driver->reset(device); + + return ret; +} + +static ssize_t modalias_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + int len; + + len = snprintf(buf, PAGE_SIZE, "ishtp:%s\n", dev_name(dev)); + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} + +static struct device_attribute ishtp_cl_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int ishtp_cl_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + if (add_uevent_var(env, "MODALIAS=ishtp:%s", dev_name(dev))) + return -ENOMEM; + return 0; +} + +static const struct dev_pm_ops ishtp_cl_bus_dev_pm_ops = { + /* Suspend callbacks */ + .suspend = ishtp_cl_device_suspend, + .resume = ishtp_cl_device_resume, + /* Hibernate callbacks */ + .freeze = ishtp_cl_device_suspend, + .thaw = ishtp_cl_device_resume, + .restore = ishtp_cl_device_resume, +}; + +static struct bus_type ishtp_cl_bus_type = { + .name = "ishtp", + .dev_attrs = ishtp_cl_dev_attrs, + .probe = ishtp_cl_device_probe, + .remove = ishtp_cl_device_remove, + .pm = &ishtp_cl_bus_dev_pm_ops, + .uevent = ishtp_cl_uevent, +}; + +static void ishtp_cl_dev_release(struct device *dev) +{ + kfree(to_ishtp_cl_device(dev)); +} + +static struct device_type ishtp_cl_device_type = { + .release = ishtp_cl_dev_release, +}; + +/** + * ishtp_bus_add_device() - Function to create device on bus + * @dev: ishtp device + * @uuid: uuid of the client + * @name: Name of the client + * + * Allocate ISHTP bus client device, attach it to uuid + * and register with ISHTP bus. + * + * Return: ishtp_cl_device pointer or NULL on failure + */ +static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev, + uuid_le uuid, char *name) +{ + struct ishtp_cl_device *device; + int status; + unsigned long flags; + struct list_head *pos; + + spin_lock_irqsave(&dev->device_list_lock, flags); + list_for_each(pos, &dev->device_list) { + device = list_entry(pos, struct ishtp_cl_device, device_link); + if (!strcmp(name, dev_name(&device->dev))) { + device->fw_client = &dev->fw_clients[ + dev->fw_client_presentation_num - 1]; + spin_unlock_irqrestore(&dev->device_list_lock, flags); + ishtp_cl_device_reset(device); + return device; + } + } + spin_unlock_irqrestore(&dev->device_list_lock, flags); + + device = kzalloc(sizeof(struct ishtp_cl_device), GFP_KERNEL); + if (!device) + return NULL; + + device->dev.parent = dev->devc; + device->dev.bus = &ishtp_cl_bus_type; + device->dev.type = &ishtp_cl_device_type; + device->ishtp_dev = dev; + + device->fw_client = + &dev->fw_clients[dev->fw_client_presentation_num - 1]; + + dev_set_name(&device->dev, "%s", name); + + spin_lock_irqsave(&dev->device_list_lock, flags); + list_add_tail(&device->device_link, &dev->device_list); + spin_unlock_irqrestore(&dev->device_list_lock, flags); + + status = device_register(&device->dev); + if (status) { + spin_lock_irqsave(&dev->device_list_lock, flags); + list_del(&device->device_link); + spin_unlock_irqrestore(&dev->device_list_lock, flags); + dev_err(dev->devc, "Failed to register ISHTP client device\n"); + kfree(device); + return NULL; + } + + ishtp_device_ready = true; + + return device; +} + +/** + * ishtp_bus_remove_device() - Function to relase device on bus + * @device: client device instance + * + * This is a counterpart of ishtp_bus_add_device. + * Device is unregistered. + * the device structure is freed in 'ishtp_cl_dev_release' function + * Called only during error in pci driver init path. + */ +static void ishtp_bus_remove_device(struct ishtp_cl_device *device) +{ + device_unregister(&device->dev); +} + +/** + * __ishtp_cl_driver_register() - Client driver register + * @driver: the client driver instance + * @owner: Owner of this driver module + * + * Once a client driver is probed, it created a client + * instance and registers with the bus. + * + * Return: Return value of driver_register or -ENODEV if not ready + */ +int __ishtp_cl_driver_register(struct ishtp_cl_driver *driver, + struct module *owner) +{ + int err; + + if (!ishtp_device_ready) + return -ENODEV; + + driver->driver.name = driver->name; + driver->driver.owner = owner; + driver->driver.bus = &ishtp_cl_bus_type; + + err = driver_register(&driver->driver); + if (err) + return err; + + return 0; +} +EXPORT_SYMBOL(__ishtp_cl_driver_register); + +/** + * ishtp_cl_driver_unregister() - Client driver unregister + * @driver: the client driver instance + * + * Unregister client during device removal process. + */ +void ishtp_cl_driver_unregister(struct ishtp_cl_driver *driver) +{ + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL(ishtp_cl_driver_unregister); + +/** + * ishtp_bus_event_work() - event work function + * @work: work struct pointer + * + * Once an event is received for a client this work + * function is called. If the device has registered a + * callback then the callback is called. + */ +static void ishtp_bus_event_work(struct work_struct *work) +{ + struct ishtp_cl_device *device; + + device = container_of(work, struct ishtp_cl_device, event_work); + + if (device->event_cb) + device->event_cb(device); +} + +/** + * ishtp_cl_bus_rx_event() - schedule event work + * @device: client device instance + * + * Once an event is received for a client this schedules + * a work function to process. + */ +void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device) +{ + if (!device || !device->event_cb) + return; + + if (device->event_cb) + schedule_work(&device->event_work); +} + +/** + * ishtp_register_event_cb() - Register callback + * @device: client device instance + * @event_cb: Event processor for an client + * + * Register a callback for events, called from client driver + * + * Return: Return 0 or -EALREADY if already registered + */ +int ishtp_register_event_cb(struct ishtp_cl_device *device, + void (*event_cb)(struct ishtp_cl_device *)) +{ + if (device->event_cb) + return -EALREADY; + + device->event_cb = event_cb; + INIT_WORK(&device->event_work, ishtp_bus_event_work); + + return 0; +} +EXPORT_SYMBOL(ishtp_register_event_cb); + +/** + * ishtp_get_device() - update usage count for the device + * @cl_device: client device instance + * + * Increment the usage count. The device can't be deleted + */ +void ishtp_get_device(struct ishtp_cl_device *cl_device) +{ + cl_device->reference_count++; +} +EXPORT_SYMBOL(ishtp_get_device); + +/** + * ishtp_put_device() - decrement usage count for the device + * @cl_device: client device instance + * + * Decrement the usage count. The device can be deleted is count = 0 + */ +void ishtp_put_device(struct ishtp_cl_device *cl_device) +{ + cl_device->reference_count--; +} +EXPORT_SYMBOL(ishtp_put_device); + +/** + * ishtp_bus_new_client() - Create a new client + * @dev: ISHTP device instance + * + * Once bus protocol enumerates a client, this is called + * to add a device for the client. + * + * Return: 0 on success or error code on failure + */ +int ishtp_bus_new_client(struct ishtp_device *dev) +{ + int i; + char *dev_name; + struct ishtp_cl_device *cl_device; + uuid_le device_uuid; + + /* + * For all reported clients, create an unconnected client and add its + * device to ISHTP bus. + * If appropriate driver has loaded, this will trigger its probe(). + * Otherwise, probe() will be called when driver is loaded + */ + i = dev->fw_client_presentation_num - 1; + device_uuid = dev->fw_clients[i].props.protocol_name; + dev_name = kasprintf(GFP_KERNEL, + "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + device_uuid.b[3], device_uuid.b[2], device_uuid.b[1], + device_uuid.b[0], device_uuid.b[5], device_uuid.b[4], + device_uuid.b[7], device_uuid.b[6], device_uuid.b[8], + device_uuid.b[9], device_uuid.b[10], device_uuid.b[11], + device_uuid.b[12], device_uuid.b[13], device_uuid.b[14], + device_uuid.b[15]); + if (!dev_name) + return -ENOMEM; + + cl_device = ishtp_bus_add_device(dev, device_uuid, dev_name); + if (!cl_device) { + kfree(dev_name); + return -ENOENT; + } + + kfree(dev_name); + + return 0; +} + +/** + * ishtp_cl_device_bind() - bind a device + * @cl: ishtp client device + * + * Binds connected ishtp_cl to ISHTP bus device + * + * Return: 0 on success or fault code + */ +int ishtp_cl_device_bind(struct ishtp_cl *cl) +{ + struct ishtp_cl_device *cl_device; + unsigned long flags; + int rv; + + if (!cl->fw_client_id || cl->state != ISHTP_CL_CONNECTED) + return -EFAULT; + + rv = -ENOENT; + spin_lock_irqsave(&cl->dev->device_list_lock, flags); + list_for_each_entry(cl_device, &cl->dev->device_list, + device_link) { + if (cl_device->fw_client->client_id == cl->fw_client_id) { + cl->device = cl_device; + rv = 0; + break; + } + } + spin_unlock_irqrestore(&cl->dev->device_list_lock, flags); + return rv; +} + +/** + * ishtp_bus_remove_all_clients() - Remove all clients + * @ishtp_dev: ishtp device + * @warm_reset: Reset due to FW reset dure to errors or S3 suspend + * + * This is part of reset/remove flow. This function the main processing + * only targets error processing, if the FW has forced reset or + * error to remove connected clients. When warm reset the client devices are + * not removed. + */ +void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev, + bool warm_reset) +{ + struct ishtp_cl_device *cl_device, *n; + struct ishtp_cl *cl; + unsigned long flags; + + spin_lock_irqsave(&ishtp_dev->cl_list_lock, flags); + list_for_each_entry(cl, &ishtp_dev->cl_list, link) { + cl->state = ISHTP_CL_DISCONNECTED; + + /* + * Wake any pending process. The waiter would check dev->state + * and determine that it's not enabled already, + * and will return error to its caller + */ + wake_up_interruptible(&cl->wait_ctrl_res); + + /* Disband any pending read/write requests and free rb */ + ishtp_cl_flush_queues(cl); + + /* Remove all free and in_process rings, both Rx and Tx */ + ishtp_cl_free_rx_ring(cl); + ishtp_cl_free_tx_ring(cl); + + /* + * Free client and ISHTP bus client device structures + * don't free host client because it is part of the OS fd + * structure + */ + } + spin_unlock_irqrestore(&ishtp_dev->cl_list_lock, flags); + + /* Release DMA buffers for client messages */ + ishtp_cl_free_dma_buf(ishtp_dev); + + /* remove bus clients */ + spin_lock_irqsave(&ishtp_dev->device_list_lock, flags); + list_for_each_entry_safe(cl_device, n, &ishtp_dev->device_list, + device_link) { + if (warm_reset && cl_device->reference_count) + continue; + + list_del(&cl_device->device_link); + spin_unlock_irqrestore(&ishtp_dev->device_list_lock, flags); + ishtp_bus_remove_device(cl_device); + spin_lock_irqsave(&ishtp_dev->device_list_lock, flags); + } + spin_unlock_irqrestore(&ishtp_dev->device_list_lock, flags); + + /* Free all client structures */ + spin_lock_irqsave(&ishtp_dev->fw_clients_lock, flags); + kfree(ishtp_dev->fw_clients); + ishtp_dev->fw_clients = NULL; + ishtp_dev->fw_clients_num = 0; + ishtp_dev->fw_client_presentation_num = 0; + ishtp_dev->fw_client_index = 0; + bitmap_zero(ishtp_dev->fw_clients_map, ISHTP_CLIENTS_MAX); + spin_unlock_irqrestore(&ishtp_dev->fw_clients_lock, flags); +} +EXPORT_SYMBOL(ishtp_bus_remove_all_clients); + +/** + * ishtp_reset_handler() - IPC reset handler + * @dev: ishtp device + * + * ISHTP Handler for IPC_RESET notification + */ +void ishtp_reset_handler(struct ishtp_device *dev) +{ + unsigned long flags; + + /* Handle FW-initiated reset */ + dev->dev_state = ISHTP_DEV_RESETTING; + + /* Clear BH processing queue - no further HBMs */ + spin_lock_irqsave(&dev->rd_msg_spinlock, flags); + dev->rd_msg_fifo_head = dev->rd_msg_fifo_tail = 0; + spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags); + + /* Handle ISH FW reset against upper layers */ + ishtp_bus_remove_all_clients(dev, true); +} +EXPORT_SYMBOL(ishtp_reset_handler); + +/** + * ishtp_reset_compl_handler() - Reset completion handler + * @dev: ishtp device + * + * ISHTP handler for IPC_RESET sequence completion to start + * host message bus start protocol sequence. + */ +void ishtp_reset_compl_handler(struct ishtp_device *dev) +{ + dev->dev_state = ISHTP_DEV_INIT_CLIENTS; + dev->hbm_state = ISHTP_HBM_START; + ishtp_hbm_start_req(dev); +} +EXPORT_SYMBOL(ishtp_reset_compl_handler); + +/** + * ishtp_use_dma_transfer() - Function to use DMA + * + * This interface is used to enable usage of DMA + * + * Return non zero if DMA can be enabled + */ +int ishtp_use_dma_transfer(void) +{ + return ishtp_use_dma; +} + +/** + * ishtp_bus_register() - Function to register bus + * + * This register ishtp bus + * + * Return: Return output of bus_register + */ +static int __init ishtp_bus_register(void) +{ + return bus_register(&ishtp_cl_bus_type); +} + +/** + * ishtp_bus_unregister() - Function to unregister bus + * + * This unregister ishtp bus + */ +static void __exit ishtp_bus_unregister(void) +{ + bus_unregister(&ishtp_cl_bus_type); +} + +module_init(ishtp_bus_register); +module_exit(ishtp_bus_unregister); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.h b/drivers/hid/intel-ish-hid/ishtp/bus.h new file mode 100644 index 000000000000..a1ffae7f26ad --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/bus.h @@ -0,0 +1,114 @@ +/* + * ISHTP bus definitions + * + * Copyright (c) 2014-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ +#ifndef _LINUX_ISHTP_CL_BUS_H +#define _LINUX_ISHTP_CL_BUS_H + +#include +#include + +struct ishtp_cl; +struct ishtp_cl_device; +struct ishtp_device; +struct ishtp_msg_hdr; + +/** + * struct ishtp_cl_device - ISHTP device handle + * @dev: device pointer + * @ishtp_dev: pointer to ishtp device structure to primarily to access + * hw device operation callbacks and properties + * @fw_client: fw_client pointer to get fw information like protocol name + * max message length etc. + * @device_link: Link to next client in the list on a bus + * @event_work: Used to schedule rx event for client + * @driver_data: Storage driver private data + * @reference_count: Used for get/put device + * @event_cb: Callback to driver to send events + * + * An ishtp_cl_device pointer is returned from ishtp_add_device() + * and links ISHTP bus clients to their actual host client pointer. + * Drivers for ISHTP devices will get an ishtp_cl_device pointer + * when being probed and shall use it for doing bus I/O. + */ +struct ishtp_cl_device { + struct device dev; + struct ishtp_device *ishtp_dev; + struct ishtp_fw_client *fw_client; + struct list_head device_link; + struct work_struct event_work; + void *driver_data; + int reference_count; + void (*event_cb)(struct ishtp_cl_device *device); +}; + +/** + * struct ishtp_cl_device - ISHTP device handle + * @driver: driver instance on a bus + * @name: Name of the device for probe + * @probe: driver callback for device probe + * @remove: driver callback on device removal + * + * Client drivers defines to get probed/removed for ISHTP client device. + */ +struct ishtp_cl_driver { + struct device_driver driver; + const char *name; + int (*probe)(struct ishtp_cl_device *dev); + int (*remove)(struct ishtp_cl_device *dev); + int (*reset)(struct ishtp_cl_device *dev); + const struct dev_pm_ops *pm; +}; + + +int ishtp_bus_new_client(struct ishtp_device *dev); +void ishtp_remove_all_clients(struct ishtp_device *dev); +int ishtp_cl_device_bind(struct ishtp_cl *cl); +void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device); + +/* Write a multi-fragment message */ +int ishtp_send_msg(struct ishtp_device *dev, + struct ishtp_msg_hdr *hdr, void *msg, + void (*ipc_send_compl)(void *), + void *ipc_send_compl_prm); + +/* Write a single-fragment message */ +int ishtp_write_message(struct ishtp_device *dev, + struct ishtp_msg_hdr *hdr, + unsigned char *buf); + +/* Use DMA to send/receive messages */ +int ishtp_use_dma_transfer(void); + +/* Exported functions */ +void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev, + bool warm_reset); + +void ishtp_recv(struct ishtp_device *dev); +void ishtp_reset_handler(struct ishtp_device *dev); +void ishtp_reset_compl_handler(struct ishtp_device *dev); + +void ishtp_put_device(struct ishtp_cl_device *); +void ishtp_get_device(struct ishtp_cl_device *); + +int __ishtp_cl_driver_register(struct ishtp_cl_driver *driver, + struct module *owner); +#define ishtp_cl_driver_register(driver) \ + __ishtp_cl_driver_register(driver, THIS_MODULE) +void ishtp_cl_driver_unregister(struct ishtp_cl_driver *driver); + +int ishtp_register_event_cb(struct ishtp_cl_device *device, + void (*read_cb)(struct ishtp_cl_device *)); +int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const uuid_le *cuuid); + +#endif /* _LINUX_ISHTP_CL_BUS_H */ diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c new file mode 100644 index 000000000000..3f5ce5ee687e --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -0,0 +1,258 @@ +/* + * ISHTP Ring Buffers + * + * Copyright (c) 2003-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include "client.h" + +/** + * ishtp_cl_alloc_rx_ring() - Allocate RX ring buffers + * @cl: client device instance + * + * Allocate and initialize RX ring buffers + * + * Return: 0 on success else -ENOMEM + */ +int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl) +{ + size_t len = cl->device->fw_client->props.max_msg_length; + int j; + struct ishtp_cl_rb *rb; + int ret = 0; + unsigned long flags; + + for (j = 0; j < cl->rx_ring_size; ++j) { + rb = ishtp_io_rb_init(cl); + if (!rb) { + ret = -ENOMEM; + goto out; + } + ret = ishtp_io_rb_alloc_buf(rb, len); + if (ret) + goto out; + spin_lock_irqsave(&cl->free_list_spinlock, flags); + list_add_tail(&rb->list, &cl->free_rb_list.list); + spin_unlock_irqrestore(&cl->free_list_spinlock, flags); + } + + return 0; + +out: + dev_err(&cl->device->dev, "error in allocating Rx buffers\n"); + ishtp_cl_free_rx_ring(cl); + return ret; +} + +/** + * ishtp_cl_alloc_tx_ring() - Allocate TX ring buffers + * @cl: client device instance + * + * Allocate and initialize TX ring buffers + * + * Return: 0 on success else -ENOMEM + */ +int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl) +{ + size_t len = cl->device->fw_client->props.max_msg_length; + int j; + unsigned long flags; + + /* Allocate pool to free Tx bufs */ + for (j = 0; j < cl->tx_ring_size; ++j) { + struct ishtp_cl_tx_ring *tx_buf; + + tx_buf = kmalloc(sizeof(struct ishtp_cl_tx_ring), GFP_KERNEL); + if (!tx_buf) + goto out; + + memset(tx_buf, 0, sizeof(struct ishtp_cl_tx_ring)); + tx_buf->send_buf.data = kmalloc(len, GFP_KERNEL); + if (!tx_buf->send_buf.data) { + kfree(tx_buf); + goto out; + } + + spin_lock_irqsave(&cl->tx_free_list_spinlock, flags); + list_add_tail(&tx_buf->list, &cl->tx_free_list.list); + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags); + } + return 0; +out: + dev_err(&cl->device->dev, "error in allocating Tx pool\n"); + ishtp_cl_free_rx_ring(cl); + return -ENOMEM; +} + +/** + * ishtp_cl_free_rx_ring() - Free RX ring buffers + * @cl: client device instance + * + * Free RX ring buffers + */ +void ishtp_cl_free_rx_ring(struct ishtp_cl *cl) +{ + struct ishtp_cl_rb *rb; + unsigned long flags; + + /* release allocated memory - pass over free_rb_list */ + spin_lock_irqsave(&cl->free_list_spinlock, flags); + while (!list_empty(&cl->free_rb_list.list)) { + rb = list_entry(cl->free_rb_list.list.next, struct ishtp_cl_rb, + list); + list_del(&rb->list); + kfree(rb->buffer.data); + kfree(rb); + } + spin_unlock_irqrestore(&cl->free_list_spinlock, flags); + /* release allocated memory - pass over in_process_list */ + spin_lock_irqsave(&cl->in_process_spinlock, flags); + while (!list_empty(&cl->in_process_list.list)) { + rb = list_entry(cl->in_process_list.list.next, + struct ishtp_cl_rb, list); + list_del(&rb->list); + kfree(rb->buffer.data); + kfree(rb); + } + spin_unlock_irqrestore(&cl->in_process_spinlock, flags); +} + +/** + * ishtp_cl_free_tx_ring() - Free TX ring buffers + * @cl: client device instance + * + * Free TX ring buffers + */ +void ishtp_cl_free_tx_ring(struct ishtp_cl *cl) +{ + struct ishtp_cl_tx_ring *tx_buf; + unsigned long flags; + + spin_lock_irqsave(&cl->tx_free_list_spinlock, flags); + /* release allocated memory - pass over tx_free_list */ + while (!list_empty(&cl->tx_free_list.list)) { + tx_buf = list_entry(cl->tx_free_list.list.next, + struct ishtp_cl_tx_ring, list); + list_del(&tx_buf->list); + kfree(tx_buf->send_buf.data); + kfree(tx_buf); + } + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags); + + spin_lock_irqsave(&cl->tx_list_spinlock, flags); + /* release allocated memory - pass over tx_list */ + while (!list_empty(&cl->tx_list.list)) { + tx_buf = list_entry(cl->tx_list.list.next, + struct ishtp_cl_tx_ring, list); + list_del(&tx_buf->list); + kfree(tx_buf->send_buf.data); + kfree(tx_buf); + } + spin_unlock_irqrestore(&cl->tx_list_spinlock, flags); +} + +/** + * ishtp_io_rb_free() - Free IO request block + * @rb: IO request block + * + * Free io request block memory + */ +void ishtp_io_rb_free(struct ishtp_cl_rb *rb) +{ + if (rb == NULL) + return; + + kfree(rb->buffer.data); + kfree(rb); +} + +/** + * ishtp_io_rb_init() - Allocate and init IO request block + * @cl: client device instance + * + * Allocate and initialize request block + * + * Return: Allocted IO request block pointer + */ +struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl) +{ + struct ishtp_cl_rb *rb; + + rb = kzalloc(sizeof(struct ishtp_cl_rb), GFP_KERNEL); + if (!rb) + return NULL; + + INIT_LIST_HEAD(&rb->list); + rb->cl = cl; + rb->buf_idx = 0; + return rb; +} + +/** + * ishtp_io_rb_alloc_buf() - Allocate and init response buffer + * @rb: IO request block + * @length: length of response buffer + * + * Allocate respose buffer + * + * Return: 0 on success else -ENOMEM + */ +int ishtp_io_rb_alloc_buf(struct ishtp_cl_rb *rb, size_t length) +{ + if (!rb) + return -EINVAL; + + if (length == 0) + return 0; + + rb->buffer.data = kmalloc(length, GFP_KERNEL); + if (!rb->buffer.data) + return -ENOMEM; + + rb->buffer.size = length; + return 0; +} + +/** + * ishtp_cl_io_rb_recycle() - Recycle IO request blocks + * @rb: IO request block + * + * Re-append rb to its client's free list and send flow control if needed + * + * Return: 0 on success else -EFAULT + */ +int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb) +{ + struct ishtp_cl *cl; + int rets = 0; + unsigned long flags; + + if (!rb || !rb->cl) + return -EFAULT; + + cl = rb->cl; + spin_lock_irqsave(&cl->free_list_spinlock, flags); + list_add_tail(&rb->list, &cl->free_rb_list.list); + spin_unlock_irqrestore(&cl->free_list_spinlock, flags); + + /* + * If we returned the first buffer to empty 'free' list, + * send flow control + */ + if (!cl->out_flow_ctrl_creds) + rets = ishtp_cl_read_start(cl); + + return rets; +} +EXPORT_SYMBOL(ishtp_cl_io_rb_recycle); diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c new file mode 100644 index 000000000000..aad61328f282 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/client.c @@ -0,0 +1,1054 @@ +/* + * ISHTP client logic + * + * Copyright (c) 2003-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include "hbm.h" +#include "client.h" + +/** + * ishtp_read_list_flush() - Flush read queue + * @cl: ishtp client instance + * + * Used to remove all entries from read queue for a client + */ +static void ishtp_read_list_flush(struct ishtp_cl *cl) +{ + struct ishtp_cl_rb *rb; + struct ishtp_cl_rb *next; + unsigned long flags; + + spin_lock_irqsave(&cl->dev->read_list_spinlock, flags); + list_for_each_entry_safe(rb, next, &cl->dev->read_list.list, list) + if (rb->cl && ishtp_cl_cmp_id(cl, rb->cl)) { + list_del(&rb->list); + ishtp_io_rb_free(rb); + } + spin_unlock_irqrestore(&cl->dev->read_list_spinlock, flags); +} + +/** + * ishtp_cl_flush_queues() - Flush all queues for a client + * @cl: ishtp client instance + * + * Used to remove all queues for a client. This is called when a client device + * needs reset due to error, S3 resume or during module removal + * + * Return: 0 on success else -EINVAL if device is NULL + */ +int ishtp_cl_flush_queues(struct ishtp_cl *cl) +{ + if (WARN_ON(!cl || !cl->dev)) + return -EINVAL; + + ishtp_read_list_flush(cl); + + return 0; +} +EXPORT_SYMBOL(ishtp_cl_flush_queues); + +/** + * ishtp_cl_init() - Initialize all fields of a client device + * @cl: ishtp client instance + * @dev: ishtp device + * + * Initializes a client device fields: Init spinlocks, init queues etc. + * This function is called during new client creation + */ +static void ishtp_cl_init(struct ishtp_cl *cl, struct ishtp_device *dev) +{ + memset(cl, 0, sizeof(struct ishtp_cl)); + init_waitqueue_head(&cl->wait_ctrl_res); + spin_lock_init(&cl->free_list_spinlock); + spin_lock_init(&cl->in_process_spinlock); + spin_lock_init(&cl->tx_list_spinlock); + spin_lock_init(&cl->tx_free_list_spinlock); + spin_lock_init(&cl->fc_spinlock); + INIT_LIST_HEAD(&cl->link); + cl->dev = dev; + + INIT_LIST_HEAD(&cl->free_rb_list.list); + INIT_LIST_HEAD(&cl->tx_list.list); + INIT_LIST_HEAD(&cl->tx_free_list.list); + INIT_LIST_HEAD(&cl->in_process_list.list); + + cl->rx_ring_size = CL_DEF_RX_RING_SIZE; + cl->tx_ring_size = CL_DEF_TX_RING_SIZE; + + /* dma */ + cl->last_tx_path = CL_TX_PATH_IPC; + cl->last_dma_acked = 1; + cl->last_dma_addr = NULL; + cl->last_ipc_acked = 1; +} + +/** + * ishtp_cl_allocate() - allocates client structure and sets it up. + * @dev: ishtp device + * + * Allocate memory for new client device and call to initialize each field. + * + * Return: The allocated client instance or NULL on failure + */ +struct ishtp_cl *ishtp_cl_allocate(struct ishtp_device *dev) +{ + struct ishtp_cl *cl; + + cl = kmalloc(sizeof(struct ishtp_cl), GFP_KERNEL); + if (!cl) + return NULL; + + ishtp_cl_init(cl, dev); + return cl; +} +EXPORT_SYMBOL(ishtp_cl_allocate); + +/** + * ishtp_cl_free() - Frees a client device + * @cl: client device instance + * + * Frees a client device + */ +void ishtp_cl_free(struct ishtp_cl *cl) +{ + struct ishtp_device *dev; + unsigned long flags; + + if (!cl) + return; + + dev = cl->dev; + if (!dev) + return; + + spin_lock_irqsave(&dev->cl_list_lock, flags); + ishtp_cl_free_rx_ring(cl); + ishtp_cl_free_tx_ring(cl); + kfree(cl); + spin_unlock_irqrestore(&dev->cl_list_lock, flags); +} +EXPORT_SYMBOL(ishtp_cl_free); + +/** + * ishtp_cl_link() - Reserve a host id and link the client instance + * @cl: client device instance + * @id: host client id to use. It can be ISHTP_HOST_CLIENT_ID_ANY if any + * id from the available can be used + * + * + * This allocates a single bit in the hostmap. This function will make sure + * that not many client sessions are opened at the same time. Once allocated + * the client device instance is added to the ishtp device in the current + * client list + * + * Return: 0 or error code on failure + */ +int ishtp_cl_link(struct ishtp_cl *cl, int id) +{ + struct ishtp_device *dev; + unsigned long flags, flags_cl; + int ret = 0; + + if (WARN_ON(!cl || !cl->dev)) + return -EINVAL; + + dev = cl->dev; + + spin_lock_irqsave(&dev->device_lock, flags); + + if (dev->open_handle_count >= ISHTP_MAX_OPEN_HANDLE_COUNT) { + ret = -EMFILE; + goto unlock_dev; + } + + /* If Id is not assigned get one*/ + if (id == ISHTP_HOST_CLIENT_ID_ANY) + id = find_first_zero_bit(dev->host_clients_map, + ISHTP_CLIENTS_MAX); + + if (id >= ISHTP_CLIENTS_MAX) { + spin_unlock_irqrestore(&dev->device_lock, flags); + dev_err(&cl->device->dev, "id exceeded %d", ISHTP_CLIENTS_MAX); + return -ENOENT; + } + + dev->open_handle_count++; + cl->host_client_id = id; + spin_lock_irqsave(&dev->cl_list_lock, flags_cl); + if (dev->dev_state != ISHTP_DEV_ENABLED) { + ret = -ENODEV; + goto unlock_cl; + } + list_add_tail(&cl->link, &dev->cl_list); + set_bit(id, dev->host_clients_map); + cl->state = ISHTP_CL_INITIALIZING; + +unlock_cl: + spin_unlock_irqrestore(&dev->cl_list_lock, flags_cl); +unlock_dev: + spin_unlock_irqrestore(&dev->device_lock, flags); + return ret; +} +EXPORT_SYMBOL(ishtp_cl_link); + +/** + * ishtp_cl_unlink() - remove fw_cl from the client device list + * @cl: client device instance + * + * Remove a previously linked device to a ishtp device + */ +void ishtp_cl_unlink(struct ishtp_cl *cl) +{ + struct ishtp_device *dev; + struct ishtp_cl *pos; + unsigned long flags; + + /* don't shout on error exit path */ + if (!cl || !cl->dev) + return; + + dev = cl->dev; + + spin_lock_irqsave(&dev->device_lock, flags); + if (dev->open_handle_count > 0) { + clear_bit(cl->host_client_id, dev->host_clients_map); + dev->open_handle_count--; + } + spin_unlock_irqrestore(&dev->device_lock, flags); + + /* + * This checks that 'cl' is actually linked into device's structure, + * before attempting 'list_del' + */ + spin_lock_irqsave(&dev->cl_list_lock, flags); + list_for_each_entry(pos, &dev->cl_list, link) + if (cl->host_client_id == pos->host_client_id) { + list_del_init(&pos->link); + break; + } + spin_unlock_irqrestore(&dev->cl_list_lock, flags); +} +EXPORT_SYMBOL(ishtp_cl_unlink); + +/** + * ishtp_cl_disconnect() - Send disconnect request to firmware + * @cl: client device instance + * + * Send a disconnect request for a client to firmware. + * + * Return: 0 if successful disconnect response from the firmware or error + * code on failure + */ +int ishtp_cl_disconnect(struct ishtp_cl *cl) +{ + struct ishtp_device *dev; + int err; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + dev->print_log(dev, "%s() state %d\n", __func__, cl->state); + + if (cl->state != ISHTP_CL_DISCONNECTING) { + dev->print_log(dev, "%s() Disconnect in progress\n", __func__); + return 0; + } + + if (ishtp_hbm_cl_disconnect_req(dev, cl)) { + dev->print_log(dev, "%s() Failed to disconnect\n", __func__); + dev_err(&cl->device->dev, "failed to disconnect.\n"); + return -ENODEV; + } + + err = wait_event_interruptible_timeout(cl->wait_ctrl_res, + (dev->dev_state != ISHTP_DEV_ENABLED || + cl->state == ISHTP_CL_DISCONNECTED), + ishtp_secs_to_jiffies(ISHTP_CL_CONNECT_TIMEOUT)); + + /* + * If FW reset arrived, this will happen. Don't check cl->, + * as 'cl' may be freed already + */ + if (dev->dev_state != ISHTP_DEV_ENABLED) { + dev->print_log(dev, "%s() dev_state != ISHTP_DEV_ENABLED\n", + __func__); + return -ENODEV; + } + + if (cl->state == ISHTP_CL_DISCONNECTED) { + dev->print_log(dev, "%s() successful\n", __func__); + return 0; + } + + return -ENODEV; +} +EXPORT_SYMBOL(ishtp_cl_disconnect); + +/** + * ishtp_cl_is_other_connecting() - Check other client is connecting + * @cl: client device instance + * + * Checks if other client with the same fw client id is connecting + * + * Return: true if other client is connected else false + */ +static bool ishtp_cl_is_other_connecting(struct ishtp_cl *cl) +{ + struct ishtp_device *dev; + struct ishtp_cl *pos; + unsigned long flags; + + if (WARN_ON(!cl || !cl->dev)) + return false; + + dev = cl->dev; + spin_lock_irqsave(&dev->cl_list_lock, flags); + list_for_each_entry(pos, &dev->cl_list, link) { + if ((pos->state == ISHTP_CL_CONNECTING) && (pos != cl) && + cl->fw_client_id == pos->fw_client_id) { + spin_unlock_irqrestore(&dev->cl_list_lock, flags); + return true; + } + } + spin_unlock_irqrestore(&dev->cl_list_lock, flags); + + return false; +} + +/** + * ishtp_cl_connect() - Send connect request to firmware + * @cl: client device instance + * + * Send a connect request for a client to firmware. If successful it will + * RX and TX ring buffers + * + * Return: 0 if successful connect response from the firmware and able + * to bind and allocate ring buffers or error code on failure + */ +int ishtp_cl_connect(struct ishtp_cl *cl) +{ + struct ishtp_device *dev; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + dev->print_log(dev, "%s() current_state = %d\n", __func__, cl->state); + + if (ishtp_cl_is_other_connecting(cl)) { + dev->print_log(dev, "%s() Busy\n", __func__); + return -EBUSY; + } + + if (ishtp_hbm_cl_connect_req(dev, cl)) { + dev->print_log(dev, "%s() HBM connect req fail\n", __func__); + return -ENODEV; + } + + rets = wait_event_interruptible_timeout(cl->wait_ctrl_res, + (dev->dev_state == ISHTP_DEV_ENABLED && + (cl->state == ISHTP_CL_CONNECTED || + cl->state == ISHTP_CL_DISCONNECTED)), + ishtp_secs_to_jiffies( + ISHTP_CL_CONNECT_TIMEOUT)); + /* + * If FW reset arrived, this will happen. Don't check cl->, + * as 'cl' may be freed already + */ + if (dev->dev_state != ISHTP_DEV_ENABLED) { + dev->print_log(dev, "%s() dev_state != ISHTP_DEV_ENABLED\n", + __func__); + return -EFAULT; + } + + if (cl->state != ISHTP_CL_CONNECTED) { + dev->print_log(dev, "%s() state != ISHTP_CL_CONNECTED\n", + __func__); + return -EFAULT; + } + + rets = cl->status; + if (rets) { + dev->print_log(dev, "%s() Invalid status\n", __func__); + return rets; + } + + rets = ishtp_cl_device_bind(cl); + if (rets) { + dev->print_log(dev, "%s() Bind error\n", __func__); + ishtp_cl_disconnect(cl); + return rets; + } + + rets = ishtp_cl_alloc_rx_ring(cl); + if (rets) { + dev->print_log(dev, "%s() Alloc RX ring failed\n", __func__); + /* if failed allocation, disconnect */ + ishtp_cl_disconnect(cl); + return rets; + } + + rets = ishtp_cl_alloc_tx_ring(cl); + if (rets) { + dev->print_log(dev, "%s() Alloc TX ring failed\n", __func__); + /* if failed allocation, disconnect */ + ishtp_cl_free_rx_ring(cl); + ishtp_cl_disconnect(cl); + return rets; + } + + /* Upon successful connection and allocation, emit flow-control */ + rets = ishtp_cl_read_start(cl); + + dev->print_log(dev, "%s() successful\n", __func__); + + return rets; +} +EXPORT_SYMBOL(ishtp_cl_connect); + +/** + * ishtp_cl_read_start() - Prepare to read client message + * @cl: client device instance + * + * Get a free buffer from pool of free read buffers and add to read buffer + * pool to add contents. Send a flow control request to firmware to be able + * send next message. + * + * Return: 0 if successful or error code on failure + */ +int ishtp_cl_read_start(struct ishtp_cl *cl) +{ + struct ishtp_device *dev; + struct ishtp_cl_rb *rb; + int rets; + int i; + unsigned long flags; + unsigned long dev_flags; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + if (cl->state != ISHTP_CL_CONNECTED) + return -ENODEV; + + if (dev->dev_state != ISHTP_DEV_ENABLED) + return -ENODEV; + + i = ishtp_fw_cl_by_id(dev, cl->fw_client_id); + if (i < 0) { + dev_err(&cl->device->dev, "no such fw client %d\n", + cl->fw_client_id); + return -ENODEV; + } + + /* The current rb is the head of the free rb list */ + spin_lock_irqsave(&cl->free_list_spinlock, flags); + if (list_empty(&cl->free_rb_list.list)) { + dev_warn(&cl->device->dev, + "[ishtp-ish] Rx buffers pool is empty\n"); + rets = -ENOMEM; + rb = NULL; + spin_unlock_irqrestore(&cl->free_list_spinlock, flags); + goto out; + } + rb = list_entry(cl->free_rb_list.list.next, struct ishtp_cl_rb, list); + list_del_init(&rb->list); + spin_unlock_irqrestore(&cl->free_list_spinlock, flags); + + rb->cl = cl; + rb->buf_idx = 0; + + INIT_LIST_HEAD(&rb->list); + rets = 0; + + /* + * This must be BEFORE sending flow control - + * response in ISR may come too fast... + */ + spin_lock_irqsave(&dev->read_list_spinlock, dev_flags); + list_add_tail(&rb->list, &dev->read_list.list); + spin_unlock_irqrestore(&dev->read_list_spinlock, dev_flags); + if (ishtp_hbm_cl_flow_control_req(dev, cl)) { + rets = -ENODEV; + goto out; + } +out: + /* if ishtp_hbm_cl_flow_control_req failed, return rb to free list */ + if (rets && rb) { + spin_lock_irqsave(&dev->read_list_spinlock, dev_flags); + list_del(&rb->list); + spin_unlock_irqrestore(&dev->read_list_spinlock, dev_flags); + + spin_lock_irqsave(&cl->free_list_spinlock, flags); + list_add_tail(&rb->list, &cl->free_rb_list.list); + spin_unlock_irqrestore(&cl->free_list_spinlock, flags); + } + return rets; +} + +/** + * ishtp_cl_send() - Send a message to firmware + * @cl: client device instance + * @buf: message buffer + * @length: length of message + * + * If the client is correct state to send message, this function gets a buffer + * from tx ring buffers, copy the message data and call to send the message + * using ishtp_cl_send_msg() + * + * Return: 0 if successful or error code on failure + */ +int ishtp_cl_send(struct ishtp_cl *cl, uint8_t *buf, size_t length) +{ + struct ishtp_device *dev; + int id; + struct ishtp_cl_tx_ring *cl_msg; + int have_msg_to_send = 0; + unsigned long tx_flags, tx_free_flags; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + if (cl->state != ISHTP_CL_CONNECTED) { + ++cl->err_send_msg; + return -EPIPE; + } + + if (dev->dev_state != ISHTP_DEV_ENABLED) { + ++cl->err_send_msg; + return -ENODEV; + } + + /* Check if we have fw client device */ + id = ishtp_fw_cl_by_id(dev, cl->fw_client_id); + if (id < 0) { + ++cl->err_send_msg; + return -ENOENT; + } + + if (length > dev->fw_clients[id].props.max_msg_length) { + ++cl->err_send_msg; + return -EMSGSIZE; + } + + /* No free bufs */ + spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags); + if (list_empty(&cl->tx_free_list.list)) { + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, + tx_free_flags); + ++cl->err_send_msg; + return -ENOMEM; + } + + cl_msg = list_first_entry(&cl->tx_free_list.list, + struct ishtp_cl_tx_ring, list); + if (!cl_msg->send_buf.data) { + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, + tx_free_flags); + return -EIO; + /* Should not happen, as free list is pre-allocated */ + } + /* + * This is safe, as 'length' is already checked for not exceeding + * max ISHTP message size per client + */ + list_del_init(&cl_msg->list); + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags); + memcpy(cl_msg->send_buf.data, buf, length); + cl_msg->send_buf.size = length; + spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags); + have_msg_to_send = !list_empty(&cl->tx_list.list); + list_add_tail(&cl_msg->list, &cl->tx_list.list); + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + + if (!have_msg_to_send && cl->ishtp_flow_ctrl_creds > 0) + ishtp_cl_send_msg(dev, cl); + + return 0; +} +EXPORT_SYMBOL(ishtp_cl_send); + +/** + * ishtp_cl_read_complete() - read complete + * @rb: Pointer to client request block + * + * If the message is completely received call ishtp_cl_bus_rx_event() + * to process message + */ +static void ishtp_cl_read_complete(struct ishtp_cl_rb *rb) +{ + unsigned long flags; + int schedule_work_flag = 0; + struct ishtp_cl *cl = rb->cl; + + spin_lock_irqsave(&cl->in_process_spinlock, flags); + /* + * if in-process list is empty, then need to schedule + * the processing thread + */ + schedule_work_flag = list_empty(&cl->in_process_list.list); + list_add_tail(&rb->list, &cl->in_process_list.list); + spin_unlock_irqrestore(&cl->in_process_spinlock, flags); + + if (schedule_work_flag) + ishtp_cl_bus_rx_event(cl->device); +} + +/** + * ipc_tx_callback() - IPC tx callback function + * @prm: Pointer to client device instance + * + * Send message over IPC either first time or on callback on previous message + * completion + */ +static void ipc_tx_callback(void *prm) +{ + struct ishtp_cl *cl = prm; + struct ishtp_cl_tx_ring *cl_msg; + size_t rem; + struct ishtp_device *dev = (cl ? cl->dev : NULL); + struct ishtp_msg_hdr ishtp_hdr; + unsigned long tx_flags, tx_free_flags; + unsigned char *pmsg; + + if (!dev) + return; + + /* + * Other conditions if some critical error has + * occurred before this callback is called + */ + if (dev->dev_state != ISHTP_DEV_ENABLED) + return; + + if (cl->state != ISHTP_CL_CONNECTED) + return; + + spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags); + if (list_empty(&cl->tx_list.list)) { + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + return; + } + + if (cl->ishtp_flow_ctrl_creds != 1 && !cl->sending) { + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + return; + } + + if (!cl->sending) { + --cl->ishtp_flow_ctrl_creds; + cl->last_ipc_acked = 0; + cl->last_tx_path = CL_TX_PATH_IPC; + cl->sending = 1; + } + + cl_msg = list_entry(cl->tx_list.list.next, struct ishtp_cl_tx_ring, + list); + rem = cl_msg->send_buf.size - cl->tx_offs; + + ishtp_hdr.host_addr = cl->host_client_id; + ishtp_hdr.fw_addr = cl->fw_client_id; + ishtp_hdr.reserved = 0; + pmsg = cl_msg->send_buf.data + cl->tx_offs; + + if (rem <= dev->mtu) { + ishtp_hdr.length = rem; + ishtp_hdr.msg_complete = 1; + cl->sending = 0; + list_del_init(&cl_msg->list); /* Must be before write */ + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + /* Submit to IPC queue with no callback */ + ishtp_write_message(dev, &ishtp_hdr, pmsg); + spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags); + list_add_tail(&cl_msg->list, &cl->tx_free_list.list); + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, + tx_free_flags); + } else { + /* Send IPC fragment */ + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + cl->tx_offs += dev->mtu; + ishtp_hdr.length = dev->mtu; + ishtp_hdr.msg_complete = 0; + ishtp_send_msg(dev, &ishtp_hdr, pmsg, ipc_tx_callback, cl); + } +} + +/** + * ishtp_cl_send_msg_ipc() -Send message using IPC + * @dev: ISHTP device instance + * @cl: Pointer to client device instance + * + * Send message over IPC not using DMA + */ +static void ishtp_cl_send_msg_ipc(struct ishtp_device *dev, + struct ishtp_cl *cl) +{ + /* If last DMA message wasn't acked yet, leave this one in Tx queue */ + if (cl->last_tx_path == CL_TX_PATH_DMA && cl->last_dma_acked == 0) + return; + + cl->tx_offs = 0; + ipc_tx_callback(cl); + ++cl->send_msg_cnt_ipc; +} + +/** + * ishtp_cl_send_msg_dma() -Send message using DMA + * @dev: ISHTP device instance + * @cl: Pointer to client device instance + * + * Send message using DMA + */ +static void ishtp_cl_send_msg_dma(struct ishtp_device *dev, + struct ishtp_cl *cl) +{ + struct ishtp_msg_hdr hdr; + struct dma_xfer_hbm dma_xfer; + unsigned char *msg_addr; + int off; + struct ishtp_cl_tx_ring *cl_msg; + unsigned long tx_flags, tx_free_flags; + + /* If last IPC message wasn't acked yet, leave this one in Tx queue */ + if (cl->last_tx_path == CL_TX_PATH_IPC && cl->last_ipc_acked == 0) + return; + + spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags); + if (list_empty(&cl->tx_list.list)) { + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + return; + } + + cl_msg = list_entry(cl->tx_list.list.next, struct ishtp_cl_tx_ring, + list); + + msg_addr = ishtp_cl_get_dma_send_buf(dev, cl_msg->send_buf.size); + if (!msg_addr) { + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + if (dev->transfer_path == CL_TX_PATH_DEFAULT) + ishtp_cl_send_msg_ipc(dev, cl); + return; + } + + list_del_init(&cl_msg->list); /* Must be before write */ + spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags); + + --cl->ishtp_flow_ctrl_creds; + cl->last_dma_acked = 0; + cl->last_dma_addr = msg_addr; + cl->last_tx_path = CL_TX_PATH_DMA; + + /* write msg to dma buf */ + memcpy(msg_addr, cl_msg->send_buf.data, cl_msg->send_buf.size); + + /* send dma_xfer hbm msg */ + off = msg_addr - (unsigned char *)dev->ishtp_host_dma_tx_buf; + ishtp_hbm_hdr(&hdr, sizeof(struct dma_xfer_hbm)); + dma_xfer.hbm = DMA_XFER; + dma_xfer.fw_client_id = cl->fw_client_id; + dma_xfer.host_client_id = cl->host_client_id; + dma_xfer.reserved = 0; + dma_xfer.msg_addr = dev->ishtp_host_dma_tx_buf_phys + off; + dma_xfer.msg_length = cl_msg->send_buf.size; + dma_xfer.reserved2 = 0; + ishtp_write_message(dev, &hdr, (unsigned char *)&dma_xfer); + spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags); + list_add_tail(&cl_msg->list, &cl->tx_free_list.list); + spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags); + ++cl->send_msg_cnt_dma; +} + +/** + * ishtp_cl_send_msg() -Send message using DMA or IPC + * @dev: ISHTP device instance + * @cl: Pointer to client device instance + * + * Send message using DMA or IPC based on transfer_path + */ +void ishtp_cl_send_msg(struct ishtp_device *dev, struct ishtp_cl *cl) +{ + if (dev->transfer_path == CL_TX_PATH_DMA) + ishtp_cl_send_msg_dma(dev, cl); + else + ishtp_cl_send_msg_ipc(dev, cl); +} + +/** + * recv_ishtp_cl_msg() -Receive client message + * @dev: ISHTP device instance + * @ishtp_hdr: Pointer to message header + * + * Receive and dispatch ISHTP client messages. This function executes in ISR + * context + */ +void recv_ishtp_cl_msg(struct ishtp_device *dev, + struct ishtp_msg_hdr *ishtp_hdr) +{ + struct ishtp_cl *cl; + struct ishtp_cl_rb *rb; + struct ishtp_cl_rb *new_rb; + unsigned char *buffer = NULL; + struct ishtp_cl_rb *complete_rb = NULL; + unsigned long dev_flags; + unsigned long flags; + int rb_count; + + if (ishtp_hdr->reserved) { + dev_err(dev->devc, "corrupted message header.\n"); + goto eoi; + } + + if (ishtp_hdr->length > IPC_PAYLOAD_SIZE) { + dev_err(dev->devc, + "ISHTP message length in hdr exceeds IPC MTU\n"); + goto eoi; + } + + spin_lock_irqsave(&dev->read_list_spinlock, dev_flags); + rb_count = -1; + list_for_each_entry(rb, &dev->read_list.list, list) { + ++rb_count; + cl = rb->cl; + if (!cl || !(cl->host_client_id == ishtp_hdr->host_addr && + cl->fw_client_id == ishtp_hdr->fw_addr) || + !(cl->state == ISHTP_CL_CONNECTED)) + continue; + + /* If no Rx buffer is allocated, disband the rb */ + if (rb->buffer.size == 0 || rb->buffer.data == NULL) { + spin_unlock_irqrestore(&dev->read_list_spinlock, + dev_flags); + dev_err(&cl->device->dev, + "Rx buffer is not allocated.\n"); + list_del(&rb->list); + ishtp_io_rb_free(rb); + cl->status = -ENOMEM; + goto eoi; + } + + /* + * If message buffer overflown (exceeds max. client msg + * size, drop message and return to free buffer. + * Do we need to disconnect such a client? (We don't send + * back FC, so communication will be stuck anyway) + */ + if (rb->buffer.size < ishtp_hdr->length + rb->buf_idx) { + spin_unlock_irqrestore(&dev->read_list_spinlock, + dev_flags); + dev_err(&cl->device->dev, + "message overflow. size %d len %d idx %ld\n", + rb->buffer.size, ishtp_hdr->length, + rb->buf_idx); + list_del(&rb->list); + ishtp_cl_io_rb_recycle(rb); + cl->status = -EIO; + goto eoi; + } + + buffer = rb->buffer.data + rb->buf_idx; + dev->ops->ishtp_read(dev, buffer, ishtp_hdr->length); + + rb->buf_idx += ishtp_hdr->length; + if (ishtp_hdr->msg_complete) { + /* Last fragment in message - it's complete */ + cl->status = 0; + list_del(&rb->list); + complete_rb = rb; + + --cl->out_flow_ctrl_creds; + /* + * the whole msg arrived, send a new FC, and add a new + * rb buffer for the next coming msg + */ + spin_lock_irqsave(&cl->free_list_spinlock, flags); + + if (!list_empty(&cl->free_rb_list.list)) { + new_rb = list_entry(cl->free_rb_list.list.next, + struct ishtp_cl_rb, list); + list_del_init(&new_rb->list); + spin_unlock_irqrestore(&cl->free_list_spinlock, + flags); + new_rb->cl = cl; + new_rb->buf_idx = 0; + INIT_LIST_HEAD(&new_rb->list); + list_add_tail(&new_rb->list, + &dev->read_list.list); + + ishtp_hbm_cl_flow_control_req(dev, cl); + } else { + spin_unlock_irqrestore(&cl->free_list_spinlock, + flags); + } + } + /* One more fragment in message (even if this was last) */ + ++cl->recv_msg_num_frags; + + /* + * We can safely break here (and in BH too), + * a single input message can go only to a single request! + */ + break; + } + + spin_unlock_irqrestore(&dev->read_list_spinlock, dev_flags); + /* If it's nobody's message, just read and discard it */ + if (!buffer) { + uint8_t rd_msg_buf[ISHTP_RD_MSG_BUF_SIZE]; + + dev_err(dev->devc, "Dropped Rx msg - no request\n"); + dev->ops->ishtp_read(dev, rd_msg_buf, ishtp_hdr->length); + goto eoi; + } + + if (complete_rb) { + getnstimeofday(&cl->ts_rx); + ++cl->recv_msg_cnt_ipc; + ishtp_cl_read_complete(complete_rb); + } +eoi: + return; +} + +/** + * recv_ishtp_cl_msg_dma() -Receive client message + * @dev: ISHTP device instance + * @msg: message pointer + * @hbm: hbm buffer + * + * Receive and dispatch ISHTP client messages using DMA. This function executes + * in ISR context + */ +void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg, + struct dma_xfer_hbm *hbm) +{ + struct ishtp_cl *cl; + struct ishtp_cl_rb *rb; + struct ishtp_cl_rb *new_rb; + unsigned char *buffer = NULL; + struct ishtp_cl_rb *complete_rb = NULL; + unsigned long dev_flags; + unsigned long flags; + + spin_lock_irqsave(&dev->read_list_spinlock, dev_flags); + list_for_each_entry(rb, &dev->read_list.list, list) { + cl = rb->cl; + if (!cl || !(cl->host_client_id == hbm->host_client_id && + cl->fw_client_id == hbm->fw_client_id) || + !(cl->state == ISHTP_CL_CONNECTED)) + continue; + + /* + * If no Rx buffer is allocated, disband the rb + */ + if (rb->buffer.size == 0 || rb->buffer.data == NULL) { + spin_unlock_irqrestore(&dev->read_list_spinlock, + dev_flags); + dev_err(&cl->device->dev, + "response buffer is not allocated.\n"); + list_del(&rb->list); + ishtp_io_rb_free(rb); + cl->status = -ENOMEM; + goto eoi; + } + + /* + * If message buffer overflown (exceeds max. client msg + * size, drop message and return to free buffer. + * Do we need to disconnect such a client? (We don't send + * back FC, so communication will be stuck anyway) + */ + if (rb->buffer.size < hbm->msg_length) { + spin_unlock_irqrestore(&dev->read_list_spinlock, + dev_flags); + dev_err(&cl->device->dev, + "message overflow. size %d len %d idx %ld\n", + rb->buffer.size, hbm->msg_length, rb->buf_idx); + list_del(&rb->list); + ishtp_cl_io_rb_recycle(rb); + cl->status = -EIO; + goto eoi; + } + + buffer = rb->buffer.data; + memcpy(buffer, msg, hbm->msg_length); + rb->buf_idx = hbm->msg_length; + + /* Last fragment in message - it's complete */ + cl->status = 0; + list_del(&rb->list); + complete_rb = rb; + + --cl->out_flow_ctrl_creds; + /* + * the whole msg arrived, send a new FC, and add a new + * rb buffer for the next coming msg + */ + spin_lock_irqsave(&cl->free_list_spinlock, flags); + + if (!list_empty(&cl->free_rb_list.list)) { + new_rb = list_entry(cl->free_rb_list.list.next, + struct ishtp_cl_rb, list); + list_del_init(&new_rb->list); + spin_unlock_irqrestore(&cl->free_list_spinlock, + flags); + new_rb->cl = cl; + new_rb->buf_idx = 0; + INIT_LIST_HEAD(&new_rb->list); + list_add_tail(&new_rb->list, + &dev->read_list.list); + + ishtp_hbm_cl_flow_control_req(dev, cl); + } else { + spin_unlock_irqrestore(&cl->free_list_spinlock, + flags); + } + + /* One more fragment in message (this is always last) */ + ++cl->recv_msg_num_frags; + + /* + * We can safely break here (and in BH too), + * a single input message can go only to a single request! + */ + break; + } + + spin_unlock_irqrestore(&dev->read_list_spinlock, dev_flags); + /* If it's nobody's message, just read and discard it */ + if (!buffer) { + dev_err(dev->devc, "Dropped Rx (DMA) msg - no request\n"); + goto eoi; + } + + if (complete_rb) { + getnstimeofday(&cl->ts_rx); + ++cl->recv_msg_cnt_dma; + ishtp_cl_read_complete(complete_rb); + } +eoi: + return; +} diff --git a/drivers/hid/intel-ish-hid/ishtp/client.h b/drivers/hid/intel-ish-hid/ishtp/client.h new file mode 100644 index 000000000000..444d069c2ed4 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/client.h @@ -0,0 +1,182 @@ +/* + * ISHTP client logic + * + * Copyright (c) 2003-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef _ISHTP_CLIENT_H_ +#define _ISHTP_CLIENT_H_ + +#include +#include "ishtp-dev.h" + +/* Client state */ +enum cl_state { + ISHTP_CL_INITIALIZING = 0, + ISHTP_CL_CONNECTING, + ISHTP_CL_CONNECTED, + ISHTP_CL_DISCONNECTING, + ISHTP_CL_DISCONNECTED +}; + +/* Tx and Rx ring size */ +#define CL_DEF_RX_RING_SIZE 2 +#define CL_DEF_TX_RING_SIZE 2 +#define CL_MAX_RX_RING_SIZE 32 +#define CL_MAX_TX_RING_SIZE 32 + +#define DMA_SLOT_SIZE 4096 +/* Number of IPC fragments after which it's worth sending via DMA */ +#define DMA_WORTH_THRESHOLD 3 + +/* DMA/IPC Tx paths. Other the default means enforcement */ +#define CL_TX_PATH_DEFAULT 0 +#define CL_TX_PATH_IPC 1 +#define CL_TX_PATH_DMA 2 + +/* Client Tx buffer list entry */ +struct ishtp_cl_tx_ring { + struct list_head list; + struct ishtp_msg_data send_buf; +}; + +/* ISHTP client instance */ +struct ishtp_cl { + struct list_head link; + struct ishtp_device *dev; + enum cl_state state; + int status; + + /* Link to ISHTP bus device */ + struct ishtp_cl_device *device; + + /* ID of client connected */ + uint8_t host_client_id; + uint8_t fw_client_id; + uint8_t ishtp_flow_ctrl_creds; + uint8_t out_flow_ctrl_creds; + + /* dma */ + int last_tx_path; + /* 0: ack wasn't received,1:ack was received */ + int last_dma_acked; + unsigned char *last_dma_addr; + /* 0: ack wasn't received,1:ack was received */ + int last_ipc_acked; + + /* Rx ring buffer pool */ + unsigned int rx_ring_size; + struct ishtp_cl_rb free_rb_list; + spinlock_t free_list_spinlock; + /* Rx in-process list */ + struct ishtp_cl_rb in_process_list; + spinlock_t in_process_spinlock; + + /* Client Tx buffers list */ + unsigned int tx_ring_size; + struct ishtp_cl_tx_ring tx_list, tx_free_list; + spinlock_t tx_list_spinlock; + spinlock_t tx_free_list_spinlock; + size_t tx_offs; /* Offset in buffer at head of 'tx_list' */ + + /** + * if we get a FC, and the list is not empty, we must know whether we + * are at the middle of sending. + * if so -need to increase FC counter, otherwise, need to start sending + * the first msg in list + * (!)This is for counting-FC implementation only. Within single-FC the + * other party may NOT send FC until it receives complete message + */ + int sending; + + /* Send FC spinlock */ + spinlock_t fc_spinlock; + + /* wait queue for connect and disconnect response from FW */ + wait_queue_head_t wait_ctrl_res; + + /* Error stats */ + unsigned int err_send_msg; + unsigned int err_send_fc; + + /* Send/recv stats */ + unsigned int send_msg_cnt_ipc; + unsigned int send_msg_cnt_dma; + unsigned int recv_msg_cnt_ipc; + unsigned int recv_msg_cnt_dma; + unsigned int recv_msg_num_frags; + unsigned int ishtp_flow_ctrl_cnt; + unsigned int out_flow_ctrl_cnt; + + /* Rx msg ... out FC timing */ + struct timespec ts_rx; + struct timespec ts_out_fc; + struct timespec ts_max_fc_delay; + void *client_data; +}; + +/* Client connection managenment internal functions */ +int ishtp_can_client_connect(struct ishtp_device *ishtp_dev, uuid_le *uuid); +int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id); +void ishtp_cl_send_msg(struct ishtp_device *dev, struct ishtp_cl *cl); +void recv_ishtp_cl_msg(struct ishtp_device *dev, + struct ishtp_msg_hdr *ishtp_hdr); +int ishtp_cl_read_start(struct ishtp_cl *cl); + +/* Ring Buffer I/F */ +int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl); +int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl); +void ishtp_cl_free_rx_ring(struct ishtp_cl *cl); +void ishtp_cl_free_tx_ring(struct ishtp_cl *cl); + +/* DMA I/F functions */ +void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg, + struct dma_xfer_hbm *hbm); +void ishtp_cl_alloc_dma_buf(struct ishtp_device *dev); +void ishtp_cl_free_dma_buf(struct ishtp_device *dev); +void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev, + uint32_t size); +void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev, + void *msg_addr, + uint8_t size); + +/* Request blocks alloc/free I/F */ +struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl); +void ishtp_io_rb_free(struct ishtp_cl_rb *priv_rb); +int ishtp_io_rb_alloc_buf(struct ishtp_cl_rb *rb, size_t length); + +/** + * ishtp_cl_cmp_id - tells if file private data have same id + * returns true - if ids are the same and not NULL + */ +static inline bool ishtp_cl_cmp_id(const struct ishtp_cl *cl1, + const struct ishtp_cl *cl2) +{ + return cl1 && cl2 && + (cl1->host_client_id == cl2->host_client_id) && + (cl1->fw_client_id == cl2->fw_client_id); +} + +/* exported functions from ISHTP under client management scope */ +struct ishtp_cl *ishtp_cl_allocate(struct ishtp_device *dev); +void ishtp_cl_free(struct ishtp_cl *cl); +int ishtp_cl_link(struct ishtp_cl *cl, int id); +void ishtp_cl_unlink(struct ishtp_cl *cl); +int ishtp_cl_disconnect(struct ishtp_cl *cl); +int ishtp_cl_connect(struct ishtp_cl *cl); +int ishtp_cl_send(struct ishtp_cl *cl, uint8_t *buf, size_t length); +int ishtp_cl_flush_queues(struct ishtp_cl *cl); + +/* exported functions from ISHTP client buffer management scope */ +int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb); + +#endif /* _ISHTP_CLIENT_H_ */ diff --git a/drivers/hid/intel-ish-hid/ishtp/dma-if.c b/drivers/hid/intel-ish-hid/ishtp/dma-if.c new file mode 100644 index 000000000000..2783f3666114 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/dma-if.c @@ -0,0 +1,175 @@ +/* + * ISHTP DMA I/F functions + * + * Copyright (c) 2003-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include "ishtp-dev.h" +#include "client.h" + +/** + * ishtp_cl_alloc_dma_buf() - Allocate DMA RX and TX buffer + * @dev: ishtp device + * + * Allocate RX and TX DMA buffer once during bus setup. + * It allocates 1MB, RX and TX DMA buffer, which are divided + * into slots. + */ +void ishtp_cl_alloc_dma_buf(struct ishtp_device *dev) +{ + dma_addr_t h; + + if (dev->ishtp_host_dma_tx_buf) + return; + + dev->ishtp_host_dma_tx_buf_size = 1024*1024; + dev->ishtp_host_dma_rx_buf_size = 1024*1024; + + /* Allocate Tx buffer and init usage bitmap */ + dev->ishtp_host_dma_tx_buf = dma_alloc_coherent(dev->devc, + dev->ishtp_host_dma_tx_buf_size, + &h, GFP_KERNEL); + if (dev->ishtp_host_dma_tx_buf) + dev->ishtp_host_dma_tx_buf_phys = h; + + dev->ishtp_dma_num_slots = dev->ishtp_host_dma_tx_buf_size / + DMA_SLOT_SIZE; + + dev->ishtp_dma_tx_map = kcalloc(dev->ishtp_dma_num_slots, + sizeof(uint8_t), + GFP_KERNEL); + spin_lock_init(&dev->ishtp_dma_tx_lock); + + /* Allocate Rx buffer */ + dev->ishtp_host_dma_rx_buf = dma_alloc_coherent(dev->devc, + dev->ishtp_host_dma_rx_buf_size, + &h, GFP_KERNEL); + + if (dev->ishtp_host_dma_rx_buf) + dev->ishtp_host_dma_rx_buf_phys = h; +} + +/** + * ishtp_cl_free_dma_buf() - Free DMA RX and TX buffer + * @dev: ishtp device + * + * Free DMA buffer when all clients are released. This is + * only happens during error path in ISH built in driver + * model + */ +void ishtp_cl_free_dma_buf(struct ishtp_device *dev) +{ + dma_addr_t h; + + if (dev->ishtp_host_dma_tx_buf) { + h = dev->ishtp_host_dma_tx_buf_phys; + dma_free_coherent(dev->devc, dev->ishtp_host_dma_tx_buf_size, + dev->ishtp_host_dma_tx_buf, h); + } + + if (dev->ishtp_host_dma_rx_buf) { + h = dev->ishtp_host_dma_rx_buf_phys; + dma_free_coherent(dev->devc, dev->ishtp_host_dma_rx_buf_size, + dev->ishtp_host_dma_rx_buf, h); + } + + kfree(dev->ishtp_dma_tx_map); + dev->ishtp_host_dma_tx_buf = NULL; + dev->ishtp_host_dma_rx_buf = NULL; + dev->ishtp_dma_tx_map = NULL; +} + +/* + * ishtp_cl_get_dma_send_buf() - Get a DMA memory slot + * @dev: ishtp device + * @size: Size of memory to get + * + * Find and return free address of "size" bytes in dma tx buffer. + * the function will mark this address as "in-used" memory. + * + * Return: NULL when no free buffer else a buffer to copy + */ +void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev, + uint32_t size) +{ + unsigned long flags; + int i, j, free; + /* additional slot is needed if there is rem */ + int required_slots = (size / DMA_SLOT_SIZE) + + 1 * (size % DMA_SLOT_SIZE != 0); + + spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags); + for (i = 0; i <= (dev->ishtp_dma_num_slots - required_slots); i++) { + free = 1; + for (j = 0; j < required_slots; j++) + if (dev->ishtp_dma_tx_map[i+j]) { + free = 0; + i += j; + break; + } + if (free) { + /* mark memory as "caught" */ + for (j = 0; j < required_slots; j++) + dev->ishtp_dma_tx_map[i+j] = 1; + spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags); + return (i * DMA_SLOT_SIZE) + + (unsigned char *)dev->ishtp_host_dma_tx_buf; + } + } + spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags); + dev_err(dev->devc, "No free DMA buffer to send msg\n"); + return NULL; +} + +/* + * ishtp_cl_release_dma_acked_mem() - Release DMA memory slot + * @dev: ishtp device + * @msg_addr: message address of slot + * @size: Size of memory to get + * + * Release_dma_acked_mem - returnes the acked memory to free list. + * (from msg_addr, size bytes long) + */ +void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev, + void *msg_addr, + uint8_t size) +{ + unsigned long flags; + int acked_slots = (size / DMA_SLOT_SIZE) + + 1 * (size % DMA_SLOT_SIZE != 0); + int i, j; + + if ((msg_addr - dev->ishtp_host_dma_tx_buf) % DMA_SLOT_SIZE) { + dev_err(dev->devc, "Bad DMA Tx ack address\n"); + return; + } + + i = (msg_addr - dev->ishtp_host_dma_tx_buf) / DMA_SLOT_SIZE; + spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags); + for (j = 0; j < acked_slots; j++) { + if ((i + j) >= dev->ishtp_dma_num_slots || + !dev->ishtp_dma_tx_map[i+j]) { + /* no such slot, or memory is already free */ + spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags); + dev_err(dev->devc, "Bad DMA Tx ack address\n"); + return; + } + dev->ishtp_dma_tx_map[i+j] = 0; + } + spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags); +} diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c new file mode 100644 index 000000000000..74bffee60774 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c @@ -0,0 +1,1032 @@ +/* + * ISHTP bus layer messages handling + * + * Copyright (c) 2003-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include "ishtp-dev.h" +#include "hbm.h" +#include "client.h" + +/** + * ishtp_hbm_fw_cl_allocate() - Allocate FW clients + * @dev: ISHTP device instance + * + * Allocates storage for fw clients + */ +static void ishtp_hbm_fw_cl_allocate(struct ishtp_device *dev) +{ + struct ishtp_fw_client *clients; + int b; + + /* count how many ISH clients we have */ + for_each_set_bit(b, dev->fw_clients_map, ISHTP_CLIENTS_MAX) + dev->fw_clients_num++; + + if (dev->fw_clients_num <= 0) + return; + + /* allocate storage for fw clients representation */ + clients = kcalloc(dev->fw_clients_num, sizeof(struct ishtp_fw_client), + GFP_KERNEL); + if (!clients) { + dev->dev_state = ISHTP_DEV_RESETTING; + ish_hw_reset(dev); + return; + } + dev->fw_clients = clients; +} + +/** + * ishtp_hbm_cl_hdr() - construct client hbm header + * @cl: client + * @hbm_cmd: host bus message command + * @buf: buffer for cl header + * @len: buffer length + * + * Initialize HBM buffer + */ +static inline void ishtp_hbm_cl_hdr(struct ishtp_cl *cl, uint8_t hbm_cmd, + void *buf, size_t len) +{ + struct ishtp_hbm_cl_cmd *cmd = buf; + + memset(cmd, 0, len); + + cmd->hbm_cmd = hbm_cmd; + cmd->host_addr = cl->host_client_id; + cmd->fw_addr = cl->fw_client_id; +} + +/** + * ishtp_hbm_cl_addr_equal() - Compare client address + * @cl: client + * @buf: Client command buffer + * + * Compare client address with the address in command buffer + * + * Return: True if they have the same address + */ +static inline bool ishtp_hbm_cl_addr_equal(struct ishtp_cl *cl, void *buf) +{ + struct ishtp_hbm_cl_cmd *cmd = buf; + + return cl->host_client_id == cmd->host_addr && + cl->fw_client_id == cmd->fw_addr; +} + +/** + * ishtp_hbm_start_wait() - Wait for HBM start message + * @dev: ISHTP device instance + * + * Wait for HBM start message from firmware + * + * Return: 0 if HBM start is/was received else timeout error + */ +int ishtp_hbm_start_wait(struct ishtp_device *dev) +{ + int ret; + + if (dev->hbm_state > ISHTP_HBM_START) + return 0; + + dev_dbg(dev->devc, "Going to wait for ishtp start. hbm_state=%08X\n", + dev->hbm_state); + ret = wait_event_interruptible_timeout(dev->wait_hbm_recvd_msg, + dev->hbm_state >= ISHTP_HBM_STARTED, + (ISHTP_INTEROP_TIMEOUT * HZ)); + + dev_dbg(dev->devc, + "Woke up from waiting for ishtp start. hbm_state=%08X\n", + dev->hbm_state); + + if (ret <= 0 && (dev->hbm_state <= ISHTP_HBM_START)) { + dev->hbm_state = ISHTP_HBM_IDLE; + dev_err(dev->devc, + "waiting for ishtp start failed. ret=%d hbm_state=%08X\n", + ret, dev->hbm_state); + return -ETIMEDOUT; + } + return 0; +} + +/** + * ishtp_hbm_start_req() - Send HBM start message + * @dev: ISHTP device instance + * + * Send HBM start message to firmware + * + * Return: 0 if success else error code + */ +int ishtp_hbm_start_req(struct ishtp_device *dev) +{ + struct ishtp_msg_hdr hdr; + unsigned char data[128]; + struct ishtp_msg_hdr *ishtp_hdr = &hdr; + struct hbm_host_version_request *start_req; + const size_t len = sizeof(struct hbm_host_version_request); + + ishtp_hbm_hdr(ishtp_hdr, len); + + /* host start message */ + start_req = (struct hbm_host_version_request *)data; + memset(start_req, 0, len); + start_req->hbm_cmd = HOST_START_REQ_CMD; + start_req->host_version.major_version = HBM_MAJOR_VERSION; + start_req->host_version.minor_version = HBM_MINOR_VERSION; + + /* + * (!) Response to HBM start may be so quick that this thread would get + * preempted BEFORE managing to set hbm_state = ISHTP_HBM_START. + * So set it at first, change back to ISHTP_HBM_IDLE upon failure + */ + dev->hbm_state = ISHTP_HBM_START; + if (ishtp_write_message(dev, ishtp_hdr, data)) { + dev_err(dev->devc, "version message send failed\n"); + dev->dev_state = ISHTP_DEV_RESETTING; + dev->hbm_state = ISHTP_HBM_IDLE; + ish_hw_reset(dev); + return -ENODEV; + } + + return 0; +} + +/** + * ishtp_hbm_enum_clients_req() - Send client enum req + * @dev: ISHTP device instance + * + * Send enumeration client request message + * + * Return: 0 if success else error code + */ +void ishtp_hbm_enum_clients_req(struct ishtp_device *dev) +{ + struct ishtp_msg_hdr hdr; + unsigned char data[128]; + struct ishtp_msg_hdr *ishtp_hdr = &hdr; + struct hbm_host_enum_request *enum_req; + const size_t len = sizeof(struct hbm_host_enum_request); + + /* enumerate clients */ + ishtp_hbm_hdr(ishtp_hdr, len); + + enum_req = (struct hbm_host_enum_request *)data; + memset(enum_req, 0, len); + enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; + + if (ishtp_write_message(dev, ishtp_hdr, data)) { + dev->dev_state = ISHTP_DEV_RESETTING; + dev_err(dev->devc, "enumeration request send failed\n"); + ish_hw_reset(dev); + } + dev->hbm_state = ISHTP_HBM_ENUM_CLIENTS; +} + +/** + * ishtp_hbm_prop_req() - Request property + * @dev: ISHTP device instance + * + * Request property for a single client + * + * Return: 0 if success else error code + */ +static int ishtp_hbm_prop_req(struct ishtp_device *dev) +{ + + struct ishtp_msg_hdr hdr; + unsigned char data[128]; + struct ishtp_msg_hdr *ishtp_hdr = &hdr; + struct hbm_props_request *prop_req; + const size_t len = sizeof(struct hbm_props_request); + unsigned long next_client_index; + uint8_t client_num; + + client_num = dev->fw_client_presentation_num; + + next_client_index = find_next_bit(dev->fw_clients_map, + ISHTP_CLIENTS_MAX, dev->fw_client_index); + + /* We got all client properties */ + if (next_client_index == ISHTP_CLIENTS_MAX) { + dev->hbm_state = ISHTP_HBM_WORKING; + dev->dev_state = ISHTP_DEV_ENABLED; + + for (dev->fw_client_presentation_num = 1; + dev->fw_client_presentation_num < client_num + 1; + ++dev->fw_client_presentation_num) + /* Add new client device */ + ishtp_bus_new_client(dev); + return 0; + } + + dev->fw_clients[client_num].client_id = next_client_index; + + ishtp_hbm_hdr(ishtp_hdr, len); + prop_req = (struct hbm_props_request *)data; + + memset(prop_req, 0, sizeof(struct hbm_props_request)); + + prop_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; + prop_req->address = next_client_index; + + if (ishtp_write_message(dev, ishtp_hdr, data)) { + dev->dev_state = ISHTP_DEV_RESETTING; + dev_err(dev->devc, "properties request send failed\n"); + ish_hw_reset(dev); + return -EIO; + } + + dev->fw_client_index = next_client_index; + + return 0; +} + +/** + * ishtp_hbm_stop_req() - Send HBM stop + * @dev: ISHTP device instance + * + * Send stop request message + */ +static void ishtp_hbm_stop_req(struct ishtp_device *dev) +{ + struct ishtp_msg_hdr hdr; + unsigned char data[128]; + struct ishtp_msg_hdr *ishtp_hdr = &hdr; + struct hbm_host_stop_request *req; + const size_t len = sizeof(struct hbm_host_stop_request); + + ishtp_hbm_hdr(ishtp_hdr, len); + req = (struct hbm_host_stop_request *)data; + + memset(req, 0, sizeof(struct hbm_host_stop_request)); + req->hbm_cmd = HOST_STOP_REQ_CMD; + req->reason = DRIVER_STOP_REQUEST; + + ishtp_write_message(dev, ishtp_hdr, data); +} + +/** + * ishtp_hbm_cl_flow_control_req() - Send flow control request + * @dev: ISHTP device instance + * @cl: ISHTP client instance + * + * Send flow control request + * + * Return: 0 if success else error code + */ +int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev, + struct ishtp_cl *cl) +{ + struct ishtp_msg_hdr hdr; + unsigned char data[128]; + struct ishtp_msg_hdr *ishtp_hdr = &hdr; + const size_t len = sizeof(struct hbm_flow_control); + int rv; + unsigned int num_frags; + unsigned long flags; + + spin_lock_irqsave(&cl->fc_spinlock, flags); + ishtp_hbm_hdr(ishtp_hdr, len); + ishtp_hbm_cl_hdr(cl, ISHTP_FLOW_CONTROL_CMD, data, len); + + /* + * Sync possible race when RB recycle and packet receive paths + * both try to send an out FC + */ + if (cl->out_flow_ctrl_creds) { + spin_unlock_irqrestore(&cl->fc_spinlock, flags); + return 0; + } + + num_frags = cl->recv_msg_num_frags; + cl->recv_msg_num_frags = 0; + + rv = ishtp_write_message(dev, ishtp_hdr, data); + if (!rv) { + ++cl->out_flow_ctrl_creds; + ++cl->out_flow_ctrl_cnt; + getnstimeofday(&cl->ts_out_fc); + if (cl->ts_rx.tv_sec && cl->ts_rx.tv_nsec) { + struct timespec ts_diff; + + ts_diff = timespec_sub(cl->ts_out_fc, cl->ts_rx); + if (timespec_compare(&ts_diff, &cl->ts_max_fc_delay) + > 0) + cl->ts_max_fc_delay = ts_diff; + } + } else { + ++cl->err_send_fc; + } + + spin_unlock_irqrestore(&cl->fc_spinlock, flags); + return rv; +} + +/** + * ishtp_hbm_cl_disconnect_req() - Send disconnect request + * @dev: ISHTP device instance + * @cl: ISHTP client instance + * + * Send disconnect message to fw + * + * Return: 0 if success else error code + */ +int ishtp_hbm_cl_disconnect_req(struct ishtp_device *dev, struct ishtp_cl *cl) +{ + struct ishtp_msg_hdr hdr; + unsigned char data[128]; + struct ishtp_msg_hdr *ishtp_hdr = &hdr; + const size_t len = sizeof(struct hbm_client_connect_request); + + ishtp_hbm_hdr(ishtp_hdr, len); + ishtp_hbm_cl_hdr(cl, CLIENT_DISCONNECT_REQ_CMD, data, len); + + return ishtp_write_message(dev, ishtp_hdr, data); +} + +/** + * ishtp_hbm_cl_disconnect_res() - Get disconnect response + * @dev: ISHTP device instance + * @rs: Response message + * + * Received disconnect response from fw + */ +static void ishtp_hbm_cl_disconnect_res(struct ishtp_device *dev, + struct hbm_client_connect_response *rs) +{ + struct ishtp_cl *cl = NULL; + unsigned long flags; + + spin_lock_irqsave(&dev->cl_list_lock, flags); + list_for_each_entry(cl, &dev->cl_list, link) { + if (!rs->status && ishtp_hbm_cl_addr_equal(cl, rs)) { + cl->state = ISHTP_CL_DISCONNECTED; + break; + } + } + if (cl) + wake_up_interruptible(&cl->wait_ctrl_res); + spin_unlock_irqrestore(&dev->cl_list_lock, flags); +} + +/** + * ishtp_hbm_cl_connect_req() - Send connect request + * @dev: ISHTP device instance + * @cl: client device instance + * + * Send connection request to specific fw client + * + * Return: 0 if success else error code + */ +int ishtp_hbm_cl_connect_req(struct ishtp_device *dev, struct ishtp_cl *cl) +{ + struct ishtp_msg_hdr hdr; + unsigned char data[128]; + struct ishtp_msg_hdr *ishtp_hdr = &hdr; + const size_t len = sizeof(struct hbm_client_connect_request); + + ishtp_hbm_hdr(ishtp_hdr, len); + ishtp_hbm_cl_hdr(cl, CLIENT_CONNECT_REQ_CMD, data, len); + + return ishtp_write_message(dev, ishtp_hdr, data); +} + +/** + * ishtp_hbm_cl_connect_res() - Get connect response + * @dev: ISHTP device instance + * @rs: Response message + * + * Received connect response from fw + */ +static void ishtp_hbm_cl_connect_res(struct ishtp_device *dev, + struct hbm_client_connect_response *rs) +{ + struct ishtp_cl *cl = NULL; + unsigned long flags; + + spin_lock_irqsave(&dev->cl_list_lock, flags); + list_for_each_entry(cl, &dev->cl_list, link) { + if (ishtp_hbm_cl_addr_equal(cl, rs)) { + if (!rs->status) { + cl->state = ISHTP_CL_CONNECTED; + cl->status = 0; + } else { + cl->state = ISHTP_CL_DISCONNECTED; + cl->status = -ENODEV; + } + break; + } + } + if (cl) + wake_up_interruptible(&cl->wait_ctrl_res); + spin_unlock_irqrestore(&dev->cl_list_lock, flags); +} + +/** + * ishtp_client_disconnect_request() - Receive disconnect request + * @dev: ISHTP device instance + * @disconnect_req: disconnect request structure + * + * Disconnect request bus message from the fw. Send diconnect response. + */ +static void ishtp_hbm_fw_disconnect_req(struct ishtp_device *dev, + struct hbm_client_connect_request *disconnect_req) +{ + struct ishtp_cl *cl; + const size_t len = sizeof(struct hbm_client_connect_response); + unsigned long flags; + struct ishtp_msg_hdr hdr; + unsigned char data[4]; /* All HBM messages are 4 bytes */ + + spin_lock_irqsave(&dev->cl_list_lock, flags); + list_for_each_entry(cl, &dev->cl_list, link) { + if (ishtp_hbm_cl_addr_equal(cl, disconnect_req)) { + cl->state = ISHTP_CL_DISCONNECTED; + + /* send disconnect response */ + ishtp_hbm_hdr(&hdr, len); + ishtp_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, data, + len); + ishtp_write_message(dev, &hdr, data); + break; + } + } + spin_unlock_irqrestore(&dev->cl_list_lock, flags); +} + +/** + * ishtp_hbm_dma_xfer_ack(() - Receive transfer ACK + * @dev: ISHTP device instance + * @dma_xfer: HBM transfer message + * + * Receive ack for ISHTP-over-DMA client message + */ +static void ishtp_hbm_dma_xfer_ack(struct ishtp_device *dev, + struct dma_xfer_hbm *dma_xfer) +{ + void *msg; + uint64_t offs; + struct ishtp_msg_hdr *ishtp_hdr = + (struct ishtp_msg_hdr *)&dev->ishtp_msg_hdr; + unsigned int msg_offs; + struct ishtp_cl *cl; + + for (msg_offs = 0; msg_offs < ishtp_hdr->length; + msg_offs += sizeof(struct dma_xfer_hbm)) { + offs = dma_xfer->msg_addr - dev->ishtp_host_dma_tx_buf_phys; + if (offs > dev->ishtp_host_dma_tx_buf_size) { + dev_err(dev->devc, "Bad DMA Tx ack message address\n"); + return; + } + if (dma_xfer->msg_length > + dev->ishtp_host_dma_tx_buf_size - offs) { + dev_err(dev->devc, "Bad DMA Tx ack message size\n"); + return; + } + + /* logical address of the acked mem */ + msg = (unsigned char *)dev->ishtp_host_dma_tx_buf + offs; + ishtp_cl_release_dma_acked_mem(dev, msg, dma_xfer->msg_length); + + list_for_each_entry(cl, &dev->cl_list, link) { + if (cl->fw_client_id == dma_xfer->fw_client_id && + cl->host_client_id == dma_xfer->host_client_id) + /* + * in case that a single ack may be sent + * over several dma transfers, and the last msg + * addr was inside the acked memory, but not in + * its start + */ + if (cl->last_dma_addr >= + (unsigned char *)msg && + cl->last_dma_addr < + (unsigned char *)msg + + dma_xfer->msg_length) { + cl->last_dma_acked = 1; + + if (!list_empty(&cl->tx_list.list) && + cl->ishtp_flow_ctrl_creds) { + /* + * start sending the first msg + */ + ishtp_cl_send_msg(dev, cl); + } + } + } + ++dma_xfer; + } +} + +/** + * ishtp_hbm_dma_xfer() - Receive DMA transfer message + * @dev: ISHTP device instance + * @dma_xfer: HBM transfer message + * + * Receive ISHTP-over-DMA client message + */ +static void ishtp_hbm_dma_xfer(struct ishtp_device *dev, + struct dma_xfer_hbm *dma_xfer) +{ + void *msg; + uint64_t offs; + struct ishtp_msg_hdr hdr; + struct ishtp_msg_hdr *ishtp_hdr = + (struct ishtp_msg_hdr *) &dev->ishtp_msg_hdr; + struct dma_xfer_hbm *prm = dma_xfer; + unsigned int msg_offs; + + for (msg_offs = 0; msg_offs < ishtp_hdr->length; + msg_offs += sizeof(struct dma_xfer_hbm)) { + + offs = dma_xfer->msg_addr - dev->ishtp_host_dma_rx_buf_phys; + if (offs > dev->ishtp_host_dma_rx_buf_size) { + dev_err(dev->devc, "Bad DMA Rx message address\n"); + return; + } + if (dma_xfer->msg_length > + dev->ishtp_host_dma_rx_buf_size - offs) { + dev_err(dev->devc, "Bad DMA Rx message size\n"); + return; + } + msg = dev->ishtp_host_dma_rx_buf + offs; + recv_ishtp_cl_msg_dma(dev, msg, dma_xfer); + dma_xfer->hbm = DMA_XFER_ACK; /* Prepare for response */ + ++dma_xfer; + } + + /* Send DMA_XFER_ACK [...] */ + ishtp_hbm_hdr(&hdr, ishtp_hdr->length); + ishtp_write_message(dev, &hdr, (unsigned char *)prm); +} + +/** + * ishtp_hbm_dispatch() - HBM dispatch function + * @dev: ISHTP device instance + * @hdr: bus message + * + * Bottom half read routine after ISR to handle the read bus message cmd + * processing + */ +void ishtp_hbm_dispatch(struct ishtp_device *dev, + struct ishtp_bus_message *hdr) +{ + struct ishtp_bus_message *ishtp_msg; + struct ishtp_fw_client *fw_client; + struct hbm_host_version_response *version_res; + struct hbm_client_connect_response *connect_res; + struct hbm_client_connect_response *disconnect_res; + struct hbm_client_connect_request *disconnect_req; + struct hbm_props_response *props_res; + struct hbm_host_enum_response *enum_res; + struct ishtp_msg_hdr ishtp_hdr; + struct dma_alloc_notify dma_alloc_notify; + struct dma_xfer_hbm *dma_xfer; + + ishtp_msg = hdr; + + switch (ishtp_msg->hbm_cmd) { + case HOST_START_RES_CMD: + version_res = (struct hbm_host_version_response *)ishtp_msg; + if (!version_res->host_version_supported) { + dev->version = version_res->fw_max_version; + + dev->hbm_state = ISHTP_HBM_STOPPED; + ishtp_hbm_stop_req(dev); + return; + } + + dev->version.major_version = HBM_MAJOR_VERSION; + dev->version.minor_version = HBM_MINOR_VERSION; + if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS && + dev->hbm_state == ISHTP_HBM_START) { + dev->hbm_state = ISHTP_HBM_STARTED; + ishtp_hbm_enum_clients_req(dev); + } else { + dev_err(dev->devc, + "reset: wrong host start response\n"); + /* BUG: why do we arrive here? */ + ish_hw_reset(dev); + return; + } + + wake_up_interruptible(&dev->wait_hbm_recvd_msg); + break; + + case CLIENT_CONNECT_RES_CMD: + connect_res = (struct hbm_client_connect_response *)ishtp_msg; + ishtp_hbm_cl_connect_res(dev, connect_res); + break; + + case CLIENT_DISCONNECT_RES_CMD: + disconnect_res = + (struct hbm_client_connect_response *)ishtp_msg; + ishtp_hbm_cl_disconnect_res(dev, disconnect_res); + break; + + case HOST_CLIENT_PROPERTIES_RES_CMD: + props_res = (struct hbm_props_response *)ishtp_msg; + fw_client = &dev->fw_clients[dev->fw_client_presentation_num]; + + if (props_res->status || !dev->fw_clients) { + dev_err(dev->devc, + "reset: properties response hbm wrong status\n"); + ish_hw_reset(dev); + return; + } + + if (fw_client->client_id != props_res->address) { + dev_err(dev->devc, + "reset: host properties response address mismatch [%02X %02X]\n", + fw_client->client_id, props_res->address); + ish_hw_reset(dev); + return; + } + + if (dev->dev_state != ISHTP_DEV_INIT_CLIENTS || + dev->hbm_state != ISHTP_HBM_CLIENT_PROPERTIES) { + dev_err(dev->devc, + "reset: unexpected properties response\n"); + ish_hw_reset(dev); + return; + } + + fw_client->props = props_res->client_properties; + dev->fw_client_index++; + dev->fw_client_presentation_num++; + + /* request property for the next client */ + ishtp_hbm_prop_req(dev); + + if (dev->dev_state != ISHTP_DEV_ENABLED) + break; + + if (!ishtp_use_dma_transfer()) + break; + + dev_dbg(dev->devc, "Requesting to use DMA\n"); + ishtp_cl_alloc_dma_buf(dev); + if (dev->ishtp_host_dma_rx_buf) { + const size_t len = sizeof(dma_alloc_notify); + + memset(&dma_alloc_notify, 0, sizeof(dma_alloc_notify)); + dma_alloc_notify.hbm = DMA_BUFFER_ALLOC_NOTIFY; + dma_alloc_notify.buf_size = + dev->ishtp_host_dma_rx_buf_size; + dma_alloc_notify.buf_address = + dev->ishtp_host_dma_rx_buf_phys; + ishtp_hbm_hdr(&ishtp_hdr, len); + ishtp_write_message(dev, &ishtp_hdr, + (unsigned char *)&dma_alloc_notify); + } + + break; + + case HOST_ENUM_RES_CMD: + enum_res = (struct hbm_host_enum_response *) ishtp_msg; + memcpy(dev->fw_clients_map, enum_res->valid_addresses, 32); + if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS && + dev->hbm_state == ISHTP_HBM_ENUM_CLIENTS) { + dev->fw_client_presentation_num = 0; + dev->fw_client_index = 0; + + ishtp_hbm_fw_cl_allocate(dev); + dev->hbm_state = ISHTP_HBM_CLIENT_PROPERTIES; + + /* first property request */ + ishtp_hbm_prop_req(dev); + } else { + dev_err(dev->devc, + "reset: unexpected enumeration response hbm\n"); + ish_hw_reset(dev); + return; + } + break; + + case HOST_STOP_RES_CMD: + if (dev->hbm_state != ISHTP_HBM_STOPPED) + dev_err(dev->devc, "unexpected stop response\n"); + + dev->dev_state = ISHTP_DEV_DISABLED; + dev_info(dev->devc, "reset: FW stop response\n"); + ish_hw_reset(dev); + break; + + case CLIENT_DISCONNECT_REQ_CMD: + /* search for client */ + disconnect_req = + (struct hbm_client_connect_request *)ishtp_msg; + ishtp_hbm_fw_disconnect_req(dev, disconnect_req); + break; + + case FW_STOP_REQ_CMD: + dev->hbm_state = ISHTP_HBM_STOPPED; + break; + + case DMA_BUFFER_ALLOC_RESPONSE: + dev->ishtp_host_dma_enabled = 1; + break; + + case DMA_XFER: + dma_xfer = (struct dma_xfer_hbm *)ishtp_msg; + if (!dev->ishtp_host_dma_enabled) { + dev_err(dev->devc, + "DMA XFER requested but DMA is not enabled\n"); + break; + } + ishtp_hbm_dma_xfer(dev, dma_xfer); + break; + + case DMA_XFER_ACK: + dma_xfer = (struct dma_xfer_hbm *)ishtp_msg; + if (!dev->ishtp_host_dma_enabled || + !dev->ishtp_host_dma_tx_buf) { + dev_err(dev->devc, + "DMA XFER acked but DMA Tx is not enabled\n"); + break; + } + ishtp_hbm_dma_xfer_ack(dev, dma_xfer); + break; + + default: + dev_err(dev->devc, "unknown HBM: %u\n", + (unsigned int)ishtp_msg->hbm_cmd); + + break; + } +} + +/** + * bh_hbm_work_fn() - HBM work function + * @work: work struct + * + * Bottom half processing work function (instead of thread handler) + * for processing hbm messages + */ +void bh_hbm_work_fn(struct work_struct *work) +{ + unsigned long flags; + struct ishtp_device *dev; + unsigned char hbm[IPC_PAYLOAD_SIZE]; + + dev = container_of(work, struct ishtp_device, bh_hbm_work); + spin_lock_irqsave(&dev->rd_msg_spinlock, flags); + if (dev->rd_msg_fifo_head != dev->rd_msg_fifo_tail) { + memcpy(hbm, dev->rd_msg_fifo + dev->rd_msg_fifo_head, + IPC_PAYLOAD_SIZE); + dev->rd_msg_fifo_head = + (dev->rd_msg_fifo_head + IPC_PAYLOAD_SIZE) % + (RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE); + spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags); + ishtp_hbm_dispatch(dev, (struct ishtp_bus_message *)hbm); + } else { + spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags); + } +} + +/** + * recv_hbm() - Receive HBM message + * @dev: ISHTP device instance + * @ishtp_hdr: received bus message + * + * Receive and process ISHTP bus messages in ISR context. This will schedule + * work function to process message + */ +void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr) +{ + uint8_t rd_msg_buf[ISHTP_RD_MSG_BUF_SIZE]; + struct ishtp_bus_message *ishtp_msg = + (struct ishtp_bus_message *)rd_msg_buf; + unsigned long flags; + + dev->ops->ishtp_read(dev, rd_msg_buf, ishtp_hdr->length); + + /* Flow control - handle in place */ + if (ishtp_msg->hbm_cmd == ISHTP_FLOW_CONTROL_CMD) { + struct hbm_flow_control *flow_control = + (struct hbm_flow_control *)ishtp_msg; + struct ishtp_cl *cl = NULL; + unsigned long flags, tx_flags; + + spin_lock_irqsave(&dev->cl_list_lock, flags); + list_for_each_entry(cl, &dev->cl_list, link) { + if (cl->host_client_id == flow_control->host_addr && + cl->fw_client_id == + flow_control->fw_addr) { + /* + * NOTE: It's valid only for counting + * flow-control implementation to receive a + * FC in the middle of sending. Meanwhile not + * supported + */ + if (cl->ishtp_flow_ctrl_creds) + dev_err(dev->devc, + "recv extra FC from FW client %u (host client %u) (FC count was %d)\n", + (unsigned int)cl->fw_client_id, + (unsigned int)cl->host_client_id, + cl->ishtp_flow_ctrl_creds); + else { + ++cl->ishtp_flow_ctrl_creds; + ++cl->ishtp_flow_ctrl_cnt; + cl->last_ipc_acked = 1; + spin_lock_irqsave( + &cl->tx_list_spinlock, + tx_flags); + if (!list_empty(&cl->tx_list.list)) { + /* + * start sending the first msg + * = the callback function + */ + spin_unlock_irqrestore( + &cl->tx_list_spinlock, + tx_flags); + ishtp_cl_send_msg(dev, cl); + } else { + spin_unlock_irqrestore( + &cl->tx_list_spinlock, + tx_flags); + } + } + break; + } + } + spin_unlock_irqrestore(&dev->cl_list_lock, flags); + goto eoi; + } + + /* + * Some messages that are safe for ISR processing and important + * to be done "quickly" and in-order, go here + */ + if (ishtp_msg->hbm_cmd == CLIENT_CONNECT_RES_CMD || + ishtp_msg->hbm_cmd == CLIENT_DISCONNECT_RES_CMD || + ishtp_msg->hbm_cmd == CLIENT_DISCONNECT_REQ_CMD || + ishtp_msg->hbm_cmd == DMA_XFER) { + ishtp_hbm_dispatch(dev, ishtp_msg); + goto eoi; + } + + /* + * All other HBMs go here. + * We schedule HBMs for processing serially by using system wq, + * possibly there will be multiple HBMs scheduled at the same time. + */ + spin_lock_irqsave(&dev->rd_msg_spinlock, flags); + if ((dev->rd_msg_fifo_tail + IPC_PAYLOAD_SIZE) % + (RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE) == + dev->rd_msg_fifo_head) { + spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags); + dev_err(dev->devc, "BH buffer overflow, dropping HBM %u\n", + (unsigned int)ishtp_msg->hbm_cmd); + goto eoi; + } + memcpy(dev->rd_msg_fifo + dev->rd_msg_fifo_tail, ishtp_msg, + ishtp_hdr->length); + dev->rd_msg_fifo_tail = (dev->rd_msg_fifo_tail + IPC_PAYLOAD_SIZE) % + (RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE); + spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags); + schedule_work(&dev->bh_hbm_work); +eoi: + return; +} + +/** + * recv_fixed_cl_msg() - Receive fixed client message + * @dev: ISHTP device instance + * @ishtp_hdr: received bus message + * + * Receive and process ISHTP fixed client messages (address == 0) + * in ISR context + */ +void recv_fixed_cl_msg(struct ishtp_device *dev, + struct ishtp_msg_hdr *ishtp_hdr) +{ + uint8_t rd_msg_buf[ISHTP_RD_MSG_BUF_SIZE]; + + dev->print_log(dev, + "%s() got fixed client msg from client #%d\n", + __func__, ishtp_hdr->fw_addr); + dev->ops->ishtp_read(dev, rd_msg_buf, ishtp_hdr->length); + if (ishtp_hdr->fw_addr == ISHTP_SYSTEM_STATE_CLIENT_ADDR) { + struct ish_system_states_header *msg_hdr = + (struct ish_system_states_header *)rd_msg_buf; + if (msg_hdr->cmd == SYSTEM_STATE_SUBSCRIBE) + ishtp_send_resume(dev); + /* if FW request arrived here, the system is not suspended */ + else + dev_err(dev->devc, "unknown fixed client msg [%02X]\n", + msg_hdr->cmd); + } +} + +/** + * fix_cl_hdr() - Initialize fixed client header + * @hdr: message header + * @length: length of message + * @cl_addr: Client address + * + * Initialize message header for fixed client + */ +static inline void fix_cl_hdr(struct ishtp_msg_hdr *hdr, size_t length, + uint8_t cl_addr) +{ + hdr->host_addr = 0; + hdr->fw_addr = cl_addr; + hdr->length = length; + hdr->msg_complete = 1; + hdr->reserved = 0; +} + +/*** Suspend and resume notification ***/ + +static uint32_t current_state; +static uint32_t supported_states = 0 | SUSPEND_STATE_BIT; + +/** + * ishtp_send_suspend() - Send suspend message to FW + * @dev: ISHTP device instance + * + * Send suspend message to FW. This is useful for system freeze (non S3) case + */ +void ishtp_send_suspend(struct ishtp_device *dev) +{ + struct ishtp_msg_hdr ishtp_hdr; + struct ish_system_states_status state_status_msg; + const size_t len = sizeof(struct ish_system_states_status); + + fix_cl_hdr(&ishtp_hdr, len, ISHTP_SYSTEM_STATE_CLIENT_ADDR); + + memset(&state_status_msg, 0, len); + state_status_msg.hdr.cmd = SYSTEM_STATE_STATUS; + state_status_msg.supported_states = supported_states; + current_state |= SUSPEND_STATE_BIT; + dev->print_log(dev, "%s() sends SUSPEND notification\n", __func__); + state_status_msg.states_status = current_state; + + ishtp_write_message(dev, &ishtp_hdr, + (unsigned char *)&state_status_msg); +} +EXPORT_SYMBOL(ishtp_send_suspend); + +/** + * ishtp_send_resume() - Send resume message to FW + * @dev: ISHTP device instance + * + * Send resume message to FW. This is useful for system freeze (non S3) case + */ +void ishtp_send_resume(struct ishtp_device *dev) +{ + struct ishtp_msg_hdr ishtp_hdr; + struct ish_system_states_status state_status_msg; + const size_t len = sizeof(struct ish_system_states_status); + + fix_cl_hdr(&ishtp_hdr, len, ISHTP_SYSTEM_STATE_CLIENT_ADDR); + + memset(&state_status_msg, 0, len); + state_status_msg.hdr.cmd = SYSTEM_STATE_STATUS; + state_status_msg.supported_states = supported_states; + current_state &= ~SUSPEND_STATE_BIT; + dev->print_log(dev, "%s() sends RESUME notification\n", __func__); + state_status_msg.states_status = current_state; + + ishtp_write_message(dev, &ishtp_hdr, + (unsigned char *)&state_status_msg); +} +EXPORT_SYMBOL(ishtp_send_resume); + +/** + * ishtp_query_subscribers() - Send query subscribers message + * @dev: ISHTP device instance + * + * Send message to query subscribers + */ +void ishtp_query_subscribers(struct ishtp_device *dev) +{ + struct ishtp_msg_hdr ishtp_hdr; + struct ish_system_states_query_subscribers query_subscribers_msg; + const size_t len = sizeof(struct ish_system_states_query_subscribers); + + fix_cl_hdr(&ishtp_hdr, len, ISHTP_SYSTEM_STATE_CLIENT_ADDR); + + memset(&query_subscribers_msg, 0, len); + query_subscribers_msg.hdr.cmd = SYSTEM_STATE_QUERY_SUBSCRIBERS; + + ishtp_write_message(dev, &ishtp_hdr, + (unsigned char *)&query_subscribers_msg); +} diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.h b/drivers/hid/intel-ish-hid/ishtp/hbm.h new file mode 100644 index 000000000000..d96111cef7f8 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.h @@ -0,0 +1,321 @@ +/* + * ISHTP bus layer messages handling + * + * Copyright (c) 2003-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef _ISHTP_HBM_H_ +#define _ISHTP_HBM_H_ + +#include + +struct ishtp_device; +struct ishtp_msg_hdr; +struct ishtp_cl; + +/* + * Timeouts in Seconds + */ +#define ISHTP_INTEROP_TIMEOUT 7 /* Timeout on ready message */ + +#define ISHTP_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */ + +/* + * ISHTP Version + */ +#define HBM_MINOR_VERSION 0 +#define HBM_MAJOR_VERSION 1 + +/* Host bus message command opcode */ +#define ISHTP_HBM_CMD_OP_MSK 0x7f +/* Host bus message command RESPONSE */ +#define ISHTP_HBM_CMD_RES_MSK 0x80 + +/* + * ISHTP Bus Message Command IDs + */ +#define HOST_START_REQ_CMD 0x01 +#define HOST_START_RES_CMD 0x81 + +#define HOST_STOP_REQ_CMD 0x02 +#define HOST_STOP_RES_CMD 0x82 + +#define FW_STOP_REQ_CMD 0x03 + +#define HOST_ENUM_REQ_CMD 0x04 +#define HOST_ENUM_RES_CMD 0x84 + +#define HOST_CLIENT_PROPERTIES_REQ_CMD 0x05 +#define HOST_CLIENT_PROPERTIES_RES_CMD 0x85 + +#define CLIENT_CONNECT_REQ_CMD 0x06 +#define CLIENT_CONNECT_RES_CMD 0x86 + +#define CLIENT_DISCONNECT_REQ_CMD 0x07 +#define CLIENT_DISCONNECT_RES_CMD 0x87 + +#define ISHTP_FLOW_CONTROL_CMD 0x08 + +#define DMA_BUFFER_ALLOC_NOTIFY 0x11 +#define DMA_BUFFER_ALLOC_RESPONSE 0x91 + +#define DMA_XFER 0x12 +#define DMA_XFER_ACK 0x92 + +/* + * ISHTP Stop Reason + * used by hbm_host_stop_request.reason + */ +#define DRIVER_STOP_REQUEST 0x00 + +/* + * ISHTP BUS Interface Section + */ +struct ishtp_msg_hdr { + uint32_t fw_addr:8; + uint32_t host_addr:8; + uint32_t length:9; + uint32_t reserved:6; + uint32_t msg_complete:1; +} __packed; + +struct ishtp_bus_message { + uint8_t hbm_cmd; + uint8_t data[0]; +} __packed; + +/** + * struct hbm_cl_cmd - client specific host bus command + * CONNECT, DISCONNECT, and FlOW CONTROL + * + * @hbm_cmd - bus message command header + * @fw_addr - address of the fw client + * @host_addr - address of the client in the driver + * @data + */ +struct ishtp_hbm_cl_cmd { + uint8_t hbm_cmd; + uint8_t fw_addr; + uint8_t host_addr; + uint8_t data; +}; + +struct hbm_version { + uint8_t minor_version; + uint8_t major_version; +} __packed; + +struct hbm_host_version_request { + uint8_t hbm_cmd; + uint8_t reserved; + struct hbm_version host_version; +} __packed; + +struct hbm_host_version_response { + uint8_t hbm_cmd; + uint8_t host_version_supported; + struct hbm_version fw_max_version; +} __packed; + +struct hbm_host_stop_request { + uint8_t hbm_cmd; + uint8_t reason; + uint8_t reserved[2]; +} __packed; + +struct hbm_host_stop_response { + uint8_t hbm_cmd; + uint8_t reserved[3]; +} __packed; + +struct hbm_host_enum_request { + uint8_t hbm_cmd; + uint8_t reserved[3]; +} __packed; + +struct hbm_host_enum_response { + uint8_t hbm_cmd; + uint8_t reserved[3]; + uint8_t valid_addresses[32]; +} __packed; + +struct ishtp_client_properties { + uuid_le protocol_name; + uint8_t protocol_version; + uint8_t max_number_of_connections; + uint8_t fixed_address; + uint8_t single_recv_buf; + uint32_t max_msg_length; + uint8_t dma_hdr_len; +#define ISHTP_CLIENT_DMA_ENABLED 0x80 + uint8_t reserved4; + uint8_t reserved5; + uint8_t reserved6; +} __packed; + +struct hbm_props_request { + uint8_t hbm_cmd; + uint8_t address; + uint8_t reserved[2]; +} __packed; + +struct hbm_props_response { + uint8_t hbm_cmd; + uint8_t address; + uint8_t status; + uint8_t reserved[1]; + struct ishtp_client_properties client_properties; +} __packed; + +/** + * struct hbm_client_connect_request - connect/disconnect request + * + * @hbm_cmd - bus message command header + * @fw_addr - address of the fw client + * @host_addr - address of the client in the driver + * @reserved + */ +struct hbm_client_connect_request { + uint8_t hbm_cmd; + uint8_t fw_addr; + uint8_t host_addr; + uint8_t reserved; +} __packed; + +/** + * struct hbm_client_connect_response - connect/disconnect response + * + * @hbm_cmd - bus message command header + * @fw_addr - address of the fw client + * @host_addr - address of the client in the driver + * @status - status of the request + */ +struct hbm_client_connect_response { + uint8_t hbm_cmd; + uint8_t fw_addr; + uint8_t host_addr; + uint8_t status; +} __packed; + + +#define ISHTP_FC_MESSAGE_RESERVED_LENGTH 5 + +struct hbm_flow_control { + uint8_t hbm_cmd; + uint8_t fw_addr; + uint8_t host_addr; + uint8_t reserved[ISHTP_FC_MESSAGE_RESERVED_LENGTH]; +} __packed; + +struct dma_alloc_notify { + uint8_t hbm; + uint8_t status; + uint8_t reserved[2]; + uint32_t buf_size; + uint64_t buf_address; + /* [...] May come more size/address pairs */ +} __packed; + +struct dma_xfer_hbm { + uint8_t hbm; + uint8_t fw_client_id; + uint8_t host_client_id; + uint8_t reserved; + uint64_t msg_addr; + uint32_t msg_length; + uint32_t reserved2; +} __packed; + +/* System state */ +#define ISHTP_SYSTEM_STATE_CLIENT_ADDR 13 + +#define SYSTEM_STATE_SUBSCRIBE 0x1 +#define SYSTEM_STATE_STATUS 0x2 +#define SYSTEM_STATE_QUERY_SUBSCRIBERS 0x3 +#define SYSTEM_STATE_STATE_CHANGE_REQ 0x4 +/*indicates suspend and resume states*/ +#define SUSPEND_STATE_BIT (1<<1) + +struct ish_system_states_header { + uint32_t cmd; + uint32_t cmd_status; /*responses will have this set*/ +} __packed; + +struct ish_system_states_subscribe { + struct ish_system_states_header hdr; + uint32_t states; +} __packed; + +struct ish_system_states_status { + struct ish_system_states_header hdr; + uint32_t supported_states; + uint32_t states_status; +} __packed; + +struct ish_system_states_query_subscribers { + struct ish_system_states_header hdr; +} __packed; + +struct ish_system_states_state_change_req { + struct ish_system_states_header hdr; + uint32_t requested_states; + uint32_t states_status; +} __packed; + +/** + * enum ishtp_hbm_state - host bus message protocol state + * + * @ISHTP_HBM_IDLE : protocol not started + * @ISHTP_HBM_START : start request message was sent + * @ISHTP_HBM_ENUM_CLIENTS : enumeration request was sent + * @ISHTP_HBM_CLIENT_PROPERTIES : acquiring clients properties + */ +enum ishtp_hbm_state { + ISHTP_HBM_IDLE = 0, + ISHTP_HBM_START, + ISHTP_HBM_STARTED, + ISHTP_HBM_ENUM_CLIENTS, + ISHTP_HBM_CLIENT_PROPERTIES, + ISHTP_HBM_WORKING, + ISHTP_HBM_STOPPED, +}; + +static inline void ishtp_hbm_hdr(struct ishtp_msg_hdr *hdr, size_t length) +{ + hdr->host_addr = 0; + hdr->fw_addr = 0; + hdr->length = length; + hdr->msg_complete = 1; + hdr->reserved = 0; +} + +int ishtp_hbm_start_req(struct ishtp_device *dev); +int ishtp_hbm_start_wait(struct ishtp_device *dev); +int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev, + struct ishtp_cl *cl); +int ishtp_hbm_cl_disconnect_req(struct ishtp_device *dev, struct ishtp_cl *cl); +int ishtp_hbm_cl_connect_req(struct ishtp_device *dev, struct ishtp_cl *cl); +void ishtp_hbm_enum_clients_req(struct ishtp_device *dev); +void bh_hbm_work_fn(struct work_struct *work); +void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr); +void recv_fixed_cl_msg(struct ishtp_device *dev, + struct ishtp_msg_hdr *ishtp_hdr); +void ishtp_hbm_dispatch(struct ishtp_device *dev, + struct ishtp_bus_message *hdr); + +void ishtp_query_subscribers(struct ishtp_device *dev); + +/* Exported I/F */ +void ishtp_send_suspend(struct ishtp_device *dev); +void ishtp_send_resume(struct ishtp_device *dev); + +#endif /* _ISHTP_HBM_H_ */ diff --git a/drivers/hid/intel-ish-hid/ishtp/init.c b/drivers/hid/intel-ish-hid/ishtp/init.c new file mode 100644 index 000000000000..ac364418e17c --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/init.c @@ -0,0 +1,115 @@ +/* + * Initialization protocol for ISHTP driver + * + * Copyright (c) 2003-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include "ishtp-dev.h" +#include "hbm.h" +#include "client.h" + +/** + * ishtp_dev_state_str() -Convert to string format + * @state: state to convert + * + * Convert state to string for prints + * + * Return: character pointer to converted string + */ +const char *ishtp_dev_state_str(int state) +{ + switch (state) { + case ISHTP_DEV_INITIALIZING: + return "INITIALIZING"; + case ISHTP_DEV_INIT_CLIENTS: + return "INIT_CLIENTS"; + case ISHTP_DEV_ENABLED: + return "ENABLED"; + case ISHTP_DEV_RESETTING: + return "RESETTING"; + case ISHTP_DEV_DISABLED: + return "DISABLED"; + case ISHTP_DEV_POWER_DOWN: + return "POWER_DOWN"; + case ISHTP_DEV_POWER_UP: + return "POWER_UP"; + default: + return "unknown"; + } +} + +/** + * ishtp_device_init() - ishtp device init + * @dev: ISHTP device instance + * + * After ISHTP device is alloacted, this function is used to initialize + * each field which includes spin lock, work struct and lists + */ +void ishtp_device_init(struct ishtp_device *dev) +{ + dev->dev_state = ISHTP_DEV_INITIALIZING; + INIT_LIST_HEAD(&dev->cl_list); + INIT_LIST_HEAD(&dev->device_list); + dev->rd_msg_fifo_head = 0; + dev->rd_msg_fifo_tail = 0; + spin_lock_init(&dev->rd_msg_spinlock); + + init_waitqueue_head(&dev->wait_hbm_recvd_msg); + spin_lock_init(&dev->read_list_spinlock); + spin_lock_init(&dev->device_lock); + spin_lock_init(&dev->device_list_lock); + spin_lock_init(&dev->cl_list_lock); + spin_lock_init(&dev->fw_clients_lock); + INIT_WORK(&dev->bh_hbm_work, bh_hbm_work_fn); + + bitmap_zero(dev->host_clients_map, ISHTP_CLIENTS_MAX); + dev->open_handle_count = 0; + + /* + * Reserving client ID 0 for ISHTP Bus Message communications + */ + bitmap_set(dev->host_clients_map, 0, 1); + + INIT_LIST_HEAD(&dev->read_list.list); + +} +EXPORT_SYMBOL(ishtp_device_init); + +/** + * ishtp_start() - Start ISH processing + * @dev: ISHTP device instance + * + * Start ISHTP processing by sending query subscriber message + * + * Return: 0 on success else -ENODEV + */ +int ishtp_start(struct ishtp_device *dev) +{ + if (ishtp_hbm_start_wait(dev)) { + dev_err(dev->devc, "HBM haven't started"); + goto err; + } + + /* suspend & resume notification - send QUERY_SUBSCRIBERS msg */ + ishtp_query_subscribers(dev); + + return 0; +err: + dev_err(dev->devc, "link layer initialization failed.\n"); + dev->dev_state = ISHTP_DEV_DISABLED; + return -ENODEV; +} +EXPORT_SYMBOL(ishtp_start); diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h new file mode 100644 index 000000000000..a94f9a8a96a0 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h @@ -0,0 +1,277 @@ +/* + * Most ISHTP provider device and ISHTP logic declarations + * + * Copyright (c) 2003-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef _ISHTP_DEV_H_ +#define _ISHTP_DEV_H_ + +#include +#include +#include "bus.h" +#include "hbm.h" + +#define IPC_PAYLOAD_SIZE 128 +#define ISHTP_RD_MSG_BUF_SIZE IPC_PAYLOAD_SIZE +#define IPC_FULL_MSG_SIZE 132 + +/* Number of messages to be held in ISR->BH FIFO */ +#define RD_INT_FIFO_SIZE 64 + +/* + * Number of IPC messages to be held in Tx FIFO, to be sent by ISR - + * Tx complete interrupt or RX_COMPLETE handler + */ +#define IPC_TX_FIFO_SIZE 512 + +/* + * Number of Maximum ISHTP Clients + */ +#define ISHTP_CLIENTS_MAX 256 + +/* + * Number of File descriptors/handles + * that can be opened to the driver. + * + * Limit to 255: 256 Total Clients + * minus internal client for ISHTP Bus Messages + */ +#define ISHTP_MAX_OPEN_HANDLE_COUNT (ISHTP_CLIENTS_MAX - 1) + +/* Internal Clients Number */ +#define ISHTP_HOST_CLIENT_ID_ANY (-1) +#define ISHTP_HBM_HOST_CLIENT_ID 0 + +#define MAX_DMA_DELAY 20 + +/* ISHTP device states */ +enum ishtp_dev_state { + ISHTP_DEV_INITIALIZING = 0, + ISHTP_DEV_INIT_CLIENTS, + ISHTP_DEV_ENABLED, + ISHTP_DEV_RESETTING, + ISHTP_DEV_DISABLED, + ISHTP_DEV_POWER_DOWN, + ISHTP_DEV_POWER_UP +}; +const char *ishtp_dev_state_str(int state); + +struct ishtp_cl; + +/** + * struct ishtp_fw_client - representation of fw client + * + * @props - client properties + * @client_id - fw client id + */ +struct ishtp_fw_client { + struct ishtp_client_properties props; + uint8_t client_id; +}; + +/** + * struct ishtp_msg_data - ISHTP message data struct + * @size: Size of data in the *data + * @data: Pointer to data + */ +struct ishtp_msg_data { + uint32_t size; + unsigned char *data; +}; + +/* + * struct ishtp_cl_rb - request block structure + * @list: Link to list members + * @cl: ISHTP client instance + * @buffer: message header + * @buf_idx: Index into buffer + * @read_time: unused at this time + */ +struct ishtp_cl_rb { + struct list_head list; + struct ishtp_cl *cl; + struct ishtp_msg_data buffer; + unsigned long buf_idx; + unsigned long read_time; +}; + +/* + * Control info for IPC messages ISHTP/IPC sending FIFO - + * list with inline data buffer + * This structure will be filled with parameters submitted + * by the caller glue layer + * 'buf' may be pointing to the external buffer or to 'inline_data' + * 'offset' will be initialized to 0 by submitting + * + * 'ipc_send_compl' is intended for use by clients that send fragmented + * messages. When a fragment is sent down to IPC msg regs, + * it will be called. + * If it has more fragments to send, it will do it. With last fragment + * it will send appropriate ISHTP "message-complete" flag. + * It will remove the outstanding message + * (mark outstanding buffer as available). + * If counting flow control is in work and there are more flow control + * credits, it can put the next client message queued in cl. + * structure for IPC processing. + * + */ +struct wr_msg_ctl_info { + /* Will be called with 'ipc_send_compl_prm' as parameter */ + void (*ipc_send_compl)(void *); + + void *ipc_send_compl_prm; + size_t length; + struct list_head link; + unsigned char inline_data[IPC_FULL_MSG_SIZE]; +}; + +/* + * The ISHTP layer talks to hardware IPC message using the following + * callbacks + */ +struct ishtp_hw_ops { + int (*hw_reset)(struct ishtp_device *dev); + int (*ipc_reset)(struct ishtp_device *dev); + uint32_t (*ipc_get_header)(struct ishtp_device *dev, int length, + int busy); + int (*write)(struct ishtp_device *dev, + void (*ipc_send_compl)(void *), void *ipc_send_compl_prm, + unsigned char *msg, int length); + uint32_t (*ishtp_read_hdr)(const struct ishtp_device *dev); + int (*ishtp_read)(struct ishtp_device *dev, unsigned char *buffer, + unsigned long buffer_length); + uint32_t (*get_fw_status)(struct ishtp_device *dev); + void (*sync_fw_clock)(struct ishtp_device *dev); +}; + +/** + * struct ishtp_device - ISHTP private device struct + */ +struct ishtp_device { + struct device *devc; /* pointer to lowest device */ + struct pci_dev *pdev; /* PCI device to get device ids */ + + /* waitq for waiting for suspend response */ + wait_queue_head_t suspend_wait; + bool suspend_flag; /* Suspend is active */ + + /* waitq for waiting for resume response */ + wait_queue_head_t resume_wait; + bool resume_flag; /*Resume is active */ + + /* + * lock for the device, for everything that doesn't have + * a dedicated spinlock + */ + spinlock_t device_lock; + + bool recvd_hw_ready; + struct hbm_version version; + int transfer_path; /* Choice of transfer path: IPC or DMA */ + + /* ishtp device states */ + enum ishtp_dev_state dev_state; + enum ishtp_hbm_state hbm_state; + + /* driver read queue */ + struct ishtp_cl_rb read_list; + spinlock_t read_list_spinlock; + + /* list of ishtp_cl's */ + struct list_head cl_list; + spinlock_t cl_list_lock; + long open_handle_count; + + /* List of bus devices */ + struct list_head device_list; + spinlock_t device_list_lock; + + /* waiting queues for receive message from FW */ + wait_queue_head_t wait_hw_ready; + wait_queue_head_t wait_hbm_recvd_msg; + + /* FIFO for input messages for BH processing */ + unsigned char rd_msg_fifo[RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE]; + unsigned int rd_msg_fifo_head, rd_msg_fifo_tail; + spinlock_t rd_msg_spinlock; + struct work_struct bh_hbm_work; + + /* IPC write queue */ + struct wr_msg_ctl_info wr_processing_list_head, wr_free_list_head; + /* For both processing list and free list */ + spinlock_t wr_processing_spinlock; + + spinlock_t out_ipc_spinlock; + + struct ishtp_fw_client *fw_clients; /*Note:memory has to be allocated*/ + DECLARE_BITMAP(fw_clients_map, ISHTP_CLIENTS_MAX); + DECLARE_BITMAP(host_clients_map, ISHTP_CLIENTS_MAX); + uint8_t fw_clients_num; + uint8_t fw_client_presentation_num; + uint8_t fw_client_index; + spinlock_t fw_clients_lock; + + /* TX DMA buffers and slots */ + int ishtp_host_dma_enabled; + void *ishtp_host_dma_tx_buf; + unsigned int ishtp_host_dma_tx_buf_size; + uint64_t ishtp_host_dma_tx_buf_phys; + int ishtp_dma_num_slots; + + /* map of 4k blocks in Tx dma buf: 0-free, 1-used */ + uint8_t *ishtp_dma_tx_map; + spinlock_t ishtp_dma_tx_lock; + + /* RX DMA buffers and slots */ + void *ishtp_host_dma_rx_buf; + unsigned int ishtp_host_dma_rx_buf_size; + uint64_t ishtp_host_dma_rx_buf_phys; + + /* Dump to trace buffers if enabled*/ + void (*print_log)(struct ishtp_device *dev, char *format, ...); + + /* Debug stats */ + unsigned int ipc_rx_cnt; + unsigned long long ipc_rx_bytes_cnt; + unsigned int ipc_tx_cnt; + unsigned long long ipc_tx_bytes_cnt; + + const struct ishtp_hw_ops *ops; + size_t mtu; + uint32_t ishtp_msg_hdr; + char hw[0] __aligned(sizeof(void *)); +}; + +static inline unsigned long ishtp_secs_to_jiffies(unsigned long sec) +{ + return msecs_to_jiffies(sec * MSEC_PER_SEC); +} + +/* + * Register Access Function + */ +static inline int ish_ipc_reset(struct ishtp_device *dev) +{ + return dev->ops->ipc_reset(dev); +} + +static inline int ish_hw_reset(struct ishtp_device *dev) +{ + return dev->ops->hw_reset(dev); +} + +/* Exported function */ +void ishtp_device_init(struct ishtp_device *dev); +int ishtp_start(struct ishtp_device *dev); + +#endif /*_ISHTP_DEV_H_*/ -- cgit v1.2.3 From ae02e5d40d5f829c589412c6253f925e35cf7a22 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 7 Aug 2016 02:25:35 -0700 Subject: HID: intel-ish-hid: ipc layer This layer is responsible for - Enumerating over PCI bus - Inform FW about host readiness - Provide HW interface to transport layer for control and messages - Interrupt handling and routing Original-author: Daniel Drubin Reviewed-and-tested-by: Ooi, Joyce Tested-by: Grant Likely Tested-by: Rann Bar-On Tested-by: Atri Bhattacharya Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/Makefile | 6 + drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h | 220 +++++++ drivers/hid/intel-ish-hid/ipc/hw-ish.h | 71 +++ drivers/hid/intel-ish-hid/ipc/ipc.c | 882 ++++++++++++++++++++++++++++ drivers/hid/intel-ish-hid/ipc/pci-ish.c | 333 +++++++++++ drivers/hid/intel-ish-hid/ipc/utils.h | 64 ++ include/trace/events/intel_ish.h | 30 + 7 files changed, 1606 insertions(+) create mode 100644 drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h create mode 100644 drivers/hid/intel-ish-hid/ipc/hw-ish.h create mode 100644 drivers/hid/intel-ish-hid/ipc/ipc.c create mode 100644 drivers/hid/intel-ish-hid/ipc/pci-ish.c create mode 100644 drivers/hid/intel-ish-hid/ipc/utils.h create mode 100644 include/trace/events/intel_ish.h diff --git a/drivers/hid/intel-ish-hid/Makefile b/drivers/hid/intel-ish-hid/Makefile index 7b32d49624ae..ab626d8f7bb8 100644 --- a/drivers/hid/intel-ish-hid/Makefile +++ b/drivers/hid/intel-ish-hid/Makefile @@ -10,3 +10,9 @@ intel-ishtp-objs += ishtp/client.o intel-ishtp-objs += ishtp/bus.o intel-ishtp-objs += ishtp/dma-if.o intel-ishtp-objs += ishtp/client-buffers.o + +obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o +intel-ish-ipc-objs := ipc/ipc.o +intel-ish-ipc-objs += ipc/pci-ish.o + +ccflags-y += -Idrivers/hid/intel-ish-hid/ishtp diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h b/drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h new file mode 100644 index 000000000000..ab68afcba2a2 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h @@ -0,0 +1,220 @@ +/* + * ISH registers definitions + * + * Copyright (c) 2012-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef _ISHTP_ISH_REGS_H_ +#define _ISHTP_ISH_REGS_H_ + + +/*** IPC PCI Offsets and sizes ***/ +/* ISH IPC Base Address */ +#define IPC_REG_BASE 0x0000 +/* Peripheral Interrupt Status Register */ +#define IPC_REG_PISR_CHV_AB (IPC_REG_BASE + 0x00) +/* Peripheral Interrupt Mask Register */ +#define IPC_REG_PIMR_CHV_AB (IPC_REG_BASE + 0x04) +/*BXT, CHV_K0*/ +/*Peripheral Interrupt Status Register */ +#define IPC_REG_PISR_BXT (IPC_REG_BASE + 0x0C) +/*Peripheral Interrupt Mask Register */ +#define IPC_REG_PIMR_BXT (IPC_REG_BASE + 0x08) +/***********************************/ +/* ISH Host Firmware status Register */ +#define IPC_REG_ISH_HOST_FWSTS (IPC_REG_BASE + 0x34) +/* Host Communication Register */ +#define IPC_REG_HOST_COMM (IPC_REG_BASE + 0x38) +/* Reset register */ +#define IPC_REG_ISH_RST (IPC_REG_BASE + 0x44) + +/* Inbound doorbell register Host to ISH */ +#define IPC_REG_HOST2ISH_DRBL (IPC_REG_BASE + 0x48) +/* Outbound doorbell register ISH to Host */ +#define IPC_REG_ISH2HOST_DRBL (IPC_REG_BASE + 0x54) +/* ISH to HOST message registers */ +#define IPC_REG_ISH2HOST_MSG (IPC_REG_BASE + 0x60) +/* HOST to ISH message registers */ +#define IPC_REG_HOST2ISH_MSG (IPC_REG_BASE + 0xE0) +/* REMAP2 to enable DMA (D3 RCR) */ +#define IPC_REG_ISH_RMP2 (IPC_REG_BASE + 0x368) + +#define IPC_REG_MAX (IPC_REG_BASE + 0x400) + +/*** register bits - HISR ***/ +/* bit corresponds HOST2ISH interrupt in PISR and PIMR registers */ +#define IPC_INT_HOST2ISH_BIT (1<<0) +/***********************************/ +/*CHV_A0, CHV_B0*/ +/* bit corresponds ISH2HOST interrupt in PISR and PIMR registers */ +#define IPC_INT_ISH2HOST_BIT_CHV_AB (1<<3) +/*BXT, CHV_K0*/ +/* bit corresponds ISH2HOST interrupt in PISR and PIMR registers */ +#define IPC_INT_ISH2HOST_BIT_BXT (1<<0) +/***********************************/ + +/* bit corresponds ISH2HOST busy clear interrupt in PIMR register */ +#define IPC_INT_ISH2HOST_CLR_MASK_BIT (1<<11) + +/* offset of ISH2HOST busy clear interrupt in IPC_BUSY_CLR register */ +#define IPC_INT_ISH2HOST_CLR_OFFS (0) + +/* bit corresponds ISH2HOST busy clear interrupt in IPC_BUSY_CLR register */ +#define IPC_INT_ISH2HOST_CLR_BIT (1<> IPC_HEADER_LENGTH_OFFSET)&IPC_HEADER_LENGTH_MASK) +#define IPC_HEADER_GET_PROTOCOL(drbl_reg) \ + (((drbl_reg) >> IPC_HEADER_PROTOCOL_OFFSET)&IPC_HEADER_PROTOCOL_MASK) +#define IPC_HEADER_GET_MNG_CMD(drbl_reg) \ + (((drbl_reg) >> IPC_HEADER_MNG_CMD_OFFSET)&IPC_HEADER_MNG_CMD_MASK) + +#define IPC_IS_BUSY(drbl_reg) \ + (((drbl_reg)&IPC_DRBL_BUSY_BIT) == ((uint32_t)IPC_DRBL_BUSY_BIT)) + +/***********************************/ +/*CHV_A0, CHV_B0*/ +#define IPC_INT_FROM_ISH_TO_HOST_CHV_AB(drbl_reg) \ + (((drbl_reg)&IPC_INT_ISH2HOST_BIT_CHV_AB) == \ + ((u32)IPC_INT_ISH2HOST_BIT_CHV_AB)) +/*BXT, CHV_K0*/ +#define IPC_INT_FROM_ISH_TO_HOST_BXT(drbl_reg) \ + (((drbl_reg)&IPC_INT_ISH2HOST_BIT_BXT) == \ + ((u32)IPC_INT_ISH2HOST_BIT_BXT)) +/***********************************/ + +#define IPC_BUILD_HEADER(length, protocol, busy) \ + (((busy)< +#include +#include "hw-ish-regs.h" +#include "ishtp-dev.h" + +#define CHV_DEVICE_ID 0x22D8 +#define BXT_Ax_DEVICE_ID 0x0AA2 +#define BXT_Bx_DEVICE_ID 0x1AA2 +#define APL_Ax_DEVICE_ID 0x5AA2 +#define SPT_Ax_DEVICE_ID 0x9D35 + +#define REVISION_ID_CHT_A0 0x6 +#define REVISION_ID_CHT_Ax_SI 0x0 +#define REVISION_ID_CHT_Bx_SI 0x10 +#define REVISION_ID_CHT_Kx_SI 0x20 +#define REVISION_ID_CHT_Dx_SI 0x30 +#define REVISION_ID_CHT_B0 0xB0 +#define REVISION_ID_SI_MASK 0x70 + +struct ipc_rst_payload_type { + uint16_t reset_id; + uint16_t reserved; +}; + +struct time_sync_format { + uint8_t ts1_source; + uint8_t ts2_source; + uint16_t reserved; +} __packed; + +struct ipc_time_update_msg { + uint64_t primary_host_time; + struct time_sync_format sync_info; + uint64_t secondary_host_time; +} __packed; + +enum { + HOST_UTC_TIME_USEC = 0, + HOST_SYSTEM_TIME_USEC = 1 +}; + +struct ish_hw { + void __iomem *mem_addr; +}; + +#define to_ish_hw(dev) (struct ish_hw *)((dev)->hw) + +irqreturn_t ish_irq_handler(int irq, void *dev_id); +struct ishtp_device *ish_dev_init(struct pci_dev *pdev); +int ish_hw_start(struct ishtp_device *dev); +void ish_device_disable(struct ishtp_device *dev); + +#endif /* _ISHTP_HW_ISH_H_ */ diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c new file mode 100644 index 000000000000..851029bf17bc --- /dev/null +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -0,0 +1,882 @@ +/* + * H/W layer of ISHTP provider device (ISH) + * + * Copyright (c) 2014-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include "client.h" +#include "hw-ish.h" +#include "utils.h" +#include "hbm.h" + +/* For FW reset flow */ +static struct work_struct fw_reset_work; +static struct ishtp_device *ishtp_dev; + +/** + * ish_reg_read() - Read register + * @dev: ISHTP device pointer + * @offset: Register offset + * + * Read 32 bit register at a given offset + * + * Return: Read register value + */ +static inline uint32_t ish_reg_read(const struct ishtp_device *dev, + unsigned long offset) +{ + struct ish_hw *hw = to_ish_hw(dev); + + return readl(hw->mem_addr + offset); +} + +/** + * ish_reg_write() - Write register + * @dev: ISHTP device pointer + * @offset: Register offset + * @value: Value to write + * + * Writes 32 bit register at a give offset + */ +static inline void ish_reg_write(struct ishtp_device *dev, + unsigned long offset, + uint32_t value) +{ + struct ish_hw *hw = to_ish_hw(dev); + + writel(value, hw->mem_addr + offset); +} + +/** + * _ish_read_fw_sts_reg() - Read FW status register + * @dev: ISHTP device pointer + * + * Read FW status register + * + * Return: Read register value + */ +static inline uint32_t _ish_read_fw_sts_reg(struct ishtp_device *dev) +{ + return ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); +} + +/** + * check_generated_interrupt() - Check if ISH interrupt + * @dev: ISHTP device pointer + * + * Check if an interrupt was generated for ISH + * + * Return: Read true or false + */ +static bool check_generated_interrupt(struct ishtp_device *dev) +{ + bool interrupt_generated = true; + uint32_t pisr_val = 0; + + if (dev->pdev->device == CHV_DEVICE_ID) { + pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB); + interrupt_generated = + IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val); + } else { + pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT); + interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_BXT(pisr_val); + } + + return interrupt_generated; +} + +/** + * ish_is_input_ready() - Check if FW ready for RX + * @dev: ISHTP device pointer + * + * Check if ISH FW is ready for receiving data + * + * Return: Read true or false + */ +static bool ish_is_input_ready(struct ishtp_device *dev) +{ + uint32_t doorbell_val; + + doorbell_val = ish_reg_read(dev, IPC_REG_HOST2ISH_DRBL); + return !IPC_IS_BUSY(doorbell_val); +} + +/** + * set_host_ready() - Indicate host ready + * @dev: ISHTP device pointer + * + * Set host ready indication to FW + */ +static void set_host_ready(struct ishtp_device *dev) +{ + if (dev->pdev->device == CHV_DEVICE_ID) { + if (dev->pdev->revision == REVISION_ID_CHT_A0 || + (dev->pdev->revision & REVISION_ID_SI_MASK) == + REVISION_ID_CHT_Ax_SI) + ish_reg_write(dev, IPC_REG_HOST_COMM, 0x81); + else if (dev->pdev->revision == REVISION_ID_CHT_B0 || + (dev->pdev->revision & REVISION_ID_SI_MASK) == + REVISION_ID_CHT_Bx_SI || + (dev->pdev->revision & REVISION_ID_SI_MASK) == + REVISION_ID_CHT_Kx_SI || + (dev->pdev->revision & REVISION_ID_SI_MASK) == + REVISION_ID_CHT_Dx_SI) { + uint32_t host_comm_val; + + host_comm_val = ish_reg_read(dev, IPC_REG_HOST_COMM); + host_comm_val |= IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81; + ish_reg_write(dev, IPC_REG_HOST_COMM, host_comm_val); + } + } else { + uint32_t host_pimr_val; + + host_pimr_val = ish_reg_read(dev, IPC_REG_PIMR_BXT); + host_pimr_val |= IPC_PIMR_INT_EN_BIT_BXT; + /* + * disable interrupt generated instead of + * RX_complete_msg + */ + host_pimr_val &= ~IPC_HOST2ISH_BUSYCLEAR_MASK_BIT; + + ish_reg_write(dev, IPC_REG_PIMR_BXT, host_pimr_val); + } +} + +/** + * ishtp_fw_is_ready() - Check if FW ready + * @dev: ISHTP device pointer + * + * Check if ISH FW is ready + * + * Return: Read true or false + */ +static bool ishtp_fw_is_ready(struct ishtp_device *dev) +{ + uint32_t ish_status = _ish_read_fw_sts_reg(dev); + + return IPC_IS_ISH_ILUP(ish_status) && + IPC_IS_ISH_ISHTP_READY(ish_status); +} + +/** + * ish_set_host_rdy() - Indicate host ready + * @dev: ISHTP device pointer + * + * Set host ready indication to FW + */ +static void ish_set_host_rdy(struct ishtp_device *dev) +{ + uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); + + IPC_SET_HOST_READY(host_status); + ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); +} + +/** + * ish_clr_host_rdy() - Indicate host not ready + * @dev: ISHTP device pointer + * + * Send host not ready indication to FW + */ +static void ish_clr_host_rdy(struct ishtp_device *dev) +{ + uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); + + IPC_CLEAR_HOST_READY(host_status); + ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); +} + +/** + * _ishtp_read_hdr() - Read message header + * @dev: ISHTP device pointer + * + * Read header of 32bit length + * + * Return: Read register value + */ +static uint32_t _ishtp_read_hdr(const struct ishtp_device *dev) +{ + return ish_reg_read(dev, IPC_REG_ISH2HOST_MSG); +} + +/** + * _ishtp_read - Read message + * @dev: ISHTP device pointer + * @buffer: message buffer + * @buffer_length: length of message buffer + * + * Read message from FW + * + * Return: Always 0 + */ +static int _ishtp_read(struct ishtp_device *dev, unsigned char *buffer, + unsigned long buffer_length) +{ + uint32_t i; + uint32_t *r_buf = (uint32_t *)buffer; + uint32_t msg_offs; + + msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr); + for (i = 0; i < buffer_length; i += sizeof(uint32_t)) + *r_buf++ = ish_reg_read(dev, msg_offs + i); + + return 0; +} + +/** + * write_ipc_from_queue() - try to write ipc msg from Tx queue to device + * @dev: ishtp device pointer + * + * Check if DRBL is cleared. if it is - write the first IPC msg, then call + * the callback function (unless it's NULL) + * + * Return: 0 for success else failure code + */ +static int write_ipc_from_queue(struct ishtp_device *dev) +{ + struct wr_msg_ctl_info *ipc_link; + unsigned long length; + unsigned long rem; + unsigned long flags; + uint32_t doorbell_val; + uint32_t *r_buf; + uint32_t reg_addr; + int i; + void (*ipc_send_compl)(void *); + void *ipc_send_compl_prm; + static int out_ipc_locked; + unsigned long out_ipc_flags; + + if (dev->dev_state == ISHTP_DEV_DISABLED) + return -EINVAL; + + spin_lock_irqsave(&dev->out_ipc_spinlock, out_ipc_flags); + if (out_ipc_locked) { + spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); + return -EBUSY; + } + out_ipc_locked = 1; + if (!ish_is_input_ready(dev)) { + out_ipc_locked = 0; + spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); + return -EBUSY; + } + spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); + + spin_lock_irqsave(&dev->wr_processing_spinlock, flags); + /* + * if tx send list is empty - return 0; + * may happen, as RX_COMPLETE handler doesn't check list emptiness. + */ + if (list_empty(&dev->wr_processing_list_head.link)) { + spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); + out_ipc_locked = 0; + return 0; + } + + ipc_link = list_entry(dev->wr_processing_list_head.link.next, + struct wr_msg_ctl_info, link); + /* first 4 bytes of the data is the doorbell value (IPC header) */ + length = ipc_link->length - sizeof(uint32_t); + doorbell_val = *(uint32_t *)ipc_link->inline_data; + r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t)); + + /* If sending MNG_SYNC_FW_CLOCK, update clock again */ + if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG && + IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) { + struct timespec ts_system; + struct timeval tv_utc; + uint64_t usec_system, usec_utc; + struct ipc_time_update_msg time_update; + struct time_sync_format ts_format; + + get_monotonic_boottime(&ts_system); + do_gettimeofday(&tv_utc); + usec_system = (timespec_to_ns(&ts_system)) / NSEC_PER_USEC; + usec_utc = (uint64_t)tv_utc.tv_sec * 1000000 + + ((uint32_t)tv_utc.tv_usec); + ts_format.ts1_source = HOST_SYSTEM_TIME_USEC; + ts_format.ts2_source = HOST_UTC_TIME_USEC; + + time_update.primary_host_time = usec_system; + time_update.secondary_host_time = usec_utc; + time_update.sync_info = ts_format; + + memcpy(r_buf, &time_update, + sizeof(struct ipc_time_update_msg)); + } + + for (i = 0, reg_addr = IPC_REG_HOST2ISH_MSG; i < length >> 2; i++, + reg_addr += 4) + ish_reg_write(dev, reg_addr, r_buf[i]); + + rem = length & 0x3; + if (rem > 0) { + uint32_t reg = 0; + + memcpy(®, &r_buf[length >> 2], rem); + ish_reg_write(dev, reg_addr, reg); + } + /* Flush writes to msg registers and doorbell */ + ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); + + /* Update IPC counters */ + ++dev->ipc_tx_cnt; + dev->ipc_tx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); + + ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val); + out_ipc_locked = 0; + + ipc_send_compl = ipc_link->ipc_send_compl; + ipc_send_compl_prm = ipc_link->ipc_send_compl_prm; + list_del_init(&ipc_link->link); + list_add_tail(&ipc_link->link, &dev->wr_free_list_head.link); + spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); + + /* + * callback will be called out of spinlock, + * after ipc_link returned to free list + */ + if (ipc_send_compl) + ipc_send_compl(ipc_send_compl_prm); + + return 0; +} + +/** + * write_ipc_to_queue() - write ipc msg to Tx queue + * @dev: ishtp device instance + * @ipc_send_compl: Send complete callback + * @ipc_send_compl_prm: Parameter to send in complete callback + * @msg: Pointer to message + * @length: Length of message + * + * Recived msg with IPC (and upper protocol) header and add it to the device + * Tx-to-write list then try to send the first IPC waiting msg + * (if DRBL is cleared) + * This function returns negative value for failure (means free list + * is empty, or msg too long) and 0 for success. + * + * Return: 0 for success else failure code + */ +static int write_ipc_to_queue(struct ishtp_device *dev, + void (*ipc_send_compl)(void *), void *ipc_send_compl_prm, + unsigned char *msg, int length) +{ + struct wr_msg_ctl_info *ipc_link; + unsigned long flags; + + if (length > IPC_FULL_MSG_SIZE) + return -EMSGSIZE; + + spin_lock_irqsave(&dev->wr_processing_spinlock, flags); + if (list_empty(&dev->wr_free_list_head.link)) { + spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); + return -ENOMEM; + } + ipc_link = list_entry(dev->wr_free_list_head.link.next, + struct wr_msg_ctl_info, link); + list_del_init(&ipc_link->link); + + ipc_link->ipc_send_compl = ipc_send_compl; + ipc_link->ipc_send_compl_prm = ipc_send_compl_prm; + ipc_link->length = length; + memcpy(ipc_link->inline_data, msg, length); + + list_add_tail(&ipc_link->link, &dev->wr_processing_list_head.link); + spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); + + write_ipc_from_queue(dev); + + return 0; +} + +/** + * ipc_send_mng_msg() - Send management message + * @dev: ishtp device instance + * @msg_code: Message code + * @msg: Pointer to message + * @size: Length of message + * + * Send management message to FW + * + * Return: 0 for success else failure code + */ +static int ipc_send_mng_msg(struct ishtp_device *dev, uint32_t msg_code, + void *msg, size_t size) +{ + unsigned char ipc_msg[IPC_FULL_MSG_SIZE]; + uint32_t drbl_val = IPC_BUILD_MNG_MSG(msg_code, size); + + memcpy(ipc_msg, &drbl_val, sizeof(uint32_t)); + memcpy(ipc_msg + sizeof(uint32_t), msg, size); + return write_ipc_to_queue(dev, NULL, NULL, ipc_msg, + sizeof(uint32_t) + size); +} + +/** + * ish_fw_reset_handler() - FW reset handler + * @dev: ishtp device pointer + * + * Handle FW reset + * + * Return: 0 for success else failure code + */ +static int ish_fw_reset_handler(struct ishtp_device *dev) +{ + uint32_t reset_id; + unsigned long flags; + struct wr_msg_ctl_info *processing, *next; + + /* Read reset ID */ + reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; + + /* Clear IPC output queue */ + spin_lock_irqsave(&dev->wr_processing_spinlock, flags); + list_for_each_entry_safe(processing, next, + &dev->wr_processing_list_head.link, link) { + list_del(&processing->link); + list_add_tail(&processing->link, &dev->wr_free_list_head.link); + } + spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); + + /* ISHTP notification in IPC_RESET */ + ishtp_reset_handler(dev); + + if (!ish_is_input_ready(dev)) + timed_wait_for_timeout(WAIT_FOR_SEND_SLICE, + ish_is_input_ready(dev), (2 * HZ)); + + /* ISH FW is dead */ + if (!ish_is_input_ready(dev)) + return -EPIPE; + /* + * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending + * RESET_NOTIFY_ACK - FW will be checking for it + */ + ish_set_host_rdy(dev); + /* Send RESET_NOTIFY_ACK (with reset_id) */ + ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, + sizeof(uint32_t)); + + /* Wait for ISH FW'es ILUP and ISHTP_READY */ + timed_wait_for_timeout(WAIT_FOR_SEND_SLICE, ishtp_fw_is_ready(dev), + (2 * HZ)); + if (!ishtp_fw_is_ready(dev)) { + /* ISH FW is dead */ + uint32_t ish_status; + + ish_status = _ish_read_fw_sts_reg(dev); + dev_err(dev->devc, + "[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n", + ish_status); + return -ENODEV; + } + return 0; +} + +/** + * ish_fw_reset_work_fn() - FW reset worker function + * @unused: not used + * + * Call ish_fw_reset_handler to complete FW reset + */ +static void fw_reset_work_fn(struct work_struct *unused) +{ + int rv; + + rv = ish_fw_reset_handler(ishtp_dev); + if (!rv) { + /* ISH is ILUP & ISHTP-ready. Restart ISHTP */ + schedule_timeout(HZ / 3); + ishtp_dev->recvd_hw_ready = 1; + wake_up_interruptible(&ishtp_dev->wait_hw_ready); + + /* ISHTP notification in IPC_RESET sequence completion */ + ishtp_reset_compl_handler(ishtp_dev); + } else + dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n", + rv); +} + +/** + * _ish_sync_fw_clock() -Sync FW clock with the OS clock + * @dev: ishtp device pointer + * + * Sync FW and OS time + */ +static void _ish_sync_fw_clock(struct ishtp_device *dev) +{ + static unsigned long prev_sync; + struct timespec ts; + uint64_t usec; + + if (prev_sync && jiffies - prev_sync < 20 * HZ) + return; + + prev_sync = jiffies; + get_monotonic_boottime(&ts); + usec = (timespec_to_ns(&ts)) / NSEC_PER_USEC; + ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t)); +} + +/** + * recv_ipc() - Receive and process IPC management messages + * @dev: ishtp device instance + * @doorbell_val: doorbell value + * + * This function runs in ISR context. + * NOTE: Any other mng command than reset_notify and reset_notify_ack + * won't wake BH handler + */ +static void recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val) +{ + uint32_t mng_cmd; + + mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val); + + switch (mng_cmd) { + default: + break; + + case MNG_RX_CMPL_INDICATION: + if (dev->suspend_flag) { + dev->suspend_flag = 0; + wake_up_interruptible(&dev->suspend_wait); + } + if (dev->resume_flag) { + dev->resume_flag = 0; + wake_up_interruptible(&dev->resume_wait); + } + + write_ipc_from_queue(dev); + break; + + case MNG_RESET_NOTIFY: + if (!ishtp_dev) { + ishtp_dev = dev; + INIT_WORK(&fw_reset_work, fw_reset_work_fn); + } + schedule_work(&fw_reset_work); + break; + + case MNG_RESET_NOTIFY_ACK: + dev->recvd_hw_ready = 1; + wake_up_interruptible(&dev->wait_hw_ready); + break; + } +} + +/** + * ish_irq_handler() - ISH IRQ handler + * @irq: irq number + * @dev_id: ishtp device pointer + * + * ISH IRQ handler. If interrupt is generated and is for ISH it will process + * the interrupt. + */ +irqreturn_t ish_irq_handler(int irq, void *dev_id) +{ + struct ishtp_device *dev = dev_id; + uint32_t doorbell_val; + bool interrupt_generated; + + /* Check that it's interrupt from ISH (may be shared) */ + interrupt_generated = check_generated_interrupt(dev); + + if (!interrupt_generated) + return IRQ_NONE; + + doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL); + if (!IPC_IS_BUSY(doorbell_val)) + return IRQ_HANDLED; + + if (dev->dev_state == ISHTP_DEV_DISABLED) + return IRQ_HANDLED; + + /* Sanity check: IPC dgram length in header */ + if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) { + dev_err(dev->devc, + "IPC hdr - bad length: %u; dropped\n", + (unsigned int)IPC_HEADER_GET_LENGTH(doorbell_val)); + goto eoi; + } + + switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) { + default: + break; + case IPC_PROTOCOL_MNG: + recv_ipc(dev, doorbell_val); + break; + case IPC_PROTOCOL_ISHTP: + ishtp_recv(dev); + break; + } + +eoi: + /* Update IPC counters */ + ++dev->ipc_rx_cnt; + dev->ipc_rx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); + + ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); + /* Flush write to doorbell */ + ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); + + return IRQ_HANDLED; +} + +/** + * _ish_hw_reset() - HW reset + * @dev: ishtp device pointer + * + * Reset ISH HW to recover if any error + * + * Return: 0 for success else error fault code + */ +static int _ish_hw_reset(struct ishtp_device *dev) +{ + struct pci_dev *pdev = dev->pdev; + int rv; + unsigned int dma_delay; + uint16_t csr; + + if (!pdev) + return -ENODEV; + + rv = pci_reset_function(pdev); + if (!rv) + dev->dev_state = ISHTP_DEV_RESETTING; + + if (!pdev->pm_cap) { + dev_err(&pdev->dev, "Can't reset - no PM caps\n"); + return -EINVAL; + } + + /* Now trigger reset to FW */ + ish_reg_write(dev, IPC_REG_ISH_RMP2, 0); + + for (dma_delay = 0; dma_delay < MAX_DMA_DELAY && + _ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA); + dma_delay += 5) + mdelay(5); + + if (dma_delay >= MAX_DMA_DELAY) { + dev_err(&pdev->dev, + "Can't reset - stuck with DMA in-progress\n"); + return -EBUSY; + } + + pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &csr); + + csr &= ~PCI_PM_CTRL_STATE_MASK; + csr |= PCI_D3hot; + pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); + + mdelay(pdev->d3_delay); + + csr &= ~PCI_PM_CTRL_STATE_MASK; + csr |= PCI_D0; + pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); + + ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED); + + /* + * Send 0 IPC message so that ISH FW wakes up if it was already + * asleep + */ + ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); + + /* Flush writes to doorbell and REMAP2 */ + ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); + + return 0; +} + +/** + * _ish_ipc_reset() - IPC reset + * @dev: ishtp device pointer + * + * Resets host and fw IPC and upper layers + * + * Return: 0 for success else error fault code + */ +static int _ish_ipc_reset(struct ishtp_device *dev) +{ + struct ipc_rst_payload_type ipc_mng_msg; + int rv = 0; + + ipc_mng_msg.reset_id = 1; + ipc_mng_msg.reserved = 0; + + set_host_ready(dev); + + /* Clear the incoming doorbell */ + ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); + /* Flush write to doorbell */ + ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); + + dev->recvd_hw_ready = 0; + + /* send message */ + rv = ipc_send_mng_msg(dev, MNG_RESET_NOTIFY, &ipc_mng_msg, + sizeof(struct ipc_rst_payload_type)); + if (rv) { + dev_err(dev->devc, "Failed to send IPC MNG_RESET_NOTIFY\n"); + return rv; + } + + wait_event_interruptible_timeout(dev->wait_hw_ready, + dev->recvd_hw_ready, 2 * HZ); + if (!dev->recvd_hw_ready) { + dev_err(dev->devc, "Timed out waiting for HW ready\n"); + rv = -ENODEV; + } + + return rv; +} + +/** + * ish_hw_start() -Start ISH HW + * @dev: ishtp device pointer + * + * Set host to ready state and wait for FW reset + * + * Return: 0 for success else error fault code + */ +int ish_hw_start(struct ishtp_device *dev) +{ + ish_set_host_rdy(dev); + /* After that we can enable ISH DMA operation */ + ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED); + + /* + * Send 0 IPC message so that ISH FW wakes up if it was already + * asleep + */ + ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); + /* Flush write to doorbell */ + ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); + + set_host_ready(dev); + + /* wait for FW-initiated reset flow */ + if (!dev->recvd_hw_ready) + wait_event_interruptible_timeout(dev->wait_hw_ready, + dev->recvd_hw_ready, + 10 * HZ); + + if (!dev->recvd_hw_ready) { + dev_err(dev->devc, + "[ishtp-ish]: Timed out waiting for FW-initiated reset\n"); + return -ENODEV; + } + + return 0; +} + +/** + * ish_ipc_get_header() -Get doorbell value + * @dev: ishtp device pointer + * @length: length of message + * @busy: busy status + * + * Get door bell value from message header + * + * Return: door bell value + */ +static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length, + int busy) +{ + uint32_t drbl_val; + + drbl_val = IPC_BUILD_HEADER(length, IPC_PROTOCOL_ISHTP, busy); + + return drbl_val; +} + +static const struct ishtp_hw_ops ish_hw_ops = { + .hw_reset = _ish_hw_reset, + .ipc_reset = _ish_ipc_reset, + .ipc_get_header = ish_ipc_get_header, + .ishtp_read = _ishtp_read, + .write = write_ipc_to_queue, + .get_fw_status = _ish_read_fw_sts_reg, + .sync_fw_clock = _ish_sync_fw_clock, + .ishtp_read_hdr = _ishtp_read_hdr +}; + +/** + * ish_dev_init() -Initialize ISH devoce + * @pdev: PCI device + * + * Allocate ISHTP device and initialize IPC processing + * + * Return: ISHTP device instance on success else NULL + */ +struct ishtp_device *ish_dev_init(struct pci_dev *pdev) +{ + struct ishtp_device *dev; + int i; + + dev = kzalloc(sizeof(struct ishtp_device) + sizeof(struct ish_hw), + GFP_KERNEL); + if (!dev) + return NULL; + + ishtp_device_init(dev); + + init_waitqueue_head(&dev->wait_hw_ready); + + spin_lock_init(&dev->wr_processing_spinlock); + spin_lock_init(&dev->out_ipc_spinlock); + + /* Init IPC processing and free lists */ + INIT_LIST_HEAD(&dev->wr_processing_list_head.link); + INIT_LIST_HEAD(&dev->wr_free_list_head.link); + for (i = 0; i < IPC_TX_FIFO_SIZE; ++i) { + struct wr_msg_ctl_info *tx_buf; + + tx_buf = kzalloc(sizeof(struct wr_msg_ctl_info), GFP_KERNEL); + if (!tx_buf) { + /* + * IPC buffers may be limited or not available + * at all - although this shouldn't happen + */ + dev_err(dev->devc, + "[ishtp-ish]: failure in Tx FIFO allocations (%d)\n", + i); + break; + } + list_add_tail(&tx_buf->link, &dev->wr_free_list_head.link); + } + + dev->ops = &ish_hw_ops; + dev->devc = &pdev->dev; + dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr); + return dev; +} + +/** + * ish_device_disable() - Disable ISH device + * @dev: ISHTP device pointer + * + * Disable ISH by clearing host ready to inform firmware. + */ +void ish_device_disable(struct ishtp_device *dev) +{ + dev->dev_state = ISHTP_DEV_DISABLED; + ish_clr_host_rdy(dev); +} diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c new file mode 100644 index 000000000000..b14d6f4774b9 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -0,0 +1,333 @@ +/* + * PCI glue for ISHTP provider device (ISH) driver + * + * Copyright (c) 2014-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define CREATE_TRACE_POINTS +#include +#include "ishtp-dev.h" +#include "hw-ish.h" + +static const struct pci_device_id ish_pci_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)}, + {0, } +}; +MODULE_DEVICE_TABLE(pci, ish_pci_tbl); + +/** + * ish_event_tracer() - Callback function to dump trace messages + * @dev: ishtp device + * @format: printf style format + * + * Callback to direct log messages to Linux trace buffers + */ +static void ish_event_tracer(struct ishtp_device *dev, char *format, ...) +{ + if (trace_ishtp_dump_enabled()) { + va_list args; + char tmp_buf[100]; + + va_start(args, format); + vsnprintf(tmp_buf, sizeof(tmp_buf), format, args); + va_end(args); + + trace_ishtp_dump(tmp_buf); + } +} + +/** + * ish_init() - Init function + * @dev: ishtp device + * + * This function initialize wait queues for suspend/resume and call + * calls hadware initialization function. This will initiate + * startup sequence + * + * Return: 0 for success or error code for failure + */ +static int ish_init(struct ishtp_device *dev) +{ + int ret; + + /* Set the state of ISH HW to start */ + ret = ish_hw_start(dev); + if (ret) { + dev_err(dev->devc, "ISH: hw start failed.\n"); + return ret; + } + + /* Start the inter process communication to ISH processor */ + ret = ishtp_start(dev); + if (ret) { + dev_err(dev->devc, "ISHTP: Protocol init failed.\n"); + return ret; + } + + return 0; +} + +/** + * ish_probe() - PCI driver probe callback + * @pdev: pci device + * @ent: pci device id + * + * Initialize PCI function, setup interrupt and call for ISH initialization + * + * Return: 0 for success or error code for failure + */ +static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct ishtp_device *dev; + struct ish_hw *hw; + int ret; + + /* enable pci dev */ + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "ISH: Failed to enable PCI device\n"); + return ret; + } + + /* set PCI host mastering */ + pci_set_master(pdev); + + /* pci request regions for ISH driver */ + ret = pci_request_regions(pdev, KBUILD_MODNAME); + if (ret) { + dev_err(&pdev->dev, "ISH: Failed to get PCI regions\n"); + goto disable_device; + } + + /* allocates and initializes the ISH dev structure */ + dev = ish_dev_init(pdev); + if (!dev) { + ret = -ENOMEM; + goto release_regions; + } + hw = to_ish_hw(dev); + dev->print_log = ish_event_tracer; + + /* mapping IO device memory */ + hw->mem_addr = pci_iomap(pdev, 0, 0); + if (!hw->mem_addr) { + dev_err(&pdev->dev, "ISH: mapping I/O range failure\n"); + ret = -ENOMEM; + goto free_device; + } + + dev->pdev = pdev; + + pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; + + /* request and enable interrupt */ + ret = request_irq(pdev->irq, ish_irq_handler, IRQF_NO_SUSPEND, + KBUILD_MODNAME, dev); + if (ret) { + dev_err(&pdev->dev, "ISH: request IRQ failure (%d)\n", + pdev->irq); + goto free_device; + } + + dev_set_drvdata(dev->devc, dev); + + init_waitqueue_head(&dev->suspend_wait); + init_waitqueue_head(&dev->resume_wait); + + ret = ish_init(dev); + if (ret) + goto free_irq; + + return 0; + +free_irq: + free_irq(pdev->irq, dev); +free_device: + pci_iounmap(pdev, hw->mem_addr); + kfree(dev); +release_regions: + pci_release_regions(pdev); +disable_device: + pci_clear_master(pdev); + pci_disable_device(pdev); + dev_err(&pdev->dev, "ISH: PCI driver initialization failed.\n"); + + return ret; +} + +/** + * ish_remove() - PCI driver remove callback + * @pdev: pci device + * + * This function does cleanup of ISH on pci remove callback + */ +static void ish_remove(struct pci_dev *pdev) +{ + struct ishtp_device *ishtp_dev = pci_get_drvdata(pdev); + struct ish_hw *hw = to_ish_hw(ishtp_dev); + + ishtp_bus_remove_all_clients(ishtp_dev, false); + ish_device_disable(ishtp_dev); + + free_irq(pdev->irq, ishtp_dev); + pci_iounmap(pdev, hw->mem_addr); + pci_release_regions(pdev); + pci_clear_master(pdev); + pci_disable_device(pdev); + kfree(ishtp_dev); +} + +static struct device *ish_resume_device; + +/** + * ish_resume_handler() - Work function to complete resume + * @work: work struct + * + * The resume work function to complete resume function asynchronously. + * There are two types of platforms, one where ISH is not powered off, + * in that case a simple resume message is enough, others we need + * a reset sequence. + */ +static void ish_resume_handler(struct work_struct *work) +{ + struct pci_dev *pdev = to_pci_dev(ish_resume_device); + struct ishtp_device *dev = pci_get_drvdata(pdev); + int ret; + + ishtp_send_resume(dev); + + /* 50 ms to get resume response */ + if (dev->resume_flag) + ret = wait_event_interruptible_timeout(dev->resume_wait, + !dev->resume_flag, + msecs_to_jiffies(50)); + + /* + * If no resume response. This platform is not S0ix compatible + * So on resume full reboot of ISH processor will happen, so + * need to go through init sequence again + */ + if (dev->resume_flag) + ish_init(dev); +} + +/** + * ish_suspend() - ISH suspend callback + * @device: device pointer + * + * ISH suspend callback + * + * Return: 0 to the pm core + */ +static int ish_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct ishtp_device *dev = pci_get_drvdata(pdev); + + enable_irq_wake(pdev->irq); + /* + * If previous suspend hasn't been asnwered then ISH is likely dead, + * don't attempt nested notification + */ + if (dev->suspend_flag) + return 0; + + dev->resume_flag = 0; + dev->suspend_flag = 1; + ishtp_send_suspend(dev); + + /* 25 ms should be enough for live ISH to flush all IPC buf */ + if (dev->suspend_flag) + wait_event_interruptible_timeout(dev->suspend_wait, + !dev->suspend_flag, + msecs_to_jiffies(25)); + + return 0; +} + +static DECLARE_WORK(resume_work, ish_resume_handler); +/** + * ish_resume() - ISH resume callback + * @device: device pointer + * + * ISH resume callback + * + * Return: 0 to the pm core + */ +static int ish_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct ishtp_device *dev = pci_get_drvdata(pdev); + + ish_resume_device = device; + dev->resume_flag = 1; + + disable_irq_wake(pdev->irq); + schedule_work(&resume_work); + + return 0; +} + +#ifdef CONFIG_PM +static const struct dev_pm_ops ish_pm_ops = { + .suspend = ish_suspend, + .resume = ish_resume, +}; +#define ISHTP_ISH_PM_OPS (&ish_pm_ops) +#else +#define ISHTP_ISH_PM_OPS NULL +#endif + +static struct pci_driver ish_driver = { + .name = KBUILD_MODNAME, + .id_table = ish_pci_tbl, + .probe = ish_probe, + .remove = ish_remove, + .driver.pm = ISHTP_ISH_PM_OPS, +}; + +static int __init ish_driver_init(void) +{ + return pci_register_driver(&ish_driver); +} + +static void __exit ish_driver_exit(void) +{ + pci_unregister_driver(&ish_driver); +} + +module_init(ish_driver_init); +module_exit(ish_driver_exit); + +/* Original author */ +MODULE_AUTHOR("Daniel Drubin "); +/* Adoption to upstream Linux kernel */ +MODULE_AUTHOR("Srinivas Pandruvada "); + +MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/intel-ish-hid/ipc/utils.h b/drivers/hid/intel-ish-hid/ipc/utils.h new file mode 100644 index 000000000000..5a82123dc7b4 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ipc/utils.h @@ -0,0 +1,64 @@ +/* + * Utility macros of ISH + * + * Copyright (c) 2014-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ +#ifndef UTILS__H +#define UTILS__H + +#define WAIT_FOR_SEND_SLICE (HZ / 10) +#define WAIT_FOR_CONNECT_SLICE (HZ / 10) + +/* + * Waits for specified event when a thread that triggers event can't signal + * Also, waits *at_least* `timeinc` after condition is satisfied + */ +#define timed_wait_for(timeinc, condition) \ + do { \ + int completed = 0; \ + do { \ + unsigned long j; \ + int done = 0; \ + \ + completed = (condition); \ + for (j = jiffies, done = 0; !done; ) { \ + schedule_timeout(timeinc); \ + if (time_is_before_eq_jiffies(j + timeinc)) \ + done = 1; \ + } \ + } while (!(completed)); \ + } while (0) + + +/* + * Waits for specified event when a thread that triggers event + * can't signal with timeout (use whenever we may hang) + */ +#define timed_wait_for_timeout(timeinc, condition, timeout) \ + do { \ + int t = timeout; \ + do { \ + unsigned long j; \ + int done = 0; \ + \ + for (j = jiffies, done = 0; !done; ) { \ + schedule_timeout(timeinc); \ + if (time_is_before_eq_jiffies(j + timeinc)) \ + done = 1; \ + } \ + t -= timeinc; \ + if (t <= 0) \ + break; \ + } while (!(condition)); \ + } while (0) + +#endif /* UTILS__H */ diff --git a/include/trace/events/intel_ish.h b/include/trace/events/intel_ish.h new file mode 100644 index 000000000000..92f7d5b23177 --- /dev/null +++ b/include/trace/events/intel_ish.h @@ -0,0 +1,30 @@ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM intel_ish + +#if !defined(_TRACE_INTEL_ISH_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_INTEL_ISH_H + +#include + +TRACE_EVENT(ishtp_dump, + + TP_PROTO(const char *message), + + TP_ARGS(message), + + TP_STRUCT__entry( + __string(message, message) + ), + + TP_fast_assign( + __assign_str(message, message); + ), + + TP_printk("%s", __get_str(message)) +); + + +#endif /* _TRACE_INTEL_ISH_H */ + +/* This part must be outside protection */ +#include -- cgit v1.2.3 From 0b28cb4bcb17dcb5fe0763fc3e1a94398b8f6cf6 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 7 Aug 2016 02:25:36 -0700 Subject: HID: intel-ish-hid: ISH HID client driver This driver is responsible for implementing ISH HID client, which gets HID description and report. Once it has completely gets report descriptors, it registers as a HID LL drivers. This implements necessary callbacks so that it can be used by HID sensor hub driver. Original-author: Daniel Drubin Reviewed-and-tested-by: Ooi, Joyce Tested-by: Grant Likely Tested-by: Rann Bar-On Tested-by: Atri Bhattacharya Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/Makefile | 4 + drivers/hid/intel-ish-hid/ishtp-hid-client.c | 978 +++++++++++++++++++++++++++ drivers/hid/intel-ish-hid/ishtp-hid.c | 246 +++++++ drivers/hid/intel-ish-hid/ishtp-hid.h | 182 +++++ include/uapi/linux/input.h | 1 + 5 files changed, 1411 insertions(+) create mode 100644 drivers/hid/intel-ish-hid/ishtp-hid-client.c create mode 100644 drivers/hid/intel-ish-hid/ishtp-hid.c create mode 100644 drivers/hid/intel-ish-hid/ishtp-hid.h diff --git a/drivers/hid/intel-ish-hid/Makefile b/drivers/hid/intel-ish-hid/Makefile index ab626d8f7bb8..8c08b0b358b1 100644 --- a/drivers/hid/intel-ish-hid/Makefile +++ b/drivers/hid/intel-ish-hid/Makefile @@ -15,4 +15,8 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o intel-ish-ipc-objs := ipc/ipc.o intel-ish-ipc-objs += ipc/pci-ish.o +obj-$(CONFIG_INTEL_ISH_HID) += intel-ishtp-hid.o +intel-ishtp-hid-objs := ishtp-hid.o +intel-ishtp-hid-objs += ishtp-hid-client.o + ccflags-y += -Idrivers/hid/intel-ish-hid/ishtp diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c new file mode 100644 index 000000000000..5c643d7a07b2 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -0,0 +1,978 @@ +/* + * ISHTP client driver for HID (ISH) + * + * Copyright (c) 2014-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include "ishtp/ishtp-dev.h" +#include "ishtp/client.h" +#include "ishtp-hid.h" + +/* Rx ring buffer pool size */ +#define HID_CL_RX_RING_SIZE 32 +#define HID_CL_TX_RING_SIZE 16 + +/** + * report_bad_packets() - Report bad packets + * @hid_ishtp_cl: Client instance to get stats + * @recv_buf: Raw received host interface message + * @cur_pos: Current position index in payload + * @payload_len: Length of payload expected + * + * Dumps error in case bad packet is received + */ +static void report_bad_packet(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, + size_t cur_pos, size_t payload_len) +{ + struct hostif_msg *recv_msg = recv_buf; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + + dev_err(&client_data->cl_device->dev, "[hid-ish]: BAD packet %02X\n" + "total_bad=%u cur_pos=%u\n" + "[%02X %02X %02X %02X]\n" + "payload_len=%u\n" + "multi_packet_cnt=%u\n" + "is_response=%02X\n", + recv_msg->hdr.command, client_data->bad_recv_cnt, + (unsigned int)cur_pos, + ((unsigned char *)recv_msg)[0], ((unsigned char *)recv_msg)[1], + ((unsigned char *)recv_msg)[2], ((unsigned char *)recv_msg)[3], + (unsigned int)payload_len, client_data->multi_packet_cnt, + recv_msg->hdr.command & ~CMD_MASK); +} + +/** + * process_recv() - Received and parse incoming packet + * @hid_ishtp_cl: Client instance to get stats + * @recv_buf: Raw received host interface message + * @data_len: length of the message + * + * Parse the incoming packet. If it is a response packet then it will update + * per instance flags and wake up the caller waiting to for the response. + */ +static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, + size_t data_len) +{ + struct hostif_msg *recv_msg; + unsigned char *payload; + struct device_info *dev_info; + int i, j; + size_t payload_len, total_len, cur_pos; + int report_type; + struct report_list *reports_list; + char *reports; + size_t report_len; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + int curr_hid_dev = client_data->cur_hid_dev; + + if (data_len < sizeof(struct hostif_msg_hdr)) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: error, received %u which is less than data header %u\n", + (unsigned int)data_len, + (unsigned int)sizeof(struct hostif_msg_hdr)); + ++client_data->bad_recv_cnt; + ish_hw_reset(hid_ishtp_cl->dev); + return; + } + + payload = recv_buf + sizeof(struct hostif_msg_hdr); + total_len = data_len; + cur_pos = 0; + + do { + recv_msg = (struct hostif_msg *)(recv_buf + cur_pos); + payload_len = recv_msg->hdr.size; + + /* Sanity checks */ + if (cur_pos + payload_len + sizeof(struct hostif_msg) > + total_len) { + ++client_data->bad_recv_cnt; + report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos, + payload_len); + ish_hw_reset(hid_ishtp_cl->dev); + break; + } + + hid_ishtp_trace(client_data, "%s %d\n", + __func__, recv_msg->hdr.command & CMD_MASK); + + switch (recv_msg->hdr.command & CMD_MASK) { + case HOSTIF_DM_ENUM_DEVICES: + if ((!(recv_msg->hdr.command & ~CMD_MASK) || + client_data->init_done)) { + ++client_data->bad_recv_cnt; + report_bad_packet(hid_ishtp_cl, recv_msg, + cur_pos, + payload_len); + ish_hw_reset(hid_ishtp_cl->dev); + break; + } + client_data->hid_dev_count = (unsigned int)*payload; + if (!client_data->hid_devices) + client_data->hid_devices = devm_kzalloc( + &client_data->cl_device->dev, + client_data->hid_dev_count * + sizeof(struct device_info), + GFP_KERNEL); + if (!client_data->hid_devices) { + dev_err(&client_data->cl_device->dev, + "Mem alloc failed for hid device info\n"); + wake_up_interruptible(&client_data->init_wait); + break; + } + for (i = 0; i < client_data->hid_dev_count; ++i) { + if (1 + sizeof(struct device_info) * i >= + payload_len) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: [ENUM_DEVICES]: content size %lu is bigger than payload_len %u\n", + 1 + sizeof(struct device_info) + * i, + (unsigned int)payload_len); + } + + if (1 + sizeof(struct device_info) * i >= + data_len) + break; + + dev_info = (struct device_info *)(payload + 1 + + sizeof(struct device_info) * i); + if (client_data->hid_devices) + memcpy(client_data->hid_devices + i, + dev_info, + sizeof(struct device_info)); + } + + client_data->enum_devices_done = true; + wake_up_interruptible(&client_data->init_wait); + + break; + + case HOSTIF_GET_HID_DESCRIPTOR: + if ((!(recv_msg->hdr.command & ~CMD_MASK) || + client_data->init_done)) { + ++client_data->bad_recv_cnt; + report_bad_packet(hid_ishtp_cl, recv_msg, + cur_pos, + payload_len); + ish_hw_reset(hid_ishtp_cl->dev); + break; + } + if (!client_data->hid_descr[curr_hid_dev]) + client_data->hid_descr[curr_hid_dev] = + devm_kmalloc(&client_data->cl_device->dev, + payload_len, GFP_KERNEL); + if (client_data->hid_descr[curr_hid_dev]) { + memcpy(client_data->hid_descr[curr_hid_dev], + payload, payload_len); + client_data->hid_descr_size[curr_hid_dev] = + payload_len; + client_data->hid_descr_done = true; + } + wake_up_interruptible(&client_data->init_wait); + + break; + + case HOSTIF_GET_REPORT_DESCRIPTOR: + if ((!(recv_msg->hdr.command & ~CMD_MASK) || + client_data->init_done)) { + ++client_data->bad_recv_cnt; + report_bad_packet(hid_ishtp_cl, recv_msg, + cur_pos, + payload_len); + ish_hw_reset(hid_ishtp_cl->dev); + break; + } + if (!client_data->report_descr[curr_hid_dev]) + client_data->report_descr[curr_hid_dev] = + devm_kmalloc(&client_data->cl_device->dev, + payload_len, GFP_KERNEL); + if (client_data->report_descr[curr_hid_dev]) { + memcpy(client_data->report_descr[curr_hid_dev], + payload, + payload_len); + client_data->report_descr_size[curr_hid_dev] = + payload_len; + client_data->report_descr_done = true; + } + wake_up_interruptible(&client_data->init_wait); + + break; + + case HOSTIF_GET_FEATURE_REPORT: + report_type = HID_FEATURE_REPORT; + goto do_get_report; + + case HOSTIF_GET_INPUT_REPORT: + report_type = HID_INPUT_REPORT; +do_get_report: + /* Get index of device that matches this id */ + for (i = 0; i < client_data->num_hid_devices; ++i) { + if (recv_msg->hdr.device_id == + client_data->hid_devices[i].dev_id) + if (client_data->hid_sensor_hubs[i]) { + hid_input_report( + client_data->hid_sensor_hubs[ + i], + report_type, payload, + payload_len, 0); + ishtp_hid_wakeup( + client_data->hid_sensor_hubs[ + i]); + break; + } + } + break; + + case HOSTIF_SET_FEATURE_REPORT: + /* Get index of device that matches this id */ + for (i = 0; i < client_data->num_hid_devices; ++i) { + if (recv_msg->hdr.device_id == + client_data->hid_devices[i].dev_id) + if (client_data->hid_sensor_hubs[i]) { + ishtp_hid_wakeup( + client_data->hid_sensor_hubs[ + i]); + break; + } + } + break; + + case HOSTIF_PUBLISH_INPUT_REPORT: + report_type = HID_INPUT_REPORT; + for (i = 0; i < client_data->num_hid_devices; ++i) + if (recv_msg->hdr.device_id == + client_data->hid_devices[i].dev_id) + if (client_data->hid_sensor_hubs[i]) + hid_input_report( + client_data->hid_sensor_hubs[ + i], + report_type, payload, + payload_len, 0); + break; + + case HOSTIF_PUBLISH_INPUT_REPORT_LIST: + report_type = HID_INPUT_REPORT; + reports_list = (struct report_list *)payload; + reports = (char *)reports_list->reports; + + for (j = 0; j < reports_list->num_of_reports; j++) { + recv_msg = (struct hostif_msg *)(reports + + sizeof(uint16_t)); + report_len = *(uint16_t *)reports; + payload = reports + sizeof(uint16_t) + + sizeof(struct hostif_msg_hdr); + payload_len = report_len - + sizeof(struct hostif_msg_hdr); + + for (i = 0; i < client_data->num_hid_devices; + ++i) + if (recv_msg->hdr.device_id == + client_data->hid_devices[i].dev_id && + client_data->hid_sensor_hubs[i]) { + hid_input_report( + client_data->hid_sensor_hubs[ + i], + report_type, + payload, payload_len, + 0); + } + + reports += sizeof(uint16_t) + report_len; + } + break; + default: + ++client_data->bad_recv_cnt; + report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos, + payload_len); + ish_hw_reset(hid_ishtp_cl->dev); + break; + + } + + if (!cur_pos && cur_pos + payload_len + + sizeof(struct hostif_msg) < total_len) + ++client_data->multi_packet_cnt; + + cur_pos += payload_len + sizeof(struct hostif_msg); + payload += payload_len + sizeof(struct hostif_msg); + + } while (cur_pos < total_len); +} + +/** + * ish_cl_event_cb() - bus driver callback for incoming message/packet + * @device: Pointer to the the ishtp client device for which this message + * is targeted + * + * Remove the packet from the list and process the message by calling + * process_recv + */ +static void ish_cl_event_cb(struct ishtp_cl_device *device) +{ + struct ishtp_cl *hid_ishtp_cl = device->driver_data; + struct ishtp_cl_rb *rb_in_proc; + size_t r_length; + unsigned long flags; + + if (!hid_ishtp_cl) + return; + + spin_lock_irqsave(&hid_ishtp_cl->in_process_spinlock, flags); + while (!list_empty(&hid_ishtp_cl->in_process_list.list)) { + rb_in_proc = list_entry( + hid_ishtp_cl->in_process_list.list.next, + struct ishtp_cl_rb, list); + list_del_init(&rb_in_proc->list); + spin_unlock_irqrestore(&hid_ishtp_cl->in_process_spinlock, + flags); + + if (!rb_in_proc->buffer.data) + return; + + r_length = rb_in_proc->buf_idx; + + /* decide what to do with received data */ + process_recv(hid_ishtp_cl, rb_in_proc->buffer.data, r_length); + + ishtp_cl_io_rb_recycle(rb_in_proc); + spin_lock_irqsave(&hid_ishtp_cl->in_process_spinlock, flags); + } + spin_unlock_irqrestore(&hid_ishtp_cl->in_process_spinlock, flags); +} + +/** + * hid_ishtp_set_feature() - send request to ISH FW to set a feature request + * @hid: hid device instance for this request + * @buf: feature buffer + * @len: Length of feature buffer + * @report_id: Report id for the feature set request + * + * This is called from hid core .request() callback. This function doesn't wait + * for response. + */ +void hid_ishtp_set_feature(struct hid_device *hid, char *buf, unsigned int len, + int report_id) +{ + struct ishtp_hid_data *hid_data = hid->driver_data; + struct ishtp_cl_data *client_data = hid_data->client_data; + struct hostif_msg *msg = (struct hostif_msg *)buf; + int rv; + int i; + + hid_ishtp_trace(client_data, "%s hid %p\n", __func__, hid); + + rv = ishtp_hid_link_ready_wait(client_data); + if (rv) { + hid_ishtp_trace(client_data, "%s hid %p link not ready\n", + __func__, hid); + return; + } + + memset(msg, 0, sizeof(struct hostif_msg)); + msg->hdr.command = HOSTIF_SET_FEATURE_REPORT; + for (i = 0; i < client_data->num_hid_devices; ++i) { + if (hid == client_data->hid_sensor_hubs[i]) { + msg->hdr.device_id = + client_data->hid_devices[i].dev_id; + break; + } + } + + if (i == client_data->num_hid_devices) + return; + + rv = ishtp_cl_send(client_data->hid_ishtp_cl, buf, len); + if (rv) + hid_ishtp_trace(client_data, "%s hid %p send failed\n", + __func__, hid); +} + +/** + * hid_ishtp_get_report() - request to get feature/input report + * @hid: hid device instance for this request + * @report_id: Report id for the get request + * @report_type: Report type for the this request + * + * This is called from hid core .request() callback. This function will send + * request to FW and return without waiting for response. + */ +void hid_ishtp_get_report(struct hid_device *hid, int report_id, + int report_type) +{ + struct ishtp_hid_data *hid_data = hid->driver_data; + struct ishtp_cl_data *client_data = hid_data->client_data; + static unsigned char buf[10]; + unsigned int len; + struct hostif_msg_to_sensor *msg = (struct hostif_msg_to_sensor *)buf; + int rv; + int i; + + hid_ishtp_trace(client_data, "%s hid %p\n", __func__, hid); + rv = ishtp_hid_link_ready_wait(client_data); + if (rv) { + hid_ishtp_trace(client_data, "%s hid %p link not ready\n", + __func__, hid); + return; + } + + len = sizeof(struct hostif_msg_to_sensor); + + memset(msg, 0, sizeof(struct hostif_msg_to_sensor)); + msg->hdr.command = (report_type == HID_FEATURE_REPORT) ? + HOSTIF_GET_FEATURE_REPORT : HOSTIF_GET_INPUT_REPORT; + for (i = 0; i < client_data->num_hid_devices; ++i) { + if (hid == client_data->hid_sensor_hubs[i]) { + msg->hdr.device_id = + client_data->hid_devices[i].dev_id; + break; + } + } + + if (i == client_data->num_hid_devices) + return; + + msg->report_id = report_id; + rv = ishtp_cl_send(client_data->hid_ishtp_cl, buf, len); + if (rv) + hid_ishtp_trace(client_data, "%s hid %p send failed\n", + __func__, hid); +} + +/** + * ishtp_hid_link_ready_wait() - Wait for link ready + * @client_data: client data instance + * + * If the transport link started suspend process, then wait, till either + * resumed or timeout + * + * Return: 0 on success, non zero on error + */ +int ishtp_hid_link_ready_wait(struct ishtp_cl_data *client_data) +{ + int rc; + + if (client_data->suspended) { + hid_ishtp_trace(client_data, "wait for link ready\n"); + rc = wait_event_interruptible_timeout( + client_data->ishtp_resume_wait, + !client_data->suspended, + 5 * HZ); + + if (rc == 0) { + hid_ishtp_trace(client_data, "link not ready\n"); + return -EIO; + } + hid_ishtp_trace(client_data, "link ready\n"); + } + + return 0; +} + +/** + * ishtp_enum_enum_devices() - Enumerate hid devices + * @hid_ishtp_cl: client instance + * + * Helper function to send request to firmware to enumerate HID devices + * + * Return: 0 on success, non zero on error + */ +static int ishtp_enum_enum_devices(struct ishtp_cl *hid_ishtp_cl) +{ + struct hostif_msg msg; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + int retry_count; + int rv; + + /* Send HOSTIF_DM_ENUM_DEVICES */ + memset(&msg, 0, sizeof(struct hostif_msg)); + msg.hdr.command = HOSTIF_DM_ENUM_DEVICES; + rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *)&msg, + sizeof(struct hostif_msg)); + if (rv) + return rv; + + retry_count = 0; + while (!client_data->enum_devices_done && + retry_count < 10) { + wait_event_interruptible_timeout(client_data->init_wait, + client_data->enum_devices_done, + 3 * HZ); + ++retry_count; + if (!client_data->enum_devices_done) + /* Send HOSTIF_DM_ENUM_DEVICES */ + rv = ishtp_cl_send(hid_ishtp_cl, + (unsigned char *) &msg, + sizeof(struct hostif_msg)); + } + if (!client_data->enum_devices_done) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: timed out waiting for enum_devices\n"); + return -ETIMEDOUT; + } + if (!client_data->hid_devices) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: failed to allocate HID dev structures\n"); + return -ENOMEM; + } + + client_data->num_hid_devices = client_data->hid_dev_count; + dev_info(&hid_ishtp_cl->device->dev, + "[hid-ish]: enum_devices_done OK, num_hid_devices=%d\n", + client_data->num_hid_devices); + + return 0; +} + +/** + * ishtp_get_hid_descriptor() - Get hid descriptor + * @hid_ishtp_cl: client instance + * @index: Index into the hid_descr array + * + * Helper function to send request to firmware get HID descriptor of a device + * + * Return: 0 on success, non zero on error + */ +static int ishtp_get_hid_descriptor(struct ishtp_cl *hid_ishtp_cl, int index) +{ + struct hostif_msg msg; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + int rv; + + /* Get HID descriptor */ + client_data->hid_descr_done = false; + memset(&msg, 0, sizeof(struct hostif_msg)); + msg.hdr.command = HOSTIF_GET_HID_DESCRIPTOR; + msg.hdr.device_id = client_data->hid_devices[index].dev_id; + rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg, + sizeof(struct hostif_msg)); + if (rv) + return rv; + + if (!client_data->hid_descr_done) { + wait_event_interruptible_timeout(client_data->init_wait, + client_data->hid_descr_done, + 3 * HZ); + if (!client_data->hid_descr_done) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: timed out for hid_descr_done\n"); + return -EIO; + } + + if (!client_data->hid_descr[index]) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: allocation HID desc fail\n"); + return -ENOMEM; + } + } + + return 0; +} + +/** + * ishtp_get_report_descriptor() - Get report descriptor + * @hid_ishtp_cl: client instance + * @index: Index into the hid_descr array + * + * Helper function to send request to firmware get HID report descriptor of + * a device + * + * Return: 0 on success, non zero on error + */ +static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl, + int index) +{ + struct hostif_msg msg; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + int rv; + + /* Get report descriptor */ + client_data->report_descr_done = false; + memset(&msg, 0, sizeof(struct hostif_msg)); + msg.hdr.command = HOSTIF_GET_REPORT_DESCRIPTOR; + msg.hdr.device_id = client_data->hid_devices[index].dev_id; + rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg, + sizeof(struct hostif_msg)); + if (rv) + return rv; + + if (!client_data->report_descr_done) + wait_event_interruptible_timeout(client_data->init_wait, + client_data->report_descr_done, + 3 * HZ); + if (!client_data->report_descr_done) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: timed out for report descr\n"); + return -EIO; + } + if (!client_data->report_descr[index]) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: failed to alloc report descr\n"); + return -ENOMEM; + } + + return 0; +} + +/** + * hid_ishtp_cl_init() - Init function for ISHTP client + * @hid_ishtp_cl: ISHTP client instance + * @reset: true if called for init after reset + * + * This function complete the initializtion of the client. The summary of + * processing: + * - Send request to enumerate the hid clients + * Get the HID descriptor for each enumearated device + * Get report description of each device + * Register each device wik hid core by calling ishtp_hid_probe + * + * Return: 0 on success, non zero on error + */ +static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset) +{ + struct ishtp_device *dev; + unsigned long flags; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + int i; + int rv; + + dev_dbg(&client_data->cl_device->dev, "%s\n", __func__); + hid_ishtp_trace(client_data, "%s reset flag: %d\n", __func__, reset); + + rv = ishtp_cl_link(hid_ishtp_cl, ISHTP_HOST_CLIENT_ID_ANY); + if (rv) { + dev_err(&client_data->cl_device->dev, + "ishtp_cl_link failed\n"); + return -ENOMEM; + } + + client_data->init_done = 0; + + dev = hid_ishtp_cl->dev; + + /* Connect to FW client */ + hid_ishtp_cl->rx_ring_size = HID_CL_RX_RING_SIZE; + hid_ishtp_cl->tx_ring_size = HID_CL_TX_RING_SIZE; + + spin_lock_irqsave(&dev->fw_clients_lock, flags); + i = ishtp_fw_cl_by_uuid(dev, &hid_ishtp_guid); + if (i < 0) { + spin_unlock_irqrestore(&dev->fw_clients_lock, flags); + dev_err(&client_data->cl_device->dev, + "ish client uuid not found\n"); + return i; + } + hid_ishtp_cl->fw_client_id = dev->fw_clients[i].client_id; + spin_unlock_irqrestore(&dev->fw_clients_lock, flags); + hid_ishtp_cl->state = ISHTP_CL_CONNECTING; + + rv = ishtp_cl_connect(hid_ishtp_cl); + if (rv) { + dev_err(&client_data->cl_device->dev, + "client connect fail\n"); + goto err_cl_unlink; + } + + hid_ishtp_trace(client_data, "%s client connected\n", __func__); + + /* Register read callback */ + ishtp_register_event_cb(hid_ishtp_cl->device, ish_cl_event_cb); + + rv = ishtp_enum_enum_devices(hid_ishtp_cl); + if (rv) + goto err_cl_disconnect; + + hid_ishtp_trace(client_data, "%s enumerated device count %d\n", + __func__, client_data->num_hid_devices); + + for (i = 0; i < client_data->num_hid_devices; ++i) { + client_data->cur_hid_dev = i; + + rv = ishtp_get_hid_descriptor(hid_ishtp_cl, i); + if (rv) + goto err_cl_disconnect; + + rv = ishtp_get_report_descriptor(hid_ishtp_cl, i); + if (rv) + goto err_cl_disconnect; + + if (!reset) { + rv = ishtp_hid_probe(i, client_data); + if (rv) { + dev_err(&client_data->cl_device->dev, + "[hid-ish]: HID probe for #%u failed: %d\n", + i, rv); + goto err_cl_disconnect; + } + } + } /* for() on all hid devices */ + + client_data->init_done = 1; + client_data->suspended = false; + wake_up_interruptible(&client_data->ishtp_resume_wait); + hid_ishtp_trace(client_data, "%s successful init\n", __func__); + return 0; + +err_cl_disconnect: + hid_ishtp_cl->state = ISHTP_CL_DISCONNECTING; + ishtp_cl_disconnect(hid_ishtp_cl); +err_cl_unlink: + ishtp_cl_unlink(hid_ishtp_cl); + return rv; +} + +/** + * hid_ishtp_cl_deinit() - Deinit function for ISHTP client + * @hid_ishtp_cl: ISHTP client instance + * + * Unlink and free hid client + */ +static void hid_ishtp_cl_deinit(struct ishtp_cl *hid_ishtp_cl) +{ + ishtp_cl_unlink(hid_ishtp_cl); + ishtp_cl_flush_queues(hid_ishtp_cl); + + /* disband and free all Tx and Rx client-level rings */ + ishtp_cl_free(hid_ishtp_cl); +} + +static void hid_ishtp_cl_reset_handler(struct work_struct *work) +{ + struct ishtp_cl_data *client_data; + struct ishtp_cl *hid_ishtp_cl; + struct ishtp_cl_device *cl_device; + int retry; + int rv; + + client_data = container_of(work, struct ishtp_cl_data, work); + + hid_ishtp_cl = client_data->hid_ishtp_cl; + cl_device = client_data->cl_device; + + hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, + hid_ishtp_cl); + dev_dbg(&cl_device->dev, "%s\n", __func__); + + hid_ishtp_cl_deinit(hid_ishtp_cl); + + hid_ishtp_cl = ishtp_cl_allocate(cl_device->ishtp_dev); + if (!hid_ishtp_cl) + return; + + cl_device->driver_data = hid_ishtp_cl; + hid_ishtp_cl->client_data = client_data; + client_data->hid_ishtp_cl = hid_ishtp_cl; + + client_data->num_hid_devices = 0; + + for (retry = 0; retry < 3; ++retry) { + rv = hid_ishtp_cl_init(hid_ishtp_cl, 1); + if (!rv) + break; + dev_err(&client_data->cl_device->dev, "Retry reset init\n"); + } + if (rv) { + dev_err(&client_data->cl_device->dev, "Reset Failed\n"); + hid_ishtp_trace(client_data, "%s Failed hid_ishtp_cl %p\n", + __func__, hid_ishtp_cl); + } +} + +/** + * hid_ishtp_cl_probe() - ISHTP client driver probe + * @cl_device: ISHTP client device instance + * + * This function gets called on device create on ISHTP bus + * + * Return: 0 on success, non zero on error + */ +static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl *hid_ishtp_cl; + struct ishtp_cl_data *client_data; + int rv; + + if (!cl_device) + return -ENODEV; + + if (uuid_le_cmp(hid_ishtp_guid, + cl_device->fw_client->props.protocol_name) != 0) + return -ENODEV; + + client_data = devm_kzalloc(&cl_device->dev, sizeof(*client_data), + GFP_KERNEL); + if (!client_data) + return -ENOMEM; + + hid_ishtp_cl = ishtp_cl_allocate(cl_device->ishtp_dev); + if (!hid_ishtp_cl) + return -ENOMEM; + + cl_device->driver_data = hid_ishtp_cl; + hid_ishtp_cl->client_data = client_data; + client_data->hid_ishtp_cl = hid_ishtp_cl; + client_data->cl_device = cl_device; + + init_waitqueue_head(&client_data->init_wait); + init_waitqueue_head(&client_data->ishtp_resume_wait); + + INIT_WORK(&client_data->work, hid_ishtp_cl_reset_handler); + + rv = hid_ishtp_cl_init(hid_ishtp_cl, 0); + if (rv) { + ishtp_cl_free(hid_ishtp_cl); + return rv; + } + ishtp_get_device(cl_device); + + return 0; +} + +/** + * hid_ishtp_cl_remove() - ISHTP client driver remove + * @cl_device: ISHTP client device instance + * + * This function gets called on device remove on ISHTP bus + * + * Return: 0 + */ +static int hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + + hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, + hid_ishtp_cl); + + dev_dbg(&cl_device->dev, "%s\n", __func__); + hid_ishtp_cl->state = ISHTP_CL_DISCONNECTING; + ishtp_cl_disconnect(hid_ishtp_cl); + ishtp_put_device(cl_device); + ishtp_hid_remove(client_data); + hid_ishtp_cl_deinit(hid_ishtp_cl); + + hid_ishtp_cl = NULL; + + client_data->num_hid_devices = 0; + + return 0; +} + +/** + * hid_ishtp_cl_reset() - ISHTP client driver reset + * @cl_device: ISHTP client device instance + * + * This function gets called on device reset on ISHTP bus + * + * Return: 0 + */ +static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + + hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, + hid_ishtp_cl); + + schedule_work(&client_data->work); + + return 0; +} + +#define to_ishtp_cl_device(d) container_of(d, struct ishtp_cl_device, dev) + +/** + * hid_ishtp_cl_suspend() - ISHTP client driver suspend + * @device: device instance + * + * This function gets called on system suspend + * + * Return: 0 + */ +static int hid_ishtp_cl_suspend(struct device *device) +{ + struct ishtp_cl_device *cl_device = to_ishtp_cl_device(device); + struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + + hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, + hid_ishtp_cl); + client_data->suspended = true; + + return 0; +} + +/** + * hid_ishtp_cl_resume() - ISHTP client driver resume + * @device: device instance + * + * This function gets called on system resume + * + * Return: 0 + */ +static int hid_ishtp_cl_resume(struct device *device) +{ + struct ishtp_cl_device *cl_device = to_ishtp_cl_device(device); + struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; + struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; + + hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, + hid_ishtp_cl); + client_data->suspended = false; + return 0; +} + +static const struct dev_pm_ops hid_ishtp_pm_ops = { + .suspend = hid_ishtp_cl_suspend, + .resume = hid_ishtp_cl_resume, +}; + +static struct ishtp_cl_driver hid_ishtp_cl_driver = { + .name = "ish-hid", + .probe = hid_ishtp_cl_probe, + .remove = hid_ishtp_cl_remove, + .reset = hid_ishtp_cl_reset, + .driver.pm = &hid_ishtp_pm_ops, +}; + +static int __init ish_hid_init(void) +{ + int rv; + + /* Register ISHTP client device driver with ISHTP Bus */ + rv = ishtp_cl_driver_register(&hid_ishtp_cl_driver); + + return rv; + +} + +static void __exit ish_hid_exit(void) +{ + ishtp_cl_driver_unregister(&hid_ishtp_cl_driver); +} + +late_initcall(ish_hid_init); +module_exit(ish_hid_exit); + +MODULE_DESCRIPTION("ISH ISHTP HID client driver"); +/* Primary author */ +MODULE_AUTHOR("Daniel Drubin "); +/* + * Several modification for multi instance support + * suspend/resume and clean up + */ +MODULE_AUTHOR("Srinivas Pandruvada "); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ishtp:*"); diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c new file mode 100644 index 000000000000..277983aa1d90 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp-hid.c @@ -0,0 +1,246 @@ +/* + * ISHTP-HID glue driver. + * + * Copyright (c) 2012-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include "ishtp/client.h" +#include "ishtp-hid.h" + +/** + * ishtp_hid_parse() - hid-core .parse() callback + * @hid: hid device instance + * + * This function gets called during call to hid_add_device + * + * Return: 0 on success and non zero on error + */ +static int ishtp_hid_parse(struct hid_device *hid) +{ + struct ishtp_hid_data *hid_data = hid->driver_data; + struct ishtp_cl_data *client_data = hid_data->client_data; + int rv; + + rv = hid_parse_report(hid, client_data->report_descr[hid_data->index], + client_data->report_descr_size[hid_data->index]); + if (rv) + return rv; + + return 0; +} + +/* Empty callbacks with success return code */ +static int ishtp_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void ishtp_hid_stop(struct hid_device *hid) +{ +} + +static int ishtp_hid_open(struct hid_device *hid) +{ + return 0; +} + +static void ishtp_hid_close(struct hid_device *hid) +{ +} + +static int ishtp_raw_request(struct hid_device *hdev, unsigned char reportnum, + __u8 *buf, size_t len, unsigned char rtype, int reqtype) +{ + return 0; +} + +/** + * ishtp_hid_request() - hid-core .request() callback + * @hid: hid device instance + * @rep: pointer to hid_report + * @reqtype: type of req. [GET|SET]_REPORT + * + * This function is used to set/get feaure/input report. + */ +static void ishtp_hid_request(struct hid_device *hid, struct hid_report *rep, + int reqtype) +{ + struct ishtp_hid_data *hid_data = hid->driver_data; + /* the specific report length, just HID part of it */ + unsigned int len = ((rep->size - 1) >> 3) + 1 + (rep->id > 0); + char *buf; + unsigned int header_size = sizeof(struct hostif_msg); + + len += header_size; + + hid_data->request_done = false; + switch (reqtype) { + case HID_REQ_GET_REPORT: + hid_ishtp_get_report(hid, rep->id, rep->type); + break; + case HID_REQ_SET_REPORT: + /* + * Spare 7 bytes for 64b accesses through + * get/put_unaligned_le64() + */ + buf = kzalloc(len + 7, GFP_KERNEL); + if (!buf) + return; + + hid_output_report(rep, buf + header_size); + hid_ishtp_set_feature(hid, buf, len, rep->id); + kfree(buf); + break; + } +} + +/** + * ishtp_wait_for_response() - hid-core .wait() callback + * @hid: hid device instance + * + * This function is used to wait after get feaure/input report. + * + * Return: 0 on success and non zero on error + */ +static int ishtp_wait_for_response(struct hid_device *hid) +{ + struct ishtp_hid_data *hid_data = hid->driver_data; + struct ishtp_cl_data *client_data = hid_data->client_data; + int rv; + + hid_ishtp_trace(client_data, "%s hid %p\n", __func__, hid); + + rv = ishtp_hid_link_ready_wait(hid_data->client_data); + if (rv) + return rv; + + if (!hid_data->request_done) + wait_event_interruptible_timeout(hid_data->hid_wait, + hid_data->request_done, 3 * HZ); + + if (!hid_data->request_done) { + hid_err(hid, + "timeout waiting for response from ISHTP device\n"); + return -ETIMEDOUT; + } + hid_ishtp_trace(client_data, "%s hid %p done\n", __func__, hid); + + hid_data->request_done = false; + + return 0; +} + +/** + * ishtp_hid_wakeup() - Wakeup caller + * @hid: hid device instance + * + * This function will wakeup caller waiting for Get/Set feature report + */ +void ishtp_hid_wakeup(struct hid_device *hid) +{ + struct ishtp_hid_data *hid_data = hid->driver_data; + + hid_data->request_done = true; + wake_up_interruptible(&hid_data->hid_wait); +} + +static struct hid_ll_driver ishtp_hid_ll_driver = { + .parse = ishtp_hid_parse, + .start = ishtp_hid_start, + .stop = ishtp_hid_stop, + .open = ishtp_hid_open, + .close = ishtp_hid_close, + .request = ishtp_hid_request, + .wait = ishtp_wait_for_response, + .raw_request = ishtp_raw_request +}; + +/** + * ishtp_hid_probe() - hid register ll driver + * @cur_hid_dev: Index of hid device calling to register + * @client_data: Client data pointer + * + * This function is used to allocate and add HID device. + * + * Return: 0 on success, non zero on error + */ +int ishtp_hid_probe(unsigned int cur_hid_dev, + struct ishtp_cl_data *client_data) +{ + int rv; + struct hid_device *hid; + struct ishtp_hid_data *hid_data; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) { + rv = PTR_ERR(hid); + return -ENOMEM; + } + + hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL); + if (!hid_data) { + rv = -ENOMEM; + goto err_hid_data; + } + + hid_data->index = cur_hid_dev; + hid_data->client_data = client_data; + init_waitqueue_head(&hid_data->hid_wait); + + hid->driver_data = hid_data; + + client_data->hid_sensor_hubs[cur_hid_dev] = hid; + + hid->ll_driver = &ishtp_hid_ll_driver; + hid->bus = BUS_INTEL_ISHTP; + hid->dev.parent = &client_data->cl_device->dev; + hid->version = le16_to_cpu(ISH_HID_VERSION); + hid->vendor = le16_to_cpu(ISH_HID_VENDOR); + hid->product = le16_to_cpu(ISH_HID_PRODUCT); + snprintf(hid->name, sizeof(hid->name), "%s %04hX:%04hX", "hid-ishtp", + hid->vendor, hid->product); + + rv = hid_add_device(hid); + if (rv) + goto err_hid_device; + + hid_ishtp_trace(client_data, "%s allocated hid %p\n", __func__, hid); + + return 0; + +err_hid_device: + kfree(hid_data); +err_hid_data: + kfree(hid); + return rv; +} + +/** + * ishtp_hid_probe() - Remove registered hid device + * @client_data: client data pointer + * + * This function is used to destroy allocatd HID device. + */ +void ishtp_hid_remove(struct ishtp_cl_data *client_data) +{ + int i; + + for (i = 0; i < client_data->num_hid_devices; ++i) { + if (client_data->hid_sensor_hubs[i]) { + kfree(client_data->hid_sensor_hubs[i]->driver_data); + hid_destroy_device(client_data->hid_sensor_hubs[i]); + client_data->hid_sensor_hubs[i] = NULL; + } + } +} diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.h b/drivers/hid/intel-ish-hid/ishtp-hid.h new file mode 100644 index 000000000000..f5c7eb79b7b5 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp-hid.h @@ -0,0 +1,182 @@ +/* + * ISHTP-HID glue driver's definitions. + * + * Copyright (c) 2014-2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ +#ifndef ISHTP_HID__H +#define ISHTP_HID__H + +/* The fixed ISH product and vendor id */ +#define ISH_HID_VENDOR 0x8086 +#define ISH_HID_PRODUCT 0x22D8 +#define ISH_HID_VERSION 0x0200 + +#define CMD_MASK 0x7F +#define IS_RESPONSE 0x80 + +/* Used to dump to Linux trace buffer, if enabled */ +#define hid_ishtp_trace(client, ...) \ + client->cl_device->ishtp_dev->print_log(\ + client->cl_device->ishtp_dev, __VA_ARGS__) + +/* ISH Transport protocol (ISHTP in short) GUID */ +static const uuid_le hid_ishtp_guid = UUID_LE(0x33AECD58, 0xB679, 0x4E54, + 0x9B, 0xD9, 0xA0, 0x4D, 0x34, + 0xF0, 0xC2, 0x26); + +/* ISH HID message structure */ +struct hostif_msg_hdr { + uint8_t command; /* Bit 7: is_response */ + uint8_t device_id; + uint8_t status; + uint8_t flags; + uint16_t size; +} __packed; + +struct hostif_msg { + struct hostif_msg_hdr hdr; +} __packed; + +struct hostif_msg_to_sensor { + struct hostif_msg_hdr hdr; + uint8_t report_id; +} __packed; + +struct device_info { + uint32_t dev_id; + uint8_t dev_class; + uint16_t pid; + uint16_t vid; +} __packed; + +struct ishtp_version { + uint8_t major; + uint8_t minor; + uint8_t hotfix; + uint16_t build; +} __packed; + +/* struct for ISHTP aggregated input data */ +struct report_list { + uint16_t total_size; + uint8_t num_of_reports; + uint8_t flags; + struct { + uint16_t size_of_report; + uint8_t report[1]; + } __packed reports[1]; +} __packed; + +/* HOSTIF commands */ +#define HOSTIF_HID_COMMAND_BASE 0 +#define HOSTIF_GET_HID_DESCRIPTOR 0 +#define HOSTIF_GET_REPORT_DESCRIPTOR 1 +#define HOSTIF_GET_FEATURE_REPORT 2 +#define HOSTIF_SET_FEATURE_REPORT 3 +#define HOSTIF_GET_INPUT_REPORT 4 +#define HOSTIF_PUBLISH_INPUT_REPORT 5 +#define HOSTIF_PUBLISH_INPUT_REPORT_LIST 6 +#define HOSTIF_DM_COMMAND_BASE 32 +#define HOSTIF_DM_ENUM_DEVICES 33 +#define HOSTIF_DM_ADD_DEVICE 34 + +#define MAX_HID_DEVICES 32 + +/** + * struct ishtp_cl_data - Encapsulate per ISH TP HID Client + * @enum_device_done: Enum devices response complete flag + * @hid_descr_done: HID descriptor complete flag + * @report_descr_done: Get report descriptor complete flag + * @init_done: Init process completed successfully + * @suspended: System is under suspend state or in progress + * @num_hid_devices: Number of HID devices enumerated in this client + * @cur_hid_dev: This keeps track of the device index for which + * initialization and registration with HID core + * in progress. + * @hid_devices: Store vid/pid/devid for each enumerated HID device + * @report_descr: Stores the raw report descriptors for each HID device + * @report_descr_size: Report description of size of above repo_descr[] + * @hid_sensor_hubs: Pointer to hid_device for all HID device, so that + * when clients are removed, they can be freed + * @hid_descr: Pointer to hid descriptor for each enumerated hid + * device + * @hid_descr_size: Size of each above report descriptor + * @init_wait: Wait queue to wait during initialization, where the + * client send message to ISH FW and wait for response + * @ishtp_hid_wait: The wait for get report during wait callback from hid + * core + * @bad_recv_cnt: Running count of packets received with error + * @multi_packet_cnt: Count of fragmented packet count + * + * This structure is used to store completion flags and per client data like + * like report description, number of HID devices etc. + */ +struct ishtp_cl_data { + /* completion flags */ + bool enum_devices_done; + bool hid_descr_done; + bool report_descr_done; + bool init_done; + bool suspended; + + unsigned int num_hid_devices; + unsigned int cur_hid_dev; + unsigned int hid_dev_count; + + struct device_info *hid_devices; + unsigned char *report_descr[MAX_HID_DEVICES]; + int report_descr_size[MAX_HID_DEVICES]; + struct hid_device *hid_sensor_hubs[MAX_HID_DEVICES]; + unsigned char *hid_descr[MAX_HID_DEVICES]; + int hid_descr_size[MAX_HID_DEVICES]; + + wait_queue_head_t init_wait; + wait_queue_head_t ishtp_resume_wait; + struct ishtp_cl *hid_ishtp_cl; + + /* Statistics */ + unsigned int bad_recv_cnt; + int multi_packet_cnt; + + struct work_struct work; + struct ishtp_cl_device *cl_device; +}; + +/** + * struct ishtp_hid_data - Per instance HID data + * @index: Device index in the order of enumeration + * @request_done: Get Feature/Input report complete flag + * used during get/set request from hid core + * @client_data: Link to the client instance + * @hid_wait: Completion waitq + * + * Used to tie hid hid->driver data to driver client instance + */ +struct ishtp_hid_data { + int index; + bool request_done; + struct ishtp_cl_data *client_data; + wait_queue_head_t hid_wait; +}; + +/* Interface functions between HID LL driver and ISH TP client */ +void hid_ishtp_set_feature(struct hid_device *hid, char *buf, unsigned int len, + int report_id); +void hid_ishtp_get_report(struct hid_device *hid, int report_id, + int report_type); +int ishtp_hid_probe(unsigned int cur_hid_dev, + struct ishtp_cl_data *client_data); +void ishtp_hid_remove(struct ishtp_cl_data *client_data); +int ishtp_hid_link_ready_wait(struct ishtp_cl_data *client_data); +void ishtp_hid_wakeup(struct hid_device *hid); + +#endif /* ISHTP_HID__H */ diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h index c51494119817..e794f7bee22f 100644 --- a/include/uapi/linux/input.h +++ b/include/uapi/linux/input.h @@ -248,6 +248,7 @@ struct input_mask { #define BUS_SPI 0x1C #define BUS_RMI 0x1D #define BUS_CEC 0x1E +#define BUS_INTEL_ISHTP 0x1F /* * MT_TOOL types -- cgit v1.2.3 From 930fafd9afb435129b49cc91e5a1267d6ddd2b66 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 7 Aug 2016 02:25:38 -0700 Subject: HID: hid-sensor-hub: Add ISH quirk Need enum quirk to change the base of enums to 1 for power and report descriptors. Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/hid-sensor-hub.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 3d5ba5b51af3..658a607dc6d9 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -16,6 +16,7 @@ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ + #include #include #include @@ -798,6 +799,9 @@ static const struct hid_device_id sensor_hub_devices[] = { { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_ITE, USB_DEVICE_ID_ITE_LENOVO_YOGA900), .driver_data = HID_SENSOR_HUB_ENUM_QUIRK}, + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, USB_VENDOR_ID_INTEL_0, + 0x22D8), + .driver_data = HID_SENSOR_HUB_ENUM_QUIRK}, { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, HID_ANY_ID, HID_ANY_ID) }, { } -- cgit v1.2.3 From 8fb861fa0718c414f42588eb16eb81f1893c3522 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 7 Aug 2016 02:25:39 -0700 Subject: MAINTAINERS: Add Intel ISH Add maintainer entry for Intel Integrated Sensor Hub Signed-off-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 6cbef714a975..5b5ca76ccecc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5992,6 +5992,13 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git S: Supported F: drivers/idle/intel_idle.c +INTEL INTEGRATED SENSOR HUB DRIVER +M: Srinivas Pandruvada +M: Jiri Kosina +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/intel-ish-hid/ + INTEL PSTATE DRIVER M: Srinivas Pandruvada M: Len Brown -- cgit v1.2.3 From 67c0fe420ea716268b761de72ef0420b7b02bc96 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sun, 21 Aug 2016 15:27:29 +0000 Subject: HID: intel-ish-hid: use list_move_tail instead of list_del/list_add_tail Using list_move_tail() instead of list_del() + list_add_tail(). Signed-off-by: Wei Yongjun Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ipc/ipc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index 851029bf17bc..e2517c11e0ee 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -448,8 +448,7 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) spin_lock_irqsave(&dev->wr_processing_spinlock, flags); list_for_each_entry_safe(processing, next, &dev->wr_processing_list_head.link, link) { - list_del(&processing->link); - list_add_tail(&processing->link, &dev->wr_free_list_head.link); + list_move_tail(&processing->link, &dev->wr_free_list_head.link); } spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); -- cgit v1.2.3 From 37becf6e603236a19660e76a3b82f9cc25972e2d Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sun, 21 Aug 2016 15:28:36 +0000 Subject: HID: intel-ish-hid: use module_pci_driver to simplify the code Use the module_pci_driver() macro to make the code simpler by eliminating module_init and module_exit calls. Signed-off-by: Wei Yongjun Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ipc/pci-ish.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index b14d6f4774b9..42f0beeb09fd 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -311,18 +311,7 @@ static struct pci_driver ish_driver = { .driver.pm = ISHTP_ISH_PM_OPS, }; -static int __init ish_driver_init(void) -{ - return pci_register_driver(&ish_driver); -} - -static void __exit ish_driver_exit(void) -{ - pci_unregister_driver(&ish_driver); -} - -module_init(ish_driver_init); -module_exit(ish_driver_exit); +module_pci_driver(ish_driver); /* Original author */ MODULE_AUTHOR("Daniel Drubin "); -- cgit v1.2.3 From e8c61135956ce35fcd7661f52ed3a7e21eb86bfb Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sun, 21 Aug 2016 15:29:58 +0000 Subject: HID: intel_ish-hid: Convert list_for_each to entry variant convert list_for_each() to list_for_each_entry() where applicable. Signed-off-by: Wei Yongjun Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp/bus.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index 0183eacaf6c9..8a1bb8c25357 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -380,11 +380,9 @@ static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev, struct ishtp_cl_device *device; int status; unsigned long flags; - struct list_head *pos; spin_lock_irqsave(&dev->device_list_lock, flags); - list_for_each(pos, &dev->device_list) { - device = list_entry(pos, struct ishtp_cl_device, device_link); + list_for_each_entry(device, &dev->device_list, device_link) { if (!strcmp(name, dev_name(&device->dev))) { device->fw_client = &dev->fw_clients[ dev->fw_client_presentation_num - 1]; -- cgit v1.2.3 From 6cf5c1c7162878a99c2fd335d52a7d10079af6b7 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sun, 21 Aug 2016 15:30:50 +0000 Subject: HID: intel_ish-hid: Use kzalloc instead of kmalloc/memset Use kzalloc rather than kmalloc followed by memset with 0. Generated by: scripts/coccinelle/api/alloc/kzalloc-simple.cocci Signed-off-by: Wei Yongjun Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp/client-buffers.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c index 3f5ce5ee687e..b9b917d2d50d 100644 --- a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -73,11 +73,10 @@ int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl) for (j = 0; j < cl->tx_ring_size; ++j) { struct ishtp_cl_tx_ring *tx_buf; - tx_buf = kmalloc(sizeof(struct ishtp_cl_tx_ring), GFP_KERNEL); + tx_buf = kzalloc(sizeof(struct ishtp_cl_tx_ring), GFP_KERNEL); if (!tx_buf) goto out; - memset(tx_buf, 0, sizeof(struct ishtp_cl_tx_ring)); tx_buf->send_buf.data = kmalloc(len, GFP_KERNEL); if (!tx_buf->send_buf.data) { kfree(tx_buf); -- cgit v1.2.3 From 814cb72e25afccdd24855a26c934c1d3dfbf808e Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sun, 21 Aug 2016 15:31:35 +0000 Subject: HID: intel_ish-hid: Remove duplicated include from bus.c Remove duplicated include. Signed-off-by: Wei Yongjun Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp/bus.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c index 8a1bb8c25357..256521509d20 100644 --- a/drivers/hid/intel-ish-hid/ishtp/bus.c +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include -- cgit v1.2.3 From 1989dada7ce07848196991c9ebf25ff9c5f14d4e Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 13 Sep 2016 11:52:37 +0200 Subject: HID: input: ignore System Control application usages if not System Controls Microsoft is reusing its report descriptor again and again, and part of it looks like this: 0x05, 0x01, // Usage Page (Generic Desktop) 299 0x09, 0x80, // Usage (System Control) 301 0xa1, 0x01, // Collection (Application) 303 0x85, 0x03, // Report ID (3) 305 0x19, 0x00, // Usage Minimum (0) 307 0x29, 0xff, // Usage Maximum (255) 309 0x15, 0x00, // Logical Minimum (0) 311 0x26, 0xff, 0x00, // Logical Maximum (255) 313 0x81, 0x00, // Input (Data,Arr,Abs) 316 0xc0, // End Collection 318 While there is nothing wrong in term of processing, we do however blindly map the full usage range (it's an array) from 0x00 to 0xff, which creates some interesting axis, like ABS_X|Y, and a bunch of ABS_MISC + n. While libinput and other stacks don't care that much (we can detect them), joydev is very happy and attaches itself to the mouse or keyboard. The problem is that joydev now handles the device as a joystick, but given that we have a HID array, it sets all the ABS_* values to 0. And in its world, 0 means -32767 (minimum value), which sends spurious events to games (think Steam). It looks like hid-microsoft tries to tackle the very same problem with its .report_fixup callback. But fixing the report descriptor is an endless task and is quite obfuscated. So take the hammer, and decide that if the application is meant to be System Control, any other usage not in the System Control range should be ignored. Link: https://bugzilla.redhat.com/show_bug.cgi?id=1325354 Link: https://bugzilla.kernel.org/show_bug.cgi?id=28912 Link: https://github.com/ValveSoftware/steam-for-linux/issues/3384 Link: https://bugzilla.redhat.com/show_bug.cgi?id=1325354 Link: https://bugzilla.kernel.org/show_bug.cgi?id=37982 Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index bcfaf32d9e5e..058919d5b2bb 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -604,6 +604,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel break; } + /* + * Some lazy vendors declare 255 usages for System Control, + * leading to the creation of ABS_X|Y axis and too many others. + * It wouldn't be a problem if joydev doesn't consider the + * device as a joystick then. + */ + if (field->application == HID_GD_SYSTEM_CONTROL) + goto ignore; + if ((usage->hid & 0xf0) == 0x90) { /* D-pad */ switch (usage->hid) { case HID_GD_UP: usage->hat_dir = 1; break; -- cgit v1.2.3 From ce7ef551b9102a0a89fd413e680680bcd62c23a9 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 14 Sep 2016 21:38:12 +0300 Subject: HID: Remove broken links to tablet descriptions Remove comments with broken links to tablet descriptions from hid-kye/uclogic/waltop drivers. They pointed to now dead DIGImend project wiki at sf.net. Even though the DIGImend project still hosts descriptions, now at digimend.github.io, it's better to avoid them going stale again, and let anyone interested search the web instead. Signed-off-by: Nikolai Kondrashov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-kye.c | 15 --------------- drivers/hid/hid-uclogic.c | 27 --------------------------- drivers/hid/hid-waltop.c | 36 ------------------------------------ 3 files changed, 78 deletions(-) diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c index 32e6d8d9ded0..a3b70df9f912 100644 --- a/drivers/hid/hid-kye.c +++ b/drivers/hid/hid-kye.c @@ -19,11 +19,6 @@ #include "hid-ids.h" -/* - * See EasyPen i405X description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=KYE_EasyPen_i405X - */ - /* Original EasyPen i405X report descriptor size */ #define EASYPEN_I405X_RDESC_ORIG_SIZE 476 @@ -82,11 +77,6 @@ static __u8 easypen_i405x_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See MousePen i608X description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=KYE_MousePen_i608X - */ - /* Original MousePen i608X report descriptor size */ #define MOUSEPEN_I608X_RDESC_ORIG_SIZE 476 @@ -186,11 +176,6 @@ static __u8 mousepen_i608x_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See EasyPen M610X description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=KYE_EasyPen_M610X - */ - /* Original EasyPen M610X report descriptor size */ #define EASYPEN_M610X_RDESC_ORIG_SIZE 476 diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index 85ac43517e3f..6a59f6a28f7e 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic.c @@ -21,13 +21,6 @@ #include "hid-ids.h" -/* - * See WPXXXXU model descriptions, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP4030U - * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP5540U - * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP8060U - */ - /* Size of the original descriptor of WPXXXXU tablets */ #define WPXXXXU_RDESC_ORIG_SIZE 212 @@ -221,11 +214,6 @@ static __u8 wp8060u_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See WP1062 description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP1062 - */ - /* Size of the original descriptor of WP1062 tablet */ #define WP1062_RDESC_ORIG_SIZE 254 @@ -274,11 +262,6 @@ static __u8 wp1062_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See PF1209 description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_PF1209 - */ - /* Size of the original descriptor of PF1209 tablet */ #define PF1209_RDESC_ORIG_SIZE 234 @@ -356,11 +339,6 @@ static __u8 pf1209_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See TWHL850 description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Wireless_Tablet_TWHL850 - */ - /* Size of the original descriptors of TWHL850 tablet */ #define TWHL850_RDESC_ORIG_SIZE0 182 #define TWHL850_RDESC_ORIG_SIZE1 161 @@ -469,11 +447,6 @@ static __u8 twhl850_rdesc_fixed2[] = { 0xC0 /* End Collection */ }; -/* - * See TWHA60 description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_TWHA60 - */ - /* Size of the original descriptors of TWHA60 tablet */ #define TWHA60_RDESC_ORIG_SIZE0 254 #define TWHA60_RDESC_ORIG_SIZE1 139 diff --git a/drivers/hid/hid-waltop.c b/drivers/hid/hid-waltop.c index 059931d7b392..a91aabe4a70a 100644 --- a/drivers/hid/hid-waltop.c +++ b/drivers/hid/hid-waltop.c @@ -42,11 +42,6 @@ * 02 16 02 ink */ -/* - * See Slim Tablet 5.8 inch description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Slim_Tablet_5.8%22 - */ - /* Size of the original report descriptor of Slim Tablet 5.8 inch */ #define SLIM_TABLET_5_8_INCH_RDESC_ORIG_SIZE 222 @@ -98,11 +93,6 @@ static __u8 slim_tablet_5_8_inch_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See Slim Tablet 12.1 inch description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Slim_Tablet_12.1%22 - */ - /* Size of the original report descriptor of Slim Tablet 12.1 inch */ #define SLIM_TABLET_12_1_INCH_RDESC_ORIG_SIZE 269 @@ -154,11 +144,6 @@ static __u8 slim_tablet_12_1_inch_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See Q Pad description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Q_Pad - */ - /* Size of the original report descriptor of Q Pad */ #define Q_PAD_RDESC_ORIG_SIZE 241 @@ -210,11 +195,6 @@ static __u8 q_pad_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See description, device and HID report descriptors of tablet with PID 0038 at - * http://sf.net/apps/mediawiki/digimend/?title=Waltop_PID_0038 - */ - /* Size of the original report descriptor of tablet with PID 0038 */ #define PID_0038_RDESC_ORIG_SIZE 241 @@ -268,11 +248,6 @@ static __u8 pid_0038_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See Media Tablet 10.6 inch description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Media_Tablet_10.6%22 - */ - /* Size of the original report descriptor of Media Tablet 10.6 inch */ #define MEDIA_TABLET_10_6_INCH_RDESC_ORIG_SIZE 300 @@ -386,11 +361,6 @@ static __u8 media_tablet_10_6_inch_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See Media Tablet 14.1 inch description, device and HID report descriptors at - * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Media_Tablet_14.1%22 - */ - /* Size of the original report descriptor of Media Tablet 14.1 inch */ #define MEDIA_TABLET_14_1_INCH_RDESC_ORIG_SIZE 309 @@ -502,12 +472,6 @@ static __u8 media_tablet_14_1_inch_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* - * See Sirius Battery Free Tablet description, device and HID report descriptors - * at - * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Sirius_Battery_Free_Tablet - */ - /* Size of the original report descriptor of Sirius Battery Free Tablet */ #define SIRIUS_BATTERY_FREE_TABLET_RDESC_ORIG_SIZE 335 -- cgit v1.2.3 From 102750b8c96cb228d806bdd9a9f13c4d93c07fa8 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 14 Sep 2016 21:38:13 +0300 Subject: HID: kye: Rename MousePen i608X v2 macro Rename the device ID macro for the second version of KYE MousePen i608x graphics tablet to have "V" in its name to signify that "2" is a version. I.e. USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2 -> USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2 This also makes applying additional fixes from DIGImend easier. Signed-off-by: Nikolai Kondrashov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 2 +- drivers/hid/hid-ids.h | 2 +- drivers/hid/hid-kye.c | 6 +++--- drivers/hid/usbhid/hid-quirks.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 08f53c7fd513..758619d63ae8 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1916,7 +1916,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_I405X) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X) }, - { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912) }, { HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 4ed9a4fdfea7..5963cf1986a8 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -565,7 +565,7 @@ #define USB_DEVICE_ID_KYE_GPEN_560 0x5003 #define USB_DEVICE_ID_KYE_EASYPEN_I405X 0x5010 #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X 0x5011 -#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2 0x501a +#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2 0x501a #define USB_DEVICE_ID_KYE_EASYPEN_M610X 0x5013 #define USB_DEVICE_ID_KYE_PENSKETCH_M912 0x5015 diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c index a3b70df9f912..ee5ca1f78caf 100644 --- a/drivers/hid/hid-kye.c +++ b/drivers/hid/hid-kye.c @@ -439,7 +439,7 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc, } break; case USB_DEVICE_ID_KYE_MOUSEPEN_I608X: - case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2: + case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2: if (*rsize == MOUSEPEN_I608X_RDESC_ORIG_SIZE) { rdesc = mousepen_i608x_rdesc_fixed; *rsize = sizeof(mousepen_i608x_rdesc_fixed); @@ -538,7 +538,7 @@ static int kye_probe(struct hid_device *hdev, const struct hid_device_id *id) switch (id->product) { case USB_DEVICE_ID_KYE_EASYPEN_I405X: case USB_DEVICE_ID_KYE_MOUSEPEN_I608X: - case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2: + case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2: case USB_DEVICE_ID_KYE_EASYPEN_M610X: case USB_DEVICE_ID_KYE_PENSKETCH_M912: ret = kye_tablet_enable(hdev); @@ -571,7 +571,7 @@ static const struct hid_device_id kye_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, - USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2) }, + USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index b4b8c6abb03e..ab3a31cf8986 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -143,7 +143,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X, HID_QUIRK_MULTI_INPUT }, - { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2, HID_QUIRK_MULTI_INPUT }, + { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912, HID_QUIRK_MULTI_INPUT }, { USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE, HID_QUIRK_NO_INIT_REPORTS }, -- cgit v1.2.3 From 515cdc19754d0df37d3813b6cb4c65d5ed1df0f2 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 14 Sep 2016 21:38:14 +0300 Subject: HID: kye: Fix MousePen i608X v2 report descriptor Add a dedicated, fixed report descriptor for the second version of KYE MousePen i608X graphics tablet. The descriptor fixes pressure and drawing area ranges. Signed-off-by: Nikolai Kondrashov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-kye.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c index ee5ca1f78caf..0dd1167b2c9b 100644 --- a/drivers/hid/hid-kye.c +++ b/drivers/hid/hid-kye.c @@ -176,6 +176,105 @@ static __u8 mousepen_i608x_rdesc_fixed[] = { 0xC0 /* End Collection */ }; +/* Original MousePen i608X v2 report descriptor size */ +#define MOUSEPEN_I608X_V2_RDESC_ORIG_SIZE 482 + +/* Fixed MousePen i608X v2 report descriptor */ +static __u8 mousepen_i608x_v2_rdesc_fixed[] = { + 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ + 0x09, 0x01, /* Usage (01h), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x05, /* Report ID (5), */ + 0x09, 0x01, /* Usage (01h), */ + 0x15, 0x80, /* Logical Minimum (-128), */ + 0x25, 0x7F, /* Logical Maximum (127), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x07, /* Report Count (7), */ + 0xB1, 0x02, /* Feature (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x02, /* Usage (Pen), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x10, /* Report ID (16), */ + 0x09, 0x20, /* Usage (Stylus), */ + 0xA0, /* Collection (Physical), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x09, 0x44, /* Usage (Barrel Switch), */ + 0x09, 0x46, /* Usage (Tablet Pick), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x04, /* Report Count (4), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x09, 0x32, /* Usage (In Range), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x75, 0x10, /* Report Size (16), */ + 0x95, 0x01, /* Report Count (1), */ + 0xA4, /* Push, */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x55, 0xFD, /* Unit Exponent (-3), */ + 0x65, 0x13, /* Unit (Inch), */ + 0x34, /* Physical Minimum (0), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x40, 0x1F, /* Physical Maximum (8000), */ + 0x27, 0x00, 0xA0, 0x00, 0x00, /* Logical Maximum (40960), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x31, /* Usage (Y), */ + 0x46, 0x70, 0x17, /* Physical Maximum (6000), */ + 0x26, 0x00, 0x78, /* Logical Maximum (30720), */ + 0x81, 0x02, /* Input (Variable), */ + 0xB4, /* Pop, */ + 0x09, 0x30, /* Usage (Tip Pressure), */ + 0x26, 0xFF, 0x07, /* Logical Maximum (2047), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0xC0, /* End Collection, */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x02, /* Usage (Mouse), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x11, /* Report ID (17), */ + 0x09, 0x01, /* Usage (Pointer), */ + 0xA0, /* Collection (Physical), */ + 0x14, /* Logical Minimum (0), */ + 0xA4, /* Push, */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x75, 0x01, /* Report Size (1), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x03, /* Usage Maximum (03h), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x05, /* Report Count (5), */ + 0x81, 0x01, /* Input (Constant), */ + 0xB4, /* Pop, */ + 0x95, 0x01, /* Report Count (1), */ + 0xA4, /* Push, */ + 0x55, 0xFD, /* Unit Exponent (-3), */ + 0x65, 0x13, /* Unit (Inch), */ + 0x34, /* Physical Minimum (0), */ + 0x75, 0x10, /* Report Size (16), */ + 0x09, 0x30, /* Usage (X), */ + 0x46, 0x40, 0x1F, /* Physical Maximum (8000), */ + 0x27, 0x00, 0xA0, 0x00, 0x00, /* Logical Maximum (40960), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x31, /* Usage (Y), */ + 0x46, 0x70, 0x17, /* Physical Maximum (6000), */ + 0x26, 0x00, 0x78, /* Logical Maximum (30720), */ + 0x81, 0x02, /* Input (Variable), */ + 0xB4, /* Pop, */ + 0x75, 0x08, /* Report Size (8), */ + 0x09, 0x38, /* Usage (Wheel), */ + 0x15, 0xFF, /* Logical Minimum (-1), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x81, 0x06, /* Input (Variable, Relative), */ + 0x81, 0x01, /* Input (Constant), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; + /* Original EasyPen M610X report descriptor size */ #define EASYPEN_M610X_RDESC_ORIG_SIZE 476 @@ -439,12 +538,17 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc, } break; case USB_DEVICE_ID_KYE_MOUSEPEN_I608X: - case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2: if (*rsize == MOUSEPEN_I608X_RDESC_ORIG_SIZE) { rdesc = mousepen_i608x_rdesc_fixed; *rsize = sizeof(mousepen_i608x_rdesc_fixed); } break; + case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2: + if (*rsize == MOUSEPEN_I608X_V2_RDESC_ORIG_SIZE) { + rdesc = mousepen_i608x_v2_rdesc_fixed; + *rsize = sizeof(mousepen_i608x_v2_rdesc_fixed); + } + break; case USB_DEVICE_ID_KYE_EASYPEN_M610X: if (*rsize == EASYPEN_M610X_RDESC_ORIG_SIZE) { rdesc = easypen_m610x_rdesc_fixed; -- cgit v1.2.3 From 82d2efd2db4465b467b52d37142b1cc87b876506 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 14 Sep 2016 21:38:15 +0300 Subject: HID: uclogic: Remove allocation failure messages Remove unnecessary allocation failure messages from hid-uclogic.c, following the checkpatch.pl recommendations. Signed-off-by: Nikolai Kondrashov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index 6a59f6a28f7e..790528e47000 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic.c @@ -777,7 +777,6 @@ static int uclogic_tablet_enable(struct hid_device *hdev) len = UCLOGIC_PRM_NUM * sizeof(*buf); buf = kmalloc(len, GFP_KERNEL); if (buf == NULL) { - hid_err(hdev, "failed to allocate parameter buffer\n"); rc = -ENOMEM; goto cleanup; } @@ -821,7 +820,6 @@ static int uclogic_tablet_enable(struct hid_device *hdev) sizeof(uclogic_tablet_rdesc_template), GFP_KERNEL); if (drvdata->rdesc == NULL) { - hid_err(hdev, "failed to allocate fixed rdesc\n"); rc = -ENOMEM; goto cleanup; } -- cgit v1.2.3 From 21003968cdc36e3be2091170fe76bec3f282f155 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 14 Sep 2016 21:38:16 +0300 Subject: HID: uclogic: Switch to reporting abstract button events Based on a patch from: Nikolai Kondrashov Enable abstract keyboard mode for Huion tablets, which makes them report frame buttons using the pen interface and report ID. Divert these reports to a virtual report ID describing them. This makes the tablet compatible with xf86-input-wacom and libinput, but stops the frame buttons from reporting keyboard events. Signed-off-by: Nikolai Kondrashov Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic.c | 102 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index 790528e47000..73c040d56954 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic.c @@ -586,6 +586,27 @@ static const __u8 uclogic_tablet_rdesc_template[] = { 0xC0 /* End Collection */ }; +/* Fixed virtual pad report descriptor */ +static const __u8 uclogic_buttonpad_rdesc[] = { + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x07, /* Usage (Keypad), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0xF7, /* Report ID (247), */ + 0x05, 0x0D, /* Usage Page (Digitizers), */ + 0x09, 0x39, /* Usage (Tablet Function Keys), */ + 0xA0, /* Collection (Physical), */ + 0x05, 0x09, /* Usage Page (Button), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x18, /* Report Count (24), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x08, /* Usage Maximum (08h), */ + 0x95, 0x08, /* Report Count (8), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection */ + 0xC0 /* End Collection */ +}; + /* Parameter indices */ enum uclogic_prm { UCLOGIC_PRM_X_LM = 1, @@ -601,6 +622,7 @@ struct uclogic_drvdata { unsigned int rsize; bool invert_pen_inrange; bool ignore_pen_usage; + bool has_virtual_pad_interface; }; static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, @@ -847,6 +869,69 @@ cleanup: return rc; } +/** + * Enable actual button mode. + * + * @hdev: HID device + */ +static int uclogic_button_enable(struct hid_device *hdev) +{ + int rc; + struct usb_device *usb_dev = hid_to_usb_dev(hdev); + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + char *str_buf; + size_t str_len = 16; + unsigned char *rdesc; + size_t rdesc_len; + + str_buf = kzalloc(str_len, GFP_KERNEL); + if (str_buf == NULL) { + rc = -ENOMEM; + goto cleanup; + } + + /* Enable abstract keyboard mode */ + rc = usb_string(usb_dev, 0x7b, str_buf, str_len); + if (rc == -EPIPE) { + hid_info(hdev, "button mode setting not found\n"); + rc = 0; + goto cleanup; + } else if (rc < 0) { + hid_err(hdev, "failed to enable abstract keyboard\n"); + goto cleanup; + } else if (strncmp(str_buf, "HK On", rc)) { + hid_info(hdev, "invalid answer when requesting buttons: '%s'\n", + str_buf); + rc = -EINVAL; + goto cleanup; + } + + /* Re-allocate fixed report descriptor */ + rdesc_len = drvdata->rsize + sizeof(uclogic_buttonpad_rdesc); + rdesc = devm_kzalloc(&hdev->dev, rdesc_len, GFP_KERNEL); + if (!rdesc) { + rc = -ENOMEM; + goto cleanup; + } + + memcpy(rdesc, drvdata->rdesc, drvdata->rsize); + + /* Append the buttonpad descriptor */ + memcpy(rdesc + drvdata->rsize, uclogic_buttonpad_rdesc, + sizeof(uclogic_buttonpad_rdesc)); + + /* clean up old rdesc and use the new one */ + drvdata->rsize = rdesc_len; + devm_kfree(&hdev->dev, drvdata->rdesc); + drvdata->rdesc = rdesc; + + rc = 0; + +cleanup: + kfree(str_buf); + return rc; +} + static int uclogic_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -878,6 +963,9 @@ static int uclogic_probe(struct hid_device *hdev, return rc; } drvdata->invert_pen_inrange = true; + + rc = uclogic_button_enable(hdev); + drvdata->has_virtual_pad_interface = !rc; } else { drvdata->ignore_pen_usage = true; } @@ -904,12 +992,16 @@ static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, { struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - if ((drvdata->invert_pen_inrange) && - (report->type == HID_INPUT_REPORT) && + if ((report->type == HID_INPUT_REPORT) && (report->id == UCLOGIC_PEN_REPORT_ID) && - (size >= 2)) - /* Invert the in-range bit */ - data[1] ^= 0x40; + (size >= 2)) { + if (drvdata->has_virtual_pad_interface && (data[1] & 0x20)) + /* Change to virtual frame button report ID */ + data[0] = 0xf7; + else if (drvdata->invert_pen_inrange) + /* Invert the in-range bit */ + data[1] ^= 0x40; + } return 0; } -- cgit v1.2.3 From 4b7e7e5eda1d415158c04c77b0b2a1648fa59815 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 14 Sep 2016 21:38:17 +0300 Subject: HID: uclogic: Add support for several more tablets Add more product IDs to hid-uclogic.c driver to support several more tablet models, including: UC-Logic DrawImage G3 aka Ugee G3 UC-Logic SP1001 aka Ugee SP1001 aka Yiynova UC-Logic SP-1001 Ugee 1000L Ugee M708 Yiynova DP10U aka Yiynova DP10U+ Yiynova MSP19U Yiynova MSP19U+ Yiynova MVP10U aka Yiynova MVP10U IPS Yiynova MVP10UHD+IPS Yiynova MVP22U+ Signed-off-by: Nikolai Kondrashov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 4 ++++ drivers/hid/hid-ids.h | 4 ++++ drivers/hid/hid-uclogic.c | 8 ++++++++ 3 files changed, 16 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 758619d63ae8..df2770a5291f 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2083,6 +2083,10 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SUPER_JOY_BOX_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 5963cf1986a8..6ef9dbf84059 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -996,6 +996,10 @@ #define USB_DEVICE_ID_UCLOGIC_TABLET_WP1062 0x0064 #define USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850 0x0522 #define USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60 0x0781 +#define USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3 0x3031 +#define USB_DEVICE_ID_UGEE_TABLET_81 0x0081 +#define USB_DEVICE_ID_UGEE_TABLET_45 0x0045 +#define USB_DEVICE_ID_YIYNOVA_TABLET 0x004d #define USB_VENDOR_ID_UNITEC 0x227d #define USB_DEVICE_ID_UNITEC_USB_TOUCH_0709 0x0709 diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index 73c040d56954..f4377c458787 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic.c @@ -955,6 +955,10 @@ static int uclogic_probe(struct hid_device *hdev, switch (id->product) { case USB_DEVICE_ID_HUION_TABLET: + case USB_DEVICE_ID_YIYNOVA_TABLET: + case USB_DEVICE_ID_UGEE_TABLET_81: + case USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3: + case USB_DEVICE_ID_UGEE_TABLET_45: /* If this is the pen interface */ if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { rc = uclogic_tablet_enable(hdev); @@ -1023,6 +1027,10 @@ static const struct hid_device_id uclogic_devices[] = { USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) }, { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, { } }; MODULE_DEVICE_TABLE(hid, uclogic_devices); -- cgit v1.2.3 From 18e3dfac16ae66209fdb570a429089be1907caa1 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 14 Sep 2016 21:38:18 +0300 Subject: HID: uclogic: Support UGTizer GP0610 partially Add partial support for the UGTizer GP0610 tablet (aka iBall PF1064U, aka UGTizer GT1060) to the hid-uclogic.c driver. The pen input is supported fully, but frame buttons and wheel don't work yet. Signed-off-by: Nikolai Kondrashov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-uclogic.c | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index df2770a5291f..e21ac0b9f566 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2087,6 +2087,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SUPER_JOY_BOX_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 6ef9dbf84059..15067a902e8e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1089,4 +1089,7 @@ #define USB_DEVICE_ID_RAPHNET_2NES2SNES 0x0002 #define USB_DEVICE_ID_RAPHNET_4NES4SNES 0x0003 +#define USB_VENDOR_ID_UGTIZER 0x2179 +#define USB_DEVICE_ID_UGTIZER_TABLET_GP0610 0x0053 + #endif diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index f4377c458787..72778b34046e 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic.c @@ -974,6 +974,19 @@ static int uclogic_probe(struct hid_device *hdev, drvdata->ignore_pen_usage = true; } break; + case USB_DEVICE_ID_UGTIZER_TABLET_GP0610: + /* If this is the pen interface */ + if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { + rc = uclogic_tablet_enable(hdev); + if (rc) { + hid_err(hdev, "tablet enabling failed\n"); + return rc; + } + drvdata->invert_pen_inrange = true; + } else { + drvdata->ignore_pen_usage = true; + } + break; } rc = hid_parse(hdev); @@ -1031,6 +1044,7 @@ static const struct hid_device_id uclogic_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) }, { } }; MODULE_DEVICE_TABLE(hid, uclogic_devices); -- cgit v1.2.3 From 6e0263346321372e5494d0e5d74a700b0829776d Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 14 Sep 2016 21:38:19 +0300 Subject: HID: uclogic: Override constant descriptors Make uclogic_report_fixup override constant descriptors with generated descriptors, so models reusing product ID, but working with generated descriptor get correct one. Signed-off-by: Nikolai Kondrashov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index 72778b34046e..64abd8d73125 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic.c @@ -632,6 +632,12 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber; struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + if (drvdata->rdesc != NULL) { + rdesc = drvdata->rdesc; + *rsize = drvdata->rsize; + return rdesc; + } + switch (hdev->product) { case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209: if (*rsize == PF1209_RDESC_ORIG_SIZE) { @@ -701,11 +707,6 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, break; } break; - default: - if (drvdata->rdesc != NULL) { - rdesc = drvdata->rdesc; - *rsize = drvdata->rsize; - } } return rdesc; -- cgit v1.2.3 From 3202bb7fb021e81dc988735a8b5f42f82aa912f3 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 14 Sep 2016 21:38:20 +0300 Subject: HID: uclogic: Add support for UC-Logic TWHA60 v3 Add support for the third version of UC-Logic TWHA60, which has three interfaces and responds to initialization. Signed-off-by: Nikolai Kondrashov Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index 64abd8d73125..1509d7287ff3 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic.c @@ -938,6 +938,7 @@ static int uclogic_probe(struct hid_device *hdev, { int rc; struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *udev = hid_to_usb_dev(hdev); struct uclogic_drvdata *drvdata; /* @@ -988,6 +989,28 @@ static int uclogic_probe(struct hid_device *hdev, drvdata->ignore_pen_usage = true; } break; + case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60: + /* + * If it is the three-interface version, which is known to + * respond to initialization. + */ + if (udev->config->desc.bNumInterfaces == 3) { + /* If it is the pen interface */ + if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { + rc = uclogic_tablet_enable(hdev); + if (rc) { + hid_err(hdev, "tablet enabling failed\n"); + return rc; + } + drvdata->invert_pen_inrange = true; + + rc = uclogic_button_enable(hdev); + drvdata->has_virtual_pad_interface = !rc; + } else { + drvdata->ignore_pen_usage = true; + } + } + break; } rc = hid_parse(hdev); -- cgit v1.2.3 From 3da30bfc0b0a572a4f977a586edf34cf3dd503c3 Mon Sep 17 00:00:00 2001 From: Marian Krivoš Date: Mon, 19 Sep 2016 15:52:06 +0200 Subject: HID: support for keyboard - Corsair STRAFE Add quirk for Corsair STRAFE keyboard, similarly to what we've been doing for other CORSAIR devices already, in order to avoid long delays during boot. [jkosina@suse.cz: reword changelog a little bit] Signed-off-by: Marian Krivos Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/usbhid/hid-quirks.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 4ed9a4fdfea7..3301ec484363 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -268,6 +268,7 @@ #define USB_DEVICE_ID_CORSAIR_K95RGB 0x1b11 #define USB_DEVICE_ID_CORSAIR_M65RGB 0x1b12 #define USB_DEVICE_ID_CORSAIR_K70RGB 0x1b13 +#define USB_DEVICE_ID_CORSAIR_STRAFE 0x1b15 #define USB_DEVICE_ID_CORSAIR_K65RGB 0x1b17 #define USB_VENDOR_ID_CREATIVELABS 0x041e diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index b4b8c6abb03e..be37b014bcb5 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -76,6 +76,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K95RGB, HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL }, { USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70RGB, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB, HID_QUIRK_NO_INIT_REPORTS }, + { USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_STRAFE, HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL }, { USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51, HID_QUIRK_NOGET }, { USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC, HID_QUIRK_NOGET }, { USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU, HID_QUIRK_MULTI_INPUT }, -- cgit v1.2.3 From 46a41b512f6800b531e94bdf5f1ca0e226492d91 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 21 Sep 2016 15:12:05 +0000 Subject: HID: alps: fix error return code in alps_input_configured() Fix to return a negative error code from the error handling case instead of 0, as done elsewhere in this function. While at it, remove redundant input_free_device(NULL) call. [jkosina@suse.cz: ammend changelog] Signed-off-by: Wei Yongjun Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-alps.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 048befde295a..0c18d473a5d8 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -383,7 +383,7 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) input2 = input_allocate_device(); if (!input2) { - input_free_device(input2); + ret = -ENOMEM; goto exit; } @@ -425,7 +425,8 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) __set_bit(INPUT_PROP_POINTER, input2->propbit); __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); - if (input_register_device(data->input2)) { + ret = input_register_device(data->input2); + if (ret) { input_free_device(input2); goto exit; } -- cgit v1.2.3 From 52dc085a50c6b4b8143087ce194d0b193cb3662d Mon Sep 17 00:00:00 2001 From: Michel Hermier Date: Tue, 20 Sep 2016 19:50:51 +0200 Subject: Revert "HID: microsoft: fix invalid rdesc for 3k kbd" This reverts commit 3ccc60f9d8c39180c205dba1a020735bda1b2491. While investigating bug https://bugzilla.kernel.org/show_bug.cgi?id=37982 , there was solid evidences that Microsoft reused the same report descriptor for the its Digital Media keyboard series. Since 1989dad "HID: input: ignore System Control application usages if not System Controls", the keyboard series do not produce a spurious joystick input device inode without needing to patch the problematic report descriptor. As such the MS_RDESC_3K reportdescriptor fixup can be removed. Signed-off-by: Michel Hermier Signed-off-by: Jiri Kosina --- drivers/hid/hid-microsoft.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c index e924d555536c..3b2f7023bab6 100644 --- a/drivers/hid/hid-microsoft.c +++ b/drivers/hid/hid-microsoft.c @@ -28,7 +28,6 @@ #define MS_RDESC 0x08 #define MS_NOGET 0x10 #define MS_DUPLICATE_USAGES 0x20 -#define MS_RDESC_3K 0x40 static __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) @@ -45,13 +44,6 @@ static __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc, rdesc[557] = 0x35; rdesc[559] = 0x45; } - /* the same as above (s/usage/physical/) */ - if ((quirks & MS_RDESC_3K) && *rsize == 106 && rdesc[94] == 0x19 && - rdesc[95] == 0x00 && rdesc[96] == 0x29 && - rdesc[97] == 0xff) { - rdesc[94] = 0x35; - rdesc[96] = 0x45; - } return rdesc; } @@ -271,7 +263,7 @@ static const struct hid_device_id ms_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_USB), .driver_data = MS_PRESENTER }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K), - .driver_data = MS_ERGONOMY | MS_RDESC_3K }, + .driver_data = MS_ERGONOMY }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_7K), .driver_data = MS_ERGONOMY }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_600), -- cgit v1.2.3 From 0f3982308b5fc7373c8d4e6dcfc8a02ee5a3408d Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Thu, 22 Sep 2016 20:18:08 -0400 Subject: HID: sony: Relax duplicate checking for USB-only devices Some USB-only devices which masquerade as Sixaxis controllers report the same generic Bluetooth address for all hardware when queried via the HID report. This causes these devices to be wrongly rejected as duplicates when more than one is connected at once. This introduces a connection type comparison when checking for duplicates and only rejects the newly connected device if the existing matching device is connected using a different connection protocol. The results of the connection type comparison are also used when registering power supply info as the device Bluetooth address is used as the unique identifier string. In cases where more than one valid device has the same Bluetooth address the device ID is now appended to the power supply name string to avoid name collisions when registering the power supply information. Signed-off-by: Frank Praznik Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 310436a54a3f..92f27b7d892a 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -65,6 +65,8 @@ MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER) #define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER |\ MOTION_CONTROLLER) +#define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | DUALSHOCK4_CONTROLLER_BT |\ + MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT) #define MAX_LEDS 4 @@ -2039,8 +2041,11 @@ static int sony_battery_get_property(struct power_supply *psy, return ret; } -static int sony_battery_probe(struct sony_sc *sc) +static int sony_battery_probe(struct sony_sc *sc, int append_dev_id) { + const char *battery_str_fmt = append_dev_id ? + "sony_controller_battery_%pMR_%i" : + "sony_controller_battery_%pMR"; struct power_supply_config psy_cfg = { .drv_data = sc, }; struct hid_device *hdev = sc->hdev; int ret; @@ -2056,9 +2061,8 @@ static int sony_battery_probe(struct sony_sc *sc) sc->battery_desc.get_property = sony_battery_get_property; sc->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; sc->battery_desc.use_for_apm = 0; - sc->battery_desc.name = kasprintf(GFP_KERNEL, - "sony_controller_battery_%pMR", - sc->mac_address); + sc->battery_desc.name = kasprintf(GFP_KERNEL, battery_str_fmt, + sc->mac_address, sc->device_id); if (!sc->battery_desc.name) return -ENOMEM; @@ -2094,7 +2098,21 @@ static void sony_battery_remove(struct sony_sc *sc) * it will show up as two devices. A global list of connected controllers and * their MAC addresses is maintained to ensure that a device is only connected * once. + * + * Some USB-only devices masquerade as Sixaxis controllers and all have the + * same dummy Bluetooth address, so a comparison of the connection type is + * required. Devices are only rejected in the case where two devices have + * matching Bluetooth addresses on different bus types. */ +static inline int sony_compare_connection_type(struct sony_sc *sc0, + struct sony_sc *sc1) +{ + const int sc0_not_bt = !(sc0->quirks & SONY_BT_DEVICE); + const int sc1_not_bt = !(sc1->quirks & SONY_BT_DEVICE); + + return sc0_not_bt == sc1_not_bt; +} + static int sony_check_add_dev_list(struct sony_sc *sc) { struct sony_sc *entry; @@ -2107,9 +2125,14 @@ static int sony_check_add_dev_list(struct sony_sc *sc) ret = memcmp(sc->mac_address, entry->mac_address, sizeof(sc->mac_address)); if (!ret) { - ret = -EEXIST; - hid_info(sc->hdev, "controller with MAC address %pMR already connected\n", + if (sony_compare_connection_type(sc, entry)) { + ret = 1; + } else { + ret = -EEXIST; + hid_info(sc->hdev, + "controller with MAC address %pMR already connected\n", sc->mac_address); + } goto unlock; } } @@ -2285,6 +2308,7 @@ static inline void sony_cancel_work_sync(struct sony_sc *sc) static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret; + int append_dev_id; unsigned long quirks = id->driver_data; struct sony_sc *sc; unsigned int connect_mask = HID_CONNECT_DEFAULT; @@ -2379,7 +2403,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret < 0) goto err_stop; - ret = sony_check_add(sc); + ret = append_dev_id = sony_check_add(sc); if (ret < 0) goto err_stop; @@ -2390,7 +2414,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) } if (sc->quirks & SONY_BATTERY_SUPPORT) { - ret = sony_battery_probe(sc); + ret = sony_battery_probe(sc, append_dev_id); if (ret < 0) goto err_stop; -- cgit v1.2.3 From 2a2429327711558aa23fd73d770b6fd5e0d10de7 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Thu, 22 Sep 2016 20:18:09 -0400 Subject: HID: sony: Defer the initial USB Sixaxis output report When initially connected via USB the Sixaxis isn't fully initialized until the PS logo button is pressed and won't send any input reports nor will any state set by output reports be retained. This adds a 'defer_initialization' flag to the sony_sc struct which, when set, will delay sending any output reports until the first input report has arrived. This flag is used with the USB Sixaxis to ensure that any state sent will persist since, until the PS button is pushed, any changes sent to the controller via an output report will be lost after a couple of seconds. The initial state of the controller is still configured at the time of the initial connection and won't be internally modified after that, so any state set by the user between that time and the recepit of the first input report won't be lost. Signed-off-by: Frank Praznik Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 92f27b7d892a..9b96f9570531 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -1050,6 +1050,7 @@ struct sony_sc { u8 mac_address[6]; u8 worker_initialized; + u8 defer_initialization; u8 cable_state; u8 battery_charging; u8 battery_capacity; @@ -1060,6 +1061,12 @@ struct sony_sc { u8 led_count; }; +static inline void sony_schedule_work(struct sony_sc *sc) +{ + if (!sc->defer_initialization) + schedule_work(&sc->state_worker); +} + static u8 *sixaxis_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *rsize) { @@ -1319,6 +1326,11 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report, dualshock4_parse_report(sc, rd, size); } + if (sc->defer_initialization) { + sc->defer_initialization = 0; + sony_schedule_work(sc); + } + return 0; } @@ -1556,7 +1568,7 @@ static void buzz_set_leds(struct sony_sc *sc) static void sony_set_leds(struct sony_sc *sc) { if (!(sc->quirks & BUZZ_CONTROLLER)) - schedule_work(&sc->state_worker); + sony_schedule_work(sc); else buzz_set_leds(sc); } @@ -1667,7 +1679,7 @@ static int sony_led_blink_set(struct led_classdev *led, unsigned long *delay_on, new_off != drv_data->led_delay_off[n]) { drv_data->led_delay_on[n] = new_on; drv_data->led_delay_off[n] = new_off; - schedule_work(&drv_data->state_worker); + sony_schedule_work(drv_data); } return 0; @@ -1978,7 +1990,7 @@ static int sony_play_effect(struct input_dev *dev, void *data, sc->left = effect->u.rumble.strong_magnitude / 256; sc->right = effect->u.rumble.weak_magnitude / 256; - schedule_work(&sc->state_worker); + sony_schedule_work(sc); return 0; } @@ -2365,9 +2377,16 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id) * the Sixaxis does not want the report_id as part of the data * packet, so we have to discard buf[0] when sending the actual * control message, even for numbered reports, humpf! + * + * Additionally, the Sixaxis on USB isn't properly initialized + * until the PS logo button is pressed and as such won't retain + * any state set by an output report, so the initial + * configuration report is deferred until the first input + * report arrives. */ hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID; + sc->defer_initialization = 1; ret = sixaxis_set_operational_usb(hdev); sony_init_output_report(sc, sixaxis_send_output_report); } else if ((sc->quirks & SIXAXIS_CONTROLLER_BT) || @@ -2510,8 +2529,10 @@ static int sony_resume(struct hid_device *hdev) * reinitialized on resume or they won't behave properly. */ if ((sc->quirks & SIXAXIS_CONTROLLER_USB) || - (sc->quirks & NAVIGATION_CONTROLLER_USB)) + (sc->quirks & NAVIGATION_CONTROLLER_USB)) { sixaxis_set_operational_usb(sc->hdev); + sc->defer_initialization = 1; + } sony_set_leds(sc); } -- cgit v1.2.3 From c4425c8f26aad2b49c5959fad277c9d109fb8ca3 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Thu, 22 Sep 2016 20:18:10 -0400 Subject: HID: sony: Update copyright and add Dualshock 4 rate control note Update the copyright notice with the current year and add a note about values for controlling the Dualshock 4 reporting rate. Processing reports at the default full rate of 1000hz can be too demanding for some low-power embedded processors so noting alternate values for people working with this hardware can be useful. Thanks to Rostislav Pehlivanov for finding these values. Signed-off-by: Frank Praznik Signed-off-by: Jiri Kosina --- drivers/hid/hid-sony.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 9b96f9570531..9bf4e3637ba2 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -8,7 +8,7 @@ * Copyright (c) 2012 David Dillow * Copyright (c) 2006-2013 Jiri Kosina * Copyright (c) 2013 Colin Leitner - * Copyright (c) 2014 Frank Praznik + * Copyright (c) 2014-2016 Frank Praznik */ /* @@ -1879,6 +1879,17 @@ static void dualshock4_send_output_report(struct sony_sc *sc) u8 *buf = sc->output_report_dmabuf; int offset; + /* + * NOTE: The buf[1] field of the Bluetooth report controls + * the Dualshock 4 reporting rate. + * + * Known values include: + * + * 0x80 - 1000hz (full speed) + * 0xA0 - 31hz + * 0xB0 - 20hz + * 0xD0 - 66hz + */ if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) { memset(buf, 0, DS4_REPORT_0x05_SIZE); buf[0] = 0x05; -- cgit v1.2.3 From 961af46f8e2c7bf793352c11262fb37e87706921 Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Sun, 18 Sep 2016 10:55:37 -0600 Subject: HID: hid-logitech: Introduce control for combined pedals feature Introduce a dev_attr which can be used to combine the accelerator and brake pedals into a single axis. This is useful for older games which can not handle seperate accelerator and brake. Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- .../ABI/testing/sysfs-driver-hid-logitech-lg4ff | 9 ++++ drivers/hid/hid-lg4ff.c | 58 ++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff index db197a879580..9cd7c5adeb7e 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff +++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff @@ -50,3 +50,12 @@ Description: Displays the real model of the wheel regardless of any alternate mode the wheel might be switched to. It is a read-only value. This entry is not created for devices that have only one mode. + +What: /sys/bus/hid/drivers/logitech//combine_pedals +Date: Sep 2016 +KernelVersion: 4.9 +Contact: Simon Wood +Description: Controls whether a combined value of accelerator and brake is + reported on the Y axis of the controller. Useful for older games + which can do not work with separate accelerator/brake axis. + Off ('0') by default, enabled by setting '1'. diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index af3a8ec8a746..ca31ce4d2c24 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -75,6 +75,7 @@ static void lg4ff_set_range_g25(struct hid_device *hid, u16 range); struct lg4ff_wheel_data { const u32 product_id; + u16 combine; u16 range; const u16 min_range; const u16 max_range; @@ -345,6 +346,7 @@ static void lg4ff_init_wheel_data(struct lg4ff_wheel_data * const wdata, const s { struct lg4ff_wheel_data t_wdata = { .product_id = wheel->product_id, .real_product_id = real_product_id, + .combine = 0, .min_range = wheel->min_range, .max_range = wheel->max_range, .set_range = wheel->set_range, @@ -885,6 +887,58 @@ static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_att } static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store); +static ssize_t lg4ff_combine_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; + size_t count; + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return 0; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return 0; + } + + count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.combine); + return count; +} + +static ssize_t lg4ff_combine_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hid = to_hid_device(dev); + struct lg4ff_device_entry *entry; + struct lg_drv_data *drv_data; + u16 combine = simple_strtoul(buf, NULL, 10); + + drv_data = hid_get_drvdata(hid); + if (!drv_data) { + hid_err(hid, "Private driver data not found!\n"); + return -EINVAL; + } + + entry = drv_data->device_props; + if (!entry) { + hid_err(hid, "Device properties not found!\n"); + return -EINVAL; + } + + if (combine > 1) + combine = 1; + + entry->wdata.combine = combine; + return count; +} +static DEVICE_ATTR(combine_pedals, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_combine_show, lg4ff_combine_store); + /* Export the currently set range of the wheel */ static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1259,6 +1313,9 @@ int lg4ff_init(struct hid_device *hid) } /* Create sysfs interface */ + error = device_create_file(&hid->dev, &dev_attr_combine_pedals); + if (error) + hid_warn(hid, "Unable to create sysfs interface for \"combine\", errno %d\n", error); error = device_create_file(&hid->dev, &dev_attr_range); if (error) hid_warn(hid, "Unable to create sysfs interface for \"range\", errno %d\n", error); @@ -1358,6 +1415,7 @@ int lg4ff_deinit(struct hid_device *hid) device_remove_file(&hid->dev, &dev_attr_alternate_modes); } + device_remove_file(&hid->dev, &dev_attr_combine_pedals); device_remove_file(&hid->dev, &dev_attr_range); #ifdef CONFIG_LEDS_CLASS { -- cgit v1.2.3 From c832f86effbcf8833fc2c842aa501ce1eb4d0478 Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Sun, 18 Sep 2016 10:55:38 -0600 Subject: HID: hid-logitech: Add combined pedal support Logitech wheels Add support for reporting a combined accelerator/brake axis for wheels which contain combined data in their HID stream. This includes DF, MOMO, MOMO2 and DFP. Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg.c | 12 ++++++++++++ drivers/hid/hid-lg4ff.c | 32 ++++++++++++++++++++++++++++++++ drivers/hid/hid-lg4ff.h | 4 ++++ 3 files changed, 48 insertions(+) diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index feb2be71f77c..06f8a5e0fc8f 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -657,6 +657,17 @@ static int lg_event(struct hid_device *hdev, struct hid_field *field, return 0; } +static int lg_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *rd, int size) +{ + struct lg_drv_data *drv_data = hid_get_drvdata(hdev); + + if (drv_data->quirks & LG_FF4) + return lg4ff_raw_event(hdev, report, rd, size, drv_data); + + return 0; +} + static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct usb_interface *iface = to_usb_interface(hdev->dev.parent); @@ -830,6 +841,7 @@ static struct hid_driver lg_driver = { .input_mapping = lg_input_mapping, .input_mapped = lg_input_mapped, .event = lg_event, + .raw_event = lg_raw_event, .probe = lg_probe, .remove = lg_remove, }; diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index ca31ce4d2c24..79d34c2f639b 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -329,6 +329,38 @@ int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, } } +int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *rd, int size, struct lg_drv_data *drv_data) +{ + struct lg4ff_device_entry *entry = drv_data->device_props; + + if (!entry) + return 0; + + /* adjust HID report present combined pedals data */ + if (entry->wdata.combine) { + switch (entry->wdata.product_id) { + case USB_DEVICE_ID_LOGITECH_WHEEL: + rd[5] = rd[3]; + rd[6] = 0x7F; + return 1; + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: + case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2: + rd[4] = rd[3]; + rd[5] = 0x7F; + return 1; + case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: + rd[5] = rd[4]; + rd[6] = 0x7F; + return 1; + default: + return 0; + } + } + + return 0; +} + static void lg4ff_init_wheel_data(struct lg4ff_wheel_data * const wdata, const struct lg4ff_wheel *wheel, const struct lg4ff_multimode_wheel *mmode_wheel, const u16 real_product_id) diff --git a/drivers/hid/hid-lg4ff.h b/drivers/hid/hid-lg4ff.h index 66201af44da3..de1f350e0bd3 100644 --- a/drivers/hid/hid-lg4ff.h +++ b/drivers/hid/hid-lg4ff.h @@ -6,11 +6,15 @@ extern int lg4ff_no_autoswitch; /* From hid-lg.c */ int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data); +int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *rd, int size, struct lg_drv_data *drv_data); int lg4ff_init(struct hid_device *hdev); int lg4ff_deinit(struct hid_device *hdev); #else static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data) { return 0; } +static inline int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *rd, int size, struct lg_drv_data *drv_data) { return 0; } static inline int lg4ff_init(struct hid_device *hdev) { return -1; } static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; } #endif -- cgit v1.2.3 From b456634b8d08b184a437bba55ac0efe7ef02d3b3 Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Sun, 18 Sep 2016 10:55:39 -0600 Subject: HID: hid-logitech: Compute combined pedals value Add support for computing a combined accelerator/brake axis for wheels which don't contain combined data in their HID stream. This includes DFGT, G25, G27, G29 and Wii-Wheel. Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg4ff.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 79d34c2f639b..cc5c9ebd12a1 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -332,6 +332,7 @@ int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field, int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *rd, int size, struct lg_drv_data *drv_data) { + int offset; struct lg4ff_device_entry *entry = drv_data->device_props; if (!entry) @@ -353,9 +354,25 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, rd[5] = rd[4]; rd[6] = 0x7F; return 1; + case USB_DEVICE_ID_LOGITECH_G25_WHEEL: + case USB_DEVICE_ID_LOGITECH_G27_WHEEL: + offset = 5; + break; + case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL: + case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + offset = 6; + break; + case USB_DEVICE_ID_LOGITECH_WII_WHEEL: + offset = 3; + break; default: return 0; } + + /* Compute a combined axis when wheel does not supply it */ + rd[offset] = (0xFF + rd[offset] - rd[offset+1]) >> 1; + rd[offset+1] = 0x7F; + return 1; } return 0; -- cgit v1.2.3 From 7363b25af33251d036a4bedb7daed1e6bc395142 Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Sun, 18 Sep 2016 10:55:40 -0600 Subject: HID: hid-logitech: Rewrite of descriptor for all DF wheels Rewrite the HID descriptor for _all_ Driving Force wheels (real or emulated) so that they can report separate Accelerator and Brake axis. If the user wants a combined accel/brake axis, they can use the 'combined pedals' feature. $ echo 1 > /sys/bus/hid/devices//combine_pedals Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 06f8a5e0fc8f..ffcf4f162ea5 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -343,8 +343,6 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { struct lg_drv_data *drv_data = hid_get_drvdata(hdev); - struct usb_device_descriptor *udesc; - __u16 bcdDevice, rev_maj, rev_min; if ((drv_data->quirks & LG_RDESC) && *rsize >= 91 && rdesc[83] == 0x26 && rdesc[84] == 0x8c && rdesc[85] == 0x02) { @@ -365,18 +363,7 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc, /* Several wheels report as this id when operating in emulation mode. */ case USB_DEVICE_ID_LOGITECH_WHEEL: - udesc = &(hid_to_usb_dev(hdev)->descriptor); - if (!udesc) { - hid_err(hdev, "NULL USB device descriptor\n"); - break; - } - bcdDevice = le16_to_cpu(udesc->bcdDevice); - rev_maj = bcdDevice >> 8; - rev_min = bcdDevice & 0xff; - - /* Update the report descriptor for only the Driving Force wheel */ - if (rev_maj == 1 && rev_min == 2 && - *rsize == DF_RDESC_ORIG_SIZE) { + if (*rsize == DF_RDESC_ORIG_SIZE) { hid_info(hdev, "fixing up Logitech Driving Force report descriptor\n"); rdesc = df_rdesc_fixed; -- cgit v1.2.3 From 560bea30ff9dd4823d8f611aaab88d3c3f9c7d8d Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Sun, 18 Sep 2016 10:55:41 -0600 Subject: HID: hid-logitech: Improve Wingman Formula Force GP support Move ForceFeedback support for the Formula Force GP into hid-lgff4 and re-write HID descriptor, thus allowing combined pedals or not as user desires. Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- drivers/hid/hid-lg.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++- drivers/hid/hid-lg4ff.c | 2 ++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index ffcf4f162ea5..76f644deb0a7 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -49,6 +49,7 @@ #define FV_RDESC_ORIG_SIZE 130 #define MOMO_RDESC_ORIG_SIZE 87 #define MOMO2_RDESC_ORIG_SIZE 87 +#define FFG_RDESC_ORIG_SIZE 85 /* Fixed report descriptors for Logitech Driving Force (and Pro) * wheel controllers @@ -334,6 +335,52 @@ static __u8 momo2_rdesc_fixed[] = { 0xC0 /* End Collection */ }; +static __u8 ffg_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x04, /* Usage (Joystik), */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x0A, /* Report Size (10), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0xFF, 0x03, /* Physical Maximum (1023), */ +0x09, 0x30, /* Usage (X), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x06, /* Report Count (6), */ +0x75, 0x01, /* Report Size (1), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x05, 0x09, /* Usage Page (Button), */ +0x19, 0x01, /* Usage Minimum (01h), */ +0x29, 0x06, /* Usage Maximum (06h), */ +0x81, 0x02, /* Input (Variable), */ +0x95, 0x01, /* Report Count (1), */ +0x75, 0x08, /* Report Size (8), */ +0x26, 0xFF, 0x00, /* Logical Maximum (255), */ +0x46, 0xFF, 0x00, /* Physical Maximum (255), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x01, /* Usage (01h), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x81, 0x01, /* Input (Constant), */ +0x09, 0x31, /* Usage (Y), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x32, /* Usage (Z), */ +0x81, 0x02, /* Input (Variable), */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x01, /* Usage (01h), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x02, /* Usage (02h), */ +0x95, 0x07, /* Report Count (7), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection */ +}; + /* * Certain Logitech keyboards send in report #3 keys which are far * above the logical maximum described in descriptor. This extends @@ -361,6 +408,15 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc, switch (hdev->product) { + case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG: + if (*rsize == FFG_RDESC_ORIG_SIZE) { + hid_info(hdev, + "fixing up Logitech Wingman Formula Force GP report descriptor\n"); + rdesc = ffg_rdesc_fixed; + *rsize = sizeof(ffg_rdesc_fixed); + } + break; + /* Several wheels report as this id when operating in emulation mode. */ case USB_DEVICE_ID_LOGITECH_WHEEL: if (*rsize == DF_RDESC_ORIG_SIZE) { @@ -608,6 +664,7 @@ static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi, usage->code == ABS_RZ)) { switch (hdev->product) { case USB_DEVICE_ID_LOGITECH_G29_WHEEL: + case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG: case USB_DEVICE_ID_LOGITECH_WHEEL: case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: case USB_DEVICE_ID_LOGITECH_DFP_WHEEL: @@ -807,7 +864,7 @@ static const struct hid_device_id lg_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL), .driver_data = LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG), - .driver_data = LG_FF }, + .driver_data = LG_NOGET | LG_FF4 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2), .driver_data = LG_FF2 }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940), diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index cc5c9ebd12a1..1fc12e357035 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -137,6 +137,7 @@ struct lg4ff_alternate_mode { }; static const struct lg4ff_wheel lg4ff_devices[] = { + {USB_DEVICE_ID_LOGITECH_WINGMAN_FFG, lg4ff_wheel_effects, 40, 180, NULL}, {USB_DEVICE_ID_LOGITECH_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL, lg4ff_wheel_effects, 40, 270, NULL}, {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, lg4ff_wheel_effects, 40, 900, lg4ff_set_range_dfp}, @@ -345,6 +346,7 @@ int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report, rd[5] = rd[3]; rd[6] = 0x7F; return 1; + case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG: case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL: case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2: rd[4] = rd[3]; -- cgit v1.2.3 From f777a3a7bd44509d42ecd57135cdb463ea7f023e Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Sun, 18 Sep 2016 10:55:42 -0600 Subject: HID: hid-logitech: Documentation updates/corrections Signed-off-by: Simon Wood Signed-off-by: Jiri Kosina --- .../ABI/testing/sysfs-driver-hid-logitech-lg4ff | 6 ++++++ drivers/hid/Kconfig | 23 +++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff index 9cd7c5adeb7e..305dffd229a8 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff +++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff @@ -35,6 +35,12 @@ Description: Displays a set of alternate modes supported by a wheel. Each DF-EX <*--------> G25 <-> G27 DF-EX <*----------------> G27 + G29: + DF-EX <*> DFP <-> G25 <-> G27 <-> G29 + DF-EX <*--------> G25 <-> G27 <-> G29 + DF-EX <*----------------> G27 <-> G29 + DF-EX <*------------------------> G29 + DFGT: DF-EX <*> DFP <-> DFGT DF-EX <*--------> DFGT diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 78ac4811bd3c..5227d8a9c9ea 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -457,8 +457,6 @@ config LOGITECH_FF - Logitech WingMan Cordless RumblePad - Logitech WingMan Cordless RumblePad 2 - Logitech WingMan Force 3D - - Logitech Formula Force EX - - Logitech WingMan Formula Force GP and if you want to enable force feedback for them. Note: if you say N here, this device will still be supported, but without @@ -488,15 +486,22 @@ config LOGIWHEELS_FF select INPUT_FF_MEMLESS default LOGITECH_FF help - Say Y here if you want to enable force feedback and range setting + Say Y here if you want to enable force feedback and range setting(*) support for following Logitech wheels: + - Logitech G25 (*) + - Logitech G27 (*) + - Logitech G29 (*) - Logitech Driving Force - - Logitech Driving Force Pro - - Logitech Driving Force GT - - Logitech G25 - - Logitech G27 - - Logitech MOMO/MOMO 2 - - Logitech Formula Force EX + - Logitech Driving Force Pro (*) + - Logitech Driving Force GT (*) + - Logitech Driving Force EX/RX + - Logitech Driving Force Wireless + - Logitech Speed Force Wireless + - Logitech MOMO Force + - Logitech MOMO Racing Force + - Logitech Formula Force GP + - Logitech Formula Force EX/RX + - Logitech Wingman Formula Force GP config HID_MAGICMOUSE tristate "Apple Magic Mouse/Trackpad multi-touch support" -- cgit v1.2.3 From 9a54cf462d6f3c383a5a4f5fe15c020a03db44e6 Mon Sep 17 00:00:00 2001 From: Masaki Ota <012nexus@gmail.com> Date: Tue, 27 Sep 2016 14:04:37 +0900 Subject: HID: alps: fix multitouch cursor issue Issue reproduction procedure: 1. three or more fingers put on Touchpad. 2. release fingers from Touchpad. 3. move the cursor by one finger. 4. the cursor does not move. Cause: We do not notify multi fingers state correctly to input subsystem. For example, when three fingers release from Touchpad, fingers state is 3 -> 0. It needs to notify first, second and third finger's releasing state. Fix this by not breaking out on z axis and move x,y,z input handling code to the correct place so that it's in fact per-finger. [jkosina@suse.cz: reword changelog] Signed-off-by: Masaki Ota Signed-off-by: Jiri Kosina --- drivers/hid/hid-alps.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 048befde295a..610df92837bb 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -190,16 +190,16 @@ static int alps_raw_event(struct hid_device *hdev, if (z != 0) { input_mt_report_slot_state(hdata->input, MT_TOOL_FINGER, 1); + input_report_abs(hdata->input, + ABS_MT_POSITION_X, x); + input_report_abs(hdata->input, + ABS_MT_POSITION_Y, y); + input_report_abs(hdata->input, + ABS_MT_PRESSURE, z); } else { input_mt_report_slot_state(hdata->input, MT_TOOL_FINGER, 0); - break; } - - input_report_abs(hdata->input, ABS_MT_POSITION_X, x); - input_report_abs(hdata->input, ABS_MT_POSITION_Y, y); - input_report_abs(hdata->input, ABS_MT_PRESSURE, z); - } input_mt_sync_frame(hdata->input); -- cgit v1.2.3 From 6c3f70ac7c6b4a29b6905be879282628e65f50dd Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 27 Sep 2016 11:58:44 -0700 Subject: HID: add missing \n to end of dev_warn messages Trival fix, dev_warn messages are missing a \n, so add it. Signed-off-by: Colin Ian King Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- include/linux/hid.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/hid.h b/include/linux/hid.h index 75b66eccc692..b2ec82712baa 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -837,7 +837,7 @@ __u32 hid_field_extract(const struct hid_device *hid, __u8 *report, */ static inline void hid_device_io_start(struct hid_device *hid) { if (hid->io_started) { - dev_warn(&hid->dev, "io already started"); + dev_warn(&hid->dev, "io already started\n"); return; } hid->io_started = true; @@ -857,7 +857,7 @@ static inline void hid_device_io_start(struct hid_device *hid) { */ static inline void hid_device_io_stop(struct hid_device *hid) { if (!hid->io_started) { - dev_warn(&hid->dev, "io already stopped"); + dev_warn(&hid->dev, "io already stopped\n"); return; } hid->io_started = false; -- cgit v1.2.3