/* Copyright (c) 2018, 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/debugfs.h>
#include <linux/device.h>
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/termios.h>
#include <linux/wait.h>
#include "mhi.h"
#include "mhi_internal.h"

struct __packed dtr_ctrl_msg {
	u32 preamble;
	u32 msg_id;
	u32 dest_id;
	u32 size;
	u32 msg;
};

#define CTRL_MAGIC (0x4C525443)
#define CTRL_MSG_DTR BIT(0)
#define CTRL_MSG_ID (0x10)

static int mhi_dtr_tiocmset(struct mhi_controller *mhi_cntrl,
			    struct mhi_chan *mhi_chan,
			    u32 tiocm)
{
	struct dtr_ctrl_msg *dtr_msg = NULL;
	struct mhi_chan *dtr_chan = mhi_cntrl->dtr_dev->ul_chan;
	int ret = 0;

	tiocm &= TIOCM_DTR;
	if (mhi_chan->tiocm == tiocm)
		return 0;

	mutex_lock(&dtr_chan->mutex);

	dtr_msg = kzalloc(sizeof(*dtr_msg), GFP_KERNEL);
	if (!dtr_msg) {
		ret = -ENOMEM;
		goto tiocm_exit;
	}

	dtr_msg->preamble = CTRL_MAGIC;
	dtr_msg->msg_id = CTRL_MSG_ID;
	dtr_msg->dest_id = mhi_chan->chan;
	dtr_msg->size = sizeof(u32);
	if (tiocm & TIOCM_DTR)
		dtr_msg->msg |= CTRL_MSG_DTR;

	reinit_completion(&dtr_chan->completion);
	ret = mhi_queue_transfer(mhi_cntrl->dtr_dev, DMA_TO_DEVICE, dtr_msg,
				 sizeof(*dtr_msg), MHI_EOT);
	if (ret)
		goto tiocm_exit;

	ret = wait_for_completion_timeout(&dtr_chan->completion,
				msecs_to_jiffies(mhi_cntrl->timeout_ms));

	if (!ret) {
		MHI_ERR("Failed to receive transfer callback\n");
		ret = -EIO;
		goto tiocm_exit;
	}

	ret = 0;
	mhi_chan->tiocm = tiocm;

tiocm_exit:
	kfree(dtr_msg);
	mutex_unlock(&dtr_chan->mutex);

	return ret;
}

long mhi_ioctl(struct mhi_device *mhi_dev, unsigned int cmd, unsigned long arg)
{
	struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;
	struct mhi_chan *mhi_chan = mhi_dev->ul_chan;
	int ret;

	/* ioctl not supported by this controller */
	if (!mhi_cntrl->dtr_dev)
		return -EIO;

	switch (cmd) {
	case TIOCMGET:
		return mhi_chan->tiocm;
	case TIOCMSET:
	{
		u32 tiocm;

		ret = get_user(tiocm, (u32 *)arg);
		if (ret)
			return ret;

		return mhi_dtr_tiocmset(mhi_cntrl, mhi_chan, tiocm);
	}
	default:
		break;
	}

	return -EINVAL;
}
EXPORT_SYMBOL(mhi_ioctl);

static void mhi_dtr_xfer_cb(struct mhi_device *mhi_dev,
			    struct mhi_result *mhi_result)
{
	struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;
	struct mhi_chan *dtr_chan = mhi_cntrl->dtr_dev->ul_chan;

	MHI_VERB("Received with status:%d\n", mhi_result->transaction_status);
	if (!mhi_result->transaction_status)
		complete(&dtr_chan->completion);
}

static void mhi_dtr_remove(struct mhi_device *mhi_dev)
{
	struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;

	mhi_cntrl->dtr_dev = NULL;
}

static int mhi_dtr_probe(struct mhi_device *mhi_dev,
			 const struct mhi_device_id *id)
{
	struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl;
	int ret;

	MHI_LOG("Enter for DTR control channel\n");

	ret = mhi_prepare_for_transfer(mhi_dev);
	if (!ret)
		mhi_cntrl->dtr_dev = mhi_dev;

	MHI_LOG("Exit with ret:%d\n", ret);

	return ret;
}

static const struct mhi_device_id mhi_dtr_table[] = {
	{ .chan = "IP_CTRL" },
	{ },
};

static struct mhi_driver mhi_dtr_driver = {
	.id_table = mhi_dtr_table,
	.remove = mhi_dtr_remove,
	.probe = mhi_dtr_probe,
	.ul_xfer_cb = mhi_dtr_xfer_cb,
	.dl_xfer_cb = mhi_dtr_xfer_cb,
	.driver = {
		.name = "MHI_DTR",
		.owner = THIS_MODULE,
	}
};

int __init mhi_dtr_init(void)
{
	return mhi_driver_register(&mhi_dtr_driver);
}

void mhi_dtr_exit(void) {
	mhi_driver_unregister(&mhi_dtr_driver);
}
