// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved.
 */

#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/eventfd.h>
#include <linux/interrupt.h>
#include <linux/mailbox_client.h>
#include <linux/mailbox_controller.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/suspend.h>

#include <uapi/linux/sail_mbox.h>

#define MAILBOX_PHY_ADDR	0x90d80000
#define OTA_PHY_ADDR	0x90e00000
#define SAIL_S1_MAGIC	0xD0000000U
#define SAIL_S2_MAGIC	0xD0020000U
#define SAIL_SUSPEND_MAGIC	0xD0030000U
#define SAIL_IPC_SIGNAL	BIT(23)
#define SAIL_SUSPEND_ACK_TIMEOUT_MS	2000
#define SAIL_HANDSHAKE_DEFAULT_DELAY_US	50000

struct mmap_data {
	unsigned long mmap_phy_addr;
	unsigned long mmap_size;
};

struct sail_irq_data {
	unsigned int irq;
	unsigned int client_id;
	unsigned int signal_id;
	char *irq_name;
	struct eventfd_ctx *event_fd_sig;
};

struct sail_mbox_data {
	struct mbox_client mbox_client;
	struct mbox_chan *mchan;
	unsigned int client_id;
	unsigned int signal_id;
};

struct sail_mailbox {
	struct device *dev;
	struct cdev sailmb_cdev;
	struct class *sailmb_driver_class;
	int sailmb_dev_major;
	dev_t sailmb_dev_num;
	wait_queue_head_t event;
	struct mutex dev_lock;
	struct sail_mbox_data *mbox_data;
	struct sail_irq_data *irq_data;
	int mbox_count;
	int irq_count;
	struct mmap_data pr_data;
	bool mmap_ioctl_set;
	void __iomem *req_tcsr_reg;
	void __iomem *resp_tcsr_reg;
	void __iomem *ipc_reg;
	unsigned int sail_handshake_delay;
};

static struct sail_mailbox *sailmb_dev;

static struct mbox_chan *get_mchan_from_signal(const struct sail_mbox_data *mbox_data,
			const int mbox_count, const unsigned int sig)
{
	int i;

	for (i = 0; i < mbox_count; i++) {
		if (mbox_data[i].signal_id == sig)
			return mbox_data[i].mchan;
	}
	return NULL;
}

static int sail_send_interrupt(struct sail_mailbox *sailmb_dev, const unsigned int sig)
{
	int ret;
	struct mbox_chan *mchan;

	mchan = get_mchan_from_signal(sailmb_dev->mbox_data, sailmb_dev->mbox_count, sig);
	if (mchan == NULL) {
		dev_err(sailmb_dev->dev, "unable to find mchan corresponding to signal %d\n", sig);
		return -EFAULT;
	}

	ret = mbox_send_message(mchan, NULL);
	if (ret < 0) {
		dev_err(sailmb_dev->dev, "mbox send message failed for signal %d, err=%d\n",
			sig, ret);
		return ret;
	}

	mbox_client_txdone(mchan, 0);
	return 0;
}

static int get_irq_from_signal(const struct sail_irq_data *irq_data, const int irq_count,
	unsigned int signal, unsigned int *irq)
{
	int i;

	for (i = 0; i < irq_count; i++) {
		if (irq_data[i].signal_id == signal) {
			*irq = irq_data[i].irq;
			return 0;
		}
	}
	return -ENOENT;
}

static int set_eventfd_for_signal(struct sail_irq_data *irq_data, const int irq_count,
	const unsigned int signal, int32_t *event_fd)
{
	int i, ret;

	for (i = 0; i < irq_count; i++) {
		if (irq_data[i].signal_id == signal) {
			irq_data[i].event_fd_sig = eventfd_ctx_fdget(*event_fd);
			if (IS_ERR(irq_data[i].event_fd_sig)) {
				ret = (int) PTR_ERR(irq_data[i].event_fd_sig);
				irq_data[i].event_fd_sig = NULL;
				return ret;
			}
			return 0;
		}
	}
	return -ENOENT;
}

