From: Dmitry Bezrukov <dmitry.bezru...@aquantia.com>

Implement PHY power up/down sequences.
AQC111, depending on FW used, may has PHY being controlled either
directly (dpa = 1) or via vendor command interface (dpa = 0).
Drivers supports both themes.
We determine this from firmware versioning agreement.

Signed-off-by: Dmitry Bezrukov <dmitry.bezru...@aquantia.com>
Signed-off-by: Igor Russkikh <igor.russk...@aquantia.com>
---
 drivers/net/usb/aqc111.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/usb/aqc111.h | 70 ++++++++++++++++++++++++++++++++++++
 2 files changed, 163 insertions(+)

diff --git a/drivers/net/usb/aqc111.c b/drivers/net/usb/aqc111.c
index 22bb259d71fb..30219bb6ddfd 100644
--- a/drivers/net/usb/aqc111.c
+++ b/drivers/net/usb/aqc111.c
@@ -138,15 +138,44 @@ static int aqc111_write_cmd(struct usbnet *dev, u8 cmd, 
u16 value,
        return __aqc111_write_cmd(dev, cmd, value, index, size, data, 0);
 }
 
+static int aq_mdio_read_cmd(struct usbnet *dev, u16 value, u16 index,
+                           u16 size, void *data)
+{
+       return aqc111_read_cmd(dev, AQ_PHY_CMD, value, index, size, data);
+}
+
+static int aq_mdio_write_cmd(struct usbnet *dev, u16 value, u16 index,
+                            u16 size, void *data)
+{
+       return aqc111_write_cmd(dev, AQ_PHY_CMD, value, index, size, data);
+}
+
 static const struct net_device_ops aqc111_netdev_ops = {
        .ndo_open               = usbnet_open,
        .ndo_stop               = usbnet_stop,
 };
 
+static void aqc111_read_fw_version(struct usbnet *dev,
+                                  struct aqc111_data *aqc111_data)
+{
+       aqc111_read_cmd(dev, AQ_ACCESS_MAC, AQ_FW_VER_MAJOR,
+                       1, 1, &aqc111_data->fw_ver.major);
+       aqc111_read_cmd(dev, AQ_ACCESS_MAC, AQ_FW_VER_MINOR,
+                       1, 1, &aqc111_data->fw_ver.minor);
+       aqc111_read_cmd(dev, AQ_ACCESS_MAC, AQ_FW_VER_REV,
+                       1, 1, &aqc111_data->fw_ver.rev);
+
+       if (aqc111_data->fw_ver.major & 0x80)
+               aqc111_data->fw_ver.major &= ~0x80;
+       else
+               aqc111_data->dpa = 1;
+}
+
 static int aqc111_bind(struct usbnet *dev, struct usb_interface *intf)
 {
        int ret;
        struct usb_device *udev = interface_to_usbdev(intf);
+       struct aqc111_data *aqc111_data;
 
        /* Check if vendor configuration */
        if (udev->actconfig->desc.bConfigurationValue != 1) {
@@ -162,8 +191,18 @@ static int aqc111_bind(struct usbnet *dev, struct 
usb_interface *intf)
                return ret;
        }
 
+       aqc111_data = kzalloc(sizeof(*aqc111_data), GFP_KERNEL);
+       if (!aqc111_data)
+               return -ENOMEM;
+
+       /* store aqc111_data pointer in device data field */
+       dev->data[0] = (unsigned long)aqc111_data;
+       memset(aqc111_data, 0, sizeof(*aqc111_data));
+
        dev->net->netdev_ops = &aqc111_netdev_ops;
 
+       aqc111_read_fw_version(dev, aqc111_data);
+
        return 0;
 }
 
@@ -172,6 +211,8 @@ static void aqc111_unbind(struct usbnet *dev, struct 
usb_interface *intf)
        u8 reg8;
        u16 reg16;
 
