18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */ 38c2ecf20Sopenharmony_ci 48c2ecf20Sopenharmony_ci#include <linux/types.h> 58c2ecf20Sopenharmony_ci#include <linux/io.h> 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include "shm_ipc.h" 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#undef pr_fmt 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "qtnfmac shm_ipc: %s: " fmt, __func__ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_cistatic bool qtnf_shm_ipc_has_new_data(struct qtnf_shm_ipc *ipc) 138c2ecf20Sopenharmony_ci{ 148c2ecf20Sopenharmony_ci const u32 flags = readl(&ipc->shm_region->headroom.hdr.flags); 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci return (flags & QTNF_SHM_IPC_NEW_DATA); 178c2ecf20Sopenharmony_ci} 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistatic void qtnf_shm_handle_new_data(struct qtnf_shm_ipc *ipc) 208c2ecf20Sopenharmony_ci{ 218c2ecf20Sopenharmony_ci size_t size; 228c2ecf20Sopenharmony_ci bool rx_buff_ok = true; 238c2ecf20Sopenharmony_ci struct qtnf_shm_ipc_region_header __iomem *shm_reg_hdr; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci shm_reg_hdr = &ipc->shm_region->headroom.hdr; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci size = readw(&shm_reg_hdr->data_len); 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci if (unlikely(size == 0 || size > QTN_IPC_MAX_DATA_SZ)) { 308c2ecf20Sopenharmony_ci pr_err("wrong rx packet size: %zu\n", size); 318c2ecf20Sopenharmony_ci rx_buff_ok = false; 328c2ecf20Sopenharmony_ci } 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci if (likely(rx_buff_ok)) { 358c2ecf20Sopenharmony_ci ipc->rx_packet_count++; 368c2ecf20Sopenharmony_ci ipc->rx_callback.fn(ipc->rx_callback.arg, 378c2ecf20Sopenharmony_ci ipc->shm_region->data, size); 388c2ecf20Sopenharmony_ci } 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci writel(QTNF_SHM_IPC_ACK, &shm_reg_hdr->flags); 418c2ecf20Sopenharmony_ci readl(&shm_reg_hdr->flags); /* flush PCIe write */ 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci ipc->interrupt.fn(ipc->interrupt.arg); 448c2ecf20Sopenharmony_ci} 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic void qtnf_shm_ipc_irq_work(struct work_struct *work) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci struct qtnf_shm_ipc *ipc = container_of(work, struct qtnf_shm_ipc, 498c2ecf20Sopenharmony_ci irq_work); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci while (qtnf_shm_ipc_has_new_data(ipc)) 528c2ecf20Sopenharmony_ci qtnf_shm_handle_new_data(ipc); 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic void qtnf_shm_ipc_irq_inbound_handler(struct qtnf_shm_ipc *ipc) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci u32 flags; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci flags = readl(&ipc->shm_region->headroom.hdr.flags); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci if (flags & QTNF_SHM_IPC_NEW_DATA) 628c2ecf20Sopenharmony_ci queue_work(ipc->workqueue, &ipc->irq_work); 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic void qtnf_shm_ipc_irq_outbound_handler(struct qtnf_shm_ipc *ipc) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci u32 flags; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci if (!READ_ONCE(ipc->waiting_for_ack)) 708c2ecf20Sopenharmony_ci return; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci flags = readl(&ipc->shm_region->headroom.hdr.flags); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci if (flags & QTNF_SHM_IPC_ACK) { 758c2ecf20Sopenharmony_ci WRITE_ONCE(ipc->waiting_for_ack, 0); 768c2ecf20Sopenharmony_ci complete(&ipc->tx_completion); 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ciint qtnf_shm_ipc_init(struct qtnf_shm_ipc *ipc, 818c2ecf20Sopenharmony_ci enum qtnf_shm_ipc_direction direction, 828c2ecf20Sopenharmony_ci struct qtnf_shm_ipc_region __iomem *shm_region, 838c2ecf20Sopenharmony_ci struct workqueue_struct *workqueue, 848c2ecf20Sopenharmony_ci const struct qtnf_shm_ipc_int *interrupt, 858c2ecf20Sopenharmony_ci const struct qtnf_shm_ipc_rx_callback *rx_callback) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci BUILD_BUG_ON(offsetof(struct qtnf_shm_ipc_region, data) != 888c2ecf20Sopenharmony_ci QTN_IPC_REG_HDR_SZ); 898c2ecf20Sopenharmony_ci BUILD_BUG_ON(sizeof(struct qtnf_shm_ipc_region) > QTN_IPC_REG_SZ); 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci ipc->shm_region = shm_region; 928c2ecf20Sopenharmony_ci ipc->direction = direction; 938c2ecf20Sopenharmony_ci ipc->interrupt = *interrupt; 948c2ecf20Sopenharmony_ci ipc->rx_callback = *rx_callback; 958c2ecf20Sopenharmony_ci ipc->tx_packet_count = 0; 968c2ecf20Sopenharmony_ci ipc->rx_packet_count = 0; 978c2ecf20Sopenharmony_ci ipc->workqueue = workqueue; 988c2ecf20Sopenharmony_ci ipc->waiting_for_ack = 0; 998c2ecf20Sopenharmony_ci ipc->tx_timeout_count = 0; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci switch (direction) { 1028c2ecf20Sopenharmony_ci case QTNF_SHM_IPC_OUTBOUND: 1038c2ecf20Sopenharmony_ci ipc->irq_handler = qtnf_shm_ipc_irq_outbound_handler; 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci case QTNF_SHM_IPC_INBOUND: 1068c2ecf20Sopenharmony_ci ipc->irq_handler = qtnf_shm_ipc_irq_inbound_handler; 1078c2ecf20Sopenharmony_ci break; 1088c2ecf20Sopenharmony_ci default: 1098c2ecf20Sopenharmony_ci return -EINVAL; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci INIT_WORK(&ipc->irq_work, qtnf_shm_ipc_irq_work); 1138c2ecf20Sopenharmony_ci init_completion(&ipc->tx_completion); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci return 0; 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_civoid qtnf_shm_ipc_free(struct qtnf_shm_ipc *ipc) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci complete_all(&ipc->tx_completion); 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ciint qtnf_shm_ipc_send(struct qtnf_shm_ipc *ipc, const u8 *buf, size_t size) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci int ret = 0; 1268c2ecf20Sopenharmony_ci struct qtnf_shm_ipc_region_header __iomem *shm_reg_hdr; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci shm_reg_hdr = &ipc->shm_region->headroom.hdr; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (unlikely(size > QTN_IPC_MAX_DATA_SZ)) 1318c2ecf20Sopenharmony_ci return -E2BIG; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci ipc->tx_packet_count++; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci writew(size, &shm_reg_hdr->data_len); 1368c2ecf20Sopenharmony_ci memcpy_toio(ipc->shm_region->data, buf, size); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* sync previous writes before proceeding */ 1398c2ecf20Sopenharmony_ci dma_wmb(); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci WRITE_ONCE(ipc->waiting_for_ack, 1); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci /* sync previous memory write before announcing new data ready */ 1448c2ecf20Sopenharmony_ci wmb(); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci writel(QTNF_SHM_IPC_NEW_DATA, &shm_reg_hdr->flags); 1478c2ecf20Sopenharmony_ci readl(&shm_reg_hdr->flags); /* flush PCIe write */ 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci ipc->interrupt.fn(ipc->interrupt.arg); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci if (!wait_for_completion_timeout(&ipc->tx_completion, 1528c2ecf20Sopenharmony_ci QTN_SHM_IPC_ACK_TIMEOUT)) { 1538c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 1548c2ecf20Sopenharmony_ci ipc->tx_timeout_count++; 1558c2ecf20Sopenharmony_ci pr_err("TX ACK timeout\n"); 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* now we're not waiting for ACK even in case of timeout */ 1598c2ecf20Sopenharmony_ci WRITE_ONCE(ipc->waiting_for_ack, 0); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci return ret; 1628c2ecf20Sopenharmony_ci} 163