static int release_eventfd_for_signal(struct sail_irq_data *irq_data, const int irq_count,
	const unsigned int signal)
{
	int i;

	for (i = 0; i < irq_count; i++) {
		if ((irq_data[i].signal_id == signal) && (irq_data[i].event_fd_sig)) {
			eventfd_ctx_put(irq_data[i].event_fd_sig);
			irq_data[i].event_fd_sig = NULL;
			return 0;
		}
	}
	return -ENOENT;
}

static struct eventfd_ctx *get_eventfd_from_irq(const unsigned int irq)
{
	int i;
	struct sail_irq_data *irq_data = sailmb_dev->irq_data;

	for (i = 0; i < sailmb_dev->irq_count; i++) {
		if (irq_data[i].irq == irq)
			return irq_data[i].event_fd_sig;
	}
	return NULL;
}

static irqreturn_t sailmb_intr_handler(int irq, void *data)
{
	struct eventfd_ctx *event_fd;

	event_fd = get_eventfd_from_irq(irq);
	if (event_fd == NULL) {
		dev_err(sailmb_dev->dev,
			"could not retrieve eventfd corresponding to %d\n", irq);
		return IRQ_NONE;
	}

	eventfd_signal(event_fd, 1);
	return IRQ_HANDLED;
}

static long sail_mb_ioctl_call(struct file *f, unsigned int cmd, unsigned long arg)
{
	int ret = 0;

	switch (cmd) {
	case SEND_INTERRUPT: {
		struct sailmb_data data;

		mutex_lock(&sailmb_dev->dev_lock);

		if (copy_from_user(&data, (struct sailmb_data *)arg,
				sizeof(struct sailmb_data))) {
			dev_err(sailmb_dev->dev, "copy from user failed, cmd=%x\n", cmd);
			mutex_unlock(&sailmb_dev->dev_lock);
			return -EFAULT;
		}

		if (data.sailmb_client.tx_mode == 1) {
			dev_dbg(sailmb_dev->dev, "sending irq %d\n", data.sailmb_client.signal_num);

			ret = sail_send_interrupt(sailmb_dev, data.sailmb_client.signal_num);
			if (ret < 0) {
				mutex_unlock(&sailmb_dev->dev_lock);
				return ret;
			}
		} else {
			ret = -EPERM;
			dev_err(sailmb_dev->dev, "send interrupt not permitted on RX channel\n");
		}

		mutex_unlock(&sailmb_dev->dev_lock);
		break;
	}

	case SET_EVENT_FD: {
		unsigned int irq;
		struct sailmb_data data;

		mutex_lock(&sailmb_dev->dev_lock);

		if (copy_from_user(&data,
			(struct sailmb_data *)arg, sizeof(struct sailmb_data))) {
			dev_err(sailmb_dev->dev, "copy from user failed, cmd=%x\n", cmd);
			mutex_unlock(&sailmb_dev->dev_lock);
			return -EFAULT;
		}

		if (data.sailmb_client.rx_mode == 1) {
			dev_dbg(sailmb_dev->dev, "setting eventfd for irq %d\n",
				data.sailmb_client.signal_num);

			ret = set_eventfd_for_signal(sailmb_dev->irq_data, sailmb_dev->irq_count,
				data.sailmb_client.signal_num, &data.sailmb_client.event_fd);
			if (ret) {
				dev_err(sailmb_dev->dev, "set eventfd failed %d\n", ret);
				mutex_unlock(&sailmb_dev->dev_lock);
				return ret;
			}

			dev_dbg(sailmb_dev->dev, "set eventfd successful, enabling irq\n");
			ret = get_irq_from_signal(sailmb_dev->irq_data, sailmb_dev->irq_count,
				data.sailmb_client.signal_num, &irq);
			if (ret) {
				dev_err(sailmb_dev->dev, "error %d getting irq for signal %d\n",
					ret, data.sailmb_client.signal_num);
				mutex_unlock(&sailmb_dev->dev_lock);
				return ret;
			}

			enable_irq(irq);
		} else {
			ret = -EPERM;
			dev_err(sailmb_dev->dev, "cannot set eventfd for TX channel\n");
		}

		mutex_unlock(&sailmb_dev->dev_lock);
		break;
	}

	case DISABLE_INTERRUPT: {
		unsigned int irq;
		struct sailmb_data data;

		mutex_lock(&sailmb_dev->dev_lock);

		if (copy_from_user(&data, (struct sailmb_data *)arg,
				sizeof(struct sailmb_data))) {
			dev_err(sailmb_dev->dev, "copy from user failed\n");
			mutex_unlock(&sailmb_dev->dev_lock);
			return -EFAULT;
		}

		if (data.sailmb_client.rx_mode == 1) {
			ret = get_irq_from_signal(sailmb_dev->irq_data, sailmb_dev->irq_count,
				data.sailmb_client.signal_num, &irq);
			if (ret) {
				dev_err(sailmb_dev->dev, "error %d getting irq for signal %d\n",
					ret, data.sailmb_client.signal_num);
				mutex_unlock(&sailmb_dev->dev_lock);
				return ret;
			}

			ret = release_eventfd_for_signal(sailmb_dev->irq_data,
				sailmb_dev->irq_count, data.sailmb_client.signal_num);
			if (ret) {
				dev_err(sailmb_dev->dev, "failed to reset eventfd %d\n", ret);
				mutex_unlock(&sailmb_dev->dev_lock);
				return ret;
			}

			disable_irq(irq);
		} else {
			ret = -EPERM;
			dev_err(sailmb_dev->dev, "cannot disable interrupt for TX channel\n");
		}

		mutex_unlock(&sailmb_dev->dev_lock);
		break;
	}

	case SET_MAILBOX_ADDR: {
		mutex_lock(&sailmb_dev->dev_lock);
		if (sailmb_dev->mmap_ioctl_set) {
			dev_err(sailmb_dev->dev, "invalid driver state to perform mmap\n");
			mutex_unlock(&sailmb_dev->dev_lock);
			return -EPERM;
		}

		sailmb_dev->pr_data.mmap_phy_addr = MAILBOX_PHY_ADDR;
		sailmb_dev->pr_data.mmap_size = MAILBOX_SIZE;
		sailmb_dev->mmap_ioctl_set = true;
		mutex_unlock(&sailmb_dev->dev_lock);
		break;
	}

	case SET_OTA_ADDR: {
		mutex_lock(&sailmb_dev->dev_lock);
		if (sailmb_dev->mmap_ioctl_set) {
			dev_err(sailmb_dev->dev, "invalid driver state to perform mmap\n");
			mutex_unlock(&sailmb_dev->dev_lock);
			return -EPERM;
		}

		sailmb_dev->pr_data.mmap_phy_addr = OTA_PHY_ADDR;
		sailmb_dev->pr_data.mmap_size = OTA_SIZE;
		sailmb_dev->mmap_ioctl_set = true;
		mutex_unlock(&sailmb_dev->dev_lock);
		break;
	}

	default:
		dev_err(sailmb_dev->dev, "invalid ioctl call, cmd=%x\n", cmd);
		ret = -EPERM;
	}
	return ret;
}

