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

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/proc_fs.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/notifier.h>
#include <linux/wcd-spi-ac-params.h>
#include <soc/wcd-spi-ac.h>
#include <soc/qcom/msm_qmi_interface.h>

#include "wcd_spi_ctl_v01.h"

#define WCD_SPI_AC_PFS_ENTRY_MAX_LEN	16
#define WCD_SPI_AC_WRITE_CMD_MIN_SIZE	\
	(sizeof(struct wcd_spi_ac_write_cmd))
#define WCD_SPI_AC_WRITE_CMD_MAX_SIZE		\
	(WCD_SPI_AC_WRITE_CMD_MIN_SIZE +	\
	 (WCD_SPI_AC_MAX_BUFFERS *		\
	  sizeof(struct wcd_spi_ac_buf_data)))

#define WCD_SPI_AC_MUTEX_LOCK(dev, lock)	\
{						\
	dev_dbg(dev, "%s: mutex_lock(%s)\n",	\
		__func__, __stringify_1(lock));	\
	mutex_lock(&lock);			\
}

#define WCD_SPI_AC_MUTEX_UNLOCK(dev, lock)	\
{						\
	dev_dbg(dev, "%s: mutex_unlock(%s)\n",	\
		__func__, __stringify_1(lock));	\
	mutex_unlock(&lock);			\
}

/*
 * All bits of status should be cleared for SPI access
 * to be released.
 */
#define WCD_SPI_AC_STATUS_RELEASE_ACCESS	0x00
#define WCD_SPI_AC_LOCAL_ACCESS	0x00
#define WCD_SPI_AC_REMOTE_ACCESS 0x01
#define WCD_SPI_CTL_INS_ID 0
#define WCD_SPI_AC_QMI_TIMEOUT_MS 100

struct wcd_spi_ac_priv {

	/* Pointer to device for this driver */
	struct device *dev;

	/* Pointer to parent's device */
	struct device *parent;

	/* char dev related */
	struct class *cls;
	struct device *chardev;
	struct cdev cdev;
	dev_t cdev_num;

	/* proc entry related */
	struct proc_dir_entry *pfs_root;
	struct proc_dir_entry *pfs_status;

	/* service status related */
	u8 svc_offline;
	u8 svc_offline_change;
	wait_queue_head_t svc_poll_wait;
	struct mutex status_lock;

	/* state maintenence related */
	u32 state;
	struct mutex state_lock;
	u8 current_access;

	/* qmi related */
	struct qmi_handle *qmi_hdl;
	struct work_struct svc_arr_work;
	struct work_struct svc_exit_work;
	struct notifier_block nb;
	struct mutex svc_lock;
	struct workqueue_struct *qmi_wq;
	struct work_struct recv_msg_work;
};


static void wcd_spi_ac_status_change(struct wcd_spi_ac_priv *ac,
				     u8 online)
{
	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->status_lock);
	ac->svc_offline = !online;
	/* Make sure the write is complete */
	wmb();
	xchg(&ac->svc_offline_change, 1);
	wake_up_interruptible(&ac->svc_poll_wait);
	dev_dbg(ac->dev,
		"%s request %u offline %u off_change %u\n",
		__func__, online, ac->svc_offline,
		ac->svc_offline_change);
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->status_lock);
}

static int wcd_spi_ac_status_open(struct inode *inode,
				    struct file *file)
{
	struct wcd_spi_ac_priv *ac = PDE_DATA(inode);

	file->private_data = ac;

	return 0;
}

static ssize_t wcd_spi_ac_status_read(struct file *file,
		char __user *buffer,
		size_t count, loff_t *offset)
{
	struct wcd_spi_ac_priv *ac;
	char buf[WCD_SPI_AC_PFS_ENTRY_MAX_LEN];
	int len, ret;
	u8 offline;

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

	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->status_lock);
	offline = ac->svc_offline;
	/* Make sure the read is complete */
	rmb();
	dev_dbg(ac->dev, "%s: offline = %sline\n",
		__func__, offline ? "off" : "on");
	len = snprintf(buf, sizeof(buf), "%s\n",
		       offline ? "OFFLINE" : "ONLINE");
	ret = simple_read_from_buffer(buffer, count, offset, buf, len);
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->status_lock);

	return ret;
}

