// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved.
 */

#define pr_fmt(msg) "slatecom: %s: " msg, __func__

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/bitops.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/kthread.h>
#include <linux/dma-mapping.h>
#include <linux/pm_runtime.h>
#include <linux/suspend.h>
#include <linux/ipc_logging.h>
#include "slatecom.h"
#include <linux/soc/qcom/slatecom_interface.h>

#define SLATE_SPI_WORD_SIZE (0x04)
#define SLATE_SPI_READ_LEN (0x04)
#define SLATE_SPI_WRITE_CMND_LEN (0x01)
#define SLATE_SPI_FIFO_READ_CMD (0x41)
#define SLATE_SPI_FIFO_WRITE_CMD (0x40)
#define SLATE_SPI_AHB_READ_CMD (0x43)
#define SLATE_SPI_AHB_WRITE_CMD (0x42)
#define SLATE_SPI_AHB_CMD_LEN (0x05)
#define SLATE_SPI_AHB_READ_CMD_LEN (0x08)
#define SLATE_STATUS_REG (0x05)
#define SLATE_CMND_REG (0x14)

#define SLATE_SPI_MAX_WORDS (0x3FFFFFFD)
#define SLATE_SPI_MAX_REGS (0x0A)
#define HED_EVENT_ID_LEN (0x02)
#define HED_EVENT_SIZE_LEN (0x02)
#define HED_EVENT_DATA_STRT_LEN (0x05)
#define CMA_BFFR_POOL_SIZE (128*1024)
#define TX_AHB_BUF_SIZE 1024
#define SLATE_HEALTH_CHECK     BIT(23)
#define SLATE_OK_SLP_RBSC      BIT(24)
#define SLATE_OK_SLP_S2R       BIT(25)
#define SLATE_OK_SLP_S2D      (BIT(25) | BIT(24))
#define SLATE_OK_SLP_SIF       BIT(26)

#define OK_TO_SLEEP_CLEARED    BIT(18)
#define SLAVE_STATUS_READY     BIT(31)

#define WR_PROTOCOL_OVERHEAD              (5)
#define WR_PROTOCOL_OVERHEAD_IN_WORDS     (2)

#define WR_BUF_SIZE_IN_BYTES	CMA_BFFR_POOL_SIZE
#define WR_BUF_SIZE_IN_WORDS	(CMA_BFFR_POOL_SIZE / sizeof(uint32_t))
#define WR_BUF_SIZE_IN_WORDS_FOR_USE   \
		(WR_BUF_SIZE_IN_WORDS - WR_PROTOCOL_OVERHEAD_IN_WORDS)

#define WR_BUF_SIZE_IN_BYTES_FOR_USE	(WR_BUF_SIZE_IN_WORDS_FOR_USE * sizeof(uint32_t))
#define SLATE_RESUME_IRQ_TIMEOUT 100
#define SLATE_SPI_AUTOSUSPEND_TIMEOUT 500
#define MIN_SLEEP_TIME	5

/* Master_Command[27] */
#define SLATE_PAUSE_OK		BIT(27)

/* SLAVE_STATUS_AUTO_CLEAR[16:15] */
#define SLATE_PAUSE_REQ		BIT(15)
#define SLATE_RESUME_IND	BIT(16)

#define SPI_FREQ_1MHZ	1000000
#define SPI_FREQ_40MHZ	40000000

#define MAX_RETRY 3

/* Define IPC Logging Macros */
#define LOG_PAGES_CNT 2
static void *slatecom_ipc_log;