static int sail_mb_mmap(struct file *f, struct vm_area_struct *vma)
{
	int ret;
	unsigned long phy_addr;
	unsigned long size;
	unsigned long page_offset;

	mutex_lock(&sailmb_dev->dev_lock);
	phy_addr = sailmb_dev->pr_data.mmap_phy_addr;
	size = sailmb_dev->pr_data.mmap_size;
	page_offset = phy_addr & ~PAGE_MASK;

	if (!sailmb_dev->mmap_ioctl_set) {
		dev_err(sailmb_dev->dev, "invalid driver state to perform mmap\n");
		mutex_unlock(&sailmb_dev->dev_lock);
		return -EPERM;
	}

	if (page_offset) {
		phy_addr &= PAGE_MASK;
		phy_addr += PAGE_SIZE;
		size += page_offset;
	}

	if (size > sailmb_dev->pr_data.mmap_size)
		dev_err(sailmb_dev->dev, "requested size 0x%x greater than mmap_size 0x%x\n",
			size, sailmb_dev->pr_data.mmap_size);

	vma->vm_page_prot = pgprot_writecombine(pgprot_noncached(vma->vm_page_prot));

	ret = remap_pfn_range(vma, vma->vm_start, phy_addr >> PAGE_SHIFT, size, vma->vm_page_prot);
	if (ret) {
		dev_err(sailmb_dev->dev, "error while remapping %d\n", ret);
		mutex_unlock(&sailmb_dev->dev_lock);
		return ret;
	}

	sailmb_dev->mmap_ioctl_set = false;
	mutex_unlock(&sailmb_dev->dev_lock);
	return 0;
}

