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

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/vmalloc.h>
#include <linux/rpmsg.h>
#include "sound/wcd-dsp-glink.h"

#define WDSP_GLINK_DRIVER_NAME "wcd-dsp-glink"
#define WDSP_MAX_WRITE_SIZE (256 * 1024)
#define WDSP_MAX_READ_SIZE (4 * 1024)
#define WDSP_WRITE_PKT_SIZE (sizeof(struct wdsp_write_pkt))
#define WDSP_CMD_PKT_SIZE (sizeof(struct wdsp_cmd_pkt))

#define MINOR_NUMBER_COUNT 1
#define RESP_QUEUE_SIZE 3
#define TIMEOUT_MS 2000

enum wdsp_ch_state {
	WDSP_CH_DISCONNECTED,
	WDSP_CH_CONNECTED,
};

struct wdsp_glink_dev {
	struct class *cls;
	struct device *dev;
	struct cdev cdev;
	dev_t dev_num;
};

struct wdsp_rsp_que {
	/* Size of valid data in buffer */
	u32 buf_size;

	/* Response buffer */
	u8 buf[WDSP_MAX_READ_SIZE];
};

struct wdsp_ch {
	struct wdsp_glink_priv *wpriv;
	/* rpmsg handle */
	void *handle;
	/* Channel states like connect, disconnect */
	int ch_state;
	char ch_name[RPMSG_NAME_SIZE];
	spinlock_t ch_lock;
};

struct wdsp_tx_buf {
	struct work_struct tx_work;

	/* Glink channel information */
	struct wdsp_ch *ch;

	/* Tx buffer to send to glink */
	u8 buf[0];
};

struct wdsp_glink_priv {
	/* Respone buffer related */
	u8 rsp_cnt;
	struct wdsp_rsp_que rsp[RESP_QUEUE_SIZE];
	u8 write_idx;
	u8 read_idx;
	struct completion rsp_complete;
	spinlock_t rsp_lock;

	/* Glink channel related */
	int no_of_channels;
	struct wdsp_ch **ch;
	struct workqueue_struct *work_queue;
	/* Wait for all channels state before sending any command */
	wait_queue_head_t ch_state_wait;

	struct wdsp_glink_dev *wdev;
	struct device *dev;
};
static struct wdsp_glink_priv *wpriv;

static struct wdsp_ch *wdsp_get_ch(char *ch_name)
{
	int i;

	for (i = 0; i < wpriv->no_of_channels; i++) {
		if (!strcmp(ch_name, wpriv->ch[i]->ch_name))
			return wpriv->ch[i];
	}
	return NULL;
}

/*
 * wdsp_rpmsg_callback - Rpmsg callback for responses
 * rpdev:     Rpmsg device structure
 * data:      Pointer to the Rx data
 * len:       Size of the Rx data
 * priv:      Private pointer to the channel
 * addr:      Address variable
 * Returns 0 on success and an appropriate error value on failure
 */
static int wdsp_rpmsg_callback(struct rpmsg_device *rpdev, void *data,
				int len, void *priv, u32 addr__unused)
{
	struct wdsp_ch *ch = dev_get_drvdata(&rpdev->dev);
	struct wdsp_glink_priv *wpriv;
	unsigned long flags;
	u8 *rx_buf;
	u8 rsp_cnt = 0;

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

	wpriv = ch->wpriv;
	rx_buf = (u8 *)data;
	if (len > WDSP_MAX_READ_SIZE) {
		dev_info_ratelimited(wpriv->dev, "%s: Size %d is greater than allowed %d\n",
			__func__, len, WDSP_MAX_READ_SIZE);
		len = WDSP_MAX_READ_SIZE;
	}
	dev_dbg_ratelimited(wpriv->dev, "%s: copy into buffer %d\n", __func__,
			    wpriv->rsp_cnt);

	if (wpriv->rsp_cnt >= RESP_QUEUE_SIZE) {
		dev_info_ratelimited(wpriv->dev, "%s: Resp Queue is Full. Ignore new one.\n",
				      __func__);
		return -EINVAL;
	}
	spin_lock_irqsave(&wpriv->rsp_lock, flags);
	rsp_cnt = wpriv->rsp_cnt;
	memcpy(wpriv->rsp[wpriv->write_idx].buf, rx_buf, len);
	wpriv->rsp[wpriv->write_idx].buf_size = len;
	wpriv->write_idx = (wpriv->write_idx + 1) % RESP_QUEUE_SIZE;
	wpriv->rsp_cnt = ++rsp_cnt;
	spin_unlock_irqrestore(&wpriv->rsp_lock, flags);

	complete(&wpriv->rsp_complete);

	return 0;
}