#define SLATECOM_INFO(x, ...)						     \
	ipc_log_string(slatecom_ipc_log, "[%s]: "x, __func__, ##__VA_ARGS__)

#define SLATECOM_ERR(x, ...)						     \
do {									     \
	printk_ratelimited("%s[%s]: " x, KERN_ERR, __func__, ##__VA_ARGS__); \
	ipc_log_string(slatecom_ipc_log, "%s[%s]: " x, "", __func__,         \
			##__VA_ARGS__);\
} while (0)

enum slatecom_state {
	/*SLATECOM Staus ready*/
	SLATECOM_PROB_SUCCESS = 0,
	SLATECOM_PROB_WAIT = 1,
	SLATECOM_STATE_SUSPEND_PREPARE = 2,
	SLATECOM_STATE_SUSPEND = 3,
	SLATECOM_STATE_ACTIVE = 4,
	SLATECOM_STATE_RUNTIME_SUSPEND = 5,
	SLATECOM_STATE_HIBERNATE = 6,
};

enum slatecom_req_type {
	/*SLATECOM local requests*/
	SLATECOM_READ_REG = 0,
	SLATECOM_READ_FIFO = 1,
	SLATECOM_READ_AHB = 2,
	SLATECOM_WRITE_REG = 3,
};

struct slate_spi_priv {
	struct spi_device *spi;
	/* Transaction related */
	struct mutex xfer_mutex;
	void *lhandle;
	/* Message for single transfer */
	struct spi_message msg1;
	struct spi_transfer xfer1;
	atomic_t irq_lock;

};

struct cb_data {
	void *priv;
	void *handle;
	void (*slatecom_notification_cb)(void *handle, void *priv,
		enum slatecom_event_type event,
		union slatecom_event_data_type *event_data);
	struct list_head list;
};

struct slate_context {
	struct slate_spi_priv *slate_spi;
	enum slatecom_state state;
	struct cb_data *cb;
};

struct event_list {
	struct event *evnt;
	struct list_head list;
};
static void *slate_com_drv;
static uint32_t g_slav_status_reg;
static uint32_t g_slave_status_auto_clear_reg;
static bool is_hibernate;
static bool s2a_status;

/* SLATECOM client callbacks set-up */
static void send_input_events(struct work_struct *work);
static struct list_head cb_head = LIST_HEAD_INIT(cb_head);
static struct list_head pr_lst_hd = LIST_HEAD_INIT(pr_lst_hd);
static DEFINE_SPINLOCK(lst_setup_lock);
static enum slatecom_spi_state spi_state;


static struct workqueue_struct *wq;
static DECLARE_WORK(input_work, send_input_events);

static struct mutex slate_resume_mutex;
static struct mutex slate_task_mutex;

static atomic_t  slate_is_runtime_suspend;
static atomic_t  slate_is_spi_active;
static atomic_t  ok_to_sleep;
static atomic_t  state;
static int slate_irq;

static uint8_t *fxd_mem_buffer;
static struct mutex cma_buffer_lock;
static ktime_t sleep_time_start;

static DECLARE_COMPLETION(slate_resume_wait);
static int slatecom_reg_write_cmd(void *handle, uint8_t reg_start_addr,
		uint8_t num_regs, void *write_buf, bool flag);

static int slatecom_reg_read_internal(void *handle, uint8_t reg_start_addr,
	uint32_t num_regs, void *read_buf);
static int slatecom_force_resume(void *handle);

struct subsys_state_ops state_ops;
static irqreturn_t slate_irq_tasklet_hndlr(int irq, void *device);
static int req_irq_flag = 1;

static struct spi_device *get_spi_device(void)
{
	struct slate_spi_priv *slate_spi = container_of(slate_com_drv,
						struct slate_spi_priv, lhandle);
	struct spi_device *spi = slate_spi->spi;
	return spi;
}

static void augmnt_fifo(uint8_t *data, int pos)
{
	data[pos] = '\0';
}

void slatecom_state_init(void (*fn1)(bool), void (*fn2)(bool))
{
	state_ops.set_dsp_state = fn1;
	state_ops.set_bt_state = fn2;
}
EXPORT_SYMBOL(slatecom_state_init);

static void send_input_events(struct work_struct *work)
{
	struct list_head *temp;
	struct list_head *pos;
	struct event_list *node;
	struct event *evnt;

	if (list_empty(&pr_lst_hd))
		return;

	list_for_each_safe(pos, temp, &pr_lst_hd) {
		node = list_entry(pos, struct event_list, list);
		evnt = node->evnt;
		//bgrsb_send_input(evnt);
		kfree(evnt);
		spin_lock(&lst_setup_lock);
		list_del(&node->list);
		spin_unlock(&lst_setup_lock);
		kfree(node);
	}
}

int slatecom_set_spi_state(enum slatecom_spi_state state)
{
	struct slate_spi_priv *slate_spi = container_of(slate_com_drv,
						struct slate_spi_priv, lhandle);
	ktime_t time_start, delta;
	s64 time_elapsed;
	struct slate_context clnt_handle;
	int ret = 0;
	struct device spi_dev;

	if (!slate_com_drv)
		return -ENODEV;
	spi_dev = slate_spi->spi->master->dev;

	if (req_irq_flag && state == SLATECOM_SPI_FREE) {
		ret = request_threaded_irq(slate_irq, NULL, slate_irq_tasklet_hndlr,
		IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "qcom-slate_spi", slate_spi);
		if (ret) {
			pr_err("qcom-slate_spi: failed to register IRQ:%d\n", ret);
		}
		ret = irq_set_irq_wake(slate_irq, true);
		if (ret) {
			pr_err("irq set as wakeup return: %d\n", ret);
		}
		req_irq_flag = 0;
	}

	clnt_handle.slate_spi = slate_spi;

	if (state < 0 || state > 1) {
		SLATECOM_ERR("Invalid spi state. Returning %d\n", -EINVAL);
		return -EINVAL;
	}

	if (state == spi_state) {
		SLATECOM_ERR("state same as spi_state. Returning 0\n");
		return 0;
	}

	mutex_lock(&slate_spi->xfer_mutex);
	if (state == SLATECOM_SPI_BUSY) {
		time_start = ktime_get();
		while (!pm_runtime_status_suspended(spi_dev.parent)) {
			delta = ktime_sub(ktime_get(), time_start);
			time_elapsed = ktime_to_ms(delta);
			WARN_ON(time_elapsed > 5 * MSEC_PER_SEC);
			SLATECOM_INFO("Waiting to set state busy....\n");
			msleep(100);
		}
		/* After PIL seq, clear preceding state. As rmproc
		 * reloaded, it is unaware of previous context.
		 */
		atomic_set(&ok_to_sleep, 0);
		atomic_set(&slate_is_runtime_suspend, 0);
	}
	spi_state = state;
	SLATECOM_INFO("state = %d\n", state);
	mutex_unlock(&slate_spi->xfer_mutex);

	if (state == SLATECOM_SPI_FREE) {
		SLATECOM_ERR("Need force resume\n");
		slatecom_force_resume(&clnt_handle);
	}

	return 0;
}
EXPORT_SYMBOL(slatecom_set_spi_state);

static inline
void add_to_irq_list(struct  cb_data *data)
{
	list_add_tail(&data->list, &cb_head);
}

static uint8_t is_slatecom_ready(void)
{
	if (slate_com_drv == NULL)
		pr_err("driver not probed yet\n");
	return (slate_com_drv != NULL ? 1 : 0);
}

static void slate_spi_reinit_xfer(struct spi_transfer *xfer)
{
	xfer->tx_buf = NULL;
	xfer->rx_buf = NULL;
	xfer->delay.value = 0;
	xfer->len = 0;
}

static int read_slate_locl(enum slatecom_req_type req_type,
	uint32_t no_of_words, void *buf)
{
	struct slate_context clnt_handle;
	struct slate_spi_priv *spi = NULL;
	int ret = 0;

	if (!slate_com_drv || !buf) {
		pr_err("driver not probed yet or buf is empty\n");
		return -ENODEV;
	}
	spi = container_of(slate_com_drv, struct slate_spi_priv, lhandle);
	clnt_handle.slate_spi = spi;

	switch (req_type) {
	case SLATECOM_READ_REG:
		ret = slatecom_reg_read(&clnt_handle,
			SLATE_STATUS_REG, no_of_words, buf);
		break;
	case SLATECOM_READ_FIFO:
		ret = slatecom_fifo_read(&clnt_handle, no_of_words, buf);
		break;
	case SLATECOM_WRITE_REG:
		ret = slatecom_reg_write(&clnt_handle, SLATE_CMND_REG,
					no_of_words, buf);
		break;
	case SLATECOM_READ_AHB:
		break;
	}
	return ret;
}

static int slatecom_transfer(void *handle, uint8_t *tx_buf,
	uint8_t *rx_buf, uint32_t txn_len, uint32_t freq, bool flag)
{
	struct spi_transfer *tx_xfer;
	struct slate_spi_priv *slate_spi;
	struct slate_context *cntx;
	struct spi_device *spi = NULL;
	int ret = 0;

	if (!handle || !tx_buf)
		return -EINVAL;

	cntx = (struct slate_context *)handle;

	if (cntx->state == SLATECOM_PROB_WAIT) {
		if (!is_slatecom_ready())
			return -ENODEV;
		cntx->slate_spi = container_of(slate_com_drv,
						struct slate_spi_priv, lhandle);
		cntx->state = SLATECOM_PROB_SUCCESS;
	}
	slate_spi = cntx->slate_spi;

	if (!slate_spi)
		return -ENODEV;

	tx_xfer = &slate_spi->xfer1;
	spi = slate_spi->spi;

	mutex_lock(&slate_spi->xfer_mutex);

	if (!atomic_read(&slate_is_spi_active)) {
		SLATECOM_ERR("slatecom is inactive\n");
		mutex_unlock(&slate_spi->xfer_mutex);
		return -ECANCELED;
	}
	if (flag) {
		SLATECOM_ERR("OK_TO_SLEEP Flag is set\n");
		atomic_set(&slate_is_spi_active, 0);
	}

	slate_spi_reinit_xfer(tx_xfer);
	tx_xfer->tx_buf = tx_buf;
	if (rx_buf)
		tx_xfer->rx_buf = rx_buf;

	tx_xfer->len = txn_len;
	SLATECOM_INFO("txn_len = %d\n", txn_len);
	tx_xfer->speed_hz = freq;
	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("SPI is held by TZ, skip spi_sync\n");
		mutex_unlock(&slate_spi->xfer_mutex);
		return -EBUSY;
	}
	ret = spi_sync(spi, &slate_spi->msg1);
	mutex_unlock(&slate_spi->xfer_mutex);

	if (ret)
		SLATECOM_ERR("SPI transaction failed: %d\n", ret);
	return ret;
}

/* SLATE-COM Interrupt handling */
static inline
void send_event(enum slatecom_event_type event,
	void *data)
{
	struct list_head *pos;
	struct cb_data *cb;

	/* send interrupt notification for each
	 * registered call-back
	 */
	list_for_each(pos, &cb_head) {
		cb = list_entry(pos, struct cb_data, list);
		cb->slatecom_notification_cb(cb->handle,
		cb->priv,  event, data);
	}
}

void slatecom_slatedown_handler(void)
{
	struct spi_device *spi = NULL;

	if (!is_slatecom_ready())
		return;
	spi = get_spi_device();
	g_slav_status_reg = 0;
	atomic_set(&ok_to_sleep, 0);
	pm_runtime_get_sync(&spi->dev);
	send_event(SLATECOM_EVENT_RESET_OCCURRED, NULL);
	pm_runtime_mark_last_busy(&spi->dev);
	pm_runtime_put_sync_autosuspend(&spi->dev);
}
EXPORT_SYMBOL(slatecom_slatedown_handler);

static void parse_fifo(uint8_t *data, uint16_t data_len, union slatecom_event_data_type *event_data)
{
	uint16_t p_len;
	uint16_t event_id;
	void *evnt_data;

	while (*data != '\0') {
		if (data_len < HED_EVENT_ID_LEN)
			break;
		event_id = *((uint16_t *) data);
		data = data + HED_EVENT_ID_LEN;
		data_len = data_len - HED_EVENT_ID_LEN;
		if (data_len < HED_EVENT_SIZE_LEN)
			break;
		p_len = *((uint16_t *) data);
		data = data + HED_EVENT_SIZE_LEN;
		data_len = data_len - HED_EVENT_SIZE_LEN;

		if (event_id == 0x0001) {
			evnt_data = kmalloc(p_len, GFP_KERNEL);
			if (evnt_data != NULL) {
				memcpy(evnt_data, data, p_len);
				event_data->fifo_data.to_master_fifo_used =
						p_len/SLATE_SPI_WORD_SIZE;
				event_data->fifo_data.data = evnt_data;
				send_event(SLATECOM_EVENT_TO_MASTER_FIFO_USED,
						event_data);
			}
		} else if (event_id == 0xc8) {
			data = data + 12;
			data_len = data_len - 12;
			SLATECOM_INFO("Packet Received = 0x%X, len = %u\n", event_id, p_len);
		}

		data = data + p_len;
		data_len = data_len - p_len;
	}
	if (!list_empty(&pr_lst_hd))
		queue_work(wq, &input_work);
}

static void send_back_notification(uint32_t slav_status_reg,
	uint32_t slav_status_auto_clear_reg,
	uint32_t fifo_fill_reg, uint32_t fifo_size_reg)
{
	uint16_t master_fifo_used;
	uint16_t slave_fifo_free;
	uint32_t *ptr;
	int ret = 0;
	uint32_t oem_provisioning_status;
	union slatecom_event_data_type event_data = { .fifo_data = {0} };

	master_fifo_used = (uint16_t)fifo_fill_reg;
	slave_fifo_free = (uint16_t)(fifo_fill_reg >> 16);

	if (slav_status_auto_clear_reg & BIT(31))
		send_event(SLATECOM_EVENT_RESET_OCCURRED, NULL);

	if (slav_status_auto_clear_reg & BIT(30))
		send_event(SLATECOM_EVENT_ERROR_WRITE_FIFO_OVERRUN, NULL);

	if (slav_status_auto_clear_reg & BIT(29))
		send_event(SLATECOM_EVENT_ERROR_WRITE_FIFO_BUS_ERR, NULL);

	if (slav_status_auto_clear_reg & BIT(28))
		send_event(SLATECOM_EVENT_ERROR_WRITE_FIFO_ACCESS, NULL);

	if (slav_status_auto_clear_reg & BIT(27))
		send_event(SLATECOM_EVENT_ERROR_READ_FIFO_UNDERRUN, NULL);

	if (slav_status_auto_clear_reg & BIT(26))
		send_event(SLATECOM_EVENT_ERROR_READ_FIFO_BUS_ERR, NULL);

	if (slav_status_auto_clear_reg & BIT(25))
		send_event(SLATECOM_EVENT_ERROR_READ_FIFO_ACCESS, NULL);

	if (slav_status_auto_clear_reg & BIT(24))
		send_event(SLATECOM_EVENT_ERROR_TRUNCATED_READ, NULL);

	if (slav_status_auto_clear_reg & BIT(23))
		send_event(SLATECOM_EVENT_ERROR_TRUNCATED_WRITE, NULL);

	if (slav_status_auto_clear_reg & BIT(22))
		send_event(SLATECOM_EVENT_ERROR_AHB_ILLEGAL_ADDRESS, NULL);

	if (slav_status_auto_clear_reg & BIT(21))
		send_event(SLATECOM_EVENT_ERROR_AHB_BUS_ERR, NULL);

	/* check if SLATE status is changed */
	if (g_slav_status_reg ^ slav_status_reg) {
		SLATECOM_ERR("Slate status 0x%x\n", slav_status_reg);
		if (slav_status_reg & BIT(30)) {
			event_data.application_running = true;
			send_event(SLATECOM_EVENT_APPLICATION_RUNNING,
				&event_data);
		}

		if (slav_status_reg & BIT(29)) {
			event_data.to_slave_fifo_ready = true;
			send_event(SLATECOM_EVENT_TO_SLAVE_FIFO_READY,
				&event_data);
		}

		if (slav_status_reg & BIT(28)) {
			event_data.to_master_fifo_ready = true;
			send_event(SLATECOM_EVENT_TO_MASTER_FIFO_READY,
				&event_data);
		}

		if (slav_status_reg & BIT(27)) {
			event_data.ahb_ready = true;
			send_event(SLATECOM_EVENT_AHB_READY,
				&event_data);
		}

		if (slav_status_reg & BIT(26)) {
			pr_err("Slate DSP DOWN\n", __func__);
			state_ops.set_dsp_state(false);
		} else if (slav_status_reg & BIT(30)) {
			if (!(slav_status_reg & BIT(26))) {
				pr_err("Slate DSP UP\n", __func__);
				state_ops.set_dsp_state(true);
			}
		}

		if (slav_status_reg & BIT(25)) {
			pr_err("Slate BT DOWN\n", __func__);
			state_ops.set_bt_state(false);
		} else if (slav_status_reg & BIT(30)) {
			if (!(slav_status_reg & BIT(25))) {
				pr_err("Slate BT UP\n", __func__);
				state_ops.set_bt_state(true);
			}
		}

		oem_provisioning_status = slav_status_reg & (BIT(23) | BIT(24));
		oem_provisioning_status = ((oem_provisioning_status<<7)>>30);
		SLATECOM_ERR("Slate OEM prov. status 0x%x\n", oem_provisioning_status);
	}

	if (master_fifo_used > 0) {
		ptr = kzalloc(master_fifo_used*SLATE_SPI_WORD_SIZE + 1,
			GFP_KERNEL | GFP_ATOMIC);
		if (ptr != NULL) {
			ret = read_slate_locl(SLATECOM_READ_FIFO,
				master_fifo_used,  ptr);
			if (!ret) {
				augmnt_fifo((uint8_t *)ptr,
					master_fifo_used*SLATE_SPI_WORD_SIZE);
				parse_fifo((uint8_t *)ptr,
					master_fifo_used*SLATE_SPI_WORD_SIZE, &event_data);
			}
			kfree(ptr);
		}
	}

	event_data.to_slave_fifo_free = slave_fifo_free;
	send_event(SLATECOM_EVENT_TO_SLAVE_FIFO_FREE, &event_data);
}

static void slate_irq_tasklet_hndlr_l(void)
{
	uint32_t slave_status_reg;
	uint32_t glink_isr_reg;
	uint32_t slav_status_auto_clear_reg;
	uint32_t fifo_fill_reg;
	uint32_t fifo_size_reg;
	int ret =  0;
	uint32_t irq_buf[5] = {0};
	uint32_t cmnd_reg = 0;
	struct slate_context clnt_handle;
	struct slate_spi_priv *spi = NULL;

	if (!slate_com_drv)
		return;
	spi = container_of(slate_com_drv, struct slate_spi_priv, lhandle);
	clnt_handle.slate_spi = spi;

	ret = slatecom_reg_read_internal(&clnt_handle, SLATE_STATUS_REG, 5, &irq_buf[0]);
	if (ret) {
		SLATECOM_ERR("Returning from tasklet handler with value %d\n", ret);
		return;
	}
	/* save current state */
	slave_status_reg = irq_buf[0];
	glink_isr_reg = irq_buf[1];
	slav_status_auto_clear_reg = irq_buf[2];
	fifo_fill_reg = irq_buf[3];
	fifo_size_reg = irq_buf[4];

	if (slav_status_auto_clear_reg & SLATE_PAUSE_REQ) {
		cmnd_reg |= SLATE_PAUSE_OK;
		ret = slatecom_reg_write_cmd(&clnt_handle,
				SLATE_CMND_REG, 1, &cmnd_reg, false);
		if (ret == 0) {
			spi_state = SLATECOM_SPI_PAUSE;
			SLATECOM_INFO("SPI is in Pause State\n");
		}
	}

	if (slav_status_auto_clear_reg & SLATE_RESUME_IND) {
		spi_state = SLATECOM_SPI_FREE;
		SLATECOM_INFO("Apps to resume operation\n");
	}

	/* Check if there are any status updates */
	if (slav_status_auto_clear_reg & OK_TO_SLEEP_CLEARED) {
		SLATECOM_INFO("SLAVE_STATUS_READY = 0x%08X, OK_TO_SLEEP_CLEARED = 0x%08X\n",
				(slave_status_reg & SLAVE_STATUS_READY),
				(OK_TO_SLEEP_CLEARED & slav_status_auto_clear_reg));
		g_slave_status_auto_clear_reg = slav_status_auto_clear_reg;
		atomic_set(&ok_to_sleep, 0);
		atomic_set(&slate_is_spi_active, 1);
		complete(&slate_resume_wait);
	}

	send_back_notification(slave_status_reg,
		slav_status_auto_clear_reg, fifo_fill_reg, fifo_size_reg);

	g_slav_status_reg = slave_status_reg;
}

static int wakeup_ahb_read(void *handle)
{
	uint8_t *tx_ahb_buf = NULL;
	uint8_t *rx_ahb_buf = fxd_mem_buffer;
	uint32_t ahb_addr = 0x200E1800;
	uint32_t txn_len;
	uint8_t cmnd = 0;
	int ret = 0;

	SLATECOM_INFO("slatecom AHB read to resume\n");
	tx_ahb_buf = kmalloc(TX_AHB_BUF_SIZE, GFP_KERNEL | GFP_ATOMIC);
	if (!tx_ahb_buf)
		return -ENOMEM;
	txn_len = 8;
	cmnd |= SLATE_SPI_AHB_READ_CMD;
	memcpy(tx_ahb_buf, &cmnd, sizeof(cmnd));
	memcpy(tx_ahb_buf+sizeof(cmnd), &ahb_addr, sizeof(ahb_addr));

	ret = slatecom_transfer(handle, tx_ahb_buf, rx_ahb_buf, txn_len, SPI_FREQ_1MHZ, false);
	if (ret)
		pr_err("slatecom_transfer fail with error %d\n", ret);
	kfree(tx_ahb_buf);
	return ret;
}

/* Returns 1, if the slate spi is active */
static int is_slate_resume(void *handle, uint32_t *slav_status_reg,
		uint32_t *slav_status_auto_clear_reg)
{
	uint32_t txn_len;
	int ret = 0;
	uint8_t *tx_buf = NULL;
	uint8_t *rx_buf = NULL;
	uint32_t read_buf[5] = {0};
	uint32_t size;
	uint32_t num_regs = 5;
	uint8_t reg_start_addr = SLATE_STATUS_REG;
	uint8_t cmnd = 0;
	ktime_t delta;
	s64 time_elapsed;

	SLATECOM_INFO("slatecom entry\n");
	if (spi_state == SLATECOM_SPI_BUSY) {
		printk_ratelimited("SPI is held by TZ\n");
		ret = -EBUSY;
		goto ret_err;
	}

	/* require a min time gap between OK_TO_SLEEP message and resume. */
	delta = ktime_sub(ktime_get(), sleep_time_start);
	time_elapsed = ktime_to_ms(delta);
	if (time_elapsed < MIN_SLEEP_TIME) {
		pr_err("avoid aggresive wakeup, sleep for %lu ms\n",
				MIN_SLEEP_TIME - time_elapsed);
		msleep(MIN_SLEEP_TIME - time_elapsed);
	}

	size = num_regs * SLATE_SPI_WORD_SIZE;
	txn_len = SLATE_SPI_READ_LEN + size;

	tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!tx_buf) {
		ret = -ENOMEM;
		goto ret_err;
	}

	rx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!rx_buf) {
		kfree(tx_buf);
		ret = -ENOMEM;
		goto ret_err;
	}

	cmnd |= reg_start_addr;
	memcpy(tx_buf, &cmnd, sizeof(cmnd));
	ret = slatecom_transfer(handle, tx_buf, rx_buf, txn_len, SPI_FREQ_40MHZ, false);

	if (!ret) {
		memcpy(read_buf, rx_buf+SLATE_SPI_READ_LEN, size);
		*slav_status_reg = read_buf[0];
		*slav_status_auto_clear_reg = read_buf[2];
		SLATECOM_INFO("slav_status_auto_clear_reg = 0x%X - OK_TO_SLEEP_CLEARED = 0x%08X\n",
				*slav_status_auto_clear_reg,
				(*slav_status_auto_clear_reg & OK_TO_SLEEP_CLEARED));
	}
	kfree(rx_buf);
	kfree(tx_buf);

 ret_err:
	return ((*slav_status_reg) & BIT(31));
}

