Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 7 Oct 2016 18:58:38 +0000 (11:58 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 7 Oct 2016 18:58:38 +0000 (11:58 -0700)
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
  ...

48 files changed:
Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff
Documentation/ABI/testing/sysfs-driver-wacom
Documentation/hid/intel-ish-hid.txt [new file with mode: 0644]
MAINTAINERS
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/hid-alps.c
drivers/hid/hid-core.c
drivers/hid/hid-ids.h
drivers/hid/hid-input.c
drivers/hid/hid-kye.c
drivers/hid/hid-lg.c
drivers/hid/hid-lg4ff.c
drivers/hid/hid-lg4ff.h
drivers/hid/hid-microsoft.c
drivers/hid/hid-saitek.c
drivers/hid/hid-sensor-hub.c
drivers/hid/hid-sony.c
drivers/hid/hid-uclogic.c
drivers/hid/hid-waltop.c
drivers/hid/intel-ish-hid/Kconfig [new file with mode: 0644]
drivers/hid/intel-ish-hid/Makefile [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/hw-ish.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/ipc.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/pci-ish.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ipc/utils.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp-hid-client.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp-hid.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp-hid.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/bus.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/bus.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/client-buffers.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/client.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/client.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/dma-if.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/hbm.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/hbm.h [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/init.c [new file with mode: 0644]
drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h [new file with mode: 0644]
drivers/hid/usbhid/hid-quirks.c
drivers/hid/wacom.h
drivers/hid/wacom_sys.c
drivers/hid/wacom_wac.c
drivers/hid/wacom_wac.h
include/linux/hid.h
include/trace/events/intel_ish.h [new file with mode: 0644]
include/uapi/linux/input.h

index db197a8795809b9bc6f2e882b7519b12affbeab3..305dffd229a83cab521758cde40d61b055b9d6c6 100644 (file)
@@ -35,6 +35,12 @@ Description: Displays a set of alternate modes supported by a wheel. Each
                  DF-EX <*--------> G25 <-> G27
                  DF-EX <*----------------> G27
 
+               G29:
+                 DF-EX <*> DFP <-> G25 <-> G27 <-> G29
+                 DF-EX <*--------> G25 <-> G27 <-> G29
+                 DF-EX <*----------------> G27 <-> G29
+                 DF-EX <*------------------------> G29
+
                DFGT:
                  DF-EX <*> DFP <-> DFGT
                  DF-EX <*--------> DFGT
@@ -50,3 +56,12 @@ Description: Displays the real model of the wheel regardless of any
                alternate mode the wheel might be switched to.
                It is a read-only value.
                This entry is not created for devices that have only one mode.
+
+What:          /sys/bus/hid/drivers/logitech/<dev>/combine_pedals
+Date:          Sep 2016
+KernelVersion: 4.9
+Contact:       Simon Wood <simon@mungewell.org>
+Description:   Controls whether a combined value of accelerator and brake is
+               reported on the Y axis of the controller. Useful for older games
+               which can do not work with separate accelerator/brake axis.
+               Off ('0') by default, enabled by setting '1'.
index dca4293407726654d4a5da02f1911c644d052972..2aa5503ee200763bf5e841fbdcd77f0422247d1d 100644 (file)
@@ -24,6 +24,7 @@ What:         /sys/bus/hid/devices/<bus>:<vid>:<pid>.<n>/wacom_led/status0_luminance
 Date:          August 2014
 Contact:       linux-input@vger.kernel.org
 Description:
+               <obsoleted by the LED class API now exported by the driver>
                Writing to this file sets the status LED luminance (1..127)
                when the stylus does not touch the tablet surface, and no
                button is pressed on the stylus. This luminance level is
@@ -33,6 +34,7 @@ What:         /sys/bus/hid/devices/<bus>:<vid>:<pid>.<n>/wacom_led/status1_luminance
 Date:          August 2014
 Contact:       linux-input@vger.kernel.org
 Description:
+               <obsoleted by the LED class API now exported by the driver>
                Writing to this file sets the status LED luminance (1..127)
                when the stylus touches the tablet surface, or any button is
                pressed on the stylus.
@@ -41,6 +43,7 @@ What:         /sys/bus/hid/devices/<bus>:<vid>:<pid>.<n>/wacom_led/status_led0_select
 Date:          August 2014
 Contact:       linux-input@vger.kernel.org
 Description:
+               <obsoleted by the LED class API now exported by the driver>
                Writing to this file sets which one of the four (for Intuos 4
                and Intuos 5) or of the right four (for Cintiq 21UX2 and Cintiq
                24HD) status LEDs is active (0..3). The other three LEDs on the
@@ -50,6 +53,7 @@ What:         /sys/bus/hid/devices/<bus>:<vid>:<pid>.<n>/wacom_led/status_led1_select
 Date:          August 2014
 Contact:       linux-input@vger.kernel.org
 Description:
+               <obsoleted by the LED class API now exported by the driver>
                Writing to this file sets which one of the left four (for Cintiq 21UX2
                and Cintiq 24HD) status LEDs is active (0..3). The other three LEDs on
                the left are always inactive.
@@ -91,6 +95,7 @@ What:         /sys/bus/hid/devices/<bus>:<vid>:<pid>.<n>/wacom_remote/<serial_number>/r
 Date:          July 2015
 Contact:       linux-input@vger.kernel.org
 Description:
+               <obsoleted by the LED class API now exported by the driver>
                Reading from this file reports the mode status of the
                remote as indicated by the LED lights on the device. If no
                reports have been received from the paired device, reading
diff --git a/Documentation/hid/intel-ish-hid.txt b/Documentation/hid/intel-ish-hid.txt
new file mode 100644 (file)
index 0000000..d48b21c
--- /dev/null
@@ -0,0 +1,454 @@
+Intel Integrated Sensor Hub (ISH)
+===============================
+
+A sensor hub enables the ability to offload sensor polling and algorithm
+processing to a dedicated low power co-processor. This allows the core
+processor to go into low power modes more often, resulting in the increased
+battery life.
+
+There are many vendors providing external sensor hubs confirming to HID
+Sensor usage tables, and used in several tablets, 2 in 1 convertible laptops
+and embedded products. Linux had this support since Linux 3.9.
+
+Intel® introduced integrated sensor hubs as a part of the SoC starting from
+Cherry Trail and now supported on multiple generations of CPU packages. There
+are many commercial devices already shipped with Integrated Sensor Hubs (ISH).
+These ISH also comply to HID sensor specification, but the  difference is the
+transport protocol used for communication. The current external sensor hubs
+mainly use HID over i2C or USB. But ISH doesn't use either i2c or USB.
+
+1. Overview
+
+Using a analogy with a usbhid implementation, the ISH follows a similar model
+for a very high speed communication:
+
+       -----------------               ----------------------
+       |    USB HID    |       -->     |    ISH HID         |
+       -----------------               ----------------------
+       -----------------               ----------------------
+       |  USB protocol |       -->     |    ISH Transport   |
+       -----------------               ----------------------
+       -----------------               ----------------------
+       |  EHCI/XHCI    |       -->     |    ISH IPC         |
+       -----------------               ----------------------
+             PCI                                PCI
+       -----------------               ----------------------
+        |Host controller|      -->     |    ISH processor   |
+       -----------------               ----------------------
+            USB Link
+       -----------------               ----------------------
+       | USB End points|       -->     |    ISH Clients     |
+       -----------------               ----------------------
+
+Like USB protocol provides a method for device enumeration, link management
+and user data encapsulation, the ISH also provides similar services. But it is
+very light weight tailored to manage and communicate with ISH client
+applications implemented in the firmware.
+
+The ISH allows multiple sensor management applications executing in the
+firmware. Like USB endpoints the messaging can be to/from a client. As part of
+enumeration process, these clients are identified. These clients can be simple
+HID sensor applications, sensor calibration application or senor firmware
+update application.
+
+The implementation model is similar, like USB bus, ISH transport is also
+implemented as a bus. Each client application executing in the ISH processor
+is registered as a device on this bus. The driver, which binds each device
+(ISH HID driver) identifies the device type and registers with the hid core.
+
+2. ISH Implementation: Block Diagram
+
+        ---------------------------
+       |  User Space Applications  |
+        ---------------------------
+
+----------------IIO ABI----------------
+        --------------------------
+       |  IIO Sensor Drivers     |
+        --------------------------
+        --------------------------
+       |        IIO core         |
+        --------------------------
+        --------------------------
+       |   HID Sensor Hub MFD    |
+        --------------------------
+        --------------------------
+       |       HID Core          |
+        --------------------------
+        --------------------------
+       |   HID over ISH Client   |
+        --------------------------
+        --------------------------
+       |   ISH Transport (ISHTP) |
+        --------------------------
+        --------------------------
+       |      IPC Drivers        |
+        --------------------------
+OS
+----------------   PCI -----------------
+Hardware + Firmware
+        ----------------------------
+       | ISH Hardware/Firmware(FW) |
+        ----------------------------
+
+3. High level processing in above blocks
+
+3.1 Hardware Interface
+
+The ISH is exposed as "Non-VGA unclassified PCI device" to the host. The PCI
+product and vendor IDs are changed from different generations of processors. So
+the source code which enumerate drivers needs to update from generation to
+generation.
+
+3.2 Inter Processor Communication (IPC) driver
+Location: drivers/hid/intel-ish-hid/ipc
+
+The IPC message used memory mapped I/O. The registers are defined in
+hw-ish-regs.h.
+
+3.2.1 IPC/FW message types
+
+There are two types of messages, one for management of link and other messages
+are to and from transport layers.
+
+TX and RX of Transport messages
+
+A set of memory mapped register offers support of multi byte messages TX and
+RX (E.g.IPC_REG_ISH2HOST_MSG, IPC_REG_HOST2ISH_MSG). The IPC layer maintains
+internal queues to sequence messages and send them in order to the FW.
+Optionally the caller can register handler to get notification of completion.
+A door bell mechanism is used in messaging to trigger processing in host and
+client firmware side. When ISH interrupt handler is called, the ISH2HOST
+doorbell register is used by host drivers to determine that the interrupt
+is for ISH.
+
+Each side has 32 32-bit message registers and a 32-bit doorbell. Doorbell
+register has the following format:
+Bits 0..6: fragment length (7 bits are used)
+Bits 10..13: encapsulated protocol
+Bits 16..19: management command (for IPC management protocol)
+Bit 31: doorbell trigger (signal H/W interrupt to the other side)
+Other bits are reserved, should be 0.
+
+3.2.2 Transport layer interface
+
+To abstract HW level IPC communication, a set of callbacks are registered.
+The transport layer uses them to send and receive messages.
+Refer to  struct ishtp_hw_ops for callbacks.
+
+3.3 ISH Transport layer
+Location: drivers/hid/intel-ish-hid/ishtp/
+
+3.3.1 A Generic Transport Layer
+
+The transport layer is a bi-directional protocol, which defines:
+- Set of commands to start, stop, connect, disconnect and flow control
+(ishtp/hbm.h) for details
+- A flow control mechanism to avoid buffer overflows
+
+This protocol resembles bus messages described in the following document:
+http://www.intel.com/content/dam/www/public/us/en/documents/technical-\
+specifications/dcmi-hi-1-0-spec.pdf "Chapter 7: Bus Message Layer"
+
+3.3.2 Connection and Flow Control Mechanism
+
+Each FW client and a protocol is identified by an UUID. In order to communicate
+to a FW client, a connection must be established using connect request and
+response bus messages. If successful, a pair (host_client_id and fw_client_id)
+will identify the connection.
+
+Once connection is established, peers send each other flow control bus messages
+independently. Every peer may send a message only if it has received a
+flow-control credit before. Once it sent a message, it may not send another one
+before receiving the next flow control credit.
+Either side can send disconnect request bus message to end communication. Also
+the link will be dropped if major FW reset occurs.
+
+3.3.3 Peer to Peer data transfer
+
+Peer to Peer data transfer can happen with or without using DMA. Depending on
+the sensor bandwidth requirement DMA can be enabled by using module parameter
+ishtp_use_dma under intel_ishtp.
+
+Each side (host and FW) manages its DMA transfer memory independently. When an
+ISHTP client from either host or FW side wants to send something, it decides
+whether to send over IPC or over DMA; for each transfer the decision is
+independent. The sending side sends DMA_XFER message when the message is in
+the respective host buffer (TX when host client sends, RX when FW client
+sends). The recipient of DMA message responds with DMA_XFER_ACK, indicating
+the sender that the memory region for that message may be reused.
+
+DMA initialization is started with host sending DMA_ALLOC_NOTIFY bus message
+(that includes RX buffer) and FW responds with DMA_ALLOC_NOTIFY_ACK.
+Additionally to DMA address communication, this sequence checks capabilities:
+if thw host doesn't support DMA, then it won't send DMA allocation, so FW can't
+send DMA; if FW doesn't support DMA then it won't respond with
+DMA_ALLOC_NOTIFY_ACK, in which case host will not use DMA transfers.
+Here ISH acts as busmaster DMA controller. Hence when host sends DMA_XFER,
+it's request to do host->ISH DMA transfer; when FW sends DMA_XFER, it means
+that it already did DMA and the message resides at host. Thus, DMA_XFER
+and DMA_XFER_ACK act as ownership indicators.
+
+At initial state all outgoing memory belongs to the sender (TX to host, RX to
+FW), DMA_XFER transfers ownership on the region that contains ISHTP message to
+the receiving side, DMA_XFER_ACK returns ownership to the sender. A sender
+needs not wait for previous DMA_XFER to be ack'ed, and may send another message
+as long as remaining continuous memory in its ownership is enough.
+In principle, multiple DMA_XFER and DMA_XFER_ACK messages may be sent at once
+(up to IPC MTU), thus allowing for interrupt throttling.
+Currently, ISH FW decides to send over DMA if ISHTP message is more than 3 IPC
+fragments and via IPC otherwise.
+
+3.3.4 Ring Buffers
+
+When a client initiate a connection, a ring or RX and TX buffers are allocated.
+The size of ring can be specified by the client. HID client set 16 and 32 for
+TX and RX buffers respectively. On send request from client, the data to be
+sent is copied to one of the send ring buffer and scheduled to be sent using
+bus message protocol. These buffers are required because the FW may have not
+have processed the last message and may not have enough flow control credits
+to send. Same thing holds true on receive side and flow control is required.
+
+3.3.5 Host Enumeration
+
+The host enumeration bus command allow discovery of clients present in the FW.
+There can be multiple sensor clients and clients for calibration function.
+
+To ease in implantation and allow independent driver handle each client
+this transport layer takes advantage of Linux Bus driver model. Each
+client is registered as device on the the transport bus (ishtp bus).
+
+Enumeration sequence of messages:
+- Host sends HOST_START_REQ_CMD, indicating that host ISHTP layer is up.
+- FW responds with HOST_START_RES_CMD
+- Host sends HOST_ENUM_REQ_CMD (enumerate FW clients)
+- FW responds with HOST_ENUM_RES_CMD that includes bitmap of available FW
+client IDs
+- For each FW ID found in that bitmap host sends
+HOST_CLIENT_PROPERTIES_REQ_CMD
+- FW responds with HOST_CLIENT_PROPERTIES_RES_CMD. Properties include UUID,
+max ISHTP message size, etc.
+- Once host received properties for that last discovered client, it considers
+ISHTP device fully functional (and allocates DMA buffers)
+
+3.4 HID over ISH Client
+Location: drivers/hid/intel-ish-hid
+
+The ISHTP client driver is responsible for:
+- enumerate HID devices under FW ISH client
+- Get Report descriptor
+- Register with HID core as a LL driver
+- Process Get/Set feature request
+- Get input reports
+
+3.5 HID Sensor Hub MFD and IIO sensor drivers
+
+The functionality in these drivers is the same as an external sensor hub.
+Refer to
+Documentation/hid/hid-sensor.txt for HID sensor
+Documentation/ABI/testing/sysfs-bus-iio for IIO ABIs to user space
+
+3.6 End to End HID transport Sequence Diagram
+
+HID-ISH-CLN                    ISHTP                   IPC                             HW
+       |                       |                       |                               |
+       |                       |                       |-----WAKE UP------------------>|
+       |                       |                       |                               |
+       |                       |                       |-----HOST READY--------------->|
+       |                       |                       |                               |
+       |                       |                       |<----MNG_RESET_NOTIFY_ACK----- |
+       |                       |                       |                               |
+       |                       |<----ISHTP_START------ |                               |
+       |                       |                       |                               |
+       |                       |<-----------------HOST_START_RES_CMD-------------------|
+       |                       |                       |                               |
+       |                       |------------------QUERY_SUBSCRIBER-------------------->|
+       |                       |                       |                               |
+       |                       |------------------HOST_ENUM_REQ_CMD------------------->|
+       |                       |                       |                               |
+       |                       |<-----------------HOST_ENUM_RES_CMD--------------------|
+       |                       |                       |                               |
+       |                       |------------------HOST_CLIENT_PROPERTIES_REQ_CMD------>|
+       |                       |                       |                               |
+       |                       |<-----------------HOST_CLIENT_PROPERTIES_RES_CMD-------|
+       |       Create new device on in ishtp bus       |                               |
+       |                       |                       |                               |
+       |                       |------------------HOST_CLIENT_PROPERTIES_REQ_CMD------>|
+       |                       |                       |                               |
+       |                       |<-----------------HOST_CLIENT_PROPERTIES_RES_CMD-------|
+       |       Create new device on in ishtp bus       |                               |
+       |                       |                       |                               |
+       |                       |--Repeat HOST_CLIENT_PROPERTIES_REQ_CMD-till last one--|
+       |                       |                       |                               |
+     probed()
+       |----ishtp_cl_connect-->|----------------- CLIENT_CONNECT_REQ_CMD-------------->|
+       |                       |                       |                               |
+       |                       |<----------------CLIENT_CONNECT_RES_CMD----------------|
+       |                       |                       |                               |
+       |register event callback|                       |                               |
+       |                       |                       |                               |
+       |ishtp_cl_send(
+       HOSTIF_DM_ENUM_DEVICES) |----------fill ishtp_msg_hdr struct write to HW-----  >|
+       |                       |                       |                               |
+       |                       |                       |<-----IRQ(IPC_PROTOCOL_ISHTP---|
+       |                       |                       |                               |
+       |<--ENUM_DEVICE RSP-----|                       |                               |
+       |                       |                       |                               |
+for each enumerated device
+       |ishtp_cl_send(
+       HOSTIF_GET_HID_DESCRIPTOR |----------fill ishtp_msg_hdr struct write to HW---  >|
+       |                       |                       |                               |
+       ...Response
+       |                       |                       |                               |
+for each enumerated device
+       |ishtp_cl_send(
+       HOSTIF_GET_REPORT_DESCRIPTOR |----------fill ishtp_msg_hdr struct write to HW- >|
+       |                       |                       |                               |
+       |                       |                       |                               |
+ hid_allocate_device
+       |                       |                       |                               |
+ hid_add_device                        |                       |                               |
+       |                       |                       |                               |
+
+
+3.7 ISH Debugging
+
+To debug ISH, event tracing mechanism is used. To enable debug logs
+echo 1 > /sys/kernel/debug/tracing/events/intel_ish/enable
+cat sys/kernel/debug/tracing/trace
+
+3.8 ISH IIO sysfs Example on Lenovo thinkpad Yoga 260
+
+root@otcpl-ThinkPad-Yoga-260:~# tree -l /sys/bus/iio/devices/
+/sys/bus/iio/devices/
+├── iio:device0 -> ../../../devices/0044:8086:22D8.0001/HID-SENSOR-200073.9.auto/iio:device0
+│   ├── buffer
+│   │   ├── enable
+│   │   ├── length
+│   │   └── watermark
+...
+│   ├── in_accel_hysteresis
+│   ├── in_accel_offset
+│   ├── in_accel_sampling_frequency
+│   ├── in_accel_scale
+│   ├── in_accel_x_raw
+│   ├── in_accel_y_raw
+│   ├── in_accel_z_raw
+│   ├── name
+│   ├── scan_elements
+│   │   ├── in_accel_x_en
+│   │   ├── in_accel_x_index
+│   │   ├── in_accel_x_type
+│   │   ├── in_accel_y_en
+│   │   ├── in_accel_y_index
+│   │   ├── in_accel_y_type
+│   │   ├── in_accel_z_en
+│   │   ├── in_accel_z_index
+│   │   └── in_accel_z_type
+...
+│   │   ├── devices
+│   │   │   │   ├── buffer
+│   │   │   │   │   ├── enable
+│   │   │   │   │   ├── length
+│   │   │   │   │   └── watermark
+│   │   │   │   ├── dev
+│   │   │   │   ├── in_intensity_both_raw
+│   │   │   │   ├── in_intensity_hysteresis
+│   │   │   │   ├── in_intensity_offset
+│   │   │   │   ├── in_intensity_sampling_frequency
+│   │   │   │   ├── in_intensity_scale
+│   │   │   │   ├── name
+│   │   │   │   ├── scan_elements
+│   │   │   │   │   ├── in_intensity_both_en
+│   │   │   │   │   ├── in_intensity_both_index
+│   │   │   │   │   └── in_intensity_both_type
+│   │   │   │   ├── trigger
+│   │   │   │   │   └── current_trigger
+...
+│   │   │   │   ├── buffer
+│   │   │   │   │   ├── enable
+│   │   │   │   │   ├── length
+│   │   │   │   │   └── watermark
+│   │   │   │   ├── dev
+│   │   │   │   ├── in_magn_hysteresis
+│   │   │   │   ├── in_magn_offset
+│   │   │   │   ├── in_magn_sampling_frequency
+│   │   │   │   ├── in_magn_scale
+│   │   │   │   ├── in_magn_x_raw
+│   │   │   │   ├── in_magn_y_raw
+│   │   │   │   ├── in_magn_z_raw
+│   │   │   │   ├── in_rot_from_north_magnetic_tilt_comp_raw
+│   │   │   │   ├── in_rot_hysteresis
+│   │   │   │   ├── in_rot_offset
+│   │   │   │   ├── in_rot_sampling_frequency
+│   │   │   │   ├── in_rot_scale
+│   │   │   │   ├── name
+...
+│   │   │   │   ├── scan_elements
+│   │   │   │   │   ├── in_magn_x_en
+│   │   │   │   │   ├── in_magn_x_index
+│   │   │   │   │   ├── in_magn_x_type
+│   │   │   │   │   ├── in_magn_y_en
+│   │   │   │   │   ├── in_magn_y_index
+│   │   │   │   │   ├── in_magn_y_type
+│   │   │   │   │   ├── in_magn_z_en
+│   │   │   │   │   ├── in_magn_z_index
+│   │   │   │   │   ├── in_magn_z_type
+│   │   │   │   │   ├── in_rot_from_north_magnetic_tilt_comp_en
+│   │   │   │   │   ├── in_rot_from_north_magnetic_tilt_comp_index
+│   │   │   │   │   └── in_rot_from_north_magnetic_tilt_comp_type
+│   │   │   │   ├── trigger
+│   │   │   │   │   └── current_trigger
+...
+│   │   │   │   ├── buffer
+│   │   │   │   │   ├── enable
+│   │   │   │   │   ├── length
+│   │   │   │   │   └── watermark
+│   │   │   │   ├── dev
+│   │   │   │   ├── in_anglvel_hysteresis
+│   │   │   │   ├── in_anglvel_offset
+│   │   │   │   ├── in_anglvel_sampling_frequency
+│   │   │   │   ├── in_anglvel_scale
+│   │   │   │   ├── in_anglvel_x_raw
+│   │   │   │   ├── in_anglvel_y_raw
+│   │   │   │   ├── in_anglvel_z_raw
+│   │   │   │   ├── name
+│   │   │   │   ├── scan_elements
+│   │   │   │   │   ├── in_anglvel_x_en
+│   │   │   │   │   ├── in_anglvel_x_index
+│   │   │   │   │   ├── in_anglvel_x_type
+│   │   │   │   │   ├── in_anglvel_y_en
+│   │   │   │   │   ├── in_anglvel_y_index
+│   │   │   │   │   ├── in_anglvel_y_type
+│   │   │   │   │   ├── in_anglvel_z_en
+│   │   │   │   │   ├── in_anglvel_z_index
+│   │   │   │   │   └── in_anglvel_z_type
+│   │   │   │   ├── trigger
+│   │   │   │   │   └── current_trigger
+...
+│   │   │   │   ├── buffer
+│   │   │   │   │   ├── enable
+│   │   │   │   │   ├── length
+│   │   │   │   │   └── watermark
+│   │   │   │   ├── dev
+│   │   │   │   ├── in_anglvel_hysteresis
+│   │   │   │   ├── in_anglvel_offset
+│   │   │   │   ├── in_anglvel_sampling_frequency
+│   │   │   │   ├── in_anglvel_scale
+│   │   │   │   ├── in_anglvel_x_raw
+│   │   │   │   ├── in_anglvel_y_raw
+│   │   │   │   ├── in_anglvel_z_raw
+│   │   │   │   ├── name
+│   │   │   │   ├── scan_elements
+│   │   │   │   │   ├── in_anglvel_x_en
+│   │   │   │   │   ├── in_anglvel_x_index
+│   │   │   │   │   ├── in_anglvel_x_type
+│   │   │   │   │   ├── in_anglvel_y_en
+│   │   │   │   │   ├── in_anglvel_y_index
+│   │   │   │   │   ├── in_anglvel_y_type
+│   │   │   │   │   ├── in_anglvel_z_en
+│   │   │   │   │   ├── in_anglvel_z_index
+│   │   │   │   │   └── in_anglvel_z_type
+│   │   │   │   ├── trigger
+│   │   │   │   │   └── current_trigger
+...
index 4d3426434a987e9c35adbdd52b29000b4d6d9d41..33d77794a1f2289c64418bbed0ab2ba8bd49ac0d 100644 (file)
@@ -6205,6 +6205,13 @@ T:       git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git
 S:     Supported
 F:     drivers/idle/intel_idle.c
 
+INTEL INTEGRATED SENSOR HUB DRIVER
+M:     Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+M:     Jiri Kosina <jikos@kernel.org>
+L:     linux-input@vger.kernel.org
+S:     Maintained
+F:     drivers/hid/intel-ish-hid/
+
 INTEL PSTATE DRIVER
 M:     Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
 M:     Len Brown <lenb@kernel.org>
index 78ac4811bd3c0c0683e62bfe424c43c0fba4c8a9..cd4599c0523bf62e2c4ebd886c51763fb6307bb5 100644 (file)
@@ -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
index fc4b2aa47f2e7bb5dcbf8c71e65777d5ba785fe5..86b2b5785fd220974a0fe9814957e799e5bcec6a 100644 (file)
@@ -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/
index 048befde295a2f79e8c847b87f7aebd0b93d7c0c..ed9c0ea5b026efa698462bc389307bd16427f98c 100644 (file)
@@ -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;
                }
index 08f53c7fd5132f86123f15fc2b5c3cadf12604b7..2b89c701076f3eadc9c572015c3d20cfcfab6bd1 100644 (file)
@@ -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) },
index 4ed9a4fdfea789ff94da728555b6c9ca7d802bd8..cd59c79eebdd2bd896c1ff19d687f25686d7fb35 100644 (file)
 #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
 #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_    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
 
 #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
 
 #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
 #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
 #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
index bcfaf32d9e5ebd4284d0608db962aa2c6d789cbd..fb9ace1cef8b50cbcb52955eb41f4490cb7aff7f 100644 (file)
@@ -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;
index 32e6d8d9ded0ed4b2e4f1ab1d7c86d7f6413ec07..0dd1167b2c9b5f87b3e7ca3c828e38b171c043fb 100644 (file)
 
 #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,
index feb2be71f77c199e9ab21fabd0e6ffcd267d2d9e..76f644deb0a75cab6d6810517d4196465be18314 100644 (file)
@@ -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,
 };
index af3a8ec8a746787a9702755b6ffb2f54c23a666a..1fc12e3570359248f2cd2847aad219ad4f134a81 100644 (file)
@@ -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
        {
index 66201af44da33b6c699983c7e04330fd765c3c7e..de1f350e0bd300e5a81d4546bd1b4063becf8e11 100644 (file)
@@ -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
index e924d555536cf7fb1b221af4c15e51112df107c8..c6cd392e9f99c6cba2c5948bc2c42f2cf066e525 100644 (file)
@@ -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),
index 2f84b26f116706c131abda474ab855d1aff11cfa..39e642686ff0466322cdacf7ea410fd6306932e5 100644 (file)
@@ -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),
index 3d5ba5b51af3aa4dc70197b9453955fb17cc3437..658a607dc6d9eb1f7c57da3c4afe065e32ffd156 100644 (file)
@@ -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) },
        { }
