1*ff233cb5SSergey Matyukevich // SPDX-License-Identifier: GPL-2.0+
2*ff233cb5SSergey Matyukevich /* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
398f44cb0SIgor Mitsyanko
498f44cb0SIgor Mitsyanko #include <linux/types.h>
598f44cb0SIgor Mitsyanko #include <linux/io.h>
698f44cb0SIgor Mitsyanko
798f44cb0SIgor Mitsyanko #include "shm_ipc.h"
898f44cb0SIgor Mitsyanko
998f44cb0SIgor Mitsyanko #undef pr_fmt
1098f44cb0SIgor Mitsyanko #define pr_fmt(fmt) "qtnfmac shm_ipc: %s: " fmt, __func__
1198f44cb0SIgor Mitsyanko
qtnf_shm_ipc_has_new_data(struct qtnf_shm_ipc * ipc)1298f44cb0SIgor Mitsyanko static bool qtnf_shm_ipc_has_new_data(struct qtnf_shm_ipc *ipc)
1398f44cb0SIgor Mitsyanko {
1498f44cb0SIgor Mitsyanko const u32 flags = readl(&ipc->shm_region->headroom.hdr.flags);
1598f44cb0SIgor Mitsyanko
1698f44cb0SIgor Mitsyanko return (flags & QTNF_SHM_IPC_NEW_DATA);
1798f44cb0SIgor Mitsyanko }
1898f44cb0SIgor Mitsyanko
qtnf_shm_handle_new_data(struct qtnf_shm_ipc * ipc)1998f44cb0SIgor Mitsyanko static void qtnf_shm_handle_new_data(struct qtnf_shm_ipc *ipc)
2098f44cb0SIgor Mitsyanko {
2198f44cb0SIgor Mitsyanko size_t size;
2298f44cb0SIgor Mitsyanko bool rx_buff_ok = true;
2398f44cb0SIgor Mitsyanko struct qtnf_shm_ipc_region_header __iomem *shm_reg_hdr;
2498f44cb0SIgor Mitsyanko
2598f44cb0SIgor Mitsyanko shm_reg_hdr = &ipc->shm_region->headroom.hdr;
2698f44cb0SIgor Mitsyanko
2798f44cb0SIgor Mitsyanko size = readw(&shm_reg_hdr->data_len);
2898f44cb0SIgor Mitsyanko
2998f44cb0SIgor Mitsyanko if (unlikely(size == 0 || size > QTN_IPC_MAX_DATA_SZ)) {
3098f44cb0SIgor Mitsyanko pr_err("wrong rx packet size: %zu\n", size);
3198f44cb0SIgor Mitsyanko rx_buff_ok = false;
328804ea9eSSergey Matyukevich }
338804ea9eSSergey Matyukevich
348804ea9eSSergey Matyukevich if (likely(rx_buff_ok)) {
358804ea9eSSergey Matyukevich ipc->rx_packet_count++;
368804ea9eSSergey Matyukevich ipc->rx_callback.fn(ipc->rx_callback.arg,
378804ea9eSSergey Matyukevich ipc->shm_region->data, size);
3898f44cb0SIgor Mitsyanko }
3998f44cb0SIgor Mitsyanko
4098f44cb0SIgor Mitsyanko writel(QTNF_SHM_IPC_ACK, &shm_reg_hdr->flags);
4198f44cb0SIgor Mitsyanko readl(&shm_reg_hdr->flags); /* flush PCIe write */
4298f44cb0SIgor Mitsyanko
4398f44cb0SIgor Mitsyanko ipc->interrupt.fn(ipc->interrupt.arg);
4498f44cb0SIgor Mitsyanko }
4598f44cb0SIgor Mitsyanko
qtnf_shm_ipc_irq_work(struct work_struct * work)4698f44cb0SIgor Mitsyanko static void qtnf_shm_ipc_irq_work(struct work_struct *work)
4798f44cb0SIgor Mitsyanko {
4898f44cb0SIgor Mitsyanko struct qtnf_shm_ipc *ipc = container_of(work, struct qtnf_shm_ipc,
4998f44cb0SIgor Mitsyanko irq_work);
5098f44cb0SIgor Mitsyanko
5198f44cb0SIgor Mitsyanko while (qtnf_shm_ipc_has_new_data(ipc))
5298f44cb0SIgor Mitsyanko qtnf_shm_handle_new_data(ipc);
5398f44cb0SIgor Mitsyanko }
5498f44cb0SIgor Mitsyanko
qtnf_shm_ipc_irq_inbound_handler(struct qtnf_shm_ipc * ipc)5598f44cb0SIgor Mitsyanko static void qtnf_shm_ipc_irq_inbound_handler(struct qtnf_shm_ipc *ipc)
5698f44cb0SIgor Mitsyanko {
5798f44cb0SIgor Mitsyanko u32 flags;
5898f44cb0SIgor Mitsyanko
5998f44cb0SIgor Mitsyanko flags = readl(&ipc->shm_region->headroom.hdr.flags);
6098f44cb0SIgor Mitsyanko
6198f44cb0SIgor Mitsyanko if (flags & QTNF_SHM_IPC_NEW_DATA)
6298f44cb0SIgor Mitsyanko queue_work(ipc->workqueue, &ipc->irq_work);
6398f44cb0SIgor Mitsyanko }
6498f44cb0SIgor Mitsyanko
qtnf_shm_ipc_irq_outbound_handler(struct qtnf_shm_ipc * ipc)6598f44cb0SIgor Mitsyanko static void qtnf_shm_ipc_irq_outbound_handler(struct qtnf_shm_ipc *ipc)
6698f44cb0SIgor Mitsyanko {
6798f44cb0SIgor Mitsyanko u32 flags;
6898f44cb0SIgor Mitsyanko
6998f44cb0SIgor Mitsyanko if (!READ_ONCE(ipc->waiting_for_ack))
7098f44cb0SIgor Mitsyanko return;
7198f44cb0SIgor Mitsyanko
7298f44cb0SIgor Mitsyanko flags = readl(&ipc->shm_region->headroom.hdr.flags);
7398f44cb0SIgor Mitsyanko
7498f44cb0SIgor Mitsyanko if (flags & QTNF_SHM_IPC_ACK) {
7598f44cb0SIgor Mitsyanko WRITE_ONCE(ipc->waiting_for_ack, 0);
7698f44cb0SIgor Mitsyanko complete(&ipc->tx_completion);
7798f44cb0SIgor Mitsyanko }
7898f44cb0SIgor Mitsyanko }
7998f44cb0SIgor Mitsyanko
qtnf_shm_ipc_init(struct qtnf_shm_ipc * ipc,enum qtnf_shm_ipc_direction direction,struct qtnf_shm_ipc_region __iomem * shm_region,struct workqueue_struct * workqueue,const struct qtnf_shm_ipc_int * interrupt,const struct qtnf_shm_ipc_rx_callback * rx_callback)8098f44cb0SIgor Mitsyanko int qtnf_shm_ipc_init(struct qtnf_shm_ipc *ipc,
8198f44cb0SIgor Mitsyanko enum qtnf_shm_ipc_direction direction,
8298f44cb0SIgor Mitsyanko struct qtnf_shm_ipc_region __iomem *shm_region,
8398f44cb0SIgor Mitsyanko struct workqueue_struct *workqueue,
8498f44cb0SIgor Mitsyanko const struct qtnf_shm_ipc_int *interrupt,
8598f44cb0SIgor Mitsyanko const struct qtnf_shm_ipc_rx_callback *rx_callback)
8698f44cb0SIgor Mitsyanko {
8798f44cb0SIgor Mitsyanko BUILD_BUG_ON(offsetof(struct qtnf_shm_ipc_region, data) !=
8898f44cb0SIgor Mitsyanko QTN_IPC_REG_HDR_SZ);
8998f44cb0SIgor Mitsyanko BUILD_BUG_ON(sizeof(struct qtnf_shm_ipc_region) > QTN_IPC_REG_SZ);
9098f44cb0SIgor Mitsyanko
9198f44cb0SIgor Mitsyanko ipc->shm_region = shm_region;
9298f44cb0SIgor Mitsyanko ipc->direction = direction;
9398f44cb0SIgor Mitsyanko ipc->interrupt = *interrupt;
9498f44cb0SIgor Mitsyanko ipc->rx_callback = *rx_callback;
9598f44cb0SIgor Mitsyanko ipc->tx_packet_count = 0;
9698f44cb0SIgor Mitsyanko ipc->rx_packet_count = 0;
9798f44cb0SIgor Mitsyanko ipc->workqueue = workqueue;
9898f44cb0SIgor Mitsyanko ipc->waiting_for_ack = 0;
9998f44cb0SIgor Mitsyanko ipc->tx_timeout_count = 0;
10098f44cb0SIgor Mitsyanko
10198f44cb0SIgor Mitsyanko switch (direction) {
10298f44cb0SIgor Mitsyanko case QTNF_SHM_IPC_OUTBOUND:
10398f44cb0SIgor Mitsyanko ipc->irq_handler = qtnf_shm_ipc_irq_outbound_handler;
10498f44cb0SIgor Mitsyanko break;
10598f44cb0SIgor Mitsyanko case QTNF_SHM_IPC_INBOUND:
10698f44cb0SIgor Mitsyanko ipc->irq_handler = qtnf_shm_ipc_irq_inbound_handler;
10798f44cb0SIgor Mitsyanko break;
10898f44cb0SIgor Mitsyanko default:
10998f44cb0SIgor Mitsyanko return -EINVAL;
11098f44cb0SIgor Mitsyanko }
11198f44cb0SIgor Mitsyanko
11298f44cb0SIgor Mitsyanko INIT_WORK(&ipc->irq_work, qtnf_shm_ipc_irq_work);
11398f44cb0SIgor Mitsyanko init_completion(&ipc->tx_completion);
11498f44cb0SIgor Mitsyanko
11598f44cb0SIgor Mitsyanko return 0;
11698f44cb0SIgor Mitsyanko }
11798f44cb0SIgor Mitsyanko
qtnf_shm_ipc_free(struct qtnf_shm_ipc * ipc)11898f44cb0SIgor Mitsyanko void qtnf_shm_ipc_free(struct qtnf_shm_ipc *ipc)
11998f44cb0SIgor Mitsyanko {
12098f44cb0SIgor Mitsyanko complete_all(&ipc->tx_completion);
12198f44cb0SIgor Mitsyanko }
12298f44cb0SIgor Mitsyanko
qtnf_shm_ipc_send(struct qtnf_shm_ipc * ipc,const u8 * buf,size_t size)12398f44cb0SIgor Mitsyanko int qtnf_shm_ipc_send(struct qtnf_shm_ipc *ipc, const u8 *buf, size_t size)
12498f44cb0SIgor Mitsyanko {
12598f44cb0SIgor Mitsyanko int ret = 0;
12698f44cb0SIgor Mitsyanko struct qtnf_shm_ipc_region_header __iomem *shm_reg_hdr;
12798f44cb0SIgor Mitsyanko
12898f44cb0SIgor Mitsyanko shm_reg_hdr = &ipc->shm_region->headroom.hdr;
12998f44cb0SIgor Mitsyanko
13098f44cb0SIgor Mitsyanko if (unlikely(size > QTN_IPC_MAX_DATA_SZ))
13198f44cb0SIgor Mitsyanko return -E2BIG;
13298f44cb0SIgor Mitsyanko
13398f44cb0SIgor Mitsyanko ipc->tx_packet_count++;
13498f44cb0SIgor Mitsyanko
13598f44cb0SIgor Mitsyanko writew(size, &shm_reg_hdr->data_len);
13698f44cb0SIgor Mitsyanko memcpy_toio(ipc->shm_region->data, buf, size);
13798f44cb0SIgor Mitsyanko
13898f44cb0SIgor Mitsyanko /* sync previous writes before proceeding */
13998f44cb0SIgor Mitsyanko dma_wmb();
14098f44cb0SIgor Mitsyanko
14198f44cb0SIgor Mitsyanko WRITE_ONCE(ipc->waiting_for_ack, 1);
14298f44cb0SIgor Mitsyanko
14398f44cb0SIgor Mitsyanko /* sync previous memory write before announcing new data ready */
14498f44cb0SIgor Mitsyanko wmb();
14598f44cb0SIgor Mitsyanko
14698f44cb0SIgor Mitsyanko writel(QTNF_SHM_IPC_NEW_DATA, &shm_reg_hdr->flags);
14798f44cb0SIgor Mitsyanko readl(&shm_reg_hdr->flags); /* flush PCIe write */
14898f44cb0SIgor Mitsyanko
14998f44cb0SIgor Mitsyanko ipc->interrupt.fn(ipc->interrupt.arg);
15098f44cb0SIgor Mitsyanko
15198f44cb0SIgor Mitsyanko if (!wait_for_completion_timeout(&ipc->tx_completion,
15298f44cb0SIgor Mitsyanko QTN_SHM_IPC_ACK_TIMEOUT)) {
15398f44cb0SIgor Mitsyanko ret = -ETIMEDOUT;
15498f44cb0SIgor Mitsyanko ipc->tx_timeout_count++;
15598f44cb0SIgor Mitsyanko pr_err("TX ACK timeout\n");
15698f44cb0SIgor Mitsyanko }
15798f44cb0SIgor Mitsyanko
15898f44cb0SIgor Mitsyanko /* now we're not waiting for ACK even in case of timeout */
15998f44cb0SIgor Mitsyanko WRITE_ONCE(ipc->waiting_for_ack, 0);
16098f44cb0SIgor Mitsyanko
16198f44cb0SIgor Mitsyanko return ret;
16298f44cb0SIgor Mitsyanko }
163