void update_s2a_status(bool s2a_status_value)
{
	s2a_status = s2a_status_value;
}
EXPORT_SYMBOL_GPL(update_s2a_status);

static int slatecom_resume_l(void *handle)
{
	struct slate_context *cntx;
	uint32_t slav_status_reg = 0;
	uint32_t slav_status_auto_clear_reg = 0;
	int retry = 0;
	int ret = 0;
	uint32_t cmnd_reg = 0;

	if (handle == NULL) {
		SLATECOM_ERR("slatecom handle null\n");
		return -EINVAL;
	}

	if (!atomic_read(&slate_is_spi_active)) {
		SLATECOM_ERR("slatecom is inactive. Returning %d\n", -ECANCELED);
		return -ECANCELED;
	}
	cntx = (struct slate_context *)handle;

	/* if client is outside slatecom scope and
	 * handle is provided before SLATECOM probed
	 */
	if (cntx->state == SLATECOM_PROB_WAIT) {
		SLATECOM_INFO("handle is provided before SLATECOM probed\n");
		if (!is_slatecom_ready())
			return -EAGAIN;
		cntx->slate_spi = container_of(slate_com_drv,
						struct slate_spi_priv, lhandle);
		cntx->state = SLATECOM_PROB_SUCCESS;
	}

	mutex_lock(&slate_resume_mutex);
	if (atomic_read(&state) == SLATECOM_STATE_ACTIVE) {
		SLATECOM_ERR("Slatecom in active state\n");
		goto unlock;
	}

	if (!(g_slav_status_reg & BIT(31)) || !s2a_status) {
		SLATECOM_ERR("Slate boot is not complete, skip SPI resume\n");
		goto unlock;
	}

	do {
		reinit_completion(&slate_resume_wait);

		is_slate_resume(handle, &slav_status_reg, &slav_status_auto_clear_reg);
		SLATECOM_INFO("SLAVE_STATUS_READY = 0x%08X, OK_TO_SLEEP_CLEARED = 0x%08X\n",
			(slav_status_reg & SLAVE_STATUS_READY),
			(slav_status_auto_clear_reg & OK_TO_SLEEP_CLEARED));

		if (slav_status_reg & SLAVE_STATUS_READY) {
			if (slav_status_auto_clear_reg & OK_TO_SLEEP_CLEARED) {
				atomic_set(&ok_to_sleep, 0);
				goto complete;
			}
		}
		wakeup_ahb_read(handle);
		ret = wait_for_completion_timeout(&slate_resume_wait,
			msecs_to_jiffies(SLATE_RESUME_IRQ_TIMEOUT));

		SLATECOM_INFO("resume local ret = %d\n", ret);
		if (atomic_read(&ok_to_sleep) == 0)
			goto complete;

		retry++;
	} while (retry < MAX_RETRY);

	reinit_completion(&slate_resume_wait);
	cmnd_reg |= SLATE_HEALTH_CHECK;
	ret = slatecom_reg_write_cmd(cntx,
	SLATE_CMND_REG, 1, &cmnd_reg, false);
	if (ret < 0)
		SLATECOM_ERR("SLATE_HEALTH_CHECK write cmd failed\n");
	ret = wait_for_completion_timeout(&slate_resume_wait,
	msecs_to_jiffies(SLATE_RESUME_IRQ_TIMEOUT));
	SLATECOM_INFO("slate health check ret:%d\n", ret);

	if (atomic_read(&ok_to_sleep) == 0)
		goto complete;
	if (retry == MAX_RETRY) {
		mutex_unlock(&slate_resume_mutex);
		/* SLATE failed to resume. Trigger watchdog. */
		SLATECOM_ERR("SLATE failed to resume, gpio#95 value is: %d\n", gpio_get_value(95));
		BUG();
		return -ETIMEDOUT;
	}

complete:
	SLATECOM_INFO("slatecom resume completed\n");
	atomic_set(&state, SLATECOM_STATE_ACTIVE);
	atomic_set(&slate_is_spi_active, 1);

unlock:
	g_slave_status_auto_clear_reg = 0;
	mutex_unlock(&slate_resume_mutex);
	return 0;
}

