// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2022 Qualcomm Innovation Center, Inc. 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\n", rc);
			return;
		}

		rc = qrtr_endpoint_register(&qep->ep, qep->net_id, qep->rt, NULL);
		if (rc) {
			dev_err(qep->dev, "register failed %d\n", rc);
			qrtr_mhi_dev_close_channels(qep);
		}
		break;
	case MHI_STATE_DISCONNECTED:
		mutex_lock(&qep->out_lock);
		complete_all(&qep->out_tre);
		mutex_unlock(&qep->out_lock);

		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");

