Merge branches 'for-4.8/upstream-fixes', 'for-4.9/alps', 'for-4.9/hid-input', 'for-4.9/intel-ish', 'for-4.9/kye-uclogic-waltop-fixes', 'for-4.9/logitech', 'for-4.9/sony', 'for-4.9/upstream' and 'for-4.9/wacom' into for-linus
This commit is contained in:
parent
04fd4cb0b0
9a54cf462d
52dc085a50
814cb72e25
3202bb7fb0
f777a3a7bd
c4425c8f26
6c3f70ac7c
1924e05e60
commit
179023e6af
47 changed files with 9602 additions and 635 deletions
|
@ -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
|
||||
...
|
|
@ -5992,6 +5992,13 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git
|
|||
S: Supported
|
||||
F: drivers/idle/intel_idle.c
|
||||
|
||||
INTEL INTEGRATED SENSOR HUB DRIVER
|
||||
M: Srinivas Pandruvada <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/
|
||||
|
|
|
@ -191,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);
|
||||
|
@ -384,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;
|
||||
}
|
||||
|
||||
|
@ -426,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;
|
||||
}
|
||||
|
|
|
@ -1917,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) },
|
||||
|
@ -2086,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) },
|
||||
|
@ -2483,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
|
||||
|
||||
|
@ -998,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
|
||||
|
@ -1087,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;
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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>
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -66,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
|
||||
|
||||
|
@ -1049,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;
|
||||
|
@ -1059,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)
|
||||
{
|
||||
|
@ -1318,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;
|
||||
}
|
||||
|
||||
|
@ -1555,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);
|
||||
}
|
||||
|
@ -1666,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;
|
||||
|
@ -1866,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;
|
||||
|
@ -1977,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;
|
||||
}
|
||||
|
||||
|
@ -2040,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;
|
||||
|
@ -2057,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;
|
||||
|
||||
|
@ -2095,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;
|
||||
|
@ -2108,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;
|
||||
}
|
||||
}
|
||||
|
@ -2286,6 +2332,7 @@ static inline void sony_cancel_work_sync(struct sony_sc *sc)
|
|||
static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
int append_dev_id;
|
||||
unsigned long quirks = id->driver_data;
|
||||
struct sony_sc *sc;
|
||||
unsigned int connect_mask = HID_CONNECT_DEFAULT;
|
||||
|
@ -2345,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) ||
|
||||
|
@ -2383,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;
|
||||
|
||||
|
@ -2394,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;
|
||||
|
||||
|
@ -2490,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 @@ static int uclogic_tablet_enable(struct hid_device *hdev)
|
|||
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 },
|
||||
|
@ -144,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);
|
||||
|
@ -1585,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)
|
||||
|
@ -1730,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);
|
||||
|
@ -1815,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);
|
||||
|
@ -1860,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);
|
||||
|
@ -1932,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);
|
||||
}
|
||||
|
@ -1973,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) {
|
||||
|
@ -2067,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);
|
||||
|
@ -2089,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;
|
||||
}
|
||||
|
@ -2139,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);
|
||||
}
|
||||
|
||||
|
@ -2180,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;
|
||||
|
@ -2302,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;
|
||||
|
@ -2441,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;
|
||||
|
||||
|
@ -2459,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,
|
||||
|
@ -2471,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;
|
||||
|
@ -2489,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);
|
||||
|
@ -2513,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:
|
||||
|
@ -2529,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;
|
||||
|
||||
|
@ -2545,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;
|
||||
|
||||
|
@ -2556,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);
|
||||
|
@ -2587,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:
|
||||
|
@ -2599,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 {
|
||||
|
@ -2639,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;
|
||||
|
@ -2673,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);
|
||||
|
@ -2697,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:
|
||||
|
@ -2742,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++)
|
||||
|
@ -2763,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 */
|
||||
|
@ -3393,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
|
||||
|
@ -218,31 +219,30 @@ struct hid_data {
|
|||
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 a new issue