static int slatecom_force_resume(void *handle)
{
	int ret =  0;

	mutex_lock(&slate_task_mutex);

	SLATECOM_INFO("Doing force resume\n");
	atomic_set(&slate_is_spi_active, 1);

	ret = slatecom_resume_l(handle);
	mutex_unlock(&slate_task_mutex);
	return 0;
}

int slatecom_ahb_read(void *handle, uint32_t ahb_start_addr,
	uint32_t num_words, void *read_buf)
{
	uint32_t txn_len;
	uint8_t *tx_buf;
	uint8_t *rx_buf;
	uint32_t size;
	int ret = 0;
	uint8_t cmnd = 0;
	uint32_t ahb_addr = 0;
	struct spi_device *spi = NULL;

	if (!handle || !read_buf || num_words == 0
		|| num_words > SLATE_SPI_MAX_WORDS) {
		SLATECOM_ERR("Invalid param\n");
		return -EINVAL;
	}
	if (!is_slatecom_ready())
		return -ENODEV;

	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("Device busy\n");
		return -EBUSY;
	}
	spi = get_spi_device();
	pm_runtime_get_sync(&spi->dev);
	mutex_lock(&slate_task_mutex);

	size = num_words*SLATE_SPI_WORD_SIZE;
	txn_len = SLATE_SPI_AHB_READ_CMD_LEN + size;

	tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!tx_buf) {
		ret = -ENOMEM;
		goto error_ret;
	}

	rx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!rx_buf) {
		kfree(tx_buf);
		ret = -ENOMEM;
		goto error_ret;
	}

	cmnd |= SLATE_SPI_AHB_READ_CMD;
	ahb_addr |= ahb_start_addr;

	memcpy(tx_buf, &cmnd, sizeof(cmnd));
	memcpy(tx_buf+sizeof(cmnd), &ahb_addr, sizeof(ahb_addr));

	ret = slatecom_transfer(handle, tx_buf, rx_buf, txn_len, SPI_FREQ_40MHZ, false);

	if (!ret)
		memcpy(read_buf, rx_buf+SLATE_SPI_AHB_READ_CMD_LEN, size);

	kfree(tx_buf);
	kfree(rx_buf);

error_ret:
	pm_runtime_mark_last_busy(&spi->dev);
	pm_runtime_put_sync_autosuspend(&spi->dev);
	mutex_unlock(&slate_task_mutex);
	return ret;
}
EXPORT_SYMBOL(slatecom_ahb_read);