index 310436a54a3f308157a272d56d6624c3dd3c98c1..b0bb99a821bd777692cc845ec2992d95834ffeb6 100644 (file)
@@ -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);
        }
index 85ac43517e3ffc3232461f54d43054cd36a39a29..1509d7287ff3e60e79c845214e47e901fae650f8 100644 (file)
 
 #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);
index 059931d7b3922ff064ea0f59b63f2d9b8d9ff745..a91aabe4a70a7a6d873f223309552f8c5e543eaf 100644 (file)
  * 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 (file)
index 0000000..ea065b3
--- /dev/null
@@ -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 (file)
index 0000000..8c08b0b
--- /dev/null
@@ -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 (file)
index 0000000..ab68afc
--- /dev/null
@@ -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 (file)
index 0000000..46615a0
--- /dev/null
@@ -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 (file)
index 0000000..e2517c1
--- /dev/null
@@ -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(&reg, &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 (file)
index 0000000..42f0bee
--- /dev/null
@@ -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 (file)
index 0000000..5a82123
--- /dev/null
@@ -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 (file)
index 0000000..5c643d7
--- /dev/null
@@ -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 (file)
index 0000000..277983a
--- /dev/null
@@ -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 (file)
index 0000000..f5c7eb7
--- /dev/null
@@ -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 (file)
index 0000000..2565215
--- /dev/null
@@ -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 (file)
index 0000000..a1ffae7
--- /dev/null
@@ -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 (file)
index 0000000..b9b917d
--- /dev/null
@@ -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 (file)
index 0000000..aad6132
--- /dev/null
@@ -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 (file)
index 0000000..444d069
--- /dev/null
@@ -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 (file)
index 0000000..2783f36
--- /dev/null
@@ -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 lon