// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2017-2018 The Linux Foundation. All rights reserved.
 */

#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/rpmsg.h>
#include <ipc/apr_tal.h>

enum apr_channel_state {
	APR_CH_DISCONNECTED,
	APR_CH_CONNECTED,
};

#define APR_MAXIMUM_NUM_OF_RETRIES 2

static struct apr_svc_ch_dev
	apr_svc_ch[APR_DL_MAX][APR_DEST_MAX][APR_CLIENT_MAX];

/**
 * apr_tal_write() - Write a message across to the remote processor
 * @apr_ch: apr channel handle
 * @data: buffer that needs to be transferred over the channel
 * @pkt_priv: private data of the packet
 * @len: length of the buffer
 *
 * Returns len of buffer successfully transferred on success
 * and an appropriate error value on failure.
 */
int apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data,
			   struct apr_pkt_priv *pkt_priv, int len)
{
	int rc = 0, retries = 0;
	unsigned long flags;
	struct rpmsg_device *rpdev = NULL;

	if (!apr_ch || len > APR_MAX_BUF ||
	    apr_ch->channel_state != APR_CH_CONNECTED)
		return -EINVAL;

	spin_lock_irqsave(&apr_ch->w_lock, flags);
	rpdev = apr_ch->handle;
	if (!rpdev) {
		spin_unlock_irqrestore(&apr_ch->w_lock, flags);
		return -EINVAL;
	}

	do {
		if (rc == -EAGAIN)
			udelay(50);
		rc = rpmsg_trysend(rpdev->ept, data, len);
	} while (rc == -EAGAIN && retries++ < APR_MAXIMUM_NUM_OF_RETRIES);
	spin_unlock_irqrestore(&apr_ch->w_lock, flags);

	if (rc)
		pr_err("%s: Unable to send the packet, rc:%d\n", __func__, rc);
	else
		rc = len;

	return rc;
}
EXPORT_SYMBOL(apr_tal_write);

/**
 * apr_tal_rx_intents_config() - Configure glink intents for remote processor
 * @apr_ch: apr channel handle
 * @num_of_intents: number of intents
 * @size: size of the intents
 *
 * This api is not supported with RPMSG. Returns 0 to indicate success
 */
int apr_tal_rx_intents_config(struct apr_svc_ch_dev *apr_ch,
			      int num_of_intents, uint32_t size)
{
	pr_debug("%s: NO-OP\n", __func__);
	return 0;
}
EXPORT_SYMBOL(apr_tal_rx_intents_config);

/**
 * apr_tal_start_rx_rt() - Set RT thread priority for APR RX transfer
 * @apr_ch: apr channel handle
 *
 * This api is not supported with RPMSG as message transfer occurs
 * in client's context. Returns 0 to indicate success.
 */
int apr_tal_start_rx_rt(struct apr_svc_ch_dev *apr_ch)
{
	pr_debug("%s: NO-OP\n", __func__);
	return 0;
}
EXPORT_SYMBOL(apr_tal_start_rx_rt);

/**
 * apr_tal_end_rx_rt() - Remove RT thread priority for APR RX transfer
 * @apr_ch: apr channel handle
 *
 * This api is not supported with RPMSG. Returns 0 to indicate success
 */
int apr_tal_end_rx_rt(struct apr_svc_ch_dev *apr_ch)
{
	pr_debug("%s: NO-OP\n", __func__);
	return 0;
}
EXPORT_SYMBOL(apr_tal_end_rx_rt);

/**
 * apr_tal_open() - Open a transport channel for data transfer
 * on remote processor.
 * @clnt: apr client, audio or voice
 * @dest: destination remote processor for which apr channel is requested for.
 * @dl: type of data link
 * @func: callback function to handle data transfer from remote processor
 * @priv: private data of the client
 *
 * Returns apr_svc_ch_dev handle on success and NULL on failure.
 */
struct apr_svc_ch_dev *apr_tal_open(uint32_t clnt, uint32_t dest, uint32_t dl,
				    apr_svc_cb_fn func, void *priv)
{
	int rc = 0;
	struct apr_svc_ch_dev *apr_ch = NULL;

	if ((clnt != APR_CLIENT_AUDIO) || (dest != APR_DEST_QDSP6) ||
	    (dl != APR_DL_SMD)) {
		pr_err("%s: Invalid params, clnt:%d, dest:%d, dl:%d\n",
		       __func__, clnt, dest, dl);
		return NULL;
	}

	apr_ch = &apr_svc_ch[APR_DL_SMD][APR_DEST_QDSP6][APR_CLIENT_AUDIO];
	mutex_lock(&apr_ch->m_lock);
	if (!apr_ch->handle) {
		rc = wait_event_timeout(apr_ch->wait,
			(apr_ch->channel_state == APR_CH_CONNECTED), 5 * HZ);

		if (rc == 0) {
			pr_err("%s: TIMEOUT for APR_CH_CONNECTED event\n",
				__func__);
			rc = -ETIMEDOUT;
			goto unlock;
		}
	}

	pr_debug("%s: Channel connected, returning handle :%pK\n",
			 __func__, apr_ch->handle);
	apr_ch->func = func;
	apr_ch->priv = priv;

unlock:
	mutex_unlock(&apr_ch->m_lock);
	return rc ? NULL : apr_ch;
}
EXPORT_SYMBOL(apr_tal_open);