int slatecom_ahb_write_bytes(void *handle, uint32_t ahb_start_addr,
	uint32_t num_bytes, void *write_buf)
{
	uint32_t txn_len;
	uint8_t *tx_buf;
	int ret = 0;
	uint8_t cmnd = 0;
	uint32_t ahb_addr = 0;
	uint32_t curr_num_bytes;
	struct spi_device *spi = NULL;

	if (!handle || !write_buf || num_bytes == 0
		|| num_bytes > (SLATE_SPI_MAX_WORDS * sizeof(int))) {
		SLATECOM_ERR("Invalid param\n");
		return -EINVAL;
	}

	if (!is_slatecom_ready())
		return -ENODEV;

	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("Device busy\n");
		return -EBUSY;
	}
	spi = get_spi_device();
	pm_runtime_get_sync(&spi->dev);
	mutex_lock(&slate_task_mutex);

	ahb_addr = ahb_start_addr;

	mutex_lock(&cma_buffer_lock);
	while (num_bytes) {
		curr_num_bytes = (num_bytes < WR_BUF_SIZE_IN_BYTES_FOR_USE) ?
						num_bytes : WR_BUF_SIZE_IN_BYTES_FOR_USE;

		txn_len = SLATE_SPI_AHB_CMD_LEN + curr_num_bytes;

		if ((txn_len % sizeof(uint32_t)) != 0) {
			txn_len +=
				(sizeof(uint32_t) - (txn_len % sizeof(uint32_t)));
		}
		memset(fxd_mem_buffer, 0, txn_len);
		tx_buf = fxd_mem_buffer;

		cmnd |= SLATE_SPI_AHB_WRITE_CMD;

		memcpy(tx_buf, &cmnd, sizeof(cmnd));
		memcpy(tx_buf+sizeof(cmnd), &ahb_addr, sizeof(ahb_addr));
		memcpy(tx_buf+SLATE_SPI_AHB_CMD_LEN, write_buf, curr_num_bytes);

		ret = slatecom_transfer(handle, tx_buf, NULL, txn_len, SPI_FREQ_40MHZ, false);
		if (ret) {
			SLATECOM_ERR("slatecom_transfer fail with error %d\n", ret);
			goto error;
		}
		write_buf += curr_num_bytes;
		ahb_addr += curr_num_bytes;
		num_bytes -= curr_num_bytes;
	}

error:
	mutex_unlock(&cma_buffer_lock);
	pm_runtime_mark_last_busy(&spi->dev);
	pm_runtime_put_sync_autosuspend(&spi->dev);
	mutex_unlock(&slate_task_mutex);
	return ret;
}
EXPORT_SYMBOL(slatecom_ahb_write_bytes);

int slatecom_ahb_write(void *handle, uint32_t ahb_start_addr,
	uint32_t num_words, void *write_buf)
{
	uint32_t txn_len;
	uint8_t *tx_buf;
	int ret = 0;
	uint8_t cmnd = 0;
	uint32_t ahb_addr = 0;
	uint32_t curr_num_words;
	uint32_t curr_num_bytes;
	struct spi_device *spi = NULL;

	if (!handle || !write_buf || num_words == 0
		|| num_words > SLATE_SPI_MAX_WORDS) {
		SLATECOM_ERR("Invalid param\n");
		return -EINVAL;
	}

	if (!is_slatecom_ready())
		return -ENODEV;

	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("Device busy\n");
		return -EBUSY;
	}
	spi = get_spi_device();
	pm_runtime_get_sync(&spi->dev);
	mutex_lock(&slate_task_mutex);

	ahb_addr = ahb_start_addr;

	mutex_lock(&cma_buffer_lock);
	while (num_words) {
		curr_num_words = (num_words < WR_BUF_SIZE_IN_WORDS_FOR_USE) ?
						num_words : WR_BUF_SIZE_IN_WORDS_FOR_USE;
		curr_num_bytes = curr_num_words * SLATE_SPI_WORD_SIZE;

		txn_len = SLATE_SPI_AHB_CMD_LEN + curr_num_bytes;
		memset(fxd_mem_buffer, 0, txn_len);
		tx_buf = fxd_mem_buffer;

		cmnd |= SLATE_SPI_AHB_WRITE_CMD;

		memcpy(tx_buf, &cmnd, sizeof(cmnd));
		memcpy(tx_buf+sizeof(cmnd), &ahb_addr, sizeof(ahb_addr));
		memcpy(tx_buf+SLATE_SPI_AHB_CMD_LEN, write_buf, curr_num_bytes);

		ret = slatecom_transfer(handle, tx_buf, NULL, txn_len, SPI_FREQ_40MHZ, false);
		if (ret) {
			SLATECOM_ERR("slatecom_transfer fail with error %d\n", ret);
			goto error;
		}
		write_buf += curr_num_bytes;
		ahb_addr += curr_num_bytes;
		num_words -= curr_num_words;
	}

error:
	mutex_unlock(&cma_buffer_lock);
	pm_runtime_mark_last_busy(&spi->dev);
	pm_runtime_put_sync_autosuspend(&spi->dev);
	mutex_unlock(&slate_task_mutex);
	return ret;
}
EXPORT_SYMBOL(slatecom_ahb_write);

int slatecom_fifo_write(void *handle, uint32_t num_words,
	void  *write_buf)
{
	uint32_t txn_len;
	uint8_t *tx_buf;
	uint32_t size;
	int ret = 0;
	uint8_t cmnd = 0;
	struct spi_device *spi = NULL;

	if (!is_slatecom_ready())
		return -ENODEV;

	if (!handle || !write_buf || num_words == 0
		|| num_words > SLATE_SPI_MAX_WORDS) {
		SLATECOM_ERR("Invalid param\n");
		return -EINVAL;
	}

	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("Device busy\n");
		return -EBUSY;
	}
	spi = get_spi_device();
	pm_runtime_get_sync(&spi->dev);
	mutex_lock(&slate_task_mutex);

	size = num_words*SLATE_SPI_WORD_SIZE;
	txn_len = SLATE_SPI_WRITE_CMND_LEN + size;

	tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!tx_buf) {
		ret = -ENOMEM;
		goto error_ret;
	}

	cmnd |= SLATE_SPI_FIFO_WRITE_CMD;
	memcpy(tx_buf, &cmnd, sizeof(cmnd));
	memcpy(tx_buf+sizeof(cmnd), write_buf, size);

	ret = slatecom_transfer(handle, tx_buf, NULL, txn_len, SPI_FREQ_40MHZ, false);
	kfree(tx_buf);

error_ret:
	pm_runtime_mark_last_busy(&spi->dev);
	pm_runtime_put_sync_autosuspend(&spi->dev);
	mutex_unlock(&slate_task_mutex);
	return ret;
}
EXPORT_SYMBOL(slatecom_fifo_write);

int slatecom_fifo_read(void *handle, uint32_t num_words,
	void *read_buf)
{
	uint32_t txn_len;
	uint8_t *tx_buf;
	uint8_t *rx_buf;
	uint32_t size;
	uint8_t cmnd = 0;
	int ret =  0;
	struct spi_device *spi = NULL;

	if (!handle || !read_buf || num_words == 0
		|| num_words > SLATE_SPI_MAX_WORDS) {
		SLATECOM_ERR("Invalid param\n");
		return -EINVAL;
	}

	if (!is_slatecom_ready())
		return -ENODEV;

	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("Device busy\n");
		return -EBUSY;
	}
	spi = get_spi_device();
	pm_runtime_get_sync(&spi->dev);
	mutex_lock(&slate_task_mutex);

	size = num_words*SLATE_SPI_WORD_SIZE;
	txn_len = SLATE_SPI_READ_LEN + size;
	tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!tx_buf) {
		ret = -ENOMEM;
		goto error_ret;
	}

	rx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!rx_buf) {
		kfree(tx_buf);
		ret = -ENOMEM;
		goto error_ret;
	}

	cmnd |= SLATE_SPI_FIFO_READ_CMD;
	memcpy(tx_buf, &cmnd, sizeof(cmnd));

	ret = slatecom_transfer(handle, tx_buf, rx_buf, txn_len, SPI_FREQ_40MHZ, false);

	if (!ret)
		memcpy(read_buf, rx_buf+SLATE_SPI_READ_LEN, size);
	kfree(tx_buf);
	kfree(rx_buf);

error_ret:
	pm_runtime_mark_last_busy(&spi->dev);
	pm_runtime_put_sync_autosuspend(&spi->dev);
	mutex_unlock(&slate_task_mutex);
	return ret;
}
EXPORT_SYMBOL(slatecom_fifo_read);

