net: aquantia: implement WOL support
authorYana Esina <yana.esina@aquantia.com>
Mon, 10 Sep 2018 09:39:30 +0000 (12:39 +0300)
committerDavid S. Miller <davem@davemloft.net>
Wed, 12 Sep 2018 06:41:02 +0000 (23:41 -0700)
Add WOL support. Currently only magic packet
(ethtool -s <ethX> wol g) feature is implemented.

Remove hw_set_power and move that to FW_OPS set_power:
because WOL configuration behaves differently on 1x and 2x
firmwares

Signed-off-by: Yana Esina <yana.esina@aquantia.com>
Signed-off-by: Nikita Danilov <nikita.danilov@aquantia.com>
Tested-by: Nikita Danilov <nikita.danilov@aquantia.com>
Signed-off-by: Igor Russkikh <igor.russkikh@aquantia.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
drivers/net/ethernet/aquantia/atlantic/aq_hw.h
drivers/net/ethernet/aquantia/atlantic/aq_nic.c
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_a0.c
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_b0.c
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.c
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.h
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils_fw2x.c

index 08c9fa6ca71f273b5695887005ce6a5025fbc9c5..b88be5e5f0a2f814e8257bfe0fbfff2860d3c67c 100644 (file)
@@ -285,6 +285,36 @@ static int aq_ethtool_set_coalesce(struct net_device *ndev,
        return aq_nic_update_interrupt_moderation_settings(aq_nic);
 }
 