/*
 * wdsp_rpmsg_probe - Rpmsg channel probe function
 * rpdev:     Rpmsg device structure
 * Returns 0 on success and an appropriate error value on failure
 */
static int wdsp_rpmsg_probe(struct rpmsg_device *rpdev)
{
	struct wdsp_ch *ch;

	ch = wdsp_get_ch(rpdev->id.name);
	if (!ch) {
		dev_err(&rpdev->dev, "%s, Invalid Channel [%s]\n",
			__func__, rpdev->id.name);
		return -EINVAL;
	}

	dev_dbg(&rpdev->dev, "%s: Channel[%s] state[Up]\n",
		 __func__, rpdev->id.name);

	spin_lock(&ch->ch_lock);
	ch->handle = rpdev;
	ch->ch_state = WDSP_CH_CONNECTED;
	spin_unlock(&ch->ch_lock);
	dev_set_drvdata(&rpdev->dev, ch);
	wake_up(&wpriv->ch_state_wait);

	return 0;
}

/*
 * wdsp_rpmsg_remove - Rpmsg channel remove function
 * rpdev:     Rpmsg device structure
 */
static void wdsp_rpmsg_remove(struct rpmsg_device *rpdev)
{
	struct wdsp_ch *ch = dev_get_drvdata(&rpdev->dev);

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

	dev_dbg(&rpdev->dev, "%s: Channel[%s] state[Down]\n",
		 __func__, rpdev->id.name);
	spin_lock(&ch->ch_lock);
	ch->handle = NULL;
	ch->ch_state = WDSP_CH_DISCONNECTED;
	spin_unlock(&ch->ch_lock);
	dev_set_drvdata(&rpdev->dev, NULL);
}

static bool wdsp_is_ch_connected(struct wdsp_glink_priv *wpriv)
{
	int i;

	for (i = 0; i < wpriv->no_of_channels; i++) {
		spin_lock(&wpriv->ch[i]->ch_lock);
		if (wpriv->ch[i]->ch_state != WDSP_CH_CONNECTED) {
			spin_unlock(&wpriv->ch[i]->ch_lock);
			return false;
		}
		spin_unlock(&wpriv->ch[i]->ch_lock);
	}
	return true;
}

static int wdsp_wait_for_all_ch_connect(struct wdsp_glink_priv *wpriv)
{
	int ret;

	ret = wait_event_timeout(wpriv->ch_state_wait,
				wdsp_is_ch_connected(wpriv),
				msecs_to_jiffies(TIMEOUT_MS));
	if (!ret) {
		dev_err_ratelimited(wpriv->dev, "%s: All channels are not connected\n",
				    __func__);
		ret = -ETIMEDOUT;
		goto err;
	}
	ret = 0;

err:
	return ret;
}

/*
 * wdsp_tx_buf_work - Work queue function to send tx buffer to glink
 * work:     Work structure
 */
static void wdsp_tx_buf_work(struct work_struct *work)
{
	struct wdsp_glink_priv *wpriv;
	struct wdsp_ch *ch;
	struct wdsp_tx_buf *tx_buf;
	struct wdsp_write_pkt *wpkt;
	struct wdsp_cmd_pkt *cpkt;
	int ret = 0;
	struct rpmsg_device *rpdev = NULL;

	tx_buf = container_of(work, struct wdsp_tx_buf,
			      tx_work);
	ch = tx_buf->ch;
	wpriv = ch->wpriv;
	wpkt = (struct wdsp_write_pkt *)tx_buf->buf;
	cpkt = (struct wdsp_cmd_pkt *)wpkt->payload;

	dev_dbg(wpriv->dev, "%s: ch name = %s, payload size = %d\n",
		__func__, cpkt->ch_name, cpkt->payload_size);

	spin_lock(&ch->ch_lock);
	rpdev = ch->handle;
	if (rpdev && ch->ch_state == WDSP_CH_CONNECTED) {
		spin_unlock(&ch->ch_lock);
		ret = rpmsg_send(rpdev->ept, cpkt->payload,
				 cpkt->payload_size);
		if (ret < 0)
			dev_err(wpriv->dev, "%s: rpmsg send failed, ret = %d\n",
				__func__, ret);
	} else {
		spin_unlock(&ch->ch_lock);
		if (rpdev)
			dev_err(wpriv->dev, "%s: channel %s is not in connected state\n",
				__func__, ch->ch_name);
		else
			dev_err(wpriv->dev, "%s: rpdev is NULL\n", __func__);
	}
	vfree(tx_buf);
}

/*
 * wdsp_glink_read - Read API to send the data to userspace
 * file:    Pointer to the file structure
 * buf:     Pointer to the userspace buffer
 * count:   Number bytes to read from the file
 * ppos:    Pointer to the position into the file
 * Returns 0 on success and an appropriate error value on failure
 */
static ssize_t wdsp_glink_read(struct file *file, char __user *buf,
			       size_t count, loff_t *ppos)
{
	int ret = 0, ret1 = 0;
	struct wdsp_rsp_que *read_rsp = NULL;
	struct wdsp_glink_priv *wpriv;
	unsigned long flags;

	wpriv = (struct wdsp_glink_priv *)file->private_data;
	if (!wpriv) {
		pr_err("%s: Invalid private data\n", __func__);
		return -EINVAL;
	}

	if (count > WDSP_MAX_READ_SIZE) {
		dev_info_ratelimited(wpriv->dev, "%s: count = %zd is more than WDSP_MAX_READ_SIZE\n",
			__func__, count);
		count = WDSP_MAX_READ_SIZE;
	}
	/*
	 * Complete signal has given from gwdsp_rpmsg_callback()
	 * or from flush API. Also use interruptible wait_for_completion API
	 * to allow the system to go in suspend.
	 */
	ret = wait_for_completion_interruptible(&wpriv->rsp_complete);
	if (ret < 0)
		return ret;

	read_rsp = kzalloc(sizeof(struct wdsp_rsp_que), GFP_KERNEL);
	if (!read_rsp)
		return -ENOMEM;

	spin_lock_irqsave(&wpriv->rsp_lock, flags);
	if (wpriv->rsp_cnt) {
		wpriv->rsp_cnt--;
		dev_dbg(wpriv->dev, "%s: rsp_cnt=%d read from buffer %d\n",
			__func__, wpriv->rsp_cnt, wpriv->read_idx);

		memcpy(read_rsp, &wpriv->rsp[wpriv->read_idx],
			sizeof(struct wdsp_rsp_que));
		wpriv->read_idx = (wpriv->read_idx + 1) % RESP_QUEUE_SIZE;
		spin_unlock_irqrestore(&wpriv->rsp_lock, flags);

		if (count < read_rsp->buf_size) {
			ret1 = copy_to_user(buf, read_rsp->buf, count);
			/* Return the number of bytes copied */
			ret = count;
		} else {
			ret1 = copy_to_user(buf, read_rsp->buf,
					    read_rsp->buf_size);
			/* Return the number of bytes copied */
			ret = read_rsp->buf_size;
		}

		if (ret1) {
			dev_err_ratelimited(wpriv->dev, "%s: copy_to_user failed %d\n",
					    __func__, ret);
			ret = -EFAULT;
			goto done;
		}
	} else {
		/*
		 * This will execute only if flush API is called or
		 * something wrong with ref_cnt
		 */
		dev_dbg(wpriv->dev, "%s: resp count = %d\n", __func__,
			wpriv->rsp_cnt);
		spin_unlock_irqrestore(&wpriv->rsp_lock, flags);
		ret = -EINVAL;
	}

done:
	kfree(read_rsp);
	return ret;
}

/*
 * wdsp_glink_write - Write API to receive the data from userspace
 * file:    Pointer to the file structure
 * buf:     Pointer to the userspace buffer
 * count:   Number bytes to read from the file
 * ppos:    Pointer to the position into the file
 * Returns 0 on success and an appropriate error value on failure
 */
static ssize_t wdsp_glink_write(struct file *file, const char __user *buf,
				size_t count, loff_t *ppos)
{
	int ret = 0, i, tx_buf_size;
	struct wdsp_write_pkt *wpkt;
	struct wdsp_cmd_pkt *cpkt;
	struct wdsp_tx_buf *tx_buf;
	struct wdsp_glink_priv *wpriv;
	size_t pkt_max_size;

	wpriv = (struct wdsp_glink_priv *)file->private_data;
	if (!wpriv) {
		pr_err("%s: Invalid private data\n", __func__);
		ret = -EINVAL;
		goto done;
	}

	if ((count < WDSP_WRITE_PKT_SIZE) ||
	    (count > WDSP_MAX_WRITE_SIZE)) {
		dev_err_ratelimited(wpriv->dev, "%s: Invalid count = %zd\n",
			__func__, count);
		ret = -EINVAL;
		goto done;
	}

	dev_dbg(wpriv->dev, "%s: count = %zd\n", __func__, count);

	tx_buf_size = count + sizeof(struct wdsp_tx_buf);
	tx_buf = vzalloc(tx_buf_size);
	if (!tx_buf) {
		ret = -ENOMEM;
		goto done;
	}

	ret = copy_from_user(tx_buf->buf, buf, count);
	if (ret) {
		dev_err_ratelimited(wpriv->dev, "%s: copy_from_user failed %d\n",
			__func__, ret);
		ret = -EFAULT;
		goto free_buf;
	}

	wpkt = (struct wdsp_write_pkt *)tx_buf->buf;
	switch (wpkt->pkt_type) {
	case WDSP_REG_PKT:
		/* Keep this case to support backward compatibility */
		vfree(tx_buf);
		break;
	case WDSP_READY_PKT:
		ret = wdsp_wait_for_all_ch_connect(wpriv);
		if (ret < 0)
			dev_err_ratelimited(wpriv->dev, "%s: Channels not in connected state\n",
					    __func__);
		vfree(tx_buf);
		break;
	case WDSP_CMD_PKT:
		if (count <= (WDSP_WRITE_PKT_SIZE + WDSP_CMD_PKT_SIZE)) {
			dev_err_ratelimited(wpriv->dev, "%s: Invalid cmd pkt size = %zd\n",
				__func__, count);
			ret = -EINVAL;
			goto free_buf;
		}
		cpkt = (struct wdsp_cmd_pkt *)wpkt->payload;
		pkt_max_size =  sizeof(struct wdsp_write_pkt) +
					sizeof(struct wdsp_cmd_pkt) +
					cpkt->payload_size;
		if (count < pkt_max_size) {
			dev_err_ratelimited(wpriv->dev, "%s: Invalid cmd pkt count = %zd, pkt_size = %zd\n",
				__func__, count, pkt_max_size);
			ret = -EINVAL;
			goto free_buf;
		}
		for (i = 0; i < wpriv->no_of_channels; i++) {
			if (!strcmp(cpkt->ch_name, wpriv->ch[i]->ch_name)) {
				tx_buf->ch = wpriv->ch[i];
				break;
			}
		}
		if (!tx_buf->ch) {
			dev_err_ratelimited(wpriv->dev, "%s: Failed to get channel\n",
				__func__);
			ret = -EINVAL;
			goto free_buf;
		}
		dev_dbg(wpriv->dev, "%s: requested ch_name: %s, pkt_size: %zd\n",
			__func__, cpkt->ch_name, pkt_max_size);

		spin_lock(&tx_buf->ch->ch_lock);
		if (tx_buf->ch->ch_state != WDSP_CH_CONNECTED) {
			spin_unlock(&tx_buf->ch->ch_lock);
			ret = -ENETRESET;
			dev_err_ratelimited(wpriv->dev, "%s: Channels are not in connected state\n",
						__func__);
			goto free_buf;
		}
		spin_unlock(&tx_buf->ch->ch_lock);

		INIT_WORK(&tx_buf->tx_work, wdsp_tx_buf_work);
		queue_work(wpriv->work_queue, &tx_buf->tx_work);
		break;
	default:
		dev_err_ratelimited(wpriv->dev, "%s: Invalid packet type\n",
				    __func__);
		ret = -EINVAL;
		vfree(tx_buf);
		break;
	}
	goto done;

free_buf:
	vfree(tx_buf);

done:
	return ret;
}

/*
 * wdsp_glink_open - Open API to initialize private data
 * inode:   Pointer to the inode structure
 * file:    Pointer to the file structure
 * Returns 0 on success and an appropriate error value on failure
 */
static int wdsp_glink_open(struct inode *inode, struct file *file)
{

	pr_debug("%s: wpriv = %pK\n", __func__, wpriv);
	file->private_data = wpriv;

	return 0;
}

/*
 * wdsp_glink_flush - Flush API to unblock read.
 * file:    Pointer to the file structure
 * id:      Lock owner ID
 * Returns 0 on success and an appropriate error value on failure
 */
