summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/amd/xgbe/xgbe-drv.c
diff options
context:
space:
mode:
authorLendacky, Thomas2014-08-05 20:30:44 +0200
committerDavid S. Miller2014-08-06 01:47:02 +0200
commit88131a812b16b45cf999e577ad8d89b41ad606e3 (patch)
tree19519ab7ec4444ccdd97e05b22a126ee6f511985 /drivers/net/ethernet/amd/xgbe/xgbe-drv.c
parentamd-xgbe: Use dma_set_mask_and_coherent to set DMA mask (diff)
downloadkernel-qcow2-linux-88131a812b16b45cf999e577ad8d89b41ad606e3.tar.gz
kernel-qcow2-linux-88131a812b16b45cf999e577ad8d89b41ad606e3.tar.xz
kernel-qcow2-linux-88131a812b16b45cf999e577ad8d89b41ad606e3.zip
amd-xgbe: Perform phy connect/disconnect at dev open/stop
A change added to the mdiobus/phy api added a module_get/module_put during phy connect/disconnect processing. Currently, the driver performs a phy connect during module probe and a phy disconnect during module remove. With the addition of the module_get during phy connect the amd-xgbe module use count is incremented and can no longer be unloaded. Move the phy connect/disconnect from the driver probe/remove functions to the net_device_ops ndo_open/ndo_stop functions. This allows the module use count to be decremented when the device(s) are brought down and allows the module to be unloaded. Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/ethernet/amd/xgbe/xgbe-drv.c')
-rw-r--r--drivers/net/ethernet/amd/xgbe/xgbe-drv.c122
1 files changed, 121 insertions, 1 deletions
diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-drv.c b/drivers/net/ethernet/amd/xgbe/xgbe-drv.c
index 3bf3c0194ad3..1f5487f4888c 100644
--- a/drivers/net/ethernet/amd/xgbe/xgbe-drv.c
+++ b/drivers/net/ethernet/amd/xgbe/xgbe-drv.c
@@ -122,6 +122,7 @@
#include <linux/clk.h>
#include <linux/if_ether.h>
#include <linux/net_tstamp.h>
+#include <linux/phy.h>
#include "xgbe.h"
#include "xgbe-common.h"
@@ -521,6 +522,114 @@ static void xgbe_free_rx_skbuff(struct xgbe_prv_data *pdata)
DBGPR("<--xgbe_free_rx_skbuff\n");
}
+static void xgbe_adjust_link(struct net_device *netdev)
+{
+ struct xgbe_prv_data *pdata = netdev_priv(netdev);
+ struct xgbe_hw_if *hw_if = &pdata->hw_if;
+ struct phy_device *phydev = pdata->phydev;
+ int new_state = 0;
+
+ if (phydev == NULL)
+ return;
+
+ if (phydev->link) {
+ /* Flow control support */
+ if (pdata->pause_autoneg) {
+ if (phydev->pause || phydev->asym_pause) {
+ pdata->tx_pause = 1;
+ pdata->rx_pause = 1;
+ } else {
+ pdata->tx_pause = 0;
+ pdata->rx_pause = 0;
+ }
+ }
+
+ if (pdata->tx_pause != pdata->phy_tx_pause) {
+ hw_if->config_tx_flow_control(pdata);
+ pdata->phy_tx_pause = pdata->tx_pause;
+ }
+
+ if (pdata->rx_pause != pdata->phy_rx_pause) {
+ hw_if->config_rx_flow_control(pdata);
+ pdata->phy_rx_pause = pdata->rx_pause;
+ }
+
+ /* Speed support */
+ if (phydev->speed != pdata->phy_speed) {
+ new_state = 1;
+
+ switch (phydev->speed) {
+ case SPEED_10000:
+ hw_if->set_xgmii_speed(pdata);
+ break;
+
+ case SPEED_2500:
+ hw_if->set_gmii_2500_speed(pdata);
+ break;
+
+ case SPEED_1000:
+ hw_if->set_gmii_speed(pdata);
+ break;
+ }
+ pdata->phy_speed = phydev->speed;
+ }
+
+ if (phydev->link != pdata->phy_link) {
+ new_state = 1;
+ pdata->phy_link = 1;
+ }
+ } else if (pdata->phy_link) {
+ new_state = 1;
+ pdata->phy_link = 0;
+ pdata->phy_speed = SPEED_UNKNOWN;
+ }
+
+ if (new_state)
+ phy_print_status(phydev);
+}
+
+static int xgbe_phy_init(struct xgbe_prv_data *pdata)
+{
+ struct net_device *netdev = pdata->netdev;
+ struct phy_device *phydev = pdata->phydev;
+ int ret;
+
+ pdata->phy_link = -1;
+ pdata->phy_speed = SPEED_UNKNOWN;
+ pdata->phy_tx_pause = pdata->tx_pause;
+ pdata->phy_rx_pause = pdata->rx_pause;
+
+ ret = phy_connect_direct(netdev, phydev, &xgbe_adjust_link,
+ pdata->phy_mode);
+ if (ret) {
+ netdev_err(netdev, "phy_connect_direct failed\n");
+ return ret;
+ }
+
+ if (!phydev->drv || (phydev->drv->phy_id == 0)) {
+ netdev_err(netdev, "phy_id not valid\n");
+ ret = -ENODEV;
+ goto err_phy_connect;
+ }
+ DBGPR(" phy_connect_direct succeeded for PHY %s, link=%d\n",
+ dev_name(&phydev->dev), phydev->link);
+
+ return 0;
+
+err_phy_connect:
+ phy_disconnect(phydev);
+
+ return ret;
+}
+
+static void xgbe_phy_exit(struct xgbe_prv_data *pdata)
+{
+ if (!pdata->phydev)
+ return;
+
+ phy_disconnect(pdata->phydev);
+}
+
int xgbe_powerdown(struct net_device *netdev, unsigned int caller)
{
struct xgbe_prv_data *pdata = netdev_priv(netdev);
@@ -986,11 +1095,16 @@ static int xgbe_open(struct net_device *netdev)
DBGPR("-->xgbe_open\n");
+ /* Initialize the phy */
+ ret = xgbe_phy_init(pdata);
+ if (ret)
+ return ret;
+
/* Enable the clocks */
ret = clk_prepare_enable(pdata->sysclk);
if (ret) {
netdev_alert(netdev, "dma clk_prepare_enable failed\n");
- return ret;
+ goto err_phy_init;
}
ret = clk_prepare_enable(pdata->ptpclk);
@@ -1047,6 +1161,9 @@ err_ptpclk:
err_sysclk:
clk_disable_unprepare(pdata->sysclk);
+err_phy_init:
+ xgbe_phy_exit(pdata);
+
return ret;
}
@@ -1077,6 +1194,9 @@ static int xgbe_close(struct net_device *netdev)
clk_disable_unprepare(pdata->ptpclk);
clk_disable_unprepare(pdata->sysclk);
+ /* Release the phy */
+ xgbe_phy_exit(pdata);
+
DBGPR("<--xgbe_close\n");
return 0;