Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
Pull HID updates from Jiri Kosina: - Integrated Sensor Hub support (Cherrytrail+) from Srinivas Pandruvada - Big cleanup of Wacom driver; namely it's now using devres, and the standardized LED API so that libinput doesn't need to have root access any more, with substantial amount of other cleanups piggy-backing on top. All this from Benjamin Tissoires - Report descriptor parsing would now ignore and out-of-range System controls in case of the application actually being System Control. This fixes quite some issues with several devices, and allows us to remove a few ->report_fixup callbacks. From Benjamin Tissoires - ... a lot of other assorted small fixes and device ID additions * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (76 commits) HID: add missing \n to end of dev_warn messages HID: alps: fix multitouch cursor issue HID: hid-logitech: Documentation updates/corrections HID: hid-logitech: Improve Wingman Formula Force GP support HID: hid-logitech: Rewrite of descriptor for all DF wheels HID: hid-logitech: Compute combined pedals value HID: hid-logitech: Add combined pedal support Logitech wheels HID: hid-logitech: Introduce control for combined pedals feature HID: sony: Update copyright and add Dualshock 4 rate control note HID: sony: Defer the initial USB Sixaxis output report HID: sony: Relax duplicate checking for USB-only devices Revert "HID: microsoft: fix invalid rdesc for 3k kbd" HID: alps: fix error return code in alps_input_configured() HID: alps: fix stick device not working after resume HID: support for keyboard - Corsair STRAFE HID: alps: Fix memory leak HID: uclogic: Add support for UC-Logic TWHA60 v3 HID: uclogic: Override constant descriptors HID: uclogic: Support UGTizer GP0610 partially HID: uclogic: Add support for several more tablets ...
This commit is contained in:
		
						commit
						bc75450cc3
					
				| @ -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'. | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
							
								
								
									
										454
									
								
								Documentation/hid/intel-ish-hid.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										454
									
								
								Documentation/hid/intel-ish-hid.txt
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| ... | ||||
| @ -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> | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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/ | ||||
|  | ||||
| @ -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; | ||||
| 		} | ||||
|  | ||||
| @ -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) }, | ||||
|  | ||||
| @ -268,6 +268,7 @@ | ||||
| #define USB_DEVICE_ID_CORSAIR_K95RGB    0x1b11 | ||||
| #define USB_DEVICE_ID_CORSAIR_M65RGB    0x1b12 | ||||
| #define USB_DEVICE_ID_CORSAIR_K70RGB    0x1b13 | ||||
| #define USB_DEVICE_ID_CORSAIR_STRAFE    0x1b15 | ||||
| #define USB_DEVICE_ID_CORSAIR_K65RGB    0x1b17 | ||||
| 
 | ||||
| #define USB_VENDOR_ID_CREATIVELABS	0x041e | ||||
| @ -565,7 +566,7 @@ | ||||
| #define USB_DEVICE_ID_KYE_GPEN_560	0x5003 | ||||
| #define USB_DEVICE_ID_KYE_EASYPEN_I405X	0x5010 | ||||
| #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X	0x5011 | ||||
| #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2	0x501a | ||||
| #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2	0x501a | ||||
| #define USB_DEVICE_ID_KYE_EASYPEN_M610X	0x5013 | ||||
| #define USB_DEVICE_ID_KYE_PENSKETCH_M912	0x5015 | ||||
| 
 | ||||
| @ -713,6 +714,7 @@ | ||||
| #define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3    0x07dc | ||||
| #define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2  0x07e2 | ||||
| #define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP 0x07dd | ||||
| #define USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_JP 0x07e9 | ||||
| #define USB_DEVICE_ID_MS_TYPE_COVER_3    0x07de | ||||
| #define USB_DEVICE_ID_MS_POWER_COVER     0x07da | ||||
| 
 | ||||
| @ -859,6 +861,7 @@ | ||||
| #define USB_DEVICE_ID_SAITEK_PS1000	0x0621 | ||||
| #define USB_DEVICE_ID_SAITEK_RAT7_OLD	0x0ccb | ||||
| #define USB_DEVICE_ID_SAITEK_RAT7	0x0cd7 | ||||
| #define USB_DEVICE_ID_SAITEK_RAT9	0x0cfa | ||||
| #define USB_DEVICE_ID_SAITEK_MMO7	0x0cd0 | ||||
| 
 | ||||
| #define USB_VENDOR_ID_SAMSUNG		0x0419 | ||||
| @ -996,6 +999,10 @@ | ||||
| #define USB_DEVICE_ID_UCLOGIC_TABLET_WP1062	0x0064 | ||||
| #define USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850	0x0522 | ||||
| #define USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60	0x0781 | ||||
| #define USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3	0x3031 | ||||
| #define USB_DEVICE_ID_UGEE_TABLET_81		0x0081 | ||||
| #define USB_DEVICE_ID_UGEE_TABLET_45		0x0045 | ||||
| #define USB_DEVICE_ID_YIYNOVA_TABLET		0x004d | ||||
| 
 | ||||
| #define USB_VENDOR_ID_UNITEC	0x227d | ||||
| #define USB_DEVICE_ID_UNITEC_USB_TOUCH_0709	0x0709 | ||||
| @ -1085,4 +1092,7 @@ | ||||
| #define USB_DEVICE_ID_RAPHNET_2NES2SNES	0x0002 | ||||
| #define USB_DEVICE_ID_RAPHNET_4NES4SNES	0x0003 | ||||
| 
 | ||||
| #define USB_VENDOR_ID_UGTIZER			0x2179 | ||||
| #define USB_DEVICE_ID_UGTIZER_TABLET_GP0610	0x0053 | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -19,11 +19,6 @@ | ||||
| 
 | ||||
| #include "hid-ids.h" | ||||
| 
 | ||||
| /*
 | ||||
|  * See EasyPen i405X description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=KYE_EasyPen_i405X
 | ||||
|  */ | ||||
| 
 | ||||
| /* Original EasyPen i405X report descriptor size */ | ||||
| #define EASYPEN_I405X_RDESC_ORIG_SIZE	476 | ||||
| 
 | ||||
| @ -82,11 +77,6 @@ static __u8 easypen_i405x_rdesc_fixed[] = { | ||||
| 	0xC0              /*  End Collection                  */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See MousePen i608X description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=KYE_MousePen_i608X
 | ||||
|  */ | ||||
| 
 | ||||
| /* Original MousePen i608X report descriptor size */ | ||||
| #define MOUSEPEN_I608X_RDESC_ORIG_SIZE	476 | ||||
| 
 | ||||
| @ -186,10 +176,104 @@ static __u8 mousepen_i608x_rdesc_fixed[] = { | ||||
| 	0xC0              /*  End Collection                  */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See EasyPen M610X description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=KYE_EasyPen_M610X
 | ||||
|  */ | ||||
| /* Original MousePen i608X v2 report descriptor size */ | ||||
| #define MOUSEPEN_I608X_V2_RDESC_ORIG_SIZE	482 | ||||
| 
 | ||||
| /* Fixed MousePen i608X v2 report descriptor */ | ||||
| static __u8 mousepen_i608x_v2_rdesc_fixed[] = { | ||||
| 	0x06, 0x00, 0xFF,             /*  Usage Page (FF00h),             */ | ||||
| 	0x09, 0x01,                   /*  Usage (01h),                    */ | ||||
| 	0xA1, 0x01,                   /*  Collection (Application),       */ | ||||
| 	0x85, 0x05,                   /*    Report ID (5),                */ | ||||
| 	0x09, 0x01,                   /*    Usage (01h),                  */ | ||||
| 	0x15, 0x80,                   /*    Logical Minimum (-128),       */ | ||||
| 	0x25, 0x7F,                   /*    Logical Maximum (127),        */ | ||||
| 	0x75, 0x08,                   /*    Report Size (8),              */ | ||||
| 	0x95, 0x07,                   /*    Report Count (7),             */ | ||||
| 	0xB1, 0x02,                   /*    Feature (Variable),           */ | ||||
| 	0xC0,                         /*  End Collection,                 */ | ||||
| 	0x05, 0x0D,                   /*  Usage Page (Digitizer),         */ | ||||
| 	0x09, 0x02,                   /*  Usage (Pen),                    */ | ||||
| 	0xA1, 0x01,                   /*  Collection (Application),       */ | ||||
| 	0x85, 0x10,                   /*    Report ID (16),               */ | ||||
| 	0x09, 0x20,                   /*    Usage (Stylus),               */ | ||||
| 	0xA0,                         /*    Collection (Physical),        */ | ||||
| 	0x14,                         /*      Logical Minimum (0),        */ | ||||
| 	0x25, 0x01,                   /*      Logical Maximum (1),        */ | ||||
| 	0x75, 0x01,                   /*      Report Size (1),            */ | ||||
| 	0x09, 0x42,                   /*      Usage (Tip Switch),         */ | ||||
| 	0x09, 0x44,                   /*      Usage (Barrel Switch),      */ | ||||
| 	0x09, 0x46,                   /*      Usage (Tablet Pick),        */ | ||||
| 	0x95, 0x03,                   /*      Report Count (3),           */ | ||||
| 	0x81, 0x02,                   /*      Input (Variable),           */ | ||||
| 	0x95, 0x04,                   /*      Report Count (4),           */ | ||||
| 	0x81, 0x03,                   /*      Input (Constant, Variable), */ | ||||
| 	0x09, 0x32,                   /*      Usage (In Range),           */ | ||||
| 	0x95, 0x01,                   /*      Report Count (1),           */ | ||||
| 	0x81, 0x02,                   /*      Input (Variable),           */ | ||||
| 	0x75, 0x10,                   /*      Report Size (16),           */ | ||||
| 	0x95, 0x01,                   /*      Report Count (1),           */ | ||||
| 	0xA4,                         /*      Push,                       */ | ||||
| 	0x05, 0x01,                   /*      Usage Page (Desktop),       */ | ||||
| 	0x55, 0xFD,                   /*      Unit Exponent (-3),         */ | ||||
| 	0x65, 0x13,                   /*      Unit (Inch),                */ | ||||
| 	0x34,                         /*      Physical Minimum (0),       */ | ||||
| 	0x09, 0x30,                   /*      Usage (X),                  */ | ||||
| 	0x46, 0x40, 0x1F,             /*      Physical Maximum (8000),    */ | ||||
| 	0x27, 0x00, 0xA0, 0x00, 0x00, /*      Logical Maximum (40960),    */ | ||||
| 	0x81, 0x02,                   /*      Input (Variable),           */ | ||||
| 	0x09, 0x31,                   /*      Usage (Y),                  */ | ||||
| 	0x46, 0x70, 0x17,             /*      Physical Maximum (6000),    */ | ||||
| 	0x26, 0x00, 0x78,             /*      Logical Maximum (30720),    */ | ||||
| 	0x81, 0x02,                   /*      Input (Variable),           */ | ||||
| 	0xB4,                         /*      Pop,                        */ | ||||
| 	0x09, 0x30,                   /*      Usage (Tip Pressure),       */ | ||||
| 	0x26, 0xFF, 0x07,             /*      Logical Maximum (2047),     */ | ||||
| 	0x81, 0x02,                   /*      Input (Variable),           */ | ||||
| 	0xC0,                         /*    End Collection,               */ | ||||
| 	0xC0,                         /*  End Collection,                 */ | ||||
| 	0x05, 0x01,                   /*  Usage Page (Desktop),           */ | ||||
| 	0x09, 0x02,                   /*  Usage (Mouse),                  */ | ||||
| 	0xA1, 0x01,                   /*  Collection (Application),       */ | ||||
| 	0x85, 0x11,                   /*    Report ID (17),               */ | ||||
| 	0x09, 0x01,                   /*    Usage (Pointer),              */ | ||||
| 	0xA0,                         /*    Collection (Physical),        */ | ||||
| 	0x14,                         /*      Logical Minimum (0),        */ | ||||
| 	0xA4,                         /*      Push,                       */ | ||||
| 	0x05, 0x09,                   /*      Usage Page (Button),        */ | ||||
| 	0x75, 0x01,                   /*      Report Size (1),            */ | ||||
| 	0x19, 0x01,                   /*      Usage Minimum (01h),        */ | ||||
| 	0x29, 0x03,                   /*      Usage Maximum (03h),        */ | ||||
| 	0x25, 0x01,                   /*      Logical Maximum (1),        */ | ||||
| 	0x95, 0x03,                   /*      Report Count (3),           */ | ||||
| 	0x81, 0x02,                   /*      Input (Variable),           */ | ||||
| 	0x95, 0x05,                   /*      Report Count (5),           */ | ||||
| 	0x81, 0x01,                   /*      Input (Constant),           */ | ||||
| 	0xB4,                         /*      Pop,                        */ | ||||
| 	0x95, 0x01,                   /*      Report Count (1),           */ | ||||
| 	0xA4,                         /*      Push,                       */ | ||||
| 	0x55, 0xFD,                   /*      Unit Exponent (-3),         */ | ||||
| 	0x65, 0x13,                   /*      Unit (Inch),                */ | ||||
| 	0x34,                         /*      Physical Minimum (0),       */ | ||||
| 	0x75, 0x10,                   /*      Report Size (16),           */ | ||||
| 	0x09, 0x30,                   /*      Usage (X),                  */ | ||||
| 	0x46, 0x40, 0x1F,             /*      Physical Maximum (8000),    */ | ||||
| 	0x27, 0x00, 0xA0, 0x00, 0x00, /*      Logical Maximum (40960),    */ | ||||
| 	0x81, 0x02,                   /*      Input (Variable),           */ | ||||
| 	0x09, 0x31,                   /*      Usage (Y),                  */ | ||||
| 	0x46, 0x70, 0x17,             /*      Physical Maximum (6000),    */ | ||||
| 	0x26, 0x00, 0x78,             /*      Logical Maximum (30720),    */ | ||||
| 	0x81, 0x02,                   /*      Input (Variable),           */ | ||||
| 	0xB4,                         /*      Pop,                        */ | ||||
| 	0x75, 0x08,                   /*      Report Size (8),            */ | ||||
| 	0x09, 0x38,                   /*      Usage (Wheel),              */ | ||||
| 	0x15, 0xFF,                   /*      Logical Minimum (-1),       */ | ||||
| 	0x25, 0x01,                   /*      Logical Maximum (1),        */ | ||||
| 	0x81, 0x06,                   /*      Input (Variable, Relative), */ | ||||
| 	0x81, 0x01,                   /*      Input (Constant),           */ | ||||
| 	0xC0,                         /*    End Collection,               */ | ||||
| 	0xC0                          /*  End Collection                  */ | ||||
| }; | ||||
| 
 | ||||
| /* Original EasyPen M610X report descriptor size */ | ||||
| #define EASYPEN_M610X_RDESC_ORIG_SIZE	476 | ||||
| @ -454,12 +538,17 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc, | ||||
| 		} | ||||
| 		break; | ||||
| 	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X: | ||||
| 	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2: | ||||
| 		if (*rsize == MOUSEPEN_I608X_RDESC_ORIG_SIZE) { | ||||
| 			rdesc = mousepen_i608x_rdesc_fixed; | ||||
| 			*rsize = sizeof(mousepen_i608x_rdesc_fixed); | ||||
| 		} | ||||
| 		break; | ||||
| 	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2: | ||||
| 		if (*rsize == MOUSEPEN_I608X_V2_RDESC_ORIG_SIZE) { | ||||
| 			rdesc = mousepen_i608x_v2_rdesc_fixed; | ||||
| 			*rsize = sizeof(mousepen_i608x_v2_rdesc_fixed); | ||||
| 		} | ||||
| 		break; | ||||
| 	case USB_DEVICE_ID_KYE_EASYPEN_M610X: | ||||
| 		if (*rsize == EASYPEN_M610X_RDESC_ORIG_SIZE) { | ||||
| 			rdesc = easypen_m610x_rdesc_fixed; | ||||
| @ -553,7 +642,7 @@ static int kye_probe(struct hid_device *hdev, const struct hid_device_id *id) | ||||
| 	switch (id->product) { | ||||
| 	case USB_DEVICE_ID_KYE_EASYPEN_I405X: | ||||
| 	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X: | ||||
| 	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2: | ||||
| 	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2: | ||||
| 	case USB_DEVICE_ID_KYE_EASYPEN_M610X: | ||||
| 	case USB_DEVICE_ID_KYE_PENSKETCH_M912: | ||||
| 		ret = kye_tablet_enable(hdev); | ||||
| @ -586,7 +675,7 @@ static const struct hid_device_id kye_devices[] = { | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, | ||||
| 				USB_DEVICE_ID_KYE_MOUSEPEN_I608X) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, | ||||
| 				USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2) }, | ||||
| 				USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, | ||||
| 				USB_DEVICE_ID_KYE_EASYPEN_M610X) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, | ||||
|  | ||||
| @ -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) { | ||||
| 
 | ||||
| 	case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG: | ||||
| 		if (*rsize == FFG_RDESC_ORIG_SIZE) { | ||||
| 			hid_info(hdev, | ||||
| 				"fixing up Logitech Wingman Formula Force GP report descriptor\n"); | ||||
| 			rdesc = ffg_rdesc_fixed; | ||||
| 			*rsize = sizeof(ffg_rdesc_fixed); | ||||
| 		} | ||||
| 		break; | ||||
| 
 | ||||
| 	/* Several wheels report as this id when operating in emulation mode. */ | ||||
| 	case USB_DEVICE_ID_LOGITECH_WHEEL: | ||||
| 		udesc = &(hid_to_usb_dev(hdev)->descriptor); | ||||
| 		if (!udesc) { | ||||
| 			hid_err(hdev, "NULL USB device descriptor\n"); | ||||
| 			break; | ||||
| 		} | ||||
| 		bcdDevice = le16_to_cpu(udesc->bcdDevice); | ||||
| 		rev_maj = bcdDevice >> 8; | ||||
| 		rev_min = bcdDevice & 0xff; | ||||
| 
 | ||||
| 		/* Update the report descriptor for only the Driving Force wheel */ | ||||
| 		if (rev_maj == 1 && rev_min == 2 && | ||||
| 				*rsize == DF_RDESC_ORIG_SIZE) { | ||||
| 		if (*rsize == DF_RDESC_ORIG_SIZE) { | ||||
| 			hid_info(hdev, | ||||
| 				"fixing up Logitech Driving Force report descriptor\n"); | ||||
| 			rdesc = df_rdesc_fixed; | ||||
| @ -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, | ||||
| }; | ||||
|  | ||||
| @ -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 | ||||
| 	{ | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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), | ||||
|  | ||||
| @ -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), | ||||
|  | ||||
| @ -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) }, | ||||
| 	{ } | ||||
|  | ||||
| @ -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); | ||||
| 	} | ||||
|  | ||||
| @ -21,13 +21,6 @@ | ||||
| 
 | ||||
| #include "hid-ids.h" | ||||
| 
 | ||||
| /*
 | ||||
|  * See WPXXXXU model descriptions, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP4030U
 | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP5540U
 | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP8060U
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original descriptor of WPXXXXU tablets */ | ||||
| #define WPXXXXU_RDESC_ORIG_SIZE	212 | ||||
| 
 | ||||
| @ -221,11 +214,6 @@ static __u8 wp8060u_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See WP1062 description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_WP1062
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original descriptor of WP1062 tablet */ | ||||
| #define WP1062_RDESC_ORIG_SIZE	254 | ||||
| 
 | ||||
| @ -274,11 +262,6 @@ static __u8 wp1062_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See PF1209 description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_PF1209
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original descriptor of PF1209 tablet */ | ||||
| #define PF1209_RDESC_ORIG_SIZE	234 | ||||
| 
 | ||||