static unsigned int wcd_spi_ac_status_poll(struct file *file,
		poll_table *wait)
{
	struct wcd_spi_ac_priv *ac;
	unsigned int ret = 0;

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

	dev_dbg(ac->dev, "%s: Poll wait, svc = %s\n",
		__func__, ac->svc_offline ? "offline" : "online");
	poll_wait(file, &ac->svc_poll_wait, wait);
	dev_dbg(ac->dev, "%s: Woken up Poll wait, svc = %s\n",
		__func__, ac->svc_offline ? "offline" : "online");

	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->status_lock);
	if (xchg(&ac->svc_offline_change, 0))
		ret = POLLIN | POLLPRI | POLLRDNORM;
	dev_dbg(ac->dev, "%s: ret (%d) from poll_wait\n",
		__func__, ret);
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->status_lock);

	return ret;
}

static const struct file_operations wcd_spi_ac_status_ops = {
	.owner = THIS_MODULE,
	.open = wcd_spi_ac_status_open,
	.read = wcd_spi_ac_status_read,
	.poll = wcd_spi_ac_status_poll,
};

static int wcd_spi_ac_procfs_init(struct wcd_spi_ac_priv *ac)
{
	int ret = 0;

	ac->pfs_root = proc_mkdir(WCD_SPI_AC_PROCFS_DIR_NAME, NULL);
	if (!ac->pfs_root) {
		dev_err(ac->dev, "%s: proc_mkdir failed\n", __func__);
		return -EINVAL;
	}

	ac->pfs_status = proc_create_data(WCD_SPI_AC_PROCFS_STATE_NAME,
					  0444, ac->pfs_root,
					  &wcd_spi_ac_status_ops,
					  ac);
	if (!ac->pfs_status) {
		dev_err(ac->dev, "%s: proc_create_data failed\n",
			__func__);
		ret = -EINVAL;
		goto rmdir_root;
	}

	proc_set_size(ac->pfs_status, WCD_SPI_AC_PFS_ENTRY_MAX_LEN);

	return 0;

rmdir_root:
	proc_remove(ac->pfs_root);
	return ret;
}

static void wcd_spi_ac_procfs_deinit(struct wcd_spi_ac_priv *ac)
{
	proc_remove(ac->pfs_status);
	proc_remove(ac->pfs_root);
}

static int wcd_spi_ac_request_access(struct wcd_spi_ac_priv *ac,
				     bool is_svc_locked)
{
	struct wcd_spi_req_access_msg_v01 req;
	struct wcd_spi_req_access_resp_v01 rsp;
	struct msg_desc req_desc, rsp_desc;
	int ret = 0;

	dev_dbg(ac->dev, "%s: is_svc_locked = %s\n",
		__func__, is_svc_locked ? "true" : "false");

	memset(&req, 0, sizeof(req));
	memset(&rsp, 0, sizeof(rsp));

	req.reason_valid = 1;
	req.reason = ac->state  & 0x03;

	req_desc.max_msg_len = WCD_SPI_REQ_ACCESS_MSG_V01_MAX_MSG_LEN;
	req_desc.msg_id = WCD_SPI_REQ_ACCESS_MSG_V01;
	req_desc.ei_array = wcd_spi_req_access_msg_v01_ei;

	rsp_desc.max_msg_len = WCD_SPI_REQ_ACCESS_RESP_V01_MAX_MSG_LEN;
	rsp_desc.msg_id = WCD_SPI_REQ_ACCESS_RESP_V01;
	rsp_desc.ei_array = wcd_spi_req_access_resp_v01_ei;

	if (!is_svc_locked)
		WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock);

	ret = qmi_send_req_wait(ac->qmi_hdl,
				&req_desc, &req, sizeof(req),
				&rsp_desc, &rsp, sizeof(rsp),
				WCD_SPI_AC_QMI_TIMEOUT_MS);
	if (ret) {
		dev_err(ac->dev, "%s: msg send failed %d\n",
			__func__, ret);
		goto done;
	}

	if (rsp.resp.result != QMI_RESULT_SUCCESS_V01) {
		ret = -EIO;
		dev_err(ac->dev, "%s: qmi resp error %d\n",
			__func__, rsp.resp.result);
	}
