Commit 6246168b authored by WingMan Kwok's avatar WingMan Kwok Committed by David S. Miller
Browse files

net: ethernet: ti: netcp: add support of cpts



This patch adds support of the cpts device found in the
gbe and 10gbe ethernet switches on the keystone 2 SoCs
(66AK2E/L/Hx, 66AK2Gx).

Cc: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: default avatarWingMan Kwok <w-kwok2@ti.com>
Signed-off-by: default avatarGrygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 529ed127
......@@ -75,12 +75,13 @@ config TI_CPSW
config TI_CPTS
tristate "TI Common Platform Time Sync (CPTS) Support"
depends on TI_CPSW
depends on TI_CPSW || TI_KEYSTONE_NETCP
select PTP_1588_CLOCK
---help---
This driver supports the Common Platform Time Sync unit of
the CPSW Ethernet Switch. The unit can time stamp PTP UDP/IPv4
and Layer 2 packets, and the driver offers a PTP Hardware Clock.
the CPSW Ethernet Switch and Keystone 2 1g/10g Switch Subsystem.
The unit can time stamp PTP UDP/IPv4 and Layer 2 packets, and the
driver offers a PTP Hardware Clock.
config TI_KEYSTONE_NETCP
tristate "TI Keystone NETCP Core Support"
......
......@@ -121,7 +121,7 @@ struct netcp_packet {
bool rxtstamp_complete;
void *ts_context;
int (*txtstamp_complete)(void *ctx, struct netcp_packet *pkt);
void (*txtstamp)(void *ctx, struct sk_buff *skb);
};
static inline u32 *netcp_push_psdata(struct netcp_packet *p_info,
......
......@@ -100,6 +100,11 @@ struct netcp_intf_modpriv {
void *module_priv;
};
struct netcp_tx_cb {
void *ts_context;
void (*txtstamp)(void *context, struct sk_buff *skb);
};
static LIST_HEAD(netcp_devices);
static LIST_HEAD(netcp_modules);
static DEFINE_MUTEX(netcp_modules_lock);
......@@ -544,6 +549,7 @@ int netcp_register_rxhook(struct netcp_intf *netcp_priv, int order,
return 0;
}
EXPORT_SYMBOL_GPL(netcp_register_rxhook);
int netcp_unregister_rxhook(struct netcp_intf *netcp_priv, int order,
netcp_hook_rtn *hook_rtn, void *hook_data)
......@@ -566,6 +572,7 @@ int netcp_unregister_rxhook(struct netcp_intf *netcp_priv, int order,
return -ENOENT;
}
EXPORT_SYMBOL_GPL(netcp_unregister_rxhook);
static void netcp_frag_free(bool is_frag, void *ptr)
{
......@@ -730,6 +737,7 @@ static int netcp_process_one_rx_packet(struct netcp_intf *netcp)
/* Call each of the RX hooks */
p_info.skb = skb;
skb->dev = netcp->ndev;
p_info.rxtstamp_complete = false;
list_for_each_entry(rx_hook, &netcp->rxhook_list_head, list) {
int ret;
......@@ -987,6 +995,7 @@ static int netcp_process_tx_compl_packets(struct netcp_intf *netcp,
unsigned int budget)
{
struct knav_dma_desc *desc;
struct netcp_tx_cb *tx_cb;
struct sk_buff *skb;
unsigned int dma_sz;
dma_addr_t dma;
......@@ -1014,6 +1023,10 @@ static int netcp_process_tx_compl_packets(struct netcp_intf *netcp,
continue;
}
tx_cb = (struct netcp_tx_cb *)skb->cb;
if (tx_cb->txtstamp)
tx_cb->txtstamp(tx_cb->ts_context, skb);
if (netif_subqueue_stopped(netcp->ndev, skb) &&
netif_running(netcp->ndev) &&
(knav_pool_count(netcp->tx_pool) >
......@@ -1154,6 +1167,7 @@ static int netcp_tx_submit_skb(struct netcp_intf *netcp,
struct netcp_tx_pipe *tx_pipe = NULL;
struct netcp_hook_list *tx_hook;
struct netcp_packet p_info;
struct netcp_tx_cb *tx_cb;
unsigned int dma_sz;
dma_addr_t dma;
u32 tmp = 0;
......@@ -1164,7 +1178,7 @@ static int netcp_tx_submit_skb(struct netcp_intf *netcp,
p_info.tx_pipe = NULL;
p_info.psdata_len = 0;
p_info.ts_context = NULL;
p_info.txtstamp_complete = NULL;
p_info.txtstamp = NULL;
p_info.epib = desc->epib;
p_info.psdata = (u32 __force *)desc->psdata;
memset(p_info.epib, 0, KNAV_DMA_NUM_EPIB_WORDS * sizeof(__le32));
......@@ -1189,6 +1203,10 @@ static int netcp_tx_submit_skb(struct netcp_intf *netcp,
goto out;
}
tx_cb = (struct netcp_tx_cb *)skb->cb;
tx_cb->ts_context = p_info.ts_context;
tx_cb->txtstamp = p_info.txtstamp;
/* update descriptor */
if (p_info.psdata_len) {
/* psdata points to both native-endian and device-endian data */
......
......@@ -23,10 +23,13 @@
#include <linux/of_mdio.h>
#include <linux/of_address.h>
#include <linux/if_vlan.h>
#include <linux/ptp_classify.h>
#include <linux/net_tstamp.h>
#include <linux/ethtool.h>
#include "cpsw_ale.h"
#include "netcp.h"
#include "cpts.h"
#define NETCP_DRIVER_NAME "TI KeyStone Ethernet Driver"
#define NETCP_DRIVER_VERSION "v1.0"
......@@ -51,6 +54,7 @@
#define GBE13_EMAC_OFFSET 0x100
#define GBE13_SLAVE_PORT2_OFFSET 0x200
#define GBE13_HW_STATS_OFFSET 0x300
#define GBE13_CPTS_OFFSET 0x500
#define GBE13_ALE_OFFSET 0x600
#define GBE13_HOST_PORT_NUM 0
#define GBE13_NUM_ALE_ENTRIES 1024
......@@ -74,6 +78,7 @@
#define GBENU_SLAVE_PORT_OFFSET 0x2000
#define GBENU_EMAC_OFFSET 0x2330
#define GBENU_HW_STATS_OFFSET 0x1a000
#define GBENU_CPTS_OFFSET 0x1d000
#define GBENU_ALE_OFFSET 0x1e000
#define GBENU_HOST_PORT_NUM 0
#define GBENU_NUM_ALE_ENTRIES 1024
......@@ -93,6 +98,7 @@
#define XGBE10_HOST_PORT_OFFSET 0x34
#define XGBE10_SLAVE_PORT_OFFSET 0x64
#define XGBE10_EMAC_OFFSET 0x400
#define XGBE10_CPTS_OFFSET 0x600
#define XGBE10_ALE_OFFSET 0x700
#define XGBE10_HW_STATS_OFFSET 0x800
#define XGBE10_HOST_PORT_NUM 0
......@@ -155,6 +161,7 @@
#define GBE_TX_QUEUE 648
#define GBE_TXHOOK_ORDER 0
#define GBE_RXHOOK_ORDER 0
#define GBE_DEFAULT_ALE_AGEOUT 30
#define SLAVE_LINK_IS_XGMII(s) ((s)->link_interface >= XGMII_LINK_MAC_PHY)
#define NETCP_LINK_STATE_INVALID -1
......@@ -169,6 +176,56 @@
#define HOST_TX_PRI_MAP_DEFAULT 0x00000000
#if IS_ENABLED(CONFIG_TI_CPTS)
/* Px_TS_CTL register fields */
#define TS_RX_ANX_F_EN BIT(0)
#define TS_RX_VLAN_LT1_EN BIT(1)
#define TS_RX_VLAN_LT2_EN BIT(2)
#define TS_RX_ANX_D_EN BIT(3)
#define TS_TX_ANX_F_EN BIT(4)
#define TS_TX_VLAN_LT1_EN BIT(5)
#define TS_TX_VLAN_LT2_EN BIT(6)
#define TS_TX_ANX_D_EN BIT(7)
#define TS_LT2_EN BIT(8)
#define TS_RX_ANX_E_EN BIT(9)
#define TS_TX_ANX_E_EN BIT(10)
#define TS_MSG_TYPE_EN_SHIFT 16
#define TS_MSG_TYPE_EN_MASK 0xffff
/* Px_TS_SEQ_LTYPE register fields */
#define TS_SEQ_ID_OFS_SHIFT 16
#define TS_SEQ_ID_OFS_MASK 0x3f
/* Px_TS_CTL_LTYPE2 register fields */
#define TS_107 BIT(16)
#define TS_129 BIT(17)
#define TS_130 BIT(18)
#define TS_131 BIT(19)
#define TS_132 BIT(20)
#define TS_319 BIT(21)
#define TS_320 BIT(22)
#define TS_TTL_NONZERO BIT(23)
#define TS_UNI_EN BIT(24)
#define TS_UNI_EN_SHIFT 24
#define TS_TX_ANX_ALL_EN \
(TS_TX_ANX_D_EN | TS_TX_ANX_E_EN | TS_TX_ANX_F_EN)
#define TS_RX_ANX_ALL_EN \
(TS_RX_ANX_D_EN | TS_RX_ANX_E_EN | TS_RX_ANX_F_EN)
#define TS_CTL_DST_PORT TS_319
#define TS_CTL_DST_PORT_SHIFT 21
#define TS_CTL_MADDR_ALL \
(TS_107 | TS_129 | TS_130 | TS_131 | TS_132)
#define TS_CTL_MADDR_SHIFT 16
/* The PTP event messages - Sync, Delay_Req, Pdelay_Req, and Pdelay_Resp. */
#define EVENT_MSG_BITS (BIT(0) | BIT(1) | BIT(2) | BIT(3))
#endif /* CONFIG_TI_CPTS */
struct xgbe_ss_regs {
u32 id_ver;
u32 synce_count;
......@@ -616,6 +673,13 @@ struct gbe_hw_stats {
#define GBE_MAX_HW_STAT_MODS 9
#define GBE_HW_STATS_REG_MAP_SZ 0x100
struct ts_ctl {
int uni;
u8 dst_port_map;
u8 maddr_map;
u8 ts_mcast_type;
};
struct gbe_slave {
void __iomem *port_regs;
void __iomem *emac_regs;
......@@ -630,6 +694,7 @@ struct gbe_slave {
u32 mac_control;
u8 phy_port_t;
struct device_node *phy_node;
struct ts_ctl ts_ctl;
struct list_head slave_list;
};
......@@ -655,6 +720,7 @@ struct gbe_priv {
void __iomem *switch_regs;
void __iomem *host_port_regs;
void __iomem *ale_reg;
void __iomem *cpts_reg;
void __iomem *sgmii_port_regs;
void __iomem *sgmii_port34_regs;
void __iomem *xgbe_serdes_regs;
......@@ -678,6 +744,9 @@ struct gbe_priv {
int num_et_stats;
/* Lock for updating the hwstats */
spinlock_t hw_stats_lock;
int cpts_registered;
struct cpts *cpts;
};
struct gbe_intf {
......@@ -1912,6 +1981,49 @@ static int keystone_set_link_ksettings(struct net_device *ndev,
return phy_ethtool_ksettings_set(phy, cmd);
}
#if IS_ENABLED(CONFIG_TI_CPTS)
static int keystone_get_ts_info(struct net_device *ndev,
struct ethtool_ts_info *info)
{
struct netcp_intf *netcp = netdev_priv(ndev);
struct gbe_intf *gbe_intf;
gbe_intf = netcp_module_get_intf_data(&gbe_module, netcp);
if (!gbe_intf || !gbe_intf->gbe_dev->cpts)
return -EINVAL;
info->so_timestamping =
SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RX_SOFTWARE |
SOF_TIMESTAMPING_SOFTWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
info->phc_index = gbe_intf->gbe_dev->cpts->phc_index;
info->tx_types =
(1 << HWTSTAMP_TX_OFF) |
(1 << HWTSTAMP_TX_ON);
info->rx_filters =
(1 << HWTSTAMP_FILTER_NONE) |
(1 << HWTSTAMP_FILTER_PTP_V1_L4_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_EVENT);
return 0;
}
#else
static int keystone_get_ts_info(struct net_device *ndev,
struct ethtool_ts_info *info)
{
info->so_timestamping =
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_RX_SOFTWARE |
SOF_TIMESTAMPING_SOFTWARE;
info->phc_index = -1;
info->tx_types = 0;
info->rx_filters = 0;
return 0;
}
#endif /* CONFIG_TI_CPTS */
static const struct ethtool_ops keystone_ethtool_ops = {
.get_drvinfo = keystone_get_drvinfo,
.get_link = ethtool_op_get_link,
......@@ -1922,6 +2034,7 @@ static const struct ethtool_ops keystone_ethtool_ops = {
.get_ethtool_stats = keystone_get_ethtool_stats,
.get_link_ksettings = keystone_get_link_ksettings,
.set_link_ksettings = keystone_set_link_ksettings,
.get_ts_info = keystone_get_ts_info,
};
#define mac_hi(mac) (((mac)[0] << 0) | ((mac)[1] << 8) | \
......@@ -2365,16 +2478,279 @@ static int gbe_del_vid(void *intf_priv, int vid)
return 0;
}
#if IS_ENABLED(CONFIG_TI_CPTS)
#define HAS_PHY_TXTSTAMP(p) ((p)->drv && (p)->drv->txtstamp)
#define HAS_PHY_RXTSTAMP(p) ((p)->drv && (p)->drv->rxtstamp)
static void gbe_txtstamp(void *context, struct sk_buff *skb)
{
struct gbe_intf *gbe_intf = context;
struct gbe_priv *gbe_dev = gbe_intf->gbe_dev;
cpts_tx_timestamp(gbe_dev->cpts, skb);
}
static bool gbe_need_txtstamp(struct gbe_intf *gbe_intf,
const struct netcp_packet *p_info)
{
struct sk_buff *skb = p_info->skb;
unsigned int class = ptp_classify_raw(skb);
if (class == PTP_CLASS_NONE)
return false;
switch (class) {
case PTP_CLASS_V1_IPV4:
case PTP_CLASS_V1_IPV6:
case PTP_CLASS_V2_IPV4:
case PTP_CLASS_V2_IPV6:
case PTP_CLASS_V2_L2:
case (PTP_CLASS_V2_VLAN | PTP_CLASS_L2):
case (PTP_CLASS_V2_VLAN | PTP_CLASS_IPV4):
case (PTP_CLASS_V2_VLAN | PTP_CLASS_IPV6):
return true;
}
return false;
}
static int gbe_txtstamp_mark_pkt(struct gbe_intf *gbe_intf,
struct netcp_packet *p_info)
{
struct phy_device *phydev = p_info->skb->dev->phydev;
struct gbe_priv *gbe_dev = gbe_intf->gbe_dev;
if (!(skb_shinfo(p_info->skb)->tx_flags & SKBTX_HW_TSTAMP) ||
!cpts_is_tx_enabled(gbe_dev->cpts))
return 0;
/* If phy has the txtstamp api, assume it will do it.
* We mark it here because skb_tx_timestamp() is called
* after all the txhooks are called.
*/
if (phydev && HAS_PHY_TXTSTAMP(phydev)) {
skb_shinfo(p_info->skb)->tx_flags |= SKBTX_IN_PROGRESS;
return 0;
}
if (gbe_need_txtstamp(gbe_intf, p_info)) {
p_info->txtstamp = gbe_txtstamp;
p_info->ts_context = (void *)gbe_intf;
skb_shinfo(p_info->skb)->tx_flags |= SKBTX_IN_PROGRESS;
}
return 0;
}
static int gbe_rxtstamp(struct gbe_intf *gbe_intf, struct netcp_packet *p_info)
{
struct phy_device *phydev = p_info->skb->dev->phydev;
struct gbe_priv *gbe_dev = gbe_intf->gbe_dev;
if (p_info->rxtstamp_complete)
return 0;
if (phydev && HAS_PHY_RXTSTAMP(phydev)) {
p_info->rxtstamp_complete = true;
return 0;
}
cpts_rx_timestamp(gbe_dev->cpts, p_info->skb);
p_info->rxtstamp_complete = true;
return 0;
}
static int gbe_hwtstamp_get(struct gbe_intf *gbe_intf, struct ifreq *ifr)
{
struct gbe_priv *gbe_dev = gbe_intf->gbe_dev;
struct cpts *cpts = gbe_dev->cpts;
struct hwtstamp_config cfg;
if (!cpts)
return -EOPNOTSUPP;
cfg.flags = 0;
cfg.tx_type = cpts_is_tx_enabled(cpts) ?
HWTSTAMP_TX_ON : HWTSTAMP_TX_OFF;
cfg.rx_filter = (cpts_is_rx_enabled(cpts) ?
cpts->rx_enable : HWTSTAMP_FILTER_NONE);
return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
}
static void gbe_hwtstamp(struct gbe_intf *gbe_intf)
{
struct gbe_priv *gbe_dev = gbe_intf->gbe_dev;
struct gbe_slave *slave = gbe_intf->slave;
u32 ts_en, seq_id, ctl;
if (!cpts_is_rx_enabled(gbe_dev->cpts) &&
!cpts_is_tx_enabled(gbe_dev->cpts)) {
writel(0, GBE_REG_ADDR(slave, port_regs, ts_ctl));
return;
}
seq_id = (30 << TS_SEQ_ID_OFS_SHIFT) | ETH_P_1588;
ts_en = EVENT_MSG_BITS << TS_MSG_TYPE_EN_SHIFT;
ctl = ETH_P_1588 | TS_TTL_NONZERO |
(slave->ts_ctl.dst_port_map << TS_CTL_DST_PORT_SHIFT) |
(slave->ts_ctl.uni ? TS_UNI_EN :
slave->ts_ctl.maddr_map << TS_CTL_MADDR_SHIFT);
if (cpts_is_tx_enabled(gbe_dev->cpts))
ts_en |= (TS_TX_ANX_ALL_EN | TS_TX_VLAN_LT1_EN);
if (cpts_is_rx_enabled(gbe_dev->cpts))
ts_en |= (TS_RX_ANX_ALL_EN | TS_RX_VLAN_LT1_EN);
writel(ts_en, GBE_REG_ADDR(slave, port_regs, ts_ctl));
writel(seq_id, GBE_REG_ADDR(slave, port_regs, ts_seq_ltype));
writel(ctl, GBE_REG_ADDR(slave, port_regs, ts_ctl_ltype2));
}
static int gbe_hwtstamp_set(struct gbe_intf *gbe_intf, struct ifreq *ifr)
{
struct gbe_priv *gbe_dev = gbe_intf->gbe_dev;
struct cpts *cpts = gbe_dev->cpts;
struct hwtstamp_config cfg;
if (!cpts)
return -EOPNOTSUPP;
if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
return -EFAULT;
/* reserved for future extensions */
if (cfg.flags)
return -EINVAL;
switch (cfg.tx_type) {
case HWTSTAMP_TX_OFF:
cpts_tx_enable(cpts, 0);
break;
case HWTSTAMP_TX_ON:
cpts_tx_enable(cpts, 1);
break;
default:
return -ERANGE;
}
switch (cfg.rx_filter) {
case HWTSTAMP_FILTER_NONE:
cpts_rx_enable(cpts, 0);
break;
case HWTSTAMP_FILTER_ALL:
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
cpts_rx_enable(cpts, HWTSTAMP_FILTER_PTP_V1_L4_EVENT);
cfg.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
break;
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
cpts_rx_enable(cpts, HWTSTAMP_FILTER_PTP_V2_EVENT);
cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
break;
default:
return -ERANGE;
}
gbe_hwtstamp(gbe_intf);
return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
}
static void gbe_register_cpts(struct gbe_priv *gbe_dev)
{
if (!gbe_dev->cpts)
return;
if (gbe_dev->cpts_registered > 0)
goto done;
if (cpts_register(gbe_dev->cpts)) {
dev_err(gbe_dev->dev, "error registering cpts device\n");
return;
}
done:
++gbe_dev->cpts_registered;
}
static void gbe_unregister_cpts(struct gbe_priv *gbe_dev)
{
if (!gbe_dev->cpts || (gbe_dev->cpts_registered <= 0))
return;
if (--gbe_dev->cpts_registered)
return;
cpts_unregister(gbe_dev->cpts);
}
#else
static inline int gbe_txtstamp_mark_pkt(struct gbe_intf *gbe_intf,
struct netcp_packet *p_info)
{
return 0;
}
static inline int gbe_rxtstamp(struct gbe_intf *gbe_intf,
struct netcp_packet *p_info)
{
return 0;
}
static inline int gbe_hwtstamp(struct gbe_intf *gbe_intf,
struct ifreq *ifr, int cmd)
{
return -EOPNOTSUPP;
}
static inline void gbe_register_cpts(struct gbe_priv *gbe_dev)
{
}
static inline void gbe_unregister_cpts(struct gbe_priv *gbe_dev)
{
}
static inline int gbe_hwtstamp_get(struct gbe_intf *gbe_intf, struct ifreq *req)
{
return -EOPNOTSUPP;
}
static inline int gbe_hwtstamp_set(struct gbe_intf *gbe_intf, struct ifreq *req)
{
return -EOPNOTSUPP;
}
#endif /* CONFIG_TI_CPTS */
static int gbe_ioctl(void *intf_priv, struct ifreq *req, int cmd)
{
struct gbe_intf *gbe_intf = intf_priv;
struct phy_device *phy = gbe_intf->slave->phy;
int ret = -EOPNOTSUPP;
if (!phy || !phy->drv->hwtstamp) {
switch (cmd) {
case SIOCGHWTSTAMP:
return gbe_hwtstamp_get(gbe_intf, req);
case SIOCSHWTSTAMP:
return gbe_hwtstamp_set(gbe_intf, req);
}
}
if (phy)
ret = phy_mii_ioctl(phy, req, cmd);
return phy_mii_ioctl(phy, req, cmd);
return ret;
return -EOPNOTSUPP;
}
static void netcp_ethss_timer(unsigned long arg)
......@@ -2410,12 +2786,20 @@ static void netcp_ethss_timer(unsigned long arg)
add_timer(&gbe_dev->timer);
}
static int gbe_tx_hook(int order, void *data, struct netcp_packet *p_info)
static int gbe_txhook(int order, void *data, struct netcp_packet *p_info)
{
struct gbe_intf *gbe_intf = data;
p_info->tx_pipe = &gbe_intf->tx_pipe;
return 0;
return gbe_txtstamp_mark_pkt(gbe_intf, p_info);
}
static int gbe_rxhook(int order, void *data, struct netcp_packet *p_info)
{
struct gbe_intf *gbe_intf = data;
return gbe_rxtstamp(gbe_intf, p_info);
}
static int gbe_open(void *intf_priv, struct net_device *ndev)
......@@ -2465,11 +2849,14 @@ static int gbe_open(void *intf_priv, struct net_device *ndev)
if (ret)
goto fail;
netcp_register_txhook(netcp, GBE_TXHOOK_ORDER, gbe_tx_hook,
gbe_intf);
netcp_register_txhook(netcp, GBE_TXHOOK_ORDER, gbe_txhook, gbe_intf);
netcp_register_rxhook(netcp, GBE_RXHOOK_ORDER, gbe_rxhook, gbe_intf);
slave->open = true;
netcp_ethss_update_link_state(gbe_dev, slave, ndev);
gbe_register_cpts(