static int wdsp_glink_flush(struct file *file, fl_owner_t id)
{
	struct wdsp_glink_priv *wpriv;

	wpriv = (struct wdsp_glink_priv *)file->private_data;
	if (!wpriv) {
		pr_err("%s: Invalid private data\n", __func__);
		return -EINVAL;
	}

	complete(&wpriv->rsp_complete);

	return 0;
}

/*
 * wdsp_glink_release - Release API to clean up resources.
 * Whenever a file structure is shared across multiple threads,
 * release won't be invoked until all copies are closed
 * (file->f_count.counter should be 0). If we need to flush pending
 * data when any copy is closed, you should implement the flush method.
 *
 * inode:   Pointer to the inode structure
 * file:    Pointer to the file structure
 * Returns 0 on success and an appropriate error value on failure
 */
static int wdsp_glink_release(struct inode *inode, struct file *file)
{
	pr_debug("%s: file->private_data = %pK\n", __func__,
		 file->private_data);
	file->private_data = NULL;

	return 0;
}

static struct rpmsg_driver wdsp_rpmsg_driver = {
	.probe = wdsp_rpmsg_probe,
	.remove = wdsp_rpmsg_remove,
	.callback = wdsp_rpmsg_callback,
	/* Update this dynamically before register_rpmsg() */
	.id_table = NULL,
	.drv = {
		.name = "wdsp_rpmsg",
	},
};

static int wdsp_register_rpmsg(struct platform_device *pdev,
				struct wdsp_glink_dev *wdev)
{
	int ret = 0;
	int i, no_of_channels;
	struct rpmsg_device_id *wdsp_rpmsg_id_table, *id_table;
	const char *ch_name = NULL;

	wpriv = devm_kzalloc(&pdev->dev,
			     sizeof(struct wdsp_glink_priv), GFP_KERNEL);
	if (!wpriv)
		return -ENOMEM;

	no_of_channels = of_property_count_strings(pdev->dev.of_node,
						   "qcom,wdsp-channels");
	if (no_of_channels < 0) {
		dev_err(&pdev->dev, "%s: channel name parse error %d\n",
			__func__, no_of_channels);
		return -EINVAL;
	}

	wpriv->ch = devm_kzalloc(&pdev->dev,
			(sizeof(struct wdsp_glink_priv *) * no_of_channels),
			GFP_KERNEL);
	if (!wpriv->ch)
		return -ENOMEM;

	for (i = 0; i < no_of_channels; i++) {
		ret = of_property_read_string_index(pdev->dev.of_node,
						   "qcom,wdsp-channels", i,
						   &ch_name);
		if (ret) {
			dev_err(&pdev->dev, "%s: channel name parse error %d\n",
				__func__, ret);
			return -EINVAL;
		}
		wpriv->ch[i] = devm_kzalloc(&pdev->dev,
					    sizeof(struct wdsp_glink_priv),
					    GFP_KERNEL);
		if (!wpriv->ch[i])
			return -ENOMEM;

		strlcpy(wpriv->ch[i]->ch_name, ch_name, RPMSG_NAME_SIZE);
		wpriv->ch[i]->wpriv = wpriv;
		spin_lock_init(&wpriv->ch[i]->ch_lock);
	}
	init_waitqueue_head(&wpriv->ch_state_wait);
	init_completion(&wpriv->rsp_complete);
	spin_lock_init(&wpriv->rsp_lock);

	wpriv->wdev = wdev;
	wpriv->dev = wdev->dev;
	wpriv->work_queue = create_singlethread_workqueue("wdsp_glink_wq");
	if (!wpriv->work_queue) {
		dev_err(&pdev->dev, "%s: Error creating wdsp_glink_wq\n",
			__func__);
		return -EINVAL;
	}

	wdsp_rpmsg_id_table = devm_kzalloc(&pdev->dev,
					   (sizeof(struct rpmsg_device_id) *
							(no_of_channels + 1)),
					   GFP_KERNEL);
	if (!wdsp_rpmsg_id_table) {
		ret = -ENOMEM;
		goto err;
	}

	wpriv->no_of_channels = no_of_channels;
	id_table = wdsp_rpmsg_id_table;
	for (i = 0; i < no_of_channels; i++) {
		strlcpy(id_table->name, wpriv->ch[i]->ch_name,
			RPMSG_NAME_SIZE);
		id_table++;
	}
	wdsp_rpmsg_driver.id_table = wdsp_rpmsg_id_table;
	ret = register_rpmsg_driver(&wdsp_rpmsg_driver);
	if (ret < 0) {
		dev_err(&pdev->dev, "%s: Rpmsg driver register failed, err = %d\n",
			__func__, ret);
		goto err;
	}

