From: Yana Esina <yana.es...@aquantia.com>

Support of Energy-Efficient Ethernet to aQuantia NIC's via ethtool
(according to the IEEE 802.3az specifications)

Signed-off-by: Yana Esina <yana.es...@aquantia.com>
Signed-off-by: Nikita Danilov <nikita.dani...@aquantia.com>
Tested-by: Nikita Danilov <nikita.dani...@aquantia.com>
Signed-off-by: Igor Russkikh <igor.russk...@aquantia.com>
---
 drivers/net/ethernet/aquantia/atlantic/aq_common.h |  5 ++
 .../net/ethernet/aquantia/atlantic/aq_ethtool.c    | 77 +++++++++++++++++++
 drivers/net/ethernet/aquantia/atlantic/aq_hw.h     |  5 ++
 drivers/net/ethernet/aquantia/atlantic/aq_nic.h    |  1 +
 .../aquantia/atlantic/hw_atl/hw_atl_utils.c        |  2 +
 .../aquantia/atlantic/hw_atl/hw_atl_utils.h        | 13 ++++
 .../aquantia/atlantic/hw_atl/hw_atl_utils_fw2x.c   | 86 ++++++++++++++++++++++
 7 files changed, 189 insertions(+)

diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_common.h 
b/drivers/net/ethernet/aquantia/atlantic/aq_common.h
index d52b088ff8f0..becb578211ed 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_common.h
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_common.h
@@ -57,4 +57,9 @@
 #define AQ_NIC_RATE_1G         BIT(4)
 #define AQ_NIC_RATE_100M       BIT(5)
 
+#define AQ_NIC_RATE_EEE_10G    BIT(6)
+#define AQ_NIC_RATE_EEE_5G     BIT(7)
+#define AQ_NIC_RATE_EEE_2GS    BIT(8)
+#define AQ_NIC_RATE_EEE_1G     BIT(9)
+
 #endif /* AQ_COMMON_H */
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c 
b/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
index b88be5e5f0a2..22dd4fbd34d7 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
@@ -315,6 +315,81 @@ static int aq_ethtool_set_wol(struct net_device *ndev,
        return err;
 }
 
