Commit aea3dee8 authored by David S. Miller's avatar David S. Miller

Merge branch 'Peer-to-Peer-One-Step-time-stamping'

Richard Cochran says:

====================
Peer to Peer One-Step time stamping

This series adds support for PTP (IEEE 1588) P2P one-step time
stamping along with a driver for a hardware device that supports this.

If the hardware supports p2p one-step, it subtracts the ingress time
stamp value from the Pdelay_Request correction field.  The user space
software stack then simply copies the correction field into the
Pdelay_Response, and on transmission the hardware adds the egress time
stamp into the correction field.

This new functionality extends CONFIG_NETWORK_PHY_TIMESTAMPING to
cover MII snooping devices, but it still depends on phylib, just as
that option does.  Expanding beyond phylib is not within the scope of
the this series.

User space support is available in the current linuxptp master branch.

- Patch 1 adds phy_device methods for existing time stamping fields.
- Patches 2-5 convert the stack and drivers to the new methods.
- Patch 6 moves code around the dp83640 driver.
- Patches 7-10 add support for MII time stamping in non-PHY devices.
- Patch 11 adds the new P2P 1-step option.
- Patch 12 adds a driver implementing the new option.

Thanks,
Richard

Changed in v9:
~~~~~~~~~~~~~~

- Fix two more drivers' switch/case blocks WRT the new HWTSTAMP ioctl.
- Picked up two more review tags from Andrew.

Changed in v8:
~~~~~~~~~~~~~~

- Avoided adding forward functional declarations in the dp83640 driver.
- Picked up Florian's new review tags and another one from Andrew.

Changed in v7:
~~~~~~~~~~~~~~

- Converted pr_debug|err to dev_ variants in new driver.
- Fixed device tree documentation per Rob's v6 review.
- Picked up Andrew's and Rob's review tags.
- Silenced sparse warnings in new driver.

Changed in v6:
~~~~~~~~~~~~~~

- Added methods for accessing the phy_device time stamping fields.
- Adjust the device tree documentation per Rob's v5 review.
- Fixed the build failures due to missing exports.

Changed in v5:
~~~~~~~~~~~~~~

- Fixed build failure in macvlan.
- Fixed latent bug with its gcc warning in the driver.

Changed in v4:
~~~~~~~~~~~~~~

- Correct error paths and PTR_ERR return values in the framework.
- Expanded KernelDoc comments WRT PHY locking.
- Pick up Andrew's review tag.

Changed in v3:
~~~~~~~~~~~~~~

- Simplify the device tree binding and document the time stamping
  phandle by itself.

Changed in v2:
~~~~~~~~~~~~~~

- Per the v1 review, changed the modeling of MII time stamping
  devices.  They are no longer a kind of mdio device.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 9f6cff99 bad1eaa6