+static void aq_ethtool_get_wol(struct net_device *ndev,
+                              struct ethtool_wolinfo *wol)
+{
+       struct aq_nic_s *aq_nic = netdev_priv(ndev);
+       struct aq_nic_cfg_s *cfg = aq_nic_get_cfg(aq_nic);
+
+       wol->supported = WAKE_MAGIC;
+       wol->wolopts = 0;
+
+       if (cfg->wol)
+               wol->wolopts |= WAKE_MAGIC;
+}
+
+static int aq_ethtool_set_wol(struct net_device *ndev,
+                             struct ethtool_wolinfo *wol)
+{
+       struct pci_dev *pdev = to_pci_dev(ndev->dev.parent);
+       struct aq_nic_s *aq_nic = netdev_priv(ndev);
+       struct aq_nic_cfg_s *cfg = aq_nic_get_cfg(aq_nic);
+       int err = 0;
+
+       if (wol->wolopts & WAKE_MAGIC)
+               cfg->wol |= AQ_NIC_WOL_ENABLED;
+       else
+               cfg->wol &= ~AQ_NIC_WOL_ENABLED;
+       err = device_set_wakeup_enable(&pdev->dev, wol->wolopts);
+
+       return err;
+}
+
 static int aq_ethtool_nway_reset(struct net_device *ndev)
 {
        struct aq_nic_s *aq_nic = netdev_priv(ndev);
@@ -403,6 +433,8 @@ const struct ethtool_ops aq_ethtool_ops = {
        .get_drvinfo         = aq_ethtool_get_drvinfo,
        .get_strings         = aq_ethtool_get_strings,
        .get_rxfh_indir_size = aq_ethtool_get_rss_indir_size,
+       .get_wol             = aq_ethtool_get_wol,
+       .set_wol             = aq_ethtool_set_wol,
        .nway_reset          = aq_ethtool_nway_reset,
        .get_ringparam       = aq_get_ringparam,
        .set_ringparam       = aq_set_ringparam,
index 5c00671f248df2087ac8665c08214feda9c744d1..9050b40d4f580cbf86af1ae3e7e817a6794a1abf 100644 (file)
@@ -204,7 +204,6 @@ struct aq_hw_ops {
 
        int (*hw_get_fw_version)(struct aq_hw_s *self, u32 *fw_version);
 
-       int (*hw_set_power)(struct aq_hw_s *self, unsigned int power_state);
 };
 
 struct aq_fw_ops {
@@ -228,6 +227,9 @@ struct aq_fw_ops {
        int (*update_stats)(struct aq_hw_s *self);
 
        int (*set_flow_control)(struct aq_hw_s *self);
+
+       int (*set_power)(struct aq_hw_s *self, unsigned int power_state,
+                        u8 *mac);
 };
 
 #endif /* AQ_HW_H */
index 26dc6782b4750f311f8311f1523e6ea98bc08f5b..9809dbf8c2726d632a6038ed3a032494a3eef703 100644 (file)
@@ -889,11 +889,13 @@ void aq_nic_deinit(struct aq_nic_s *self)
                self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i])
                aq_vec_deinit(aq_vec);
 
-       if (self->power_state == AQ_HW_POWER_STATE_D0) {
-               (void)self->aq_fw_ops->deinit(self->aq_hw);
-       } else {
-               (void)self->aq_hw_ops->hw_set_power(self->aq_hw,
-                                                  self->power_state);
+       self->aq_fw_ops->deinit(self->aq_hw);
+
+       if (self->power_state != AQ_HW_POWER_STATE_D0 ||
+           self->aq_hw->aq_nic_cfg->wol) {
+               self->aq_fw_ops->set_power(self->aq_hw,
+                                          self->power_state,
+                                          self->ndev->dev_addr);
        }
 
 err_exit:;
index 97addfa6f89569d7d23f8b60f42d67030c55ac0a..1dd0ef4a895c2f79f1d55a0054e5d7e2a533f4cf 100644 (file)
@@ -877,7 +877,6 @@ static int hw_atl_a0_hw_ring_rx_stop(struct aq_hw_s *self,
 const struct aq_hw_ops hw_atl_ops_a0 = {
        .hw_set_mac_address   = hw_atl_a0_hw_mac_addr_set,
        .hw_init              = hw_atl_a0_hw_init,
-       .hw_set_power         = hw_atl_utils_hw_set_power,
        .hw_reset             = hw_atl_a0_hw_reset,
        .hw_start             = hw_atl_a0_hw_start,
        .hw_ring_tx_start     = hw_atl_a0_hw_ring_tx_start,
index 1d44a386e7d34bc1775b3b66a3643111204c6909..d03f43683d8b60ca3033420ff341c6de2fa473ec 100644 (file)
@@ -935,7 +935,6 @@ static int hw_atl_b0_hw_ring_rx_stop(struct aq_hw_s *self,
 const struct aq_hw_ops hw_atl_ops_b0 = {
        .hw_set_mac_address   = hw_atl_b0_hw_mac_addr_set,
        .hw_init              = hw_atl_b0_hw_init,
-       .hw_set_power         = hw_atl_utils_hw_set_power,
        .hw_reset             = hw_atl_b0_hw_reset,
        .hw_start             = hw_atl_b0_hw_start,
        .hw_ring_tx_start     = hw_atl_b0_hw_ring_tx_start,
index 1926532bd1afe4c1e06b2e3afb0cc0af447320f0..c6fe4a58e047622d2b3b5a0c8dc910fca87209ad 100644 (file)
@@ -744,14 +744,6 @@ static int hw_atl_fw1x_deinit(struct aq_hw_s *self)
        return 0;
 }
 
-int hw_atl_utils_hw_set_power(struct aq_hw_s *self,
-                             unsigned int power_state)
-{
-       hw_atl_utils_mpi_set_speed(self, 0);
-       hw_atl_utils_mpi_set_state(self, MPI_POWER);
-       return 0;
-}
-
 int hw_atl_utils_update_stats(struct aq_hw_s *self)
 {
        struct hw_aq_atl_utils_mbox mbox;
@@ -839,6 +831,81 @@ int hw_atl_utils_get_fw_version(struct aq_hw_s *self, u32 *fw_version)
        return 0;
 }
 
+static int aq_fw1x_set_wol(struct aq_hw_s *self, bool wol_enabled, u8 *mac)
+{
+       struct hw_aq_atl_utils_fw_rpc *prpc = NULL;
+       unsigned int rpc_size = 0U;
+       int err = 0;
+
+       err = hw_atl_utils_fw_rpc_wait(self, &prpc);
+       if (err < 0)
+               goto err_exit;
+
+       memset(prpc, 0, sizeof(*prpc));
+
+       if (wol_enabled) {
+               rpc_size = sizeof(prpc->msg_id) + sizeof(prpc->msg_wol);
+
+               prpc->msg_id = HAL_ATLANTIC_UTILS_FW_MSG_WOL_ADD;
+               prpc->msg_wol.priority =
+                               HAL_ATLANTIC_UTILS_FW_MSG_WOL_PRIOR;
+               prpc->msg_wol.pattern_id =
+                               HAL_ATLANTIC_UTILS_FW_MSG_WOL_PATTERN;
+               prpc->msg_wol.wol_packet_type =
+                               HAL_ATLANTIC_UTILS_FW_MSG_WOL_MAG_PKT;
+
+               ether_addr_copy((u8 *)&prpc->msg_wol.wol_pattern, mac);
+       } else {
+               rpc_size = sizeof(prpc->msg_id) + sizeof(prpc->msg_del_id);
+
+               prpc->msg_id = HAL_ATLANTIC_UTILS_FW_MSG_WOL_DEL;
+               prpc->msg_wol.pattern_id =
+                               HAL_ATLANTIC_UTILS_FW_MSG_WOL_PATTERN;
+       }
+
+       err = hw_atl_utils_fw_rpc_call(self, rpc_size);
+
+err_exit:
+       return err;
+}
+
+int aq_fw1x_set_power(struct aq_hw_s *self, unsigned int power_state,
+                     u8 *mac)
+{
+       struct hw_aq_atl_utils_fw_rpc *prpc = NULL;
+       unsigned int rpc_size = 0U;
+       int err = 0;
+
+       if (self->aq_nic_cfg->wol & AQ_NIC_WOL_ENABLED) {
+               err = aq_fw1x_set_wol(self, 1, mac);
+
+               if (err < 0)
+                       goto err_exit;
+
+               rpc_size = sizeof(prpc->msg_id) +
+                          sizeof(prpc->msg_enable_wakeup);
+
+               err = hw_atl_utils_fw_rpc_wait(self, &prpc);
+
+               if (err < 0)
+                       goto err_exit;
+
+               memset(prpc, 0, rpc_size);
+
+               prpc->msg_id = HAL_ATLANTIC_UTILS_FW_MSG_ENABLE_WAKEUP;
+               prpc->msg_enable_wakeup.pattern_mask = 0x00000002;
+
+               err = hw_atl_utils_fw_rpc_call(self, rpc_size);
+               if (err < 0)
+                       goto err_exit;
+       }
+       hw_atl_utils_mpi_set_speed(self, 0);
+       hw_atl_utils_mpi_set_state(self, MPI_POWER);
+
+err_exit:
+       return err;
+}
+
 const struct aq_fw_ops aq_fw_1x_ops = {
        .init = hw_atl_utils_mpi_create,
        .deinit = hw_atl_fw1x_deinit,
@@ -848,5 +915,6 @@ const struct aq_fw_ops aq_fw_1x_ops = {
        .set_state = hw_atl_utils_mpi_set_state,
        .update_link_status = hw_atl_utils_mpi_get_link_status,
        .update_stats = hw_atl_utils_update_stats,
+       .set_power = aq_fw1x_set_power,
        .set_flow_control = NULL,
 };
index beec0775f1c1fc9fdc17dbd2e9da5dcffa57f6dc..48bebb6868191c3794604ecd041e4a9bcfe409d8 100644 (file)
@@ -257,6 +257,9 @@ enum hal_atl_utils_fw_state_e {
 #define HAL_ATLANTIC_UTILS_FW_MSG_ARP           0x2U
 #define HAL_ATLANTIC_UTILS_FW_MSG_INJECT        0x3U
 #define HAL_ATLANTIC_UTILS_FW_MSG_WOL_ADD       0x4U
+#define HAL_ATLANTIC_UTILS_FW_MSG_WOL_PRIOR     0x10000000U
+#define HAL_ATLANTIC_UTILS_FW_MSG_WOL_PATTERN   0x1U
+#define HAL_ATLANTIC_UTILS_FW_MSG_WOL_MAG_PKT   0x2U
 #define HAL_ATLANTIC_UTILS_FW_MSG_WOL_DEL       0x5U
 #define HAL_ATLANTIC_UTILS_FW_MSG_ENABLE_WAKEUP 0x6U
 #define HAL_ATLANTIC_UTILS_FW_MSG_MSM_PFC       0x7U
@@ -403,6 +406,8 @@ struct aq_stats_s *hw_atl_utils_get_hw_stats(struct aq_hw_s *self);
 int hw_atl_utils_fw_downld_dwords(struct aq_hw_s *self, u32 a,
                                  u32 *p, u32 cnt);
 
+int hw_atl_utils_fw_set_wol(struct aq_hw_s *self, bool wol_enabled, u8 *mac);
+
 int hw_atl_utils_fw_rpc_call(struct aq_hw_s *self, unsigned int rpc_size);
 
 int hw_atl_utils_fw_rpc_wait(struct aq_hw_s *self,
index 3e5fed50a44cf628b7635025ffd3484f12d9186b..9fc187f57ed45c79083a67ec882b93822fe39c86 100644 (file)
@@ -231,7 +231,7 @@ static int aq_fw2x_get_mac_permanent(struct aq_hw_s *self, u8 *mac)
        return err;
 }
 
-static int aq_fw2x_update_stats(struct aq_hw_s *self)
+int aq_fw2x_update_stats(struct aq_hw_s *self)
 {
        int err = 0;
        u32 mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
@@ -252,6 +252,101 @@ static int aq_fw2x_update_stats(struct aq_hw_s *self)
        return hw_atl_utils_update_stats(self);
 }
 
+static int aq_fw2x_set_sleep_proxy(struct aq_hw_s *self, u8 *mac)
+{
+       struct hw_aq_atl_utils_fw_rpc *rpc = NULL;
+       struct offload_info *cfg = NULL;
+       unsigned int rpc_size = 0U;
+       u32 mpi_opts;
+       int err = 0;
+
+       rpc_size = sizeof(rpc->msg_id) + sizeof(*cfg);
+
+       err = hw_atl_utils_fw_rpc_wait(self, &rpc);
+       if (err < 0)
+               goto err_exit;
+
+       memset(rpc, 0, rpc_size);
+       cfg = (struct offload_info *)(&rpc->msg_id + 1);
+
+       memcpy(cfg->mac_addr, mac, ETH_ALEN);
+       cfg->len = sizeof(*cfg);
+
+       /* Clear bit 0x36C.23 and 0x36C.22 */
+       mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
+       mpi_opts &= ~HW_ATL_FW2X_CTRL_SLEEP_PROXY;
+       mpi_opts &= ~HW_ATL_FW2X_CTRL_LINK_DROP;
+
+       aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);
+
+       err = hw_atl_utils_fw_rpc_call(self, rpc_size);
+       if (err < 0)
+               goto err_exit;
+
+       /* Set bit 0x36C.23 */
+       mpi_opts |= HW_ATL_FW2X_CTRL_SLEEP_PROXY;
+       aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);
+
+       AQ_HW_WAIT_FOR((aq_hw_read_reg(self, HW_ATL_FW2X_MPI_STATE2_ADDR) &
+                       HW_ATL_FW2X_CTRL_SLEEP_PROXY), 1U, 10000U);
+
+err_exit:
+       return err;
+}
+
+static int aq_fw2x_set_wol_params(struct aq_hw_s *self, u8 *mac)
+{
+       struct hw_aq_atl_utils_fw_rpc *rpc = NULL;
+       struct fw2x_msg_wol *msg = NULL;
+       u32 mpi_opts;
+       int err = 0;
+
+       err = hw_atl_utils_fw_rpc_wait(self, &rpc);
+       if (err < 0)
+               goto err_exit;
+
+       msg = (struct fw2x_msg_wol *)rpc;
+
+       msg->msg_id = HAL_ATLANTIC_UTILS_FW2X_MSG_WOL;
+       msg->magic_packet_enabled = true;
+       memcpy(msg->hw_addr, mac, ETH_ALEN);
+
+       mpi_opts = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR);
+       mpi_opts &= ~(HW_ATL_FW2X_CTRL_SLEEP_PROXY | HW_ATL_FW2X_CTRL_WOL);
+
+       aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);
+
+       err = hw_atl_utils_fw_rpc_call(self, sizeof(*msg));
+       if (err < 0)
+               goto err_exit;
+
+       /* Set bit 0x36C.24 */
+       mpi_opts |= HW_ATL_FW2X_CTRL_WOL;
+       aq_hw_write_reg(self, HW_ATL_FW2X_MPI_CONTROL2_ADDR, mpi_opts);
+
+       AQ_HW_WAIT_FOR((aq_hw_read_reg(self, HW_ATL_FW2X_MPI_STATE2_ADDR) &
+                       HW_ATL_FW2X_CTRL_WOL), 1U, 10000U);
+
+err_exit:
+       return err;
+}
+
+static int aq_fw2x_set_power(struct aq_hw_s *self, unsigned int power_state,
+                            u8 *mac)
+{
+       int err = 0;
+
+       if (self->aq_nic_cfg->wol & AQ_NIC_WOL_ENABLED) {
+               err = aq_fw2x_set_sleep_proxy(self, mac);
+               if (err < 0)
+                       goto err_exit;
+               err = aq_fw2x_set_wol_params(self, mac);
+       }
+
+err_exit:
+       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);
@@ -284,5 +379,6 @@ const struct aq_fw_ops aq_fw_2x_ops = {
        .set_state = aq_fw2x_set_state,
        .update_link_status = aq_fw2x_update_link_status,
        .update_stats = aq_fw2x_update_stats,
+       .set_power = aq_fw2x_set_power,
        .set_flow_control   = aq_fw2x_set_flow_control,
 };