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