ZHAW InES PTP time stamping IP core
The IP core needs two different kinds of nodes. The control node
lives somewhere in the memory map and specifies the address of the
control registers. There can be up to three port handles placed as
attributes of PHY nodes. These associate a particular MII bus with a
port index within the IP core.
Required properties of the control node:
- compatible: "ines,ptp-ctrl"
- reg: physical address and size of the register bank
Required format of the port handle within the PHY node:
- timestamper: provides control node reference and
the port channel within the IP core
Example:
tstamper: timestamper@60000000 {
compatible = "ines,ptp-ctrl";
reg = <0x60000000 0x80>;
};
ethernet@80000000 {
...
mdio {
...
ethernet-phy@3 {
...
timestamper = <&tstamper 0>;
};
};
};
Time stamps from MII bus snooping devices
This binding supports non-PHY devices that snoop the MII bus and
provide time stamps. In contrast to PHY time stamping drivers (which
can simply attach their interface directly to the PHY instance), stand
alone MII time stamping drivers use this binding to specify the
connection between the snooping device and a given network interface.
Non-PHY MII time stamping drivers typically talk to the control
interface over another bus like I2C, SPI, UART, or via a memory mapped
peripheral. This controller device is associated with one or more
time stamping channels, each of which snoops on a MII bus.
The "timestamper" property lives in a phy node and links a time
stamping channel from the controller device to that phy's MII bus.
Example:
tstamper: timestamper@10000000 {
compatible = "ines,ptp-ctrl";
reg = <0x10000000 0x80>;
};
ethernet@20000000 {
mdio {
ethernet-phy@1 {
timestamper = <&tstamper 0>;
};
};
};
ethernet@30000000 {
mdio {
ethernet-phy@2 {
timestamper = <&tstamper 1>;
};
};
};
In this example, time stamps from the MII bus attached to phy@1 will
appear on time stamp channel 0 (zero), and those from phy@2 appear on
channel 1.
...@@ -15410,6 +15410,7 @@ int bnx2x_configure_ptp_filters(struct bnx2x *bp) ...@@ -15410,6 +15410,7 @@ int bnx2x_configure_ptp_filters(struct bnx2x *bp)
REG_WR(bp, rule, BNX2X_PTP_TX_ON_RULE_MASK); REG_WR(bp, rule, BNX2X_PTP_TX_ON_RULE_MASK);
break; break;
case HWTSTAMP_TX_ONESTEP_SYNC: case HWTSTAMP_TX_ONESTEP_SYNC:
case HWTSTAMP_TX_ONESTEP_P2P:
BNX2X_ERR("One-step timestamping is not supported\n"); BNX2X_ERR("One-step timestamping is not supported\n");
return -ERANGE; return -ERANGE;
} }
......
...@@ -920,6 +920,7 @@ static int mlxsw_sp_ptp_get_message_types(const struct hwtstamp_config *config, ...@@ -920,6 +920,7 @@ static int mlxsw_sp_ptp_get_message_types(const struct hwtstamp_config *config,
egr_types = 0xff; egr_types = 0xff;
break; break;
case HWTSTAMP_TX_ONESTEP_SYNC: case HWTSTAMP_TX_ONESTEP_SYNC:
case HWTSTAMP_TX_ONESTEP_P2P:
return -ERANGE; return -ERANGE;
} }
......
...@@ -1265,6 +1265,9 @@ int lan743x_ptp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) ...@@ -1265,6 +1265,9 @@ int lan743x_ptp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
lan743x_ptp_set_sync_ts_insert(adapter, true); lan743x_ptp_set_sync_ts_insert(adapter, true);
break; break;
case HWTSTAMP_TX_ONESTEP_P2P:
ret = -ERANGE;
break;
default: default:
netif_warn(adapter, drv, adapter->netdev, netif_warn(adapter, drv, adapter->netdev,
" tx_type = %d, UNKNOWN\n", config.tx_type); " tx_type = %d, UNKNOWN\n", config.tx_type);
......
...@@ -247,6 +247,7 @@ static int qede_ptp_cfg_filters(struct qede_dev *edev) ...@@ -247,6 +247,7 @@ static int qede_ptp_cfg_filters(struct qede_dev *edev)
break; break;
case HWTSTAMP_TX_ONESTEP_SYNC: case HWTSTAMP_TX_ONESTEP_SYNC:
case HWTSTAMP_TX_ONESTEP_P2P:
DP_ERR(edev, "One-step timestamping is not supported\n"); DP_ERR(edev, "One-step timestamping is not supported\n");
return -ERANGE; return -ERANGE;
} }
......
...@@ -2533,8 +2533,6 @@ static int gbe_del_vid(void *intf_priv, int vid) ...@@ -2533,8 +2533,6 @@ static int gbe_del_vid(void *intf_priv, int vid)
} }
#if IS_ENABLED(CONFIG_TI_CPTS) #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) static void gbe_txtstamp(void *context, struct sk_buff *skb)
{ {
...@@ -2566,7 +2564,7 @@ static int gbe_txtstamp_mark_pkt(struct gbe_intf *gbe_intf, ...@@ -2566,7 +2564,7 @@ static int gbe_txtstamp_mark_pkt(struct gbe_intf *gbe_intf,
* We mark it here because skb_tx_timestamp() is called * We mark it here because skb_tx_timestamp() is called
* after all the txhooks are called. * after all the txhooks are called.
*/ */
if (phydev && HAS_PHY_TXTSTAMP(phydev)) { if (phy_has_txtstamp(phydev)) {
skb_shinfo(p_info->skb)->tx_flags |= SKBTX_IN_PROGRESS; skb_shinfo(p_info->skb)->tx_flags |= SKBTX_IN_PROGRESS;
return 0; return 0;
} }
...@@ -2588,7 +2586,7 @@ static int gbe_rxtstamp(struct gbe_intf *gbe_intf, struct netcp_packet *p_info) ...@@ -2588,7 +2586,7 @@ static int gbe_rxtstamp(struct gbe_intf *gbe_intf, struct netcp_packet *p_info)
if (p_info->rxtstamp_complete) if (p_info->rxtstamp_complete)
return 0; return 0;
if (phydev && HAS_PHY_RXTSTAMP(phydev)) { if (phy_has_rxtstamp(phydev)) {
p_info->rxtstamp_complete = true; p_info->rxtstamp_complete = true;
return 0; return 0;
} }
...@@ -2830,7 +2828,7 @@ static int gbe_ioctl(void *intf_priv, struct ifreq *req, int cmd) ...@@ -2830,7 +2828,7 @@ static int gbe_ioctl(void *intf_priv, struct ifreq *req, int cmd)
struct gbe_intf *gbe_intf = intf_priv; struct gbe_intf *gbe_intf = intf_priv;
struct phy_device *phy = gbe_intf->slave->phy; struct phy_device *phy = gbe_intf->slave->phy;
if (!phy || !phy->drv->hwtstamp) { if (!phy_has_hwtstamp(phy)) {
switch (cmd) { switch (cmd) {
case SIOCGHWTSTAMP: case SIOCGHWTSTAMP:
return gbe_hwtstamp_get(gbe_intf, req); return gbe_hwtstamp_get(gbe_intf, req);
......
...@@ -1036,8 +1036,8 @@ static int macvlan_ethtool_get_ts_info(struct net_device *dev, ...@@ -1036,8 +1036,8 @@ static int macvlan_ethtool_get_ts_info(struct net_device *dev,
const struct ethtool_ops *ops = real_dev->ethtool_ops; const struct ethtool_ops *ops = real_dev->ethtool_ops;
struct phy_device *phydev = real_dev->phydev; struct phy_device *phydev = real_dev->phydev;
if (phydev && phydev->drv && phydev->drv->ts_info) { if (phy_has_tsinfo(phydev)) {
return phydev->drv->ts_info(phydev, info); return phy_ts_info(phydev, info);
} else if (ops->get_ts_info) { } else if (ops->get_ts_info) {
return ops->get_ts_info(real_dev, info); return ops->get_ts_info(real_dev, info);
} else { } else {
......
...@@ -43,6 +43,8 @@ obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o ...@@ -43,6 +43,8 @@ obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o
obj-$(CONFIG_SFP) += sfp.o obj-$(CONFIG_SFP) += sfp.o
sfp-obj-$(CONFIG_SFP) += sfp-bus.o sfp-obj-$(CONFIG_SFP) += sfp-bus.o
obj-y += $(sfp-obj-y) $(sfp-obj-m) obj-y += $(sfp-obj-y) $(sfp-obj-m)
......
...@@ -98,6 +98,7 @@ struct dp83640_private { ...@@ -98,6 +98,7 @@ struct dp83640_private {
struct list_head list; struct list_head list;
struct dp83640_clock *clock; struct dp83640_clock *clock;
struct phy_device *phydev; struct phy_device *phydev;
struct mii_timestamper mii_ts;
struct delayed_work ts_work; struct delayed_work ts_work;
int hwts_tx_en; int hwts_tx_en;
int hwts_rx_en; int hwts_rx_en;
...@@ -1131,96 +1132,6 @@ static void dp83640_clock_put(struct dp83640_clock *clock) ...@@ -1131,96 +1132,6 @@ static void dp83640_clock_put(struct dp83640_clock *clock)
mutex_unlock(&clock->clock_lock); mutex_unlock(&clock->clock_lock);
} }
static int dp83640_probe(struct phy_device *phydev)
{
struct dp83640_clock *clock;
struct dp83640_private *dp83640;
int err = -ENOMEM, i;
if (phydev->mdio.addr == BROADCAST_ADDR)
return 0;
clock = dp83640_clock_get_bus(phydev->mdio.bus);
if (!clock)
goto no_clock;
dp83640 = kzalloc(sizeof(struct dp83640_private), GFP_KERNEL);
if (!dp83640)
goto no_memory;
dp83640->phydev = phydev;
INIT_DELAYED_WORK(&dp83640->ts_work, rx_timestamp_work);
INIT_LIST_HEAD(&dp83640->rxts);
INIT_LIST_HEAD(&dp83640->rxpool);
for (i = 0; i < MAX_RXTS; i++)
list_add(&dp83640->rx_pool_data[i].list, &dp83640->rxpool);
phydev->priv = dp83640;
spin_lock_init(&dp83640->rx_lock);
skb_queue_head_init(&dp83640->rx_queue);
skb_queue_head_init(&dp83640->tx_queue);
dp83640->clock = clock;
if (choose_this_phy(clock, phydev)) {
clock->chosen = dp83640;
clock->ptp_clock = ptp_clock_register(&clock->caps,
&phydev->mdio.dev);
if (IS_ERR(clock->ptp_clock)) {
err = PTR_ERR(clock->ptp_clock);
goto no_register;
}
} else
list_add_tail(&dp83640->list, &clock->phylist);
dp83640_clock_put(clock);
return 0;
no_register:
clock->chosen = NULL;
kfree(dp83640);
no_memory:
dp83640_clock_put(clock);
no_clock:
return err;
}
static void dp83640_remove(struct phy_device *phydev)
{
struct dp83640_clock *clock;
struct list_head *this, *next;
struct dp83640_private *tmp, *dp83640 = phydev->priv;
if (phydev->mdio.addr == BROADCAST_ADDR)
return;
enable_status_frames(phydev, false);
cancel_delayed_work_sync(&dp83640->ts_work);
skb_queue_purge(&dp83640->rx_queue);
skb_queue_purge(&dp83640->tx_queue);
clock = dp83640_clock_get(dp83640->clock);
if (dp83640 == clock->chosen) {
ptp_clock_unregister(clock->ptp_clock);
clock->chosen = NULL;
} else {
list_for_each_safe(this, next, &clock->phylist) {
tmp = list_entry(this, struct dp83640_private, list);
if (tmp == dp83640) {
list_del_init(&tmp->list);
break;
}
}
}
dp83640_clock_put(clock);
kfree(dp83640);
}
static int dp83640_soft_reset(struct phy_device *phydev) static int dp83640_soft_reset(struct phy_device *phydev)
{ {
int ret; int ret;
...@@ -1319,9 +1230,10 @@ static int dp83640_config_intr(struct phy_device *phydev) ...@@ -1319,9 +1230,10 @@ static int dp83640_config_intr(struct phy_device *phydev)
} }
} }
static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr) static int dp83640_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr)
{ {
struct dp83640_private *dp83640 = phydev->priv; struct dp83640_private *dp83640 =
container_of(mii_ts, struct dp83640_private, mii_ts);
struct hwtstamp_config cfg; struct hwtstamp_config cfg;
u16 txcfg0, rxcfg0; u16 txcfg0, rxcfg0;
...@@ -1397,8 +1309,8 @@ static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr) ...@@ -1397,8 +1309,8 @@ static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
mutex_lock(&dp83640->clock->extreg_lock); mutex_lock(&dp83640->clock->extreg_lock);
ext_write(0, phydev, PAGE5, PTP_TXCFG0, txcfg0); ext_write(0, dp83640->phydev, PAGE5, PTP_TXCFG0, txcfg0);
ext_write(0, phydev, PAGE5, PTP_RXCFG0, rxcfg0); ext_write(0, dp83640->phydev, PAGE5, PTP_RXCFG0, rxcfg0);
mutex_unlock(&dp83640->clock->extreg_lock); mutex_unlock(&dp83640->clock->extreg_lock);
...@@ -1428,10 +1340,11 @@ static void rx_timestamp_work(struct work_struct *work) ...@@ -1428,10 +1340,11 @@ static void rx_timestamp_work(struct work_struct *work)
schedule_delayed_work(&dp83640->ts_work, SKB_TIMESTAMP_TIMEOUT); schedule_delayed_work(&dp83640->ts_work, SKB_TIMESTAMP_TIMEOUT);
} }
static bool dp83640_rxtstamp(struct phy_device *phydev, static bool dp83640_rxtstamp(struct mii_timestamper *mii_ts,
struct sk_buff *skb, int type) struct sk_buff *skb, int type)
{ {
struct dp83640_private *dp83640 = phydev->priv; struct dp83640_private *dp83640 =
container_of(mii_ts, struct dp83640_private, mii_ts);
struct dp83640_skb_info *skb_info = (struct dp83640_skb_info *)skb->cb; struct dp83640_skb_info *skb_info = (struct dp83640_skb_info *)skb->cb;
struct list_head *this, *next; struct list_head *this, *next;
struct rxts *rxts; struct rxts *rxts;
...@@ -1477,11 +1390,12 @@ static bool dp83640_rxtstamp(struct phy_device *phydev, ...@@ -1477,11 +1390,12 @@ static bool dp83640_rxtstamp(struct phy_device *phydev,
return true; return true;
} }
static void dp83640_txtstamp(struct phy_device *phydev, static void dp83640_txtstamp(struct mii_timestamper *mii_ts,
struct sk_buff *skb, int type) struct sk_buff *skb, int type)
{ {
struct dp83640_skb_info *skb_info = (struct dp83640_skb_info *)skb->cb; struct dp83640_skb_info *skb_info = (struct dp83640_skb_info *)skb->cb;
struct dp83640_private *dp83640 = phydev->priv; struct dp83640_private *dp83640 =
container_of(mii_ts, struct dp83640_private, mii_ts);
switch (dp83640->hwts_tx_en) { switch (dp83640->hwts_tx_en) {
...@@ -1504,9 +1418,11 @@ static void dp83640_txtstamp(struct phy_device *phydev, ...@@ -1504,9 +1418,11 @@ static void dp83640_txtstamp(struct phy_device *phydev,
} }
} }
static int dp83640_ts_info(struct phy_device *dev, struct ethtool_ts_info *info) static int dp83640_ts_info(struct mii_timestamper *mii_ts,
struct ethtool_ts_info *info)
{ {
struct dp83640_private *dp83640 = dev->priv; struct dp83640_private *dp83640 =
container_of(mii_ts, struct dp83640_private, mii_ts);
info->so_timestamping = info->so_timestamping =
SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_TX_HARDWARE |
...@@ -1526,6 +1442,103 @@ static int dp83640_ts_info(struct phy_device *dev, struct ethtool_ts_info *info) ...@@ -1526,6 +1442,103 @@ static int dp83640_ts_info(struct phy_device *dev, struct ethtool_ts_info *info)
return 0; return 0;
} }
static int dp83640_probe(struct phy_device *phydev)
{
struct dp83640_clock *clock;
struct dp83640_private *dp83640;
int err = -ENOMEM, i;
if (phydev->mdio.addr == BROADCAST_ADDR)
return 0;
clock = dp83640_clock_get_bus(phydev->mdio.bus);
if (!clock)
goto no_clock;
dp83640 = kzalloc(sizeof(struct dp83640_private), GFP_KERNEL);
if (!dp83640)
goto no_memory;
dp83640->phydev = phydev;
dp83640->mii_ts.rxtstamp = dp83640_rxtstamp;
dp83640->mii_ts.txtstamp = dp83640_txtstamp;
dp83640->mii_ts.hwtstamp = dp83640_hwtstamp;
dp83640->mii_ts.ts_info = dp83640_ts_info;
INIT_DELAYED_WORK(&dp83640->ts_work, rx_timestamp_work);
INIT_LIST_HEAD(&dp83640->rxts);
INIT_LIST_HEAD(&dp83640->rxpool);
for (i = 0; i < MAX_RXTS; i++)
list_add(&dp83640->rx_pool_data[i].list, &dp83640->rxpool);
phydev->mii_ts = &dp83640->mii_ts;
phydev->priv = dp83640;
spin_lock_init(&dp83640->rx_lock);
skb_queue_head_init(&dp83640->rx_queue);
skb_queue_head_init(&dp83640->tx_queue);
dp83640->clock = clock;
if (choose_this_phy(clock, phydev)) {
clock->chosen = dp83640;
clock->ptp_clock = ptp_clock_register(&clock->caps,
&phydev->mdio.dev);
if (IS_ERR(clock->ptp_clock)) {
err = PTR_ERR(clock->ptp_clock);
goto no_register;
}
} else
list_add_tail(&dp83640->list, &clock->phylist);
dp83640_clock_put(clock);
return 0;
no_register:
clock->chosen = NULL;
kfree(dp83640);
no_memory:
dp83640_clock_put(clock);
no_clock:
return err;
}
static void dp83640_remove(struct phy_device *phydev)
{
struct dp83640_clock *clock;
struct list_head *this, *next;
struct dp83640_private *tmp, *dp83640 = phydev->priv;
if (phydev->mdio.addr == BROADCAST_ADDR)
return;
phydev->mii_ts = NULL;
enable_status_frames(phydev, false);
cancel_delayed_work_sync(&dp83640->ts_work);
skb_queue_purge(&dp83640->rx_queue);
skb_queue_purge(&dp83640->tx_queue);
clock = dp83640_clock_get(dp83640->clock);
if (dp83640 == clock->chosen) {
ptp_clock_unregister(clock->ptp_clock);
clock->chosen = NULL;
} else {
list_for_each_safe(this, next, &clock->phylist) {
tmp = list_entry(this, struct dp83640_private, list);
if (tmp == dp83640) {
list_del_init(&tmp->list);
break;
}
}
}
dp83640_clock_put(clock);
kfree(dp83640);
}
static struct phy_driver dp83640_driver = { static struct phy_driver dp83640_driver = {
.phy_id = DP83640_PHY_ID, .phy_id = DP83640_PHY_ID,
.phy_id_mask = 0xfffffff0, .phy_id_mask = 0xfffffff0,
...@@ -1537,10 +1550,6 @@ static struct phy_driver dp83640_driver = { ...@@ -1537,10 +1550,6 @@ static struct phy_driver dp83640_driver = {
.config_init = dp83640_config_init, .config_init = dp83640_config_init,
.ack_interrupt = dp83640_ack_interrupt, .ack_interrupt = dp83640_ack_interrupt,
.config_intr = dp83640_config_intr, .config_intr = dp83640_config_intr,
.ts_info = dp83640_ts_info,
.hwtstamp = dp83640_hwtstamp,
.rxtstamp = dp83640_rxtstamp,
.txtstamp = dp83640_txtstamp,
}; };
static int __init dp83640_init(void) static int __init dp83640_init(void)
......
// SPDX-License-Identifier: GPL-2.0
//
// Support for generic time stamping devices on MII buses.
// Copyright (C) 2018 Richard Cochran <richardcochran@gmail.com>
//
#include <linux/mii_timestamper.h>
static LIST_HEAD(mii_timestamping_devices);
static DEFINE_MUTEX(tstamping_devices_lock);
struct mii_timestamping_desc {
struct list_head list;
struct mii_timestamping_ctrl *ctrl;
struct device *device;
};
/**
* register_mii_tstamp_controller() - registers an MII time stamping device.
*
* @device: The device to be registered.
* @ctrl: Pointer to device's control interface.
*
* Returns zero on success or non-zero on failure.
*/
int register_mii_tstamp_controller(struct device *device,
struct mii_timestamping_ctrl *ctrl)
{
struct mii_timestamping_desc *desc;
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
INIT_LIST_HEAD(&desc->list);
desc->ctrl = ctrl;
desc->device = device;
mutex_lock(&tstamping_devices_lock);
list_add_tail(&mii_timestamping_devices, &desc->list);
mutex_unlock(&tstamping_devices_lock);
return 0;
}
EXPORT_SYMBOL(register_mii_tstamp_controller);
/**
* unregister_mii_tstamp_controller() - unregisters an MII time stamping device.
*
* @device: A device previously passed to register_mii_tstamp_controller().
*/
void unregister_mii_tstamp_controller(struct device *device)
{
struct mii_timestamping_desc *desc;
struct list_head *this, *next;
mutex_lock(&tstamping_devices_lock);
list_for_each_safe(this, next, &mii_timestamping_devices) {
desc = list_entry(this, struct mii_timestamping_desc, list);
if (desc->device == device) {
list_del_init(&desc->list);
kfree(desc);
break;
}
}
mutex_unlock(&tstamping_devices_lock);
}
EXPORT_SYMBOL(unregister_mii_tstamp_controller);
/**
* register_mii_timestamper - Enables a given port of an MII time stamper.
*
* @node: The device tree node of the MII time stamp controller.
* @port: The index of the port to be enabled.
*
* Returns a valid interface on success or ERR_PTR otherwise.
*/
struct mii_timestamper *register_mii_timestamper(struct device_node *node,
unsigned int port)
{
struct mii_timestamper *mii_ts = NULL;
struct mii_timestamping_desc *desc;
struct list_head *this;
mutex_lock(&tstamping_devices_lock);
list_for_each(this, &mii_timestamping_devices) {
desc = list_entry(this, struct mii_timestamping_desc, list);
if (desc->device->of_node == node) {
mii_ts = desc->ctrl->probe_channel(desc->device, port);
if (!IS_ERR(mii_ts)) {
mii_ts->device = desc->device;
get_device(desc->device);
}
break;
}
}
mutex_unlock(&tstamping_devices_lock);
return mii_ts ? mii_ts : ERR_PTR(-EPROBE_DEFER);
}
EXPORT_SYMBOL(register_mii_timestamper);
/**
* unregister_mii_timestamper - Disables a given MII time stamper.
*
* @mii_ts: An interface obtained via register_mii_timestamper().
*
*/
void unregister_mii_timestamper(struct mii_timestamper *mii_ts)
{
struct mii_timestamping_desc *desc;
struct list_head *this;
mutex_lock(&tstamping_devices_lock);
list_for_each(this, &mii_timestamping_devices) {
desc = list_entry(this, struct mii_timestamping_desc, list);
if (desc->device == mii_ts->device) {
desc->ctrl->release_channel(desc->device, mii_ts);
put_device(desc->device);
break;
}
}
mutex_unlock(&tstamping_devices_lock);
}
EXPORT_SYMBOL(unregister_mii_timestamper);
...@@ -422,8 +422,8 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd) ...@@ -422,8 +422,8 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
return 0; return 0;
case SIOCSHWTSTAMP: case SIOCSHWTSTAMP:
if (phydev->drv && phydev->drv->hwtstamp) if (phydev->mii_ts && phydev->mii_ts->hwtstamp)
return phydev->drv->hwtstamp(phydev, ifr); return phydev->mii_ts->hwtstamp(phydev->mii_ts, ifr);
/* fall through */ /* fall through */
default: default:
......
...@@ -881,6 +881,9 @@ EXPORT_SYMBOL(phy_device_register); ...@@ -881,6 +881,9 @@ EXPORT_SYMBOL(phy_device_register);
*/ */
void phy_device_remove(struct phy_device *phydev) void phy_device_remove(struct phy_device *phydev)
{ {
if (phydev->mii_ts)
unregister_mii_timestamper(phydev->mii_ts);
device_del(&phydev->mdio.dev); device_del(&phydev->mdio.dev);
/* Assert the reset signal */ /* Assert the reset signal */
...@@ -919,6 +922,8 @@ static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier) ...@@ -919,6 +922,8 @@ static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier)
netif_carrier_off(netdev); netif_carrier_off(netdev);
} }
phydev->adjust_link(netdev); phydev->adjust_link(netdev);
if (phydev->mii_ts && phydev->mii_ts->link_state)
phydev->mii_ts->link_state(phydev->mii_ts, phydev);
} }
/** /**
......
...@@ -42,14 +42,37 @@ static int of_get_phy_id(struct device_node *device, u32 *phy_id) ...@@ -42,14 +42,37 @@ static int of_get_phy_id(struct device_node *device, u32 *phy_id)
return -EINVAL; return -EINVAL;
} }
static struct mii_timestamper *of_find_mii_timestamper(struct device_node *node)
{
struct of_phandle_args arg;
int err;
err = of_parse_phandle_with_fixed_args(node, "timestamper", 1, 0, &arg);
if (err == -ENOENT)
return NULL;
else if (err)
return ERR_PTR(err);
if (arg.args_count != 1)
return ERR_PTR(-EINVAL);
return register_mii_timestamper(arg.np, arg.args[0]);
}
static int of_mdiobus_register_phy(struct mii_bus *mdio, static int of_mdiobus_register_phy(struct mii_bus *mdio,
struct device_node *child, u32 addr) struct device_node *child, u32 addr)
{ {
struct mii_timestamper *mii_ts;
struct phy_device *phy; struct phy_device *phy;
bool is_c45; bool is_c45;
int rc; int rc;
u32 phy_id; u32 phy_id;
mii_ts = of_find_mii_timestamper(child);
if (IS_ERR(mii_ts))
return PTR_ERR(mii_ts);
is_c45 = of_device_is_compatible(child, is_c45 = of_device_is_compatible(child,
"ethernet-phy-ieee802.3-c45"); "ethernet-phy-ieee802.3-c45");
...@@ -57,11 +80,14 @@ static int of_mdiobus_register_phy(struct mii_bus *mdio, ...@@ -57,11 +80,14 @@ static int of_mdiobus_register_phy(struct mii_bus *mdio,
phy = phy_device_create(mdio, addr, phy_id, 0, NULL); phy = phy_device_create(mdio, addr, phy_id, 0, NULL);
else else
phy = get_phy_device(mdio, addr, is_c45); phy = get_phy_device(mdio, addr, is_c45);
if (IS_ERR(phy)) if (IS_ERR(phy)) {
unregister_mii_timestamper(mii_ts);
return PTR_ERR(phy); return PTR_ERR(phy);
}
rc = of_irq_get(child, 0); rc = of_irq_get(child, 0);
if (rc == -EPROBE_DEFER) { if (rc == -EPROBE_DEFER) {
unregister_mii_timestamper(mii_ts);
phy_device_free(phy); phy_device_free(phy);
return rc; return rc;
} }
...@@ -90,10 +116,12 @@ static int of_mdiobus_register_phy(struct mii_bus *mdio, ...@@ -90,10 +116,12 @@ static int of_mdiobus_register_phy(struct mii_bus *mdio,
* register it */ * register it */
rc = phy_device_register(phy); rc = phy_device_register(phy);
if (rc) { if (rc) {
unregister_mii_timestamper(mii_ts);
phy_device_free(phy); phy_device_free(phy);
of_node_put(child); of_node_put(child);
return rc; return rc;
} }
phy->mii_ts = mii_ts;
dev_dbg(&mdio->dev, "registered phy %pOFn at address %i\n", dev_dbg(&mdio->dev, "registered phy %pOFn at address %i\n",
child, addr); child, addr);
......
...@@ -89,6 +89,16 @@ config DP83640_PHY ...@@ -89,6 +89,16 @@ config DP83640_PHY
In order for this to work, your MAC driver must also In order for this to work, your MAC driver must also
implement the skb_tx_timestamp() function. implement the skb_tx_timestamp() function.
config PTP_1588_CLOCK_INES
tristate "ZHAW InES PTP time stamping IP core"
depends on NETWORK_PHY_TIMESTAMPING
depends on PHYLIB
depends on PTP_1588_CLOCK
help
This driver adds support for using the ZHAW InES 1588 IP
core. This clock is only useful if the MII bus of your MAC
is wired up to the core.
config PTP_1588_CLOCK_PCH config PTP_1588_CLOCK_PCH
tristate "Intel PCH EG20T as PTP clock" tristate "Intel PCH EG20T as PTP clock"
depends on X86_32 || COMPILE_TEST depends on X86_32 || COMPILE_TEST
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o
obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o
obj-$(CONFIG_PTP_1588_CLOCK_DTE) += ptp_dte.o obj-$(CONFIG_PTP_1588_CLOCK_DTE) += ptp_dte.o
obj-$(CONFIG_PTP_1588_CLOCK_INES) += ptp_ines.o
obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o
obj-$(CONFIG_PTP_1588_CLOCK_PCH) += ptp_pch.o obj-$(CONFIG_PTP_1588_CLOCK_PCH) += ptp_pch.o
obj-$(CONFIG_PTP_1588_CLOCK_KVM) += ptp_kvm.o obj-$(CONFIG_PTP_1588_CLOCK_KVM) += ptp_kvm.o
......
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Support for generic time stamping devices on MII buses.
* Copyright (C) 2018 Richard Cochran <richardcochran@gmail.com>
*/
#ifndef _LINUX_MII_TIMESTAMPER_H
#define _LINUX_MII_TIMESTAMPER_H
#include <linux/device.h>
#include <linux/ethtool.h>
#include <linux/skbuff.h>
struct phy_device;
/**
* struct mii_timestamper - Callback interface to MII time stamping devices.
*
* @rxtstamp: Requests a Rx timestamp for 'skb'. If the skb is accepted,
* the MII time stamping device promises to deliver it using
* netif_rx() as soon as a timestamp becomes available. One of
* the PTP_CLASS_ values is passed in 'type'. The function
* must return true if the skb is accepted for delivery.
*
* @txtstamp: Requests a Tx timestamp for 'skb'. The MII time stamping
* device promises to deliver it using skb_complete_tx_timestamp()
* as soon as a timestamp becomes available. One of the PTP_CLASS_
* values is passed in 'type'.
*
* @hwtstamp: Handles SIOCSHWTSTAMP ioctl for hardware time stamping.
*
* @link_state: Allows the device to respond to changes in the link
* state. The caller invokes this function while holding
* the phy_device mutex.
*
* @ts_info: Handles ethtool queries for hardware time stamping.
* @device: Remembers the device to which the instance belongs.
*
* Drivers for PHY time stamping devices should embed their
* mii_timestamper within a private structure, obtaining a reference
* to it using container_of().
*
* Drivers for non-PHY time stamping devices should return a pointer
* to a mii_timestamper from the probe_channel() callback of their
* mii_timestamping_ctrl interface.
*/
struct mii_timestamper {
bool (*rxtstamp)(struct mii_timestamper *mii_ts,
struct sk_buff *skb, int type);
void (*txtstamp)(struct mii_timestamper *mii_ts,
struct sk_buff *skb, int type);
int (*hwtstamp)(struct mii_timestamper *mii_ts,
struct ifreq *ifreq);
void (*link_state)(struct mii_timestamper *mii_ts,
struct phy_device *phydev);
int (*ts_info)(struct mii_timestamper *mii_ts,
struct ethtool_ts_info *ts_info);
struct device *device;
};
/**
* struct mii_timestamping_ctrl - MII time stamping controller interface.
*
* @probe_channel: Callback into the controller driver announcing the
* presence of the 'port' channel. The 'device' field
* had been passed to register_mii_tstamp_controller().
* The driver must return either a pointer to a valid
* MII timestamper instance or PTR_ERR.
*
* @release_channel: Releases an instance obtained via .probe_channel.
*/
struct mii_timestamping_ctrl {
struct mii_timestamper *(*probe_channel)(struct device *device,
unsigned int port);
void (*release_channel)(struct device *device,
struct mii_timestamper *mii_ts);
};
#ifdef CONFIG_NETWORK_PHY_TIMESTAMPING
int register_mii_tstamp_controller(struct device *device,
struct mii_timestamping_ctrl *ctrl);
void unregister_mii_tstamp_controller(struct device *device);
struct mii_timestamper *register_mii_timestamper(struct device_node *node,
unsigned int port);
void unregister_mii_timestamper(struct mii_timestamper *mii_ts);
#else
static inline
int register_mii_tstamp_controller(struct device *device,
struct mii_timestamping_ctrl *ctrl)
{
return -EOPNOTSUPP;
}
static inline void unregister_mii_tstamp_controller(struct device *device)
{
}
static inline
struct mii_timestamper *register_mii_timestamper(struct device_node *node,
unsigned int port)
{
return NULL;
}
static inline void unregister_mii_timestamper(struct mii_timestamper *mii_ts)
{
}
#endif
#endif
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <linux/linkmode.h> #include <linux/linkmode.h>
#include <linux/mdio.h> #include <linux/mdio.h>
#include <linux/mii.h> #include <linux/mii.h>
#include <linux/mii_timestamper.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
...@@ -441,6 +442,7 @@ struct phy_device { ...@@ -441,6 +442,7 @@ struct phy_device {
struct sfp_bus *sfp_bus; struct sfp_bus *sfp_bus;
struct phylink *phylink; struct phylink *phylink;
struct net_device *attached_dev; struct net_device *attached_dev;
struct mii_timestamper *mii_ts;
u8 mdix; u8 mdix;
u8 mdix_ctrl; u8 mdix_ctrl;
...@@ -546,29 +548,6 @@ struct phy_driver { ...@@ -546,29 +548,6 @@ struct phy_driver {
*/ */
int (*match_phy_device)(struct phy_device *phydev); int (*match_phy_device)(struct phy_device *phydev);
/* Handles ethtool queries for hardware time stamping. */
int (*ts_info)(struct phy_device *phydev, struct ethtool_ts_info *ti);
/* Handles SIOCSHWTSTAMP ioctl for hardware time stamping. */
int (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);
/*
* Requests a Rx timestamp for 'skb'. If the skb is accepted,
* the phy driver promises to deliver it using netif_rx() as
* soon as a timestamp becomes available. One of the
* PTP_CLASS_ values is passed in 'type'. The function must
* return true if the skb is accepted for delivery.
*/
bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);
/*
* Requests a Tx timestamp for 'skb'. The phy driver promises
* to deliver it using skb_complete_tx_timestamp() as soon as a
* timestamp becomes available. One of the PTP_CLASS_ values
* is passed in 'type'.
*/
void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb, int type);
/* Some devices (e.g. qnap TS-119P II) require PHY register changes to /* Some devices (e.g. qnap TS-119P II) require PHY register changes to
* enable Wake on LAN, so set_wol is provided to be called in the * enable Wake on LAN, so set_wol is provided to be called in the
* ethernet driver's set_wol function. */ * ethernet driver's set_wol function. */
...@@ -936,6 +915,66 @@ static inline bool phy_polling_mode(struct phy_device *phydev) ...@@ -936,6 +915,66 @@ static inline bool phy_polling_mode(struct phy_device *phydev)
return phydev->irq == PHY_POLL; return phydev->irq == PHY_POLL;
} }
/**
* phy_has_hwtstamp - Tests whether a PHY time stamp configuration.
* @phydev: the phy_device struct
*/
static inline bool phy_has_hwtstamp(struct phy_device *phydev)
{
return phydev && phydev->mii_ts && phydev->mii_ts->hwtstamp;
}
/**
* phy_has_rxtstamp - Tests whether a PHY supports receive time stamping.
* @phydev: the phy_device struct
*/
static inline bool phy_has_rxtstamp(struct phy_device *phydev)
{
return phydev && phydev->mii_ts && phydev->mii_ts->rxtstamp;
}
/**
* phy_has_tsinfo - Tests whether a PHY reports time stamping and/or
* PTP hardware clock capabilities.
* @phydev: the phy_device struct
*/
static inline bool phy_has_tsinfo(struct phy_device *phydev)
{
return phydev && phydev->mii_ts && phydev->mii_ts->ts_info;
}
/**
* phy_has_txtstamp - Tests whether a PHY supports transmit time stamping.
* @phydev: the phy_device struct
*/
static inline bool phy_has_txtstamp(struct phy_device *phydev)
{
return phydev && phydev->mii_ts && phydev->mii_ts->txtstamp;
}
static inline int phy_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
{
return phydev->mii_ts->hwtstamp(phydev->mii_ts, ifr);
}
static inline bool phy_rxtstamp(struct phy_device *phydev, struct sk_buff *skb,
int type)
{
return phydev->mii_ts->rxtstamp(phydev->mii_ts, skb, type);
}
static inline int phy_ts_info(struct phy_device *phydev,
struct ethtool_ts_info *tsinfo)
{
return phydev->mii_ts->ts_info(phydev->mii_ts, tsinfo);
}
static inline void phy_txtstamp(struct phy_device *phydev, struct sk_buff *skb,
int type)
{
phydev->mii_ts->txtstamp(phydev->mii_ts, skb, type);
}
/** /**
* phy_is_internal - Convenience function for testing if a PHY is internal * phy_is_internal - Convenience function for testing if a PHY is internal
* @phydev: the phy_device struct * @phydev: the phy_device struct
......
...@@ -90,6 +90,14 @@ enum hwtstamp_tx_types { ...@@ -90,6 +90,14 @@ enum hwtstamp_tx_types {
* queue. * queue.
*/ */
HWTSTAMP_TX_ONESTEP_SYNC, HWTSTAMP_TX_ONESTEP_SYNC,
/*
* Same as HWTSTAMP_TX_ONESTEP_SYNC, but also enables time
* stamp insertion directly into PDelay_Resp packets. In this
* case, neither transmitted Sync nor PDelay_Resp packets will
* receive a time stamp via the socket error queue.
*/
HWTSTAMP_TX_ONESTEP_P2P,
}; };
/* possible values for hwtstamp_config->rx_filter */ /* possible values for hwtstamp_config->rx_filter */
......
...@@ -646,8 +646,8 @@ static int vlan_ethtool_get_ts_info(struct net_device *dev, ...@@ -646,8 +646,8 @@ static int vlan_ethtool_get_ts_info(struct net_device *dev,
const struct ethtool_ops *ops = vlan->real_dev->ethtool_ops; const struct ethtool_ops *ops = vlan->real_dev->ethtool_ops;
struct phy_device *phydev = vlan->real_dev->phydev; struct phy_device *phydev = vlan->real_dev->phydev;
if (phydev && phydev->drv && phydev->drv->ts_info) { if (phy_has_tsinfo(phydev)) {
return phydev->drv->ts_info(phydev, info); return phy_ts_info(phydev, info);
} else if (ops->get_ts_info) { } else if (ops->get_ts_info) {
return ops->get_ts_info(vlan->real_dev, info); return ops->get_ts_info(vlan->real_dev, info);
} else { } else {
......
...@@ -108,9 +108,10 @@ config NETWORK_PHY_TIMESTAMPING ...@@ -108,9 +108,10 @@ config NETWORK_PHY_TIMESTAMPING
bool "Timestamping in PHY devices" bool "Timestamping in PHY devices"
select NET_PTP_CLASSIFY select NET_PTP_CLASSIFY
help help
This allows timestamping of network packets by PHYs with This allows timestamping of network packets by PHYs (or
hardware timestamping capabilities. This option adds some other MII bus snooping devices) with hardware timestamping
overhead in the transmit and receive paths. capabilities. This option adds some overhead in the transmit
and receive paths.
If you are unsure how to answer this question, answer N. If you are unsure how to answer this question, answer N.
......
...@@ -187,6 +187,7 @@ static int net_hwtstamp_validate(struct ifreq *ifr) ...@@ -187,6 +187,7 @@ static int net_hwtstamp_validate(struct ifreq *ifr)
case HWTSTAMP_TX_OFF: case HWTSTAMP_TX_OFF:
case HWTSTAMP_TX_ON: case HWTSTAMP_TX_ON:
case HWTSTAMP_TX_ONESTEP_SYNC: case HWTSTAMP_TX_ONESTEP_SYNC:
case HWTSTAMP_TX_ONESTEP_P2P:
tx_type_valid = 1; tx_type_valid = 1;
break; break;
} }
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
static unsigned int classify(const struct sk_buff *skb) static unsigned int classify(const struct sk_buff *skb)
{ {
if (likely(skb->dev && skb->dev->phydev && if (likely(skb->dev && skb->dev->phydev &&
skb->dev->phydev->drv)) skb->dev->phydev->mii_ts))
return ptp_classify_raw(skb); return ptp_classify_raw(skb);
else else
return PTP_CLASS_NONE; return PTP_CLASS_NONE;
...@@ -21,7 +21,7 @@ static unsigned int classify(const struct sk_buff *skb) ...@@ -21,7 +21,7 @@ static unsigned int classify(const struct sk_buff *skb)
void skb_clone_tx_timestamp(struct sk_buff *skb) void skb_clone_tx_timestamp(struct sk_buff *skb)
{ {
struct phy_device *phydev; struct mii_timestamper *mii_ts;
struct sk_buff *clone; struct sk_buff *clone;
unsigned int type; unsigned int type;
...@@ -32,22 +32,22 @@ void skb_clone_tx_timestamp(struct sk_buff *skb) ...@@ -32,22 +32,22 @@ void skb_clone_tx_timestamp(struct sk_buff *skb)
if (type == PTP_CLASS_NONE) if (type == PTP_CLASS_NONE)
return; return;
phydev = skb->dev->phydev; mii_ts = skb->dev->phydev->mii_ts;
if (likely(phydev->drv->txtstamp)) { if (likely(mii_ts->txtstamp)) {
clone = skb_clone_sk(skb); clone = skb_clone_sk(skb);
if (!clone) if (!clone)
return; return;
phydev->drv->txtstamp(phydev, clone, type); mii_ts->txtstamp(mii_ts, clone, type);
} }
} }
EXPORT_SYMBOL_GPL(skb_clone_tx_timestamp); EXPORT_SYMBOL_GPL(skb_clone_tx_timestamp);
bool skb_defer_rx_timestamp(struct sk_buff *skb) bool skb_defer_rx_timestamp(struct sk_buff *skb)
{ {
struct phy_device *phydev; struct mii_timestamper *mii_ts;
unsigned int type; unsigned int type;
if (!skb->dev || !skb->dev->phydev || !skb->dev->phydev->drv) if (!skb->dev || !skb->dev->phydev || !skb->dev->phydev->mii_ts)
return false; return false;
if (skb_headroom(skb) < ETH_HLEN) if (skb_headroom(skb) < ETH_HLEN)
...@@ -62,9 +62,9 @@ bool skb_defer_rx_timestamp(struct sk_buff *skb) ...@@ -62,9 +62,9 @@ bool skb_defer_rx_timestamp(struct sk_buff *skb)
if (type == PTP_CLASS_NONE) if (type == PTP_CLASS_NONE)
return false; return false;
phydev = skb->dev->phydev; mii_ts = skb->dev->phydev->mii_ts;
if (likely(phydev->drv->rxtstamp)) if (likely(mii_ts->rxtstamp))
return phydev->drv->rxtstamp(phydev, skb, type); return mii_ts->rxtstamp(mii_ts, skb, type);
return false; return false;
} }
......
...@@ -2096,8 +2096,8 @@ static int ethtool_get_ts_info(struct net_device *dev, void __user *useraddr) ...@@ -2096,8 +2096,8 @@ static int ethtool_get_ts_info(struct net_device *dev, void __user *useraddr)
memset(&info, 0, sizeof(info)); memset(&info, 0, sizeof(info));
info.cmd = ETHTOOL_GET_TS_INFO; info.cmd = ETHTOOL_GET_TS_INFO;
if (phydev && phydev->drv && phydev->drv->ts_info) { if (phy_has_tsinfo(phydev)) {
err = phydev->drv->ts_info(phydev, &info); err = phy_ts_info(phydev, &info);
} else if (ops->get_ts_info) { } else if (ops->get_ts_info) {
err = ops->get_ts_info(dev, &info); err = ops->get_ts_info(dev, &info);
} else { } else {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment