From: Dmitry Bezrukov <dmitry.bezrukov@xxxxxxxxxxxx> 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.bezrukov@xxxxxxxxxxxx> Signed-off-by: Igor Russkikh <igor.russkikh@xxxxxxxxxxxx> --- 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, ®16); + + /* Power down ethernet PHY */ + if (aqc111_data->dpa) { + reg8 = 0x00; + aqc111_write_cmd_nopm(dev, AQ_PHY_POWER, 0, + 0, 1, ®8); + } 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, ®8); + if (reg8 == 0x00) { + reg8 = 0x02; + aqc111_write_cmd(dev, AQ_PHY_POWER, 0, 0, 1, ®8); + msleep(200); + } + + aq_mdio_read_cmd(dev, AQ_GLB_STD_CTRL_REG, AQ_PHY_GLOBAL_ADDR, + 2, ®16); + 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, ®16); + } + } 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, ®8); @@ -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, ®16); @@ -214,6 +296,17 @@ static int aqc111_stop(struct usbnet *dev) aqc111_write_cmd(dev, AQ_ACCESS_MAC, SFR_RX_CTL, 2, 2, ®16); + /* 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, ®16); + } 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