/**
 * apr_tal_close() - Close transport channel on remote processor.
 * @apr_ch: apr channel handle
 *
 * Returns 0 on success and an appropriate error value on failure.
 */
int apr_tal_close(struct apr_svc_ch_dev *apr_ch)
{
	int rc = 0;

	if (!apr_ch || !apr_ch->handle) {
		rc = -EINVAL;
		goto exit;
	}

	mutex_lock(&apr_ch->m_lock);
	apr_ch->func = NULL;
	apr_ch->priv = NULL;
	mutex_unlock(&apr_ch->m_lock);

exit:
	return rc;
}
EXPORT_SYMBOL(apr_tal_close);

static int apr_tal_rpmsg_callback(struct rpmsg_device *rpdev,
				  void *data, int len, void *priv, u32 addr)
{
	struct apr_svc_ch_dev *apr_ch = dev_get_drvdata(&rpdev->dev);
	unsigned long flags;

	if (!apr_ch || !data) {
		pr_err("%s: Invalid apr_ch or ptr\n", __func__);
		return -EINVAL;
	}

	dev_dbg(&rpdev->dev, "%s: Rx packet received, len:%d\n",
		__func__, len);

	spin_lock_irqsave(&apr_ch->r_lock, flags);
	if (apr_ch->func)
		apr_ch->func((void *)data, len, apr_ch->priv);
	spin_unlock_irqrestore(&apr_ch->r_lock, flags);

	return 0;
}

static int apr_tal_rpmsg_probe(struct rpmsg_device *rpdev)
{
	struct apr_svc_ch_dev *apr_ch = NULL;

	if (!strcmp(rpdev->id.name, "apr_audio_svc")) {
		dev_info(&rpdev->dev, "%s: Channel[%s] state[Up]\n",
			 __func__, rpdev->id.name);

		apr_ch =
		&apr_svc_ch[APR_DL_SMD][APR_DEST_QDSP6][APR_CLIENT_AUDIO];
		apr_ch->handle = rpdev;
		apr_ch->channel_state = APR_CH_CONNECTED;
		dev_set_drvdata(&rpdev->dev, apr_ch);
		wake_up(&apr_ch->wait);
	} else {
		dev_err(&rpdev->dev, "%s, Invalid Channel [%s]\n",
			__func__, rpdev->id.name);
		return -EINVAL;
	}

	return 0;
}

static void apr_tal_rpmsg_remove(struct rpmsg_device *rpdev)
{
	struct apr_svc_ch_dev *apr_ch = dev_get_drvdata(&rpdev->dev);

	if (!apr_ch) {
		dev_err(&rpdev->dev, "%s: Invalid apr_ch\n", __func__);
		return;
	}

	dev_info(&rpdev->dev, "%s: Channel[%s] state[Down]\n",
		 __func__, rpdev->id.name);
	apr_ch->handle = NULL;
	apr_ch->channel_state = APR_CH_DISCONNECTED;
	dev_set_drvdata(&rpdev->dev, NULL);
}

static const struct rpmsg_device_id apr_tal_rpmsg_match[] = {
	{ "apr_audio_svc" },
	{}
};

static struct rpmsg_driver apr_tal_rpmsg_driver = {
	.probe = apr_tal_rpmsg_probe,
	.remove = apr_tal_rpmsg_remove,
	.callback = apr_tal_rpmsg_callback,
	.id_table = apr_tal_rpmsg_match,
	.drv = {
		.name = "apr_tal_rpmsg",
	},
};

/**
 * apr_tal_int() - Registers rpmsg driver with rpmsg framework.
 *
 * Returns 0 on success and an appropriate error value on failure.
 */
int apr_tal_init(void)
{
	int i, j, k;
	int ret;

	memset(apr_svc_ch, 0, sizeof(struct apr_svc_ch_dev));
	for (i = 0; i < APR_DL_MAX; i++) {
		for (j = 0; j < APR_DEST_MAX; j++) {
			for (k = 0; k < APR_CLIENT_MAX; k++) {
				init_waitqueue_head(&apr_svc_ch[i][j][k].wait);
				spin_lock_init(&apr_svc_ch[i][j][k].w_lock);
				spin_lock_init(&apr_svc_ch[i][j][k].r_lock);
				mutex_init(&apr_svc_ch[i][j][k].m_lock);
			}
		}
	}
	ret = register_rpmsg_driver(&apr_tal_rpmsg_driver);
	return ret;
}
EXPORT_SYMBOL(apr_tal_init);

/**
 * apr_tal_exit() - De-register rpmsg driver with rpmsg framework.
 */
void apr_tal_exit(void)
{
	unregister_rpmsg_driver(&apr_tal_rpmsg_driver);
}
EXPORT_SYMBOL(apr_tal_exit);