	return 0;

err:
	destroy_workqueue(wpriv->work_queue);
	return ret;
}

static const struct file_operations wdsp_glink_fops = {
	.owner =                THIS_MODULE,
	.open =                 wdsp_glink_open,
	.read =                 wdsp_glink_read,
	.write =                wdsp_glink_write,
	.flush =                wdsp_glink_flush,
	.release =              wdsp_glink_release,
};

static int wdsp_glink_probe(struct platform_device *pdev)
{
	int ret;
	struct wdsp_glink_dev *wdev;

	wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
	if (!wdev) {
		ret = -ENOMEM;
		goto done;
	}

	ret = alloc_chrdev_region(&wdev->dev_num, 0, MINOR_NUMBER_COUNT,
				  WDSP_GLINK_DRIVER_NAME);
	if (ret < 0) {
		dev_err(&pdev->dev, "%s: Failed to alloc char dev, err = %d\n",
			__func__, ret);
		goto err_chrdev;
	}

	wdev->cls = class_create(THIS_MODULE, WDSP_GLINK_DRIVER_NAME);
	if (IS_ERR(wdev->cls)) {
		ret = PTR_ERR(wdev->cls);
		dev_err(&pdev->dev, "%s: Failed to create class, err = %d\n",
			__func__, ret);
		goto err_class;
	}

	wdev->dev = device_create(wdev->cls, NULL, wdev->dev_num,
				  NULL, WDSP_GLINK_DRIVER_NAME);
	if (IS_ERR(wdev->dev)) {
		ret = PTR_ERR(wdev->dev);
		dev_err(&pdev->dev, "%s: Failed to create device, err = %d\n",
			__func__, ret);
		goto err_dev_create;
	}

	cdev_init(&wdev->cdev, &wdsp_glink_fops);
	ret = cdev_add(&wdev->cdev, wdev->dev_num, MINOR_NUMBER_COUNT);
	if (ret < 0) {
		dev_err(&pdev->dev, "%s: Failed to register char dev, err = %d\n",
			__func__, ret);
		goto err_cdev_add;
	}

	ret = wdsp_register_rpmsg(pdev, wdev);
	if (ret < 0) {
		dev_err(&pdev->dev, "%s: Failed to register with rpmsg, err = %d\n",
			__func__, ret);
		goto err_cdev_add;
	}
	platform_set_drvdata(pdev, wpriv);

	goto done;

err_cdev_add:
	device_destroy(wdev->cls, wdev->dev_num);

err_dev_create:
	class_destroy(wdev->cls);

err_class:
	unregister_chrdev_region(0, MINOR_NUMBER_COUNT);

err_chrdev:
done:
	return ret;
}

static int wdsp_glink_remove(struct platform_device *pdev)
{
	struct wdsp_glink_priv *wpriv = platform_get_drvdata(pdev);

	unregister_rpmsg_driver(&wdsp_rpmsg_driver);

	if (wpriv) {
		flush_workqueue(wpriv->work_queue);
		destroy_workqueue(wpriv->work_queue);
		if (wpriv->wdev) {
			cdev_del(&wpriv->wdev->cdev);
			device_destroy(wpriv->wdev->cls, wpriv->wdev->dev_num);
			class_destroy(wpriv->wdev->cls);
			unregister_chrdev_region(0, MINOR_NUMBER_COUNT);
		}
	}

	return 0;
}

static const struct of_device_id wdsp_glink_of_match[] = {
	{.compatible = "qcom,wcd-dsp-glink"},
	{ }
};
MODULE_DEVICE_TABLE(of, wdsp_glink_of_match);

static struct platform_driver wdsp_glink_driver = {
	.probe          = wdsp_glink_probe,
	.remove         = wdsp_glink_remove,
	.driver         = {
		.name   = WDSP_GLINK_DRIVER_NAME,
		.owner  = THIS_MODULE,
		.of_match_table = wdsp_glink_of_match,
	},
};

static int __init wdsp_glink_init(void)
{
	return platform_driver_register(&wdsp_glink_driver);
}

static void __exit wdsp_glink_exit(void)
{
	platform_driver_unregister(&wdsp_glink_driver);
}

module_init(wdsp_glink_init);
module_exit(wdsp_glink_exit);
MODULE_DESCRIPTION("SoC WCD_DSP GLINK Driver");
MODULE_LICENSE("GPL v2");