+       struct aqc111_data *aqc111_data = (struct aqc111_data *)dev->data[0];
+
        /* Force bz */
        reg16 = SFR_PHYPWR_RSTCTL_BZ;
        aqc111_write_cmd_nopm(dev, AQ_ACCESS_MAC, SFR_PHYPWR_RSTCTL,
@@ -179,12 +220,52 @@ static void aqc111_unbind(struct usbnet *dev, struct 
usb_interface *intf)
        reg16 = 0;
        aqc111_write_cmd_nopm(dev, AQ_ACCESS_MAC, SFR_PHYPWR_RSTCTL,
                              2, 2, &reg16);
+
+       /* Power down ethernet PHY */
+       if (aqc111_data->dpa) {
+               reg8 = 0x00;
+               aqc111_write_cmd_nopm(dev, AQ_PHY_POWER, 0,
+                                     0, 1, &reg8);
+       } else {
+               aqc111_data->phy_ops.low_power = 1;
+               aqc111_data->phy_ops.phy_power = 0;
+               aqc111_write_cmd_nopm(dev, AQ_PHY_OPS, 0, 0,
+                                     4, &aqc111_data->phy_ops);
+       }
+
+       kfree(aqc111_data);
 }
 
 static int aqc111_reset(struct usbnet *dev)
 {
        u8 reg8 = 0;
        u16 reg16 = 0;
+       struct aqc111_data *aqc111_data = (struct aqc111_data *)dev->data[0];
+
+       /* Power up ethernet PHY */
+       aqc111_data->phy_ops.phy_ctrl1 = 0;
+       aqc111_data->phy_ops.phy_ctrl2 = 0;
+
+       aqc111_data->phy_ops.phy_power = 1;
+       if (aqc111_data->dpa) {
+               aqc111_read_cmd(dev, AQ_PHY_POWER, 0, 0, 1, &reg8);
+               if (reg8 == 0x00) {
+                       reg8 = 0x02;
+                       aqc111_write_cmd(dev, AQ_PHY_POWER, 0, 0, 1, &reg8);
+                       msleep(200);
+               }
+
+               aq_mdio_read_cmd(dev, AQ_GLB_STD_CTRL_REG, AQ_PHY_GLOBAL_ADDR,
+                                2, &reg16);
+               if (reg16 & AQ_PHY_LOW_POWER_MODE) {
+                       reg16 &= ~AQ_PHY_LOW_POWER_MODE;
+                       aq_mdio_write_cmd(dev, AQ_GLB_STD_CTRL_REG,
+                                         AQ_PHY_GLOBAL_ADDR, 2, &reg16);
+               }
+       } else {
+               aqc111_write_cmd(dev, AQ_PHY_OPS, 0, 0,
+                                4, &aqc111_data->phy_ops);
+       }
 
        reg8 = 0xFF;
        aqc111_write_cmd(dev, AQ_ACCESS_MAC, SFR_BM_INT_MASK, 1, 1, &reg8);
@@ -204,6 +285,7 @@ static int aqc111_reset(struct usbnet *dev)
 static int aqc111_stop(struct usbnet *dev)
 {
        u16 reg16 = 0;
+       struct aqc111_data *aqc111_data = (struct aqc111_data *)dev->data[0];
 
        aqc111_read_cmd(dev, AQ_ACCESS_MAC, SFR_MEDIUM_STATUS_MODE,
                        2, 2, &reg16);
@@ -214,6 +296,17 @@ static int aqc111_stop(struct usbnet *dev)
        aqc111_write_cmd(dev, AQ_ACCESS_MAC, SFR_RX_CTL,
                         2, 2, &reg16);
 
+       /* Put PHY to low power*/
+       if (aqc111_data->dpa) {
+               reg16 = AQ_PHY_LOW_POWER_MODE;
+               aq_mdio_write_cmd(dev, AQ_GLB_STD_CTRL_REG, AQ_PHY_GLOBAL_ADDR,
+                                 2, &reg16);
+       } else {
+               aqc111_data->phy_ops.low_power = 1;
+               aqc111_write_cmd(dev, AQ_PHY_OPS, 0, 0,
+                                4, &aqc111_data->phy_ops);
+       }
+
        return 0;
 }
 
diff --git a/drivers/net/usb/aqc111.h b/drivers/net/usb/aqc111.h
index 6b34202fa22b..8738d2c4ae90 100644
--- a/drivers/net/usb/aqc111.h
+++ b/drivers/net/usb/aqc111.h
@@ -12,6 +12,17 @@
 
 #define AQ_ACCESS_MAC                  0x01
 #define AQ_PHY_POWER                   0x31
+#define AQ_PHY_CMD                     0x32
+#define AQ_PHY_OPS                     0x61
+
+#define AQC111_PHY_ID                  0x00
+#define AQ_PHY_ADDR(mmd)               ((AQC111_PHY_ID << 8) | mmd)
+
+#define AQ_PHY_GLOBAL_MMD              0x1E
+#define AQ_PHY_GLOBAL_ADDR             AQ_PHY_ADDR(AQ_PHY_GLOBAL_MMD)
+
+#define AQ_GLB_STD_CTRL_REG            0x0000
+       #define AQ_PHY_LOW_POWER_MODE           0x0800
 
 #define AQ_USB_PHY_SET_TIMEOUT         10000
 #define AQ_USB_SET_TIMEOUT             4000
@@ -102,6 +113,65 @@
        #define SFR_BULK_OUT_FLUSH_EN           0x01
        #define SFR_BULK_OUT_EFF_EN             0x02
 
+#define AQ_FW_VER_MAJOR                        0xDA
+#define AQ_FW_VER_MINOR                        0xDB
+#define AQ_FW_VER_REV                  0xDC
+
+/******************************************************************************/
+
+struct aqc111_phy_options {
+       union {
+               struct {
+                       u8 adv_100M:    1;
+                       u8 adv_1G:      1;
+                       u8 adv_2G5:     1;
+                       u8 adv_5G:      1;
+                       u8 rsvd1:       4;
+               };
+               u8 advertising;
+       };
+       union {
+               struct {
+                       u8 eee_100M:    1;
+                       u8 eee_1G:      1;
+                       u8 eee_2G5:     1;
+                       u8 eee_5G:      1;
+                       u8 rsvd2:       4;
+               };
+               u8 eee;
+       };
+       union {
+               struct {
+                       u8 pause:       1;
+                       u8 asym_pause:  1;
+                       u8 low_power:   1;
+                       u8 phy_power:   1;
+                       u8 wol:         1;
+                       u8 downshift:   1;
+                       u8 rsvd4:       2;
+               };
+               u8 phy_ctrl1;
+       };
+       union {
+               struct {
+               u8 dsh_ret_cnt: 4;
+               u8 magic_packet:1;
+               u8 rsvd5:       3;
+               };
+               u8 phy_ctrl2;
+       };
+};
+
+struct aqc111_data {
+       struct {
+               u8 major;
+               u8 minor;
+               u8 rev;
+       } fw_ver;
+       u8 dpa; /*direct PHY access*/
+       struct aqc111_phy_options phy_ops;
+} __packed;
+
 static struct {
        unsigned char ctrl;
        unsigned char timer_l;
-- 
2.7.4

Reply via email to