done:
	if (!is_svc_locked)
		WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock);

	return ret;
}

static int wcd_spi_ac_release_access(struct wcd_spi_ac_priv *ac,
				     bool is_svc_locked)
{
	struct wcd_spi_rel_access_msg_v01 req;
	struct wcd_spi_rel_access_resp_v01 rsp;
	struct msg_desc req_desc, rsp_desc;
	int ret = 0;

	dev_dbg(ac->dev, "%s: is_svc_locked = %s\n",
		__func__, is_svc_locked ? "true" : "false");

	memset(&req, 0, sizeof(req));
	memset(&rsp, 0, sizeof(rsp));

	req_desc.max_msg_len = WCD_SPI_REL_ACCESS_MSG_V01_MAX_MSG_LEN;
	req_desc.msg_id = WCD_SPI_REL_ACCESS_MSG_V01;
	req_desc.ei_array = wcd_spi_rel_access_msg_v01_ei;

	rsp_desc.max_msg_len = WCD_SPI_REL_ACCESS_RESP_V01_MAX_MSG_LEN;
	rsp_desc.msg_id = WCD_SPI_REL_ACCESS_RESP_V01;
	rsp_desc.ei_array = wcd_spi_rel_access_resp_v01_ei;

	if (!is_svc_locked)
		WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock);

	ret = qmi_send_req_wait(ac->qmi_hdl,
				&req_desc, &req, sizeof(req),
				&rsp_desc, &rsp, sizeof(rsp),
				WCD_SPI_AC_QMI_TIMEOUT_MS);
	if (ret) {
		dev_err(ac->dev, "%s: msg send failed %d\n",
			__func__, ret);
		goto done;
	}

	if (rsp.resp.result != QMI_RESULT_SUCCESS_V01) {
		ret = -EIO;
		dev_err(ac->dev, "%s: qmi resp error %d\n",
			__func__, rsp.resp.result);
	}
done:
	if (!is_svc_locked)
		WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock);
	return ret;
}

static int wcd_spi_ac_buf_msg(
		struct wcd_spi_ac_priv *ac,
		u8 *data, int data_sz)
{
	struct wcd_spi_ac_buf_data *buf_data;
	struct wcd_spi_buff_msg_v01 req;
	struct wcd_spi_buff_resp_v01 rsp;
	struct msg_desc req_desc, rsp_desc;
	int ret = 0;

	memset(&req, 0, sizeof(req));
	memset(&rsp, 0, sizeof(rsp));

	buf_data = (struct wcd_spi_ac_buf_data *) data;
	memcpy(req.buff_addr_1, buf_data,
	       sizeof(*buf_data));

	if (data_sz - sizeof(*buf_data) != 0) {
		req.buff_addr_2_valid = 1;
		buf_data++;
		memcpy(req.buff_addr_2, buf_data,
		       sizeof(*buf_data));
	}

	req_desc.max_msg_len = WCD_SPI_BUFF_MSG_V01_MAX_MSG_LEN;
	req_desc.msg_id = WCD_SPI_BUFF_MSG_V01;
	req_desc.ei_array = wcd_spi_buff_msg_v01_ei;

	rsp_desc.max_msg_len = WCD_SPI_BUFF_RESP_V01_MAX_MSG_LEN;
	rsp_desc.msg_id = WCD_SPI_BUFF_RESP_V01;
	rsp_desc.ei_array = wcd_spi_buff_resp_v01_ei;

	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock);
	ret = qmi_send_req_wait(ac->qmi_hdl,
				&req_desc, &req, sizeof(req),
				&rsp_desc, &rsp, sizeof(rsp),
				WCD_SPI_AC_QMI_TIMEOUT_MS);

	if (ret) {
		dev_err(ac->dev, "%s: msg send failed %d\n",
			__func__, ret);
		goto done;
	}

	if (rsp.resp.result != QMI_RESULT_SUCCESS_V01) {
		ret = -EIO;
		dev_err(ac->dev, "%s: qmi resp error %d\n",
			__func__, rsp.resp.result);
	}