+static enum hw_atl_fw2x_rate eee_mask_to_ethtool_mask(u32 speed)
+{
+       u32 rate = 0;
+
+       if (speed & AQ_NIC_RATE_EEE_10G)
+               rate |= SUPPORTED_10000baseT_Full;
+
+       if (speed & AQ_NIC_RATE_EEE_2GS)
+               rate |= SUPPORTED_2500baseX_Full;
+
+       if (speed & AQ_NIC_RATE_EEE_1G)
+               rate |= SUPPORTED_1000baseT_Full;
+
+       return rate;
+}
+
+static int aq_ethtool_get_eee(struct net_device *ndev, struct ethtool_eee *eee)
+{
+       struct aq_nic_s *aq_nic = netdev_priv(ndev);
+       u32 rate, supported_rates;
+       int err = 0;
+
+       if (!aq_nic->aq_fw_ops->get_eee_rate)
+               return -EOPNOTSUPP;
+
+       err = aq_nic->aq_fw_ops->get_eee_rate(aq_nic->aq_hw, &rate,
+                                             &supported_rates);
+       if (err < 0)
+               return err;
+
+       eee->supported = eee_mask_to_ethtool_mask(supported_rates);
+
+       if (aq_nic->aq_nic_cfg.eee_speeds)
+               eee->advertised = eee->supported;
+
+       eee->lp_advertised = eee_mask_to_ethtool_mask(rate);
+
+       eee->eee_enabled = !!eee->advertised;
+
+       eee->tx_lpi_enabled = eee->eee_enabled;
+       if (eee->advertised & eee->lp_advertised)
+               eee->eee_active = true;
+
+       return 0;
+}
+
+static int aq_ethtool_set_eee(struct net_device *ndev, struct ethtool_eee *eee)
+{
+       struct aq_nic_s *aq_nic = netdev_priv(ndev);
+       u32 rate, supported_rates;
+       struct aq_nic_cfg_s *cfg;
+       int err = 0;
+
+       cfg = aq_nic_get_cfg(aq_nic);
+
+       if (unlikely(!aq_nic->aq_fw_ops->get_eee_rate ||
+                    !aq_nic->aq_fw_ops->set_eee_rate))
+               return -EOPNOTSUPP;
+
+       err = aq_nic->aq_fw_ops->get_eee_rate(aq_nic->aq_hw, &rate,
+                                             &supported_rates);
+       if (err < 0)
+               return err;
+
+       if (eee->eee_enabled) {
+               rate = supported_rates;
+               cfg->eee_speeds = rate;
+       } else {
+               rate = 0;
+               cfg->eee_speeds = 0;
+       }
+
+       return aq_nic->aq_fw_ops->set_eee_rate(aq_nic->aq_hw, rate);
+}
+
 static int aq_ethtool_nway_reset(struct net_device *ndev)
 {
        struct aq_nic_s *aq_nic = netdev_priv(ndev);
@@ -438,6 +513,8 @@ const struct ethtool_ops aq_ethtool_ops = {
        .nway_reset          = aq_ethtool_nway_reset,
        .get_ringparam       = aq_get_ringparam,
        .set_ringparam       = aq_set_ringparam,
+       .get_eee             = aq_ethtool_get_eee,
+       .set_eee             = aq_ethtool_set_eee,
        .get_pauseparam      = aq_ethtool_get_pauseparam,
        .set_pauseparam      = aq_ethtool_set_pauseparam,
        .get_rxfh_key_size   = aq_ethtool_get_rss_key_size,
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_hw.h 
b/drivers/net/ethernet/aquantia/atlantic/aq_hw.h
index 9050b40d4f58..908f19fe19b3 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_hw.h
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_hw.h
@@ -230,6 +230,11 @@ struct aq_fw_ops {
 
        int (*set_power)(struct aq_hw_s *self, unsigned int power_state,
                         u8 *mac);
+
+       int (*set_eee_rate)(struct aq_hw_s *self, u32 speed);
+
+       int (*get_eee_rate)(struct aq_hw_s *self, u32 *rate,
+                           u32 *supported_rates);
 };
 
 #endif /* AQ_HW_H */
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_nic.h 
b/drivers/net/ethernet/aquantia/atlantic/aq_nic.h
index 2069cbb6e1a1..c1582f4e8e1b 100644
--- a/drivers/net/ethernet/aquantia/atlantic/aq_nic.h
+++ b/drivers/net/ethernet/aquantia/atlantic/aq_nic.h
@@ -45,6 +45,7 @@ struct aq_nic_cfg_s {
        bool is_lro;
        u8  tcs;
        struct aq_rss_parameters aq_rss;
+       u32 eee_speeds;
 };
 
 #define AQ_NIC_FLAG_STARTED     0x00000004U
diff --git a/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.c 
b/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.c
index c6fe4a58e047..bb1561c6d25a 100644
--- a/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.c
+++ b/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.c
@@ -916,5 +916,7 @@ const struct aq_fw_ops aq_fw_1x_ops = {
        .update_link_status = hw_atl_utils_mpi_get_link_status,
        .update_stats = hw_atl_utils_update_stats,
        .set_power = aq_fw1x_set_power,
+       .set_eee_rate = NULL,
+       .get_eee_rate = NULL,
        .set_flow_control = NULL,
 };
diff --git a/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.h 
b/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.h
index 48bebb686819..6ced102d02db 100644
--- a/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.h
+++ b/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.h
@@ -171,9 +171,22 @@ struct __packed hw_aq_atl_utils_mbox_header {
        u32 error;
 };
 
+struct __packed hw_aq_info {
+       u8 reserved[6];
+       u16 phy_fault_code;
+       u16 phy_temperature;
+       u8 cable_len;
+       u8 reserved1;
+       u32 cable_diag_data[4];
+       u8 reserved2[32];
+       u32 caps_lo;
+       u32 caps_hi;
+};
+
 struct __packed hw_aq_atl_utils_mbox {
        struct hw_aq_atl_utils_mbox_header header;
        struct hw_atl_stats_s stats;
+       struct hw_aq_info info;
 };
 
 /* fw2x */
diff --git a/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils_fw2x.c 
b/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils_fw2x.c
index 9fc187f57ed4..27bed5dd5295 100644
--- a/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils_fw2x.c
+++ b/drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils_fw2x.c
@@ -40,6 +40,11 @@
 #define HW_ATL_FW2X_CTRL_ASYMMETRIC_PAUSE BIT(CTRL_ASYMMETRIC_PAUSE)
 #define HW_ATL_FW2X_CTRL_FORCE_RECONNECT  BIT(CTRL_FORCE_RECONNECT)
 
+#define HW_ATL_FW2X_CAP_EEE_1G_MASK      BIT(CAPS_HI_1000BASET_FD_EEE)
+#define HW_ATL_FW2X_CAP_EEE_2G5_MASK     BIT(CAPS_HI_2P5GBASET_FD_EEE)
+#define HW_ATL_FW2X_CAP_EEE_5G_MASK      BIT(CAPS_HI_5GBASET_FD_EEE)
+#define HW_ATL_FW2X_CAP_EEE_10G_MASK     BIT(CAPS_HI_10GBASET_FD_EEE)
+
 #define HAL_ATLANTIC_WOL_FILTERS_COUNT   8
 #define HAL_ATLANTIC_UTILS_FW2X_MSG_WOL  0x0E
 
@@ -115,6 +120,38 @@ static enum hw_atl_fw2x_rate 
link_speed_mask_2fw2x_ratemask(u32 speed)
        return rate;
 }
 
+static u32 fw2x_to_eee_mask(u32 speed)
+{
+       u32 rate = 0;
+
+       if (speed & HW_ATL_FW2X_CAP_EEE_10G_MASK)
+               rate |= AQ_NIC_RATE_EEE_10G;
+       if (speed & HW_ATL_FW2X_CAP_EEE_5G_MASK)
+               rate |= AQ_NIC_RATE_EEE_5G;
+       if (speed & HW_ATL_FW2X_CAP_EEE_2G5_MASK)
+               rate |= AQ_NIC_RATE_EEE_2GS;
+       if (speed & HW_ATL_FW2X_CAP_EEE_1G_MASK)
+               rate |= AQ_NIC_RATE_EEE_1G;
+
+       return rate;
+}
+
+static u32 eee_mask_to_fw2x(u32 speed)
+{
+       u32 rate = 0;
+
+       if (speed & AQ_NIC_RATE_EEE_10G)
+               rate |= HW_ATL_FW2X_CAP_EEE_10G_MASK;
+       if (speed & AQ_NIC_RATE_EEE_5G)
+               rate |= HW_ATL_FW2X_CAP_EEE_5G_MASK;
+       if (speed & AQ_NIC_RATE_EEE_2GS)
+               rate |= HW_ATL_FW2X_CAP_EEE_2G5_MASK;
+       if (speed & AQ_NIC_RATE_EEE_1G)
+               rate |= HW_ATL_FW2X_CAP_EEE_1G_MASK;
+
+       return rate;
+}
+
 static int aq_fw2x_set_link_speed(struct aq_hw_s *self, u32 speed)
 {
        u32 val = link_speed_mask_2fw2x_ratemask(speed);
@@ -137,14 +174,27 @@ static void aq_fw2x_set_mpi_flow_control(struct aq_hw_s 
*self, u32 *mpi_state)
                *mpi_state &= ~BIT(CAPS_HI_ASYMMETRIC_PAUSE);
 }
 
+static void aq_fw2x_upd_eee_rate_bits(struct aq_hw_s *self, u32 *mpi_opts,
+                                     u32 eee_speeds)
+{
+       *mpi_opts &= ~(HW_ATL_FW2X_CAP_EEE_1G_MASK |
+                      HW_ATL_FW2X_CAP_EEE_2G5_MASK |
+                      HW_ATL_FW2X_CAP_EEE_5G_MASK |
+                      HW_ATL_FW2X_CAP_EEE_10G_MASK);
+
+       *mpi_opts |= eee_mask_to_fw2x(eee_speeds);
+}
+
 static int aq_fw2x_set_state(struct aq_hw_s *self,
                             enum hal_atl_utils_fw_state_e state)
 {
        u32 mpi_state = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
+       struct aq_nic_cfg_s *cfg = self->aq_nic_cfg;
 
        switch (state) {
        case MPI_INIT:
                mpi_state &= ~BIT(CAPS_HI_LINK_DROP);
+               aq_fw2x_upd_eee_rate_bits(self, &mpi_state, cfg->eee_speeds);
                aq_fw2x_set_mpi_flow_control(self, &mpi_state);
                break;
        case MPI_DEINIT:
@@ -347,6 +397,40 @@ static int aq_fw2x_set_power(struct aq_hw_s *self, 
unsigned int power_state,
        return err;
 }
 
+static int aq_fw2x_set_eee_rate(struct aq_hw_s *self, u32 speed)
+{
+       u32 mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
+
+       aq_fw2x_upd_eee_rate_bits(self, &mpi_opts, speed);
+
+       aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);
+
+       return 0;
+}
+
+static int aq_fw2x_get_eee_rate(struct aq_hw_s *self, u32 *rate,
+                               u32 *supported_rates)
+{
+       u32 mpi_state;
+       u32 caps_hi;
+       int err = 0;
+       u32 addr = self->mbox_addr + offsetof(struct hw_aq_atl_utils_mbox, 
info) +
+                  offsetof(struct hw_aq_info, caps_hi);
+
+       err = hw_atl_utils_fw_downld_dwords(self, addr, &caps_hi,
+                                           sizeof(caps_hi) / sizeof(u32));
+
+       if (err)
+               return err;
+
+       *supported_rates = fw2x_to_eee_mask(caps_hi);
+
+       mpi_state = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_STATE2_ADDR);
+       *rate = fw2x_to_eee_mask(mpi_state);
+
+       return err;
+}
+
 static int aq_fw2x_renegotiate(struct aq_hw_s *self)
 {
        u32 mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
@@ -380,5 +464,7 @@ const struct aq_fw_ops aq_fw_2x_ops = {
        .update_link_status = aq_fw2x_update_link_status,
        .update_stats = aq_fw2x_update_stats,
        .set_power = aq_fw2x_set_power,
+       .set_eee_rate = aq_fw2x_set_eee_rate,
+       .get_eee_rate = aq_fw2x_get_eee_rate,
        .set_flow_control   = aq_fw2x_set_flow_control,
 };
-- 
2.7.4

Reply via email to