18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Copyright(c) 2020 Intel Corporation. All rights reserved. 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Author: Cezary Rojewski <cezary.rojewski@intel.com> 68c2ecf20Sopenharmony_ci// 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/irqreturn.h> 98c2ecf20Sopenharmony_ci#include "core.h" 108c2ecf20Sopenharmony_ci#include "messages.h" 118c2ecf20Sopenharmony_ci#include "registers.h" 128c2ecf20Sopenharmony_ci#include "trace.h" 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#define CATPT_IPC_TIMEOUT_MS 300 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_civoid catpt_ipc_init(struct catpt_ipc *ipc, struct device *dev) 178c2ecf20Sopenharmony_ci{ 188c2ecf20Sopenharmony_ci ipc->dev = dev; 198c2ecf20Sopenharmony_ci ipc->ready = false; 208c2ecf20Sopenharmony_ci ipc->default_timeout = CATPT_IPC_TIMEOUT_MS; 218c2ecf20Sopenharmony_ci init_completion(&ipc->done_completion); 228c2ecf20Sopenharmony_ci init_completion(&ipc->busy_completion); 238c2ecf20Sopenharmony_ci spin_lock_init(&ipc->lock); 248c2ecf20Sopenharmony_ci mutex_init(&ipc->mutex); 258c2ecf20Sopenharmony_ci} 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic int catpt_ipc_arm(struct catpt_ipc *ipc, struct catpt_fw_ready *config) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci /* 308c2ecf20Sopenharmony_ci * Both tx and rx are put into and received from outbox. Inbox is 318c2ecf20Sopenharmony_ci * only used for notifications where payload size is known upfront, 328c2ecf20Sopenharmony_ci * thus no separate buffer is allocated for it. 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_ci ipc->rx.data = devm_kzalloc(ipc->dev, config->outbox_size, GFP_KERNEL); 358c2ecf20Sopenharmony_ci if (!ipc->rx.data) 368c2ecf20Sopenharmony_ci return -ENOMEM; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci memcpy(&ipc->config, config, sizeof(*config)); 398c2ecf20Sopenharmony_ci ipc->ready = true; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci return 0; 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic void catpt_ipc_msg_init(struct catpt_ipc *ipc, 458c2ecf20Sopenharmony_ci struct catpt_ipc_msg *reply) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci lockdep_assert_held(&ipc->lock); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci ipc->rx.header = 0; 508c2ecf20Sopenharmony_ci ipc->rx.size = reply ? reply->size : 0; 518c2ecf20Sopenharmony_ci reinit_completion(&ipc->done_completion); 528c2ecf20Sopenharmony_ci reinit_completion(&ipc->busy_completion); 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic void catpt_dsp_send_tx(struct catpt_dev *cdev, 568c2ecf20Sopenharmony_ci const struct catpt_ipc_msg *tx) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci u32 header = tx->header | CATPT_IPCC_BUSY; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci trace_catpt_ipc_request(header); 618c2ecf20Sopenharmony_ci trace_catpt_ipc_payload(tx->data, tx->size); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci memcpy_toio(catpt_outbox_addr(cdev), tx->data, tx->size); 648c2ecf20Sopenharmony_ci catpt_writel_shim(cdev, IPCC, header); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic int catpt_wait_msg_completion(struct catpt_dev *cdev, int timeout) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci struct catpt_ipc *ipc = &cdev->ipc; 708c2ecf20Sopenharmony_ci int ret; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci ret = wait_for_completion_timeout(&ipc->done_completion, 738c2ecf20Sopenharmony_ci msecs_to_jiffies(timeout)); 748c2ecf20Sopenharmony_ci if (!ret) 758c2ecf20Sopenharmony_ci return -ETIMEDOUT; 768c2ecf20Sopenharmony_ci if (ipc->rx.rsp.status != CATPT_REPLY_PENDING) 778c2ecf20Sopenharmony_ci return 0; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci /* wait for delayed reply */ 808c2ecf20Sopenharmony_ci ret = wait_for_completion_timeout(&ipc->busy_completion, 818c2ecf20Sopenharmony_ci msecs_to_jiffies(timeout)); 828c2ecf20Sopenharmony_ci return ret ? 0 : -ETIMEDOUT; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic int catpt_dsp_do_send_msg(struct catpt_dev *cdev, 868c2ecf20Sopenharmony_ci struct catpt_ipc_msg request, 878c2ecf20Sopenharmony_ci struct catpt_ipc_msg *reply, int timeout) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct catpt_ipc *ipc = &cdev->ipc; 908c2ecf20Sopenharmony_ci unsigned long flags; 918c2ecf20Sopenharmony_ci int ret; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (!ipc->ready) 948c2ecf20Sopenharmony_ci return -EPERM; 958c2ecf20Sopenharmony_ci if (request.size > ipc->config.outbox_size || 968c2ecf20Sopenharmony_ci (reply && reply->size > ipc->config.outbox_size)) 978c2ecf20Sopenharmony_ci return -EINVAL; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci spin_lock_irqsave(&ipc->lock, flags); 1008c2ecf20Sopenharmony_ci catpt_ipc_msg_init(ipc, reply); 1018c2ecf20Sopenharmony_ci catpt_dsp_send_tx(cdev, &request); 1028c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ipc->lock, flags); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci ret = catpt_wait_msg_completion(cdev, timeout); 1058c2ecf20Sopenharmony_ci if (ret) { 1068c2ecf20Sopenharmony_ci dev_crit(cdev->dev, "communication severed: %d, rebooting dsp..\n", 1078c2ecf20Sopenharmony_ci ret); 1088c2ecf20Sopenharmony_ci ipc->ready = false; 1098c2ecf20Sopenharmony_ci /* TODO: attempt recovery */ 1108c2ecf20Sopenharmony_ci return ret; 1118c2ecf20Sopenharmony_ci } 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci ret = ipc->rx.rsp.status; 1148c2ecf20Sopenharmony_ci if (reply) { 1158c2ecf20Sopenharmony_ci reply->header = ipc->rx.header; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (!ret && reply->data) 1188c2ecf20Sopenharmony_ci memcpy(reply->data, ipc->rx.data, reply->size); 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci return ret; 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ciint catpt_dsp_send_msg_timeout(struct catpt_dev *cdev, 1258c2ecf20Sopenharmony_ci struct catpt_ipc_msg request, 1268c2ecf20Sopenharmony_ci struct catpt_ipc_msg *reply, int timeout) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci struct catpt_ipc *ipc = &cdev->ipc; 1298c2ecf20Sopenharmony_ci int ret; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci mutex_lock(&ipc->mutex); 1328c2ecf20Sopenharmony_ci ret = catpt_dsp_do_send_msg(cdev, request, reply, timeout); 1338c2ecf20Sopenharmony_ci mutex_unlock(&ipc->mutex); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci return ret; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ciint catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request, 1398c2ecf20Sopenharmony_ci struct catpt_ipc_msg *reply) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci return catpt_dsp_send_msg_timeout(cdev, request, reply, 1428c2ecf20Sopenharmony_ci cdev->ipc.default_timeout); 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistatic void 1468c2ecf20Sopenharmony_cicatpt_dsp_notify_stream(struct catpt_dev *cdev, union catpt_notify_msg msg) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci struct catpt_stream_runtime *stream; 1498c2ecf20Sopenharmony_ci struct catpt_notify_position pos; 1508c2ecf20Sopenharmony_ci struct catpt_notify_glitch glitch; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci stream = catpt_stream_find(cdev, msg.stream_hw_id); 1538c2ecf20Sopenharmony_ci if (!stream) { 1548c2ecf20Sopenharmony_ci dev_warn(cdev->dev, "notify %d for non-existent stream %d\n", 1558c2ecf20Sopenharmony_ci msg.notify_reason, msg.stream_hw_id); 1568c2ecf20Sopenharmony_ci return; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci switch (msg.notify_reason) { 1608c2ecf20Sopenharmony_ci case CATPT_NOTIFY_POSITION_CHANGED: 1618c2ecf20Sopenharmony_ci memcpy_fromio(&pos, catpt_inbox_addr(cdev), sizeof(pos)); 1628c2ecf20Sopenharmony_ci trace_catpt_ipc_payload((u8 *)&pos, sizeof(pos)); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci catpt_stream_update_position(cdev, stream, &pos); 1658c2ecf20Sopenharmony_ci break; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci case CATPT_NOTIFY_GLITCH_OCCURRED: 1688c2ecf20Sopenharmony_ci memcpy_fromio(&glitch, catpt_inbox_addr(cdev), sizeof(glitch)); 1698c2ecf20Sopenharmony_ci trace_catpt_ipc_payload((u8 *)&glitch, sizeof(glitch)); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci dev_warn(cdev->dev, "glitch %d at pos: 0x%08llx, wp: 0x%08x\n", 1728c2ecf20Sopenharmony_ci glitch.type, glitch.presentation_pos, 1738c2ecf20Sopenharmony_ci glitch.write_pos); 1748c2ecf20Sopenharmony_ci break; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci default: 1778c2ecf20Sopenharmony_ci dev_warn(cdev->dev, "unknown notification: %d received\n", 1788c2ecf20Sopenharmony_ci msg.notify_reason); 1798c2ecf20Sopenharmony_ci break; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci} 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic void catpt_dsp_copy_rx(struct catpt_dev *cdev, u32 header) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci struct catpt_ipc *ipc = &cdev->ipc; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci ipc->rx.header = header; 1888c2ecf20Sopenharmony_ci if (ipc->rx.rsp.status != CATPT_REPLY_SUCCESS) 1898c2ecf20Sopenharmony_ci return; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci memcpy_fromio(ipc->rx.data, catpt_outbox_addr(cdev), ipc->rx.size); 1928c2ecf20Sopenharmony_ci trace_catpt_ipc_payload(ipc->rx.data, ipc->rx.size); 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic void catpt_dsp_process_response(struct catpt_dev *cdev, u32 header) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci union catpt_notify_msg msg = CATPT_MSG(header); 1988c2ecf20Sopenharmony_ci struct catpt_ipc *ipc = &cdev->ipc; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci if (msg.fw_ready) { 2018c2ecf20Sopenharmony_ci struct catpt_fw_ready config; 2028c2ecf20Sopenharmony_ci /* to fit 32b header original address is shifted right by 3 */ 2038c2ecf20Sopenharmony_ci u32 off = msg.mailbox_address << 3; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci memcpy_fromio(&config, cdev->lpe_ba + off, sizeof(config)); 2068c2ecf20Sopenharmony_ci trace_catpt_ipc_payload((u8 *)&config, sizeof(config)); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci catpt_ipc_arm(ipc, &config); 2098c2ecf20Sopenharmony_ci complete(&cdev->fw_ready); 2108c2ecf20Sopenharmony_ci return; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci switch (msg.global_msg_type) { 2148c2ecf20Sopenharmony_ci case CATPT_GLB_REQUEST_CORE_DUMP: 2158c2ecf20Sopenharmony_ci dev_err(cdev->dev, "ADSP device coredump received\n"); 2168c2ecf20Sopenharmony_ci ipc->ready = false; 2178c2ecf20Sopenharmony_ci catpt_coredump(cdev); 2188c2ecf20Sopenharmony_ci /* TODO: attempt recovery */ 2198c2ecf20Sopenharmony_ci break; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci case CATPT_GLB_STREAM_MESSAGE: 2228c2ecf20Sopenharmony_ci switch (msg.stream_msg_type) { 2238c2ecf20Sopenharmony_ci case CATPT_STRM_NOTIFICATION: 2248c2ecf20Sopenharmony_ci catpt_dsp_notify_stream(cdev, msg); 2258c2ecf20Sopenharmony_ci break; 2268c2ecf20Sopenharmony_ci default: 2278c2ecf20Sopenharmony_ci catpt_dsp_copy_rx(cdev, header); 2288c2ecf20Sopenharmony_ci /* signal completion of delayed reply */ 2298c2ecf20Sopenharmony_ci complete(&ipc->busy_completion); 2308c2ecf20Sopenharmony_ci break; 2318c2ecf20Sopenharmony_ci } 2328c2ecf20Sopenharmony_ci break; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci default: 2358c2ecf20Sopenharmony_ci dev_warn(cdev->dev, "unknown response: %d received\n", 2368c2ecf20Sopenharmony_ci msg.global_msg_type); 2378c2ecf20Sopenharmony_ci break; 2388c2ecf20Sopenharmony_ci } 2398c2ecf20Sopenharmony_ci} 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ciirqreturn_t catpt_dsp_irq_thread(int irq, void *dev_id) 2428c2ecf20Sopenharmony_ci{ 2438c2ecf20Sopenharmony_ci struct catpt_dev *cdev = dev_id; 2448c2ecf20Sopenharmony_ci u32 ipcd; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci ipcd = catpt_readl_shim(cdev, IPCD); 2478c2ecf20Sopenharmony_ci trace_catpt_ipc_notify(ipcd); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci /* ensure there is delayed reply or notification to process */ 2508c2ecf20Sopenharmony_ci if (!(ipcd & CATPT_IPCD_BUSY)) 2518c2ecf20Sopenharmony_ci return IRQ_NONE; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci catpt_dsp_process_response(cdev, ipcd); 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci /* tell DSP processing is completed */ 2568c2ecf20Sopenharmony_ci catpt_updatel_shim(cdev, IPCD, CATPT_IPCD_BUSY | CATPT_IPCD_DONE, 2578c2ecf20Sopenharmony_ci CATPT_IPCD_DONE); 2588c2ecf20Sopenharmony_ci /* unmask dsp BUSY interrupt */ 2598c2ecf20Sopenharmony_ci catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB, 0); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci return IRQ_HANDLED; 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ciirqreturn_t catpt_dsp_irq_handler(int irq, void *dev_id) 2658c2ecf20Sopenharmony_ci{ 2668c2ecf20Sopenharmony_ci struct catpt_dev *cdev = dev_id; 2678c2ecf20Sopenharmony_ci irqreturn_t ret = IRQ_NONE; 2688c2ecf20Sopenharmony_ci u32 isc, ipcc; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci isc = catpt_readl_shim(cdev, ISC); 2718c2ecf20Sopenharmony_ci trace_catpt_irq(isc); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci /* immediate reply */ 2748c2ecf20Sopenharmony_ci if (isc & CATPT_ISC_IPCCD) { 2758c2ecf20Sopenharmony_ci /* mask host DONE interrupt */ 2768c2ecf20Sopenharmony_ci catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCCD, CATPT_IMC_IPCCD); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci ipcc = catpt_readl_shim(cdev, IPCC); 2798c2ecf20Sopenharmony_ci trace_catpt_ipc_reply(ipcc); 2808c2ecf20Sopenharmony_ci catpt_dsp_copy_rx(cdev, ipcc); 2818c2ecf20Sopenharmony_ci complete(&cdev->ipc.done_completion); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci /* tell DSP processing is completed */ 2848c2ecf20Sopenharmony_ci catpt_updatel_shim(cdev, IPCC, CATPT_IPCC_DONE, 0); 2858c2ecf20Sopenharmony_ci /* unmask host DONE interrupt */ 2868c2ecf20Sopenharmony_ci catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCCD, 0); 2878c2ecf20Sopenharmony_ci ret = IRQ_HANDLED; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci /* delayed reply or notification */ 2918c2ecf20Sopenharmony_ci if (isc & CATPT_ISC_IPCDB) { 2928c2ecf20Sopenharmony_ci /* mask dsp BUSY interrupt */ 2938c2ecf20Sopenharmony_ci catpt_updatel_shim(cdev, IMC, CATPT_IMC_IPCDB, CATPT_IMC_IPCDB); 2948c2ecf20Sopenharmony_ci ret = IRQ_WAKE_THREAD; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci return ret; 2988c2ecf20Sopenharmony_ci} 299