static int slatecom_reg_write_cmd(void *handle, uint8_t reg_start_addr,
	uint8_t num_regs, void *write_buf, bool flag)
{
	uint32_t txn_len;
	uint8_t *tx_buf;
	uint32_t size;
	uint8_t cmnd = 0;
	int ret =  0;

	if (!handle || !write_buf || num_regs == 0
		|| num_regs > SLATE_SPI_MAX_REGS) {
		SLATECOM_ERR("Invalid param\n");
		return -EINVAL;
	}

	if (!is_slatecom_ready())
		return -ENODEV;

	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("Device busy\n");
		return -EBUSY;
	}
	if (spi_state == SLATECOM_SPI_PAUSE) {
		SLATECOM_ERR("Device in Pause State\n");
		return -EBUSY;
	}

	size = num_regs*SLATE_SPI_WORD_SIZE;
	txn_len = SLATE_SPI_WRITE_CMND_LEN + size;

	tx_buf = kzalloc(txn_len, GFP_KERNEL);

	if (!tx_buf)
		return -ENOMEM;

	cmnd |= reg_start_addr;
	memcpy(tx_buf, &cmnd, sizeof(cmnd));
	memcpy(tx_buf+sizeof(cmnd), write_buf, size);

	ret = slatecom_transfer(handle, tx_buf, NULL, txn_len, SPI_FREQ_40MHZ, flag);
	kfree(tx_buf);
	return ret;
}

int slatecom_reg_write(void *handle, uint8_t reg_start_addr,
	uint8_t num_regs, void *write_buf)
{
	int ret =  0;
	struct spi_device *spi = NULL;

	spi = get_spi_device();
	pm_runtime_get_sync(&spi->dev);
	mutex_lock(&slate_task_mutex);

	ret = slatecom_reg_write_cmd(handle, reg_start_addr,
					num_regs, write_buf, false);

	pm_runtime_mark_last_busy(&spi->dev);
	pm_runtime_put_sync_autosuspend(&spi->dev);
	mutex_unlock(&slate_task_mutex);
	return ret;
}
EXPORT_SYMBOL(slatecom_reg_write);

static int slatecom_reg_read_internal(void *handle, uint8_t reg_start_addr,
	uint32_t num_regs, void *read_buf)
{
	uint32_t txn_len;
	uint8_t *tx_buf;
	uint8_t *rx_buf;
	uint32_t size;
	int ret = 0;
	uint8_t cmnd = 0;

	if (!handle || !read_buf || num_regs == 0
		|| num_regs > SLATE_SPI_MAX_REGS) {
		SLATECOM_ERR("Invalid param\n");
		return -EINVAL;
	}

	if (!is_slatecom_ready())
		return -ENODEV;

	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("Device busy\n");
		return -EBUSY;
	}

	size = num_regs*SLATE_SPI_WORD_SIZE;
	txn_len = SLATE_SPI_READ_LEN + size;

	tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!tx_buf) {
		ret = -ENOMEM;
		goto error_ret;
	}

	rx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!rx_buf) {
		kfree(tx_buf);
		ret = -ENOMEM;
		goto error_ret;
	}

	cmnd |= reg_start_addr;
	memcpy(tx_buf, &cmnd, sizeof(cmnd));

	ret = slatecom_transfer(handle, tx_buf, rx_buf, txn_len, SPI_FREQ_40MHZ, false);

	if (!ret)
		memcpy(read_buf, rx_buf+SLATE_SPI_READ_LEN, size);
	kfree(tx_buf);
	kfree(rx_buf);

error_ret:
	return ret;
}

int slatecom_reg_read(void *handle, uint8_t reg_start_addr,
	uint32_t num_regs, void *read_buf)
{
	uint32_t txn_len;
	uint8_t *tx_buf;
	uint8_t *rx_buf;
	uint32_t size;
	int ret = 0;
	uint8_t cmnd = 0;
	struct spi_device *spi = NULL;

	if (!handle || !read_buf || num_regs == 0
		|| num_regs > SLATE_SPI_MAX_REGS) {
		SLATECOM_ERR("Invalid param\n");
		return -EINVAL;
	}

	if (!is_slatecom_ready())
		return -ENODEV;

	if (spi_state == SLATECOM_SPI_BUSY) {
		SLATECOM_ERR("Device busy\n");
		return -EBUSY;
	}
	spi = get_spi_device();
	pm_runtime_get_sync(&spi->dev);
	mutex_lock(&slate_task_mutex);

	size = num_regs*SLATE_SPI_WORD_SIZE;
	txn_len = SLATE_SPI_READ_LEN + size;

	tx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!tx_buf) {
		ret = -ENOMEM;
		goto error_ret;
	}

	rx_buf = kzalloc(txn_len, GFP_KERNEL | GFP_ATOMIC);
	if (!rx_buf) {
		kfree(tx_buf);
		ret = -ENOMEM;
		goto error_ret;
	}

	cmnd |= reg_start_addr;
	memcpy(tx_buf, &cmnd, sizeof(cmnd));

	ret = slatecom_transfer(handle, tx_buf, rx_buf, txn_len, SPI_FREQ_40MHZ, false);

	if (!ret)
		memcpy(read_buf, rx_buf+SLATE_SPI_READ_LEN, size);
	kfree(tx_buf);
	kfree(rx_buf);

error_ret:
	pm_runtime_mark_last_busy(&spi->dev);
	pm_runtime_put_sync_autosuspend(&spi->dev);
	mutex_unlock(&slate_task_mutex);
	return ret;
}
EXPORT_SYMBOL(slatecom_reg_read);

int slatecom_resume(void *handle)
{
	return 0;
}
EXPORT_SYMBOL(slatecom_resume);

int slatecom_suspend(void *handle)
{
	if (!handle)
		return -EINVAL;
	return 0;
}
EXPORT_SYMBOL(slatecom_suspend);

void *slatecom_open(struct slatecom_open_config_type *open_config)
{
	struct slate_spi_priv *spi;
	struct cb_data *irq_notification;
	struct slate_context  *clnt_handle =
			kzalloc(sizeof(*clnt_handle), GFP_KERNEL);

	if (!clnt_handle)
		return NULL;

	/* Client handle Set-up */
	if (!is_slatecom_ready()) {
		clnt_handle->slate_spi = NULL;
		clnt_handle->state = SLATECOM_PROB_WAIT;
	} else {
		spi = container_of(slate_com_drv, struct slate_spi_priv, lhandle);
		clnt_handle->slate_spi = spi;
		clnt_handle->state = SLATECOM_PROB_SUCCESS;
	}
	clnt_handle->cb = NULL;
	/* Interrupt callback Set-up */
	if (open_config && open_config->slatecom_notification_cb) {
		irq_notification = kzalloc(sizeof(*irq_notification),
			GFP_KERNEL);
		if (!irq_notification)
			goto error_ret;

		/* set irq node */
		irq_notification->handle = clnt_handle;
		irq_notification->priv = open_config->priv;
		irq_notification->slatecom_notification_cb =
					open_config->slatecom_notification_cb;
		add_to_irq_list(irq_notification);
		clnt_handle->cb = irq_notification;
	}
	return clnt_handle;

error_ret:
	kfree(clnt_handle);
	return NULL;
}
EXPORT_SYMBOL(slatecom_open);

int slatecom_close(void **handle)
{
	struct slate_context *lhandle;
	struct cb_data *cb = NULL;

	if (*handle == NULL)
		return -EINVAL;
	lhandle = *handle;
	cb = lhandle->cb;
	if (cb)
		list_del(&cb->list);

	kfree(*handle);
	*handle = NULL;
	return 0;
}
EXPORT_SYMBOL(slatecom_close);