| @ -356,11 +339,6 @@ static __u8 pf1209_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See TWHL850 description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Wireless_Tablet_TWHL850
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original descriptors of TWHL850 tablet */ | ||||
| #define TWHL850_RDESC_ORIG_SIZE0	182 | ||||
| #define TWHL850_RDESC_ORIG_SIZE1	161 | ||||
| @ -469,11 +447,6 @@ static __u8 twhl850_rdesc_fixed2[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See TWHA60 description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=UC-Logic_Tablet_TWHA60
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original descriptors of TWHA60 tablet */ | ||||
| #define TWHA60_RDESC_ORIG_SIZE0 254 | ||||
| #define TWHA60_RDESC_ORIG_SIZE1 139 | ||||
| @ -613,6 +586,27 @@ static const __u8 uclogic_tablet_rdesc_template[] = { | ||||
| 	0xC0                    /*  End Collection                          */ | ||||
| }; | ||||
| 
 | ||||
| /* Fixed virtual pad report descriptor */ | ||||
| static const __u8 uclogic_buttonpad_rdesc[] = { | ||||
| 	0x05, 0x01,             /*  Usage Page (Desktop),                   */ | ||||
| 	0x09, 0x07,             /*  Usage (Keypad),                         */ | ||||
| 	0xA1, 0x01,             /*  Collection (Application),               */ | ||||
| 	0x85, 0xF7,             /*      Report ID (247),                    */ | ||||
| 	0x05, 0x0D,             /*      Usage Page (Digitizers),            */ | ||||
| 	0x09, 0x39,             /*      Usage (Tablet Function Keys),       */ | ||||
| 	0xA0,                   /*      Collection (Physical),              */ | ||||
| 	0x05, 0x09,             /*          Usage Page (Button),            */ | ||||
| 	0x75, 0x01,             /*          Report Size (1),                */ | ||||
| 	0x95, 0x18,             /*          Report Count (24),              */ | ||||
| 	0x81, 0x03,             /*          Input (Constant, Variable),     */ | ||||
| 	0x19, 0x01,             /*          Usage Minimum (01h),            */ | ||||
| 	0x29, 0x08,             /*          Usage Maximum (08h),            */ | ||||
| 	0x95, 0x08,             /*          Report Count (8),               */ | ||||
| 	0x81, 0x02,             /*          Input (Variable),               */ | ||||
| 	0xC0,                   /*      End Collection                      */ | ||||
| 	0xC0                    /*  End Collection                          */ | ||||
| }; | ||||
| 
 | ||||
| /* Parameter indices */ | ||||
| enum uclogic_prm { | ||||
| 	UCLOGIC_PRM_X_LM	= 1, | ||||
| @ -628,6 +622,7 @@ struct uclogic_drvdata { | ||||
| 	unsigned int rsize; | ||||
| 	bool invert_pen_inrange; | ||||
| 	bool ignore_pen_usage; | ||||
| 	bool has_virtual_pad_interface; | ||||
| }; | ||||
| 
 | ||||
| static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, | ||||
| @ -637,6 +632,12 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, | ||||
| 	__u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber; | ||||
| 	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | ||||
| 
 | ||||
| 	if (drvdata->rdesc != NULL) { | ||||
| 		rdesc = drvdata->rdesc; | ||||
| 		*rsize = drvdata->rsize; | ||||
| 		return rdesc; | ||||
| 	} | ||||
| 
 | ||||
| 	switch (hdev->product) { | ||||
| 	case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209: | ||||
| 		if (*rsize == PF1209_RDESC_ORIG_SIZE) { | ||||
| @ -706,11 +707,6 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, | ||||
| 			break; | ||||
| 		} | ||||
| 		break; | ||||
| 	default: | ||||
| 		if (drvdata->rdesc != NULL) { | ||||
| 			rdesc = drvdata->rdesc; | ||||
| 			*rsize = drvdata->rsize; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return rdesc; | ||||
| @ -804,7 +800,6 @@ static int uclogic_tablet_enable(struct hid_device *hdev) | ||||
| 	len = UCLOGIC_PRM_NUM * sizeof(*buf); | ||||
| 	buf = kmalloc(len, GFP_KERNEL); | ||||
| 	if (buf == NULL) { | ||||
| 		hid_err(hdev, "failed to allocate parameter buffer\n"); | ||||
| 		rc = -ENOMEM; | ||||
| 		goto cleanup; | ||||
| 	} | ||||
| @ -848,7 +843,6 @@ static int uclogic_tablet_enable(struct hid_device *hdev) | ||||
| 				sizeof(uclogic_tablet_rdesc_template), | ||||
| 				GFP_KERNEL); | ||||
| 	if (drvdata->rdesc == NULL) { | ||||
| 		hid_err(hdev, "failed to allocate fixed rdesc\n"); | ||||
| 		rc = -ENOMEM; | ||||
| 		goto cleanup; | ||||
| 	} | ||||
| @ -876,11 +870,75 @@ cleanup: | ||||
| 	return rc; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Enable actual button mode. | ||||
|  * | ||||
|  * @hdev:	HID device | ||||
|  */ | ||||
| static int uclogic_button_enable(struct hid_device *hdev) | ||||
| { | ||||
| 	int rc; | ||||
| 	struct usb_device *usb_dev = hid_to_usb_dev(hdev); | ||||
| 	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | ||||
| 	char *str_buf; | ||||
| 	size_t str_len = 16; | ||||
| 	unsigned char *rdesc; | ||||
| 	size_t rdesc_len; | ||||
| 
 | ||||
| 	str_buf = kzalloc(str_len, GFP_KERNEL); | ||||
| 	if (str_buf == NULL) { | ||||
| 		rc = -ENOMEM; | ||||
| 		goto cleanup; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Enable abstract keyboard mode */ | ||||
| 	rc = usb_string(usb_dev, 0x7b, str_buf, str_len); | ||||
| 	if (rc == -EPIPE) { | ||||
| 		hid_info(hdev, "button mode setting not found\n"); | ||||
| 		rc = 0; | ||||
| 		goto cleanup; | ||||
| 	} else if (rc < 0) { | ||||
| 		hid_err(hdev, "failed to enable abstract keyboard\n"); | ||||
| 		goto cleanup; | ||||
| 	} else if (strncmp(str_buf, "HK On", rc)) { | ||||
| 		hid_info(hdev, "invalid answer when requesting buttons: '%s'\n", | ||||
| 			str_buf); | ||||
| 		rc = -EINVAL; | ||||
| 		goto cleanup; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Re-allocate fixed report descriptor */ | ||||
| 	rdesc_len = drvdata->rsize + sizeof(uclogic_buttonpad_rdesc); | ||||
| 	rdesc = devm_kzalloc(&hdev->dev, rdesc_len, GFP_KERNEL); | ||||
| 	if (!rdesc) { | ||||
| 		rc = -ENOMEM; | ||||
| 		goto cleanup; | ||||
| 	} | ||||
| 
 | ||||
| 	memcpy(rdesc, drvdata->rdesc, drvdata->rsize); | ||||
| 
 | ||||
| 	/* Append the buttonpad descriptor */ | ||||
| 	memcpy(rdesc + drvdata->rsize, uclogic_buttonpad_rdesc, | ||||
| 	       sizeof(uclogic_buttonpad_rdesc)); | ||||
| 
 | ||||
| 	/* clean up old rdesc and use the new one */ | ||||
| 	drvdata->rsize = rdesc_len; | ||||
| 	devm_kfree(&hdev->dev, drvdata->rdesc); | ||||
| 	drvdata->rdesc = rdesc; | ||||
| 
 | ||||
| 	rc = 0; | ||||
| 
 | ||||
| cleanup: | ||||
| 	kfree(str_buf); | ||||
| 	return rc; | ||||
| } | ||||
| 
 | ||||
| static int uclogic_probe(struct hid_device *hdev, | ||||
| 		const struct hid_device_id *id) | ||||
| { | ||||
| 	int rc; | ||||
| 	struct usb_interface *intf = to_usb_interface(hdev->dev.parent); | ||||
| 	struct usb_device *udev = hid_to_usb_dev(hdev); | ||||
| 	struct uclogic_drvdata *drvdata; | ||||
| 
 | ||||
| 	/*
 | ||||
| @ -899,6 +957,10 @@ static int uclogic_probe(struct hid_device *hdev, | ||||
| 
 | ||||
| 	switch (id->product) { | ||||
| 	case USB_DEVICE_ID_HUION_TABLET: | ||||
| 	case USB_DEVICE_ID_YIYNOVA_TABLET: | ||||
| 	case USB_DEVICE_ID_UGEE_TABLET_81: | ||||
| 	case USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3: | ||||
| 	case USB_DEVICE_ID_UGEE_TABLET_45: | ||||
| 		/* If this is the pen interface */ | ||||
| 		if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { | ||||
| 			rc = uclogic_tablet_enable(hdev); | ||||
| @ -907,10 +969,48 @@ static int uclogic_probe(struct hid_device *hdev, | ||||
| 				return rc; | ||||
| 			} | ||||
| 			drvdata->invert_pen_inrange = true; | ||||
| 
 | ||||
| 			rc = uclogic_button_enable(hdev); | ||||
| 			drvdata->has_virtual_pad_interface = !rc; | ||||
| 		} else { | ||||
| 			drvdata->ignore_pen_usage = true; | ||||
| 		} | ||||
| 		break; | ||||
| 	case USB_DEVICE_ID_UGTIZER_TABLET_GP0610: | ||||
| 		/* If this is the pen interface */ | ||||
| 		if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { | ||||
| 			rc = uclogic_tablet_enable(hdev); | ||||
| 			if (rc) { | ||||
| 				hid_err(hdev, "tablet enabling failed\n"); | ||||
| 				return rc; | ||||
| 			} | ||||
| 			drvdata->invert_pen_inrange = true; | ||||
| 		} else { | ||||
| 			drvdata->ignore_pen_usage = true; | ||||
| 		} | ||||
| 		break; | ||||
| 	case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60: | ||||
| 		/*
 | ||||
| 		 * If it is the three-interface version, which is known to | ||||
| 		 * respond to initialization. | ||||
| 		 */ | ||||
| 		if (udev->config->desc.bNumInterfaces == 3) { | ||||
| 			/* If it is the pen interface */ | ||||
| 			if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { | ||||
| 				rc = uclogic_tablet_enable(hdev); | ||||
| 				if (rc) { | ||||
| 					hid_err(hdev, "tablet enabling failed\n"); | ||||
| 					return rc; | ||||
| 				} | ||||
| 				drvdata->invert_pen_inrange = true; | ||||
| 
 | ||||
| 				rc = uclogic_button_enable(hdev); | ||||
| 				drvdata->has_virtual_pad_interface = !rc; | ||||
| 			} else { | ||||
| 				drvdata->ignore_pen_usage = true; | ||||
| 			} | ||||
| 		} | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	rc = hid_parse(hdev); | ||||
| @ -933,12 +1033,16 @@ static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, | ||||
| { | ||||
| 	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | ||||
| 
 | ||||
| 	if ((drvdata->invert_pen_inrange) && | ||||
| 	    (report->type == HID_INPUT_REPORT) && | ||||
| 	if ((report->type == HID_INPUT_REPORT) && | ||||
| 	    (report->id == UCLOGIC_PEN_REPORT_ID) && | ||||
| 	    (size >= 2)) | ||||
| 		/* Invert the in-range bit */ | ||||
| 		data[1] ^= 0x40; | ||||
| 	    (size >= 2)) { | ||||
| 		if (drvdata->has_virtual_pad_interface && (data[1] & 0x20)) | ||||
| 			/* Change to virtual frame button report ID */ | ||||
| 			data[0] = 0xf7; | ||||
| 		else if (drvdata->invert_pen_inrange) | ||||
| 			/* Invert the in-range bit */ | ||||
| 			data[1] ^= 0x40; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| @ -960,6 +1064,11 @@ static const struct hid_device_id uclogic_devices[] = { | ||||
| 				USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, | ||||
| 	{ HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) }, | ||||
| 	{ } | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(hid, uclogic_devices); | ||||
|  | ||||
| @ -42,11 +42,6 @@ | ||||
|  * 02 16 02     ink | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * See Slim Tablet 5.8 inch description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Slim_Tablet_5.8%22
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original report descriptor of Slim Tablet 5.8 inch */ | ||||
| #define SLIM_TABLET_5_8_INCH_RDESC_ORIG_SIZE	222 | ||||
| 
 | ||||
| @ -98,11 +93,6 @@ static __u8 slim_tablet_5_8_inch_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See Slim Tablet 12.1 inch description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Slim_Tablet_12.1%22
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original report descriptor of Slim Tablet 12.1 inch */ | ||||
| #define SLIM_TABLET_12_1_INCH_RDESC_ORIG_SIZE	269 | ||||
| 
 | ||||
| @ -154,11 +144,6 @@ static __u8 slim_tablet_12_1_inch_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See Q Pad description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Q_Pad
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original report descriptor of Q Pad */ | ||||
| #define Q_PAD_RDESC_ORIG_SIZE	241 | ||||
| 
 | ||||
| @ -210,11 +195,6 @@ static __u8 q_pad_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See description, device and HID report descriptors of tablet with PID 0038 at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=Waltop_PID_0038
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original report descriptor of tablet with PID 0038 */ | ||||
| #define PID_0038_RDESC_ORIG_SIZE	241 | ||||
| 
 | ||||
| @ -268,11 +248,6 @@ static __u8 pid_0038_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See Media Tablet 10.6 inch description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Media_Tablet_10.6%22
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original report descriptor of Media Tablet 10.6 inch */ | ||||
| #define MEDIA_TABLET_10_6_INCH_RDESC_ORIG_SIZE	300 | ||||
| 
 | ||||
| @ -386,11 +361,6 @@ static __u8 media_tablet_10_6_inch_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See Media Tablet 14.1 inch description, device and HID report descriptors at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Media_Tablet_14.1%22
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original report descriptor of Media Tablet 14.1 inch */ | ||||
| #define MEDIA_TABLET_14_1_INCH_RDESC_ORIG_SIZE	309 | ||||
| 
 | ||||
| @ -502,12 +472,6 @@ static __u8 media_tablet_14_1_inch_rdesc_fixed[] = { | ||||
| 	0xC0                /*  End Collection                      */ | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * See Sirius Battery Free Tablet description, device and HID report descriptors | ||||
|  * at | ||||
|  * http://sf.net/apps/mediawiki/digimend/?title=Waltop_Sirius_Battery_Free_Tablet
 | ||||
|  */ | ||||
| 
 | ||||
| /* Size of the original report descriptor of Sirius Battery Free Tablet */ | ||||
| #define SIRIUS_BATTERY_FREE_TABLET_RDESC_ORIG_SIZE	335 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										17
									
								
								drivers/hid/intel-ish-hid/Kconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								drivers/hid/intel-ish-hid/Kconfig
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
							
								
								
									
										22
									
								
								drivers/hid/intel-ish-hid/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								drivers/hid/intel-ish-hid/Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
							
								
								
									
										220
									
								
								drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h
									
									
									
									
									
										Normal file
									
								
							| @ -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_ */ | ||||
							
								
								
									
										71
									
								
								drivers/hid/intel-ish-hid/ipc/hw-ish.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								drivers/hid/intel-ish-hid/ipc/hw-ish.h
									
									
									
									
									
										Normal file
									
								
							| @ -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_ */ | ||||
							
								
								
									
										881
									
								
								drivers/hid/intel-ish-hid/ipc/ipc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										881
									
								
								drivers/hid/intel-ish-hid/ipc/ipc.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,881 @@ | ||||
| /*
 | ||||
|  * H/W layer of ISHTP provider device (ISH) | ||||
|  * | ||||
|  * Copyright (c) 2014-2016, Intel Corporation. | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  * version 2, as published by the Free Software Foundation. | ||||
|  * | ||||
|  * This program is distributed in the hope it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||||
|  * more details. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/sched.h> | ||||
| #include <linux/spinlock.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/jiffies.h> | ||||
| #include "client.h" | ||||
| #include "hw-ish.h" | ||||
| #include "utils.h" | ||||
| #include "hbm.h" | ||||
| 
 | ||||
| /* For FW reset flow */ | ||||
| static struct work_struct fw_reset_work; | ||||
| static struct ishtp_device *ishtp_dev; | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_reg_read() - Read register | ||||
|  * @dev: ISHTP device pointer | ||||
|  * @offset: Register offset | ||||
|  * | ||||
|  * Read 32 bit register at a given offset | ||||
|  * | ||||
|  * Return: Read register value | ||||
|  */ | ||||
| static inline uint32_t ish_reg_read(const struct ishtp_device *dev, | ||||
| 	unsigned long offset) | ||||
| { | ||||
| 	struct ish_hw *hw = to_ish_hw(dev); | ||||
| 
 | ||||
| 	return readl(hw->mem_addr + offset); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_reg_write() - Write register | ||||
|  * @dev: ISHTP device pointer | ||||
|  * @offset: Register offset | ||||
|  * @value: Value to write | ||||
|  * | ||||
|  * Writes 32 bit register at a give offset | ||||
|  */ | ||||
| static inline void ish_reg_write(struct ishtp_device *dev, | ||||
| 				 unsigned long offset, | ||||
| 				 uint32_t value) | ||||
| { | ||||
| 	struct ish_hw *hw = to_ish_hw(dev); | ||||
| 
 | ||||
| 	writel(value, hw->mem_addr + offset); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * _ish_read_fw_sts_reg() - Read FW status register | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Read FW status register | ||||
|  * | ||||
|  * Return: Read register value | ||||
|  */ | ||||
| static inline uint32_t _ish_read_fw_sts_reg(struct ishtp_device *dev) | ||||
| { | ||||
| 	return ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * check_generated_interrupt() - Check if ISH interrupt | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Check if an interrupt was generated for ISH | ||||
|  * | ||||
|  * Return: Read true or false | ||||
|  */ | ||||
| static bool check_generated_interrupt(struct ishtp_device *dev) | ||||
| { | ||||
| 	bool interrupt_generated = true; | ||||
| 	uint32_t pisr_val = 0; | ||||
| 
 | ||||
| 	if (dev->pdev->device == CHV_DEVICE_ID) { | ||||
| 		pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB); | ||||
| 		interrupt_generated = | ||||
| 			IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val); | ||||
| 	} else { | ||||
| 		pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT); | ||||
| 		interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_BXT(pisr_val); | ||||
| 	} | ||||
| 
 | ||||
| 	return interrupt_generated; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_is_input_ready() - Check if FW ready for RX | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Check if ISH FW is ready for receiving data | ||||
|  * | ||||
|  * Return: Read true or false | ||||
|  */ | ||||
| static bool ish_is_input_ready(struct ishtp_device *dev) | ||||
| { | ||||
| 	uint32_t doorbell_val; | ||||
| 
 | ||||
| 	doorbell_val = ish_reg_read(dev, IPC_REG_HOST2ISH_DRBL); | ||||
| 	return !IPC_IS_BUSY(doorbell_val); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * set_host_ready() - Indicate host ready | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Set host ready indication to FW | ||||
|  */ | ||||
| static void set_host_ready(struct ishtp_device *dev) | ||||
| { | ||||
| 	if (dev->pdev->device == CHV_DEVICE_ID) { | ||||
| 		if (dev->pdev->revision == REVISION_ID_CHT_A0 || | ||||
| 				(dev->pdev->revision & REVISION_ID_SI_MASK) == | ||||
| 				REVISION_ID_CHT_Ax_SI) | ||||
| 			ish_reg_write(dev, IPC_REG_HOST_COMM, 0x81); | ||||
| 		else if (dev->pdev->revision == REVISION_ID_CHT_B0 || | ||||
| 				(dev->pdev->revision & REVISION_ID_SI_MASK) == | ||||
| 				REVISION_ID_CHT_Bx_SI || | ||||
| 				(dev->pdev->revision & REVISION_ID_SI_MASK) == | ||||
| 				REVISION_ID_CHT_Kx_SI || | ||||
| 				(dev->pdev->revision & REVISION_ID_SI_MASK) == | ||||
| 				REVISION_ID_CHT_Dx_SI) { | ||||
| 			uint32_t host_comm_val; | ||||
| 
 | ||||
| 			host_comm_val = ish_reg_read(dev, IPC_REG_HOST_COMM); | ||||
| 			host_comm_val |= IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81; | ||||
| 			ish_reg_write(dev, IPC_REG_HOST_COMM, host_comm_val); | ||||
| 		} | ||||
| 	} else { | ||||
| 			uint32_t host_pimr_val; | ||||
| 
 | ||||
| 			host_pimr_val = ish_reg_read(dev, IPC_REG_PIMR_BXT); | ||||
| 			host_pimr_val |= IPC_PIMR_INT_EN_BIT_BXT; | ||||
| 			/*
 | ||||
| 			 * disable interrupt generated instead of | ||||
| 			 * RX_complete_msg | ||||
| 			 */ | ||||
| 			host_pimr_val &= ~IPC_HOST2ISH_BUSYCLEAR_MASK_BIT; | ||||
| 
 | ||||
| 			ish_reg_write(dev, IPC_REG_PIMR_BXT, host_pimr_val); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ishtp_fw_is_ready() - Check if FW ready | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Check if ISH FW is ready | ||||
|  * | ||||
|  * Return: Read true or false | ||||
|  */ | ||||
| static bool ishtp_fw_is_ready(struct ishtp_device *dev) | ||||
| { | ||||
| 	uint32_t ish_status = _ish_read_fw_sts_reg(dev); | ||||
| 
 | ||||
| 	return IPC_IS_ISH_ILUP(ish_status) && | ||||
| 		IPC_IS_ISH_ISHTP_READY(ish_status); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_set_host_rdy() - Indicate host ready | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Set host ready indication to FW | ||||
|  */ | ||||
| static void ish_set_host_rdy(struct ishtp_device *dev) | ||||
| { | ||||
| 	uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); | ||||
| 
 | ||||
| 	IPC_SET_HOST_READY(host_status); | ||||
| 	ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_clr_host_rdy() - Indicate host not ready | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Send host not ready indication to FW | ||||
|  */ | ||||
| static void ish_clr_host_rdy(struct ishtp_device *dev) | ||||
| { | ||||
| 	uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); | ||||
| 
 | ||||
| 	IPC_CLEAR_HOST_READY(host_status); | ||||
| 	ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * _ishtp_read_hdr() - Read message header | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Read header of 32bit length | ||||
|  * | ||||
|  * Return: Read register value | ||||
|  */ | ||||
| static uint32_t _ishtp_read_hdr(const struct ishtp_device *dev) | ||||
| { | ||||
| 	return ish_reg_read(dev, IPC_REG_ISH2HOST_MSG); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * _ishtp_read - Read message | ||||
|  * @dev: ISHTP device pointer | ||||
|  * @buffer: message buffer | ||||
|  * @buffer_length: length of message buffer | ||||
|  * | ||||
|  * Read message from FW | ||||
|  * | ||||
|  * Return: Always 0 | ||||
|  */ | ||||
| static int _ishtp_read(struct ishtp_device *dev, unsigned char *buffer, | ||||
| 	unsigned long buffer_length) | ||||
| { | ||||
| 	uint32_t	i; | ||||
| 	uint32_t	*r_buf = (uint32_t *)buffer; | ||||
| 	uint32_t	msg_offs; | ||||
| 
 | ||||
| 	msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr); | ||||
| 	for (i = 0; i < buffer_length; i += sizeof(uint32_t)) | ||||
| 		*r_buf++ = ish_reg_read(dev, msg_offs + i); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * write_ipc_from_queue() - try to write ipc msg from Tx queue to device | ||||
|  * @dev: ishtp device pointer | ||||
|  * | ||||
|  * Check if DRBL is cleared. if it is - write the first IPC msg,  then call | ||||
|  * the callback function (unless it's NULL) | ||||
|  * | ||||
|  * Return: 0 for success else failure code | ||||
|  */ | ||||
| static int write_ipc_from_queue(struct ishtp_device *dev) | ||||
| { | ||||
| 	struct wr_msg_ctl_info	*ipc_link; | ||||
| 	unsigned long	length; | ||||
| 	unsigned long	rem; | ||||
| 	unsigned long	flags; | ||||
| 	uint32_t	doorbell_val; | ||||
| 	uint32_t	*r_buf; | ||||
| 	uint32_t	reg_addr; | ||||
| 	int	i; | ||||
| 	void	(*ipc_send_compl)(void *); | ||||
| 	void	*ipc_send_compl_prm; | ||||
| 	static int	out_ipc_locked; | ||||
| 	unsigned long	out_ipc_flags; | ||||
| 
 | ||||
| 	if (dev->dev_state == ISHTP_DEV_DISABLED) | ||||
| 		return	-EINVAL; | ||||
| 
 | ||||
| 	spin_lock_irqsave(&dev->out_ipc_spinlock, out_ipc_flags); | ||||
| 	if (out_ipc_locked) { | ||||
| 		spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | ||||
| 		return -EBUSY; | ||||
| 	} | ||||
| 	out_ipc_locked = 1; | ||||
| 	if (!ish_is_input_ready(dev)) { | ||||
| 		out_ipc_locked = 0; | ||||
| 		spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | ||||
| 		return -EBUSY; | ||||
| 	} | ||||
| 	spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | ||||
| 
 | ||||
| 	spin_lock_irqsave(&dev->wr_processing_spinlock, flags); | ||||
| 	/*
 | ||||
| 	 * if tx send list is empty - return 0; | ||||
| 	 * may happen, as RX_COMPLETE handler doesn't check list emptiness. | ||||
| 	 */ | ||||
| 	if (list_empty(&dev->wr_processing_list_head.link)) { | ||||
| 		spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | ||||
| 		out_ipc_locked = 0; | ||||
| 		return	0; | ||||
| 	} | ||||
| 
 | ||||
| 	ipc_link = list_entry(dev->wr_processing_list_head.link.next, | ||||
| 			      struct wr_msg_ctl_info, link); | ||||
| 	/* first 4 bytes of the data is the doorbell value (IPC header) */ | ||||
| 	length = ipc_link->length - sizeof(uint32_t); | ||||
| 	doorbell_val = *(uint32_t *)ipc_link->inline_data; | ||||
| 	r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t)); | ||||
| 
 | ||||
| 	/* If sending MNG_SYNC_FW_CLOCK, update clock again */ | ||||
| 	if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG && | ||||
| 		IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) { | ||||
| 		struct timespec ts_system; | ||||
| 		struct timeval tv_utc; | ||||
| 		uint64_t        usec_system, usec_utc; | ||||
| 		struct ipc_time_update_msg time_update; | ||||
| 		struct time_sync_format ts_format; | ||||
| 
 | ||||
| 		get_monotonic_boottime(&ts_system); | ||||
| 		do_gettimeofday(&tv_utc); | ||||
| 		usec_system = (timespec_to_ns(&ts_system)) / NSEC_PER_USEC; | ||||
| 		usec_utc = (uint64_t)tv_utc.tv_sec * 1000000 + | ||||
| 						((uint32_t)tv_utc.tv_usec); | ||||
| 		ts_format.ts1_source = HOST_SYSTEM_TIME_USEC; | ||||
| 		ts_format.ts2_source = HOST_UTC_TIME_USEC; | ||||
| 
 | ||||
| 		time_update.primary_host_time = usec_system; | ||||
| 		time_update.secondary_host_time = usec_utc; | ||||
| 		time_update.sync_info = ts_format; | ||||
| 
 | ||||
| 		memcpy(r_buf, &time_update, | ||||
| 		       sizeof(struct ipc_time_update_msg)); | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 0, reg_addr = IPC_REG_HOST2ISH_MSG; i < length >> 2; i++, | ||||
| 			reg_addr += 4) | ||||
| 		ish_reg_write(dev, reg_addr, r_buf[i]); | ||||
| 
 | ||||
| 	rem = length & 0x3; | ||||
| 	if (rem > 0) { | ||||
| 		uint32_t reg = 0; | ||||
| 
 | ||||
| 		memcpy(®, &r_buf[length >> 2], rem); | ||||
| 		ish_reg_write(dev, reg_addr, reg); | ||||
| 	} | ||||
| 	/* Flush writes to msg registers and doorbell */ | ||||
| 	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | ||||
| 
 | ||||
| 	/* Update IPC counters */ | ||||
| 	++dev->ipc_tx_cnt; | ||||
| 	dev->ipc_tx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); | ||||
| 
 | ||||
| 	ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val); | ||||
| 	out_ipc_locked = 0; | ||||
| 
 | ||||
| 	ipc_send_compl = ipc_link->ipc_send_compl; | ||||
| 	ipc_send_compl_prm = ipc_link->ipc_send_compl_prm; | ||||
| 	list_del_init(&ipc_link->link); | ||||
| 	list_add_tail(&ipc_link->link, &dev->wr_free_list_head.link); | ||||
| 	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * callback will be called out of spinlock, | ||||
| 	 * after ipc_link returned to free list | ||||
| 	 */ | ||||
| 	if (ipc_send_compl) | ||||
| 		ipc_send_compl(ipc_send_compl_prm); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * write_ipc_to_queue() - write ipc msg to Tx queue | ||||
|  * @dev: ishtp device instance | ||||
|  * @ipc_send_compl: Send complete callback | ||||
|  * @ipc_send_compl_prm:	Parameter to send in complete callback | ||||
|  * @msg: Pointer to message | ||||
|  * @length: Length of message | ||||
|  * | ||||
|  * Recived msg with IPC (and upper protocol) header  and add it to the device | ||||
|  *  Tx-to-write list then try to send the first IPC waiting msg | ||||
|  *  (if DRBL is cleared) | ||||
|  * This function returns negative value for failure (means free list | ||||
|  *  is empty, or msg too long) and 0 for success. | ||||
|  * | ||||
|  * Return: 0 for success else failure code | ||||
|  */ | ||||
| static int write_ipc_to_queue(struct ishtp_device *dev, | ||||
| 	void (*ipc_send_compl)(void *), void *ipc_send_compl_prm, | ||||
| 	unsigned char *msg, int length) | ||||
| { | ||||
| 	struct wr_msg_ctl_info *ipc_link; | ||||
| 	unsigned long	flags; | ||||
| 
 | ||||
| 	if (length > IPC_FULL_MSG_SIZE) | ||||
| 		return -EMSGSIZE; | ||||
| 
 | ||||
| 	spin_lock_irqsave(&dev->wr_processing_spinlock, flags); | ||||
| 	if (list_empty(&dev->wr_free_list_head.link)) { | ||||
| 		spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 	ipc_link = list_entry(dev->wr_free_list_head.link.next, | ||||
| 		struct wr_msg_ctl_info, link); | ||||
| 	list_del_init(&ipc_link->link); | ||||
| 
 | ||||
| 	ipc_link->ipc_send_compl = ipc_send_compl; | ||||
| 	ipc_link->ipc_send_compl_prm = ipc_send_compl_prm; | ||||
| 	ipc_link->length = length; | ||||
| 	memcpy(ipc_link->inline_data, msg, length); | ||||
| 
 | ||||
| 	list_add_tail(&ipc_link->link, &dev->wr_processing_list_head.link); | ||||
| 	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | ||||
| 
 | ||||
| 	write_ipc_from_queue(dev); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ipc_send_mng_msg() - Send management message | ||||
|  * @dev: ishtp device instance | ||||
|  * @msg_code: Message code | ||||
|  * @msg: Pointer to message | ||||
|  * @size: Length of message | ||||
|  * | ||||
|  * Send management message to FW | ||||
|  * | ||||
|  * Return: 0 for success else failure code | ||||
|  */ | ||||
| static int ipc_send_mng_msg(struct ishtp_device *dev, uint32_t msg_code, | ||||
| 	void *msg, size_t size) | ||||
| { | ||||
| 	unsigned char	ipc_msg[IPC_FULL_MSG_SIZE]; | ||||
| 	uint32_t	drbl_val = IPC_BUILD_MNG_MSG(msg_code, size); | ||||
| 
 | ||||
| 	memcpy(ipc_msg, &drbl_val, sizeof(uint32_t)); | ||||
| 	memcpy(ipc_msg + sizeof(uint32_t), msg, size); | ||||
| 	return	write_ipc_to_queue(dev, NULL, NULL, ipc_msg, | ||||
| 		sizeof(uint32_t) + size); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_fw_reset_handler() - FW reset handler | ||||
|  * @dev: ishtp device pointer | ||||
|  * | ||||
|  * Handle FW reset | ||||
|  * | ||||
|  * Return: 0 for success else failure code | ||||
|  */ | ||||
| static int ish_fw_reset_handler(struct ishtp_device *dev) | ||||
| { | ||||
| 	uint32_t	reset_id; | ||||
| 	unsigned long	flags; | ||||
| 	struct wr_msg_ctl_info *processing, *next; | ||||
| 
 | ||||
| 	/* Read reset ID */ | ||||
| 	reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; | ||||
| 
 | ||||
| 	/* Clear IPC output queue */ | ||||
| 	spin_lock_irqsave(&dev->wr_processing_spinlock, flags); | ||||
| 	list_for_each_entry_safe(processing, next, | ||||
| 			&dev->wr_processing_list_head.link, link) { | ||||
| 		list_move_tail(&processing->link, &dev->wr_free_list_head.link); | ||||
| 	} | ||||
| 	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | ||||
| 
 | ||||
| 	/* ISHTP notification in IPC_RESET */ | ||||
| 	ishtp_reset_handler(dev); | ||||
| 
 | ||||
| 	if (!ish_is_input_ready(dev)) | ||||
| 		timed_wait_for_timeout(WAIT_FOR_SEND_SLICE, | ||||
| 			ish_is_input_ready(dev), (2 * HZ)); | ||||
| 
 | ||||
| 	/* ISH FW is dead */ | ||||
| 	if (!ish_is_input_ready(dev)) | ||||
| 		return	-EPIPE; | ||||
| 	/*
 | ||||
| 	 * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending | ||||
| 	 * RESET_NOTIFY_ACK - FW will be checking for it | ||||
| 	 */ | ||||
| 	ish_set_host_rdy(dev); | ||||
| 	/* Send RESET_NOTIFY_ACK (with reset_id) */ | ||||
| 	ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, | ||||
| 			 sizeof(uint32_t)); | ||||
| 
 | ||||
| 	/* Wait for ISH FW'es ILUP and ISHTP_READY */ | ||||
| 	timed_wait_for_timeout(WAIT_FOR_SEND_SLICE, ishtp_fw_is_ready(dev), | ||||
| 		(2 * HZ)); | ||||
| 	if (!ishtp_fw_is_ready(dev)) { | ||||
| 		/* ISH FW is dead */ | ||||
| 		uint32_t	ish_status; | ||||
| 
 | ||||
| 		ish_status = _ish_read_fw_sts_reg(dev); | ||||
| 		dev_err(dev->devc, | ||||
| 			"[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n", | ||||
| 			ish_status); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 	return	0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_fw_reset_work_fn() - FW reset worker function | ||||
|  * @unused: not used | ||||
|  * | ||||
|  * Call ish_fw_reset_handler to complete FW reset | ||||
|  */ | ||||
| static void fw_reset_work_fn(struct work_struct *unused) | ||||
| { | ||||
| 	int	rv; | ||||
| 
 | ||||
| 	rv = ish_fw_reset_handler(ishtp_dev); | ||||
| 	if (!rv) { | ||||
| 		/* ISH is ILUP & ISHTP-ready. Restart ISHTP */ | ||||
| 		schedule_timeout(HZ / 3); | ||||
| 		ishtp_dev->recvd_hw_ready = 1; | ||||
| 		wake_up_interruptible(&ishtp_dev->wait_hw_ready); | ||||
| 
 | ||||
| 		/* ISHTP notification in IPC_RESET sequence completion */ | ||||
| 		ishtp_reset_compl_handler(ishtp_dev); | ||||
| 	} else | ||||
| 		dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n", | ||||
| 			rv); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * _ish_sync_fw_clock() -Sync FW clock with the OS clock | ||||
|  * @dev: ishtp device pointer | ||||
|  * | ||||
|  * Sync FW and OS time | ||||
|  */ | ||||
| static void _ish_sync_fw_clock(struct ishtp_device *dev) | ||||
| { | ||||
| 	static unsigned long	prev_sync; | ||||
| 	struct timespec	ts; | ||||
| 	uint64_t	usec; | ||||
| 
 | ||||
| 	if (prev_sync && jiffies - prev_sync < 20 * HZ) | ||||
| 		return; | ||||
| 
 | ||||
| 	prev_sync = jiffies; | ||||
| 	get_monotonic_boottime(&ts); | ||||
| 	usec = (timespec_to_ns(&ts)) / NSEC_PER_USEC; | ||||
| 	ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t)); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * recv_ipc() - Receive and process IPC management messages | ||||
|  * @dev: ishtp device instance | ||||
|  * @doorbell_val: doorbell value | ||||
|  * | ||||
|  * This function runs in ISR context. | ||||
|  * NOTE: Any other mng command than reset_notify and reset_notify_ack | ||||
|  * won't wake BH handler | ||||
|  */ | ||||
| static void	recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val) | ||||
| { | ||||
| 	uint32_t	mng_cmd; | ||||
| 
 | ||||
| 	mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val); | ||||
| 
 | ||||
| 	switch (mng_cmd) { | ||||
| 	default: | ||||
| 		break; | ||||
| 
 | ||||
| 	case MNG_RX_CMPL_INDICATION: | ||||
| 		if (dev->suspend_flag) { | ||||
| 			dev->suspend_flag = 0; | ||||
| 			wake_up_interruptible(&dev->suspend_wait); | ||||
| 		} | ||||
| 		if (dev->resume_flag) { | ||||
| 			dev->resume_flag = 0; | ||||
| 			wake_up_interruptible(&dev->resume_wait); | ||||
| 		} | ||||
| 
 | ||||
| 		write_ipc_from_queue(dev); | ||||
| 		break; | ||||
| 
 | ||||
| 	case MNG_RESET_NOTIFY: | ||||
| 		if (!ishtp_dev) { | ||||
| 			ishtp_dev = dev; | ||||
| 			INIT_WORK(&fw_reset_work, fw_reset_work_fn); | ||||
| 		} | ||||
| 		schedule_work(&fw_reset_work); | ||||
| 		break; | ||||
| 
 | ||||
| 	case MNG_RESET_NOTIFY_ACK: | ||||
| 		dev->recvd_hw_ready = 1; | ||||
| 		wake_up_interruptible(&dev->wait_hw_ready); | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_irq_handler() - ISH IRQ handler | ||||
|  * @irq: irq number | ||||
|  * @dev_id: ishtp device pointer | ||||
|  * | ||||
|  * ISH IRQ handler. If interrupt is generated and is for ISH it will process | ||||
|  * the interrupt. | ||||
|  */ | ||||
| irqreturn_t ish_irq_handler(int irq, void *dev_id) | ||||
| { | ||||
| 	struct ishtp_device	*dev = dev_id; | ||||
| 	uint32_t	doorbell_val; | ||||
| 	bool	interrupt_generated; | ||||
| 
 | ||||
| 	/* Check that it's interrupt from ISH (may be shared) */ | ||||
| 	interrupt_generated = check_generated_interrupt(dev); | ||||
| 
 | ||||
| 	if (!interrupt_generated) | ||||
| 		return IRQ_NONE; | ||||
| 
 | ||||
| 	doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL); | ||||
| 	if (!IPC_IS_BUSY(doorbell_val)) | ||||
| 		return IRQ_HANDLED; | ||||
| 
 | ||||
| 	if (dev->dev_state == ISHTP_DEV_DISABLED) | ||||
| 		return	IRQ_HANDLED; | ||||
| 
 | ||||
| 	/* Sanity check: IPC dgram length in header */ | ||||
| 	if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) { | ||||
| 		dev_err(dev->devc, | ||||
| 			"IPC hdr - bad length: %u; dropped\n", | ||||
| 			(unsigned int)IPC_HEADER_GET_LENGTH(doorbell_val)); | ||||
| 		goto	eoi; | ||||
| 	} | ||||
| 
 | ||||
| 	switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) { | ||||
| 	default: | ||||
| 		break; | ||||
| 	case IPC_PROTOCOL_MNG: | ||||
| 		recv_ipc(dev, doorbell_val); | ||||
| 		break; | ||||
| 	case IPC_PROTOCOL_ISHTP: | ||||
| 		ishtp_recv(dev); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| eoi: | ||||
| 	/* Update IPC counters */ | ||||
| 	++dev->ipc_rx_cnt; | ||||
| 	dev->ipc_rx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); | ||||
| 
 | ||||
| 	ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); | ||||
| 	/* Flush write to doorbell */ | ||||
| 	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | ||||
| 
 | ||||
| 	return	IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * _ish_hw_reset() - HW reset | ||||
|  * @dev: ishtp device pointer | ||||
|  * | ||||
|  * Reset ISH HW to recover if any error | ||||
|  * | ||||
|  * Return: 0 for success else error fault code | ||||
|  */ | ||||
| static int _ish_hw_reset(struct ishtp_device *dev) | ||||
| { | ||||
| 	struct pci_dev *pdev = dev->pdev; | ||||
| 	int	rv; | ||||
| 	unsigned int	dma_delay; | ||||
| 	uint16_t csr; | ||||
| 
 | ||||
| 	if (!pdev) | ||||
| 		return	-ENODEV; | ||||
| 
 | ||||
| 	rv = pci_reset_function(pdev); | ||||
| 	if (!rv) | ||||
| 		dev->dev_state = ISHTP_DEV_RESETTING; | ||||
| 
 | ||||
| 	if (!pdev->pm_cap) { | ||||
| 		dev_err(&pdev->dev, "Can't reset - no PM caps\n"); | ||||
| 		return	-EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Now trigger reset to FW */ | ||||
| 	ish_reg_write(dev, IPC_REG_ISH_RMP2, 0); | ||||
| 
 | ||||
| 	for (dma_delay = 0; dma_delay < MAX_DMA_DELAY && | ||||
| 		_ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA); | ||||
| 		dma_delay += 5) | ||||
| 		mdelay(5); | ||||
| 
 | ||||
| 	if (dma_delay >= MAX_DMA_DELAY) { | ||||
| 		dev_err(&pdev->dev, | ||||
| 			"Can't reset - stuck with DMA in-progress\n"); | ||||
| 		return	-EBUSY; | ||||
| 	} | ||||
| 
 | ||||
| 	pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &csr); | ||||
| 
 | ||||
| 	csr &= ~PCI_PM_CTRL_STATE_MASK; | ||||
| 	csr |= PCI_D3hot; | ||||
| 	pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); | ||||
| 
 | ||||
| 	mdelay(pdev->d3_delay); | ||||
| 
 | ||||
| 	csr &= ~PCI_PM_CTRL_STATE_MASK; | ||||
| 	csr |= PCI_D0; | ||||
| 	pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); | ||||
| 
 | ||||
| 	ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Send 0 IPC message so that ISH FW wakes up if it was already | ||||
| 	 * asleep | ||||
| 	 */ | ||||
| 	ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); | ||||
| 
 | ||||
| 	/* Flush writes to doorbell and REMAP2 */ | ||||
| 	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | ||||
| 
 | ||||
| 	return	0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * _ish_ipc_reset() - IPC reset | ||||
|  * @dev: ishtp device pointer | ||||
|  * | ||||
|  * Resets host and fw IPC and upper layers | ||||
|  * | ||||
|  * Return: 0 for success else error fault code | ||||
|  */ | ||||
| static int _ish_ipc_reset(struct ishtp_device *dev) | ||||
| { | ||||
| 	struct ipc_rst_payload_type ipc_mng_msg; | ||||
| 	int	rv = 0; | ||||
| 
 | ||||
| 	ipc_mng_msg.reset_id = 1; | ||||
| 	ipc_mng_msg.reserved = 0; | ||||
| 
 | ||||
| 	set_host_ready(dev); | ||||
| 
 | ||||
| 	/* Clear the incoming doorbell */ | ||||
| 	ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); | ||||
| 	/* Flush write to doorbell */ | ||||
| 	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | ||||
| 
 | ||||
| 	dev->recvd_hw_ready = 0; | ||||
| 
 | ||||
| 	/* send message */ | ||||
| 	rv = ipc_send_mng_msg(dev, MNG_RESET_NOTIFY, &ipc_mng_msg, | ||||
| 		sizeof(struct ipc_rst_payload_type)); | ||||
| 	if (rv) { | ||||
| 		dev_err(dev->devc, "Failed to send IPC MNG_RESET_NOTIFY\n"); | ||||
| 		return	rv; | ||||
| 	} | ||||
| 
 | ||||
| 	wait_event_interruptible_timeout(dev->wait_hw_ready, | ||||
| 					 dev->recvd_hw_ready, 2 * HZ); | ||||
| 	if (!dev->recvd_hw_ready) { | ||||
| 		dev_err(dev->devc, "Timed out waiting for HW ready\n"); | ||||
| 		rv = -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	return rv; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_hw_start() -Start ISH HW | ||||
|  * @dev: ishtp device pointer | ||||
|  * | ||||
|  * Set host to ready state and wait for FW reset | ||||
|  * | ||||
|  * Return: 0 for success else error fault code | ||||
|  */ | ||||
| int ish_hw_start(struct ishtp_device *dev) | ||||
| { | ||||
| 	ish_set_host_rdy(dev); | ||||
| 	/* After that we can enable ISH DMA operation */ | ||||
| 	ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Send 0 IPC message so that ISH FW wakes up if it was already | ||||
| 	 * asleep | ||||
| 	 */ | ||||
| 	ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); | ||||
| 	/* Flush write to doorbell */ | ||||
| 	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | ||||
| 
 | ||||
| 	set_host_ready(dev); | ||||
| 
 | ||||
| 	/* wait for FW-initiated reset flow */ | ||||
| 	if (!dev->recvd_hw_ready) | ||||
| 		wait_event_interruptible_timeout(dev->wait_hw_ready, | ||||
| 						 dev->recvd_hw_ready, | ||||
| 						 10 * HZ); | ||||
| 
 | ||||
| 	if (!dev->recvd_hw_ready) { | ||||
| 		dev_err(dev->devc, | ||||
| 			"[ishtp-ish]: Timed out waiting for FW-initiated reset\n"); | ||||
| 		return	-ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_ipc_get_header() -Get doorbell value | ||||
|  * @dev: ishtp device pointer | ||||
|  * @length: length of message | ||||
|  * @busy: busy status | ||||
|  * | ||||
|  * Get door bell value from message header | ||||
|  * | ||||
|  * Return: door bell value | ||||
|  */ | ||||
| static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length, | ||||
| 				   int busy) | ||||
| { | ||||
| 	uint32_t drbl_val; | ||||
| 
 | ||||
| 	drbl_val = IPC_BUILD_HEADER(length, IPC_PROTOCOL_ISHTP, busy); | ||||
| 
 | ||||
| 	return drbl_val; | ||||
| } | ||||
| 
 | ||||
| static const struct ishtp_hw_ops ish_hw_ops = { | ||||
| 	.hw_reset = _ish_hw_reset, | ||||
| 	.ipc_reset = _ish_ipc_reset, | ||||
| 	.ipc_get_header = ish_ipc_get_header, | ||||
| 	.ishtp_read = _ishtp_read, | ||||
| 	.write = write_ipc_to_queue, | ||||
| 	.get_fw_status = _ish_read_fw_sts_reg, | ||||
| 	.sync_fw_clock = _ish_sync_fw_clock, | ||||
| 	.ishtp_read_hdr = _ishtp_read_hdr | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_dev_init() -Initialize ISH devoce | ||||
|  * @pdev: PCI device | ||||
|  * | ||||
|  * Allocate ISHTP device and initialize IPC processing | ||||
|  * | ||||
|  * Return: ISHTP device instance on success else NULL | ||||
|  */ | ||||
| struct ishtp_device *ish_dev_init(struct pci_dev *pdev) | ||||
| { | ||||
| 	struct ishtp_device *dev; | ||||
| 	int	i; | ||||
| 
 | ||||
| 	dev = kzalloc(sizeof(struct ishtp_device) + sizeof(struct ish_hw), | ||||
| 		GFP_KERNEL); | ||||
| 	if (!dev) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	ishtp_device_init(dev); | ||||
| 
 | ||||
| 	init_waitqueue_head(&dev->wait_hw_ready); | ||||
| 
 | ||||
| 	spin_lock_init(&dev->wr_processing_spinlock); | ||||
| 	spin_lock_init(&dev->out_ipc_spinlock); | ||||
| 
 | ||||
| 	/* Init IPC processing and free lists */ | ||||
| 	INIT_LIST_HEAD(&dev->wr_processing_list_head.link); | ||||
| 	INIT_LIST_HEAD(&dev->wr_free_list_head.link); | ||||
| 	for (i = 0; i < IPC_TX_FIFO_SIZE; ++i) { | ||||
| 		struct wr_msg_ctl_info	*tx_buf; | ||||
| 
 | ||||
| 		tx_buf = kzalloc(sizeof(struct wr_msg_ctl_info), GFP_KERNEL); | ||||
| 		if (!tx_buf) { | ||||
| 			/*
 | ||||
| 			 * IPC buffers may be limited or not available | ||||
| 			 * at all - although this shouldn't happen | ||||
| 			 */ | ||||
| 			dev_err(dev->devc, | ||||
| 				"[ishtp-ish]: failure in Tx FIFO allocations (%d)\n", | ||||
| 				i); | ||||
| 			break; | ||||
| 		} | ||||
| 		list_add_tail(&tx_buf->link, &dev->wr_free_list_head.link); | ||||
| 	} | ||||
| 
 | ||||
| 	dev->ops = &ish_hw_ops; | ||||
| 	dev->devc = &pdev->dev; | ||||
| 	dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr); | ||||
| 	return dev; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ish_device_disable() - Disable ISH device | ||||
|  * @dev: ISHTP device pointer | ||||
|  * | ||||
|  * Disable ISH by clearing host ready to inform firmware. | ||||
|  */ | ||||
| void	ish_device_disable(struct ishtp_device *dev) | ||||
| { | ||||
| 	dev->dev_state = ISHTP_DEV_DISABLED; | ||||
| 	ish_clr_host_rdy(dev); | ||||
| } | ||||
							
								
								
									
										322
									
								
								drivers/hid/intel-ish-hid/ipc/pci-ish.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								drivers/hid/intel-ish-hid/ipc/pci-ish.c
									
									
									
									
									
										Normal file
									
								
							| @ -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"); | ||||
							
								
								
									
										64
									
								
								drivers/hid/intel-ish-hid/ipc/utils.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								drivers/hid/intel-ish-hid/ipc/utils.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 */ | ||||
							
								
								
									
										978
									
								
								drivers/hid/intel-ish-hid/ishtp-hid-client.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										978
									
								
								drivers/hid/intel-ish-hid/ishtp-hid-client.c
									
									
									
									
									
										Normal file
									
								
							| @ -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:*"); | ||||
							
								
								
									
										246
									
								
								drivers/hid/intel-ish-hid/ishtp-hid.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								drivers/hid/intel-ish-hid/ishtp-hid.c
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										182
									
								
								drivers/hid/intel-ish-hid/ishtp-hid.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								drivers/hid/intel-ish-hid/ishtp-hid.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 */ | ||||
							
								
								
									
										788
									
								
								drivers/hid/intel-ish-hid/ishtp/bus.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										788
									
								
								drivers/hid/intel-ish-hid/ishtp/bus.c
									
									
									
									
									
										Normal file
									
								
							| @ -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"); | ||||
							
								
								
									
										114
									
								
								drivers/hid/intel-ish-hid/ishtp/bus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								drivers/hid/intel-ish-hid/ishtp/bus.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 */ | ||||
							
								
								
									
										257
									
								
								drivers/hid/intel-ish-hid/ishtp/client-buffers.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								drivers/hid/intel-ish-hid/ishtp/client-buffers.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
							
								
								
									
										1054
									
								
								drivers/hid/intel-ish-hid/ishtp/client.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1054
									
								
								drivers/hid/intel-ish-hid/ishtp/client.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										182
									
								
								drivers/hid/intel-ish-hid/ishtp/client.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								drivers/hid/intel-ish-hid/ishtp/client.h
									
									
									
									
									
										Normal file
									
								
							| @ -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_ */ | ||||
							
								
								
									
										175
									
								
								drivers/hid/intel-ish-hid/ishtp/dma-if.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								drivers/hid/intel-ish-hid/ishtp/dma-if.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,175 @@ | ||||
| /*
 | ||||
|  * ISHTP DMA I/F functions | ||||
|  * | ||||
|  * Copyright (c) 2003-2016, Intel Corporation. | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  * version 2, as published by the Free Software Foundation. | ||||
|  * | ||||
|  * This program is distributed in the hope it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||||
|  * more details. | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/slab.h> | ||||
| #include <linux/sched.h> | ||||
| #include <linux/wait.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/dma-mapping.h> | ||||
| #include "ishtp-dev.h" | ||||
| #include "client.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * ishtp_cl_alloc_dma_buf() - Allocate DMA RX and TX buffer | ||||
|  * @dev: ishtp device | ||||
|  * | ||||
|  * Allocate RX and TX DMA buffer once during bus setup. | ||||
|  * It allocates 1MB, RX and TX DMA buffer, which are divided | ||||
|  * into slots. | ||||
|  */ | ||||
| void	ishtp_cl_alloc_dma_buf(struct ishtp_device *dev) | ||||
| { | ||||
| 	dma_addr_t	h; | ||||
| 
 | ||||
| 	if (dev->ishtp_host_dma_tx_buf) | ||||
| 		return; | ||||
| 
 | ||||
| 	dev->ishtp_host_dma_tx_buf_size = 1024*1024; | ||||
| 	dev->ishtp_host_dma_rx_buf_size = 1024*1024; | ||||
| 
 | ||||
| 	/* Allocate Tx buffer and init usage bitmap */ | ||||
| 	dev->ishtp_host_dma_tx_buf = dma_alloc_coherent(dev->devc, | ||||
| 					dev->ishtp_host_dma_tx_buf_size, | ||||
| 					&h, GFP_KERNEL); | ||||
| 	if (dev->ishtp_host_dma_tx_buf) | ||||
| 		dev->ishtp_host_dma_tx_buf_phys = h; | ||||
| 
 | ||||
| 	dev->ishtp_dma_num_slots = dev->ishtp_host_dma_tx_buf_size / | ||||
| 						DMA_SLOT_SIZE; | ||||
| 
 | ||||
| 	dev->ishtp_dma_tx_map = kcalloc(dev->ishtp_dma_num_slots, | ||||
| 					sizeof(uint8_t), | ||||
| 					GFP_KERNEL); | ||||
| 	spin_lock_init(&dev->ishtp_dma_tx_lock); | ||||
| 
 | ||||
| 	/* Allocate Rx buffer */ | ||||
| 	dev->ishtp_host_dma_rx_buf = dma_alloc_coherent(dev->devc, | ||||
| 					dev->ishtp_host_dma_rx_buf_size, | ||||
| 					 &h, GFP_KERNEL); | ||||
| 
 | ||||
| 	if (dev->ishtp_host_dma_rx_buf) | ||||
| 		dev->ishtp_host_dma_rx_buf_phys = h; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ishtp_cl_free_dma_buf() - Free DMA RX and TX buffer | ||||
|  * @dev: ishtp device | ||||
|  * | ||||
|  * Free DMA buffer when all clients are released. This is | ||||
|  * only happens during error path in ISH built in driver | ||||
|  * model | ||||
|  */ | ||||
| void	ishtp_cl_free_dma_buf(struct ishtp_device *dev) | ||||
| { | ||||
| 	dma_addr_t	h; | ||||
| 
 | ||||
| 	if (dev->ishtp_host_dma_tx_buf) { | ||||
| 		h = dev->ishtp_host_dma_tx_buf_phys; | ||||
| 		dma_free_coherent(dev->devc, dev->ishtp_host_dma_tx_buf_size, | ||||
| 				  dev->ishtp_host_dma_tx_buf, h); | ||||
| 	} | ||||
| 
 | ||||
| 	if (dev->ishtp_host_dma_rx_buf) { | ||||
| 		h = dev->ishtp_host_dma_rx_buf_phys; | ||||
| 		dma_free_coherent(dev->devc, dev->ishtp_host_dma_rx_buf_size, | ||||
| 				  dev->ishtp_host_dma_rx_buf, h); | ||||
| 	} | ||||
| 
 | ||||
| 	kfree(dev->ishtp_dma_tx_map); | ||||
| 	dev->ishtp_host_dma_tx_buf = NULL; | ||||
| 	dev->ishtp_host_dma_rx_buf = NULL; | ||||
| 	dev->ishtp_dma_tx_map = NULL; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * ishtp_cl_get_dma_send_buf() - Get a DMA memory slot | ||||
|  * @dev:	ishtp device | ||||
|  * @size:	Size of memory to get | ||||
|  * | ||||
|  * Find and return free address of "size" bytes in dma tx buffer. | ||||
|  * the function will mark this address as "in-used" memory. | ||||
|  * | ||||
|  * Return: NULL when no free buffer else a buffer to copy | ||||
|  */ | ||||
| void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev, | ||||
| 				uint32_t size) | ||||
| { | ||||
| 	unsigned long	flags; | ||||
| 	int i, j, free; | ||||
| 	/* additional slot is needed if there is rem */ | ||||
| 	int required_slots = (size / DMA_SLOT_SIZE) | ||||
| 		+ 1 * (size % DMA_SLOT_SIZE != 0); | ||||
| 
 | ||||
| 	spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags); | ||||
| 	for (i = 0; i <= (dev->ishtp_dma_num_slots - required_slots); i++) { | ||||
| 		free = 1; | ||||
| 		for (j = 0; j < required_slots; j++) | ||||
| 			if (dev->ishtp_dma_tx_map[i+j]) { | ||||
| 				free = 0; | ||||
| 				i += j; | ||||
| 				break; | ||||
| 			} | ||||
| 		if (free) { | ||||
| 			/* mark memory as "caught" */ | ||||
| 			for (j = 0; j < required_slots; j++) | ||||
| 				dev->ishtp_dma_tx_map[i+j] = 1; | ||||
| 			spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags); | ||||
| 			return (i * DMA_SLOT_SIZE) + | ||||
| 				(unsigned char *)dev->ishtp_host_dma_tx_buf; | ||||
| 		} | ||||
| 	} | ||||
| 	spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags); | ||||
| 	dev_err(dev->devc, "No free DMA buffer to send msg\n"); | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * ishtp_cl_release_dma_acked_mem() - Release DMA memory slot | ||||
|  * @dev:	ishtp device | ||||
|  * @msg_addr:	message address of slot | ||||
|  * @size:	Size of memory to get | ||||
|  * | ||||
|  * Release_dma_acked_mem - returnes the acked memory to free list. | ||||
|  * (from msg_addr, size bytes long) | ||||
|  */ | ||||
| void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev, | ||||
| 				    void *msg_addr, | ||||
| 				    uint8_t size) | ||||
| { | ||||
| 	unsigned long	flags; | ||||
| 	int acked_slots = (size / DMA_SLOT_SIZE) | ||||
| 		+ 1 * (size % DMA_SLOT_SIZE != 0); | ||||
| 	int i, j; | ||||
| 
 | ||||
| 	if ((msg_addr - dev->ishtp_host_dma_tx_buf) % DMA_SLOT_SIZE) { | ||||
| 		dev_err(dev->devc, "Bad DMA Tx ack address\n"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	i = (msg_addr - dev->ishtp_host_dma_tx_buf) / DMA_SLOT_SIZE; | ||||
| 	spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags); | ||||
| 	for (j = 0; j < acked_slots; j++) { | ||||
| 		if ((i + j) >= dev->ishtp_dma_num_slots || | ||||
| 					!dev->ishtp_dma_tx_map[i+j]) { | ||||
| 			/* no such slot, or memory is already free */ | ||||
| 			spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags); | ||||
| 			dev_err(dev->devc, "Bad DMA Tx ack address\n"); | ||||
| 			return; | ||||
| 		} | ||||
| 		dev->ishtp_dma_tx_map[i+j] = 0; | ||||
| 	} | ||||
| 	spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags); | ||||
| } | ||||
							
								
								
									
										1032
									
								
								drivers/hid/intel-ish-hid/ishtp/hbm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1032
									
								
								drivers/hid/intel-ish-hid/ishtp/hbm.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										321
									
								
								drivers/hid/intel-ish-hid/ishtp/hbm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								drivers/hid/intel-ish-hid/ishtp/hbm.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,321 @@ | ||||
| /*
 | ||||
|  * ISHTP bus layer messages handling | ||||
|  * | ||||
|  * Copyright (c) 2003-2016, Intel Corporation. | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  * version 2, as published by the Free Software Foundation. | ||||
|  * | ||||
|  * This program is distributed in the hope it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||||
|  * more details. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef _ISHTP_HBM_H_ | ||||
| #define _ISHTP_HBM_H_ | ||||
| 
 | ||||
| #include <linux/uuid.h> | ||||
| 
 | ||||
| struct ishtp_device; | ||||
| struct ishtp_msg_hdr; | ||||
| struct ishtp_cl; | ||||
| 
 | ||||
| /*
 | ||||
|  * Timeouts in Seconds | ||||
|  */ | ||||
| #define ISHTP_INTEROP_TIMEOUT		7 /* Timeout on ready message */ | ||||
| 
 | ||||
| #define ISHTP_CL_CONNECT_TIMEOUT	15 /* HPS: Client Connect Timeout */ | ||||
| 
 | ||||
| /*
 | ||||
|  * ISHTP Version | ||||
|  */ | ||||
| #define HBM_MINOR_VERSION		0 | ||||
| #define HBM_MAJOR_VERSION		1 | ||||
| 
 | ||||
| /* Host bus message command opcode */ | ||||
| #define ISHTP_HBM_CMD_OP_MSK		0x7f | ||||
| /* Host bus message command RESPONSE */ | ||||
| #define ISHTP_HBM_CMD_RES_MSK		0x80 | ||||
| 
 | ||||
| /*
 | ||||
|  * ISHTP Bus Message Command IDs | ||||
|  */ | ||||
| #define HOST_START_REQ_CMD		0x01 | ||||
| #define HOST_START_RES_CMD		0x81 | ||||
| 
 | ||||
| #define HOST_STOP_REQ_CMD		0x02 | ||||
| #define HOST_STOP_RES_CMD		0x82 | ||||
| 
 | ||||
| #define FW_STOP_REQ_CMD			0x03 | ||||
| 
 | ||||
| #define HOST_ENUM_REQ_CMD		0x04 | ||||
| #define HOST_ENUM_RES_CMD		0x84 | ||||
| 
 | ||||
| #define HOST_CLIENT_PROPERTIES_REQ_CMD	0x05 | ||||
| #define HOST_CLIENT_PROPERTIES_RES_CMD	0x85 | ||||
| 
 | ||||
| #define CLIENT_CONNECT_REQ_CMD		0x06 | ||||
| #define CLIENT_CONNECT_RES_CMD		0x86 | ||||
| 
 | ||||
| #define CLIENT_DISCONNECT_REQ_CMD	0x07 | ||||
| #define CLIENT_DISCONNECT_RES_CMD	0x87 | ||||
| 
 | ||||
| #define ISHTP_FLOW_CONTROL_CMD		0x08 | ||||
| 
 | ||||
| #define DMA_BUFFER_ALLOC_NOTIFY		0x11 | ||||
| #define DMA_BUFFER_ALLOC_RESPONSE	0x91 | ||||
| 
 | ||||
| #define DMA_XFER			0x12 | ||||
| #define DMA_XFER_ACK			0x92 | ||||
| 
 | ||||
| /*
 | ||||
|  * ISHTP Stop Reason | ||||
|  * used by hbm_host_stop_request.reason | ||||
|  */ | ||||
| #define	DRIVER_STOP_REQUEST		0x00 | ||||
| 
 | ||||
| /*
 | ||||
|  * ISHTP BUS Interface Section | ||||
|  */ | ||||
| struct ishtp_msg_hdr { | ||||
| 	uint32_t fw_addr:8; | ||||
| 	uint32_t host_addr:8; | ||||
| 	uint32_t length:9; | ||||
| 	uint32_t reserved:6; | ||||
| 	uint32_t msg_complete:1; | ||||
| } __packed; | ||||
| 
 | ||||
| struct ishtp_bus_message { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t data[0]; | ||||
| } __packed; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct hbm_cl_cmd - client specific host bus command | ||||
|  *	CONNECT, DISCONNECT, and FlOW CONTROL | ||||
|  * | ||||
|  * @hbm_cmd - bus message command header | ||||
|  * @fw_addr - address of the fw client | ||||
|  * @host_addr - address of the client in the driver | ||||
|  * @data | ||||
|  */ | ||||
| struct ishtp_hbm_cl_cmd { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t fw_addr; | ||||
| 	uint8_t host_addr; | ||||
| 	uint8_t data; | ||||
| }; | ||||
| 
 | ||||
| struct hbm_version { | ||||
| 	uint8_t minor_version; | ||||
| 	uint8_t major_version; | ||||
| } __packed; | ||||
| 
 | ||||
| struct hbm_host_version_request { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t reserved; | ||||
| 	struct hbm_version host_version; | ||||
| } __packed; | ||||
| 
 | ||||
| struct hbm_host_version_response { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t host_version_supported; | ||||
| 	struct hbm_version fw_max_version; | ||||
| } __packed; | ||||
| 
 | ||||
| struct hbm_host_stop_request { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t reason; | ||||
| 	uint8_t reserved[2]; | ||||
| } __packed; | ||||
| 
 | ||||
| struct hbm_host_stop_response { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t reserved[3]; | ||||
| } __packed; | ||||
| 
 | ||||
| struct hbm_host_enum_request { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t reserved[3]; | ||||
| } __packed; | ||||
| 
 | ||||
| struct hbm_host_enum_response { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t reserved[3]; | ||||
| 	uint8_t valid_addresses[32]; | ||||
| } __packed; | ||||
| 
 | ||||
| struct ishtp_client_properties { | ||||
| 	uuid_le protocol_name; | ||||
| 	uint8_t protocol_version; | ||||
| 	uint8_t max_number_of_connections; | ||||
| 	uint8_t fixed_address; | ||||
| 	uint8_t single_recv_buf; | ||||
| 	uint32_t max_msg_length; | ||||
| 	uint8_t dma_hdr_len; | ||||
| #define	ISHTP_CLIENT_DMA_ENABLED	0x80 | ||||
| 	uint8_t reserved4; | ||||
| 	uint8_t reserved5; | ||||
| 	uint8_t reserved6; | ||||
| } __packed; | ||||
| 
 | ||||
| struct hbm_props_request { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t address; | ||||
| 	uint8_t reserved[2]; | ||||
| } __packed; | ||||
| 
 | ||||
| struct hbm_props_response { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t address; | ||||
| 	uint8_t status; | ||||
| 	uint8_t reserved[1]; | ||||
| 	struct ishtp_client_properties client_properties; | ||||
| } __packed; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct hbm_client_connect_request - connect/disconnect request | ||||
|  * | ||||
|  * @hbm_cmd - bus message command header | ||||
|  * @fw_addr - address of the fw client | ||||
|  * @host_addr - address of the client in the driver | ||||
|  * @reserved | ||||
|  */ | ||||
| struct hbm_client_connect_request { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t fw_addr; | ||||
| 	uint8_t host_addr; | ||||
| 	uint8_t reserved; | ||||
| } __packed; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct hbm_client_connect_response - connect/disconnect response | ||||
|  * | ||||
|  * @hbm_cmd - bus message command header | ||||
|  * @fw_addr - address of the fw client | ||||
|  * @host_addr - address of the client in the driver | ||||
|  * @status - status of the request | ||||
|  */ | ||||
| struct hbm_client_connect_response { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t fw_addr; | ||||
| 	uint8_t host_addr; | ||||
| 	uint8_t status; | ||||
| } __packed; | ||||
| 
 | ||||
| 
 | ||||
| #define ISHTP_FC_MESSAGE_RESERVED_LENGTH		5 | ||||
| 
 | ||||
| struct hbm_flow_control { | ||||
| 	uint8_t hbm_cmd; | ||||
| 	uint8_t fw_addr; | ||||
| 	uint8_t host_addr; | ||||
| 	uint8_t reserved[ISHTP_FC_MESSAGE_RESERVED_LENGTH]; | ||||
| } __packed; | ||||
| 
 | ||||
| struct dma_alloc_notify { | ||||
| 	uint8_t hbm; | ||||
| 	uint8_t status; | ||||
| 	uint8_t reserved[2]; | ||||
| 	uint32_t buf_size; | ||||
| 	uint64_t buf_address; | ||||
| 	/* [...] May come more size/address pairs */ | ||||
| } __packed; | ||||
| 
 | ||||
| struct dma_xfer_hbm { | ||||
| 	uint8_t hbm; | ||||
| 	uint8_t fw_client_id; | ||||
| 	uint8_t host_client_id; | ||||
| 	uint8_t reserved; | ||||
| 	uint64_t msg_addr; | ||||
| 	uint32_t msg_length; | ||||
| 	uint32_t reserved2; | ||||
| } __packed; | ||||
| 
 | ||||
| /* System state */ | ||||
| #define ISHTP_SYSTEM_STATE_CLIENT_ADDR		13 | ||||
| 
 | ||||
| #define SYSTEM_STATE_SUBSCRIBE			0x1 | ||||
| #define SYSTEM_STATE_STATUS			0x2 | ||||
| #define SYSTEM_STATE_QUERY_SUBSCRIBERS		0x3 | ||||
| #define SYSTEM_STATE_STATE_CHANGE_REQ		0x4 | ||||
| /*indicates suspend and resume states*/ | ||||
| #define SUSPEND_STATE_BIT			(1<<1) | ||||
| 
 | ||||
| struct ish_system_states_header { | ||||
| 	uint32_t cmd; | ||||
| 	uint32_t cmd_status;	/*responses will have this set*/ | ||||
| } __packed; | ||||
| 
 | ||||
| struct ish_system_states_subscribe { | ||||
| 	struct ish_system_states_header hdr; | ||||
| 	uint32_t states; | ||||
| } __packed; | ||||
| 
 | ||||
| struct ish_system_states_status { | ||||
| 	struct ish_system_states_header hdr; | ||||
| 	uint32_t supported_states; | ||||
| 	uint32_t states_status; | ||||
| } __packed; | ||||
| 
 | ||||
| struct ish_system_states_query_subscribers { | ||||
| 	struct ish_system_states_header hdr; | ||||
| } __packed; | ||||
| 
 | ||||
| struct ish_system_states_state_change_req { | ||||
| 	struct ish_system_states_header hdr; | ||||
| 	uint32_t requested_states; | ||||
| 	uint32_t states_status; | ||||
| } __packed; | ||||
| 
 | ||||
| /**
 | ||||
|  * enum ishtp_hbm_state - host bus message protocol state | ||||
|  * | ||||
|  * @ISHTP_HBM_IDLE : protocol not started | ||||
|  * @ISHTP_HBM_START : start request message was sent | ||||
|  * @ISHTP_HBM_ENUM_CLIENTS : enumeration request was sent | ||||
|  * @ISHTP_HBM_CLIENT_PROPERTIES : acquiring clients properties | ||||
|  */ | ||||
| enum ishtp_hbm_state { | ||||
| 	ISHTP_HBM_IDLE = 0, | ||||
| 	ISHTP_HBM_START, | ||||
| 	ISHTP_HBM_STARTED, | ||||
| 	ISHTP_HBM_ENUM_CLIENTS, | ||||
| 	ISHTP_HBM_CLIENT_PROPERTIES, | ||||
| 	ISHTP_HBM_WORKING, | ||||
| 	ISHTP_HBM_STOPPED, | ||||
| }; | ||||
| 
 | ||||
| static inline void ishtp_hbm_hdr(struct ishtp_msg_hdr *hdr, size_t length) | ||||
| { | ||||
| 	hdr->host_addr = 0; | ||||
| 	hdr->fw_addr = 0; | ||||
| 	hdr->length = length; | ||||
| 	hdr->msg_complete = 1; | ||||
| 	hdr->reserved = 0; | ||||
| } | ||||
| 
 | ||||
| int ishtp_hbm_start_req(struct ishtp_device *dev); | ||||
| int ishtp_hbm_start_wait(struct ishtp_device *dev); | ||||
| int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev, | ||||
| 				  struct ishtp_cl *cl); | ||||
| int ishtp_hbm_cl_disconnect_req(struct ishtp_device *dev, struct ishtp_cl *cl); | ||||
| int ishtp_hbm_cl_connect_req(struct ishtp_device *dev, struct ishtp_cl *cl); | ||||
| void ishtp_hbm_enum_clients_req(struct ishtp_device *dev); | ||||
| void bh_hbm_work_fn(struct work_struct *work); | ||||
| void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr); | ||||
| void recv_fixed_cl_msg(struct ishtp_device *dev, | ||||
| 	struct ishtp_msg_hdr *ishtp_hdr); | ||||
| void ishtp_hbm_dispatch(struct ishtp_device *dev, | ||||
| 	struct ishtp_bus_message *hdr); | ||||
| 
 | ||||
| void ishtp_query_subscribers(struct ishtp_device *dev); | ||||
| 
 | ||||
| /* Exported I/F */ | ||||
| void ishtp_send_suspend(struct ishtp_device *dev); | ||||
| void ishtp_send_resume(struct ishtp_device *dev); | ||||
| 
 | ||||
| #endif /* _ISHTP_HBM_H_ */ | ||||
							
								
								
									
										115
									
								
								drivers/hid/intel-ish-hid/ishtp/init.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								drivers/hid/intel-ish-hid/ishtp/init.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| /*
 | ||||
|  * Initialization protocol for ISHTP driver | ||||
|  * | ||||
|  * Copyright (c) 2003-2016, Intel Corporation. | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  * version 2, as published by the Free Software Foundation. | ||||
|  * | ||||
|  * This program is distributed in the hope it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||||
|  * more details. | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/export.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/sched.h> | ||||
| #include <linux/miscdevice.h> | ||||
| #include "ishtp-dev.h" | ||||
| #include "hbm.h" | ||||
| #include "client.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * ishtp_dev_state_str() -Convert to string format | ||||
|  * @state: state to convert | ||||
|  * | ||||
|  * Convert state to string for prints | ||||
|  * | ||||
|  * Return: character pointer to converted string | ||||
|  */ | ||||
| const char *ishtp_dev_state_str(int state) | ||||
| { | ||||
| 	switch (state) { | ||||
| 	case ISHTP_DEV_INITIALIZING: | ||||
| 		return	"INITIALIZING"; | ||||
| 	case ISHTP_DEV_INIT_CLIENTS: | ||||
| 		return	"INIT_CLIENTS"; | ||||
| 	case ISHTP_DEV_ENABLED: | ||||
| 		return	"ENABLED"; | ||||
| 	case ISHTP_DEV_RESETTING: | ||||
| 		return	"RESETTING"; | ||||
| 	case ISHTP_DEV_DISABLED: | ||||
| 		return	"DISABLED"; | ||||
| 	case ISHTP_DEV_POWER_DOWN: | ||||
| 		return	"POWER_DOWN"; | ||||
| 	case ISHTP_DEV_POWER_UP: | ||||
| 		return	"POWER_UP"; | ||||
| 	default: | ||||
| 		return "unknown"; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ishtp_device_init() - ishtp device init | ||||
|  * @dev: ISHTP device instance | ||||
|  * | ||||
|  * After ISHTP device is alloacted, this function is used to initialize | ||||
|  * each field which includes spin lock, work struct and lists | ||||
|  */ | ||||
| void ishtp_device_init(struct ishtp_device *dev) | ||||
| { | ||||
| 	dev->dev_state = ISHTP_DEV_INITIALIZING; | ||||
| 	INIT_LIST_HEAD(&dev->cl_list); | ||||
| 	INIT_LIST_HEAD(&dev->device_list); | ||||
| 	dev->rd_msg_fifo_head = 0; | ||||
| 	dev->rd_msg_fifo_tail = 0; | ||||
| 	spin_lock_init(&dev->rd_msg_spinlock); | ||||
| 
 | ||||
| 	init_waitqueue_head(&dev->wait_hbm_recvd_msg); | ||||
| 	spin_lock_init(&dev->read_list_spinlock); | ||||
| 	spin_lock_init(&dev->device_lock); | ||||
| 	spin_lock_init(&dev->device_list_lock); | ||||
| 	spin_lock_init(&dev->cl_list_lock); | ||||
| 	spin_lock_init(&dev->fw_clients_lock); | ||||
| 	INIT_WORK(&dev->bh_hbm_work, bh_hbm_work_fn); | ||||
| 
 | ||||
| 	bitmap_zero(dev->host_clients_map, ISHTP_CLIENTS_MAX); | ||||
| 	dev->open_handle_count = 0; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Reserving client ID 0 for ISHTP Bus Message communications | ||||
| 	 */ | ||||
| 	bitmap_set(dev->host_clients_map, 0, 1); | ||||
| 
 | ||||
| 	INIT_LIST_HEAD(&dev->read_list.list); | ||||
| 
 | ||||
| } | ||||
| EXPORT_SYMBOL(ishtp_device_init); | ||||
| 
 | ||||
| /**
 | ||||
|  * ishtp_start() - Start ISH processing | ||||
|  * @dev: ISHTP device instance | ||||
|  * | ||||
|  * Start ISHTP processing by sending query subscriber message | ||||
|  * | ||||
|  * Return: 0 on success else -ENODEV | ||||
|  */ | ||||
| int ishtp_start(struct ishtp_device *dev) | ||||
| { | ||||
| 	if (ishtp_hbm_start_wait(dev)) { | ||||
| 		dev_err(dev->devc, "HBM haven't started"); | ||||
| 		goto err; | ||||
| 	} | ||||
| 
 | ||||
| 	/* suspend & resume notification - send QUERY_SUBSCRIBERS msg */ | ||||
| 	ishtp_query_subscribers(dev); | ||||
| 
 | ||||
| 	return 0; | ||||
| err: | ||||
| 	dev_err(dev->devc, "link layer initialization failed.\n"); | ||||
| 	dev->dev_state = ISHTP_DEV_DISABLED; | ||||
| 	return -ENODEV; | ||||
| } | ||||
| EXPORT_SYMBOL(ishtp_start); | ||||
							
								
								
									
										277
									
								
								drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,277 @@ | ||||
| /*
 | ||||
|  * Most ISHTP provider device and ISHTP logic declarations | ||||
|  * | ||||
|  * Copyright (c) 2003-2016, Intel Corporation. | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  * version 2, as published by the Free Software Foundation. | ||||
|  * | ||||
|  * This program is distributed in the hope it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||||
|  * more details. | ||||
|  */ | ||||
| 
 | ||||
| #ifndef _ISHTP_DEV_H_ | ||||
| #define _ISHTP_DEV_H_ | ||||
| 
 | ||||
| #include <linux/types.h> | ||||
| #include <linux/spinlock.h> | ||||
| #include "bus.h" | ||||
| #include "hbm.h" | ||||
| 
 | ||||
| #define	IPC_PAYLOAD_SIZE	128 | ||||
| #define ISHTP_RD_MSG_BUF_SIZE	IPC_PAYLOAD_SIZE | ||||
| #define	IPC_FULL_MSG_SIZE	132 | ||||
| 
 | ||||
| /* Number of messages to be held in ISR->BH FIFO */ | ||||
| #define	RD_INT_FIFO_SIZE	64 | ||||
| 
 | ||||
| /*
 | ||||
|  * Number of IPC messages to be held in Tx FIFO, to be sent by ISR - | ||||
|  * Tx complete interrupt or RX_COMPLETE handler | ||||
|  */ | ||||
| #define	IPC_TX_FIFO_SIZE	512 | ||||
| 
 | ||||
| /*
 | ||||
|  * Number of Maximum ISHTP Clients | ||||
|  */ | ||||
| #define ISHTP_CLIENTS_MAX 256 | ||||
| 
 | ||||
| /*
 | ||||
|  * Number of File descriptors/handles | ||||
|  * that can be opened to the driver. | ||||
|  * | ||||
|  * Limit to 255: 256 Total Clients | ||||
|  * minus internal client for ISHTP Bus Messages | ||||
|  */ | ||||
| #define ISHTP_MAX_OPEN_HANDLE_COUNT (ISHTP_CLIENTS_MAX - 1) | ||||
| 
 | ||||
| /* Internal Clients Number */ | ||||
| #define ISHTP_HOST_CLIENT_ID_ANY		(-1) | ||||
| #define ISHTP_HBM_HOST_CLIENT_ID		0 | ||||
| 
 | ||||
| #define	MAX_DMA_DELAY	20 | ||||
| 
 | ||||
| /* ISHTP device states */ | ||||
| enum ishtp_dev_state { | ||||
| 	ISHTP_DEV_INITIALIZING = 0, | ||||
| 	ISHTP_DEV_INIT_CLIENTS, | ||||
| 	ISHTP_DEV_ENABLED, | ||||
| 	ISHTP_DEV_RESETTING, | ||||
| 	ISHTP_DEV_DISABLED, | ||||
| 	ISHTP_DEV_POWER_DOWN, | ||||
| 	ISHTP_DEV_POWER_UP | ||||
| }; | ||||
| const char *ishtp_dev_state_str(int state); | ||||
| 
 | ||||
| struct ishtp_cl; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct ishtp_fw_client - representation of fw client | ||||
|  * | ||||
|  * @props - client properties | ||||
|  * @client_id - fw client id | ||||
|  */ | ||||
| struct ishtp_fw_client { | ||||
| 	struct ishtp_client_properties props; | ||||
| 	uint8_t client_id; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct ishtp_msg_data - ISHTP message data struct | ||||
|  * @size:	Size of data in the *data | ||||
|  * @data:	Pointer to data | ||||
|  */ | ||||
| struct ishtp_msg_data { | ||||
| 	uint32_t size; | ||||
| 	unsigned char *data; | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * struct ishtp_cl_rb - request block structure | ||||
|  * @list:	Link to list members | ||||
|  * @cl:		ISHTP client instance | ||||
|  * @buffer:	message header | ||||
|  * @buf_idx:	Index into buffer | ||||
|  * @read_time:	 unused at this time | ||||
|  */ | ||||
| struct ishtp_cl_rb { | ||||
| 	struct list_head list; | ||||
| 	struct ishtp_cl *cl; | ||||
| 	struct ishtp_msg_data buffer; | ||||
| 	unsigned long buf_idx; | ||||
| 	unsigned long read_time; | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * Control info for IPC messages ISHTP/IPC sending FIFO - | ||||
|  * list with inline data buffer | ||||
|  * This structure will be filled with parameters submitted | ||||
|  * by the caller glue layer | ||||
|  * 'buf' may be pointing to the external buffer or to 'inline_data' | ||||
|  * 'offset' will be initialized to 0 by submitting | ||||
|  * | ||||
|  * 'ipc_send_compl' is intended for use by clients that send fragmented | ||||
|  * messages. When a fragment is sent down to IPC msg regs, | ||||
|  * it will be called. | ||||
|  * If it has more fragments to send, it will do it. With last fragment | ||||
|  * it will send appropriate ISHTP "message-complete" flag. | ||||
|  * It will remove the outstanding message | ||||
|  * (mark outstanding buffer as available). | ||||
|  * If counting flow control is in work and there are more flow control | ||||
|  * credits, it can put the next client message queued in cl. | ||||
|  * structure for IPC processing. | ||||
|  * | ||||
|  */ | ||||
| struct wr_msg_ctl_info { | ||||
| 	/* Will be called with 'ipc_send_compl_prm' as parameter */ | ||||
| 	void (*ipc_send_compl)(void *); | ||||
| 
 | ||||
| 	void *ipc_send_compl_prm; | ||||
| 	size_t length; | ||||
| 	struct list_head	link; | ||||
| 	unsigned char	inline_data[IPC_FULL_MSG_SIZE]; | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * The ISHTP layer talks to hardware IPC message using the following | ||||
|  * callbacks | ||||
|  */ | ||||
| struct ishtp_hw_ops { | ||||
| 	int	(*hw_reset)(struct ishtp_device *dev); | ||||
| 	int	(*ipc_reset)(struct ishtp_device *dev); | ||||
| 	uint32_t (*ipc_get_header)(struct ishtp_device *dev, int length, | ||||
| 				   int busy); | ||||
| 	int	(*write)(struct ishtp_device *dev, | ||||
| 		void (*ipc_send_compl)(void *), void *ipc_send_compl_prm, | ||||
| 		unsigned char *msg, int length); | ||||
| 	uint32_t	(*ishtp_read_hdr)(const struct ishtp_device *dev); | ||||
| 	int	(*ishtp_read)(struct ishtp_device *dev, unsigned char *buffer, | ||||
| 			unsigned long buffer_length); | ||||
| 	uint32_t	(*get_fw_status)(struct ishtp_device *dev); | ||||
| 	void	(*sync_fw_clock)(struct ishtp_device *dev); | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct ishtp_device - ISHTP private device struct | ||||
|  */ | ||||
| struct ishtp_device { | ||||
| 	struct device *devc;	/* pointer to lowest device */ | ||||
| 	struct pci_dev *pdev;	/* PCI device to get device ids */ | ||||
| 
 | ||||
| 	/* waitq for waiting for suspend response */ | ||||
| 	wait_queue_head_t suspend_wait; | ||||
| 	bool suspend_flag;	/* Suspend is active */ | ||||
| 
 | ||||
| 	/* waitq for waiting for resume response */ | ||||
| 	wait_queue_head_t resume_wait; | ||||
| 	bool resume_flag;	/*Resume is active */ | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * lock for the device, for everything that doesn't have | ||||
| 	 * a dedicated spinlock | ||||
| 	 */ | ||||
| 	spinlock_t device_lock; | ||||
| 
 | ||||
| 	bool recvd_hw_ready; | ||||
| 	struct hbm_version version; | ||||
| 	int transfer_path; /* Choice of transfer path: IPC or DMA */ | ||||
| 
 | ||||
| 	/* ishtp device states */ | ||||
| 	enum ishtp_dev_state dev_state; | ||||
| 	enum ishtp_hbm_state hbm_state; | ||||
| 
 | ||||
| 	/* driver read queue */ | ||||
| 	struct ishtp_cl_rb read_list; | ||||
| 	spinlock_t read_list_spinlock; | ||||
| 
 | ||||
| 	/* list of ishtp_cl's */ | ||||
| 	struct list_head cl_list; | ||||
| 	spinlock_t cl_list_lock; | ||||
| 	long open_handle_count; | ||||
| 
 | ||||
| 	/* List of bus devices */ | ||||
| 	struct list_head device_list; | ||||
| 	spinlock_t device_list_lock; | ||||
| 
 | ||||
| 	/* waiting queues for receive message from FW */ | ||||
| 	wait_queue_head_t wait_hw_ready; | ||||
| 	wait_queue_head_t wait_hbm_recvd_msg; | ||||
| 
 | ||||
| 	/* FIFO for input messages for BH processing */ | ||||
| 	unsigned char rd_msg_fifo[RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE]; | ||||
| 	unsigned int rd_msg_fifo_head, rd_msg_fifo_tail; | ||||
| 	spinlock_t rd_msg_spinlock; | ||||
| 	struct work_struct bh_hbm_work; | ||||
| 
 | ||||
| 	/* IPC write queue */ | ||||
| 	struct wr_msg_ctl_info wr_processing_list_head, wr_free_list_head; | ||||
| 	/* For both processing list  and free list */ | ||||
| 	spinlock_t wr_processing_spinlock; | ||||
| 
 | ||||
| 	spinlock_t out_ipc_spinlock; | ||||
| 
 | ||||
| 	struct ishtp_fw_client *fw_clients; /*Note:memory has to be allocated*/ | ||||
| 	DECLARE_BITMAP(fw_clients_map, ISHTP_CLIENTS_MAX); | ||||
| 	DECLARE_BITMAP(host_clients_map, ISHTP_CLIENTS_MAX); | ||||
| 	uint8_t fw_clients_num; | ||||
| 	uint8_t fw_client_presentation_num; | ||||
| 	uint8_t fw_client_index; | ||||
| 	spinlock_t fw_clients_lock; | ||||
| 
 | ||||
| 	/* TX DMA buffers and slots */ | ||||
| 	int ishtp_host_dma_enabled; | ||||
| 	void *ishtp_host_dma_tx_buf; | ||||
| 	unsigned int ishtp_host_dma_tx_buf_size; | ||||
| 	uint64_t ishtp_host_dma_tx_buf_phys; | ||||
| 	int ishtp_dma_num_slots; | ||||
| 
 | ||||
| 	/* map of 4k blocks in Tx dma buf: 0-free, 1-used */ | ||||
| 	uint8_t *ishtp_dma_tx_map; | ||||
| 	spinlock_t ishtp_dma_tx_lock; | ||||
| 
 | ||||
| 	/* RX DMA buffers and slots */ | ||||
| 	void *ishtp_host_dma_rx_buf; | ||||
| 	unsigned int ishtp_host_dma_rx_buf_size; | ||||
| 	uint64_t ishtp_host_dma_rx_buf_phys; | ||||
| 
 | ||||
| 	/* Dump to trace buffers if enabled*/ | ||||
| 	void (*print_log)(struct ishtp_device *dev, char *format, ...); | ||||
| 
 | ||||
| 	/* Debug stats */ | ||||
| 	unsigned int	ipc_rx_cnt; | ||||
| 	unsigned long long	ipc_rx_bytes_cnt; | ||||
| 	unsigned int	ipc_tx_cnt; | ||||
| 	unsigned long long	ipc_tx_bytes_cnt; | ||||
| 
 | ||||
| 	const struct ishtp_hw_ops *ops; | ||||
| 	size_t	mtu; | ||||
| 	uint32_t	ishtp_msg_hdr; | ||||
| 	char hw[0] __aligned(sizeof(void *)); | ||||
| }; | ||||
| 
 | ||||
| static inline unsigned long ishtp_secs_to_jiffies(unsigned long sec) | ||||
| { | ||||
| 	return msecs_to_jiffies(sec * MSEC_PER_SEC); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Register Access Function | ||||
|  */ | ||||
| static inline int ish_ipc_reset(struct ishtp_device *dev) | ||||
| { | ||||
| 	return dev->ops->ipc_reset(dev); | ||||
| } | ||||
| 
 | ||||
| static inline int ish_hw_reset(struct ishtp_device *dev) | ||||
| { | ||||
| 	return dev->ops->hw_reset(dev); | ||||
| } | ||||
| 
 | ||||
| /* Exported function */ | ||||
| void	ishtp_device_init(struct ishtp_device *dev); | ||||
| int	ishtp_start(struct ishtp_device *dev); | ||||
| 
 | ||||
| #endif /*_ISHTP_DEV_H_*/ | ||||
| @ -76,6 +76,7 @@ static const struct hid_blacklist { | ||||
| 	{ USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K95RGB, HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL }, | ||||
| 	{ USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70RGB, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_STRAFE, HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL }, | ||||
| 	{ USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51, HID_QUIRK_NOGET }, | ||||
| 	{ USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC, HID_QUIRK_NOGET }, | ||||
| 	{ USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU, HID_QUIRK_MULTI_INPUT }, | ||||
| @ -98,6 +99,7 @@ static const struct hid_blacklist { | ||||
| 	{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_JP, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| @ -143,7 +145,7 @@ static const struct hid_blacklist { | ||||
| 	{ USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS, HID_QUIRK_MULTI_INPUT }, | ||||
| 	{ USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD, HID_QUIRK_NO_INIT_REPORTS }, | ||||
| 	{ USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X, HID_QUIRK_MULTI_INPUT }, | ||||
| 	{ USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2, HID_QUIRK_MULTI_INPUT }, | ||||
| 	{ USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2, HID_QUIRK_MULTI_INPUT }, | ||||
| 	{ USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X, HID_QUIRK_MULTI_INPUT }, | ||||
| 	{ USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912, HID_QUIRK_MULTI_INPUT }, | ||||
| 	{ USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE, HID_QUIRK_NO_INIT_REPORTS }, | ||||
|  | ||||
| @ -90,6 +90,8 @@ | ||||
| #include <linux/module.h> | ||||
| #include <linux/mod_devicetable.h> | ||||
| #include <linux/hid.h> | ||||
| #include <linux/kfifo.h> | ||||
| #include <linux/leds.h> | ||||
| #include <linux/usb/input.h> | ||||
| #include <linux/power_supply.h> | ||||
| #include <asm/unaligned.h> | ||||
| @ -105,32 +107,95 @@ | ||||
| #define USB_VENDOR_ID_WACOM	0x056a | ||||
| #define USB_VENDOR_ID_LENOVO	0x17ef | ||||
| 
 | ||||
| enum wacom_worker { | ||||
| 	WACOM_WORKER_WIRELESS, | ||||
| 	WACOM_WORKER_BATTERY, | ||||
| 	WACOM_WORKER_REMOTE, | ||||
| }; | ||||
| 
 | ||||
| struct wacom; | ||||
| 
 | ||||
| struct wacom_led { | ||||
| 	struct led_classdev cdev; | ||||
| 	struct led_trigger trigger; | ||||
| 	struct wacom *wacom; | ||||
| 	unsigned int group; | ||||
| 	unsigned int id; | ||||
| 	u8 llv; | ||||
| 	u8 hlv; | ||||
| 	bool held; | ||||
| }; | ||||
| 
 | ||||
| struct wacom_group_leds { | ||||
| 	u8 select; /* status led selector (0..3) */ | ||||
| 	struct wacom_led *leds; | ||||
| 	unsigned int count; | ||||
| 	struct device *dev; | ||||
| }; | ||||
| 
 | ||||
| struct wacom_battery { | ||||
| 	struct wacom *wacom; | ||||
| 	struct power_supply_desc bat_desc; | ||||
| 	struct power_supply *battery; | ||||
| 	char bat_name[WACOM_NAME_MAX]; | ||||
| 	int battery_capacity; | ||||
| 	int bat_charging; | ||||
| 	int bat_connected; | ||||
| 	int ps_connected; | ||||
| }; | ||||
| 
 | ||||
| struct wacom_remote { | ||||
| 	spinlock_t remote_lock; | ||||
| 	struct kfifo remote_fifo; | ||||
| 	struct kobject *remote_dir; | ||||
| 	struct { | ||||
| 		struct attribute_group group; | ||||
| 		u32 serial; | ||||
| 		struct input_dev *input; | ||||
| 		bool registered; | ||||
| 		struct wacom_battery battery; | ||||
| 	} remotes[WACOM_MAX_REMOTES]; | ||||
| }; | ||||
| 
 | ||||
| struct wacom { | ||||
| 	struct usb_device *usbdev; | ||||
| 	struct usb_interface *intf; | ||||
| 	struct wacom_wac wacom_wac; | ||||
| 	struct hid_device *hdev; | ||||
| 	struct mutex lock; | ||||
| 	struct work_struct work; | ||||
| 	struct wacom_led { | ||||
| 		u8 select[5]; /* status led selector (0..3) */ | ||||
| 	struct work_struct wireless_work; | ||||
| 	struct work_struct battery_work; | ||||
| 	struct work_struct remote_work; | ||||
| 	struct wacom_remote *remote; | ||||
| 	struct wacom_leds { | ||||
| 		struct wacom_group_leds *groups; | ||||
| 		unsigned int count; | ||||
| 		u8 llv;       /* status led brightness no button (1..127) */ | ||||
| 		u8 hlv;       /* status led brightness button pressed (1..127) */ | ||||
| 		u8 img_lum;   /* OLED matrix display brightness */ | ||||
| 		u8 max_llv;   /* maximum brightness of LED (llv) */ | ||||
| 		u8 max_hlv;   /* maximum brightness of LED (hlv) */ | ||||
| 	} led; | ||||
| 	bool led_initialized; | ||||
| 	struct power_supply *battery; | ||||
| 	struct power_supply *ac; | ||||
| 	struct power_supply_desc battery_desc; | ||||
| 	struct power_supply_desc ac_desc; | ||||
| 	struct kobject *remote_dir; | ||||
| 	struct attribute_group remote_group[5]; | ||||
| 	struct wacom_battery battery; | ||||
| 	bool resources; | ||||
| }; | ||||
| 
 | ||||
| static inline void wacom_schedule_work(struct wacom_wac *wacom_wac) | ||||
| static inline void wacom_schedule_work(struct wacom_wac *wacom_wac, | ||||
| 				       enum wacom_worker which) | ||||
| { | ||||
| 	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); | ||||
| 	schedule_work(&wacom->work); | ||||
| 
 | ||||
| 	switch (which) { | ||||
| 	case WACOM_WORKER_WIRELESS: | ||||
| 		schedule_work(&wacom->wireless_work); | ||||
| 		break; | ||||
| 	case WACOM_WORKER_BATTERY: | ||||
| 		schedule_work(&wacom->battery_work); | ||||
| 		break; | ||||
| 	case WACOM_WORKER_REMOTE: | ||||
| 		schedule_work(&wacom->remote_work); | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| extern const struct hid_device_id wacom_ids[]; | ||||
| @ -149,7 +214,8 @@ int wacom_wac_event(struct hid_device *hdev, struct hid_field *field, | ||||
| 		struct hid_usage *usage, __s32 value); | ||||
| void wacom_wac_report(struct hid_device *hdev, struct hid_report *report); | ||||
| void wacom_battery_work(struct work_struct *work); | ||||
| int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial, | ||||
| 				   int index); | ||||
| void wacom_remote_destroy_attr_group(struct wacom *wacom, __u32 serial); | ||||
| enum led_brightness wacom_leds_brightness_get(struct wacom_led *led); | ||||
| struct wacom_led *wacom_led_find(struct wacom *wacom, unsigned int group, | ||||
| 				 unsigned int id); | ||||
| struct wacom_led *wacom_led_next(struct wacom *wacom, struct wacom_led *cur); | ||||
| #endif | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -34,6 +34,10 @@ | ||||
|  */ | ||||
| #define WACOM_CONTACT_AREA_SCALE 2607 | ||||
| 
 | ||||
| static bool touch_arbitration = 1; | ||||
| module_param(touch_arbitration, bool, 0644); | ||||
| MODULE_PARM_DESC(touch_arbitration, " on (Y) off (N)"); | ||||
| 
 | ||||
| static void wacom_report_numbered_buttons(struct input_dev *input_dev, | ||||
| 				int button_count, int mask); | ||||
| 
 | ||||
| @ -48,25 +52,34 @@ static unsigned short batcap_gr[8] = { 1, 15, 25, 35, 50, 70, 100, 100 }; | ||||
|  */ | ||||
| static unsigned short batcap_i4[8] = { 1, 15, 30, 45, 60, 70, 85, 100 }; | ||||
| 
 | ||||
| static void __wacom_notify_battery(struct wacom_battery *battery, | ||||
| 				   int bat_capacity, bool bat_charging, | ||||
| 				   bool bat_connected, bool ps_connected) | ||||
| { | ||||
| 	bool changed = battery->battery_capacity != bat_capacity  || | ||||
| 		       battery->bat_charging     != bat_charging  || | ||||
| 		       battery->bat_connected    != bat_connected || | ||||
| 		       battery->ps_connected     != ps_connected; | ||||
| 
 | ||||
| 	if (changed) { | ||||
| 		battery->battery_capacity = bat_capacity; | ||||
| 		battery->bat_charging = bat_charging; | ||||
| 		battery->bat_connected = bat_connected; | ||||
| 		battery->ps_connected = ps_connected; | ||||
| 
 | ||||
| 		if (battery->battery) | ||||
| 			power_supply_changed(battery->battery); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void wacom_notify_battery(struct wacom_wac *wacom_wac, | ||||
| 	int bat_capacity, bool bat_charging, bool bat_connected, | ||||
| 	bool ps_connected) | ||||
| { | ||||
| 	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); | ||||
| 	bool changed = wacom_wac->battery_capacity != bat_capacity  || | ||||
| 		       wacom_wac->bat_charging     != bat_charging  || | ||||
| 		       wacom_wac->bat_connected    != bat_connected || | ||||
| 		       wacom_wac->ps_connected     != ps_connected; | ||||
| 
 | ||||
| 	if (changed) { | ||||
| 		wacom_wac->battery_capacity = bat_capacity; | ||||
| 		wacom_wac->bat_charging = bat_charging; | ||||
| 		wacom_wac->bat_connected = bat_connected; | ||||
| 		wacom_wac->ps_connected = ps_connected; | ||||
| 
 | ||||
| 		if (wacom->battery) | ||||
| 			power_supply_changed(wacom->battery); | ||||
| 	} | ||||
| 	__wacom_notify_battery(&wacom->battery, bat_capacity, bat_charging, | ||||
| 			       bat_connected, ps_connected); | ||||
| } | ||||
| 
 | ||||
| static int wacom_penpartner_irq(struct wacom_wac *wacom) | ||||
| @ -751,22 +764,37 @@ static int wacom_intuos_inout(struct wacom_wac *wacom) | ||||
| static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) | ||||
| { | ||||
| 	unsigned char *data = wacom_wac->data; | ||||
| 	struct input_dev *input = wacom_wac->pad_input; | ||||
| 	struct input_dev *input; | ||||
| 	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); | ||||
| 	struct wacom_features *features = &wacom_wac->features; | ||||
| 	struct wacom_remote *remote = wacom->remote; | ||||
| 	int bat_charging, bat_percent, touch_ring_mode; | ||||
| 	__u32 serial; | ||||
| 	int i; | ||||
| 	int i, index = -1; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	if (data[0] != WACOM_REPORT_REMOTE) { | ||||
| 		dev_dbg(input->dev.parent, | ||||
| 			"%s: received unknown report #%d", __func__, data[0]); | ||||
| 		hid_dbg(wacom->hdev, "%s: received unknown report #%d", | ||||
| 			__func__, data[0]); | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	serial = data[3] + (data[4] << 8) + (data[5] << 16); | ||||
| 	wacom_wac->id[0] = PAD_DEVICE_ID; | ||||
| 
 | ||||
| 	spin_lock_irqsave(&remote->remote_lock, flags); | ||||
| 
 | ||||
| 	for (i = 0; i < WACOM_MAX_REMOTES; i++) { | ||||
| 		if (remote->remotes[i].serial == serial) { | ||||
| 			index = i; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (index < 0 || !remote->remotes[index].registered) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	input = remote->remotes[index].input; | ||||
| 
 | ||||
| 	input_report_key(input, BTN_0, (data[9] & 0x01)); | ||||
| 	input_report_key(input, BTN_1, (data[9] & 0x02)); | ||||
| 	input_report_key(input, BTN_2, (data[9] & 0x04)); | ||||
| @ -803,73 +831,69 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) | ||||
| 
 | ||||
| 	input_event(input, EV_MSC, MSC_SERIAL, serial); | ||||
| 
 | ||||
| 	input_sync(input); | ||||
| 
 | ||||
| 	/*Which mode select (LED light) is currently on?*/ | ||||
| 	touch_ring_mode = (data[11] & 0xC0) >> 6; | ||||
| 
 | ||||
| 	for (i = 0; i < WACOM_MAX_REMOTES; i++) { | ||||
| 		if (wacom_wac->serial[i] == serial) | ||||
| 			wacom->led.select[i] = touch_ring_mode; | ||||
| 		if (remote->remotes[i].serial == serial) | ||||
| 			wacom->led.groups[i].select = touch_ring_mode; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!wacom->battery && | ||||
| 	    !(features->quirks & WACOM_QUIRK_BATTERY)) { | ||||
| 		features->quirks |= WACOM_QUIRK_BATTERY; | ||||
| 		INIT_WORK(&wacom->work, wacom_battery_work); | ||||
| 		wacom_schedule_work(wacom_wac); | ||||
| 	} | ||||
| 	__wacom_notify_battery(&remote->remotes[index].battery, bat_percent, | ||||
| 				bat_charging, 1, bat_charging); | ||||
| 
 | ||||
| 	wacom_notify_battery(wacom_wac, bat_percent, bat_charging, 1, | ||||
| 			     bat_charging); | ||||
| 
 | ||||
| 	return 1; | ||||
| out: | ||||
| 	spin_unlock_irqrestore(&remote->remote_lock, flags); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) | ||||
| static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) | ||||
| { | ||||
| 	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); | ||||
| 	unsigned char *data = wacom_wac->data; | ||||
| 	int i; | ||||
| 	struct wacom_remote *remote = wacom->remote; | ||||
| 	struct wacom_remote_data remote_data; | ||||
| 	unsigned long flags; | ||||
| 	int i, ret; | ||||
| 
 | ||||
| 	if (data[0] != WACOM_REPORT_DEVICE_LIST) | ||||
| 		return 0; | ||||
| 		return; | ||||
| 
 | ||||
| 	memset(&remote_data, 0, sizeof(struct wacom_remote_data)); | ||||
| 
 | ||||
| 	for (i = 0; i < WACOM_MAX_REMOTES; i++) { | ||||
| 		int j = i * 6; | ||||
| 		int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4]; | ||||
| 		bool connected = data[j+2]; | ||||
| 
 | ||||
| 		if (connected) { | ||||
| 			int k; | ||||
| 
 | ||||
| 			if (wacom_wac->serial[i] == serial) | ||||
| 				continue; | ||||
| 
 | ||||
| 			if (wacom_wac->serial[i]) { | ||||
| 				wacom_remote_destroy_attr_group(wacom, | ||||
| 							wacom_wac->serial[i]); | ||||
| 			} | ||||
| 
 | ||||
| 			/* A remote can pair more than once with an EKR,
 | ||||
| 			 * check to make sure this serial isn't already paired. | ||||
| 			 */ | ||||
| 			for (k = 0; k < WACOM_MAX_REMOTES; k++) { | ||||
| 				if (wacom_wac->serial[k] == serial) | ||||
| 					break; | ||||
| 			} | ||||
| 
 | ||||
| 			if (k < WACOM_MAX_REMOTES) { | ||||
| 				wacom_wac->serial[i] = serial; | ||||
| 				continue; | ||||
| 			} | ||||
| 			wacom_remote_create_attr_group(wacom, serial, i); | ||||
| 
 | ||||
| 		} else if (wacom_wac->serial[i]) { | ||||
| 			wacom_remote_destroy_attr_group(wacom, | ||||
| 							wacom_wac->serial[i]); | ||||
| 		} | ||||
| 		remote_data.remote[i].serial = serial; | ||||
| 		remote_data.remote[i].connected = connected; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| 	spin_lock_irqsave(&remote->remote_lock, flags); | ||||
| 
 | ||||
| 	ret = kfifo_in(&remote->remote_fifo, &remote_data, sizeof(remote_data)); | ||||
| 	if (ret != sizeof(remote_data)) { | ||||
| 		spin_unlock_irqrestore(&remote->remote_lock, flags); | ||||
| 		hid_err(wacom->hdev, "Can't queue Remote status event.\n"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	spin_unlock_irqrestore(&remote->remote_lock, flags); | ||||
| 
 | ||||
| 	wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE); | ||||
| } | ||||
| 
 | ||||
| static inline bool report_touch_events(struct wacom_wac *wacom) | ||||
| { | ||||
| 	return (touch_arbitration ? !wacom->shared->stylus_in_proximity : 1); | ||||
| } | ||||
| 
 | ||||
| static inline bool delay_pen_events(struct wacom_wac *wacom) | ||||
| { | ||||
| 	return (wacom->shared->touch_down && touch_arbitration); | ||||
| } | ||||
| 
 | ||||
| static int wacom_intuos_general(struct wacom_wac *wacom) | ||||
| @ -885,7 +909,7 @@ static int wacom_intuos_general(struct wacom_wac *wacom) | ||||
| 		data[0] != WACOM_REPORT_INTUOS_PEN) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (wacom->shared->touch_down) | ||||
| 	if (delay_pen_events(wacom)) | ||||
| 		return 1; | ||||
| 
 | ||||
| 	/* don't report events if we don't know the tool ID */ | ||||
| @ -1145,7 +1169,7 @@ static int wacom_wac_finger_count_touches(struct wacom_wac *wacom) | ||||
| 
 | ||||
| 	if (touch_max == 1) | ||||
| 		return test_bit(BTN_TOUCH, input->key) && | ||||
| 		       !wacom->shared->stylus_in_proximity; | ||||
| 			report_touch_events(wacom); | ||||
| 
 | ||||
| 	for (i = 0; i < input->mt->num_slots; i++) { | ||||
| 		struct input_mt_slot *ps = &input->mt->slots[i]; | ||||
| @ -1186,7 +1210,7 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom) | ||||
| 
 | ||||
| 	for (i = 0; i < contacts_to_send; i++) { | ||||
| 		int offset = (byte_per_packet * i) + 1; | ||||
| 		bool touch = (data[offset] & 0x1) && !wacom->shared->stylus_in_proximity; | ||||
| 		bool touch = (data[offset] & 0x1) && report_touch_events(wacom); | ||||
| 		int slot = input_mt_get_slot_by_key(input, data[offset + 1]); | ||||
| 
 | ||||
| 		if (slot < 0) | ||||
| @ -1250,7 +1274,7 @@ static int wacom_mt_touch(struct wacom_wac *wacom) | ||||
| 
 | ||||
| 	for (i = 0; i < contacts_to_send; i++) { | ||||
| 		int offset = (WACOM_BYTES_PER_MT_PACKET + x_offset) * i + 3; | ||||
| 		bool touch = (data[offset] & 0x1) && !wacom->shared->stylus_in_proximity; | ||||
| 		bool touch = (data[offset] & 0x1) && report_touch_events(wacom); | ||||
| 		int id = get_unaligned_le16(&data[offset + 1]); | ||||
| 		int slot = input_mt_get_slot_by_key(input, id); | ||||
| 
 | ||||
| @ -1284,7 +1308,7 @@ static int wacom_tpc_mt_touch(struct wacom_wac *wacom) | ||||
| 
 | ||||
| 	for (i = 0; i < 2; i++) { | ||||
| 		int p = data[1] & (1 << i); | ||||
| 		bool touch = p && !wacom->shared->stylus_in_proximity; | ||||
| 		bool touch = p && report_touch_events(wacom); | ||||
| 
 | ||||
| 		input_mt_slot(input, i); | ||||
| 		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); | ||||
| @ -1308,7 +1332,7 @@ static int wacom_tpc_single_touch(struct wacom_wac *wacom, size_t len) | ||||
| { | ||||
| 	unsigned char *data = wacom->data; | ||||
| 	struct input_dev *input = wacom->touch_input; | ||||
| 	bool prox = !wacom->shared->stylus_in_proximity; | ||||
| 	bool prox = report_touch_events(wacom); | ||||
| 	int x = 0, y = 0; | ||||
| 
 | ||||
| 	if (wacom->features.touch_max > 1 || len > WACOM_PKGLEN_TPC2FG) | ||||
| @ -1353,8 +1377,10 @@ static int wacom_tpc_pen(struct wacom_wac *wacom) | ||||
| 	/* keep pen state for touch events */ | ||||
| 	wacom->shared->stylus_in_proximity = prox; | ||||
| 
 | ||||
| 	/* send pen events only when touch is up or forced out */ | ||||
| 	if (!wacom->shared->touch_down) { | ||||
| 	/* send pen events only when touch is up or forced out
 | ||||
| 	 * or touch arbitration is off | ||||
| 	 */ | ||||
| 	if (!delay_pen_events(wacom)) { | ||||
| 		input_report_key(input, BTN_STYLUS, data[1] & 0x02); | ||||
| 		input_report_key(input, BTN_STYLUS2, data[1] & 0x10); | ||||
| 		input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2])); | ||||
| @ -1496,8 +1522,10 @@ static int wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field, | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	/* send pen events only when touch is up or forced out */ | ||||
| 	if (!usage->type || wacom_wac->shared->touch_down) | ||||
| 	/* send pen events only when touch is up or forced out
 | ||||
| 	 * or touch arbitration is off | ||||
| 	 */ | ||||
| 	if (!usage->type || delay_pen_events(wacom_wac)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	input_event(input, usage->type, usage->code, value); | ||||
| @ -1527,8 +1555,7 @@ static void wacom_wac_pen_report(struct hid_device *hdev, | ||||
| 	/* keep pen state for touch events */ | ||||
| 	wacom_wac->shared->stylus_in_proximity = prox; | ||||
| 
 | ||||
| 	/* send pen events only when touch is up or forced out */ | ||||
| 	if (!wacom_wac->shared->touch_down) { | ||||
| 	if (!delay_pen_events(wacom_wac)) { | ||||
| 		input_report_key(input, BTN_TOUCH, | ||||
| 				wacom_wac->hid_data.tipswitch); | ||||
| 		input_report_key(input, wacom_wac->tool[0], prox); | ||||
| @ -1544,13 +1571,11 @@ static void wacom_wac_finger_usage_mapping(struct hid_device *hdev, | ||||
| { | ||||
| 	struct wacom *wacom = hid_get_drvdata(hdev); | ||||
| 	struct wacom_wac *wacom_wac = &wacom->wacom_wac; | ||||
| 	struct wacom_features *features = &wacom_wac->features; | ||||
| 	struct input_dev *input = wacom_wac->touch_input; | ||||
| 	unsigned touch_max = wacom_wac->features.touch_max; | ||||
| 
 | ||||
| 	switch (usage->hid) { | ||||
| 	case HID_GD_X: | ||||
| 		features->last_slot_field = usage->hid; | ||||
| 		if (touch_max == 1) | ||||
| 			wacom_map_usage(input, usage, field, EV_ABS, ABS_X, 4); | ||||
| 		else | ||||
| @ -1558,7 +1583,6 @@ static void wacom_wac_finger_usage_mapping(struct hid_device *hdev, | ||||
| 					ABS_MT_POSITION_X, 4); | ||||
| 		break; | ||||
| 	case HID_GD_Y: | ||||
| 		features->last_slot_field = usage->hid; | ||||
| 		if (touch_max == 1) | ||||
| 			wacom_map_usage(input, usage, field, EV_ABS, ABS_Y, 4); | ||||
| 		else | ||||
| @ -1567,22 +1591,11 @@ static void wacom_wac_finger_usage_mapping(struct hid_device *hdev, | ||||
| 		break; | ||||
| 	case HID_DG_WIDTH: | ||||
| 	case HID_DG_HEIGHT: | ||||
| 		features->last_slot_field = usage->hid; | ||||
| 		wacom_map_usage(input, usage, field, EV_ABS, ABS_MT_TOUCH_MAJOR, 0); | ||||
| 		wacom_map_usage(input, usage, field, EV_ABS, ABS_MT_TOUCH_MINOR, 0); | ||||
| 		input_set_abs_params(input, ABS_MT_ORIENTATION, 0, 1, 0, 0); | ||||
| 		break; | ||||
| 	case HID_DG_CONTACTID: | ||||
| 		features->last_slot_field = usage->hid; | ||||
| 		break; | ||||
| 	case HID_DG_INRANGE: | ||||
| 		features->last_slot_field = usage->hid; | ||||
| 		break; | ||||
| 	case HID_DG_INVERT: | ||||
| 		features->last_slot_field = usage->hid; | ||||
| 		break; | ||||
| 	case HID_DG_TIPSWITCH: | ||||
| 		features->last_slot_field = usage->hid; | ||||
| 		wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0); | ||||
| 		break; | ||||
| 	case HID_DG_CONTACTCOUNT: | ||||
| @ -1599,7 +1612,7 @@ static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac, | ||||
| 	struct hid_data *hid_data = &wacom_wac->hid_data; | ||||
| 	bool mt = wacom_wac->features.touch_max > 1; | ||||
| 	bool prox = hid_data->tipswitch && | ||||
| 		    !wacom_wac->shared->stylus_in_proximity; | ||||
| 		    report_touch_events(wacom_wac); | ||||
| 
 | ||||
| 	wacom_wac->hid_data.num_received++; | ||||
| 	if (wacom_wac->hid_data.num_received > wacom_wac->hid_data.num_expected) | ||||
| @ -1660,7 +1673,7 @@ static int wacom_wac_finger_event(struct hid_device *hdev, | ||||
| 
 | ||||
| 
 | ||||
| 	if (usage->usage_index + 1 == field->report_count) { | ||||
| 		if (usage->hid == wacom_wac->features.last_slot_field) | ||||
| 		if (usage->hid == wacom_wac->hid_data.last_slot_field) | ||||
| 			wacom_wac_finger_slot(wacom_wac, wacom_wac->touch_input); | ||||
| 	} | ||||
| 
 | ||||
| @ -1673,31 +1686,35 @@ static void wacom_wac_finger_pre_report(struct hid_device *hdev, | ||||
| 	struct wacom *wacom = hid_get_drvdata(hdev); | ||||
| 	struct wacom_wac *wacom_wac = &wacom->wacom_wac; | ||||
| 	struct hid_data* hid_data = &wacom_wac->hid_data; | ||||
| 	int i; | ||||
| 
 | ||||
| 	if (hid_data->cc_report != 0 && | ||||
| 	    hid_data->cc_report != report->id) { | ||||
| 		int i; | ||||
| 	for (i = 0; i < report->maxfield; i++) { | ||||
| 		struct hid_field *field = report->field[i]; | ||||
| 		int j; | ||||
| 
 | ||||
| 		hid_data->cc_report = report->id; | ||||
| 		hid_data->cc_index = -1; | ||||
| 		hid_data->cc_value_index = -1; | ||||
| 		for (j = 0; j < field->maxusage; j++) { | ||||
| 			struct hid_usage *usage = &field->usage[j]; | ||||
| 
 | ||||
| 		for (i = 0; i < report->maxfield; i++) { | ||||
| 			struct hid_field *field = report->field[i]; | ||||
| 			int j; | ||||
| 
 | ||||
| 			for (j = 0; j < field->maxusage; j++) { | ||||
| 				if (field->usage[j].hid == HID_DG_CONTACTCOUNT) { | ||||
| 					hid_data->cc_index = i; | ||||
| 					hid_data->cc_value_index = j; | ||||
| 
 | ||||
| 					/* break */ | ||||
| 					i = report->maxfield; | ||||
| 					j = field->maxusage; | ||||
| 				} | ||||
| 			switch (usage->hid) { | ||||
| 			case HID_GD_X: | ||||
| 			case HID_GD_Y: | ||||
| 			case HID_DG_WIDTH: | ||||
| 			case HID_DG_HEIGHT: | ||||
| 			case HID_DG_CONTACTID: | ||||
| 			case HID_DG_INRANGE: | ||||
| 			case HID_DG_INVERT: | ||||
| 			case HID_DG_TIPSWITCH: | ||||
| 				hid_data->last_slot_field = usage->hid; | ||||
| 				break; | ||||
| 			case HID_DG_CONTACTCOUNT: | ||||
| 				hid_data->cc_report = report->id; | ||||
| 				hid_data->cc_index = i; | ||||
| 				hid_data->cc_value_index = j; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (hid_data->cc_report != 0 && | ||||
| 	    hid_data->cc_index >= 0) { | ||||
| 		struct hid_field *field = report->field[hid_data->cc_index]; | ||||
| @ -1740,10 +1757,10 @@ void wacom_wac_usage_mapping(struct hid_device *hdev, | ||||
| { | ||||
| 	struct wacom *wacom = hid_get_drvdata(hdev); | ||||
| 	struct wacom_wac *wacom_wac = &wacom->wacom_wac; | ||||
| 	struct wacom_features *features = &wacom_wac->features; | ||||
| 
 | ||||
| 	/* currently, only direct devices have proper hid report descriptors */ | ||||
| 	__set_bit(INPUT_PROP_DIRECT, wacom_wac->pen_input->propbit); | ||||
| 	__set_bit(INPUT_PROP_DIRECT, wacom_wac->touch_input->propbit); | ||||
| 	features->device_type |= WACOM_DEVICETYPE_DIRECT; | ||||
| 
 | ||||
| 	if (WACOM_PEN_FIELD(field)) | ||||
| 		return wacom_wac_pen_usage_mapping(hdev, field, usage); | ||||
| @ -1825,15 +1842,8 @@ static int wacom_bpt_touch(struct wacom_wac *wacom) | ||||
| 
 | ||||
| 	for (i = 0; i < 2; i++) { | ||||
| 		int offset = (data[1] & 0x80) ? (8 * i) : (9 * i); | ||||
| 		bool touch = data[offset + 3] & 0x80; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Touch events need to be disabled while stylus is | ||||
| 		 * in proximity because user's hand is resting on touchpad | ||||
| 		 * and sending unwanted events.  User expects tablet buttons | ||||
| 		 * to continue working though. | ||||
| 		 */ | ||||
| 		touch = touch && !wacom->shared->stylus_in_proximity; | ||||
| 		bool touch = report_touch_events(wacom) | ||||
| 			   && (data[offset + 3] & 0x80); | ||||
| 
 | ||||
| 		input_mt_slot(input, i); | ||||
| 		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); | ||||
| @ -1870,7 +1880,7 @@ static void wacom_bpt3_touch_msg(struct wacom_wac *wacom, unsigned char *data) | ||||
| 	if (slot < 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	touch = touch && !wacom->shared->stylus_in_proximity; | ||||
| 	touch = touch && report_touch_events(wacom); | ||||
| 
 | ||||
| 	input_mt_slot(input, slot); | ||||
| 	input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); | ||||
| @ -1942,7 +1952,7 @@ static int wacom_bpt3_touch(struct wacom_wac *wacom) | ||||
| 	} | ||||
| 
 | ||||
| 	/* only update touch if we actually have a touchpad and touch data changed */ | ||||
| 	if (wacom->touch_registered && touch_changed) { | ||||
| 	if (wacom->touch_input && touch_changed) { | ||||
| 		input_mt_sync_frame(wacom->touch_input); | ||||
| 		wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom); | ||||
| 	} | ||||
| @ -1983,7 +1993,7 @@ static int wacom_bpt_pen(struct wacom_wac *wacom) | ||||
| 	} | ||||
| 
 | ||||
| 	wacom->shared->stylus_in_proximity = prox; | ||||
| 	if (wacom->shared->touch_down) | ||||
| 	if (delay_pen_events(wacom)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (prox) { | ||||
| @ -2077,7 +2087,7 @@ static int wacom_bamboo_pad_touch_event(struct wacom_wac *wacom, | ||||
| 
 | ||||
| 	for (id = 0; id < wacom->features.touch_max; id++) { | ||||
| 		valid = !!(prefix & BIT(id)) && | ||||
| 			!wacom->shared->stylus_in_proximity; | ||||
| 			report_touch_events(wacom); | ||||
| 
 | ||||
| 		input_mt_slot(input, id); | ||||
| 		input_mt_report_slot_state(input, MT_TOOL_FINGER, valid); | ||||
| @ -2099,8 +2109,7 @@ static int wacom_bamboo_pad_touch_event(struct wacom_wac *wacom, | ||||
| 	input_report_key(input, BTN_RIGHT, prefix & 0x80); | ||||
| 
 | ||||
| 	/* keep touch state for pen event */ | ||||
| 	wacom->shared->touch_down = !!prefix && | ||||
| 				    !wacom->shared->stylus_in_proximity; | ||||
| 	wacom->shared->touch_down = !!prefix && report_touch_events(wacom); | ||||
| 
 | ||||
| 	return 1; | ||||
| } | ||||
| @ -2149,16 +2158,15 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len) | ||||
| 		charging = !!(data[5] & 0x80); | ||||
| 		if (wacom->pid != pid) { | ||||
| 			wacom->pid = pid; | ||||
| 			wacom_schedule_work(wacom); | ||||
| 			wacom_schedule_work(wacom, WACOM_WORKER_WIRELESS); | ||||
| 		} | ||||
| 
 | ||||
| 		if (wacom->shared->type) | ||||
| 			wacom_notify_battery(wacom, battery, charging, 1, 0); | ||||
| 		wacom_notify_battery(wacom, battery, charging, 1, 0); | ||||
| 
 | ||||
| 	} else if (wacom->pid != 0) { | ||||
| 		/* disconnected while previously connected */ | ||||
| 		wacom->pid = 0; | ||||
| 		wacom_schedule_work(wacom); | ||||
| 		wacom_schedule_work(wacom, WACOM_WORKER_WIRELESS); | ||||
| 		wacom_notify_battery(wacom, 0, 0, 0, 0); | ||||
| 	} | ||||
| 
 | ||||
| @ -2190,18 +2198,16 @@ static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len) | ||||
| 		wacom_notify_battery(wacom_wac, battery, charging, | ||||
| 				     battery || charging, 1); | ||||
| 
 | ||||
| 		if (!wacom->battery && | ||||
| 		if (!wacom->battery.battery && | ||||
| 		    !(features->quirks & WACOM_QUIRK_BATTERY)) { | ||||
| 			features->quirks |= WACOM_QUIRK_BATTERY; | ||||
| 			INIT_WORK(&wacom->work, wacom_battery_work); | ||||
| 			wacom_schedule_work(wacom_wac); | ||||
| 			wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY); | ||||
| 		} | ||||
| 	} | ||||
| 	else if ((features->quirks & WACOM_QUIRK_BATTERY) && | ||||
| 		 wacom->battery) { | ||||
| 		 wacom->battery.battery) { | ||||
| 		features->quirks &= ~WACOM_QUIRK_BATTERY; | ||||
| 		INIT_WORK(&wacom->work, wacom_battery_work); | ||||
| 		wacom_schedule_work(wacom_wac); | ||||
| 		wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY); | ||||
| 		wacom_notify_battery(wacom_wac, 0, 0, 0, 0); | ||||
| 	} | ||||
| 	return 0; | ||||
| @ -2312,8 +2318,9 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len) | ||||
| 		break; | ||||
| 
 | ||||
| 	case REMOTE: | ||||
| 		sync = false; | ||||
| 		if (wacom_wac->data[0] == WACOM_REPORT_DEVICE_LIST) | ||||
| 			sync = wacom_remote_status_irq(wacom_wac, len); | ||||
| 			wacom_remote_status_irq(wacom_wac, len); | ||||
| 		else | ||||
| 			sync = wacom_remote_irq(wacom_wac, len); | ||||
| 		break; | ||||
| @ -2451,6 +2458,33 @@ void wacom_setup_device_quirks(struct wacom *wacom) | ||||
| 	if (features->type == REMOTE) | ||||
| 		features->device_type = WACOM_DEVICETYPE_PAD; | ||||
| 
 | ||||
| 	switch (features->type) { | ||||
| 	case PL: | ||||
| 	case DTU: | ||||
| 	case DTUS: | ||||
| 	case DTUSX: | ||||
| 	case WACOM_21UX2: | ||||
| 	case WACOM_22HD: | ||||
| 	case DTK: | ||||
| 	case WACOM_24HD: | ||||
| 	case WACOM_27QHD: | ||||
| 	case CINTIQ_HYBRID: | ||||
| 	case CINTIQ_COMPANION_2: | ||||
| 	case CINTIQ: | ||||
| 	case WACOM_BEE: | ||||
| 	case WACOM_13HD: | ||||
| 	case WACOM_24HDT: | ||||
| 	case WACOM_27QHDT: | ||||
| 	case TABLETPC: | ||||
| 	case TABLETPCE: | ||||
| 	case TABLETPC2FG: | ||||
| 	case MTSCREEN: | ||||
| 	case MTTPC: | ||||
| 	case MTTPC_B: | ||||
| 		features->device_type |= WACOM_DEVICETYPE_DIRECT; | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	if (wacom->hdev->bus == BUS_BLUETOOTH) | ||||
| 		features->quirks |= WACOM_QUIRK_BATTERY; | ||||
| 
 | ||||
| @ -2469,6 +2503,9 @@ void wacom_setup_device_quirks(struct wacom *wacom) | ||||
| 			features->quirks |= WACOM_QUIRK_BATTERY; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (features->type == REMOTE) | ||||
| 		features->device_type |= WACOM_DEVICETYPE_WL_MONITOR; | ||||
| } | ||||
| 
 | ||||
| int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| @ -2481,6 +2518,11 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| 	if (!(features->device_type & WACOM_DEVICETYPE_PEN)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	if (features->device_type & WACOM_DEVICETYPE_DIRECT) | ||||
| 		__set_bit(INPUT_PROP_DIRECT, input_dev->propbit); | ||||
| 	else | ||||
| 		__set_bit(INPUT_PROP_POINTER, input_dev->propbit); | ||||
| 
 | ||||
| 	if (features->type == HID_GENERIC) | ||||
| 		/* setup has already been done */ | ||||
| 		return 0; | ||||
| @ -2499,7 +2541,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| 	input_abs_set_res(input_dev, ABS_X, features->x_resolution); | ||||
| 	input_abs_set_res(input_dev, ABS_Y, features->y_resolution); | ||||
| 
 | ||||
| 
 | ||||
| 	switch (features->type) { | ||||
| 	case GRAPHIRE_BT: | ||||
| 		__clear_bit(ABS_MISC, input_dev->absbit); | ||||
| @ -2523,8 +2564,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| 		__set_bit(BTN_TOOL_MOUSE, input_dev->keybit); | ||||
| 		__set_bit(BTN_STYLUS, input_dev->keybit); | ||||
| 		__set_bit(BTN_STYLUS2, input_dev->keybit); | ||||
| 
 | ||||
| 		__set_bit(INPUT_PROP_POINTER, input_dev->propbit); | ||||
| 		break; | ||||
| 
 | ||||
| 	case WACOM_27QHD: | ||||
| @ -2539,7 +2578,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| 	case CINTIQ_COMPANION_2: | ||||
| 		input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0); | ||||
| 		input_abs_set_res(input_dev, ABS_Z, 287); | ||||
| 		__set_bit(INPUT_PROP_DIRECT, input_dev->propbit); | ||||
| 		wacom_setup_cintiq(wacom_wac); | ||||
| 		break; | ||||
| 
 | ||||
| @ -2555,8 +2593,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| 		/* fall through */ | ||||
| 
 | ||||
| 	case INTUOS: | ||||
| 		__set_bit(INPUT_PROP_POINTER, input_dev->propbit); | ||||
| 
 | ||||
| 		wacom_setup_intuos(wacom_wac); | ||||
| 		break; | ||||
| 
 | ||||
| @ -2566,8 +2602,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| 	case INTUOSPL: | ||||
| 	case INTUOS5S: | ||||
| 	case INTUOSPS: | ||||
| 		__set_bit(INPUT_PROP_POINTER, input_dev->propbit); | ||||
| 
 | ||||
| 		input_set_abs_params(input_dev, ABS_DISTANCE, 0, | ||||
| 				      features->distance_max, | ||||
| 				      features->distance_fuzz, 0); | ||||
| @ -2597,8 +2631,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| 		__set_bit(BTN_TOOL_RUBBER, input_dev->keybit); | ||||
| 		__set_bit(BTN_STYLUS, input_dev->keybit); | ||||
| 		__set_bit(BTN_STYLUS2, input_dev->keybit); | ||||
| 
 | ||||
| 		__set_bit(INPUT_PROP_DIRECT, input_dev->propbit); | ||||
| 		break; | ||||
| 
 | ||||
| 	case PTU: | ||||
| @ -2609,16 +2641,12 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev, | ||||
| 		__set_bit(BTN_TOOL_PEN, input_dev->keybit); | ||||
| 		__set_bit(BTN_TOOL_RUBBER, input_dev->keybit); | ||||
| 		__set_bit(BTN_STYLUS, input_dev->keybit); | ||||
| 
 | ||||
| 		__set_bit(INPUT_PROP_POINTER, input_dev->propbit); | ||||
| 		break; | ||||
| 
 | ||||
| 	case INTUOSHT: | ||||
| 	case BAMBOO_PT: | ||||
| 	case BAMBOO_PEN: | ||||
| 	case INTUOSHT2: | ||||
| 		__set_bit(INPUT_PROP_POINTER, input_dev->propbit); | ||||
| 
 | ||||
| 		if (features->type == INTUOSHT2) { | ||||
| 			wacom_setup_basic_pro_pen(wacom_wac); | ||||
| 		} else { | ||||
| @ -2649,6 +2677,11 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev, | ||||
| 	if (!(features->device_type & WACOM_DEVICETYPE_TOUCH)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	if (features->device_type & WACOM_DEVICETYPE_DIRECT) | ||||
| 		__set_bit(INPUT_PROP_DIRECT, input_dev->propbit); | ||||
| 	else | ||||
| 		__set_bit(INPUT_PROP_POINTER, input_dev->propbit); | ||||
| 
 | ||||
| 	if (features->type == HID_GENERIC) | ||||
| 		/* setup has already been done */ | ||||
| 		return 0; | ||||
| @ -2683,8 +2716,6 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev, | ||||
| 	case INTUOSPL: | ||||
| 	case INTUOS5S: | ||||
| 	case INTUOSPS: | ||||
| 		__set_bit(INPUT_PROP_POINTER, input_dev->propbit); | ||||
| 
 | ||||
| 		input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, features->x_max, 0, 0); | ||||
| 		input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, features->y_max, 0, 0); | ||||
| 		input_mt_init_slots(input_dev, features->touch_max, INPUT_MT_POINTER); | ||||
| @ -2707,7 +2738,6 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev, | ||||
| 
 | ||||
| 	case TABLETPC: | ||||
| 	case TABLETPCE: | ||||
| 		__set_bit(INPUT_PROP_DIRECT, input_dev->propbit); | ||||
| 		break; | ||||
| 
 | ||||
| 	case INTUOSHT: | ||||
| @ -2752,11 +2782,105 @@ static void wacom_setup_numbered_buttons(struct input_dev *input_dev, | ||||
| 		__set_bit(BTN_BASE + (i-16), input_dev->keybit); | ||||
| } | ||||
| 
 | ||||
| static void wacom_24hd_update_leds(struct wacom *wacom, int mask, int group) | ||||
| { | ||||
| 	struct wacom_led *led; | ||||
| 	int i; | ||||
| 	bool updated = false; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * 24HD has LED group 1 to the left and LED group 0 to the right. | ||||
| 	 * So group 0 matches the second half of the buttons and thus the mask | ||||
| 	 * needs to be shifted. | ||||
| 	 */ | ||||
| 	if (group == 0) | ||||
| 		mask >>= 8; | ||||
| 
 | ||||
| 	for (i = 0; i < 3; i++) { | ||||
| 		led = wacom_led_find(wacom, group, i); | ||||
| 		if (!led) { | ||||
| 			hid_err(wacom->hdev, "can't find LED %d in group %d\n", | ||||
| 				i, group); | ||||
| 			continue; | ||||
| 		} | ||||
| 		if (!updated && mask & BIT(i)) { | ||||
| 			led->held = true; | ||||
| 			led_trigger_event(&led->trigger, LED_FULL); | ||||
| 		} else { | ||||
| 			led->held = false; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static bool wacom_is_led_toggled(struct wacom *wacom, int button_count, | ||||
| 				 int mask, int group) | ||||
| { | ||||
| 	int button_per_group; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * 21UX2 has LED group 1 to the left and LED group 0 | ||||
| 	 * to the right. We need to reverse the group to match this | ||||
| 	 * historical behavior. | ||||
| 	 */ | ||||
| 	if (wacom->wacom_wac.features.type == WACOM_21UX2) | ||||
| 		group = 1 - group; | ||||
| 
 | ||||
| 	button_per_group = button_count/wacom->led.count; | ||||
| 
 | ||||
| 	return mask & (1 << (group * button_per_group)); | ||||
| } | ||||
| 
 | ||||
| static void wacom_update_led(struct wacom *wacom, int button_count, int mask, | ||||
| 			     int group) | ||||
| { | ||||
| 	struct wacom_led *led, *next_led; | ||||
| 	int cur; | ||||
| 	bool pressed; | ||||
| 
 | ||||
| 	if (wacom->wacom_wac.features.type == WACOM_24HD) | ||||
| 		return wacom_24hd_update_leds(wacom, mask, group); | ||||
| 
 | ||||
| 	pressed = wacom_is_led_toggled(wacom, button_count, mask, group); | ||||
| 	cur = wacom->led.groups[group].select; | ||||
| 
 | ||||
| 	led = wacom_led_find(wacom, group, cur); | ||||
| 	if (!led) { | ||||
| 		hid_err(wacom->hdev, "can't find current LED %d in group %d\n", | ||||
| 			cur, group); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!pressed) { | ||||
| 		led->held = false; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (led->held && pressed) | ||||
| 		return; | ||||
| 
 | ||||
| 	next_led = wacom_led_next(wacom, led); | ||||
| 	if (!next_led) { | ||||
| 		hid_err(wacom->hdev, "can't find next LED in group %d\n", | ||||
| 			group); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (next_led == led) | ||||
| 		return; | ||||
| 
 | ||||
| 	next_led->held = true; | ||||
| 	led_trigger_event(&next_led->trigger, | ||||
| 			  wacom_leds_brightness_get(next_led)); | ||||
| } | ||||
| 
 | ||||
| static void wacom_report_numbered_buttons(struct input_dev *input_dev, | ||||
| 				int button_count, int mask) | ||||
| { | ||||
| 	struct wacom *wacom = input_get_drvdata(input_dev); | ||||
| 	int i; | ||||
| 
 | ||||
| 	for (i = 0; i < wacom->led.count; i++) | ||||
| 		wacom_update_led(wacom,  button_count, mask, i); | ||||
| 
 | ||||
| 	for (i = 0; i < button_count && i < 10; i++) | ||||
| 		input_report_key(input_dev, BTN_0 + i, mask & (1 << i)); | ||||
| 	for (i = 10; i < button_count && i < 16; i++) | ||||
| @ -2773,6 +2897,9 @@ int wacom_setup_pad_input_capabilities(struct input_dev *input_dev, | ||||
| 	if (!(features->device_type & WACOM_DEVICETYPE_PAD)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	if (features->type == REMOTE && input_dev == wacom_wac->pad_input) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | ||||
| 
 | ||||
| 	/* kept for making legacy xf86-input-wacom working with the wheels */ | ||||
| @ -3403,7 +3530,7 @@ static const struct wacom_features wacom_features_0x343 = | ||||
| 	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET }; | ||||
| 
 | ||||
| static const struct wacom_features wacom_features_HID_ANY_ID = | ||||
| 	{ "Wacom HID", .type = HID_GENERIC }; | ||||
| 	{ "Wacom HID", .type = HID_GENERIC, .oVid = HID_ANY_ID, .oPid = HID_ANY_ID }; | ||||
| 
 | ||||
| #define USB_DEVICE_WACOM(prod)						\ | ||||
| 	HID_DEVICE(BUS_USB, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\ | ||||
|  | ||||
| @ -82,6 +82,7 @@ | ||||
| #define WACOM_DEVICETYPE_TOUCH          0x0002 | ||||
| #define WACOM_DEVICETYPE_PAD            0x0004 | ||||
| #define WACOM_DEVICETYPE_WL_MONITOR     0x0008 | ||||
| #define WACOM_DEVICETYPE_DIRECT         0x0010 | ||||
| 
 | ||||
| #define WACOM_VENDORDEFINED_PEN		0xff0d0001 | ||||
| #define WACOM_G9_PAGE			0xff090000 | ||||
| @ -185,7 +186,6 @@ struct wacom_features { | ||||
| 	int pktlen; | ||||
| 	bool check_for_hid_type; | ||||
| 	int hid_type; | ||||
| 	int last_slot_field; | ||||
| }; | ||||
| 
 | ||||
| struct wacom_shared { | ||||
| @ -214,35 +214,35 @@ struct hid_data { | ||||
| 	int cc_report; | ||||
| 	int cc_index; | ||||
| 	int cc_value_index; | ||||
| 	int last_slot_field; | ||||
| 	int num_expected; | ||||
| 	int num_received; | ||||
| }; | ||||
| 
 | ||||
| struct wacom_remote_data { | ||||
| 	struct { | ||||
| 		u32 serial; | ||||
| 		bool connected; | ||||
| 	} remote[WACOM_MAX_REMOTES]; | ||||
| }; | ||||
| 
 | ||||
| struct wacom_wac { | ||||
| 	char name[WACOM_NAME_MAX]; | ||||
| 	char pen_name[WACOM_NAME_MAX]; | ||||
| 	char touch_name[WACOM_NAME_MAX]; | ||||
| 	char pad_name[WACOM_NAME_MAX]; | ||||
| 	char bat_name[WACOM_NAME_MAX]; | ||||
| 	char ac_name[WACOM_NAME_MAX]; | ||||
| 	unsigned char data[WACOM_PKGLEN_MAX]; | ||||
| 	int tool[2]; | ||||
| 	int id[2]; | ||||
| 	__u32 serial[5]; | ||||
| 	__u32 serial[2]; | ||||
| 	bool reporting_data; | ||||
| 	struct wacom_features features; | ||||
| 	struct wacom_shared *shared; | ||||
| 	struct input_dev *pen_input; | ||||
| 	struct input_dev *touch_input; | ||||
| 	struct input_dev *pad_input; | ||||
| 	bool pen_registered; | ||||
| 	bool touch_registered; | ||||
| 	bool pad_registered; | ||||
| 	int pid; | ||||
| 	int battery_capacity; | ||||
| 	int num_contacts_left; | ||||
| 	int bat_charging; | ||||
| 	int bat_connected; | ||||
| 	int ps_connected; | ||||
| 	u8 bt_features; | ||||
| 	u8 bt_high_speed; | ||||
| 	int mode_report; | ||||
|  | ||||
| @ -837,7 +837,7 @@ __u32 hid_field_extract(const struct hid_device *hid, __u8 *report, | ||||
|  */ | ||||
| static inline void hid_device_io_start(struct hid_device *hid) { | ||||
| 	if (hid->io_started) { | ||||
| 		dev_warn(&hid->dev, "io already started"); | ||||
| 		dev_warn(&hid->dev, "io already started\n"); | ||||
| 		return; | ||||
| 	} | ||||
| 	hid->io_started = true; | ||||
| @ -857,7 +857,7 @@ static inline void hid_device_io_start(struct hid_device *hid) { | ||||
|  */ | ||||
| static inline void hid_device_io_stop(struct hid_device *hid) { | ||||
| 	if (!hid->io_started) { | ||||
| 		dev_warn(&hid->dev, "io already stopped"); | ||||
| 		dev_warn(&hid->dev, "io already stopped\n"); | ||||
| 		return; | ||||
| 	} | ||||
| 	hid->io_started = false; | ||||
|  | ||||
							
								
								
									
										30
									
								
								include/trace/events/intel_ish.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								include/trace/events/intel_ish.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| #undef TRACE_SYSTEM | ||||
| #define TRACE_SYSTEM intel_ish | ||||
| 
 | ||||
| #if !defined(_TRACE_INTEL_ISH_H) || defined(TRACE_HEADER_MULTI_READ) | ||||
| #define _TRACE_INTEL_ISH_H | ||||
| 
 | ||||
| #include <linux/tracepoint.h> | ||||
| 
 | ||||
| TRACE_EVENT(ishtp_dump, | ||||
| 
 | ||||
| 	TP_PROTO(const char *message), | ||||
| 
 | ||||
| 	TP_ARGS(message), | ||||
| 
 | ||||
| 	TP_STRUCT__entry( | ||||
| 		__string(message, message) | ||||
| 	), | ||||
| 
 | ||||
| 	TP_fast_assign( | ||||
| 		__assign_str(message, message); | ||||
| 	), | ||||
| 
 | ||||
| 	TP_printk("%s", __get_str(message)) | ||||
| ); | ||||
| 
 | ||||
| 
 | ||||
| #endif /* _TRACE_INTEL_ISH_H */ | ||||
| 
 | ||||
| /* This part must be outside protection */ | ||||
| #include <trace/define_trace.h> | ||||
| @ -248,6 +248,7 @@ struct input_mask { | ||||
| #define BUS_SPI			0x1C | ||||
| #define BUS_RMI			0x1D | ||||
| #define BUS_CEC			0x1E | ||||
| #define BUS_INTEL_ISHTP		0x1F | ||||
| 
 | ||||
| /*
 | ||||
|  * MT_TOOL types | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user