@@ -28,6 +28,7 @@ config TEHUTI_TN40
depends on PCI
select PAGE_POOL
select FW_LOADER
+ select PHYLINK
help
This driver supports 10G Ethernet adapters using Tehuti Networks
TN40xx chips. Currently, adapters with Applied Micro Circuits
@@ -5,5 +5,5 @@
obj-$(CONFIG_TEHUTI) += tehuti.o
-tn40xx-y := tn40.o tn40_mdio.o
+tn40xx-y := tn40.o tn40_mdio.o tn40_phy.o
obj-$(CONFIG_TEHUTI_TN40) += tn40xx.o
@@ -7,6 +7,7 @@
#include <linux/if_vlan.h>
#include <linux/netdevice.h>
#include <linux/pci.h>
+#include <linux/phylink.h>
#include <linux/vmalloc.h>
#include <net/page_pool/helpers.h>
@@ -942,7 +943,7 @@ static void tn40_tx_push_desc_safe(struct tn40_priv *priv, void *data, int size)
}
}
-static int tn40_set_link_speed(struct tn40_priv *priv, u32 speed)
+int tn40_set_link_speed(struct tn40_priv *priv, u32 speed)
{
u32 val;
int i;
@@ -1373,6 +1374,9 @@ static int tn40_close(struct net_device *ndev)
{
struct tn40_priv *priv = netdev_priv(ndev);
+ phylink_stop(priv->phylink);
+ phylink_disconnect_phy(priv->phylink);
+
napi_disable(&priv->napi);
netif_napi_del(&priv->napi);
tn40_stop(priv);
@@ -1384,13 +1388,20 @@ static int tn40_open(struct net_device *dev)
struct tn40_priv *priv = netdev_priv(dev);
int ret;
+ ret = phylink_connect_phy(priv->phylink, priv->phydev);
+ if (ret) {
+ netdev_err(dev, "failed to connect to phy %d\n", ret);
+ return ret;
+ }
tn40_sw_reset(priv);
ret = tn40_start(priv);
if (ret) {
+ phylink_disconnect_phy(priv->phylink);
netdev_err(dev, "failed to start %d\n", ret);
return ret;
}
napi_enable(&priv->napi);
+ phylink_start(priv->phylink);
netif_start_queue(priv->ndev);
return 0;
}
@@ -1667,6 +1678,12 @@ static int tn40_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto err_unset_drvdata;
}
+ ret = tn40_mdiobus_init(priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to initialize mdio bus.\n");
+ goto err_free_irq;
+ }
+
priv->stats_flag =
((tn40_read_reg(priv, TN40_FPGA_VER) & 0xFFF) != 308);
@@ -1675,19 +1692,26 @@ static int tn40_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
TN40_IR_TMR1;
tn40_mac_init(priv);
+ ret = tn40_phy_register(priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to set up PHY.\n");
+ goto err_free_irq;
+ }
ret = tn40_priv_init(priv);
if (ret) {
dev_err(&pdev->dev, "failed to initialize tn40_priv.\n");
- goto err_free_irq;
+ goto err_unregister_phydev;
}
ret = register_netdev(ndev);
if (ret) {
dev_err(&pdev->dev, "failed to register netdev.\n");
- goto err_free_irq;
+ goto err_unregister_phydev;
}
return 0;
+err_unregister_phydev:
+ tn40_phy_unregister(priv);
err_free_irq:
pci_free_irq_vectors(pdev);
err_unset_drvdata:
@@ -1708,6 +1732,7 @@ static void tn40_remove(struct pci_dev *pdev)
unregister_netdev(ndev);
+ tn40_phy_unregister(priv);
pci_free_irq_vectors(priv->pdev);
pci_set_drvdata(pdev, NULL);
iounmap(priv->regs);
@@ -141,6 +141,9 @@ struct tn40_priv {
char *b0_va; /* Virtual address of buffer */
struct mii_bus *mdio;
+ struct phy_device *phydev;
+ struct phylink *phylink;
+ struct phylink_config phylink_config;
};
/* RX FREE descriptor - 64bit */
@@ -218,6 +221,11 @@ static inline void tn40_write_reg(struct tn40_priv *priv, u32 reg, u32 val)
writel(val, priv->regs + reg);
}
+int tn40_set_link_speed(struct tn40_priv *priv, u32 speed);
+
int tn40_mdiobus_init(struct tn40_priv *priv);
+int tn40_phy_register(struct tn40_priv *priv);
+void tn40_phy_unregister(struct tn40_priv *priv);
+
#endif /* _TN40XX_H */
new file mode 100644
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) Tehuti Networks Ltd. */
+
+#include <linux/netdevice.h>
+#include <linux/pci.h>
+#include <linux/phylink.h>
+
+#include "tn40.h"
+
+static struct tn40_priv *tn40_config_to_priv(struct phylink_config *config)
+{
+ return container_of(config, struct tn40_priv, phylink_config);
+}
+
+static void tn40_link_up(struct phylink_config *config, struct phy_device *phy,
+ unsigned int mode, phy_interface_t interface,
+ int speed, int duplex, bool tx_pause, bool rx_pause)
+{
+ struct tn40_priv *priv = tn40_config_to_priv(config);
+
+ tn40_set_link_speed(priv, speed);
+ netif_wake_queue(priv->ndev);
+}
+
+static void tn40_link_down(struct phylink_config *config, unsigned int mode,
+ phy_interface_t interface)
+{
+ struct tn40_priv *priv = tn40_config_to_priv(config);
+
+ netif_stop_queue(priv->ndev);
+ tn40_set_link_speed(priv, 0);
+}
+
+static void tn40_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+}
+
+static const struct phylink_mac_ops tn40_mac_ops = {
+ .mac_config = tn40_mac_config,
+ .mac_link_up = tn40_link_up,
+ .mac_link_down = tn40_link_down,
+};
+
+int tn40_phy_register(struct tn40_priv *priv)
+{
+ struct phylink_config *config;
+ struct phy_device *phydev;
+ struct phylink *phylink;
+
+ phydev = phy_find_first(priv->mdio);
+ if (!phydev) {
+ dev_err(&priv->pdev->dev, "PHY isn't found\n");
+ return -ENODEV;
+ }
+
+ config = &priv->phylink_config;
+ config->dev = &priv->ndev->dev;
+ config->type = PHYLINK_NETDEV;
+ config->mac_capabilities = MAC_10000FD;
+ __set_bit(PHY_INTERFACE_MODE_XAUI, config->supported_interfaces);
+
+ phylink = phylink_create(config, NULL, PHY_INTERFACE_MODE_XAUI,
+ &tn40_mac_ops);
+ if (IS_ERR(phylink))
+ return PTR_ERR(phylink);
+
+ priv->phydev = phydev;
+ priv->phylink = phylink;
+ return 0;
+}
+
+void tn40_phy_unregister(struct tn40_priv *priv)
+{
+ phylink_destroy(priv->phylink);
+}