1532 lines
40 KiB
C
1532 lines
40 KiB
C
|
/*
|
||
|
* MHL3 HID Tunneling implementation
|
||
|
*
|
||
|
* Copyright (c) 2013-2014 Lee Mulcahy <william.mulcahy@siliconimage.com>
|
||
|
* Copyright (c) 2013-2014 Silicon Image, Inc
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License as
|
||
|
* published by the Free Software Foundation version 2.
|
||
|
* This program is distributed AS-IS WITHOUT ANY WARRANTY of any
|
||
|
* kind, whether express or implied; INCLUDING without the implied warranty
|
||
|
* of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE or NON-INFRINGEMENT.
|
||
|
* See the GNU General Public License for more details at
|
||
|
* http://www.gnu.org/licenses/gpl-2.0.html.
|
||
|
*
|
||
|
* This code is inspired by the "HID over I2C protocol implementation"
|
||
|
*
|
||
|
* Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
|
||
|
* Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
|
||
|
* Copyright (c) 2012 Red Hat, Inc
|
||
|
*
|
||
|
* and the "USB HID support for Linux"
|
||
|
*
|
||
|
* Copyright (c) 1999 Andreas Gal
|
||
|
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
|
||
|
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
|
||
|
* Copyright (c) 2007-2008 Oliver Neukum
|
||
|
* Copyright (c) 2006-2010 Jiri Kosina
|
||
|
*
|
||
|
* This file is subject to the terms and conditions of the GNU General Public
|
||
|
* License. See the file COPYING in the main directory of this archive for
|
||
|
* more details.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/semaphore.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/hrtimer.h>
|
||
|
#include <linux/export.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/hid.h>
|
||
|
#include <linux/workqueue.h>
|
||
|
|
||
|
#include "si_fw_macros.h"
|
||
|
#include "si_app_devcap.h"
|
||
|
#include "si_infoframe.h"
|
||
|
#include "si_edid.h"
|
||
|
#include "si_mhl_defs.h"
|
||
|
#include "si_mhl2_edid_3d_api.h"
|
||
|
#include "si_8620_internal_api.h"
|
||
|
#include "si_mhl_tx_hw_drv_api.h"
|
||
|
#ifdef MEDIA_DATA_TUNNEL_SUPPORT
|
||
|
#include "si_mdt_inputdev.h"
|
||
|
#endif
|
||
|
#include "si_mhl_callback_api.h"
|
||
|
#include "si_8620_drv.h"
|
||
|
#include "mhl_linux_tx.h"
|
||
|
#include "si_8620_regs.h"
|
||
|
#include "mhl_supp.h"
|
||
|
#include "platform.h"
|
||
|
#include "si_emsc_hid.h"
|
||
|
|
||
|
#define EMSC_RCV_MSG_START 0 /* First fragment of message. */
|
||
|
#define EMSC_RCV_MSG_NEXT 1 /* Subsequent fragment of message. */
|
||
|
|
||
|
void dump_array(int level, char *ptitle, uint8_t *pdata, int count)
|
||
|
{
|
||
|
int i, buf_offset;
|
||
|
int bufsize = 128;
|
||
|
char *buf;
|
||
|
|
||
|
if (level > debug_level)
|
||
|
return;
|
||
|
|
||
|
if (count > 1) {
|
||
|
bufsize += count * 3;
|
||
|
bufsize += ((count / 16) + 1) * 8;
|
||
|
}
|
||
|
|
||
|
buf = kmalloc(bufsize, GFP_KERNEL);
|
||
|
if (!buf)
|
||
|
return;
|
||
|
|
||
|
buf_offset = 0;
|
||
|
if (ptitle)
|
||
|
buf_offset = scnprintf(&buf[0], bufsize,
|
||
|
"%s (%d bytes):", ptitle, count);
|
||
|
for (i = 0; i < count; i++) {
|
||
|
if ((i & 0x0F) == 0)
|
||
|
buf_offset += scnprintf(&buf[buf_offset],
|
||
|
bufsize - buf_offset, "\n%04X: ", i);
|
||
|
buf_offset += scnprintf(&buf[buf_offset],
|
||
|
bufsize - buf_offset, "%02X ", pdata[i]);
|
||
|
}
|
||
|
buf_offset += scnprintf(&buf[buf_offset], bufsize - buf_offset, "\n");
|
||
|
print_formatted_debug_msg(NULL, NULL, -1, buf);
|
||
|
kfree(buf);
|
||
|
}
|
||
|
|
||
|
struct cbus_req *hid_host_role_request_done(struct mhl_dev_context *mdev,
|
||
|
struct cbus_req *req, uint8_t data1)
|
||
|
{
|
||
|
MHL_TX_DBG_ERR("\n")
|
||
|
return req;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send Host role request or relinquish it. This should be called with
|
||
|
* MHL_RHID_REQUEST_HOST for a source during MHL connection and when the
|
||
|
* want_host flag is set (after the sink has relinquished the Host role that
|
||
|
* we let them have by sending MHL_RHID_RELIQUISH_HOST).
|
||
|
*/
|
||
|
void mhl_tx_hid_host_role_request(struct mhl_dev_context *context, int request)
|
||
|
{
|
||
|
MHL3_HID_DBG_INFO("RHID: Sending HID Host role %s message\n",
|
||
|
(request == MHL_RHID_REQUEST_HOST) ?
|
||
|
"REQUEST" : "RELINQUISH");
|
||
|
/* During a request, we are in limbo */
|
||
|
context->mhl_ghid.is_host = false;
|
||
|
context->mhl_ghid.is_device = false;
|
||
|
context->mhl_ghid.want_host = false;
|
||
|
|
||
|
si_mhl_tx_send_msc_msg(context, MHL_MSC_MSG_RHID, request,
|
||
|
hid_host_role_request_done);
|
||
|
}
|
||
|
|
||
|
struct cbus_req *rhidk_done(struct mhl_dev_context *mdev,
|
||
|
struct cbus_req *req, uint8_t data1)
|
||
|
{
|
||
|
MHL_TX_DBG_ERR("\n")
|
||
|
return req;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Handle host-device negotiation request messages received from peer.
|
||
|
*
|
||
|
* A sink requesting Host role is typically just trying to determine
|
||
|
* if the source has already requested the Host role, which is required
|
||
|
* of the sink before starting the Device role (14.3.1.1).
|
||
|
* As a source, we should have sent a host role request to the sink
|
||
|
* before this (dev_context->mhl_ghid.is_host == true), so host
|
||
|
* requests should be refused.
|
||
|
*
|
||
|
* When the sink relinquishes the Host role (assuming we let the sink have
|
||
|
* it for some reason), we immediately want it back.
|
||
|
*/
|
||
|
void mhl_tx_hid_host_negotiation(struct mhl_dev_context *mdev)
|
||
|
{
|
||
|
uint8_t rhidk_status = MHL_RHID_NO_ERR;
|
||
|
|
||
|
if (mdev->msc_msg_sub_command == MHL_MSC_MSG_RHID) {
|
||
|
if (mdev->msc_msg_data == MHL_RHID_REQUEST_HOST) {
|
||
|
if (mdev->mhl_ghid.is_host)
|
||
|
rhidk_status = MHL_RHID_DENY;
|
||
|
} else if (mdev->msc_msg_data == MHL_RHID_RELINQUISH_HOST) {
|
||
|
mdev->mhl_ghid.want_host = true;
|
||
|
} else {
|
||
|
rhidk_status = MHL_RHID_INVALID;
|
||
|
}
|
||
|
|
||
|
MHL3_HID_DBG_INFO(
|
||
|
"RHID: Received HID Host role %s result: %d\n",
|
||
|
(mdev->msc_msg_data == MHL_RHID_REQUEST_HOST) ?
|
||
|
"REQUEST" : "RELINQUISH", rhidk_status);
|
||
|
|
||
|
/* Always RHIDK to the peer */
|
||
|
si_mhl_tx_send_msc_msg(mdev, MHL_MSC_MSG_RHIDK, rhidk_status,
|
||
|
rhidk_done);
|
||
|
|
||
|
} else if (mdev->msc_msg_sub_command == MHL_MSC_MSG_RHIDK) {
|
||
|
if (mdev->msc_msg_data == MHL_RHID_NO_ERR) {
|
||
|
if (mdev->msc_msg_last_data == MHL_RHID_REQUEST_HOST) {
|
||
|
mdev->mhl_ghid.is_host = true;
|
||
|
mdev->mhl_ghid.is_device = false;
|
||
|
MHL3_HID_DBG_INFO("HID HOST role granted\n");
|
||
|
} else {
|
||
|
mdev->mhl_ghid.is_host = false;
|
||
|
mdev->mhl_ghid.is_device = true;
|
||
|
MHL3_HID_DBG_INFO("HID DEVICE role granted\n");
|
||
|
}
|
||
|
} else if (mdev->msc_msg_last_data == MHL_RHID_REQUEST_HOST) {
|
||
|
mdev->mhl_ghid.is_host = false;
|
||
|
mdev->mhl_ghid.is_device = true;
|
||
|
MHL3_HID_DBG_INFO("HID HOST role DENIED\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Add a HID message to the eMSC output queue using one or more
|
||
|
* eMSC BLOCK commands.
|
||
|
*/
|
||
|
static int si_mhl_tx_emsc_add_hid_message(struct mhl_dev_context *mdev,
|
||
|
uint8_t hb0, uint8_t hb1, uint8_t *msg, int msg_len)
|
||
|
{
|
||
|
int i, cmd_size, fragment_count, msg_index, index;
|
||
|
uint8_t *payload;
|
||
|
uint8_t payload_size;
|
||
|
uint16_t accum;
|
||
|
uint16_t *pchksum;
|
||
|
bool first_fragment;
|
||
|
|
||
|
/* If message exceeds one (empty) BLOCK command buffer, we must
|
||
|
* break it into fragments. Since the first fragment will be
|
||
|
* the full command buffer size, the current request (if any) will be
|
||
|
* sent before starting to add this one.
|
||
|
*/
|
||
|
i = msg_len + HID_MSG_HEADER_LEN + HID_MSG_CHKSUM_LEN;
|
||
|
fragment_count = i / HID_FRAG_LEN_MAX;
|
||
|
if ((fragment_count * HID_FRAG_LEN_MAX) != i)
|
||
|
fragment_count++;
|
||
|
|
||
|
/* This time don't include the standard header in the message length. */
|
||
|
cmd_size =
|
||
|
HID_BURST_ID_LEN +
|
||
|
HID_FRAG_HEADER_LEN +
|
||
|
HID_MSG_HEADER_LEN +
|
||
|
HID_MSG_CHKSUM_LEN +
|
||
|
msg_len;
|
||
|
|
||
|
first_fragment = true;
|
||
|
msg_index = 0;
|
||
|
index = 0;
|
||
|
accum = 0;
|
||
|
|
||
|
/*
|
||
|
* TODO: Need to make sure there will be enough buffers
|
||
|
* for the fragments.
|
||
|
*/
|
||
|
index = 0;
|
||
|
while (fragment_count > 0) {
|
||
|
payload_size = (cmd_size > EMSC_BLK_CMD_MAX_LEN) ?
|
||
|
EMSC_BLK_CMD_MAX_LEN : cmd_size;
|
||
|
|
||
|
/* TODO: Need up to 17 buffers (do we have them?) */
|
||
|
payload = (uint8_t *)si_mhl_tx_get_sub_payload_buffer(
|
||
|
mdev, payload_size);
|
||
|
if (payload == NULL) {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"%ssi_mhl_tx_get_sub_payload_buffer failed%s\n",
|
||
|
ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
/*
|
||
|
* TODO: Should be handled with an error code and
|
||
|
* exit, but need to clean up any buffers that may
|
||
|
* have been successfully allocated.
|
||
|
*/
|
||
|
} else {
|
||
|
payload[index++] = (uint8_t)(burst_id_HID_PAYLOAD >> 8);
|
||
|
payload[index++] = (uint8_t)(burst_id_HID_PAYLOAD);
|
||
|
payload[index++] = payload_size - HID_BURST_ID_LEN;
|
||
|
payload[index++] = fragment_count - 1;
|
||
|
payload_size -=
|
||
|
(HID_BURST_ID_LEN + HID_FRAG_HEADER_LEN);
|
||
|
if (fragment_count == 1)
|
||
|
payload_size -= HID_MSG_CHKSUM_LEN;
|
||
|
|
||
|
if (first_fragment) {
|
||
|
first_fragment = false;
|
||
|
payload[index++] = hb0;
|
||
|
payload[index++] = hb1;
|
||
|
payload_size -= HID_MSG_HEADER_LEN;
|
||
|
|
||
|
accum = ((((uint16_t)hb1) << 8) | hb0);
|
||
|
pchksum = (uint16_t *)&msg[0];
|
||
|
for (i = 0; i < (msg_len / 2); i++)
|
||
|
accum += pchksum[i];
|
||
|
if (msg_len & 0x01)
|
||
|
accum += ((uint16_t)msg[msg_len - 1]);
|
||
|
accum += (msg_len + HID_MSG_HEADER_LEN);
|
||
|
}
|
||
|
memcpy(&payload[index], &msg[msg_index], payload_size);
|
||
|
msg_index += payload_size;
|
||
|
index += payload_size;
|
||
|
|
||
|
if (fragment_count == 1) {
|
||
|
payload[index++] = (uint8_t)accum;
|
||
|
payload[index++] = (uint8_t)(accum >> 8);
|
||
|
}
|
||
|
}
|
||
|
fragment_count--;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Build a HID tunneling message and send it.
|
||
|
* Returns non-zero if an error occurred, such as the device was
|
||
|
* disconnected.
|
||
|
* Always use the CTRL channel to send messages from the HOST.
|
||
|
*
|
||
|
* This function should be called wrapped in an isr_lock semaphore pair UNLESS
|
||
|
* it is being called from the Titan interrupt handler.
|
||
|
*/
|
||
|
static int send_hid_msg(struct mhl3_hid_data *mhid, int outlen, bool want_ack)
|
||
|
{
|
||
|
struct mhl_dev_context *mdev = mhid->mdev;
|
||
|
uint8_t hb0, hb1;
|
||
|
int ret;
|
||
|
|
||
|
hb0 = mhid->id << 4;
|
||
|
|
||
|
if (want_ack) {
|
||
|
hb1 = mhid->msg_count[0] | EMSC_HID_HB1_ACK;
|
||
|
mhid->msg_count[0] =
|
||
|
(mhid->msg_count[0] + 1) & EMSC_HID_HB1_MSG_CNT_FLD;
|
||
|
}
|
||
|
hb1 = mhid->msg_count[0] | (want_ack ? EMSC_HID_HB1_ACK : 0x00);
|
||
|
mhid->msg_count[0] =
|
||
|
(mhid->msg_count[0] + 1) & EMSC_HID_HB1_MSG_CNT_FLD;
|
||
|
ret = si_mhl_tx_emsc_add_hid_message(mdev, hb0, hb1,
|
||
|
mhid->out_data, outlen);
|
||
|
if (ret == 0)
|
||
|
si_mhl_tx_push_block_transactions(mdev);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send an MHL3 HID Command and wait for a response.
|
||
|
* called only from a WORK QUEUE function; Do NOT call from
|
||
|
* the Titan interrupt context.
|
||
|
* Returns negative if an error occurred, such as the device was
|
||
|
* disconnected, otherwise returns the number of bytes
|
||
|
* received.
|
||
|
*/
|
||
|
static int send_hid_wait(struct mhl3_hid_data *mhid,
|
||
|
int outlen, uint8_t *pin, int inlen, bool want_ack)
|
||
|
{
|
||
|
struct mhl_dev_context *mdev = mhid->mdev;
|
||
|
int count, ret;
|
||
|
|
||
|
/*
|
||
|
* Take a hold of the wait lock. It will be released
|
||
|
* when the response is received. This down call will be
|
||
|
* released by an up call in the hid_message_processor()
|
||
|
* function when the response message has been received. Not
|
||
|
* conventional, but until I think of a better way,
|
||
|
* this is it.
|
||
|
*/
|
||
|
if (down_timeout(&mhid->data_wait_lock, 1*HZ)) {
|
||
|
MHL3_HID_DBG_ERR("Could not acquire data_wait lock !!!\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
MHL3_HID_DBG_INFO("Acquired data_wait_lock\n");
|
||
|
|
||
|
/*
|
||
|
* Send the message when the Titan ISR is not active so
|
||
|
* that we don't deadlock with eMsc block buffer allocation.
|
||
|
*/
|
||
|
if (down_interruptible(&mdev->isr_lock)) {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"Could not acquire isr_lock for HID work queue\n");
|
||
|
ret = -ERESTARTSYS;
|
||
|
goto done_release;
|
||
|
}
|
||
|
|
||
|
/* If canceling, get out. */
|
||
|
if (mhid->flags & HID_FLAGS_WQ_CANCEL) {
|
||
|
ret = -ENODEV;
|
||
|
up(&mdev->isr_lock);
|
||
|
goto done_release;
|
||
|
}
|
||
|
|
||
|
ret = send_hid_msg(mhid, outlen, want_ack);
|
||
|
up(&mdev->isr_lock);
|
||
|
if (ret == 0) {
|
||
|
/* Wait until a message is ready (when the driver unblocks). */
|
||
|
if (down_timeout(&mhid->data_wait_lock, 15*HZ)) {
|
||
|
MHL3_HID_DBG_WARN("Timed out waiting for HID msg!\n");
|
||
|
ret = -EBUSY;
|
||
|
|
||
|
/*
|
||
|
* As odd as it may seem, we must release the semaphore
|
||
|
* that we were waiting for because something
|
||
|
* apparently has gone wrong with the communications
|
||
|
* protocol and we need to start over.
|
||
|
*/
|
||
|
goto done_release;
|
||
|
}
|
||
|
|
||
|
/* Intercept HID_ACK{NO_DEV} messages. */
|
||
|
if ((mhid->in_data[0] == MHL3_HID_ACK) &&
|
||
|
(mhid->in_data[1] == HID_ACK_NODEV)) {
|
||
|
ret = -ENODEV;
|
||
|
goto done_release;
|
||
|
}
|
||
|
|
||
|
/* Message has been placed in our input buffer. */
|
||
|
count = (mhid->in_data_length > inlen) ?
|
||
|
inlen : mhid->in_data_length;
|
||
|
memcpy(pin, mhid->in_data, count);
|
||
|
ret = count;
|
||
|
}
|
||
|
|
||
|
done_release:
|
||
|
up(&mhid->data_wait_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Message protocol level ACK packet, as opposed to the HID_ACK message.
|
||
|
*/
|
||
|
static int send_ack_packet(struct mhl_dev_context *mdev,
|
||
|
uint8_t hb0, uint8_t hb1)
|
||
|
{
|
||
|
uint8_t *payload;
|
||
|
|
||
|
payload = (uint8_t *)si_mhl_tx_get_sub_payload_buffer(
|
||
|
mdev, HID_BURST_ID_LEN + HID_ACK_PACKET_LEN);
|
||
|
if (payload == NULL) {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"%ssi_mhl_tx_get_sub_payload_buffer failed%s\n",
|
||
|
ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
|
||
|
/*
|
||
|
* TODO: Should be handled with an error code and
|
||
|
* exit, but need to clean up any buffers that may
|
||
|
* have been successfully allocated.
|
||
|
*/
|
||
|
} else {
|
||
|
payload[0] = (uint8_t)(burst_id_HID_PAYLOAD >> 8);
|
||
|
payload[1] = (uint8_t)(burst_id_HID_PAYLOAD);
|
||
|
payload[2] = HID_ACK_PACKET_LEN;
|
||
|
payload[3] = hb1 | 0x80;
|
||
|
payload[4] = hb0;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send a HID_ACK message with the passed error code from the interrupt
|
||
|
* context without using the mhid device structure (in case it's not there).
|
||
|
*/
|
||
|
static int mhl3_int_send_ack(struct mhl_dev_context *mdev,
|
||
|
int reason, uint8_t hb0)
|
||
|
{
|
||
|
int status;
|
||
|
uint8_t out_data[2];
|
||
|
|
||
|
MHL3_HID_DBG_WARN("HID_ACK reason code: %02X\n", reason);
|
||
|
out_data[0] = MHL3_HID_ACK;
|
||
|
out_data[1] = (reason < 0) ? HID_ACK_NODEV : reason;
|
||
|
|
||
|
status = si_mhl_tx_emsc_add_hid_message(mdev, hb0, 0, out_data, 2);
|
||
|
if (status < 0)
|
||
|
MHL3_HID_DBG_ERR("MHID: Failed to send HID_ACK to device.\n");
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send a HID_ACK message with the passed error code.
|
||
|
*/
|
||
|
static int mhl3_send_ack(struct mhl3_hid_data *mhid, uint8_t reason)
|
||
|
{
|
||
|
int status;
|
||
|
struct mhl_dev_context *mdev = mhid->mdev;
|
||
|
|
||
|
if (mhid == 0)
|
||
|
return -ENODEV;
|
||
|
|
||
|
MHL3_HID_DBG_WARN("%s - HID_ACK reason code: %02X\n", __func__, reason);
|
||
|
MHL3_HID_DBG_ERR("mhid->mdev: %p\n", mhid->mdev);
|
||
|
mhid->out_data[0] = MHL3_HID_ACK;
|
||
|
mhid->out_data[1] = reason;
|
||
|
|
||
|
/*
|
||
|
* Send the message when the Titan ISR is not active so
|
||
|
* that we don't deadlock with eMsc block buffer allocation.
|
||
|
*/
|
||
|
if (down_interruptible(&mdev->isr_lock)) {
|
||
|
MHL3_HID_DBG_ERR("Could not acquire isr_lock for HID work\n");
|
||
|
return -ERESTARTSYS;
|
||
|
}
|
||
|
status = send_hid_msg(mhid, 2, false);
|
||
|
up(&mdev->isr_lock);
|
||
|
if (status < 0)
|
||
|
MHL3_HID_DBG_ERR("Failed to send a HID_ACK to device.\n");
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Called from mhl3_hid_get_raw_report() and mhl3_hid_init_report()
|
||
|
* to get report data from the device.
|
||
|
*
|
||
|
* It CANNOT be called from the Titan driver interrupt context, because
|
||
|
* it blocks until it gets a response FROM the Titan interrupt.
|
||
|
*/
|
||
|
static int mhl3_hid_get_report(struct mhl3_hid_data *mhid, u8 report_type,
|
||
|
u8 report_id, unsigned char *pin, int inlen)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
/* Build MHL3_GET_REPORT message and add it to the out queue. */
|
||
|
mhid->out_data[0] = MHL3_GET_REPORT;
|
||
|
mhid->out_data[1] = report_type;
|
||
|
mhid->out_data[2] = report_id;
|
||
|
ret = send_hid_wait(mhid, 3, pin, inlen, false);
|
||
|
if (ret < 0)
|
||
|
MHL3_HID_DBG_ERR("Failed to retrieve report from device.\n");
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Called from mhl3_hid_output_raw_report() to send report data to
|
||
|
* the device.
|
||
|
*/
|
||
|
static int mhl3_hid_set_report(struct mhl3_hid_data *mhid, u8 report_type,
|
||
|
u8 report_id, unsigned char *pout, size_t outlen)
|
||
|
{
|
||
|
struct mhl_dev_context *mdev = mhid->mdev;
|
||
|
int ret = 0;
|
||
|
|
||
|
mhid->out_data[0] = MHL3_SET_REPORT;
|
||
|
mhid->out_data[1] = report_type;
|
||
|
mhid->out_data[2] = report_id;
|
||
|
memcpy(&mhid->out_data[3], pout, outlen);
|
||
|
|
||
|
/* TODO: Need to make sure that this function is never called from
|
||
|
* the Titan ISR (through linkage from a HID function called
|
||
|
* from a HID report response or some such). Just needs some
|
||
|
* research....
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Send the message when the Titan ISR is not active so
|
||
|
* that we don't deadlock with eMsc block buffer allocation.
|
||
|
*/
|
||
|
if (down_interruptible(&mdev->isr_lock)) {
|
||
|
MHL3_HID_DBG_ERR("Could not acquire isr_lock for HID work\n");
|
||
|
return -ERESTARTSYS;
|
||
|
}
|
||
|
ret = send_hid_msg(mhid, outlen + 3, false);
|
||
|
up(&mdev->isr_lock);
|
||
|
if (ret) {
|
||
|
MHL3_HID_DBG_ERR("Failed to set a report to device.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return outlen + 3;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* TODO: Doesn't do anything yet
|
||
|
*/
|
||
|
static int mhl3_hid_set_power(struct mhl_dev_context *context, int power_state)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int mhl3_hid_alloc_buffers(struct mhl3_hid_data *mhid,
|
||
|
size_t report_size)
|
||
|
{
|
||
|
mhid->in_report_buf = kzalloc(report_size, GFP_KERNEL);
|
||
|
if (mhid->in_report_buf == NULL) {
|
||
|
kfree(mhid->hdesc);
|
||
|
mhid->hdesc = NULL;
|
||
|
kfree(mhid->in_report_buf);
|
||
|
mhid->in_report_buf = NULL;
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
mhid->bufsize = report_size;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Called from the HID driver to obtain raw report data from the
|
||
|
* device.
|
||
|
*
|
||
|
* It is not called from an interrupt context, so it is OK to block
|
||
|
* until a reply is given.
|
||
|
*/
|
||
|
#if (LINUX_KERNEL_VER < 315)
|
||
|
static int mhl3_hid_get_raw_report(struct hid_device *hid,
|
||
|
unsigned char report_number, __u8 *buf,
|
||
|
size_t count, unsigned char report_type)
|
||
|
{
|
||
|
struct mhl3_hid_data *mhid = hid->driver_data;
|
||
|
int ret;
|
||
|
|
||
|
if (report_type == HID_OUTPUT_REPORT)
|
||
|
return -EINVAL;
|
||
|
|
||
|
ret = mhl3_hid_get_report(mhid,
|
||
|
report_type == HID_FEATURE_REPORT ? 0x03 : 0x01,
|
||
|
report_number, buf, count);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Called from the HID driver to send report data to the device.
|
||
|
*/
|
||
|
static int mhl3_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
|
||
|
size_t count, unsigned char report_type)
|
||
|
{
|
||
|
struct mhl3_hid_data *mhid = hid->driver_data;
|
||
|
int report_id = buf[0];
|
||
|
int ret;
|
||
|
|
||
|
if (report_type == HID_INPUT_REPORT)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (report_id) {
|
||
|
buf++;
|
||
|
count--;
|
||
|
}
|
||
|
|
||
|
ret = mhl3_hid_set_report(mhid,
|
||
|
report_type == HID_FEATURE_REPORT ? 0x03 : 0x02,
|
||
|
report_id, buf, count);
|
||
|
|
||
|
if (report_id && ret >= 0)
|
||
|
ret++; /* add report_id to the number of transfered bytes */
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int mhl3_hid_get_report_length(struct hid_report *report)
|
||
|
{
|
||
|
return ((report->size - 1) >> 3) + 1 +
|
||
|
report->device->report_enum[report->type].numbered + 2;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Traverse the supplied list of reports and find the longest
|
||
|
*/
|
||
|
static void mhl3_hid_find_max_report(struct hid_device *hid, unsigned int type,
|
||
|
unsigned int *max)
|
||
|
{
|
||
|
struct hid_report *report;
|
||
|
unsigned int size;
|
||
|
|
||
|
/*
|
||
|
* We should not rely on wMaxInputLength, as some devices may set
|
||
|
* it to a wrong length.
|
||
|
*/
|
||
|
list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
|
||
|
size = mhl3_hid_get_report_length(report);
|
||
|
if (*max < size)
|
||
|
*max = size;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
*/
|
||
|
#if 0
|
||
|
static void mhl3_hid_init_report(struct hid_report *report, u8 *buffer,
|
||
|
size_t bufsize)
|
||
|
{
|
||
|
struct hid_device *hid = report->device;
|
||
|
struct mhl3_hid_data *mhid = hid->driver_data;
|
||
|
unsigned int size, ret_size;
|
||
|
|
||
|
size = mhl3_hid_get_report_length(report);
|
||
|
if (mhl3_hid_get_report(mhid,
|
||
|
report->type == HID_FEATURE_REPORT ? 0x03 : 0x01,
|
||
|
report->id, buffer, size))
|
||
|
return;
|
||
|
|
||
|
ret_size = buffer[0] | (buffer[1] << 8);
|
||
|
|
||
|
if (ret_size != size) {
|
||
|
MHL3_HID_DBG_ERR("Error in %s size:%d / ret_size:%d\n",
|
||
|
__func__, size, ret_size);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* hid->driver_lock is held as we are in probe function,
|
||
|
* we just need to setup the input fields, so using
|
||
|
* hid_report_raw_event is safe.
|
||
|
*/
|
||
|
hid_report_raw_event(hid, report->type, buffer + 2, size - 2, 1);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if 0
|
||
|
/*
|
||
|
* Initialize all reports. This gets the current value of all
|
||
|
* input/feature reports for the device so that the HID-core can keep
|
||
|
* them in internal structures. The structure is updated as further
|
||
|
* device reports occur.
|
||
|
*/
|
||
|
static void mhl3_hid_init_reports(struct hid_device *hid)
|
||
|
{
|
||
|
struct mhl3_hid_data *mhid = hid->driver_data;
|
||
|
struct hid_report *report;
|
||
|
|
||
|
MHL3_HID_DBG_INFO("%s\n", __func__);
|
||
|
list_for_each_entry(report,
|
||
|
&hid->report_enum[HID_INPUT_REPORT].report_list, list)
|
||
|
mhl3_hid_init_report(
|
||
|
report, mhid->in_report_buf, mhid->bufsize);
|
||
|
|
||
|
list_for_each_entry(report,
|
||
|
&hid->report_enum[HID_FEATURE_REPORT].report_list, list)
|
||
|
mhl3_hid_init_report(
|
||
|
report, mhid->in_report_buf, mhid->bufsize);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* TODO: Doesn't do anything yet except manage the open count
|
||
|
*/
|
||
|
static int mhl3_hid_open(struct hid_device *hid)
|
||
|
{
|
||
|
struct mhl_dev_context *mdev = 0;
|
||
|
struct mhl3_hid_data *mhid = 0;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (hid)
|
||
|
mhid = hid->driver_data;
|
||
|
if (mhid == 0)
|
||
|
return 0;
|
||
|
mdev = mhid->mdev;
|
||
|
|
||
|
mutex_lock(&mhl3_hid_open_mutex);
|
||
|
if (!hid->open++) {
|
||
|
ret = mhl3_hid_set_power(mdev, MHL3_HID_PWR_ON);
|
||
|
if (ret) {
|
||
|
hid->open--;
|
||
|
goto done;
|
||
|
}
|
||
|
mhid->flags |= MHL3_HID_STARTED;
|
||
|
}
|
||
|
done:
|
||
|
mutex_unlock(&mhl3_hid_open_mutex);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* TODO: Doesn't do anything yet except manage the open count
|
||
|
*/
|
||
|
static void mhl3_hid_close(struct hid_device *hid)
|
||
|
{
|
||
|
struct mhl_dev_context *mdev = 0;
|
||
|
struct mhl3_hid_data *mhid = 0;
|
||
|
|
||
|
if (hid)
|
||
|
mhid = hid->driver_data;
|
||
|
if (mhid == 0)
|
||
|
return;
|
||
|
mdev = mhid->mdev;
|
||
|
|
||
|
/*
|
||
|
* Protecting hid->open to make sure we don't restart
|
||
|
* data acquisition due to a resumption we no longer
|
||
|
* care about
|
||
|
*/
|
||
|
mutex_lock(&mhl3_hid_open_mutex);
|
||
|
if (!--hid->open) {
|
||
|
mhid->flags &= ~MHL3_HID_STARTED;
|
||
|
mhl3_hid_set_power(mdev, MHL3_HID_PWR_SLEEP);
|
||
|
}
|
||
|
mutex_unlock(&mhl3_hid_open_mutex);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* TODO: Doesn't do anything yet
|
||
|
*/
|
||
|
static int mhl3_hid_power(struct hid_device *hid, int lvl)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
MHL3_HID_DBG_ERR("level: %d\n", lvl);
|
||
|
|
||
|
switch (lvl) {
|
||
|
case PM_HINT_FULLON:
|
||
|
break;
|
||
|
case PM_HINT_NORMAL:
|
||
|
break;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static struct hid_ll_driver mhl3_hid_ll_driver = {
|
||
|
.open = mhl3_hid_open,
|
||
|
.close = mhl3_hid_close,
|
||
|
.power = mhl3_hid_power,
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Use the MHL3 GET_MHID_DSCRPT message to request the HID Device
|
||
|
* Descriptor from the device on the control channel. The device
|
||
|
* should reply with an MHL3_MHID_DSCRPT message
|
||
|
*
|
||
|
* This function MUST be called from a work queue function.
|
||
|
*
|
||
|
*/
|
||
|
static int mhid_fetch_hid_descriptor(struct mhl3_hid_data *mhid)
|
||
|
{
|
||
|
struct mhl3_hid_desc *hdesc;
|
||
|
uint8_t *pdesc_raw;
|
||
|
int ret = -ENODEV;
|
||
|
int desc_len, raw_offset;
|
||
|
|
||
|
/* The actual length of the data will likely not be this much. */
|
||
|
desc_len = sizeof(struct mhl3_hid_desc) +
|
||
|
sizeof(mhid->desc_product_name) +
|
||
|
sizeof(mhid->desc_mfg_name) +
|
||
|
sizeof(mhid->desc_serial_number);
|
||
|
pdesc_raw = kmalloc(desc_len, GFP_KERNEL);
|
||
|
if (!pdesc_raw) {
|
||
|
MHL3_HID_DBG_ERR("Couldn't allocate raw descriptor memory\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
MHL3_HID_DBG_INFO("Fetching the HID descriptor\n");
|
||
|
|
||
|
/*
|
||
|
* Build GET_MHID_DSCRPT message and add it to the out queue.
|
||
|
* By setting the LANG_ID field bytes to 0, we allow the
|
||
|
* device to send any language it chooses.
|
||
|
*/
|
||
|
mhid->out_data[0] = MHL3_GET_MHID_DSCRPT;
|
||
|
mhid->out_data[1] = 0;
|
||
|
mhid->out_data[2] = 0;
|
||
|
mhid->opState = OP_STATE_WAIT_MHID_DSCRPT;
|
||
|
ret = send_hid_wait(mhid, 3, pdesc_raw, desc_len, false);
|
||
|
if ((ret < 0) || (mhid->opState == OP_STATE_IDLE)) {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"Failed to get MHID descriptor: %d.\n", ret);
|
||
|
goto raw_cleanup;
|
||
|
}
|
||
|
|
||
|
hdesc = kmalloc(desc_len, GFP_KERNEL);
|
||
|
if (!hdesc) {
|
||
|
MHL3_HID_DBG_ERR("Couldn't allocate hdesc descriptor memory\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto raw_cleanup;
|
||
|
}
|
||
|
|
||
|
/* dump_array(DBG_MSG_LEVEL_INFO, "HID descriptor", pdesc_raw, ret); */
|
||
|
|
||
|
/* Get the fixed length part and verify. */
|
||
|
memcpy(hdesc, pdesc_raw, sizeof(struct mhl3_hid_desc));
|
||
|
raw_offset = sizeof(struct mhl3_hid_desc);
|
||
|
|
||
|
/* Do some simple checks. */
|
||
|
if (hdesc->bMHL3HIDmessageID != 0x05) {
|
||
|
MHL3_HID_DBG_ERR("Invalid MHID_DSCRPT data\n");
|
||
|
ret = -EINVAL;
|
||
|
goto hdesc_cleanup;
|
||
|
}
|
||
|
|
||
|
if (ret < (sizeof(struct mhl3_hid_desc) +
|
||
|
hdesc->bProductNameSize +
|
||
|
hdesc->bManufacturerNameSize +
|
||
|
hdesc->bSerialNumberSize)) {
|
||
|
|
||
|
memcpy(mhid->desc_product_name,
|
||
|
&pdesc_raw[raw_offset], hdesc->bProductNameSize);
|
||
|
raw_offset += hdesc->bProductNameSize;
|
||
|
memcpy(mhid->desc_mfg_name,
|
||
|
&pdesc_raw[raw_offset], hdesc->bManufacturerNameSize);
|
||
|
raw_offset += hdesc->bManufacturerNameSize;
|
||
|
memcpy(mhid->desc_serial_number,
|
||
|
&pdesc_raw[raw_offset], hdesc->bSerialNumberSize);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If this was an existing mhid being updated, free the previous
|
||
|
* descriptor and reassign with the new one.
|
||
|
*/
|
||
|
kfree(mhid->hdesc);
|
||
|
mhid->hdesc = hdesc;
|
||
|
ret = 0;
|
||
|
goto raw_cleanup;
|
||
|
|
||
|
hdesc_cleanup:
|
||
|
kfree(hdesc);
|
||
|
raw_cleanup:
|
||
|
kfree(pdesc_raw);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* TODO: Eliminated ACPI stuff; i2c_hid_acpi_pdata. etc. */
|
||
|
|
||
|
/*
|
||
|
* The users of the hid_device structure don't always check that
|
||
|
* the hid_device structure has a valid hid_driver before trying to access
|
||
|
* its members. Creating an empty structure at least avoids a
|
||
|
* kernel panic.
|
||
|
*/
|
||
|
static struct hid_driver mhl3_hid_driver = {
|
||
|
|
||
|
.name = "mhl3_hid",
|
||
|
};
|
||
|
|
||
|
static struct hid_driver mhl3_mt3_hid_driver = {
|
||
|
|
||
|
.name = "mhl3_hid-mt",
|
||
|
.event = mhl3_mt_event,
|
||
|
.input_mapping = mhl3_mt_input_mapping,
|
||
|
.input_mapped = mhl3_mt_input_mapped,
|
||
|
.feature_mapping = mt_feature_mapping,
|
||
|
#if (LINUX_KERNEL_VER >= 311)
|
||
|
.input_configured = mt_input_configured,
|
||
|
.report = mt_report,
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Get report descriptors from the device and parse them.
|
||
|
* The Report Descriptor describes the report ID and the type(s)
|
||
|
* of data it contains. During operation, when the device returns
|
||
|
* a report with a specific ReportID, the HID core will know how
|
||
|
* to parse it.
|
||
|
*/
|
||
|
int mhl3_hid_report_desc_parse(struct mhl3_hid_data *mhid)
|
||
|
{
|
||
|
unsigned int report_len;
|
||
|
int ret;
|
||
|
uint8_t *report_desc_raw = NULL;
|
||
|
struct hid_device *hdev;
|
||
|
|
||
|
hdev = mhid->hid;
|
||
|
report_desc_raw = kmalloc(HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
|
||
|
if (!report_desc_raw) {
|
||
|
MHL3_HID_DBG_ERR("Allocate raw report descriptor mem failed\n");
|
||
|
ret = -ENOMEM;
|
||
|
goto err;
|
||
|
}
|
||
|
mhid->out_data[0] = MHL3_GET_REPORT_DSCRPT;
|
||
|
mhid->opState = OP_STATE_WAIT_REPORT_DSCRPT;
|
||
|
ret = send_hid_wait(mhid, 1, report_desc_raw,
|
||
|
HID_MAX_DESCRIPTOR_SIZE, false);
|
||
|
if (ret < 0) {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"Failed to get descriptor from device: %d.\n", ret);
|
||
|
goto err;
|
||
|
}
|
||
|
if (mhid->opState == OP_STATE_IDLE)
|
||
|
goto done;
|
||
|
if (report_desc_raw[0] != MHL3_REPORT_DSCRPT) {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"Invalid response to MHL3_GET_REPORT_DSCRPT\n");
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
report_len = report_desc_raw[2];
|
||
|
report_len <<= 8;
|
||
|
report_len |= report_desc_raw[1];
|
||
|
report_len = (report_len < (ret-3)) ? report_len : (ret-3);
|
||
|
ret = hid_parse_report(hdev, &report_desc_raw[3], report_len);
|
||
|
if (ret)
|
||
|
goto err;
|
||
|
|
||
|
#if (LINUX_KERNEL_VER >= 305)
|
||
|
ret = hid_open_report(hdev);
|
||
|
if (ret) {
|
||
|
MHL3_HID_DBG_ERR("hid_open_report failed\n");
|
||
|
goto err;
|
||
|
}
|
||
|
#endif
|
||
|
goto done;
|
||
|
err:
|
||
|
MHL3_HID_DBG_ERR("WORK QUEUE mhl3_hid_report_desc_parse() FAIL\n");
|
||
|
done:
|
||
|
kfree(report_desc_raw);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void mhl3_disconnect_and_destroy_hid_device(struct mhl3_hid_data *mhid)
|
||
|
{
|
||
|
if (mhid->hid) {
|
||
|
if (mhid->hid->claimed & HID_CLAIMED_INPUT) {
|
||
|
hidinput_disconnect(mhid->hid);
|
||
|
mhid->hid->claimed &= ~HID_CLAIMED_INPUT;
|
||
|
}
|
||
|
hid_destroy_device(mhid->hid);
|
||
|
mhid->hid = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The second part of the mhl3_hid_add() function, implemented as
|
||
|
* a work queue. It is entered at opState == OP_STATE_IDLE
|
||
|
* A failure here deletes the mhid device.
|
||
|
*/
|
||
|
static void mhid_add_work(struct work_struct *workdata)
|
||
|
{
|
||
|
struct mhl3_hid_data *mhid =
|
||
|
((struct hid_add_work_struct *)workdata)->mhid;
|
||
|
struct mhl_dev_context *mdev = mhid->mdev;
|
||
|
struct hid_device *hdev;
|
||
|
struct hid_device_id id = {0};
|
||
|
unsigned int bufsize = HID_MIN_BUFFER_SIZE;
|
||
|
int ret;
|
||
|
|
||
|
mhid->flags |= HID_FLAGS_WQ_ACTIVE;
|
||
|
MHL3_HID_DBG_ERR("WORK QUEUE function executing\n");
|
||
|
|
||
|
mdev->mhl_hid[mhid->id] = mhid;
|
||
|
ret = mhid_fetch_hid_descriptor(mhid); /* Allocates hdesc */
|
||
|
if ((ret < 0) || (mhid->opState == OP_STATE_IDLE))
|
||
|
goto mhid_cleanup;
|
||
|
|
||
|
/* Get a HID device if this is not an update of the existing HID. */
|
||
|
if (mhid->hid == NULL) {
|
||
|
hdev = hid_allocate_device();
|
||
|
if (IS_ERR(hdev)) {
|
||
|
ret = PTR_ERR(hdev);
|
||
|
goto mhid_cleanup;
|
||
|
}
|
||
|
|
||
|
mhid->hid = hdev;
|
||
|
hdev->driver = &mhl3_mt3_hid_driver;
|
||
|
hdev->driver_data = mhid;
|
||
|
id.vendor = mhid->hdesc->wHIDVendorID;
|
||
|
id.product = mhid->hdesc->wHIDProductID;
|
||
|
MHL3_HID_DBG_ERR("Allocated HID\n");
|
||
|
}
|
||
|
hdev = mhid->hid;
|
||
|
|
||
|
hdev->ll_driver = &mhl3_hid_ll_driver;
|
||
|
#if (LINUX_KERNEL_VER < 315)
|
||
|
hdev->hid_get_raw_report = mhl3_hid_get_raw_report;
|
||
|
hdev->hid_output_raw_report = mhl3_hid_output_raw_report;
|
||
|
#endif
|
||
|
|
||
|
hdev->dev.parent = NULL;
|
||
|
hdev->bus = BUS_VIRTUAL;
|
||
|
|
||
|
hdev->version = mhid->hdesc->wBcdHID;
|
||
|
hdev->vendor = mhid->hdesc->wHIDVendorID;
|
||
|
hdev->product = mhid->hdesc->wHIDProductID;
|
||
|
|
||
|
snprintf(hdev->name, sizeof(hdev->name), "MHL3 HID %04hX:%04hX",
|
||
|
hdev->vendor, hdev->product);
|
||
|
|
||
|
/* Check for multitouch device first. */
|
||
|
ret = mhl3_mt_add(mhid, &id);
|
||
|
if (ret) {
|
||
|
MHL3_HID_DBG_INFO("NOT Multitouch, trying generic HID\n");
|
||
|
hdev->driver = &mhl3_hid_driver;
|
||
|
ret = mhl3_hid_report_desc_parse(mhid);
|
||
|
}
|
||
|
if ((ret < 0) || (mhid->opState == OP_STATE_IDLE))
|
||
|
goto mhid_cleanup;
|
||
|
|
||
|
if ((mhid->flags & MHL3_HID_CONNECTED) == 0) {
|
||
|
if (!hidinput_connect(hdev, 0)) {
|
||
|
MHL3_HID_DBG_INFO(
|
||
|
"%s hidinput_connect succeeded\n", __func__);
|
||
|
hdev->claimed |= HID_CLAIMED_INPUT;
|
||
|
mhid->flags |= MHL3_HID_CONNECTED;
|
||
|
|
||
|
} else {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"%s hidinput_connect FAILED\n", __func__);
|
||
|
goto mhid_cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Allocate some report buffers and read the initial state of
|
||
|
* the INPUT and FEATURE reports.
|
||
|
*/
|
||
|
mhl3_hid_find_max_report(hdev, HID_INPUT_REPORT, &bufsize);
|
||
|
mhl3_hid_find_max_report(hdev, HID_OUTPUT_REPORT, &bufsize);
|
||
|
mhl3_hid_find_max_report(hdev, HID_FEATURE_REPORT, &bufsize);
|
||
|
if (bufsize > mhid->bufsize) {
|
||
|
kfree(mhid->in_report_buf);
|
||
|
mhid->in_report_buf = NULL;
|
||
|
ret = mhl3_hid_alloc_buffers(mhid, bufsize);
|
||
|
if (ret)
|
||
|
goto mhid_cleanup;
|
||
|
}
|
||
|
|
||
|
/* if (!(hdev->quirks & HID_QUIRK_NO_INIT_REPORTS))
|
||
|
mhl3_hid_init_reports(hdev);
|
||
|
*/
|
||
|
mhid->opState = OP_STATE_CONNECTED;
|
||
|
|
||
|
MHL3_HID_DBG_ERR("WORK QUEUE function SUCCESS\n");
|
||
|
mhid->flags &= ~HID_FLAGS_WQ_ACTIVE;
|
||
|
return;
|
||
|
|
||
|
mhid_cleanup:
|
||
|
|
||
|
mhl3_send_ack(mhid, HID_ACK_NODEV);
|
||
|
|
||
|
mhid->flags |= HID_FLAGS_WQ_CANCEL;
|
||
|
MHL3_HID_DBG_ERR("WORK QUEUE function FAIL - mhid: %p\n", mhid);
|
||
|
mhl3_disconnect_and_destroy_hid_device(mhid);
|
||
|
|
||
|
/*
|
||
|
* Don't destroy the mhid while an interrupt is in progress on the
|
||
|
* off chance that the message we were waiting for came in after the
|
||
|
* timeout.
|
||
|
*/
|
||
|
if (down_interruptible(&mdev->isr_lock)) {
|
||
|
MHL3_HID_DBG_ERR("Could not acquire isr_lock\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
kfree(mhid->hdesc);
|
||
|
kfree(mhid->in_report_buf);
|
||
|
kfree(mhid);
|
||
|
mdev->mhl_hid[mhid->id] = 0;
|
||
|
|
||
|
up(&mdev->isr_lock);
|
||
|
MHL3_HID_DBG_ERR("WORK QUEUE function exit\n");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Create and initialize the device control structures and get added to
|
||
|
* the system HID device list. This is the equivalent to the probe
|
||
|
* function of normal HID-type drivers, but it is called when the
|
||
|
* MHL transport receives the eMSC Block transfer HID Tunneling request
|
||
|
* message MHL3_DSCRPT_UPDATE
|
||
|
*
|
||
|
* Called from a Titan interrupt handler. All events in the MHL driver
|
||
|
* are handled in interrupt handlers, so the real work here is performed on
|
||
|
* a work queue so the function can wait for responses from the driver.
|
||
|
*/
|
||
|
static int mhid_add(struct mhl_dev_context *mdev, int dev_id)
|
||
|
{
|
||
|
struct mhl3_hid_data *mhid;
|
||
|
int status;
|
||
|
|
||
|
MHL3_HID_DBG_ERR("Adding device %d\n", dev_id);
|
||
|
|
||
|
if (dev_id >= MAX_HID_MESSAGE_CHANNELS)
|
||
|
return -EINVAL;
|
||
|
|
||
|
mhid = kzalloc(sizeof(struct mhl3_hid_data), GFP_KERNEL);
|
||
|
if (!mhid)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
sema_init(&mhid->data_wait_lock, 1);
|
||
|
|
||
|
mhid->mhl3_work.mhid = mhid;
|
||
|
mhid->id = dev_id;
|
||
|
mhid->mdev = mdev;
|
||
|
|
||
|
INIT_WORK((struct work_struct *)&mhid->mhl3_work, mhid_add_work);
|
||
|
status = queue_work(
|
||
|
mdev->hid_work_queue,
|
||
|
(struct work_struct *)&mhid->mhl3_work);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This is the reverse of the mhl3_hid_add() function.
|
||
|
* The device_id parameter is for when we support multiple HID devices.
|
||
|
*/
|
||
|
static int mhid_remove(struct mhl_dev_context *mdev, int dev_id)
|
||
|
{
|
||
|
struct mhl3_hid_data *mhid;
|
||
|
|
||
|
if (dev_id >= MAX_HID_MESSAGE_CHANNELS)
|
||
|
return -EINVAL;
|
||
|
|
||
|
mhid = mdev->mhl_hid[dev_id];
|
||
|
if (mhid == NULL)
|
||
|
return 0;
|
||
|
/*
|
||
|
* If work queue is not active, we need to free the mhid
|
||
|
* here, otherwise let the WQ do it.
|
||
|
*/
|
||
|
if ((mhid->flags & HID_FLAGS_WQ_ACTIVE) == 0) {
|
||
|
mhl3_disconnect_and_destroy_hid_device(mhid);
|
||
|
kfree(mhid->hdesc);
|
||
|
kfree(mhid->in_report_buf);
|
||
|
kfree(mhid);
|
||
|
mdev->mhl_hid[dev_id] = 0;
|
||
|
} else {
|
||
|
mhid->flags |= HID_FLAGS_WQ_CANCEL;
|
||
|
|
||
|
/* Release the data wait to let the work queue finish. */
|
||
|
if (down_trylock(&mhid->data_wait_lock))
|
||
|
MHL3_HID_DBG_ERR("Waiting for data when HID removed\n");
|
||
|
up(&mhid->data_wait_lock);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Remove all MHID devices.
|
||
|
*/
|
||
|
void mhl3_hid_remove_all(struct mhl_dev_context *mdev)
|
||
|
{
|
||
|
int dev_id;
|
||
|
|
||
|
for (dev_id = 0; dev_id < MAX_HID_MESSAGE_CHANNELS; dev_id++) {
|
||
|
if (mdev->mhl_hid[dev_id])
|
||
|
mhid_remove(mdev, dev_id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* We have received a completed MHL3 HID Tunneling message. Parse the
|
||
|
* header to decide what to do with it.
|
||
|
* Called indirectly from the Titan interrupt handler.
|
||
|
*/
|
||
|
static void hid_message_processor(struct mhl_dev_context *mdev,
|
||
|
uint8_t hb0, uint8_t hb1, uint8_t *pmsg, int length)
|
||
|
{
|
||
|
struct mhl3_hid_data *mhid;
|
||
|
uint8_t msg_id;
|
||
|
bool want_ack;
|
||
|
bool matched_expected_message;
|
||
|
int opState, dev_id, hid_msg_count, header_len, ret;
|
||
|
int msg_channel;
|
||
|
|
||
|
dev_id = (hb0 >> 4) & 0x0F;
|
||
|
msg_channel = (hb0 & 0x01);
|
||
|
want_ack = ((hb1 & 0x80) != 0);
|
||
|
hid_msg_count = hb1 & 0x7F;
|
||
|
msg_id = pmsg[0];
|
||
|
matched_expected_message = false;
|
||
|
|
||
|
if (msg_id != 1) {
|
||
|
MHL3_HID_DBG_WARN(
|
||
|
"Received message: msg_id: %02X, deviceID: %02X\n",
|
||
|
msg_id, dev_id);
|
||
|
}
|
||
|
|
||
|
mhid = mdev->mhl_hid[dev_id];
|
||
|
opState = OP_STATE_IDLE;
|
||
|
if (mhid == 0) {
|
||
|
MHL3_HID_DBG_WARN("Message received with mhid == 0\n");
|
||
|
if (msg_id != MHL3_DSCRPT_UPDATE) {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"Message NOT MHL3_DSCRPT_UPDATE: %02X\n",
|
||
|
msg_id);
|
||
|
mhl3_int_send_ack(mdev, HID_ACK_TIMEOUT, hb0);
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if (want_ack) {
|
||
|
if (hid_msg_count !=
|
||
|
(mhid->msg_count[msg_channel] + 1)) {
|
||
|
send_ack_packet(
|
||
|
mdev, hb0,
|
||
|
mhid->msg_count[msg_channel]);
|
||
|
return;
|
||
|
} else {
|
||
|
mhid->msg_count[msg_channel] += 1;
|
||
|
}
|
||
|
}
|
||
|
opState = mhid->opState;
|
||
|
}
|
||
|
|
||
|
/* if (want_ack)
|
||
|
send_ack_packet(mdev, hb0, hb1); */
|
||
|
|
||
|
/*
|
||
|
* If a DSCRPT_UPDATE we need to queue a new device add, no matter
|
||
|
* if the device already exists or not. If it already exists,
|
||
|
* destroy it first.
|
||
|
*/
|
||
|
if ((msg_id == MHL3_DSCRPT_UPDATE) && (mhid != 0)) {
|
||
|
opState = OP_STATE_IDLE;
|
||
|
mhid_remove(mdev, dev_id);
|
||
|
}
|
||
|
|
||
|
switch (opState) {
|
||
|
case OP_STATE_IDLE:
|
||
|
if (msg_id == MHL3_DSCRPT_UPDATE) {
|
||
|
MHL3_HID_DBG_ERR("call mhid_add from OP_STATE_IDLE\n");
|
||
|
ret = mhid_add(mdev, dev_id);
|
||
|
if (ret < 0)
|
||
|
mhl3_int_send_ack(mdev, ret, hb0);
|
||
|
} else {
|
||
|
mhl3_int_send_ack(mdev, HID_ACK_PROTV, hb0);
|
||
|
}
|
||
|
return;
|
||
|
|
||
|
case OP_STATE_WAIT_MHID_DSCRPT:
|
||
|
MHL3_HID_DBG_INFO("OP_STATE_WAIT_MHID_DSCRPT\n");
|
||
|
if ((msg_id == MHL3_MHID_DSCRPT) ||
|
||
|
(msg_id == MHL3_DSCRPT_UPDATE)) {
|
||
|
matched_expected_message = true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case OP_STATE_WAIT_REPORT_DSCRPT:
|
||
|
MHL3_HID_DBG_INFO("OP_STATE_WAIT_REPORT_DSCRPT\n");
|
||
|
if ((msg_id == MHL3_REPORT_DSCRPT) ||
|
||
|
(msg_id == MHL3_DSCRPT_UPDATE))
|
||
|
matched_expected_message = true;
|
||
|
break;
|
||
|
case OP_STATE_WAIT_REPORT:
|
||
|
MHL3_HID_DBG_INFO("OP_STATE_WAIT_REPORT\n");
|
||
|
if ((msg_id == MHL3_REPORT) || (msg_id == MHL3_DSCRPT_UPDATE))
|
||
|
matched_expected_message = true;
|
||
|
break;
|
||
|
|
||
|
case OP_STATE_CONNECTED:
|
||
|
/*
|
||
|
* If sent by the interrupt channel, the message is NOT from a
|
||
|
* MHL3_GET_REPORT request by the host, and we send it
|
||
|
* directly to the HID core. Otherwise, we return it via the
|
||
|
* WORK QUEUE.
|
||
|
*/
|
||
|
switch (msg_id) {
|
||
|
case MHL3_REPORT:
|
||
|
if (msg_channel == HID_ACHID_INT) {
|
||
|
/* Send the report directly to the HID core,
|
||
|
* minus the MSG_ID, REPORT_TYPE, REPORT_ID
|
||
|
* (if present), and LENGTH (2 bytes). */
|
||
|
/*
|
||
|
* According to MHL spec 3.2, the
|
||
|
* mhl_hid_report_msg.id member should reflect
|
||
|
* the report ID for numbered reports, and not
|
||
|
* be present for non-numbered reports.
|
||
|
* However, for numbered reports, the report
|
||
|
* number is already in the data being passed,
|
||
|
* and we don't need it for anything. Since
|
||
|
* the HID device side driver does not
|
||
|
* parse the HID data, it has no way to tell
|
||
|
* if the report is numbered. Therefore, it
|
||
|
* ALWAYS includes this byte and sets it to 0.
|
||
|
*/
|
||
|
header_len =
|
||
|
sizeof(struct mhl_hid_report_msg) - 1;
|
||
|
ret = hid_input_report(
|
||
|
mhid->hid, HID_INPUT_REPORT,
|
||
|
pmsg + header_len,
|
||
|
length - header_len, 1);
|
||
|
return;
|
||
|
}
|
||
|
matched_expected_message = true;
|
||
|
break;
|
||
|
case MHL3_DSCRPT_UPDATE:
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"call mhid_add from OP_STATE_CONNECTED\n");
|
||
|
ret = mhid_add(mdev, dev_id);
|
||
|
if (ret < 0)
|
||
|
mhl3_int_send_ack(mdev, ret, hb0);
|
||
|
return;
|
||
|
break;
|
||
|
default:
|
||
|
if (msg_id != MHL3_HID_ACK)
|
||
|
matched_expected_message = true;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!matched_expected_message && (msg_id != MHL3_HID_ACK)) {
|
||
|
mhl3_int_send_ack(mdev, HID_ACK_PROTV, hb0);
|
||
|
return;
|
||
|
} else if (matched_expected_message && (msg_id == MHL3_DSCRPT_UPDATE)) {
|
||
|
|
||
|
/*
|
||
|
* opstate is OP_STATE_WAIT_MHID_DSCRPT,
|
||
|
* OP_STATE_WAIT_REPORT_DSCRPT, or OP_STATE_WAIT_REPORT, and
|
||
|
* we're still running the work queue function, so we have to
|
||
|
* restart it.
|
||
|
*/
|
||
|
mhid->opState = OP_STATE_IDLE;
|
||
|
}
|
||
|
|
||
|
/* If someone was waiting for this data, let them know it's here. */
|
||
|
if (down_trylock(&mhid->data_wait_lock)) {
|
||
|
memcpy(mhid->in_data, pmsg, length);
|
||
|
mhid->in_data_length = length;
|
||
|
} else {
|
||
|
/* TODO: If not on a WORK QUEUE, where does it go? */
|
||
|
MHL3_HID_DBG_ERR("Sending received msg into the ether!!!\n");
|
||
|
}
|
||
|
/*
|
||
|
* Either the try was successful, in which case no one was waiting
|
||
|
* for the message, or it wasn't, meaning someone WAS waiting. In
|
||
|
* either case we need to release the semaphore.
|
||
|
*/
|
||
|
up(&mhid->data_wait_lock);
|
||
|
}
|
||
|
|
||
|
static void validate_ack(struct mhl_dev_context *mdev,
|
||
|
uint8_t *pmsg, int length)
|
||
|
{
|
||
|
/* uint8_t ack_msg, channel; */
|
||
|
int dev_id;
|
||
|
struct mhl3_hid_data *mhid;
|
||
|
|
||
|
if (length != HID_ACK_PACKET_LEN)
|
||
|
return;
|
||
|
|
||
|
dev_id = (pmsg[1] & HID_HB0_DEV_ID_MSK) >> 4;
|
||
|
mhid = mdev->mhl_hid[dev_id];
|
||
|
if (mhid == 0)
|
||
|
return;
|
||
|
|
||
|
/*
|
||
|
ack_msg = pmsg[0] & HID_FRAG_HB0_CNT_MSK;
|
||
|
channel = pmsg[1] & HID_HB0_ACHID_MSK;
|
||
|
if (ack_msg == mhid->msg_count[channel])
|
||
|
resend_last_message();
|
||
|
*/
|
||
|
}
|
||
|
/*
|
||
|
* Accumulate fragments of a HID message until the entire message has
|
||
|
* been received, then dispatch it properly.
|
||
|
* Called from the Titan interrupt
|
||
|
*/
|
||
|
void build_received_hid_message(struct mhl_dev_context *mdev,
|
||
|
uint8_t *pmsg, int length)
|
||
|
{
|
||
|
int i, fragment_count = 0;
|
||
|
uint16_t *pchksum;
|
||
|
uint16_t accum, msg_chksum;
|
||
|
struct mhl3_hid_global_data *emsc = &mdev->mhl_ghid;
|
||
|
|
||
|
if (length == 0)
|
||
|
return;
|
||
|
switch (emsc->hid_receive_state) {
|
||
|
|
||
|
case EMSC_RCV_MSG_START:
|
||
|
|
||
|
if ((pmsg[0] & HID_FRAG_HB0_TYPE) == HID_FRAG_HB0_TYPE_ACK) {
|
||
|
validate_ack(mdev, pmsg, length);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fragment_count = pmsg[0];
|
||
|
if (length >= (HID_FRAG_HEADER_LEN +
|
||
|
HID_MSG_HEADER_LEN +
|
||
|
HID_MSG_CHKSUM_LEN + 1)) {
|
||
|
emsc->hb0 = pmsg[1];
|
||
|
emsc->hb1 = pmsg[2];
|
||
|
length -= 3;
|
||
|
memcpy(&emsc->in_buf[0], &pmsg[3], length);
|
||
|
}
|
||
|
emsc->msg_length = length;
|
||
|
emsc->hid_receive_state = EMSC_RCV_MSG_NEXT;
|
||
|
break;
|
||
|
|
||
|
case EMSC_RCV_MSG_NEXT:
|
||
|
fragment_count = pmsg[0];
|
||
|
if (length >= (HID_FRAG_HEADER_LEN + 1)) {
|
||
|
length--;
|
||
|
memcpy(&emsc->in_buf[emsc->msg_length],
|
||
|
&pmsg[1], length);
|
||
|
emsc->msg_length += length;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
/* This is an error, so don't dispatch anything. */
|
||
|
fragment_count = 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* If the end of the message, dispatch it. */
|
||
|
if (fragment_count == 0) {
|
||
|
emsc->hid_receive_state = EMSC_RCV_MSG_START;
|
||
|
accum = 0;
|
||
|
pchksum = (uint16_t *)&emsc->in_buf[0];
|
||
|
emsc->msg_length -= HID_MSG_CHKSUM_LEN;
|
||
|
for (i = 0; i < (emsc->msg_length / 2); i++)
|
||
|
accum += pchksum[i];
|
||
|
accum += ((uint16_t)emsc->hb0 | (((uint16_t)emsc->hb1) << 8));
|
||
|
if (emsc->msg_length & 0x01)
|
||
|
accum += ((uint16_t)emsc->in_buf[emsc->msg_length - 1]);
|
||
|
|
||
|
/* Add in length of message including HB0/HB1. */
|
||
|
accum += emsc->msg_length + HID_MSG_HEADER_LEN;
|
||
|
|
||
|
msg_chksum = *((uint16_t *)&emsc->in_buf[emsc->msg_length]);
|
||
|
if (accum != msg_chksum) {
|
||
|
MHL3_HID_DBG_ERR(
|
||
|
"HID MSG CKSM fail, ignoring message: "
|
||
|
"Act: %04X Exp: %04X\n",
|
||
|
accum, msg_chksum);
|
||
|
|
||
|
/* Request a re-try. */
|
||
|
if (emsc->hb1 & EMSC_HID_HB1_ACK) {
|
||
|
send_ack_packet(
|
||
|
mdev, emsc->hb0,
|
||
|
(emsc->hb1 & EMSC_HID_HB1_MSG_CNT_FLD)
|
||
|
- 1);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
hid_message_processor(mdev,
|
||
|
emsc->hb0, emsc->hb1, emsc->in_buf,
|
||
|
emsc->msg_length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef SI_CONFIG_PM_SLEEP /* this was originally CONFIG_PM_SLEEP,
|
||
|
* but we don't want the compiler warnings
|
||
|
*/
|
||
|
/*
|
||
|
* TODO: This is used during system suspend and hibernation as well
|
||
|
* as normal runtime PM. Needs work.
|
||
|
*/
|
||
|
static int mhl3_hid_suspend(struct device *dev)
|
||
|
{
|
||
|
/* struct i2c_client *client = to_i2c_client(dev);
|
||
|
|
||
|
if (device_may_wakeup(&client->dev))
|
||
|
enable_irq_wake(client->irq);
|
||
|
|
||
|
mhl3_hid_set_power(client, I2C_HID_PWR_SLEEP);
|
||
|
*/
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* TODO: This is used during system suspend and hibernation as well
|
||
|
* as normal runtime PM. Needs work.
|
||
|
*/
|
||
|
static int mhl3_hid_resume(struct device *dev)
|
||
|
{
|
||
|
/* int ret;
|
||
|
struct i2c_client *client = to_i2c_client(dev);
|
||
|
|
||
|
ret = i2c_hid_hwreset(client);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (device_may_wakeup(&client->dev))
|
||
|
disable_irq_wake(client->irq);
|
||
|
*/
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|