LF-11880-2 net: enetc: Add power off suspend/resume and WOL supports

In previous implementations, NETCMIX does not power off during suspend.
If NETCMIX powers off, all NETCMIX devices need to be reinitialized in the
resume process.
So, add this function support.

Support Wake-on-LAN functionality in suspend at the same time.
Need to execute the following command to set it as WOL mode.
 # ethtool -s ethX wol g

When using WOL mode, NETCMIX will not power off during suspend.

Reviewed-by: Claudiu Manoil <claudiu.manoil@nxp.com>
Reviewed-by: Wei Fang <wei.fang@nxp.com>
Signed-off-by: Clark Wang <xiaoning.wang@nxp.com>
This commit is contained in:
Clark Wang 2024-04-22 16:06:18 +08:00
parent d5163605a6
commit 438c5e9014
8 changed files with 542 additions and 19 deletions

View File

@ -3011,8 +3011,6 @@ void enetc_stop(struct net_device *ndev)
enetc_wait_bdrs(priv);
enetc_clear_interrupts(priv);
clk_disable_unprepare(priv->ref_clk);
}
EXPORT_SYMBOL_GPL(enetc_stop);
@ -3045,6 +3043,8 @@ int enetc_close(struct net_device *ndev)
enetc_free_irqs(priv);
clk_disable_unprepare(priv->ref_clk);
return 0;
}
EXPORT_SYMBOL_GPL(enetc_close);
@ -3110,6 +3110,79 @@ out:
return err;
}
int enetc_suspend(struct net_device *ndev, bool wol)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
enetc_stop(ndev);
enetc_free_rxtx_rings(priv);
/* Avoids dangling pointers and also frees old resources */
enetc_assign_rx_resources(priv, NULL);
enetc_assign_tx_resources(priv, NULL);
if (!wol) {
enetc_free_irqs(priv);
clk_disable_unprepare(priv->ref_clk);
}
return 0;
}
EXPORT_SYMBOL_GPL(enetc_suspend);
int enetc_resume(struct net_device *ndev, bool wol)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
struct enetc_bdr_resource *tx_res, *rx_res;
bool extended;
int err;
extended = !!(priv->active_offloads & ENETC_F_RX_TSTAMP);
if (!wol) {
err = clk_prepare_enable(priv->ref_clk);
if (err)
return err;
err = enetc_setup_irqs(priv);
if (err)
goto out_setup_irqs;
}
tx_res = enetc_alloc_tx_resources(priv);
if (IS_ERR(tx_res)) {
err = PTR_ERR(tx_res);
goto out_free_irqs;
}
rx_res = enetc_alloc_rx_resources(priv, extended);
if (IS_ERR(rx_res)) {
err = PTR_ERR(rx_res);
goto out_free_tx_res;
}
enetc_tx_onestep_tstamp_init(priv);
enetc_assign_tx_resources(priv, tx_res);
enetc_assign_rx_resources(priv, rx_res);
enetc_setup_bdrs(priv, extended);
enetc_start(priv->ndev);
return 0;
out_free_tx_res:
enetc_free_tx_resources(tx_res, priv->num_tx_rings);
out_free_irqs:
if (!wol)
enetc_free_irqs(priv);
out_setup_irqs:
if (!wol)
clk_disable_unprepare(priv->ref_clk);
return err;
}
EXPORT_SYMBOL_GPL(enetc_resume);
static void enetc_debug_tx_ring_prios(struct enetc_ndev_priv *priv)
{
int i;

View File

@ -467,6 +467,7 @@ struct enetc_ndev_priv {
struct device *dev; /* dma-mapping device */
struct enetc_si *si;
struct clk *ref_clk; /* RGMII/RMII reference clock */
struct pci_dev *rcec;
int bdr_int_num; /* number of Rx/Tx ring interrupts */
struct enetc_int_vector *int_vector[ENETC_MAX_BDR_INT];
@ -489,6 +490,7 @@ struct enetc_ndev_priv {
struct enetc_cls_rule *cls_rules;
int max_ipf_entries;
u32 ipt_wol_eid;
union psfp_cap psfp_cap;
struct enetc_psfp_chain psfp_chain;
@ -506,6 +508,7 @@ struct enetc_ndev_priv {
struct bpf_prog *xdp_prog;
unsigned long flags;
int wolopts;
struct work_struct tx_onestep_tstamp;
struct sk_buff_head tx_skbs;
@ -539,6 +542,8 @@ int enetc_alloc_si_resources(struct enetc_ndev_priv *priv);
void enetc_free_si_resources(struct enetc_ndev_priv *priv);
int enetc_configure_si(struct enetc_ndev_priv *priv);
int enetc_suspend(struct net_device *ndev, bool wol);
int enetc_resume(struct net_device *ndev, bool wol);
int enetc_open(struct net_device *ndev);
int enetc_close(struct net_device *ndev);
void enetc_start(struct net_device *ndev);

View File

@ -18,6 +18,9 @@ static void enetc4_get_port_caps(struct enetc_pf *pf)
struct enetc_hw *hw = &pf->si->hw;
u32 val;
val = enetc_port_rd(hw, ENETC4_ECAPR0);
pf->caps.wol = !!(val & ECAPR0_WO);
val = enetc_port_rd(hw, ENETC4_ECAPR1);
pf->caps.num_vsi = (val & ECAPR1_NUM_VSI) >> 24;
pf->caps.num_msix = ((val & ECAPR1_NUM_MSIX) >> 12) + 1;
@ -967,6 +970,9 @@ static int enetc4_pf_probe(struct pci_dev *pdev,
enetc_create_debugfs(si);
if (pdev->rcec)
priv->rcec = pdev->rcec;
return 0;
err_reg_netdev:
@ -1038,11 +1044,54 @@ static const struct pci_device_id enetc4_pf_id_table[] = {
};
MODULE_DEVICE_TABLE(pci, enetc4_pf_id_table);
#ifdef CONFIG_PCI_IOV
int enetc4_sriov_suspend_resume_configure(struct pci_dev *pdev, bool suspend)
{
struct enetc_si *si = pci_get_drvdata(pdev);
struct enetc_pf *pf = enetc_si_priv(si);
int err;
if (pf->num_vfs == 0)
return 0;
if (suspend) {
pci_disable_sriov(pdev);
enetc_msg_psi_free(pf);
} else {
err = enetc_msg_psi_init(pf);
if (err) {
dev_err(&pdev->dev, "enetc_msg_psi_init (%d)\n", err);
return err;
}
err = pci_enable_sriov(pdev, pf->num_vfs);
if (err) {
dev_err(&pdev->dev, "pci_enable_sriov err %d\n", err);
goto err_en_sriov;
}
}
return 0;
err_en_sriov:
enetc_msg_psi_free(pf);
return err;
}
#else
int enetc4_sriov_suspend_resume_configure(struct pci_dev *pdev, int num_vfs)
{
return 0;
}
#endif
static int __maybe_unused enetc4_pf_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct enetc_ndev_priv *priv;
struct enetc_si *si;
struct enetc_pf *pf;
u32 val;
if (enetc_pf_is_owned_by_mcore(pdev))
return 0;
@ -1051,10 +1100,34 @@ static int __maybe_unused enetc4_pf_suspend(struct device *dev)
if (!netif_running(si->ndev))
return 0;
pf = enetc_si_priv(si);
priv = netdev_priv(si->ndev);
if (netc_ierb_may_wakeonlan() == 0)
enetc4_sriov_suspend_resume_configure(pdev, true);
netif_device_detach(si->ndev);
rtnl_lock();
phylink_suspend(priv->phylink, false);
enetc_suspend(si->ndev, netc_ierb_may_wakeonlan() > 0);
if (netc_ierb_may_wakeonlan() > 0) {
pci_pme_active(pdev, true);
val = enetc_port_mac_rd(si, ENETC4_PM_CMD_CFG(0));
val |= PM_CMD_CFG_MG;
enetc_port_mac_wr(si, ENETC4_PM_CMD_CFG(0), val);
enetc_port_mac_wr(si, ENETC4_PLPMR, PLPMR_WME);
pci_save_state(pdev);
pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3hot);
phylink_suspend(priv->phylink, true);
} else {
phylink_suspend(priv->phylink, false);
enetc_free_msix(priv);
}
rtnl_unlock();
return 0;
@ -1062,9 +1135,13 @@ static int __maybe_unused enetc4_pf_suspend(struct device *dev)
static int __maybe_unused enetc4_pf_resume(struct device *dev)
{
struct device_node *node = dev->of_node;
struct pci_dev *pdev = to_pci_dev(dev);
struct enetc_ndev_priv *priv;
struct enetc_si *si;
struct enetc_pf *pf;
u32 val;
int err;
if (enetc_pf_is_owned_by_mcore(pdev))
return 0;
@ -1073,14 +1150,66 @@ static int __maybe_unused enetc4_pf_resume(struct device *dev)
if (!netif_running(si->ndev))
return 0;
pf = enetc_si_priv(si);
priv = netdev_priv(si->ndev);
rtnl_lock();
if (netc_ierb_may_wakeonlan() > 0) {
pci_set_power_state(pdev, PCI_D0);
err = pci_enable_device(pdev);
if (err)
return err;
pci_restore_state(pdev);
val = enetc_port_mac_rd(si, ENETC4_PM_CMD_CFG(0));
val &= ~PM_CMD_CFG_MG;
enetc_port_mac_wr(si, ENETC4_PM_CMD_CFG(0), val);
enetc_port_mac_wr(si, ENETC4_PLPMR, 0);
} else {
err = enetc_init_cbdr(si);
if (err)
goto err_init_cbdr;
err = enetc_setup_mac_addresses(node, pf);
if (err)
goto err_init_address;
enetc4_configure_port(pf);
err = enetc_configure_si(priv);
if (err) {
dev_err(dev, "Failed to configure SI\n");
goto err_config_si;
}
err = enetc_alloc_msix(priv);
if (err) {
dev_err(dev, "MSIX alloc failed\n");
goto err_alloc_msix;
}
}
phylink_resume(priv->phylink);
enetc_resume(si->ndev, netc_ierb_may_wakeonlan() > 0);
rtnl_unlock();
netif_device_attach(si->ndev);
if (netc_ierb_may_wakeonlan() == 0)
enetc4_sriov_suspend_resume_configure(pdev, false);
return 0;
err_alloc_msix:
err_config_si:
err_init_address:
enetc_free_cbdr(si);
err_init_cbdr:
clk_disable_unprepare(priv->ref_clk);
return err;
}
static SIMPLE_DEV_PM_OPS(enetc4_pf_pm_ops, enetc4_pf_suspend, enetc4_pf_resume);

View File

@ -2,10 +2,11 @@
/* Copyright 2017-2019 NXP */
#include <linux/ethtool_netlink.h>
#include <linux/fsl/netc_prb_ierb.h>
#include <linux/fsl/ptp_netc.h>
#include <linux/net_tstamp.h>
#include <linux/module.h>
#include "enetc.h"
#include "enetc_pf.h"
static const u32 enetc_si_regs[] = {
ENETC_SIMR, ENETC_SIPMAR0, ENETC_SIPMAR1, ENETC_SICBDRMR,
@ -825,6 +826,38 @@ static int enetc_get_rxnfc(struct net_device *ndev, struct ethtool_rxnfc *rxnfc,
return 0;
}
static int enetc4_set_wol_mg_ipft_entry(struct enetc_ndev_priv *priv)
{
struct enetc_si *si = priv->si;
struct ntmp_ipft_key *key __free(kfree);
struct ntmp_ipft_cfg cfg;
u32 val;
int err;
key = kzalloc(sizeof(*key), GFP_KERNEL);
if (!key)
return -ENOMEM;
memset(&cfg, 0, sizeof(cfg));
key->frm_attr_flags = NTMP_IPFT_FAF_WOL_MAGIC;
key->frm_attr_flags_mask = key->frm_attr_flags;
cfg.filter = BIT(0) | BIT(4) | (NTMP_IPFT_FLTA_SI_BITMAP << 5);
cfg.flta_tgt = 1;
err = ntmp_ipft_add_entry(&si->cbdr, key, &cfg, &priv->ipt_wol_eid);
if (err)
return err;
val = enetc_port_rd(&si->hw, ENETC4_PIPFCR);
if (!(val & PIPFCR_EN))
/* Enable ingress port filter table lookup. */
enetc_port_wr(&si->hw, ENETC4_PIPFCR, PIPFCR_EN);
return 0;
}
static int enetc4_set_ipft_entry(struct enetc_si *si, struct ethtool_rx_flow_spec *fs,
u32 *entry_id)
{
@ -1437,26 +1470,79 @@ static int enetc_get_ts_info(struct net_device *ndev,
static void enetc_get_wol(struct net_device *dev,
struct ethtool_wolinfo *wol)
{
struct enetc_ndev_priv *priv = netdev_priv(dev);
struct enetc_si *si = priv->si;
struct enetc_pf *pf;
wol->supported = 0;
wol->wolopts = 0;
pf = enetc_si_priv(si);
if (dev->phydev)
phy_ethtool_get_wol(dev->phydev, wol);
if (pf->caps.wol) {
if (device_can_wakeup(priv->dev)) {
wol->supported = WAKE_MAGIC;
wol->wolopts = priv->wolopts;
}
} else {
if (dev->phydev)
phy_ethtool_get_wol(dev->phydev, wol);
}
}
static int enetc_set_wol(struct net_device *dev,
struct ethtool_wolinfo *wol)
{
int ret;
struct enetc_ndev_priv *priv = netdev_priv(dev);
struct enetc_si *si = priv->si;
u32 support = WAKE_MAGIC;
struct enetc_pf *pf;
int err;
if (!dev->phydev)
return -EOPNOTSUPP;
pf = enetc_si_priv(si);
ret = phy_ethtool_set_wol(dev->phydev, wol);
if (!ret)
device_set_wakeup_enable(&dev->dev, wol->wolopts);
if (pf->caps.wol) {
if (!device_can_wakeup(priv->dev) || wol->wolopts & ~support)
return -EOPNOTSUPP;
return ret;
if (wol->wolopts == priv->wolopts)
return 0;
if (wol->wolopts) {
err = enetc4_set_wol_mg_ipft_entry(priv);
if (err)
return err;
if (priv->rcec && netc_ierb_may_wakeonlan() == 0) {
priv->rcec->dev_flags |= PCI_DEV_FLAGS_NO_D3;
device_set_wakeup_enable(&priv->rcec->dev, 1);
}
netc_ierb_enable_wakeonlan();
netdev_info(dev, "enetc: wakeup enable\n");
} else {
netc_ierb_disable_wakeonlan();
if (priv->rcec && netc_ierb_may_wakeonlan() == 0) {
device_set_wakeup_enable(&priv->rcec->dev, 0);
priv->rcec->dev_flags &= ~PCI_DEV_FLAGS_NO_D3;
}
err = ntmp_ipft_delete_entry(&priv->si->cbdr,
priv->ipt_wol_eid);
if (err)
return err;
netdev_info(dev, "enetc: wakeup disable\n");
}
priv->wolopts = wol->wolopts;
} else {
if (!dev->phydev)
return -EOPNOTSUPP;
err = phy_ethtool_set_wol(dev->phydev, wol);
if (!err) {
device_set_wakeup_enable(&dev->dev, wol->wolopts);
return err;
}
}
return 0;
}
static void enetc_get_pauseparam(struct net_device *dev,

View File

@ -22,6 +22,7 @@ struct enetc_vf_state {
struct enetc_port_caps {
bool half_duplex;
bool wol;
int num_vsi;
int num_msix;
int num_rx_bdr;

View File

@ -84,6 +84,7 @@ struct netc_prb_ierb {
struct clk *ipg_clk;
bool ierb_init;
atomic_t wakeonlan_count;
struct regmap *netcmix;
struct platform_device *pdev;
struct dentry *debugfs_root;
@ -210,6 +211,7 @@ static int netc_ierb_init(struct platform_device *pdev)
}
pi->ierb_init = true;
atomic_set(&pi->wakeonlan_count, 0);
return 0;
}
@ -256,6 +258,43 @@ u64 netc_ierb_get_clk_config(void)
}
EXPORT_SYMBOL_GPL(netc_ierb_get_clk_config);
void netc_ierb_enable_wakeonlan(void)
{
struct netc_prb_ierb *pi = netc_pi;
if (!pi)
return;
atomic_inc(&pi->wakeonlan_count);
}
EXPORT_SYMBOL_GPL(netc_ierb_enable_wakeonlan);
void netc_ierb_disable_wakeonlan(void)
{
struct netc_prb_ierb *pi = netc_pi;
if (!pi)
return;
atomic_dec(&pi->wakeonlan_count);
if (atomic_read(&pi->wakeonlan_count) < 0) {
atomic_set(&pi->wakeonlan_count, 0);
dev_warn(&pi->pdev->dev, "Wake-on-LAN count underflow.\n");
}
}
EXPORT_SYMBOL_GPL(netc_ierb_disable_wakeonlan);
int netc_ierb_may_wakeonlan(void)
{
struct netc_prb_ierb *pi = netc_pi;
if (!pi)
return -ENXIO;
return atomic_read(&pi->wakeonlan_count);
}
EXPORT_SYMBOL_GPL(netc_ierb_may_wakeonlan);
#if IS_ENABLED(CONFIG_DEBUG_FS)
static int netc_prb_show(struct seq_file *s, void *data)
{
@ -540,6 +579,60 @@ static const struct of_device_id netc_prb_ierb_match[] = {
};
MODULE_DEVICE_TABLE(of, netc_prb_ierb_match);
static int netc_prb_ierb_suspend_noirq(struct device *dev)
{
struct netc_prb_ierb *pi = netc_pi;
if (netc_ierb_may_wakeonlan())
return 0;
pi->ierb_init = false;
clk_disable_unprepare(pi->ipg_clk);
return 0;
}
static int netc_prb_ierb_resume_noirq(struct device *dev)
{
struct netc_prb_ierb *pi = netc_pi;
struct platform_device *pdev = pi->pdev;
int err;
if (netc_ierb_may_wakeonlan())
return 0;
err = clk_prepare_enable(pi->ipg_clk);
if (err) {
dev_err(dev, "Enable ipg_clk failed\n");
return err;
}
netc_netcmix_init(pdev);
err = netc_ierb_init(pdev);
if (err) {
dev_err(&pdev->dev, "Initializing IERB failed.\n");
goto disable_ipg_clk;
}
if (netc_prb_check_error(pi) < 0)
dev_warn(&pdev->dev,
"The current IERB configuration is invalid.\n");
return 0;
disable_ipg_clk:
clk_disable_unprepare(pi->ipg_clk);
return err;
}
static const struct dev_pm_ops __maybe_unused netc_prb_ierb_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(netc_prb_ierb_suspend_noirq,
netc_prb_ierb_resume_noirq)
};
static struct platform_driver netc_prb_ierb_driver = {
.driver = {
.name = "fsl-netc-prb-ierb",
@ -547,6 +640,7 @@ static struct platform_driver netc_prb_ierb_driver = {
},
.probe = netc_prb_ierb_probe,
.remove = netc_prb_ierb_remove,
.driver.pm = &netc_prb_ierb_pm_ops,
};
module_platform_driver(netc_prb_ierb_driver);

View File

@ -573,12 +573,6 @@ static int netc_timer_init(struct netc_timer *priv)
spin_unlock_irqrestore(&priv->lock, flags);
priv->clock = ptp_clock_register(&priv->caps, priv->dev);
if (IS_ERR(priv->clock))
return PTR_ERR(priv->clock);
priv->phc_index = ptp_clock_index(priv->clock);
return 0;
}
@ -670,10 +664,21 @@ static int netc_timer_probe(struct pci_dev *pdev,
dev_err(dev, "NETC Timer initialization failed\n");
goto free_irq;
}
priv->clock = ptp_clock_register(&priv->caps, priv->dev);
if (IS_ERR(priv->clock)) {
err = PTR_ERR(priv->clock);
goto deinit_timer;
}
priv->phc_index = ptp_clock_index(priv->clock);
pci_set_drvdata(pdev, priv);
return 0;
deinit_timer:
netc_timer_deinit(priv);
free_irq:
free_irq(priv->irq, priv);
free_irq_vectors:
@ -713,11 +718,125 @@ static const struct pci_device_id netc_timer_id_table[] = {
};
MODULE_DEVICE_TABLE(pci, netc_timer_id_table);
static int ptp_netc_shutdown(struct netc_timer *priv)
{
struct pci_dev *pdev = priv->pci_dev;
netc_timer_deinit(priv);
disable_irq(priv->irq);
irq_set_affinity_hint(priv->irq, NULL);
free_irq(priv->irq, priv);
pci_free_irq_vectors(pdev);
pci_save_state(pdev);
pci_disable_device(priv->pci_dev);
return 0;
}
static int ptp_netc_powerup(struct netc_timer *priv)
{
struct pci_dev *pdev = priv->pci_dev;
int err, n;
err = netc_ierb_get_init_status();
if (err) {
dev_err(&pdev->dev, "Can't get IERB init status: %d\n", err);
return err;
}
err = pci_enable_device_mem(pdev);
if (err) {
dev_err(&pdev->dev, "device enable failed\n");
return err;
}
pci_restore_state(pdev);
pci_set_master(pdev);
priv->phc_index = -1;
n = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX);
if (n != 1) {
err = -EPERM;
goto disable_dev;
}
priv->irq = pci_irq_vector(pdev, 0);
err = request_irq(priv->irq, netc_timer_isr, 0, priv->irq_name, priv);
if (err) {
dev_err(&pdev->dev, "request_irq() failed!\n");
goto free_irq_vectors;
}
err = netc_timer_init(priv);
if (err) {
dev_err(&pdev->dev, "NETC Timer initialization failed, err=%d\n", err);
goto free_irq;
}
return 0;
free_irq:
free_irq(priv->irq, priv);
free_irq_vectors:
pci_free_irq_vectors(pdev);
disable_dev:
pci_disable_device(pdev);
return err;
}
static int ptp_netc_suspend_noirq(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct netc_timer *priv;
int err;
priv = pci_get_drvdata(pdev);
if (netc_ierb_may_wakeonlan())
return 0;
err = ptp_netc_shutdown(priv);
if (err) {
dev_err(dev, "NETC Timer shutdown failed\n");
return err;
}
return err;
}
static int ptp_netc_resume_noirq(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct netc_timer *priv;
int err;
priv = pci_get_drvdata(pdev);
if (netc_ierb_may_wakeonlan())
return 0;
err = ptp_netc_powerup(priv);
if (err) {
dev_err(dev, "NETC Timer powerup failed\n");
kfree(priv);
return err;
}
return err;
}
static const struct dev_pm_ops __maybe_unused ptp_netc_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(ptp_netc_suspend_noirq,
ptp_netc_resume_noirq)
};
static struct pci_driver netc_timer_driver = {
.name = KBUILD_MODNAME,
.id_table = netc_timer_id_table,
.probe = netc_timer_probe,
.remove = netc_timer_remove,
.driver.pm = &ptp_netc_pm_ops,
};
module_pci_driver(netc_timer_driver);

View File

@ -13,6 +13,9 @@
int netc_ierb_get_init_status(void);
u64 netc_ierb_get_clk_config(void);
void netc_ierb_enable_wakeonlan(void);
void netc_ierb_disable_wakeonlan(void);
int netc_ierb_may_wakeonlan(void);
#else
@ -26,4 +29,17 @@ static inline u64 netc_ierb_get_clk_config(void)
return 0;
}
static inline void netc_ierb_enable_wakeonlan(void)
{
}
static inline void netc_ierb_disable_wakeonlan(void)
{
}
static inline int netc_ierb_may_wakeonlan(void)
{
return -EINVAL;
}
#endif