// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2023, Intel Corporation * stmmac EST(802.3 Qbv) handling */ #include #include #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; 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++; /* 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, };