| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2018 Microsemi Corporation |
| */ |
| |
| #include <log.h> |
| #include <linux/bitops.h> |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include "mscc_xfer.h" |
| |
| #define QS_XTR_FLUSH_FLUSH GENMASK(1, 0) |
| #define QS_INJ_CTRL_GAP_SIZE(x) ((x) << 21) |
| #define QS_INJ_CTRL_EOF BIT(19) |
| #define QS_INJ_CTRL_SOF BIT(18) |
| #define QS_INJ_CTRL_VLD_BYTES(x) ((x) << 16) |
| |
| #define XTR_EOF_0 ntohl(0x80000000u) |
| #define XTR_EOF_1 ntohl(0x80000001u) |
| #define XTR_EOF_2 ntohl(0x80000002u) |
| #define XTR_EOF_3 ntohl(0x80000003u) |
| #define XTR_PRUNED ntohl(0x80000004u) |
| #define XTR_ABORT ntohl(0x80000005u) |
| #define XTR_ESCAPE ntohl(0x80000006u) |
| #define XTR_NOT_READY ntohl(0x80000007u) |
| |
| #define BUF_CELL_SZ 60 |
| #define XTR_VALID_BYTES(x) (4 - ((x) & 3)) |
| |
| int mscc_send(void __iomem *regs, const unsigned long *mscc_qs_offset, |
| u32 *ifh, size_t ifh_len, u32 *buff, size_t buff_len) |
| { |
| int i, count = (buff_len + 3) / 4, last = buff_len % 4; |
| |
| writel(QS_INJ_CTRL_GAP_SIZE(1) | QS_INJ_CTRL_SOF, |
| regs + mscc_qs_offset[MSCC_QS_INJ_CTRL]); |
| |
| for (i = 0; i < ifh_len; i++) |
| writel(ifh[i], regs + mscc_qs_offset[MSCC_QS_INJ_WR]); |
| |
| for (i = 0; i < count; i++) |
| writel(buff[i], regs + mscc_qs_offset[MSCC_QS_INJ_WR]); |
| |
| /* Add padding */ |
| while (i < (BUF_CELL_SZ / 4)) { |
| writel(0, regs + mscc_qs_offset[MSCC_QS_INJ_WR]); |
| i++; |
| } |
| |
| /* Indicate EOF and valid bytes in last word */ |
| writel(QS_INJ_CTRL_GAP_SIZE(1) | |
| QS_INJ_CTRL_VLD_BYTES(buff_len < BUF_CELL_SZ ? 0 : last) | |
| QS_INJ_CTRL_EOF, regs + mscc_qs_offset[MSCC_QS_INJ_CTRL]); |
| |
| /* Add dummy CRC */ |
| writel(0, regs + mscc_qs_offset[MSCC_QS_INJ_WR]); |
| |
| return 0; |
| } |
| |
| int mscc_recv(void __iomem *regs, const unsigned long *mscc_qs_offset, |
| u32 *rxbuf, size_t ifh_len, bool byte_swap) |
| { |
| u8 grp = 0; /* Recv everything on CPU group 0 */ |
| int i, byte_cnt = 0; |
| bool eof_flag = false, pruned_flag = false, abort_flag = false; |
| |
| if (!(readl(regs + mscc_qs_offset[MSCC_QS_XTR_DATA_PRESENT]) & |
| BIT(grp))) |
| return -EAGAIN; |
| |
| /* skip IFH */ |
| for (i = 0; i < ifh_len; i++) |
| readl(regs + mscc_qs_offset[MSCC_QS_XTR_RD]); |
| |
| while (!eof_flag) { |
| u32 val = readl(regs + mscc_qs_offset[MSCC_QS_XTR_RD]); |
| u32 cmp = val; |
| |
| if (byte_swap) |
| cmp = ntohl(val); |
| |
| switch (cmp) { |
| case XTR_NOT_READY: |
| debug("%d NOT_READY...?\n", byte_cnt); |
| break; |
| case XTR_ABORT: |
| *rxbuf = readl(regs + mscc_qs_offset[MSCC_QS_XTR_RD]); |
| abort_flag = true; |
| eof_flag = true; |
| debug("XTR_ABORT\n"); |
| break; |
| case XTR_EOF_0: |
| case XTR_EOF_1: |
| case XTR_EOF_2: |
| case XTR_EOF_3: |
| byte_cnt += XTR_VALID_BYTES(val); |
| *rxbuf = readl(regs + mscc_qs_offset[MSCC_QS_XTR_RD]); |
| eof_flag = true; |
| debug("EOF\n"); |
| break; |
| case XTR_PRUNED: |
| /* But get the last 4 bytes as well */ |
| eof_flag = true; |
| pruned_flag = true; |
| debug("PRUNED\n"); |
| /* fallthrough */ |
| case XTR_ESCAPE: |
| *rxbuf = readl(regs + mscc_qs_offset[MSCC_QS_XTR_RD]); |
| byte_cnt += 4; |
| rxbuf++; |
| debug("ESCAPED\n"); |
| break; |
| default: |
| *rxbuf = val; |
| byte_cnt += 4; |
| rxbuf++; |
| } |
| } |
| |
| if (abort_flag || pruned_flag || !eof_flag) { |
| debug("Discarded frame: abort:%d pruned:%d eof:%d\n", |
| abort_flag, pruned_flag, eof_flag); |
| return -EAGAIN; |
| } |
| |
| return byte_cnt; |
| } |
| |
| void mscc_flush(void __iomem *regs, const unsigned long *mscc_qs_offset) |
| { |
| /* All Queues flush */ |
| setbits_le32(regs + mscc_qs_offset[MSCC_QS_XTR_FLUSH], |
| QS_XTR_FLUSH_FLUSH); |
| |
| /* Allow to drain */ |
| mdelay(1); |
| |
| /* All Queues normal */ |
| clrbits_le32(regs + mscc_qs_offset[MSCC_QS_XTR_FLUSH], |
| QS_XTR_FLUSH_FLUSH); |
| } |