diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-10-07 11:58:38 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-10-07 11:58:38 -0700 |
commit | bc75450cc3db3485db1e289fef8c1028ba38296a (patch) | |
tree | cd7dca62f9f60524ed5241a9e2fbdce1c8ab0bb7 /drivers/hid | |
parent | e6e3d8f8f4f06caf25004c749bb2ba84f18c7d39 (diff) | |
parent | 179023e6af0c608ffb505821223f5580853ef6b8 (diff) | |
download | linux-bc75450cc3db3485db1e289fef8c1028ba38296a.tar.gz linux-bc75450cc3db3485db1e289fef8c1028ba38296a.tar.bz2 linux-bc75450cc3db3485db1e289fef8c1028ba38296a.zip |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
Pull HID updates from Jiri Kosina:
- Integrated Sensor Hub support (Cherrytrail+) from Srinivas Pandruvada
- Big cleanup of Wacom driver; namely it's now using devres, and the
standardized LED API so that libinput doesn't need to have root
access any more, with substantial amount of other cleanups
piggy-backing on top. All this from Benjamin Tissoires
- Report descriptor parsing would now ignore and out-of-range System
controls in case of the application actually being System Control.
This fixes quite some issues with several devices, and allows us to
remove a few ->report_fixup callbacks. From Benjamin Tissoires
- ... a lot of other assorted small fixes and device ID additions
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (76 commits)
HID: add missing \n to end of dev_warn messages
HID: alps: fix multitouch cursor issue
HID: hid-logitech: Documentation updates/corrections
HID: hid-logitech: Improve Wingman Formula Force GP support
HID: hid-logitech: Rewrite of descriptor for all DF wheels
HID: hid-logitech: Compute combined pedals value
HID: hid-logitech: Add combined pedal support Logitech wheels
HID: hid-logitech: Introduce control for combined pedals feature
HID: sony: Update copyright and add Dualshock 4 rate control note
HID: sony: Defer the initial USB Sixaxis output report
HID: sony: Relax duplicate checking for USB-only devices
Revert "HID: microsoft: fix invalid rdesc for 3k kbd"
HID: alps: fix error return code in alps_input_configured()
HID: alps: fix stick device not working after resume
HID: support for keyboard - Corsair STRAFE
HID: alps: Fix memory leak
HID: uclogic: Add support for UC-Logic TWHA60 v3
HID: uclogic: Override constant descriptors
HID: uclogic: Support UGTizer GP0610 partially
HID: uclogic: Add support for several more tablets
...
Diffstat (limited to 'drivers/hid')
41 files changed, 9131 insertions, 670 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 78ac4811bd3c..cd4599c0523b 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" @@ -862,6 +867,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. @@ -967,4 +973,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/hid-alps.c b/drivers/hid/hid-alps.c index 048befde295a..ed9c0ea5b026 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -139,8 +139,8 @@ static int u1_read_write_register(struct hid_device *hdev, u32 address, if (read_flag) { readbuf = kzalloc(U1_FEATURE_REPORT_LEN, GFP_KERNEL); if (!readbuf) { - kfree(input); - return -ENOMEM; + ret = -ENOMEM; + goto exit; } ret = hid_hw_raw_request(hdev, U1_FEATURE_REPORT_ID, readbuf, @@ -149,6 +149,7 @@ static int u1_read_write_register(struct hid_device *hdev, u32 address, if (ret < 0) { dev_err(&hdev->dev, "failed read register (%d)\n", ret); + kfree(readbuf); goto exit; } @@ -190,16 +191,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); @@ -244,13 +245,13 @@ static int alps_raw_event(struct hid_device *hdev, static int alps_post_reset(struct hid_device *hdev) { return u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1, - NULL, U1_TP_ABS_MODE, false); + NULL, U1_TP_ABS_MODE | U1_SP_ABS_MODE, false); } static int alps_post_resume(struct hid_device *hdev) { return u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1, - NULL, U1_TP_ABS_MODE, false); + NULL, U1_TP_ABS_MODE | U1_SP_ABS_MODE, false); } #endif /* CONFIG_PM */ @@ -383,7 +384,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 +426,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; } diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 08f53c7fd513..2b89c701076f 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -727,6 +727,7 @@ static void hid_scan_collection(struct hid_parser *parser, unsigned type) (hid->product == USB_DEVICE_ID_MS_TYPE_COVER_PRO_3 || hid->product == USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2 || hid->product == USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP || + hid->product == USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_JP || hid->product == USB_DEVICE_ID_MS_TYPE_COVER_3 || hid->product == USB_DEVICE_ID_MS_POWER_COVER) && hid->group == HID_GROUP_MULTITOUCH) @@ -1916,7 +1917,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) }, @@ -1982,6 +1983,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_JP) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_7K) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_600) }, @@ -2037,6 +2039,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_OLD) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT9) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7) }, { HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT5) }, { HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9) }, @@ -2083,6 +2086,11 @@ 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_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) }, @@ -2480,7 +2488,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) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 4ed9a4fdfea7..cd59c79eebdd 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 @@ -565,7 +566,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 @@ -713,6 +714,7 @@ #define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3 0x07dc #define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2 0x07e2 #define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP 0x07dd +#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_JP 0x07e9 #define USB_DEVICE_ID_MS_TYPE_COVER_3 0x07de #define USB_DEVICE_ID_MS_POWER_COVER 0x07da @@ -859,6 +861,7 @@ #define USB_DEVICE_ID_SAITEK_PS1000 0x0621 #define USB_DEVICE_ID_SAITEK_RAT7_OLD 0x0ccb #define USB_DEVICE_ID_SAITEK_RAT7 0x0cd7 +#define USB_DEVICE_ID_SAITEK_RAT9 0x0cfa #define USB_DEVICE_ID_SAITEK_MMO7 0x0cd0 #define USB_VENDOR_ID_SAMSUNG 0x0419 @@ -996,6 +999,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 @@ -1085,4 +1092,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-input.c b/drivers/hid/hid-input.c index bcfaf32d9e5e..fb9ace1cef8b 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; @@ -953,6 +962,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case HID_UP_HPVENDOR2: set_bit(EV_REP, input->evbit); switch (usage->hid & HID_USAGE) { + case 0x001: map_key_clear(KEY_MICMUTE); break; case 0x003: map_key_clear(KEY_BRIGHTNESSDOWN); break; case 0x004: map_key_clear(KEY_BRIGHTNESSUP); break; default: goto ignore; diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c index 32e6d8d9ded0..0dd1167b2c9b 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,10 +176,104 @@ 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 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 @@ -454,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_2: 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; @@ -553,7 +642,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); @@ -586,7 +675,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/hid-lg.c b/drivers/hid/hid-lg.c index feb2be71f77c..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 @@ -343,8 +390,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) { @@ -363,20 +408,18 @@ static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc, switch (hdev->product) { - /* 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; + 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); } - bcdDevice = le16_to_cpu(udesc->bcdDevice); - rev_maj = bcdDevice >> 8; - rev_min = bcdDevice & 0xff; + break; - /* Update the report descriptor for only the Driving Force wheel */ - if (rev_maj == 1 && rev_min == 2 && - *rsize == DF_RDESC_ORIG_SIZE) { + /* Several wheels report as this id when operating in emulation mode. */ + case USB_DEVICE_ID_LOGITECH_WHEEL: + if (*rsize == DF_RDESC_ORIG_SIZE) { hid_info(hdev, "fixing up Logitech Driving Force report descriptor\n"); rdesc = df_rdesc_fixed; @@ -621,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: @@ -657,6 +701,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); @@ -809,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), @@ -830,6 +885,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 af3a8ec8a746..1fc12e357035 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; @@ -136,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}, @@ -328,6 +330,56 @@ 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) + 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_WINGMAN_FFG: + 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; + 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; +} + 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) @@ -345,6 +397,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 +938,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 +1364,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 +1466,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 { 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 diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c index e924d555536c..c6cd392e9f99 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), @@ -288,6 +280,8 @@ static const struct hid_device_id ms_devices[] = { .driver_data = MS_HIDINPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP), .driver_data = MS_HIDINPUT }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_JP), + .driver_data = MS_HIDINPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3), .driver_data = MS_HIDINPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER), diff --git a/drivers/hid/hid-saitek.c b/drivers/hid/hid-saitek.c index 2f84b26f1167..39e642686ff0 100644 --- a/drivers/hid/hid-saitek.c +++ b/drivers/hid/hid-saitek.c @@ -183,6 +183,8 @@ static const struct hid_device_id saitek_devices[] = { .driver_data = SAITEK_RELEASE_MODE_RAT7 }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7), .driver_data = SAITEK_RELEASE_MODE_RAT7 }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT9), + .driver_data = SAITEK_RELEASE_MODE_RAT7 }, { HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9), .driver_data = SAITEK_RELEASE_MODE_RAT7 }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7), 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 <linux/device.h> #include <linux/hid.h> #include <linux/module.h> @@ -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) }, { } diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 310436a54a3f..b0bb99a821bd 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -8,7 +8,7 @@ * Copyright (c) 2012 David Dillow <dave@thedillows.org> * Copyright (c) 2006-2013 Jiri Kosina * Copyright (c) 2013 Colin Leitner <colin.leitner@gmail.com> - * Copyright (c) 2014 Frank Praznik <frank.praznik@gmail.com> + * Copyright (c) 2014-2016 Frank Praznik <frank.praznik@gmail.com> */ /* @@ -51,6 +51,7 @@ #define NAVIGATION_CONTROLLER_USB BIT(9) #define NAVIGATION_CONTROLLER_BT BIT(10) #define SINO_LITE_CONTROLLER BIT(11) +#define FUTUREMAX_DANCE_MAT BIT(12) #define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT) #define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT) @@ -65,6 +66,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 @@ -1048,6 +1051,7 @@ struct sony_sc { u8 mac_address[6]; u8 worker_initialized; + u8 defer_initialization; u8 cable_state; u8 battery_charging; u8 battery_capacity; @@ -1058,6 +1062,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) { @@ -1125,7 +1135,7 @@ static u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc, { struct sony_sc *sc = hid_get_drvdata(hdev); - if (sc->quirks & SINO_LITE_CONTROLLER) + if (sc->quirks & (SINO_LITE_CONTROLLER | FUTUREMAX_DANCE_MAT)) return rdesc; /* @@ -1317,6 +1327,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; } @@ -1554,7 +1569,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); } @@ -1665,7 +1680,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; @@ -1865,6 +1880,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; @@ -1976,7 +2002,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; } @@ -2039,8 +2065,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 +2085,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 +2122,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 +2149,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,10 +2332,14 @@ 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; + if (!strcmp(hdev->name, "FutureMax Dance Mat")) + quirks |= FUTUREMAX_DANCE_MAT; + sc = devm_kzalloc(&hdev->dev, sizeof(*sc), GFP_KERNEL); if (sc == NULL) { hid_err(hdev, "can't alloc sony descriptor\n"); @@ -2341,9 +2392,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) || @@ -2379,7 +2437,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 +2448,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; @@ -2486,8 +2544,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); } diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c index 85ac43517e3f..1509d7287ff3 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 @@ -613,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, @@ -628,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, @@ -637,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) { @@ -706,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; @@ -804,7 +800,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; } @@ -848,7 +843,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; } @@ -876,11 +870,75 @@ 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) { 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; /* @@ -899,6 +957,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); @@ -907,10 +969,48 @@ 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; } 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; + 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); @@ -933,12 +1033,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; } @@ -960,6 +1064,11 @@ 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) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) }, { } }; MODULE_DEVICE_TABLE(hid, uclogic_devices); 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 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..8c08b0b358b1 --- /dev/null +++ b/drivers/hid/intel-ish-hid/Makefile @@ -0,0 +1,22 @@ +# +# 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 + +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/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_INT_ISH2HOST_CLR_OFFS) + +/* bit corresponds busy bit in doorbell registers */ +#define IPC_DRBL_BUSY_OFFS (31) +#define IPC_DRBL_BUSY_BIT (1<<IPC_DRBL_BUSY_OFFS) + +#define IPC_HOST_OWNS_MSG_OFFS (30) + +/* + * A0: bit means that host owns MSGnn registers and is reading them. + * ISH FW may not write to them + */ +#define IPC_HOST_OWNS_MSG_BIT (1<<IPC_HOST_OWNS_MSG_OFFS) + +/* + * Host status bits (HOSTCOMM) + */ +/* bit corresponds host ready bit in Host Status Register (HOST_COMM) */ +#define IPC_HOSTCOMM_READY_OFFS (7) +#define IPC_HOSTCOMM_READY_BIT (1<<IPC_HOSTCOMM_READY_OFFS) + +/***********************************/ +/*CHV_A0, CHV_B0*/ +#define IPC_HOSTCOMM_INT_EN_OFFS_CHV_AB (31) +#define IPC_HOSTCOMM_INT_EN_BIT_CHV_AB \ + (1<<IPC_HOSTCOMM_INT_EN_OFFS_CHV_AB) +/*BXT, CHV_K0*/ +#define IPC_PIMR_INT_EN_OFFS_BXT (0) +#define IPC_PIMR_INT_EN_BIT_BXT (1<<IPC_PIMR_INT_EN_OFFS_BXT) + +#define IPC_HOST2ISH_BUSYCLEAR_MASK_OFFS_BXT (8) +#define IPC_HOST2ISH_BUSYCLEAR_MASK_BIT \ + (1<<IPC_HOST2ISH_BUSYCLEAR_MASK_OFFS_BXT) +/***********************************/ +/* + * both Host and ISH have ILUP at bit 0 + * bit corresponds host ready bit in both status registers + */ +#define IPC_ILUP_OFFS (0) +#define IPC_ILUP_BIT (1<<IPC_ILUP_OFFS) + +/* + * FW status bits (relevant) + */ +#define IPC_FWSTS_ILUP 0x1 +#define IPC_FWSTS_ISHTP_UP (1<<1) +#define IPC_FWSTS_DMA0 (1<<16) +#define IPC_FWSTS_DMA1 (1<<17) +#define IPC_FWSTS_DMA2 (1<<18) +#define IPC_FWSTS_DMA3 (1<<19) + +#define IPC_ISH_IN_DMA \ + (IPC_FWSTS_DMA0 | IPC_FWSTS_DMA1 | IPC_FWSTS_DMA2 | IPC_FWSTS_DMA3) + +/* bit corresponds host ready bit in ISH FW Status Register */ +#define IPC_ISH_ISHTP_READY_OFFS (1) +#define IPC_ISH_ISHTP_READY_BIT (1<<IPC_ISH_ISHTP_READY_OFFS) + +#define IPC_RMP2_DMA_ENABLED 0x1 /* Value to enable DMA, per D3 RCR */ + +#define IPC_MSG_MAX_SIZE 0x80 + + +#define IPC_HEADER_LENGTH_MASK 0x03FF +#define IPC_HEADER_PROTOCOL_MASK 0x0F +#define IPC_HEADER_MNG_CMD_MASK 0x0F + +#define IPC_HEADER_LENGTH_OFFSET 0 +#define IPC_HEADER_PROTOCOL_OFFSET 10 +#define IPC_HEADER_MNG_CMD_OFFSET 16 + +#define IPC_HEADER_GET_LENGTH(drbl_reg) \ + (((drbl_reg) >> 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)<<IPC_DRBL_BUSY_OFFS) | \ + ((protocol) << IPC_HEADER_PROTOCOL_OFFSET) | \ + ((length)<<IPC_HEADER_LENGTH_OFFSET)) + +#define IPC_BUILD_MNG_MSG(cmd, length) \ + (((1)<<IPC_DRBL_BUSY_OFFS)| \ + ((IPC_PROTOCOL_MNG)<<IPC_HEADER_PROTOCOL_OFFSET)| \ + ((cmd)<<IPC_HEADER_MNG_CMD_OFFSET)| \ + ((length)<<IPC_HEADER_LENGTH_OFFSET)) + + +#define IPC_SET_HOST_READY(host_status) \ + ((host_status) |= (IPC_HOSTCOMM_READY_BIT)) + +#define IPC_SET_HOST_ILUP(host_status) \ + ((host_status) |= (IPC_ILUP_BIT)) + +#define IPC_CLEAR_HOST_READY(host_status) \ + ((host_status) ^= (IPC_HOSTCOMM_READY_BIT)) + +#define IPC_CLEAR_HOST_ILUP(host_status) \ + ((host_status) ^= (IPC_ILUP_BIT)) + +/* todo - temp until PIMR HW ready */ +#define IPC_HOST_BUSY_READING_OFFS 6 + +/* bit corresponds host ready bit in Host Status Register (HOST_COMM) */ +#define IPC_HOST_BUSY_READING_BIT (1<<IPC_HOST_BUSY_READING_OFFS) + +#define IPC_SET_HOST_BUSY_READING(host_status) \ + ((host_status) |= (IPC_HOST_BUSY_READING_BIT)) + +#define IPC_CLEAR_HOST_BUSY_READING(host_status)\ + ((host_status) ^= (IPC_HOST_BUSY_READING_BIT)) + + +#define IPC_IS_ISH_ISHTP_READY(ish_status) \ + (((ish_status) & IPC_ISH_ISHTP_READY_BIT) == \ + ((uint32_t)IPC_ISH_ISHTP_READY_BIT)) + +#define IPC_IS_ISH_ILUP(ish_status) \ + (((ish_status) & IPC_ILUP_BIT) == ((uint32_t)IPC_ILUP_BIT)) + + +#define IPC_PROTOCOL_ISHTP 1 +#define IPC_PROTOCOL_MNG 3 + +#define MNG_RX_CMPL_ENABLE 0 +#define MNG_RX_CMPL_DISABLE 1 +#define MNG_RX_CMPL_INDICATION 2 +#define MNG_RESET_NOTIFY 3 +#define MNG_RESET_NOTIFY_ACK 4 +#define MNG_SYNC_FW_CLOCK 5 +#define MNG_ILLEGAL_CMD 0xFF + +#endif /* _ISHTP_ISH_REGS_H_ */ diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h new file mode 100644 index 000000000000..46615a03e78f --- /dev/null +++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef _ISHTP_HW_ISH_H_ +#define _ISHTP_HW_ISH_H_ + +#include <linux/pci.h> +#include <linux/interrupt.h> +#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..e2517c11e0ee --- /dev/null +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -0,0 +1,881 @@ +/* + * 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 <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#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_move_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..42f0beeb09fd --- /dev/null +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -0,0 +1,322 @@ +/* + * 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 <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/miscdevice.h> +#define CREATE_TRACE_POINTS +#include <trace/events/intel_ish.h> +#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, +}; + +module_pci_driver(ish_driver); + +/* Original author */ +MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>"); +/* Adoption to upstream Linux kernel */ +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); + +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/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 <linux/module.h> +#include <linux/hid.h> +#include <linux/sched.h> +#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 <daniel.drubin@intel.com>"); +/* + * Several modification for multi instance support + * suspend/resume and clean up + */ +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); + +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 <linux/hid.h> +#include <uapi/linux/input.h> +#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/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c new file mode 100644 index 000000000000..256521509d20 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/bus.c @@ -0,0 +1,788 @@ +/* + * 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 <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#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; + + spin_lock_irqsave(&dev->device_list_lock, flags); + 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]; + 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 <linux/device.h> +#include <linux/mod_devicetable.h> + +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..b9b917d2d50d --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c @@ -0,0 +1,257 @@ +/* + * 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 <linux/slab.h> +#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 = kzalloc(sizeof(struct ishtp_cl_tx_ring), GFP_KERNEL); + if (!tx_buf) + goto out; + + 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 <linux/slab.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#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 <linux/types.h> +#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 <linux/slab.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#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 <linux/export.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/spinlock.h> +#include <linux/miscdevice.h> +#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 <linux/uuid.h> + +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 <linux/export.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/miscdevice.h> +#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 <linux/types.h> +#include <linux/spinlock.h> +#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_*/ diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index b4b8c6abb03e..0a0eca5da47d 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 }, @@ -98,6 +99,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP, HID_QUIRK_NO_INIT_REPORTS }, + { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_JP, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER, HID_QUIRK_NO_INIT_REPORTS }, { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS }, @@ -143,7 +145,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 }, diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 4681a65a4579..b4800ea891cb 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -90,6 +90,8 @@ #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/hid.h> +#include <linux/kfifo.h> +#include <linux/leds.h> #include <linux/usb/input.h> #include <linux/power_supply.h> #include <asm/unaligned.h> @@ -105,32 +107,95 @@ #define USB_VENDOR_ID_WACOM 0x056a #define USB_VENDOR_ID_LENOVO 0x17ef +enum wacom_worker { + WACOM_WORKER_WIRELESS, + WACOM_WORKER_BATTERY, + 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 device *dev; +}; + +struct wacom_battery { + struct wacom *wacom; + struct power_supply_desc bat_desc; + struct power_supply *battery; + char bat_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; + struct kobject *remote_dir; + struct { + struct attribute_group group; + u32 serial; + struct input_dev *input; + bool registered; + struct wacom_battery battery; + } remotes[WACOM_MAX_REMOTES]; +}; + 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 wacom_led { - u8 select[5]; /* status led selector (0..3) */ + struct work_struct wireless_work; + struct work_struct battery_work; + struct work_struct remote_work; + 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; - bool led_initialized; - struct power_supply *battery; - 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]; + struct wacom_battery battery; + bool resources; }; -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; + case WACOM_WORKER_REMOTE: + schedule_work(&wacom->remote_work); + break; + } } extern const struct hid_device_id wacom_ids[]; @@ -149,7 +214,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); -int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, - int index); -void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial); +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 499cc8213cfe..5e7a5648e708 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) @@ -91,7 +90,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); } /* @@ -523,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; + + /* 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; - if (vid == 0 && pid == 0) { - vid = hdev->vendor; - pid = hdev->product; + /* + * 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; - return !strncmp(hdev->phys, sibling->phys, n1); + /* 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; + + /* + * 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); @@ -563,6 +626,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); @@ -587,6 +682,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) @@ -597,37 +699,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; @@ -635,6 +706,12 @@ 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; + if (wacom->wacom_wac.pid) { /* wireless connected */ report_id = WAC_CMD_WL_LED_CONTROL; buf_size = 13; @@ -650,7 +727,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); @@ -665,11 +742,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; @@ -741,7 +818,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); @@ -761,7 +838,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, \ @@ -904,6 +981,327 @@ 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 kobject *root, + 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 = root; + + error = sysfs_create_group(devres->root, group); + if (error) + return error; + + devres_add(&wacom->hdev->dev, devres); + + 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); +} + +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; + + 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; + 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; + 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) { + hid_err(wacom->hdev, + "failed to register LED %s: %d\n", + led->cdev.name, error); + led->cdev.name = NULL; + return error; + } + + 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, + 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; + } + + 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: + devres_release_group(dev, &wacom->led.groups[group_id]); + 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; + + 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(dev, sizeof(struct wacom_group_leds) * count, + GFP_KERNEL); + if (!groups) + return -ENOMEM; + + 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; +} + static int wacom_initialize_leds(struct wacom *wacom) { int error; @@ -917,25 +1315,38 @@ 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.max_llv = 127; + wacom->led.max_hlv = 127; wacom->led.img_lum = 10; - error = sysfs_create_group(&wacom->hdev->dev.kobj, - &intuos4_led_attr_group); + + error = wacom_leds_alloc_and_register(wacom, 1, 4, false); + 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 = sysfs_create_group(&wacom->hdev->dev.kobj, - &cintiq_led_attr_group); + error = wacom_leds_alloc_and_register(wacom, 2, 4, false); + 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; case INTUOS5S: @@ -944,16 +1355,31 @@ 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; + wacom->led.max_llv = 96; + + error = wacom_leds_alloc_and_register(wacom, 1, 4, false); + if (error) { + hid_err(wacom->hdev, + "cannot create leds err: %d\n", error); + return error; + } - error = sysfs_create_group(&wacom->hdev->dev.kobj, - &intuos5_led_attr_group); + error = wacom_devm_sysfs_create_group(wacom, + &intuos5_led_attr_group); 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, + "cannot create leds err: %d\n", error); + return error; + } + return 0; + default: return 0; } @@ -964,86 +1390,45 @@ 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_MODEL_NAME, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_SCOPE, 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) { - 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_MODEL_NAME: + val->strval = battery->wacom->wacom_wac.name; + break; 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; @@ -1056,84 +1441,64 @@ 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) +static int __wacom_initialize_battery(struct wacom *wacom, + struct wacom_battery *battery) { - struct wacom *wacom = power_supply_get_drvdata(psy); - int ret = 0; + 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; + struct power_supply_desc *bat_desc = &battery->bat_desc; + unsigned long n; + int error; - switch (psp) { - case POWER_SUPPLY_PROP_PRESENT: - /* fall through */ - case POWER_SUPPLY_PROP_ONLINE: - val->intval = wacom->wacom_wac.ps_connected; - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval = POWER_SUPPLY_SCOPE_DEVICE; - break; - default: - ret = -EINVAL; - break; + 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; + 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_USB; + bat_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; } - return ret; + + power_supply_powers(ps_bat, &wacom->hdev->dev); + + battery->battery = ps_bat; + + devres_close_group(dev, bat_desc); + return 0; + +err: + devres_release_group(dev, bat_desc); + return error; } static int wacom_initialize_battery(struct wacom *wacom) { - static atomic_t battery_no = ATOMIC_INIT(0); - struct power_supply_config psy_cfg = { .drv_data = wacom, }; - unsigned long n; - - 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; - - 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 = power_supply_register(&wacom->hdev->dev, - &wacom->battery_desc, &psy_cfg); - if (IS_ERR(wacom->battery)) - return PTR_ERR(wacom->battery); - - power_supply_powers(wacom->battery, &wacom->hdev->dev); - - wacom->ac = power_supply_register(&wacom->hdev->dev, - &wacom->ac_desc, - &psy_cfg); - if (IS_ERR(wacom->ac)) { - power_supply_unregister(wacom->battery); - return PTR_ERR(wacom->ac); - } - - power_supply_powers(wacom->ac, &wacom->hdev->dev); - } + 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) { - power_supply_unregister(wacom->battery); - wacom->battery = NULL; - power_supply_unregister(wacom->ac); - wacom->ac = NULL; + if (wacom->battery.battery) { + devres_release_group(&wacom->hdev->dev, + &wacom->battery.bat_desc); + wacom->battery.battery = NULL; } } @@ -1179,7 +1544,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 @@ -1212,54 +1577,30 @@ 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; - char *buf; - struct wacom_wac *wacom_wac = &wacom->wacom_wac; + struct wacom_remote *remote = wacom->remote; - wacom_wac->serial[index] = serial; - - buf = kzalloc(WAC_REMOTE_SERIAL_MAX_STRLEN, GFP_KERNEL); - if (!buf) + remote->remotes[index].group.name = devm_kasprintf(&wacom->hdev->dev, + GFP_KERNEL, + "%d", serial); + if (!remote->remotes[index].group.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]); + error = __wacom_devm_sysfs_create_group(wacom, remote->remote_dir, + &remote->remotes[index].group); if (error) { + remote->remotes[index].group.name = NULL; hid_err(wacom->hdev, "cannot create sysfs group err: %d\n", error); - kobject_put(wacom->remote_dir); return error; } return 0; } -void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial) -{ - struct wacom_wac *wacom_wac = &wacom->wacom_wac; - int i; - - if (!serial) - return; - - 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; - if (wacom->remote_group[i].name) { - sysfs_remove_group(wacom->remote_dir, - &wacom->remote_group[i]); - kfree(wacom->remote_group[i].name); - wacom->remote_group[i].name = NULL; - } - } - } -} - static int wacom_cmd_unpair_remote(struct wacom *wacom, unsigned char selector) { const size_t buf_size = 2; @@ -1316,27 +1657,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 = remote; - wacom->remote_dir = kobject_create_and_add("wacom_remote", - &wacom->hdev->dev.kobj); - if (!wacom->remote_dir) + 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->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); + if (!remote->remote_dir) + return -ENOMEM; + + error = sysfs_create_files(remote->remote_dir, remote_unpair_attrs); if (error) { hid_err(wacom->hdev, @@ -1345,10 +1716,15 @@ static int wacom_initialize_remote(struct wacom *wacom) } for (i = 0; i < WACOM_MAX_REMOTES; i++) { - wacom->led.select[i] = WACOM_STATUS_UNKNOWN; - wacom_wac->serial[i] = 0; + wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; + remote->remotes[i].serial = 0; } + error = devm_add_action_or_reset(&wacom->hdev->dev, + wacom_remotes_destroy, wacom); + if (error) + return error; + return 0; } @@ -1358,7 +1734,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; @@ -1377,36 +1753,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); - } - kobject_put(wacom->remote_dir); - 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; - wacom_destroy_leds(wacom); -} - static int wacom_allocate_inputs(struct wacom *wacom) { struct wacom_wac *wacom_wac = &(wacom->wacom_wac); @@ -1414,10 +1760,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; @@ -1448,8 +1794,7 @@ static int wacom_register_inputs(struct wacom *wacom) } else { error = input_register_device(pen_input_dev); if (error) - goto fail_register_pen_input; - wacom_wac->pen_registered = true; + goto fail; } error = wacom_setup_touch_input_capabilities(touch_input_dev, wacom_wac); @@ -1461,8 +1806,7 @@ static int wacom_register_inputs(struct wacom *wacom) } else { error = input_register_device(touch_input_dev); if (error) - goto fail_register_touch_input; - wacom_wac->touch_registered = true; + goto fail; } error = wacom_setup_pad_input_capabilities(pad_input_dev, wacom_wac); @@ -1474,37 +1818,15 @@ static int wacom_register_inputs(struct wacom *wacom) } else { error = input_register_device(pad_input_dev); if (error) - goto fail_register_pad_input; - wacom_wac->pad_registered = true; - - error = wacom_initialize_leds(wacom); - if (error) - goto fail_leds; - - error = wacom_initialize_remote(wacom); - if (error) - goto fail_remote; + goto fail; } return 0; -fail_remote: - wacom_destroy_leds(wacom); -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); +fail: + wacom_wac->pad_input = NULL; 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; } @@ -1543,14 +1865,14 @@ 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) { + !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); } } @@ -1606,6 +1928,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); @@ -1615,6 +1940,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; @@ -1627,9 +1968,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; /* * Bamboo Pad has a generic hid handling for the Pen, and we switch it @@ -1642,7 +1988,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; } } @@ -1662,7 +2008,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; } @@ -1673,18 +2019,28 @@ 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; + + error = wacom_initialize_remotes(wacom); + if (error) + goto fail; + } if (features->type == HID_GENERIC) connect_mask |= HID_CONNECT_DRIVER; @@ -1693,7 +2049,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) { @@ -1705,7 +2061,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,37 +2069,33 @@ 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) 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; } + devres_close_group(&hdev->dev, wacom); + return 0; -fail_hw_start: +fail_quirks: hid_hw_stop(hdev); -fail_register_inputs: - wacom_clean_inputs(wacom); - wacom_destroy_battery(wacom); -fail_battery: - wacom_remove_shared_data(wacom); -fail_shared_data: -fail_parsed: -fail_allocate_inputs: - wacom_clean_inputs(wacom); +fail: + wacom_release_resources(wacom); return error; } 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; @@ -1762,17 +2114,16 @@ 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_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_clean_inputs(wacom2); + wacom_release_resources(wacom2); 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; @@ -1814,6 +2165,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; @@ -1822,11 +2175,177 @@ static void wacom_wireless_work(struct work_struct *work) return; fail: - wacom_clean_inputs(wacom1); - wacom_clean_inputs(wacom2); + wacom_release_resources(wacom1); + wacom_release_resources(wacom2); return; } +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].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]); + + for (i = 0; i < WACOM_MAX_REMOTES; i++) { + if (remote->remotes[i].serial == serial) { + 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; + } + } +} + +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, + * check to make sure this serial isn't already paired. + */ + for (k = 0; k < WACOM_MAX_REMOTES; k++) { + if (remote->remotes[k].serial == serial) + break; + } + + if (k < WACOM_MAX_REMOTES) { + remote->remotes[index].serial = serial; + return 0; + } + + 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->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; + + 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]); + return 0; + +fail: + devres_release_group(dev, &remote->remotes[index]); + remote->remotes[index].serial = 0; + 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); + struct wacom_remote *remote = wacom->remote; + struct wacom_remote_data data; + unsigned long flags; + unsigned int count; + u32 serial; + int i; + + spin_lock_irqsave(&remote->remote_lock, flags); + + 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(&remote->remote_lock, flags); + return; + } + + if (!kfifo_is_empty(&remote->remote_fifo)) + wacom_schedule_work(&wacom->wacom_wac, WACOM_WORKER_REMOTE); + + 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 (remote->remotes[i].serial == serial) { + wacom_remote_attach_battery(wacom, i); + continue; + } + + if (remote->remotes[i].serial) + wacom_remote_destroy_one(wacom, i); + + wacom_remote_create_one(wacom, serial, i); + + } else if (remote->remotes[i].serial) { + wacom_remote_destroy_one(wacom, i); + } + } +} + static int wacom_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -1845,7 +2364,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; @@ -1858,7 +2377,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; @@ -1867,18 +2386,20 @@ 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); + INIT_WORK(&wacom->remote_work, wacom_remote_work); /* ask for the report descriptor to be loaded by HID */ 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); @@ -1890,9 +2411,7 @@ static int wacom_probe(struct hid_device *hdev, return 0; -fail_type: -fail_parse: - kfree(wacom); +fail: hid_set_drvdata(hdev, NULL); return error; } @@ -1908,15 +2427,13 @@ static void wacom_remove(struct hid_device *hdev) hid_hw_stop(hdev); - cancel_work_sync(&wacom->work); - wacom_clean_inputs(wacom); + cancel_work_sync(&wacom->wireless_work); + cancel_work_sync(&wacom->battery_work); + cancel_work_sync(&wacom->remote_work); 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); - kfree(wacom); } #ifdef CONFIG_PM diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 1eae13cdc502..1cb79925730d 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); @@ -48,25 +52,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) @@ -751,22 +764,37 @@ 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_features *features = &wacom_wac->features; + struct wacom_remote *remote = wacom->remote; 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)); @@ -803,73 +831,69 @@ 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; for (i = 0; i < WACOM_MAX_REMOTES; i++) { - if (wacom_wac->serial[i] == serial) - wacom->led.select[i] = touch_ring_mode; - } - - 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); + if (remote->remotes[i].serial == serial) + wacom->led.groups[i].select = touch_ring_mode; } - 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); - return 1; +out: + spin_unlock_irqrestore(&remote->remote_lock, flags); + return 0; } -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 *remote = wacom->remote; + 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; + remote_data.remote[i].serial = serial; + remote_data.remote[i].connected = connected; + } - if (wacom_wac->serial[i] == serial) - continue; + spin_lock_irqsave(&remote->remote_lock, flags); - if (wacom_wac->serial[i]) { - wacom_remote_destroy_attr_group(wacom, - wacom_wac->serial[i]); - } + ret = kfifo_in(&remote->remote_fifo, &remote_data, sizeof(remote_data)); + if (ret != sizeof(remote_data)) { + spin_unlock_irqrestore(&remote->remote_lock, flags); + hid_err(wacom->hdev, "Can't queue Remote status event.\n"); + return; + } - /* 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; - } + spin_unlock_irqrestore(&remote->remote_lock, flags); - if (k < WACOM_MAX_REMOTES) { - wacom_wac->serial[i] = serial; - continue; - } - wacom_remote_create_attr_group(wacom, serial, i); + wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE); +} - } else if (wacom_wac->serial[i]) { - wacom_remote_destroy_attr_group(wacom, - wacom_wac->serial[i]); - } - } +static inline bool report_touch_events(struct wacom_wac *wacom) +{ + return (touch_arbitration ? !wacom->shared->stylus_in_proximity : 1); +} - return 0; +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) @@ -885,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 */ @@ -1145,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]; @@ -1186,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) @@ -1250,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); @@ -1284,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); @@ -1308,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) @@ -1353,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])); @@ -1496,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); @@ -1527,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); @@ -1544,13 +1571,11 @@ static void wacom_wac_finger_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; struct input_dev *input = wacom_wac->touch_input; unsigned touch_max = wacom_wac->features.touch_max; switch (usage->hid) { case HID_GD_X: - features->last_slot_field = usage->hid; if (touch_max == 1) wacom_map_usage(input, usage, field, EV_ABS, ABS_X, 4); else @@ -1558,7 +1583,6 @@ static void wacom_wac_finger_usage_mapping(struct hid_device *hdev, ABS_MT_POSITION_X, 4); break; case HID_GD_Y: - features->last_slot_field = usage->hid; if (touch_max == 1) wacom_map_usage(input, usage, field, EV_ABS, ABS_Y, 4); else @@ -1567,22 +1591,11 @@ static void wacom_wac_finger_usage_mapping(struct hid_device *hdev, break; case HID_DG_WIDTH: case HID_DG_HEIGHT: - features->last_slot_field = usage->hid; wacom_map_usage(input, usage, field, EV_ABS, ABS_MT_TOUCH_MAJOR, 0); wacom_map_usage(input, usage, field, EV_ABS, ABS_MT_TOUCH_MINOR, 0); input_set_abs_params(input, ABS_MT_ORIENTATION, 0, 1, 0, 0); break; - case HID_DG_CONTACTID: - features->last_slot_field = usage->hid; - break; - case HID_DG_INRANGE: - features->last_slot_field = usage->hid; - break; - case HID_DG_INVERT: - features->last_slot_field = usage->hid; - break; case HID_DG_TIPSWITCH: - features->last_slot_field = usage->hid; wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0); break; case HID_DG_CONTACTCOUNT: @@ -1599,7 +1612,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) @@ -1660,7 +1673,7 @@ static int wacom_wac_finger_event(struct hid_device *hdev, if (usage->usage_index + 1 == field->report_count) { - if (usage->hid == wacom_wac->features.last_slot_field) + if (usage->hid == wacom_wac->hid_data.last_slot_field) wacom_wac_finger_slot(wacom_wac, wacom_wac->touch_input); } @@ -1673,31 +1686,35 @@ static void wacom_wac_finger_pre_report(struct hid_device *hdev, struct wacom *wacom = hid_get_drvdata(hdev); struct wacom_wac *wacom_wac = &wacom->wacom_wac; struct hid_data* hid_data = &wacom_wac->hid_data; + int i; - if (hid_data->cc_report != 0 && - hid_data->cc_report != report->id) { - int i; - - hid_data->cc_report = report->id; - hid_data->cc_index = -1; - hid_data->cc_value_index = -1; - - for (i = 0; i < report->maxfield; i++) { - struct hid_field *field = report->field[i]; - int j; - - for (j = 0; j < field->maxusage; j++) { - if (field->usage[j].hid == HID_DG_CONTACTCOUNT) { - hid_data->cc_index = i; - hid_data->cc_value_index = j; - - /* break */ - i = report->maxfield; - j = field->maxusage; - } + for (i = 0; i < report->maxfield; i++) { + struct hid_field *field = report->field[i]; + int j; + + for (j = 0; j < field->maxusage; j++) { + struct hid_usage *usage = &field->usage[j]; + + switch (usage->hid) { + case HID_GD_X: + case HID_GD_Y: + case HID_DG_WIDTH: + case HID_DG_HEIGHT: + case HID_DG_CONTACTID: + case HID_DG_INRANGE: + case HID_DG_INVERT: + case HID_DG_TIPSWITCH: + hid_data->last_slot_field = usage->hid; + break; + case HID_DG_CONTACTCOUNT: + hid_data->cc_report = report->id; + hid_data->cc_index = i; + hid_data->cc_value_index = j; + break; } } } + if (hid_data->cc_report != 0 && hid_data->cc_index >= 0) { struct hid_field *field = report->field[hid_data->cc_index]; @@ -1740,10 +1757,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); @@ -1825,15 +1842,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); @@ -1870,7 +1880,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); @@ -1942,7 +1952,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); } @@ -1983,7 +1993,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) { @@ -2077,7 +2087,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); @@ -2099,8 +2109,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; } @@ -2149,16 +2158,15 @@ 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 (wacom->shared->type) - 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 */ wacom->pid = 0; - wacom_schedule_work(wacom); + wacom_schedule_work(wacom, WACOM_WORKER_WIRELESS); wacom_notify_battery(wacom, 0, 0, 0, 0); } @@ -2190,18 +2198,16 @@ 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; - 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) { + wacom->battery.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; @@ -2312,8 +2318,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; @@ -2451,6 +2458,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; @@ -2469,6 +2503,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, @@ -2481,6 +2518,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; @@ -2499,7 +2541,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); @@ -2523,8 +2564,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: @@ -2539,7 +2578,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; @@ -2555,8 +2593,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; @@ -2566,8 +2602,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); @@ -2597,8 +2631,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: @@ -2609,16 +2641,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 { @@ -2649,6 +2677,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; @@ -2683,8 +2716,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); @@ -2707,7 +2738,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: @@ -2752,11 +2782,105 @@ 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; + + /* + * 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_21UX2) + group = 1 - 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; + + 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; + + 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++) @@ -2773,6 +2897,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 */ @@ -3403,7 +3530,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),\ diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 53d16537fd2a..324c40b0c119 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 @@ -185,7 +186,6 @@ struct wacom_features { int pktlen; bool check_for_hid_type; int hid_type; - int last_slot_field; }; struct wacom_shared { @@ -214,35 +214,35 @@ struct hid_data { int cc_report; int cc_index; int cc_value_index; + int last_slot_field; int num_expected; int num_received; }; +struct wacom_remote_data { + struct { + u32 serial; + bool connected; + } remote[WACOM_MAX_REMOTES]; +}; + 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]; - char bat_name[WACOM_NAME_MAX]; - char ac_name[WACOM_NAME_MAX]; 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; 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; - int bat_charging; - int bat_connected; - int ps_connected; u8 bt_features; u8 bt_high_speed; int mode_report; |