static irqreturn_t slate_irq_tasklet_hndlr(int irq, void *device)
{
	struct slate_spi_priv *slate_spi = device;
	uint32_t slav_status_reg = 0;
	uint32_t slav_status_auto_clear_reg = 0;
	struct slate_context clnt_handle;
	uint32_t cmnd_reg = 0;
	int ret = 0;

	clnt_handle.slate_spi = slate_spi;

	/* set active to allow spi transfer */
	atomic_set(&slate_is_spi_active, 1);

	if (atomic_read(&ok_to_sleep) == 1)
		SLATECOM_INFO("Interrupt received in XO/Deepsleep\n");

	/* check if call-back exists */
	if (atomic_read(&slate_is_runtime_suspend)) {
		SLATECOM_INFO("Interrupt received in suspend state\n");
		atomic_set(&slate_spi->irq_lock, 1);

		is_slate_resume(&clnt_handle, &slav_status_reg, &slav_status_auto_clear_reg);

		if (slav_status_reg & SLAVE_STATUS_READY) {
			if (slav_status_auto_clear_reg & OK_TO_SLEEP_CLEARED) {
				SLATECOM_INFO("Marked Complete\n");
				atomic_set(&ok_to_sleep, 0);
				complete(&slate_resume_wait);
				atomic_set(&slate_is_spi_active, 1);
				atomic_set(&slate_is_runtime_suspend, 0);
			}

		}
		if (slav_status_auto_clear_reg & SLATE_PAUSE_REQ) {
			cmnd_reg |= SLATE_PAUSE_OK;
			ret = slatecom_reg_write_cmd(&clnt_handle,
					SLATE_CMND_REG, 1, &cmnd_reg, false);
			if (ret == 0) {
				spi_state = SLATECOM_SPI_PAUSE;
				SLATECOM_INFO("SPI is in Pause State\n");
			}
		}
		if (slav_status_auto_clear_reg & SLATE_RESUME_IND) {
			spi_state = SLATECOM_SPI_FREE;
			SLATECOM_INFO("Apps to resume operation\n");
		}
		atomic_set(&slate_spi->irq_lock, 0);
	} else if (list_empty(&cb_head)) {
		SLATECOM_INFO("No callback registered\n");
	} else if (spi_state == SLATECOM_SPI_BUSY) {
		/* delay for SPI to be freed */
		msleep(50);
	} else if (atomic_read(&slate_spi->irq_lock) == 0) {
		atomic_set(&slate_spi->irq_lock, 1);
		slate_irq_tasklet_hndlr_l();
		atomic_set(&slate_spi->irq_lock, 0);
	}
	return IRQ_HANDLED;
}

static void slate_spi_init(struct slate_spi_priv *slate_spi)
{
	if (!slate_spi) {
		pr_err("device not found\n");
		return;
	}

	/* SLATECOM SPI set-up */
	mutex_init(&slate_spi->xfer_mutex);
	spi_message_init(&slate_spi->msg1);
	spi_message_add_tail(&slate_spi->xfer1, &slate_spi->msg1);

	/* SLATECOM IRQ set-up */
	atomic_set(&slate_spi->irq_lock, 0);

	spi_state = SLATECOM_SPI_FREE;

	wq = create_singlethread_workqueue("input_wq");

	atomic_set(&state, SLATECOM_STATE_ACTIVE);

	slate_com_drv = &slate_spi->lhandle;

	mutex_init(&slate_resume_mutex);
	mutex_init(&slate_task_mutex);

	fxd_mem_buffer = kmalloc(CMA_BFFR_POOL_SIZE, GFP_KERNEL | GFP_ATOMIC);

	mutex_init(&cma_buffer_lock);

	slatecom_ipc_log = ipc_log_context_create(LOG_PAGES_CNT, "slatecom_spi", 0);

}

static int slatecom_pm_notifier(struct notifier_block *nb, unsigned long event, void *unused)
{
	switch (event) {
#ifdef CONFIG_HIBERNATION

	case PM_HIBERNATION_PREPARE:
		pr_err("Hibernate entry\n");
		is_hibernate = true;
		break;

	case PM_POST_HIBERNATION:
		pr_err("Hibernate exit\n");
		is_hibernate = false;
		break;
#endif

	default:
		WARN_ONCE(1, "Default case: PM Notifier\n");
		break;
	}
	return NOTIFY_DONE;
}

static struct notifier_block slatecom_pm_nb = {
		.notifier_call = slatecom_pm_notifier,
};

static int slate_spi_probe(struct spi_device *spi)
{
	struct slate_spi_priv *slate_spi;
	int ret = 0;
	struct device_node *node;
	int irq_gpio = 0;

	slate_spi = devm_kzalloc(&spi->dev, sizeof(*slate_spi),
				   GFP_KERNEL | GFP_ATOMIC);

	pr_info("%s started\n", __func__);

	if (!slate_spi)
		return -ENOMEM;
	slate_spi->spi = spi;
	spi_set_drvdata(spi, slate_spi);
	slate_spi_init(slate_spi);

	/* SLATECOM Interrupt probe */
	node = slate_spi->spi->dev.of_node;
	irq_gpio = of_get_named_gpio(node, "qcom,irq-gpio", 0);
	if (!gpio_is_valid(irq_gpio)) {
		pr_err("gpio %d found is not valid\n", irq_gpio);
		goto err_ret;
	}
	ret = gpio_request(irq_gpio, "slatecom_gpio");
	if (ret) {
		pr_err("gpio %d request failed\n", irq_gpio);
		goto err_ret;
	}
	ret = gpio_direction_input(irq_gpio);
	if (ret) {
		pr_err("gpio_direction_input not set: %d\n", ret);
		goto err_ret;
	}
	slate_irq = gpio_to_irq(irq_gpio);

	atomic_set(&slate_is_spi_active, 1);
	dma_set_coherent_mask(&spi->dev, 0);

	/* Enable Runtime PM for this device */
	pm_runtime_enable(&spi->dev);
	pm_runtime_set_autosuspend_delay(&spi->dev, SLATE_SPI_AUTOSUSPEND_TIMEOUT);
	pm_runtime_use_autosuspend(&spi->dev);

	ret = register_pm_notifier(&slatecom_pm_nb);
	if (ret) {
		pr_err("slatecom notif error %d\n", ret);
		goto err_ret;
	}

	pr_info("%s success\n", __func__);
	pr_info("Slatecom Probed successfully\n");
	return ret;

err_ret:
	slate_com_drv = NULL;
	mutex_destroy(&slate_spi->xfer_mutex);
	spi_set_drvdata(spi, NULL);
	if (gpio_is_valid(irq_gpio))
		gpio_free(irq_gpio);
	return -ENODEV;
}

static int slate_spi_remove(struct spi_device *spi)
{
	struct slate_spi_priv *slate_spi = spi_get_drvdata(spi);

	slate_com_drv = NULL;
	pm_runtime_disable(&spi->dev);
	mutex_destroy(&slate_spi->xfer_mutex);
	spi_set_drvdata(spi, NULL);
	kfree(fxd_mem_buffer);
	mutex_destroy(&cma_buffer_lock);
	mutex_destroy(&slate_task_mutex);
	unregister_pm_notifier(&slatecom_pm_nb);
	return 0;
}

static void slate_spi_shutdown(struct spi_device *spi)
{
	disable_irq(slate_irq);
	slate_spi_remove(spi);
}

static int slatecom_pm_prepare(struct device *dev)
{
	struct slate_context clnt_handle;
	uint32_t cmnd_reg = 0;
	struct spi_device *s_dev = to_spi_device(dev);
	struct slate_spi_priv *slate_spi = spi_get_drvdata(s_dev);
	int ret = 0;

	g_slave_status_auto_clear_reg = 0;
	clnt_handle.slate_spi = slate_spi;

	if (!(g_slav_status_reg & BIT(31))) {
		SLATECOM_ERR("Slate boot is not complete, skip SPI suspend\n");
		return 0;
	}

	if (is_hibernate)
		cmnd_reg |= SLATE_OK_SLP_S2D;

	else if (pm_suspend_via_firmware())
		cmnd_reg |= SLATE_OK_SLP_S2R;
	else
		cmnd_reg |= SLATE_OK_SLP_RBSC;

	if (!atomic_read(&slate_is_spi_active)) {
		SLATECOM_INFO("spi is already inactive, get_sync.\n");
		pm_runtime_get_sync(&s_dev->dev);
		usleep_range(5000, 10000);
	} else
		SLATECOM_INFO("spi is already active, skip get_sync.\n");
	atomic_set(&ok_to_sleep, 1);
	ret = slatecom_reg_write_cmd(&clnt_handle, SLATE_CMND_REG, 1, &cmnd_reg, true);
	if (ret < 0) {
		atomic_set(&ok_to_sleep, 0);
		atomic_set(&slate_is_spi_active, 1);
		return ret;
	}

	(!atomic_read(&slate_is_spi_active)) ? pm_runtime_put_sync(&s_dev->dev)
			: SLATECOM_INFO("spi is already active, skip put_sync...\n");

	sleep_time_start = ktime_get();
	atomic_set(&slate_is_spi_active, 0);
	atomic_set(&state, SLATECOM_STATE_SUSPEND_PREPARE);
	atomic_set(&slate_is_runtime_suspend, 0);

	SLATECOM_INFO("reg write status: %d\n", ret);
	return ret;
}

static irqreturn_t slate_irq_tasklet_hndlr_during_suspend(int irq, void *device)
{
	return IRQ_HANDLED;
}