static const struct file_operations sailmb_fops = {
	.unlocked_ioctl = sail_mb_ioctl_call,
	.mmap = sail_mb_mmap,
};

static int sail_handshake(void __iomem *tcsr_reg, void __iomem *ipc_reg,
		const unsigned int magic)
{
	int tcsr_write_value;
	unsigned int i;

	for (i = 0; i < 4; i++) {
		tcsr_write_value = (((magic & (0xFFU << (8U * i))) >> (8U * i)) & 0xFFU);
		writel_relaxed(tcsr_write_value, tcsr_reg);
		tcsr_reg = tcsr_reg + 4;
	}

	isb();
	/* ensure TSCR register writes go through before triggering IPC to SAIL */
	mb();

	writel_relaxed(SAIL_IPC_SIGNAL, ipc_reg);

	return 0;
}

static int do_sailmb_init_handshakes(void __iomem *tcsr_reg, void __iomem *ipc_reg)
{
	int ret;

	ret = sail_handshake(tcsr_reg, ipc_reg, SAIL_S1_MAGIC);
	if (ret) {
		dev_err(sailmb_dev->dev, "sail_s1_handshake returned error %d\n", ret);
		return ret;
	}

	if (sailmb_dev->sail_handshake_delay <= 20000)
		usleep_range(sailmb_dev->sail_handshake_delay-100,
				sailmb_dev->sail_handshake_delay);
	else
		msleep(sailmb_dev->sail_handshake_delay/1000);

	ret = sail_handshake(tcsr_reg, ipc_reg, SAIL_S2_MAGIC);
	if (ret) {
		dev_err(sailmb_dev->dev, "sail_s2_handshake returned error %d\n", ret);
		return ret;
	}

	return ret;
}

static int sail_suspend_ack(void __iomem *tcsr_reg)
{
	unsigned int i;
	unsigned int magic = 0U;

	for (i = 0; i < SAIL_SUSPEND_ACK_TIMEOUT_MS; i++) {
		magic = 0U;
		magic |= readl_relaxed(tcsr_reg) & (0xFFU);
		magic |= (readl_relaxed(tcsr_reg + 4) & (0xFFU)) << 8U;
		magic |= (readl_relaxed(tcsr_reg + 8) & (0xFFU)) << 16U;
		magic |= (readl_relaxed(tcsr_reg + 12) & (0xFFU)) << 24U;

		if (magic == SAIL_SUSPEND_MAGIC) {
			dev_dbg(sailmb_dev->dev, "magic number matched\n");
			return 0;
		}

		usleep_range(900, 1000);
	}

	return -ETIMEDOUT;
}

static int sail_suspend_handshake(void __iomem *req_tcsr_reg, void __iomem *ipc_reg,
		void __iomem *resp_tcsr_reg)
{
	int ret;

	ret = sail_handshake(req_tcsr_reg, ipc_reg, SAIL_SUSPEND_MAGIC);
	if (ret) {
		dev_err(sailmb_dev->dev, "sail suspend handshake returned error %d\n", ret);
		return ret;
	}

	ret = sail_suspend_ack(resp_tcsr_reg);
	if (ret) {
		dev_err(sailmb_dev->dev, "sail failed to acknowledge suspend call\n");
		return ret;
	}

	return 0;
}

