blob: 7d52b19d21553586df115b50f292ae9ec7237af7 [file] [log] [blame]
/* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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/platform_device.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/of.h>
#include <linux/msm_mhi_dev.h>
#include "qrtr.h"
#define QRTR_MAX_PKT_SIZE SZ_32K
/* MHI DEV Enums are defined from Host perspective */
#define QRTR_MHI_DEV_OUT MHI_CLIENT_IPCR_IN
#define QRTR_MHI_DEV_IN MHI_CLIENT_IPCR_OUT
/**
* struct qrtr_mhi_dev_ep - qrtr mhi device endpoint
* @ep: endpoint
* @dev: device from platform bus
* @out: channel handle from mhi dev
* @out_tre: complete when channel is ready to send
* @out_lock: hold when resetting completion variable
* @in: channel handle from mhi dev
* @buf_in: buffer to hold incoming data
* @net_id: subnet id used by qrtr core
* @rt: realtime option used by qrtr core
*/
struct qrtr_mhi_dev_ep {
struct qrtr_endpoint ep;
struct device *dev;
struct mhi_dev_client *out;
struct completion out_tre;
struct mutex out_lock; /* for out critical sections */
struct mhi_dev_client *in;
void *buf_in;
u32 net_id;
bool rt;
};
static struct qrtr_mhi_dev_ep *qrtr_mhi_device_endpoint;
static int qrtr_mhi_dev_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
{
struct qrtr_mhi_dev_ep *qep;
struct mhi_req req = { 0 };
int rc;
qep = container_of(ep, struct qrtr_mhi_dev_ep, ep);
rc = skb_linearize(skb);
if (rc) {
kfree_skb(skb);
return rc;
}
req.chan = QRTR_MHI_DEV_OUT;
req.client = qep->out;
req.mode = DMA_SYNC;
req.buf = skb->data;
req.len = skb->len;
do {
wait_for_completion(&qep->out_tre);
mutex_lock(&qep->out_lock);
rc = mhi_dev_write_channel(&req);
if (rc == 0)
reinit_completion(&qep->out_tre);
mutex_unlock(&qep->out_lock);
} while (!rc);
if (rc != skb->len) {
dev_err(qep->dev, "send failed rc:%d len:%d\n", rc, skb->len);
kfree_skb(skb);
return rc;
}
consume_skb(skb);
return 0;
}
static void qrtr_mhi_dev_read(struct qrtr_mhi_dev_ep *qep)
{
struct mhi_req req = { 0 };
int rc;
int bytes_read;
req.chan = QRTR_MHI_DEV_IN;
req.client = qep->in;
req.mode = DMA_SYNC;
req.buf = qep->buf_in;
req.len = QRTR_MAX_PKT_SIZE;
do {
bytes_read = mhi_dev_read_channel(&req);
if (bytes_read < 0) {
dev_err(qep->dev, "failed to read channel %d\n",
bytes_read);
return;
}
if (bytes_read == 0)
return;
rc = qrtr_endpoint_post(&qep->ep, req.buf, req.transfer_len);
if (rc == -EINVAL)
dev_err(qep->dev, "invalid ipcrouter packet\n");
} while (bytes_read > 0);
}
static void qrtr_mhi_dev_event_cb(struct mhi_dev_client_cb_reason *reason)
{
struct qrtr_mhi_dev_ep *qep;
qep = qrtr_mhi_device_endpoint;
if (!qep)
return;
if (reason->reason == MHI_DEV_TRE_AVAILABLE) {
pr_debug("TRE available event for chan %d\n", reason->ch_id);
if (reason->ch_id == QRTR_MHI_DEV_IN) {
qrtr_mhi_dev_read(qep);
} else {
mutex_lock(&qep->out_lock);
complete_all(&qep->out_tre);
mutex_unlock(&qep->out_lock);
}
}
}
static int qrtr_mhi_dev_open_channels(struct qrtr_mhi_dev_ep *qep)
{
int rc;
/* write channel */
rc = mhi_dev_open_channel(QRTR_MHI_DEV_IN, &qep->in,
qrtr_mhi_dev_event_cb);
if (rc < 0)
return rc;
/* read channel */
rc = mhi_dev_open_channel(QRTR_MHI_DEV_OUT, &qep->out,
qrtr_mhi_dev_event_cb);
if (rc < 0) {
mhi_dev_close_channel(qep->in);
return rc;
}
return 0;
}
static void qrtr_mhi_dev_close_channels(struct qrtr_mhi_dev_ep *qep)
{
mhi_dev_close_channel(qep->in);
mhi_dev_close_channel(qep->out);
}
static void qrtr_mhi_dev_state_cb(struct mhi_dev_client_cb_data *cb_data)
{
struct qrtr_mhi_dev_ep *qep;
int rc;
if (!cb_data || !cb_data->user_data)
return;
qep = cb_data->user_data;
switch (cb_data->ctrl_info) {
case MHI_STATE_CONNECTED:
rc = qrtr_mhi_dev_open_channels(qep);
if (rc) {
dev_err(qep->dev, "open failed %d", rc);
return;
}
rc = qrtr_endpoint_register(&qep->ep, qep->net_id, qep->rt);
if (rc) {
dev_err(qep->dev, "register failed %d", rc);
qrtr_mhi_dev_close_channels(qep);
}
break;
case MHI_STATE_DISCONNECTED:
qrtr_endpoint_unregister(&qep->ep);
qrtr_mhi_dev_close_channels(qep);
break;
default:
break;
}
}
static int qrtr_mhi_dev_probe(struct platform_device *pdev)
{
struct qrtr_mhi_dev_ep *qep;
struct device_node *node;
int rc;
struct mhi_dev_client_cb_data cb_data;
qep = devm_kzalloc(&pdev->dev, sizeof(*qep), GFP_KERNEL);
if (!qep)
return -ENOMEM;
qep->dev = &pdev->dev;
node = pdev->dev.of_node;
rc = of_property_read_u32(node, "qcom,net-id", &qep->net_id);
if (rc < 0)
qep->net_id = QRTR_EP_NET_ID_AUTO;
qep->rt = of_property_read_bool(node, "qcom,low-latency");
qep->buf_in = devm_kzalloc(&pdev->dev, QRTR_MAX_PKT_SIZE, GFP_KERNEL);
if (!qep->buf_in)
return -ENOMEM;
qrtr_mhi_device_endpoint = qep;
mutex_init(&qep->out_lock);
init_completion(&qep->out_tre);
qep->ep.xmit = qrtr_mhi_dev_send;
/* HOST init TX first followed by RX, so register for endpoint TX
* which makes both channel ready by checking one channel state.
*/
rc = mhi_register_state_cb(qrtr_mhi_dev_state_cb, qep,
QRTR_MHI_DEV_OUT);
if (rc == -EEXIST) {
/**
* MHI stack will return -EEXIST if mhi channel is already
* opend by the host and will not invoke reqistered callback.
* But future state change notification will inform through
* registered callback.
*/
complete_all(&qep->out_tre);
cb_data.user_data = (void *)qep;
cb_data.channel = QRTR_MHI_DEV_OUT;
cb_data.ctrl_info = MHI_STATE_CONNECTED;
qrtr_mhi_dev_state_cb(&cb_data);
} else if (rc) {
return rc;
}
return 0;
}
static const struct of_device_id qrtr_mhi_dev_match_table[] = {
{ .compatible = "qcom,qrtr-mhi-dev"},
{},
};
static struct platform_driver qrtr_mhi_dev_driver = {
.probe = qrtr_mhi_dev_probe,
.driver = {
.name = "qrtr_mhi_dev",
.of_match_table = qrtr_mhi_dev_match_table,
},
};
module_platform_driver(qrtr_mhi_dev_driver);
MODULE_DESCRIPTION("QTI IPC-Router MHI device interface driver");
MODULE_LICENSE("GPL v2");