done:
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock);
	return ret;

}

/*
 * wcd_spi_ac_set_sync: Sets the current status of the SPI
 *			bus and requests access if not
 *			already accesible.
 * @ac: pointer to the drivers private data
 * @value: value to be set in the status mask
 * @is_svc_locked: flag to indicate if svc_lock is acquired by caller
 */
static int wcd_spi_ac_set_sync(struct wcd_spi_ac_priv *ac,
			       u32 value, bool is_svc_locked)
{
	int ret = 0;

	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->state_lock);
	ac->state |= value;
	/* any non-zero state indicates us to request SPI access */
	wmb();
	dev_dbg(ac->dev, "%s: current state = 0x%x, current access 0x%x\n",
		__func__, ac->state, ac->current_access);
	if (ac->current_access == WCD_SPI_AC_REMOTE_ACCESS) {
		dev_dbg(ac->dev,
			"%s: requesting access, state = 0x%x\n",
			__func__, ac->state);
		ret = wcd_spi_ac_request_access(ac, is_svc_locked);
		if (!ret)
			ac->current_access = WCD_SPI_AC_LOCAL_ACCESS;
	}
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->state_lock);

	return ret;
}

/*
 * wcd_spi_ac_clear_sync: Clears the current status of the SPI
 *			  bus and releases access if applicable
 * @ac: pointer to the drivers private data
 * @value: value to be cleared in the status mask
 * @is_svc_locked: flag to indicate if svc_lock is acquired by caller
 */
static int wcd_spi_ac_clear_sync(struct wcd_spi_ac_priv *ac,
				 u32 value, bool is_svc_locked)
{
	int ret = 0;

	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->state_lock);
	ac->state &= ~(value);
	/* make sure value is written before read */
	wmb();
	dev_dbg(ac->dev, "%s: current state = 0x%x, current access 0x%x\n",
		__func__, ac->state, ac->current_access);
	/* state should be zero to release SPI access */
	if (!ac->state &&
	    ac->current_access == WCD_SPI_AC_LOCAL_ACCESS) {
		dev_dbg(ac->dev,
			"%s: releasing access, state = 0x%x\n",
			__func__, ac->state);
		ret = wcd_spi_ac_release_access(ac, is_svc_locked);
		if (!ret)
			ac->current_access = WCD_SPI_AC_REMOTE_ACCESS;
	}
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->state_lock);

	return ret;

}

/*
 * wcd_spi_access_ctl: API to request/release the access
 *		       to wcd-spi bus.
 * @dev: handle to the wcd-spi-ac device
 * @request: enum to indicate access request or access release
 * @reason: reason for request/release. Must be one of the
 *	    valid reasons.
 * Returns success if the access handover was sucessful,
 * negative error code otherwise.
 */
int wcd_spi_access_ctl(struct device *dev,
		      enum wcd_spi_acc_req request,
		      u32 reason)
{
	struct wcd_spi_ac_priv *ac;
	int ret = 0;

	if (!dev) {
		pr_err("%s: invalid device\n", __func__);
		return -EINVAL;
	}

	/* only data_transfer and remote_down are valid reasons */
	if (reason != WCD_SPI_AC_DATA_TRANSFER &&
	    reason != WCD_SPI_AC_REMOTE_DOWN) {
		pr_err("%s: Invalid reason 0x%x\n",
			__func__, reason);
		return -EINVAL;
	}

	ac = (struct wcd_spi_ac_priv *) dev_get_drvdata(dev);
	if (!ac) {
		dev_err(dev, "%s: invalid driver data\n", __func__);
		return -EINVAL;
	}

	dev_dbg(dev, "%s: request = 0x%x, reason = 0x%x\n",
		__func__, request, reason);

	switch (request) {
	case WCD_SPI_ACCESS_REQUEST:
		ret = wcd_spi_ac_set_sync(ac, reason, false);
		if (ret)
			dev_err(dev, "%s: set_sync(0x%x) failed %d\n",
				__func__, reason, ret);
		break;
	case WCD_SPI_ACCESS_RELEASE:
		ret = wcd_spi_ac_clear_sync(ac, reason, false);
		if (ret)
			dev_err(dev, "%s: clear_sync(0x%x) failed %d\n",
				__func__, reason, ret);
		break;
	default:
		dev_err(dev, "%s: invalid request 0x%x\n",
			__func__, request);
		break;
	}

	return ret;
}
EXPORT_SYMBOL(wcd_spi_access_ctl);