static int slatecom_pm_suspend(struct device *dev)
{
	struct spi_device *s_dev = to_spi_device(dev);
	struct slate_spi_priv *slate_spi = spi_get_drvdata(s_dev);
	int ret = 0;

	SLATECOM_ERR("entry\n");

	if (!(g_slav_status_reg & BIT(31))) {
		SLATECOM_ERR("Slate boot is not complete, skip SPI suspend\n");
		return 0;
	}

	if ((g_slave_status_auto_clear_reg & OK_TO_SLEEP_CLEARED) &&
			(g_slav_status_reg & SLAVE_STATUS_READY)) {
		SLATECOM_ERR("Slate is in active, Abort Suspend\n");
		atomic_set(&slate_is_runtime_suspend, 0);
		atomic_set(&state, SLATECOM_STATE_ACTIVE);
		atomic_set(&slate_is_spi_active, 1);
		g_slave_status_auto_clear_reg = 0;
		return -ECANCELED;
	}

	atomic_set(&slate_is_runtime_suspend, 0);

	free_irq(slate_irq, slate_spi);
	ret = request_threaded_irq(slate_irq, NULL, slate_irq_tasklet_hndlr_during_suspend,
		IRQF_TRIGGER_RISING | IRQF_ONESHOT, "qcom-slate_spi", slate_spi);

	if (atomic_read(&slate_is_spi_active)) {
		SLATECOM_ERR("Slate interrupted, abort suspend\n");
		return -ECANCELED;
	}

	SLATECOM_INFO("suspended\n");

	atomic_set(&state, SLATECOM_STATE_SUSPEND);
	return 0;
}

static int slatecom_pm_resume(struct device *dev)
{
	struct slate_context clnt_handle;
	int ret = 0;
	struct slate_spi_priv *spi = NULL;

	if (!slate_com_drv)
		return -ENODEV;
	spi = container_of(slate_com_drv, struct slate_spi_priv, lhandle);
	SLATECOM_ERR("entry\n");
	free_irq(slate_irq, spi);
	ret = request_threaded_irq(slate_irq, NULL, slate_irq_tasklet_hndlr,
		IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "qcom-slate_spi", spi);

	if (atomic_read(&spi->irq_lock) == 1) {
		atomic_set(&slate_is_spi_active, 1);
		atomic_set(&slate_is_runtime_suspend, 0);
		atomic_set(&state, SLATECOM_STATE_ACTIVE);
		pr_debug("Shouldn't Execute\n");
		return 0;
	}

	if (atomic_read(&slate_is_spi_active)) {
		SLATECOM_INFO("Slatecom in resume state\n");
		return 0;
	} else {
		if (!(g_slav_status_reg & BIT(31))) {
			SLATECOM_ERR("Slate boot is not complete, skip SPI resume\n");
			return 0;
		}
		mutex_lock(&slate_task_mutex);
		clnt_handle.slate_spi = spi;
		atomic_set(&slate_is_spi_active, 1);
		atomic_set(&slate_is_runtime_suspend, 0);
		ret = slatecom_resume_l(&clnt_handle);
		SLATECOM_ERR("Slatecom resumed with : %d\n", ret);
		mutex_unlock(&slate_task_mutex);

		return ret;
	}
}

static void slatecom_pm_complete(struct device *dev)
{
	/* resume if state is still SLATECOM_STATE_SUSPEND_PREPARE*/
	SLATECOM_ERR("entry\n");
	if (atomic_read(&state) == SLATECOM_STATE_SUSPEND_PREPARE)
		slatecom_pm_resume(dev);
}

static int slatecom_pm_runtime_suspend(struct device *dev)
{
	struct slate_context clnt_handle;
	uint32_t cmnd_reg = 0;
	struct spi_device *s_dev = to_spi_device(dev);
	struct slate_spi_priv *slate_spi = spi_get_drvdata(s_dev);
	int ret = 0;

	SLATECOM_INFO("entry\n");
	clnt_handle.slate_spi = slate_spi;

	if (atomic_read(&state) == SLATECOM_STATE_RUNTIME_SUSPEND)
		return 0;

	if (!(g_slav_status_reg & BIT(31))) {
		SLATECOM_ERR("Slate boot is not complete, skip SPI suspend\n");
		return 0;
	}

	cmnd_reg |= SLATE_OK_SLP_SIF;
	atomic_set(&ok_to_sleep, 1);
	ret = slatecom_reg_write_cmd(&clnt_handle,
			SLATE_CMND_REG, 1, &cmnd_reg, false);
	sleep_time_start = ktime_get();
	if (ret == 0) {
		atomic_set(&state, SLATECOM_STATE_RUNTIME_SUSPEND);
		atomic_set(&slate_is_spi_active, 0);
		atomic_set(&slate_is_runtime_suspend, 1);
	} else
		atomic_set(&ok_to_sleep, 0);

	SLATECOM_INFO("Runtime suspended with : %d\n", ret);
	return ret;
}

static int slatecom_pm_runtime_resume(struct device *dev)
{
	struct slate_context clnt_handle;
	int ret = 0;
	struct slate_spi_priv *spi = NULL;

	if (!slate_com_drv)
		return -ENODEV;
	spi = container_of(slate_com_drv, struct slate_spi_priv, lhandle);
	SLATECOM_INFO("entry\n");
	clnt_handle.slate_spi = spi;

	if (atomic_read(&spi->irq_lock) == 1) {
		atomic_set(&slate_is_spi_active, 1);
		atomic_set(&slate_is_runtime_suspend, 0);
		atomic_set(&state, SLATECOM_STATE_ACTIVE);
		pr_debug("Slate Already Woken up! Skip.....\n");
		return 0;
	}
	mutex_lock(&slate_task_mutex);
	atomic_set(&slate_is_spi_active, 1);
	atomic_set(&slate_is_runtime_suspend, 0);
	ret = slatecom_resume_l(&clnt_handle);
	SLATECOM_INFO("Slatecom Runtime resumed with : %d\n", ret);
	mutex_unlock(&slate_task_mutex);
	return ret;
}

static int slatecom_pm_freeze(struct device *dev)
{
	struct slate_context clnt_handle;
	struct spi_device *s_dev = to_spi_device(dev);
	struct slate_spi_priv *slate_spi = spi_get_drvdata(s_dev);

	clnt_handle.slate_spi = slate_spi;
	if (atomic_read(&state) == SLATECOM_STATE_HIBERNATE)
		return 0;

	if (!(g_slav_status_reg & BIT(31))) {
		SLATECOM_ERR("Slate boot is not complete, skip SPI suspend\n");
		return 0;
	}

	atomic_set(&slate_is_spi_active, 0);
	atomic_set(&state, SLATECOM_STATE_HIBERNATE);
	atomic_set(&slate_is_runtime_suspend, 0);

	SLATECOM_INFO("Slatecom freezed\n");
	return 0;
}

static int slatecom_pm_restore(struct device *dev)
{
	struct slate_context clnt_handle;
	int ret = 0;
	struct slate_spi_priv *spi = NULL;

	if (!slate_com_drv)
		return -ENODEV;
	spi = container_of(slate_com_drv, struct slate_spi_priv, lhandle);

	if (atomic_read(&slate_is_spi_active)) {
		SLATECOM_INFO("Slatecom in restore state\n");
	} else {
		if (!(g_slav_status_reg & BIT(31))) {
			SLATECOM_ERR("Slate boot is not complete, skip SPI resume\n");
			return 0;
		}
		clnt_handle.slate_spi = spi;
		atomic_set(&slate_is_spi_active, 1);
		atomic_set(&slate_is_runtime_suspend, 0);
		is_hibernate = false;
		ret = slatecom_resume_l(&clnt_handle);
		SLATECOM_INFO("Slatecom restore with : %d\n", ret);
	}
	return ret;
}

static const struct dev_pm_ops slatecom_pm = {
	.prepare = slatecom_pm_prepare,
	.complete = slatecom_pm_complete,
	.runtime_suspend = slatecom_pm_runtime_suspend,
	.runtime_resume = slatecom_pm_runtime_resume,
	.suspend = slatecom_pm_suspend,
	.resume = slatecom_pm_resume,
	.freeze = slatecom_pm_freeze,
	.restore = slatecom_pm_restore,
};

static const struct of_device_id slate_spi_of_match[] = {
	{ .compatible = "qcom,slate-spi", },
	{ }
};
MODULE_DEVICE_TABLE(of, slate_spi_of_match);

static struct spi_driver slate_spi_driver = {
	.driver = {
		.name = "slate-spi",
		.of_match_table = slate_spi_of_match,
		.pm = &slatecom_pm,
	},
	.probe = slate_spi_probe,
	.remove = slate_spi_remove,
	.shutdown = slate_spi_shutdown,
};

module_spi_driver(slate_spi_driver);
MODULE_DESCRIPTION("slate SPI driver");
MODULE_LICENSE("GPL v2");
