| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2023, Intel Corporation |
| * stmmac EST(802.3 Qbv) handling |
| */ |
| #include <linux/iopoll.h> |
| #include <linux/types.h> |
| #include "stmmac.h" |
| #include "stmmac_est.h" |
| |
| static int est_write(void __iomem *est_addr, u32 reg, u32 val, bool gcl) |
| { |
| u32 ctrl; |
| |
| writel(val, est_addr + EST_GCL_DATA); |
| |
| ctrl = (reg << EST_ADDR_SHIFT); |
| ctrl |= gcl ? 0 : EST_GCRR; |
| writel(ctrl, est_addr + EST_GCL_CONTROL); |
| |
| ctrl |= EST_SRWO; |
| writel(ctrl, est_addr + EST_GCL_CONTROL); |
| |
| return readl_poll_timeout(est_addr + EST_GCL_CONTROL, ctrl, |
| !(ctrl & EST_SRWO), 100, 5000); |
| } |
| |
| static int est_configure(struct stmmac_priv *priv, struct stmmac_est *cfg, |
| unsigned int ptp_rate) |
| { |
| void __iomem *est_addr = priv->estaddr; |
| int i, ret = 0; |
| u32 ctrl; |
| |
| ret |= est_write(est_addr, EST_BTR_LOW, cfg->btr[0], false); |
| ret |= est_write(est_addr, EST_BTR_HIGH, cfg->btr[1], false); |
| ret |= est_write(est_addr, EST_TER, cfg->ter, false); |
| ret |= est_write(est_addr, EST_LLR, cfg->gcl_size, false); |
| ret |= est_write(est_addr, EST_CTR_LOW, cfg->ctr[0], false); |
| ret |= est_write(est_addr, EST_CTR_HIGH, cfg->ctr[1], false); |
| if (ret) |
| return ret; |
| |
| for (i = 0; i < cfg->gcl_size; i++) { |
| ret = est_write(est_addr, i, cfg->gcl[i], true); |
| if (ret) |
| return ret; |
| } |
| |
| ctrl = readl(est_addr + EST_CONTROL); |
| if (priv->plat->has_xgmac) { |
| ctrl &= ~EST_XGMAC_PTOV; |
| ctrl |= ((NSEC_PER_SEC / ptp_rate) * EST_XGMAC_PTOV_MUL) << |
| EST_XGMAC_PTOV_SHIFT; |
| } else { |
| ctrl &= ~EST_GMAC5_PTOV; |
| ctrl |= ((NSEC_PER_SEC / ptp_rate) * EST_GMAC5_PTOV_MUL) << |
| EST_GMAC5_PTOV_SHIFT; |
| } |
| if (cfg->enable) |
| ctrl |= EST_EEST | EST_SSWL; |
| else |
| ctrl &= ~EST_EEST; |
| |
| writel(ctrl, est_addr + EST_CONTROL); |
| |
| /* Configure EST interrupt */ |
| if (cfg->enable) |
| ctrl = EST_IECGCE | EST_IEHS | EST_IEHF | EST_IEBE | EST_IECC; |
| else |
| ctrl = 0; |
| |
| writel(ctrl, est_addr + EST_INT_EN); |
| |
| return 0; |
| } |
| |
| static void est_irq_status(struct stmmac_priv *priv, struct net_device *dev, |
| struct stmmac_extra_stats *x, u32 txqcnt) |
| { |
| u32 status, value, feqn, hbfq, hbfs, btrl, btrl_max; |
| void __iomem *est_addr = priv->estaddr; |
| u32 txqcnt_mask = BIT(txqcnt) - 1; |
| int i; |
| |
| status = readl(est_addr + EST_STATUS); |
| |
| value = EST_CGCE | EST_HLBS | EST_HLBF | EST_BTRE | EST_SWLC; |
| |
| /* Return if there is no error */ |
| if (!(status & value)) |
| return; |
| |
| if (status & EST_CGCE) { |
| /* Clear Interrupt */ |
| writel(EST_CGCE, est_addr + EST_STATUS); |
| |
| x->mtl_est_cgce++; |
| } |
| |
| if (status & EST_HLBS) { |
| value = readl(est_addr + EST_SCH_ERR); |
| value &= txqcnt_mask; |
| |
| x->mtl_est_hlbs++; |
| |
| /* Clear Interrupt */ |
| writel(value, est_addr + EST_SCH_ERR); |
| |
| /* Collecting info to shows all the queues that has HLBS |
| * issue. The only way to clear this is to clear the |
| * statistic |
| */ |
| if (net_ratelimit()) |
| netdev_err(dev, "EST: HLB(sched) Queue 0x%x\n", value); |
| } |
| |
| if (status & EST_HLBF) { |
| value = readl(est_addr + EST_FRM_SZ_ERR); |
| feqn = value & txqcnt_mask; |
| |
| value = readl(est_addr + EST_FRM_SZ_CAP); |
| hbfq = (value & EST_SZ_CAP_HBFQ_MASK(txqcnt)) >> |
| EST_SZ_CAP_HBFQ_SHIFT; |
| hbfs = value & EST_SZ_CAP_HBFS_MASK; |
| |
| x->mtl_est_hlbf++; |
| |
| for (i = 0; i < txqcnt; i++) { |
| if (feqn & BIT(i)) |
| x->mtl_est_txq_hlbf[i]++; |
| } |
| |
| /* Clear Interrupt */ |
| writel(feqn, est_addr + EST_FRM_SZ_ERR); |
| |
| if (net_ratelimit()) |
| netdev_err(dev, "EST: HLB(size) Queue %u Size %u\n", |
| hbfq, hbfs); |
| } |
| |
| if (status & EST_BTRE) { |
| if (priv->plat->has_xgmac) { |
| btrl = FIELD_GET(EST_XGMAC_BTRL, status); |
| btrl_max = FIELD_MAX(EST_XGMAC_BTRL); |
| } else { |
| btrl = FIELD_GET(EST_GMAC5_BTRL, status); |
| btrl_max = FIELD_MAX(EST_GMAC5_BTRL); |
| } |
| if (btrl == btrl_max) |
| x->mtl_est_btrlm++; |
| else |
| x->mtl_est_btre++; |
| |
| if (net_ratelimit()) |
| netdev_info(dev, "EST: BTR Error Loop Count %u\n", |
| btrl); |
| |
| writel(EST_BTRE, est_addr + EST_STATUS); |
| } |
| |
| if (status & EST_SWLC) { |
| writel(EST_SWLC, est_addr + EST_STATUS); |
| netdev_info(dev, "EST: SWOL has been switched\n"); |
| } |
| } |
| |
| const struct stmmac_est_ops dwmac510_est_ops = { |
| .configure = est_configure, |
| .irq_status = est_irq_status, |
| }; |