static int wcd_spi_ac_cdev_open(struct inode *inode,
				struct file *file)
{
	struct wcd_spi_ac_priv *ac;
	int ret = 0;

	ac = container_of(inode->i_cdev, struct wcd_spi_ac_priv, cdev);
	if (!ac) {
		pr_err("%s: Invalid private data\n", __func__);
		return -EINVAL;
	}

	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->status_lock);
	if (ac->svc_offline) {
		dev_err(ac->dev, "%s: SVC is not online, cannot open driver\n",
			__func__);
		ret = -ENODEV;
		goto done;
	}

	file->private_data = ac;

done:
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->status_lock);
	return ret;
}

static ssize_t wcd_spi_ac_cdev_write(struct file *file,
				     const char __user *buf,
				     size_t count,
				     loff_t *ppos)
{
	struct wcd_spi_ac_priv *ac;
	struct wcd_spi_ac_write_cmd *cmd_buf;
	int ret = 0;
	int data_sz;

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

	if (count < WCD_SPI_AC_WRITE_CMD_MIN_SIZE ||
	    count > WCD_SPI_AC_WRITE_CMD_MAX_SIZE) {
		dev_err(ac->dev, "%s: Invalid write count %zd\n",
			__func__, count);
		return -EINVAL;
	}

	cmd_buf = kzalloc(count, GFP_KERNEL);
	if (!cmd_buf)
		return -ENOMEM;

	if (get_user(cmd_buf->cmd_type, buf)) {
		dev_err(ac->dev, "%s: get_user failed\n", __func__);
		ret = -EFAULT;
		goto free_cmd_buf;
	}

	dev_dbg(ac->dev, "%s: write cmd type 0x%x\n",
		__func__, cmd_buf->cmd_type);

	switch (cmd_buf->cmd_type) {

	case WCD_SPI_AC_CMD_CONC_BEGIN:
		ret = wcd_spi_ac_set_sync(ac, WCD_SPI_AC_CONCURRENCY, false);
		if (ret) {
			dev_err(ac->dev, "%s: set_sync(CONC) fail %d\n",
				__func__, ret);
			goto free_cmd_buf;
		}

		break;

	case WCD_SPI_AC_CMD_CONC_END:
		ret = wcd_spi_ac_clear_sync(ac, WCD_SPI_AC_CONCURRENCY, false);
		if (ret) {
			dev_err(ac->dev, "%s: clear_sync(CONC) fail %d\n",
				__func__, ret);
			goto free_cmd_buf;
		}

		break;

	case WCD_SPI_AC_CMD_BUF_DATA:

		/* Read the buffer details and send to service */
		data_sz = count - sizeof(cmd_buf->cmd_type);

		if (!data_sz ||
		    (data_sz % sizeof(struct wcd_spi_ac_buf_data))) {
			dev_err(ac->dev, "%s: size %d not multiple of %ld\n",
				__func__, data_sz,
				sizeof(struct wcd_spi_ac_buf_data));
			goto free_cmd_buf;
		}

		if (data_sz / sizeof(struct wcd_spi_ac_buf_data) >
		    WCD_SPI_AC_MAX_BUFFERS) {
			dev_err(ac->dev, "%s: invalid size %d\n",
				__func__, data_sz);
			goto free_cmd_buf;
		}

		if (copy_from_user(cmd_buf->payload,
				   buf + sizeof(cmd_buf->cmd_type),
				   data_sz)) {
			dev_err(ac->dev, "%s: copy_from_user failed\n",
				__func__);
			ret = -EFAULT;
			goto free_cmd_buf;
		}

		ret = wcd_spi_ac_buf_msg(ac, cmd_buf->payload, data_sz);
		if (ret) {
			dev_err(ac->dev, "%s: _buf_msg failed %d\n",
				__func__, ret);
			goto free_cmd_buf;
		}

		ret = wcd_spi_ac_clear_sync(ac, WCD_SPI_AC_UNINITIALIZED,
					    false);
		if (ret) {
			dev_err(ac->dev, "%s: clear_sync 0x%lx failed %d\n",
				__func__, WCD_SPI_AC_UNINITIALIZED, ret);
			goto free_cmd_buf;
		}
		break;
	default:
		dev_err(ac->dev, "%s: Invalid cmd_type 0x%x\n",
			__func__, cmd_buf->cmd_type);
		ret = -EINVAL;
		goto free_cmd_buf;
	}

free_cmd_buf:

	kfree(cmd_buf);
	if (!ret)
		ret = count;

	return ret;
}