static int populate_irq_from_dt(struct platform_device *pdev, struct sail_mailbox *sailmb_dev)
{
	int i;
	int ret;
	int count = 0;
	const char *intr_name = "sailmb_irq";
	const size_t irq_name_size = strlen(intr_name) + 1;

	sailmb_dev->irq_count = of_property_count_elems_of_size(pdev->dev.of_node,
		"interrupts", (3 * sizeof(u32)));
	if (sailmb_dev->irq_count <= 0) {
		dev_err(sailmb_dev->dev, "invalid interrupt count %d\n", sailmb_dev->irq_count);
		return -EFAULT;
	}

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

	for (i = 0; i < (3 * sailmb_dev->irq_count); i += 3) {
		ret = of_property_read_u32_index(pdev->dev.of_node, "interrupts",
			i, &sailmb_dev->irq_data[count].client_id);
		if (ret) {
			dev_err(sailmb_dev->dev,
				"failed to get client_id for irq %d, err=%d\n", count, ret);
			return ret;
		}

		ret = of_property_read_u32_index(pdev->dev.of_node, "interrupts",
			i+1, &sailmb_dev->irq_data[count].signal_id);
		if (ret) {
			dev_err(sailmb_dev->dev,
				"failed to get signal_id for irq %d, err=%d\n", count, ret);
			return ret;
		}

		sailmb_dev->irq_data[count].irq = platform_get_irq(pdev, count);
		sailmb_dev->irq_data[count].irq_name =
				devm_kzalloc(&pdev->dev, irq_name_size, GFP_KERNEL);
		strscpy(sailmb_dev->irq_data[count].irq_name, intr_name, irq_name_size);

		dev_dbg(sailmb_dev->dev, "requesting irq %d\n", sailmb_dev->irq_data[count].irq);
		ret = devm_request_irq(&pdev->dev, sailmb_dev->irq_data[count].irq,
			sailmb_intr_handler, 0,
			sailmb_dev->irq_data[count].irq_name, sailmb_dev);
		if (ret) {
			dev_err(sailmb_dev->dev, "failed to request interrupt for irq %d, err=%d\n",
				 sailmb_dev->irq_data[count].irq, ret);
			return ret;
		}

		disable_irq_nosync(sailmb_dev->irq_data[count].irq);
		count++;
	}

	dev_dbg(sailmb_dev->dev, "irq initialization successful\n");
	return 0;
}

static int populate_mbox_from_dt(struct platform_device *pdev, struct sail_mailbox *sailmb_dev)
{
	int i;
	int ret;
	int count = 0;

	sailmb_dev->mbox_count = of_property_count_elems_of_size(pdev->dev.of_node,
			"mboxes", (3 * sizeof(u32)));
	if (sailmb_dev->mbox_count <= 0) {
		dev_err(sailmb_dev->dev, "invalid mbox count %d\n", sailmb_dev->mbox_count);
		return -EFAULT;
	}

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

	for (i = 1; i < (3 * sailmb_dev->mbox_count); i += 3) {
		ret = of_property_read_u32_index(pdev->dev.of_node, "mboxes",
			i, &sailmb_dev->mbox_data[count].client_id);
		if (ret) {
			dev_err(sailmb_dev->dev,
				"failed to get client_id for mbox %d, err=%d\n", count, ret);
			return ret;
		}

		ret = of_property_read_u32_index(pdev->dev.of_node, "mboxes",
			i+1, &sailmb_dev->mbox_data[count].signal_id);
		if (ret) {
			dev_err(sailmb_dev->dev,
				"failed to get signal_id for mbox %d, err=%d\n", count, ret);
			return ret;
		}

		dev_dbg(&pdev->dev, "requesting mbox %d\n", count);
		sailmb_dev->mbox_data[count].mbox_client.dev = &pdev->dev;
		sailmb_dev->mbox_data[count].mbox_client.knows_txdone = true;

		sailmb_dev->mbox_data[count].mchan =
			mbox_request_channel(&sailmb_dev->mbox_data[count].mbox_client,
				count);
		if (IS_ERR(sailmb_dev->mbox_data[count].mchan)) {
			dev_err(sailmb_dev->dev, "failed to get ipc mailbox %ld\n",
				PTR_ERR(sailmb_dev->mbox_data[count].mchan));
			ret = PTR_ERR(sailmb_dev->mbox_data[count].mchan);
			return ret;
		}

		if (sailmb_dev->mbox_data[count].mchan == NULL) {
			dev_err(sailmb_dev->dev, "mailbox mchan is NULL\n");
			return -EINVAL;
		}

		count++;
	}

	dev_dbg(sailmb_dev->dev, "mbox initialization successful\n");
	return 0;
}

