18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * AMx3 Wkup M3 IPC driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2015 Texas Instruments, Inc.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Dave Gerlach <d-gerlach@ti.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/err.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/kthread.h>
138c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
148c2ecf20Sopenharmony_ci#include <linux/irq.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/of.h>
178c2ecf20Sopenharmony_ci#include <linux/omap-mailbox.h>
188c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
198c2ecf20Sopenharmony_ci#include <linux/remoteproc.h>
208c2ecf20Sopenharmony_ci#include <linux/suspend.h>
218c2ecf20Sopenharmony_ci#include <linux/wkup_m3_ipc.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define AM33XX_CTRL_IPC_REG_COUNT	0x8
248c2ecf20Sopenharmony_ci#define AM33XX_CTRL_IPC_REG_OFFSET(m)	(0x4 + 4 * (m))
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/* AM33XX M3_TXEV_EOI register */
278c2ecf20Sopenharmony_ci#define AM33XX_CONTROL_M3_TXEV_EOI	0x00
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define AM33XX_M3_TXEV_ACK		(0x1 << 0)
308c2ecf20Sopenharmony_ci#define AM33XX_M3_TXEV_ENABLE		(0x0 << 0)
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#define IPC_CMD_DS0			0x4
338c2ecf20Sopenharmony_ci#define IPC_CMD_STANDBY			0xc
348c2ecf20Sopenharmony_ci#define IPC_CMD_IDLE			0x10
358c2ecf20Sopenharmony_ci#define IPC_CMD_RESET			0xe
368c2ecf20Sopenharmony_ci#define DS_IPC_DEFAULT			0xffffffff
378c2ecf20Sopenharmony_ci#define M3_VERSION_UNKNOWN		0x0000ffff
388c2ecf20Sopenharmony_ci#define M3_BASELINE_VERSION		0x191
398c2ecf20Sopenharmony_ci#define M3_STATUS_RESP_MASK		(0xffff << 16)
408c2ecf20Sopenharmony_ci#define M3_FW_VERSION_MASK		0xffff
418c2ecf20Sopenharmony_ci#define M3_WAKE_SRC_MASK		0xff
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci#define M3_STATE_UNKNOWN		0
448c2ecf20Sopenharmony_ci#define M3_STATE_RESET			1
458c2ecf20Sopenharmony_ci#define M3_STATE_INITED			2
468c2ecf20Sopenharmony_ci#define M3_STATE_MSG_FOR_LP		3
478c2ecf20Sopenharmony_ci#define M3_STATE_MSG_FOR_RESET		4
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic struct wkup_m3_ipc *m3_ipc_state;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic const struct wkup_m3_wakeup_src wakeups[] = {
528c2ecf20Sopenharmony_ci	{.irq_nr = 16,	.src = "PRCM"},
538c2ecf20Sopenharmony_ci	{.irq_nr = 35,	.src = "USB0_PHY"},
548c2ecf20Sopenharmony_ci	{.irq_nr = 36,	.src = "USB1_PHY"},
558c2ecf20Sopenharmony_ci	{.irq_nr = 40,	.src = "I2C0"},
568c2ecf20Sopenharmony_ci	{.irq_nr = 41,	.src = "RTC Timer"},
578c2ecf20Sopenharmony_ci	{.irq_nr = 42,	.src = "RTC Alarm"},
588c2ecf20Sopenharmony_ci	{.irq_nr = 43,	.src = "Timer0"},
598c2ecf20Sopenharmony_ci	{.irq_nr = 44,	.src = "Timer1"},
608c2ecf20Sopenharmony_ci	{.irq_nr = 45,	.src = "UART"},
618c2ecf20Sopenharmony_ci	{.irq_nr = 46,	.src = "GPIO0"},
628c2ecf20Sopenharmony_ci	{.irq_nr = 48,	.src = "MPU_WAKE"},
638c2ecf20Sopenharmony_ci	{.irq_nr = 49,	.src = "WDT0"},
648c2ecf20Sopenharmony_ci	{.irq_nr = 50,	.src = "WDT1"},
658c2ecf20Sopenharmony_ci	{.irq_nr = 51,	.src = "ADC_TSC"},
668c2ecf20Sopenharmony_ci	{.irq_nr = 0,	.src = "Unknown"},
678c2ecf20Sopenharmony_ci};
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic void am33xx_txev_eoi(struct wkup_m3_ipc *m3_ipc)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	writel(AM33XX_M3_TXEV_ACK,
728c2ecf20Sopenharmony_ci	       m3_ipc->ipc_mem_base + AM33XX_CONTROL_M3_TXEV_EOI);
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic void am33xx_txev_enable(struct wkup_m3_ipc *m3_ipc)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	writel(AM33XX_M3_TXEV_ENABLE,
788c2ecf20Sopenharmony_ci	       m3_ipc->ipc_mem_base + AM33XX_CONTROL_M3_TXEV_EOI);
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic void wkup_m3_ctrl_ipc_write(struct wkup_m3_ipc *m3_ipc,
828c2ecf20Sopenharmony_ci				   u32 val, int ipc_reg_num)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	if (WARN(ipc_reg_num < 0 || ipc_reg_num > AM33XX_CTRL_IPC_REG_COUNT,
858c2ecf20Sopenharmony_ci		 "ipc register operation out of range"))
868c2ecf20Sopenharmony_ci		return;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	writel(val, m3_ipc->ipc_mem_base +
898c2ecf20Sopenharmony_ci	       AM33XX_CTRL_IPC_REG_OFFSET(ipc_reg_num));
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic unsigned int wkup_m3_ctrl_ipc_read(struct wkup_m3_ipc *m3_ipc,
938c2ecf20Sopenharmony_ci					  int ipc_reg_num)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	if (WARN(ipc_reg_num < 0 || ipc_reg_num > AM33XX_CTRL_IPC_REG_COUNT,
968c2ecf20Sopenharmony_ci		 "ipc register operation out of range"))
978c2ecf20Sopenharmony_ci		return 0;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	return readl(m3_ipc->ipc_mem_base +
1008c2ecf20Sopenharmony_ci		     AM33XX_CTRL_IPC_REG_OFFSET(ipc_reg_num));
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic int wkup_m3_fw_version_read(struct wkup_m3_ipc *m3_ipc)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	int val;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	val = wkup_m3_ctrl_ipc_read(m3_ipc, 2);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return val & M3_FW_VERSION_MASK;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic irqreturn_t wkup_m3_txev_handler(int irq, void *ipc_data)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct wkup_m3_ipc *m3_ipc = ipc_data;
1158c2ecf20Sopenharmony_ci	struct device *dev = m3_ipc->dev;
1168c2ecf20Sopenharmony_ci	int ver = 0;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	am33xx_txev_eoi(m3_ipc);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	switch (m3_ipc->state) {
1218c2ecf20Sopenharmony_ci	case M3_STATE_RESET:
1228c2ecf20Sopenharmony_ci		ver = wkup_m3_fw_version_read(m3_ipc);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci		if (ver == M3_VERSION_UNKNOWN ||
1258c2ecf20Sopenharmony_ci		    ver < M3_BASELINE_VERSION) {
1268c2ecf20Sopenharmony_ci			dev_warn(dev, "CM3 Firmware Version %x not supported\n",
1278c2ecf20Sopenharmony_ci				 ver);
1288c2ecf20Sopenharmony_ci		} else {
1298c2ecf20Sopenharmony_ci			dev_info(dev, "CM3 Firmware Version = 0x%x\n", ver);
1308c2ecf20Sopenharmony_ci		}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci		m3_ipc->state = M3_STATE_INITED;
1338c2ecf20Sopenharmony_ci		complete(&m3_ipc->sync_complete);
1348c2ecf20Sopenharmony_ci		break;
1358c2ecf20Sopenharmony_ci	case M3_STATE_MSG_FOR_RESET:
1368c2ecf20Sopenharmony_ci		m3_ipc->state = M3_STATE_INITED;
1378c2ecf20Sopenharmony_ci		complete(&m3_ipc->sync_complete);
1388c2ecf20Sopenharmony_ci		break;
1398c2ecf20Sopenharmony_ci	case M3_STATE_MSG_FOR_LP:
1408c2ecf20Sopenharmony_ci		complete(&m3_ipc->sync_complete);
1418c2ecf20Sopenharmony_ci		break;
1428c2ecf20Sopenharmony_ci	case M3_STATE_UNKNOWN:
1438c2ecf20Sopenharmony_ci		dev_warn(dev, "Unknown CM3 State\n");
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	am33xx_txev_enable(m3_ipc);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic int wkup_m3_ping(struct wkup_m3_ipc *m3_ipc)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	struct device *dev = m3_ipc->dev;
1548c2ecf20Sopenharmony_ci	mbox_msg_t dummy_msg = 0;
1558c2ecf20Sopenharmony_ci	int ret;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	if (!m3_ipc->mbox) {
1588c2ecf20Sopenharmony_ci		dev_err(dev,
1598c2ecf20Sopenharmony_ci			"No IPC channel to communicate with wkup_m3!\n");
1608c2ecf20Sopenharmony_ci		return -EIO;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	/*
1648c2ecf20Sopenharmony_ci	 * Write a dummy message to the mailbox in order to trigger the RX
1658c2ecf20Sopenharmony_ci	 * interrupt to alert the M3 that data is available in the IPC
1668c2ecf20Sopenharmony_ci	 * registers. We must enable the IRQ here and disable it after in
1678c2ecf20Sopenharmony_ci	 * the RX callback to avoid multiple interrupts being received
1688c2ecf20Sopenharmony_ci	 * by the CM3.
1698c2ecf20Sopenharmony_ci	 */
1708c2ecf20Sopenharmony_ci	ret = mbox_send_message(m3_ipc->mbox, &dummy_msg);
1718c2ecf20Sopenharmony_ci	if (ret < 0) {
1728c2ecf20Sopenharmony_ci		dev_err(dev, "%s: mbox_send_message() failed: %d\n",
1738c2ecf20Sopenharmony_ci			__func__, ret);
1748c2ecf20Sopenharmony_ci		return ret;
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	ret = wait_for_completion_timeout(&m3_ipc->sync_complete,
1788c2ecf20Sopenharmony_ci					  msecs_to_jiffies(500));
1798c2ecf20Sopenharmony_ci	if (!ret) {
1808c2ecf20Sopenharmony_ci		dev_err(dev, "MPU<->CM3 sync failure\n");
1818c2ecf20Sopenharmony_ci		m3_ipc->state = M3_STATE_UNKNOWN;
1828c2ecf20Sopenharmony_ci		return -EIO;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	mbox_client_txdone(m3_ipc->mbox, 0);
1868c2ecf20Sopenharmony_ci	return 0;
1878c2ecf20Sopenharmony_ci}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_cistatic int wkup_m3_ping_noirq(struct wkup_m3_ipc *m3_ipc)
1908c2ecf20Sopenharmony_ci{
1918c2ecf20Sopenharmony_ci	struct device *dev = m3_ipc->dev;
1928c2ecf20Sopenharmony_ci	mbox_msg_t dummy_msg = 0;
1938c2ecf20Sopenharmony_ci	int ret;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	if (!m3_ipc->mbox) {
1968c2ecf20Sopenharmony_ci		dev_err(dev,
1978c2ecf20Sopenharmony_ci			"No IPC channel to communicate with wkup_m3!\n");
1988c2ecf20Sopenharmony_ci		return -EIO;
1998c2ecf20Sopenharmony_ci	}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	ret = mbox_send_message(m3_ipc->mbox, &dummy_msg);
2028c2ecf20Sopenharmony_ci	if (ret < 0) {
2038c2ecf20Sopenharmony_ci		dev_err(dev, "%s: mbox_send_message() failed: %d\n",
2048c2ecf20Sopenharmony_ci			__func__, ret);
2058c2ecf20Sopenharmony_ci		return ret;
2068c2ecf20Sopenharmony_ci	}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	mbox_client_txdone(m3_ipc->mbox, 0);
2098c2ecf20Sopenharmony_ci	return 0;
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic int wkup_m3_is_available(struct wkup_m3_ipc *m3_ipc)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	return ((m3_ipc->state != M3_STATE_RESET) &&
2158c2ecf20Sopenharmony_ci		(m3_ipc->state != M3_STATE_UNKNOWN));
2168c2ecf20Sopenharmony_ci}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci/* Public functions */
2198c2ecf20Sopenharmony_ci/**
2208c2ecf20Sopenharmony_ci * wkup_m3_set_mem_type - Pass wkup_m3 which type of memory is in use
2218c2ecf20Sopenharmony_ci * @mem_type: memory type value read directly from emif
2228c2ecf20Sopenharmony_ci *
2238c2ecf20Sopenharmony_ci * wkup_m3 must know what memory type is in use to properly suspend
2248c2ecf20Sopenharmony_ci * and resume.
2258c2ecf20Sopenharmony_ci */
2268c2ecf20Sopenharmony_cistatic void wkup_m3_set_mem_type(struct wkup_m3_ipc *m3_ipc, int mem_type)
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	m3_ipc->mem_type = mem_type;
2298c2ecf20Sopenharmony_ci}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci/**
2328c2ecf20Sopenharmony_ci * wkup_m3_set_resume_address - Pass wkup_m3 resume address
2338c2ecf20Sopenharmony_ci * @addr: Physical address from which resume code should execute
2348c2ecf20Sopenharmony_ci */
2358c2ecf20Sopenharmony_cistatic void wkup_m3_set_resume_address(struct wkup_m3_ipc *m3_ipc, void *addr)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	m3_ipc->resume_addr = (unsigned long)addr;
2388c2ecf20Sopenharmony_ci}
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci/**
2418c2ecf20Sopenharmony_ci * wkup_m3_request_pm_status - Retrieve wkup_m3 status code after suspend
2428c2ecf20Sopenharmony_ci *
2438c2ecf20Sopenharmony_ci * Returns code representing the status of a low power mode transition.
2448c2ecf20Sopenharmony_ci *	0 - Successful transition
2458c2ecf20Sopenharmony_ci *	1 - Failure to transition to low power state
2468c2ecf20Sopenharmony_ci */
2478c2ecf20Sopenharmony_cistatic int wkup_m3_request_pm_status(struct wkup_m3_ipc *m3_ipc)
2488c2ecf20Sopenharmony_ci{
2498c2ecf20Sopenharmony_ci	unsigned int i;
2508c2ecf20Sopenharmony_ci	int val;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	val = wkup_m3_ctrl_ipc_read(m3_ipc, 1);
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	i = M3_STATUS_RESP_MASK & val;
2558c2ecf20Sopenharmony_ci	i >>= __ffs(M3_STATUS_RESP_MASK);
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	return i;
2588c2ecf20Sopenharmony_ci}
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci/**
2618c2ecf20Sopenharmony_ci * wkup_m3_prepare_low_power - Request preparation for transition to
2628c2ecf20Sopenharmony_ci *			       low power state
2638c2ecf20Sopenharmony_ci * @state: A kernel suspend state to enter, either MEM or STANDBY
2648c2ecf20Sopenharmony_ci *
2658c2ecf20Sopenharmony_ci * Returns 0 if preparation was successful, otherwise returns error code
2668c2ecf20Sopenharmony_ci */
2678c2ecf20Sopenharmony_cistatic int wkup_m3_prepare_low_power(struct wkup_m3_ipc *m3_ipc, int state)
2688c2ecf20Sopenharmony_ci{
2698c2ecf20Sopenharmony_ci	struct device *dev = m3_ipc->dev;
2708c2ecf20Sopenharmony_ci	int m3_power_state;
2718c2ecf20Sopenharmony_ci	int ret = 0;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	if (!wkup_m3_is_available(m3_ipc))
2748c2ecf20Sopenharmony_ci		return -ENODEV;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	switch (state) {
2778c2ecf20Sopenharmony_ci	case WKUP_M3_DEEPSLEEP:
2788c2ecf20Sopenharmony_ci		m3_power_state = IPC_CMD_DS0;
2798c2ecf20Sopenharmony_ci		break;
2808c2ecf20Sopenharmony_ci	case WKUP_M3_STANDBY:
2818c2ecf20Sopenharmony_ci		m3_power_state = IPC_CMD_STANDBY;
2828c2ecf20Sopenharmony_ci		break;
2838c2ecf20Sopenharmony_ci	case WKUP_M3_IDLE:
2848c2ecf20Sopenharmony_ci		m3_power_state = IPC_CMD_IDLE;
2858c2ecf20Sopenharmony_ci		break;
2868c2ecf20Sopenharmony_ci	default:
2878c2ecf20Sopenharmony_ci		return 1;
2888c2ecf20Sopenharmony_ci	}
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	/* Program each required IPC register then write defaults to others */
2918c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, m3_ipc->resume_addr, 0);
2928c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, m3_power_state, 1);
2938c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, m3_ipc->mem_type, 4);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 2);
2968c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 3);
2978c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 5);
2988c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 6);
2998c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 7);
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	m3_ipc->state = M3_STATE_MSG_FOR_LP;
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci	if (state == WKUP_M3_IDLE)
3048c2ecf20Sopenharmony_ci		ret = wkup_m3_ping_noirq(m3_ipc);
3058c2ecf20Sopenharmony_ci	else
3068c2ecf20Sopenharmony_ci		ret = wkup_m3_ping(m3_ipc);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	if (ret) {
3098c2ecf20Sopenharmony_ci		dev_err(dev, "Unable to ping CM3\n");
3108c2ecf20Sopenharmony_ci		return ret;
3118c2ecf20Sopenharmony_ci	}
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	return 0;
3148c2ecf20Sopenharmony_ci}
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci/**
3178c2ecf20Sopenharmony_ci * wkup_m3_finish_low_power - Return m3 to reset state
3188c2ecf20Sopenharmony_ci *
3198c2ecf20Sopenharmony_ci * Returns 0 if reset was successful, otherwise returns error code
3208c2ecf20Sopenharmony_ci */
3218c2ecf20Sopenharmony_cistatic int wkup_m3_finish_low_power(struct wkup_m3_ipc *m3_ipc)
3228c2ecf20Sopenharmony_ci{
3238c2ecf20Sopenharmony_ci	struct device *dev = m3_ipc->dev;
3248c2ecf20Sopenharmony_ci	int ret = 0;
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	if (!wkup_m3_is_available(m3_ipc))
3278c2ecf20Sopenharmony_ci		return -ENODEV;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, IPC_CMD_RESET, 1);
3308c2ecf20Sopenharmony_ci	wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 2);
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	m3_ipc->state = M3_STATE_MSG_FOR_RESET;
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	ret = wkup_m3_ping(m3_ipc);
3358c2ecf20Sopenharmony_ci	if (ret) {
3368c2ecf20Sopenharmony_ci		dev_err(dev, "Unable to ping CM3\n");
3378c2ecf20Sopenharmony_ci		return ret;
3388c2ecf20Sopenharmony_ci	}
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	return 0;
3418c2ecf20Sopenharmony_ci}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci/**
3448c2ecf20Sopenharmony_ci * wkup_m3_request_wake_src - Get the wakeup source info passed from wkup_m3
3458c2ecf20Sopenharmony_ci * @m3_ipc: Pointer to wkup_m3_ipc context
3468c2ecf20Sopenharmony_ci */
3478c2ecf20Sopenharmony_cistatic const char *wkup_m3_request_wake_src(struct wkup_m3_ipc *m3_ipc)
3488c2ecf20Sopenharmony_ci{
3498c2ecf20Sopenharmony_ci	unsigned int wakeup_src_idx;
3508c2ecf20Sopenharmony_ci	int j, val;
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	val = wkup_m3_ctrl_ipc_read(m3_ipc, 6);
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	wakeup_src_idx = val & M3_WAKE_SRC_MASK;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	for (j = 0; j < ARRAY_SIZE(wakeups) - 1; j++) {
3578c2ecf20Sopenharmony_ci		if (wakeups[j].irq_nr == wakeup_src_idx)
3588c2ecf20Sopenharmony_ci			return wakeups[j].src;
3598c2ecf20Sopenharmony_ci	}
3608c2ecf20Sopenharmony_ci	return wakeups[j].src;
3618c2ecf20Sopenharmony_ci}
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci/**
3648c2ecf20Sopenharmony_ci * wkup_m3_set_rtc_only - Set the rtc_only flag
3658c2ecf20Sopenharmony_ci * @wkup_m3_wakeup: struct wkup_m3_wakeup_src * gets assigned the
3668c2ecf20Sopenharmony_ci *                  wakeup src value
3678c2ecf20Sopenharmony_ci */
3688c2ecf20Sopenharmony_cistatic void wkup_m3_set_rtc_only(struct wkup_m3_ipc *m3_ipc)
3698c2ecf20Sopenharmony_ci{
3708c2ecf20Sopenharmony_ci	if (m3_ipc_state)
3718c2ecf20Sopenharmony_ci		m3_ipc_state->is_rtc_only = true;
3728c2ecf20Sopenharmony_ci}
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_cistatic struct wkup_m3_ipc_ops ipc_ops = {
3758c2ecf20Sopenharmony_ci	.set_mem_type = wkup_m3_set_mem_type,
3768c2ecf20Sopenharmony_ci	.set_resume_address = wkup_m3_set_resume_address,
3778c2ecf20Sopenharmony_ci	.prepare_low_power = wkup_m3_prepare_low_power,
3788c2ecf20Sopenharmony_ci	.finish_low_power = wkup_m3_finish_low_power,
3798c2ecf20Sopenharmony_ci	.request_pm_status = wkup_m3_request_pm_status,
3808c2ecf20Sopenharmony_ci	.request_wake_src = wkup_m3_request_wake_src,
3818c2ecf20Sopenharmony_ci	.set_rtc_only = wkup_m3_set_rtc_only,
3828c2ecf20Sopenharmony_ci};
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci/**
3858c2ecf20Sopenharmony_ci * wkup_m3_ipc_get - Return handle to wkup_m3_ipc
3868c2ecf20Sopenharmony_ci *
3878c2ecf20Sopenharmony_ci * Returns NULL if the wkup_m3 is not yet available, otherwise returns
3888c2ecf20Sopenharmony_ci * pointer to wkup_m3_ipc struct.
3898c2ecf20Sopenharmony_ci */
3908c2ecf20Sopenharmony_cistruct wkup_m3_ipc *wkup_m3_ipc_get(void)
3918c2ecf20Sopenharmony_ci{
3928c2ecf20Sopenharmony_ci	if (m3_ipc_state)
3938c2ecf20Sopenharmony_ci		get_device(m3_ipc_state->dev);
3948c2ecf20Sopenharmony_ci	else
3958c2ecf20Sopenharmony_ci		return NULL;
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci	return m3_ipc_state;
3988c2ecf20Sopenharmony_ci}
3998c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wkup_m3_ipc_get);
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci/**
4028c2ecf20Sopenharmony_ci * wkup_m3_ipc_put - Free handle to wkup_m3_ipc returned from wkup_m3_ipc_get
4038c2ecf20Sopenharmony_ci * @m3_ipc: A pointer to wkup_m3_ipc struct returned by wkup_m3_ipc_get
4048c2ecf20Sopenharmony_ci */
4058c2ecf20Sopenharmony_civoid wkup_m3_ipc_put(struct wkup_m3_ipc *m3_ipc)
4068c2ecf20Sopenharmony_ci{
4078c2ecf20Sopenharmony_ci	if (m3_ipc_state)
4088c2ecf20Sopenharmony_ci		put_device(m3_ipc_state->dev);
4098c2ecf20Sopenharmony_ci}
4108c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wkup_m3_ipc_put);
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_cistatic void wkup_m3_rproc_boot_thread(struct wkup_m3_ipc *m3_ipc)
4138c2ecf20Sopenharmony_ci{
4148c2ecf20Sopenharmony_ci	struct device *dev = m3_ipc->dev;
4158c2ecf20Sopenharmony_ci	int ret;
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	init_completion(&m3_ipc->sync_complete);
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	ret = rproc_boot(m3_ipc->rproc);
4208c2ecf20Sopenharmony_ci	if (ret)
4218c2ecf20Sopenharmony_ci		dev_err(dev, "rproc_boot failed\n");
4228c2ecf20Sopenharmony_ci	else
4238c2ecf20Sopenharmony_ci		m3_ipc_state = m3_ipc;
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_ci	do_exit(0);
4268c2ecf20Sopenharmony_ci}
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_cistatic int wkup_m3_ipc_probe(struct platform_device *pdev)
4298c2ecf20Sopenharmony_ci{
4308c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
4318c2ecf20Sopenharmony_ci	int irq, ret;
4328c2ecf20Sopenharmony_ci	phandle rproc_phandle;
4338c2ecf20Sopenharmony_ci	struct rproc *m3_rproc;
4348c2ecf20Sopenharmony_ci	struct resource *res;
4358c2ecf20Sopenharmony_ci	struct task_struct *task;
4368c2ecf20Sopenharmony_ci	struct wkup_m3_ipc *m3_ipc;
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci	m3_ipc = devm_kzalloc(dev, sizeof(*m3_ipc), GFP_KERNEL);
4398c2ecf20Sopenharmony_ci	if (!m3_ipc)
4408c2ecf20Sopenharmony_ci		return -ENOMEM;
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
4438c2ecf20Sopenharmony_ci	m3_ipc->ipc_mem_base = devm_ioremap_resource(dev, res);
4448c2ecf20Sopenharmony_ci	if (IS_ERR(m3_ipc->ipc_mem_base)) {
4458c2ecf20Sopenharmony_ci		dev_err(dev, "could not ioremap ipc_mem\n");
4468c2ecf20Sopenharmony_ci		return PTR_ERR(m3_ipc->ipc_mem_base);
4478c2ecf20Sopenharmony_ci	}
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
4508c2ecf20Sopenharmony_ci	if (irq < 0) {
4518c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "no irq resource\n");
4528c2ecf20Sopenharmony_ci		return irq;
4538c2ecf20Sopenharmony_ci	}
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_ci	ret = devm_request_irq(dev, irq, wkup_m3_txev_handler,
4568c2ecf20Sopenharmony_ci			       0, "wkup_m3_txev", m3_ipc);
4578c2ecf20Sopenharmony_ci	if (ret) {
4588c2ecf20Sopenharmony_ci		dev_err(dev, "request_irq failed\n");
4598c2ecf20Sopenharmony_ci		return ret;
4608c2ecf20Sopenharmony_ci	}
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci	m3_ipc->mbox_client.dev = dev;
4638c2ecf20Sopenharmony_ci	m3_ipc->mbox_client.tx_done = NULL;
4648c2ecf20Sopenharmony_ci	m3_ipc->mbox_client.tx_prepare = NULL;
4658c2ecf20Sopenharmony_ci	m3_ipc->mbox_client.rx_callback = NULL;
4668c2ecf20Sopenharmony_ci	m3_ipc->mbox_client.tx_block = false;
4678c2ecf20Sopenharmony_ci	m3_ipc->mbox_client.knows_txdone = false;
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ci	m3_ipc->mbox = mbox_request_channel(&m3_ipc->mbox_client, 0);
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	if (IS_ERR(m3_ipc->mbox)) {
4728c2ecf20Sopenharmony_ci		dev_err(dev, "IPC Request for A8->M3 Channel failed! %ld\n",
4738c2ecf20Sopenharmony_ci			PTR_ERR(m3_ipc->mbox));
4748c2ecf20Sopenharmony_ci		return PTR_ERR(m3_ipc->mbox);
4758c2ecf20Sopenharmony_ci	}
4768c2ecf20Sopenharmony_ci
4778c2ecf20Sopenharmony_ci	if (of_property_read_u32(dev->of_node, "ti,rproc", &rproc_phandle)) {
4788c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "could not get rproc phandle\n");
4798c2ecf20Sopenharmony_ci		ret = -ENODEV;
4808c2ecf20Sopenharmony_ci		goto err_free_mbox;
4818c2ecf20Sopenharmony_ci	}
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	m3_rproc = rproc_get_by_phandle(rproc_phandle);
4848c2ecf20Sopenharmony_ci	if (!m3_rproc) {
4858c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "could not get rproc handle\n");
4868c2ecf20Sopenharmony_ci		ret = -EPROBE_DEFER;
4878c2ecf20Sopenharmony_ci		goto err_free_mbox;
4888c2ecf20Sopenharmony_ci	}
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci	m3_ipc->rproc = m3_rproc;
4918c2ecf20Sopenharmony_ci	m3_ipc->dev = dev;
4928c2ecf20Sopenharmony_ci	m3_ipc->state = M3_STATE_RESET;
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_ci	m3_ipc->ops = &ipc_ops;
4958c2ecf20Sopenharmony_ci
4968c2ecf20Sopenharmony_ci	/*
4978c2ecf20Sopenharmony_ci	 * Wait for firmware loading completion in a thread so we
4988c2ecf20Sopenharmony_ci	 * can boot the wkup_m3 as soon as it's ready without holding
4998c2ecf20Sopenharmony_ci	 * up kernel boot
5008c2ecf20Sopenharmony_ci	 */
5018c2ecf20Sopenharmony_ci	task = kthread_run((void *)wkup_m3_rproc_boot_thread, m3_ipc,
5028c2ecf20Sopenharmony_ci			   "wkup_m3_rproc_loader");
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci	if (IS_ERR(task)) {
5058c2ecf20Sopenharmony_ci		dev_err(dev, "can't create rproc_boot thread\n");
5068c2ecf20Sopenharmony_ci		ret = PTR_ERR(task);
5078c2ecf20Sopenharmony_ci		goto err_put_rproc;
5088c2ecf20Sopenharmony_ci	}
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_ci	return 0;
5118c2ecf20Sopenharmony_ci
5128c2ecf20Sopenharmony_cierr_put_rproc:
5138c2ecf20Sopenharmony_ci	rproc_put(m3_rproc);
5148c2ecf20Sopenharmony_cierr_free_mbox:
5158c2ecf20Sopenharmony_ci	mbox_free_channel(m3_ipc->mbox);
5168c2ecf20Sopenharmony_ci	return ret;
5178c2ecf20Sopenharmony_ci}
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_cistatic int wkup_m3_ipc_remove(struct platform_device *pdev)
5208c2ecf20Sopenharmony_ci{
5218c2ecf20Sopenharmony_ci	mbox_free_channel(m3_ipc_state->mbox);
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci	rproc_shutdown(m3_ipc_state->rproc);
5248c2ecf20Sopenharmony_ci	rproc_put(m3_ipc_state->rproc);
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_ci	m3_ipc_state = NULL;
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ci	return 0;
5298c2ecf20Sopenharmony_ci}
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_cistatic int __maybe_unused wkup_m3_ipc_suspend(struct device *dev)
5328c2ecf20Sopenharmony_ci{
5338c2ecf20Sopenharmony_ci	/*
5348c2ecf20Sopenharmony_ci	 * Nothing needs to be done on suspend even with rtc_only flag set
5358c2ecf20Sopenharmony_ci	 */
5368c2ecf20Sopenharmony_ci	return 0;
5378c2ecf20Sopenharmony_ci}
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_cistatic int __maybe_unused wkup_m3_ipc_resume(struct device *dev)
5408c2ecf20Sopenharmony_ci{
5418c2ecf20Sopenharmony_ci	if (m3_ipc_state->is_rtc_only) {
5428c2ecf20Sopenharmony_ci		rproc_shutdown(m3_ipc_state->rproc);
5438c2ecf20Sopenharmony_ci		rproc_boot(m3_ipc_state->rproc);
5448c2ecf20Sopenharmony_ci	}
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	m3_ipc_state->is_rtc_only = false;
5478c2ecf20Sopenharmony_ci
5488c2ecf20Sopenharmony_ci	return 0;
5498c2ecf20Sopenharmony_ci}
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_cistatic const struct dev_pm_ops wkup_m3_ipc_pm_ops = {
5528c2ecf20Sopenharmony_ci	SET_SYSTEM_SLEEP_PM_OPS(wkup_m3_ipc_suspend, wkup_m3_ipc_resume)
5538c2ecf20Sopenharmony_ci};
5548c2ecf20Sopenharmony_ci
5558c2ecf20Sopenharmony_cistatic const struct of_device_id wkup_m3_ipc_of_match[] = {
5568c2ecf20Sopenharmony_ci	{ .compatible = "ti,am3352-wkup-m3-ipc", },
5578c2ecf20Sopenharmony_ci	{ .compatible = "ti,am4372-wkup-m3-ipc", },
5588c2ecf20Sopenharmony_ci	{},
5598c2ecf20Sopenharmony_ci};
5608c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, wkup_m3_ipc_of_match);
5618c2ecf20Sopenharmony_ci
5628c2ecf20Sopenharmony_cistatic struct platform_driver wkup_m3_ipc_driver = {
5638c2ecf20Sopenharmony_ci	.probe = wkup_m3_ipc_probe,
5648c2ecf20Sopenharmony_ci	.remove = wkup_m3_ipc_remove,
5658c2ecf20Sopenharmony_ci	.driver = {
5668c2ecf20Sopenharmony_ci		.name = "wkup_m3_ipc",
5678c2ecf20Sopenharmony_ci		.of_match_table = wkup_m3_ipc_of_match,
5688c2ecf20Sopenharmony_ci		.pm = &wkup_m3_ipc_pm_ops,
5698c2ecf20Sopenharmony_ci	},
5708c2ecf20Sopenharmony_ci};
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_cimodule_platform_driver(wkup_m3_ipc_driver);
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
5758c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("wkup m3 remote processor ipc driver");
5768c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>");
577