static int wcd_spi_ac_cdev_release(struct inode *inode,
				   struct file *file)
{
	struct wcd_spi_ac_priv *ac;
	int ret = 0;

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

	ret = wcd_spi_ac_set_sync(ac, WCD_SPI_AC_UNINITIALIZED, false);
	if (ret)
		dev_err(ac->dev, "%s: set_sync(UNINITIALIZED) failed %d\n",
			__func__, ret);
	return ret;
}

static const struct file_operations wcd_spi_ac_cdev_fops = {
	.owner = THIS_MODULE,
	.open = wcd_spi_ac_cdev_open,
	.write = wcd_spi_ac_cdev_write,
	.release = wcd_spi_ac_cdev_release,
};

static int wcd_spi_ac_reg_chardev(struct wcd_spi_ac_priv *ac)
{
	int ret;

	ret = alloc_chrdev_region(&ac->cdev_num, 0, 1,
				  WCD_SPI_AC_CLIENT_CDEV_NAME);
	if (ret) {
		dev_err(ac->dev, "%s: alloc_chrdev_region failed %d\n",
			__func__, ret);
		return ret;
	}

	ac->cls = class_create(THIS_MODULE, WCD_SPI_AC_CLIENT_CDEV_NAME);
	if (IS_ERR(ac->cls)) {
		ret = PTR_ERR(ac->cls);
		dev_err(ac->dev, "%s: class_create failed %d\n",
			__func__, ret);
		goto unregister_chrdev;
	}

	ac->chardev = device_create(ac->cls, NULL, ac->cdev_num,
				      NULL, WCD_SPI_AC_CLIENT_CDEV_NAME);
	if (IS_ERR(ac->chardev)) {
		ret = PTR_ERR(ac->chardev);
		dev_err(ac->dev, "%s: device_create failed %d\n",
			__func__, ret);
		goto destroy_class;
	}

	cdev_init(&ac->cdev, &wcd_spi_ac_cdev_fops);
	ret = cdev_add(&ac->cdev, ac->cdev_num, 1);
	if (ret) {
		dev_err(ac->dev, "%s: cdev_add failed %d\n",
			__func__, ret);
		goto destroy_device;
	}

	return 0;

destroy_device:
	device_destroy(ac->cls, ac->cdev_num);

destroy_class:
	class_destroy(ac->cls);

unregister_chrdev:
	unregister_chrdev_region(0, 1);
	return ret;
}

static int wcd_spi_ac_unreg_chardev(struct wcd_spi_ac_priv *ac)
{
	cdev_del(&ac->cdev);
	device_destroy(ac->cls, ac->cdev_num);
	class_destroy(ac->cls);
	unregister_chrdev_region(0, 1);

	return 0;
}

static void wcd_spi_ac_recv_msg(struct work_struct *work)
{
	struct wcd_spi_ac_priv *ac;
	int rc = 0;

	ac = container_of(work, struct wcd_spi_ac_priv,
			  recv_msg_work);
	if (!ac) {
		pr_err("%s: Invalid private data\n", __func__);
		return;
	}

	do {
		dev_dbg(ac->dev, "%s: msg received, rc = %d\n",
			__func__, rc);
	} while ((rc = qmi_recv_msg(ac->qmi_hdl)) == 0);

	if (rc != -ENOMSG)
		dev_err(ac->dev, "%s: qmi_recv_msg failed %d\n",
			__func__, rc);
}