static int sailmb_probe(struct platform_device *pdev)
{
	int ret;
	struct device *dev;
	struct resource *req_tcsr_res;
	struct resource *resp_tcsr_res;
	struct resource *ipc_res;
	unsigned int delay;

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

	sailmb_dev->dev = &pdev->dev;

	req_tcsr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!req_tcsr_res) {
		dev_err(sailmb_dev->dev, "failed to acquire tcsr io resource\n");
		return -EINVAL;
	}

	sailmb_dev->req_tcsr_reg = devm_ioremap_resource(&pdev->dev, req_tcsr_res);
	if (IS_ERR(sailmb_dev->req_tcsr_reg)) {
		dev_err(sailmb_dev->dev, "ioremap of tcsr register failed\n");
		return PTR_ERR(sailmb_dev->req_tcsr_reg);
	}

	resp_tcsr_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	if (!resp_tcsr_res) {
		dev_err(sailmb_dev->dev, "failed to acquire tcsr io resource\n");
		return -EINVAL;
	}

	sailmb_dev->resp_tcsr_reg = devm_ioremap_resource(&pdev->dev, resp_tcsr_res);
	if (IS_ERR(sailmb_dev->resp_tcsr_reg)) {
		dev_err(sailmb_dev->dev, "ioremap of tcsr register failed\n");
		return PTR_ERR(sailmb_dev->resp_tcsr_reg);
	}

	ipc_res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
	if (!ipc_res) {
		dev_err(sailmb_dev->dev, "failed to acquire ipc io resource\n");
		return -EINVAL;
	}

	sailmb_dev->ipc_reg = devm_ioremap_resource(&pdev->dev, ipc_res);
	if (IS_ERR(sailmb_dev->ipc_reg)) {
		dev_err(sailmb_dev->dev, "Ioremap of ipc register failed\n");
		return PTR_ERR(sailmb_dev->ipc_reg);
	}

	ret = alloc_chrdev_region(&sailmb_dev->sailmb_dev_num, 0, 1, "sailMB");
	if (ret) {
		dev_err(sailmb_dev->dev, "failed to alloc char dev for the driver %d\n", ret);
		goto failed_chrdev_alloc;
	}

	cdev_init(&sailmb_dev->sailmb_cdev, &sailmb_fops);
	ret = cdev_add(&sailmb_dev->sailmb_cdev, sailmb_dev->sailmb_dev_num, 1);
	if (ret) {
		dev_err(sailmb_dev->dev, "char device driver add failed %d\n", ret);
		goto failed_cdev_add;
	}

	sailmb_dev->sailmb_dev_major = MAJOR(sailmb_dev->sailmb_dev_num);

	sailmb_dev->sailmb_driver_class = class_create(THIS_MODULE, "sailMB");
	if (IS_ERR_OR_NULL(sailmb_dev->sailmb_driver_class)) {
		dev_err(sailmb_dev->dev, "char device driver class create error\n");
		ret = -EFAULT;
		goto failed_class_create;
	}

	dev = device_create(sailmb_dev->sailmb_driver_class, NULL,
			sailmb_dev->sailmb_dev_num, NULL, "sailMB");
	if (IS_ERR_OR_NULL(dev)) {
		dev_err(sailmb_dev->dev, "device create error\n");
		ret = -EFAULT;
		goto failed_device_create;
	}

	ret = populate_mbox_from_dt(pdev, sailmb_dev);
	if (ret)
		goto out;

	ret = populate_irq_from_dt(pdev, sailmb_dev);
	if (ret)
		goto out;

	mutex_init(&sailmb_dev->dev_lock);

	ret = of_property_read_u32(sailmb_dev->dev->of_node, "sail-handshake-delay", &delay);
	if (ret)
		sailmb_dev->sail_handshake_delay = SAIL_HANDSHAKE_DEFAULT_DELAY_US;
	else
		sailmb_dev->sail_handshake_delay = delay;

	if (sailmb_dev->sail_handshake_delay < 100)
		sailmb_dev->sail_handshake_delay = 100;

	ret = do_sailmb_init_handshakes(sailmb_dev->req_tcsr_reg, sailmb_dev->ipc_reg);
	if (ret) {
		dev_err(sailmb_dev->dev, "sail-mailbox handshake failed %d\n", ret);
		goto out;
	}

	dev_dbg(sailmb_dev->dev, "sail mailbox ready\n");
	return 0;

