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