static void wcd_spi_ac_clnt_notify(struct qmi_handle *hdl,
		enum qmi_event_type event, void *priv_data)
{
	struct wcd_spi_ac_priv *ac;

	if (!priv_data) {
		pr_err("%s: Invalid private data\n", __func__);
		return;
	}

	ac = (struct wcd_spi_ac_priv *) priv_data;

	switch (event) {
	case QMI_RECV_MSG:
		queue_work(ac->qmi_wq, &ac->recv_msg_work);
		break;
	default:
		break;
	}
}

static void wcd_spi_ac_svc_arrive(struct work_struct *work)
{
	struct wcd_spi_ac_priv *ac;
	int ret;

	ac = container_of(work, struct wcd_spi_ac_priv,
			  svc_arr_work);
	if (!ac) {
		pr_err("%s: Invalid private data\n",
			__func__);
		return;
	}

	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock);
	ac->qmi_hdl = qmi_handle_create(wcd_spi_ac_clnt_notify,
					ac);
	if (!ac->qmi_hdl) {
		dev_err(ac->dev, "%s: qmi_handle_create failed\n",
			__func__);
		goto done;
	}

	ret = qmi_connect_to_service(ac->qmi_hdl,
			WCD_SPI_CTL_SERVICE_ID_V01,
			WCD_SPI_CTL_SERVICE_VERS_V01,
			WCD_SPI_CTL_INS_ID);
	if (ret) {
		dev_err(ac->dev, "%s, cant connect to service, error %d\n",
			__func__, ret);
		qmi_handle_destroy(ac->qmi_hdl);
		ac->qmi_hdl = NULL;
		goto done;
	}

	/* Mark service as online */
	wcd_spi_ac_status_change(ac, 1);

	/*
	 * update the state and clear the WCD_SPI_AC_SVC_OFFLINE
	 * bit to indicate that the service is now online.
	 */
	ret = wcd_spi_ac_clear_sync(ac, WCD_SPI_AC_SVC_OFFLINE, true);
	if (ret)
		dev_err(ac->dev, "%s: clear_sync(SVC_OFFLINE) failed %d\n",
			__func__, ret);
done:
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock);

}

static void wcd_spi_ac_svc_exit(struct work_struct *work)
{
	struct wcd_spi_ac_priv *ac;
	int ret = 0;

	ac = container_of(work, struct wcd_spi_ac_priv,
			  svc_exit_work);
	if (!ac) {
		pr_err("%s: Invalid private data\n",
			__func__);
		return;
	}

	WCD_SPI_AC_MUTEX_LOCK(ac->dev, ac->svc_lock);
	ret = wcd_spi_ac_set_sync(ac, WCD_SPI_AC_SVC_OFFLINE, true);
	if (ret)
		dev_err(ac->dev, "%s: set_sync(SVC_OFFLINE) failed %d\n",
			__func__, ret);
	qmi_handle_destroy(ac->qmi_hdl);
	ac->qmi_hdl = NULL;
	wcd_spi_ac_status_change(ac, 0);
	WCD_SPI_AC_MUTEX_UNLOCK(ac->dev, ac->svc_lock);
}

static int wcd_spi_ac_svc_event(struct notifier_block *this,
				unsigned long event,
				void *data)
{
	struct wcd_spi_ac_priv *ac;

	ac = container_of(this, struct wcd_spi_ac_priv, nb);
	if (!ac) {
		pr_err("%s: Invalid private data\n", __func__);
		return -EINVAL;
	}

	dev_dbg(ac->dev, "%s: event = 0x%lx", __func__, event);

	switch (event) {
	case QMI_SERVER_ARRIVE:
		schedule_work(&ac->svc_arr_work);
		break;
	case QMI_SERVER_EXIT:
		schedule_work(&ac->svc_exit_work);
		break;
	default:
		dev_err(ac->dev, "%s unhandled event %ld\n",
			__func__, event);
		break;
	}

	return 0;
}