out:
	device_destroy(sailmb_dev->sailmb_driver_class, sailmb_dev->sailmb_dev_num);
failed_device_create:
	class_destroy(sailmb_dev->sailmb_driver_class);
failed_class_create:
	cdev_del(&sailmb_dev->sailmb_cdev);
failed_cdev_add:
	unregister_chrdev_region(sailmb_dev->sailmb_dev_num, 1);
failed_chrdev_alloc:
	return ret;
}

static int sailmb_remove(struct platform_device *pdev)
{
	device_destroy(sailmb_dev->sailmb_driver_class, sailmb_dev->sailmb_dev_num);
	class_destroy(sailmb_dev->sailmb_driver_class);
	cdev_del(&sailmb_dev->sailmb_cdev);
	unregister_chrdev_region(sailmb_dev->sailmb_dev_num, 1);

	return 0;
}

static int sailmb_suspend(struct device *dev)
{
	int ret;

	ret = sail_suspend_handshake(sailmb_dev->req_tcsr_reg, sailmb_dev->ipc_reg,
			sailmb_dev->resp_tcsr_reg);
	if (ret) {
		dev_err(sailmb_dev->dev, "sail_suspend_handshake returned error %d\n", ret);
		goto suspend_fail;
	}

	return 0;

suspend_fail:
	do_sailmb_init_handshakes(sailmb_dev->req_tcsr_reg, sailmb_dev->ipc_reg);
	return ret;
}

static int sailmb_resume(struct device *dev)
{
	int ret;

	dev_dbg(sailmb_dev->dev, "sail-mailbox resuming, sending handshake signals\n");
	ret = do_sailmb_init_handshakes(sailmb_dev->req_tcsr_reg, sailmb_dev->ipc_reg);

	return ret;
}

static const struct dev_pm_ops sailmb_pm_ops = {
	.suspend = sailmb_suspend,
	.resume = sailmb_resume,
};

static const struct of_device_id sailmb_dt_match[] = {
	{ .compatible = "qcom,sail-mailbox" },
	{}
};

MODULE_DEVICE_TABLE(of, sailmb_dt_match);

static struct platform_driver sailmb_driver = {
	.driver = {
		.name = "sail_mailbox",
		.of_match_table = sailmb_dt_match,
		.suppress_bind_attrs = true,
		.pm = &sailmb_pm_ops,
	},
	.probe = sailmb_probe,
	.remove = sailmb_remove,
};

module_platform_driver(sailmb_driver);

MODULE_DESCRIPTION("Qualcomm Technologies, Inc. sail mailbox driver");
MODULE_LICENSE("GPL");