static int wcd_spi_ac_probe(struct platform_device *pdev)
{
	struct wcd_spi_ac_priv *ac;
	struct device *parent = pdev->dev.parent;
	int ret = 0;

	ac = devm_kzalloc(&pdev->dev, sizeof(*ac),
			    GFP_KERNEL);
	if (!ac)
		return -ENOMEM;

	ac->dev = &pdev->dev;
	ac->parent = parent;

	ret = wcd_spi_ac_reg_chardev(ac);
	if (ret)
		return ret;

	ret = wcd_spi_ac_procfs_init(ac);
	if (ret)
		goto unreg_chardev;

	mutex_init(&ac->status_lock);
	mutex_init(&ac->state_lock);
	mutex_init(&ac->svc_lock);
	init_waitqueue_head(&ac->svc_poll_wait);
	ac->svc_offline = 1;
	ac->state = (WCD_SPI_AC_SVC_OFFLINE |
		     WCD_SPI_AC_UNINITIALIZED);
	ac->current_access = WCD_SPI_AC_LOCAL_ACCESS;

	ac->nb.notifier_call = wcd_spi_ac_svc_event;
	INIT_WORK(&ac->svc_arr_work, wcd_spi_ac_svc_arrive);
	INIT_WORK(&ac->svc_exit_work, wcd_spi_ac_svc_exit);
	INIT_WORK(&ac->recv_msg_work, wcd_spi_ac_recv_msg);

	ac->qmi_wq = create_singlethread_workqueue("qmi_wq");
	if (!ac->qmi_wq) {
		dev_err(&pdev->dev,
			"%s: create_singlethread_workqueue failed\n",
			__func__);
		goto deinit_procfs;
	}

	dev_set_drvdata(&pdev->dev, ac);

	ret = qmi_svc_event_notifier_register(
			WCD_SPI_CTL_SERVICE_ID_V01,
			WCD_SPI_CTL_SERVICE_VERS_V01,
			WCD_SPI_CTL_INS_ID,
			&ac->nb);
	if (ret) {
		dev_err(&pdev->dev,
			"%s: qmi_svc_event_notifier_register failed %d\n",
			__func__, ret);
		goto destroy_wq;
	}

	return 0;

destroy_wq:
	destroy_workqueue(ac->qmi_wq);
	dev_set_drvdata(&pdev->dev, NULL);
deinit_procfs:
	wcd_spi_ac_procfs_deinit(ac);
	mutex_destroy(&ac->status_lock);
	mutex_destroy(&ac->state_lock);
	mutex_destroy(&ac->svc_lock);
unreg_chardev:
	wcd_spi_ac_unreg_chardev(ac);
	return ret;
}

static int wcd_spi_ac_remove(struct platform_device *pdev)
{
	struct wcd_spi_ac_priv *ac;

	ac = dev_get_drvdata(&pdev->dev);
	qmi_svc_event_notifier_unregister(
			WCD_SPI_CTL_SERVICE_ID_V01,
			WCD_SPI_CTL_SERVICE_VERS_V01,
			WCD_SPI_CTL_INS_ID,
			&ac->nb);
	if (ac->qmi_wq)
		destroy_workqueue(ac->qmi_wq);
	wcd_spi_ac_unreg_chardev(ac);
	wcd_spi_ac_procfs_deinit(ac);
	mutex_destroy(&ac->status_lock);
	mutex_destroy(&ac->state_lock);
	mutex_destroy(&ac->svc_lock);

	return 0;
}

static const struct of_device_id wcd_spi_ac_of_match[] = {
	{ .compatible = "qcom,wcd-spi-ac" },
	{ },
};

MODULE_DEVICE_TABLE(of, wcd_spi_ac_of_match);

static struct platform_driver wcd_spi_ac_driver = {
	.driver = {
		.name = "qcom,wcd-spi-ac",
		.of_match_table = wcd_spi_ac_of_match,
		.suppress_bind_attrs = true,
	},
	.probe = wcd_spi_ac_probe,
	.remove = wcd_spi_ac_remove,
};

module_platform_driver(wcd_spi_ac_driver);

MODULE_DESCRIPTION("WCD SPI access control driver");
MODULE_LICENSE("GPL v2");