diff options
Diffstat (limited to 'drivers/net/ethernet/aquantia/atlantic/aq_nic.c')
-rw-r--r-- | drivers/net/ethernet/aquantia/atlantic/aq_nic.c | 335 |
1 files changed, 268 insertions, 67 deletions
diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_nic.c b/drivers/net/ethernet/aquantia/atlantic/aq_nic.c index a369705a786a..4435c6374f7e 100644 --- a/drivers/net/ethernet/aquantia/atlantic/aq_nic.c +++ b/drivers/net/ethernet/aquantia/atlantic/aq_nic.c @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only -/* - * aQuantia Corporation Network Driver - * Copyright (C) 2014-2019 aQuantia Corporation. All rights reserved +/* Atlantic Network Driver + * + * Copyright (C) 2014-2019 aQuantia Corporation + * Copyright (C) 2019-2020 Marvell International Ltd. */ /* File aq_nic.c: Definition of common code for NIC. */ @@ -25,6 +26,7 @@ #include <linux/ip.h> #include <linux/tcp.h> #include <net/ip.h> +#include <net/pkt_cls.h> static unsigned int aq_itr = AQ_CFG_INTERRUPT_MODERATION_AUTO; module_param_named(aq_itr, aq_itr, uint, 0644); @@ -63,10 +65,38 @@ static void aq_nic_rss_init(struct aq_nic_s *self, unsigned int num_rss_queues) rss_params->indirection_table[i] = i & (num_rss_queues - 1); } +/* Recalculate the number of vectors */ +static void aq_nic_cfg_update_num_vecs(struct aq_nic_s *self) +{ + struct aq_nic_cfg_s *cfg = &self->aq_nic_cfg; + + cfg->vecs = min(cfg->aq_hw_caps->vecs, AQ_CFG_VECS_DEF); + cfg->vecs = min(cfg->vecs, num_online_cpus()); + if (self->irqvecs > AQ_HW_SERVICE_IRQS) + cfg->vecs = min(cfg->vecs, self->irqvecs - AQ_HW_SERVICE_IRQS); + /* cfg->vecs should be power of 2 for RSS */ + cfg->vecs = rounddown_pow_of_two(cfg->vecs); + + if (ATL_HW_IS_CHIP_FEATURE(self->aq_hw, ANTIGUA)) { + if (cfg->tcs > 2) + cfg->vecs = min(cfg->vecs, 4U); + } + + if (cfg->vecs <= 4) + cfg->tc_mode = AQ_TC_MODE_8TCS; + else + cfg->tc_mode = AQ_TC_MODE_4TCS; + + /*rss rings */ + cfg->num_rss_queues = min(cfg->vecs, AQ_CFG_NUM_RSS_QUEUES_DEF); + aq_nic_rss_init(self, cfg->num_rss_queues); +} + /* Checks hw_caps and 'corrects' aq_nic_cfg in runtime */ void aq_nic_cfg_start(struct aq_nic_s *self) { struct aq_nic_cfg_s *cfg = &self->aq_nic_cfg; + int i; cfg->tcs = AQ_CFG_TCS_DEF; @@ -78,7 +108,6 @@ void aq_nic_cfg_start(struct aq_nic_s *self) cfg->rxpageorder = AQ_CFG_RX_PAGEORDER; cfg->is_rss = AQ_CFG_IS_RSS_DEF; - cfg->num_rss_queues = AQ_CFG_NUM_RSS_QUEUES_DEF; cfg->aq_rss.base_cpu_number = AQ_CFG_RSS_BASE_CPU_NUM_DEF; cfg->fc.req = AQ_CFG_FC_MODE; cfg->wol = AQ_CFG_WOL_MODES; @@ -88,29 +117,13 @@ void aq_nic_cfg_start(struct aq_nic_s *self) cfg->is_autoneg = AQ_CFG_IS_AUTONEG_DEF; cfg->is_lro = AQ_CFG_IS_LRO_DEF; + cfg->is_ptp = true; /*descriptors */ cfg->rxds = min(cfg->aq_hw_caps->rxds_max, AQ_CFG_RXDS_DEF); cfg->txds = min(cfg->aq_hw_caps->txds_max, AQ_CFG_TXDS_DEF); - /*rss rings */ - cfg->vecs = min(cfg->aq_hw_caps->vecs, AQ_CFG_VECS_DEF); - cfg->vecs = min(cfg->vecs, num_online_cpus()); - if (self->irqvecs > AQ_HW_SERVICE_IRQS) - cfg->vecs = min(cfg->vecs, self->irqvecs - AQ_HW_SERVICE_IRQS); - /* cfg->vecs should be power of 2 for RSS */ - if (cfg->vecs >= 8U) - cfg->vecs = 8U; - else if (cfg->vecs >= 4U) - cfg->vecs = 4U; - else if (cfg->vecs >= 2U) - cfg->vecs = 2U; - else - cfg->vecs = 1U; - - cfg->num_rss_queues = min(cfg->vecs, AQ_CFG_NUM_RSS_QUEUES_DEF); - - aq_nic_rss_init(self, cfg->num_rss_queues); + aq_nic_cfg_update_num_vecs(self); cfg->irq_type = aq_pci_func_get_irq_type(self); @@ -135,6 +148,9 @@ void aq_nic_cfg_start(struct aq_nic_s *self) cfg->is_vlan_rx_strip = !!(cfg->features & NETIF_F_HW_VLAN_CTAG_RX); cfg->is_vlan_tx_insert = !!(cfg->features & NETIF_F_HW_VLAN_CTAG_TX); cfg->is_vlan_force_promisc = true; + + for (i = 0; i < sizeof(cfg->prio_tc_map); i++) + cfg->prio_tc_map[i] = cfg->tcs * i / 8; } static int aq_nic_update_link_status(struct aq_nic_s *self) @@ -180,6 +196,9 @@ static int aq_nic_update_link_status(struct aq_nic_s *self) #if IS_ENABLED(CONFIG_MACSEC) aq_macsec_enable(self); #endif + if (self->aq_hw_ops->hw_tc_rate_limit_set) + self->aq_hw_ops->hw_tc_rate_limit_set(self->aq_hw); + netif_tx_wake_all_queues(self->ndev); } if (netif_carrier_ok(self->ndev) && !self->link_status.mbps) { @@ -257,6 +276,28 @@ static void aq_nic_polling_timer_cb(struct timer_list *t) AQ_CFG_POLLING_TIMER_INTERVAL); } +static int aq_nic_hw_prepare(struct aq_nic_s *self) +{ + int err = 0; + + err = self->aq_hw_ops->hw_soft_reset(self->aq_hw); + if (err) + goto exit; + + err = self->aq_hw_ops->hw_prepare(self->aq_hw, &self->aq_fw_ops); + +exit: + return err; +} + +static bool aq_nic_is_valid_ether_addr(const u8 *addr) +{ + /* Some engineering samples of Aquantia NICs are provisioned with a + * partially populated MAC, which is still invalid. + */ + return !(addr[0] == 0 && addr[1] == 0 && addr[2] == 0); +} + int aq_nic_ndev_register(struct aq_nic_s *self) { int err = 0; @@ -266,7 +307,7 @@ int aq_nic_ndev_register(struct aq_nic_s *self) goto err_exit; } - err = hw_atl_utils_initfw(self->aq_hw, &self->aq_fw_ops); + err = aq_nic_hw_prepare(self); if (err) goto err_exit; @@ -281,6 +322,12 @@ int aq_nic_ndev_register(struct aq_nic_s *self) if (err) goto err_exit; + if (!is_valid_ether_addr(self->ndev->dev_addr) || + !aq_nic_is_valid_ether_addr(self->ndev->dev_addr)) { + netdev_warn(self->ndev, "MAC is invalid, will use random."); + eth_hw_addr_random(self->ndev); + } + #if defined(AQ_CFG_MAC_ADDR_PERMANENT) { static u8 mac_addr_permanent[] = AQ_CFG_MAC_ADDR_PERMANENT; @@ -364,26 +411,35 @@ int aq_nic_init(struct aq_nic_s *self) if (err < 0) goto err_exit; - if (self->aq_nic_cfg.aq_hw_caps->media_type == AQ_HW_MEDIA_TYPE_TP) { + if (ATL_HW_IS_CHIP_FEATURE(self->aq_hw, ATLANTIC) && + self->aq_nic_cfg.aq_hw_caps->media_type == AQ_HW_MEDIA_TYPE_TP) { self->aq_hw->phy_id = HW_ATL_PHY_ID_MAX; err = aq_phy_init(self->aq_hw); } - for (i = 0U, aq_vec = self->aq_vec[0]; - self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i]) + for (i = 0U; i < self->aq_vecs; i++) { + aq_vec = self->aq_vec[i]; + err = aq_vec_ring_alloc(aq_vec, self, i, + aq_nic_get_cfg(self)); + if (err) + goto err_exit; + aq_vec_init(aq_vec, self->aq_hw_ops, self->aq_hw); + } - err = aq_ptp_init(self, self->irqvecs - 1); - if (err < 0) - goto err_exit; + if (aq_nic_get_cfg(self)->is_ptp) { + err = aq_ptp_init(self, self->irqvecs - 1); + if (err < 0) + goto err_exit; - err = aq_ptp_ring_alloc(self); - if (err < 0) - goto err_exit; + err = aq_ptp_ring_alloc(self); + if (err < 0) + goto err_exit; - err = aq_ptp_ring_init(self); - if (err < 0) - goto err_exit; + err = aq_ptp_ring_init(self); + if (err < 0) + goto err_exit; + } netif_carrier_off(self->ndev); @@ -394,9 +450,12 @@ err_exit: int aq_nic_start(struct aq_nic_s *self) { struct aq_vec_s *aq_vec = NULL; + struct aq_nic_cfg_s *cfg; unsigned int i = 0U; int err = 0; + cfg = aq_nic_get_cfg(self); + err = self->aq_hw_ops->hw_multicast_list_set(self->aq_hw, self->mc_list.ar, self->mc_list.count); @@ -434,7 +493,7 @@ int aq_nic_start(struct aq_nic_s *self) timer_setup(&self->service_timer, aq_nic_service_timer_cb, 0); aq_nic_service_timer_cb(&self->service_timer); - if (self->aq_nic_cfg.is_polling) { + if (cfg->is_polling) { timer_setup(&self->polling_timer, aq_nic_polling_timer_cb, 0); mod_timer(&self->polling_timer, jiffies + AQ_CFG_POLLING_TIMER_INTERVAL); @@ -452,16 +511,16 @@ int aq_nic_start(struct aq_nic_s *self) if (err < 0) goto err_exit; - if (self->aq_nic_cfg.link_irq_vec) { + if (cfg->link_irq_vec) { int irqvec = pci_irq_vector(self->pdev, - self->aq_nic_cfg.link_irq_vec); + cfg->link_irq_vec); err = request_threaded_irq(irqvec, NULL, aq_linkstate_threaded_isr, IRQF_SHARED | IRQF_ONESHOT, self->ndev->name, self); if (err < 0) goto err_exit; - self->msix_entry_mask |= (1 << self->aq_nic_cfg.link_irq_vec); + self->msix_entry_mask |= (1 << cfg->link_irq_vec); } err = self->aq_hw_ops->hw_irq_enable(self->aq_hw, @@ -470,14 +529,21 @@ int aq_nic_start(struct aq_nic_s *self) goto err_exit; } - err = netif_set_real_num_tx_queues(self->ndev, self->aq_vecs); + err = netif_set_real_num_tx_queues(self->ndev, + self->aq_vecs * cfg->tcs); if (err < 0) goto err_exit; - err = netif_set_real_num_rx_queues(self->ndev, self->aq_vecs); + err = netif_set_real_num_rx_queues(self->ndev, + self->aq_vecs * cfg->tcs); if (err < 0) goto err_exit; + for (i = 0; i < cfg->tcs; i++) { + u16 offset = self->aq_vecs * i; + + netdev_set_tc_queue(self->ndev, i, self->aq_vecs, offset); + } netif_tx_start_all_queues(self->ndev); err_exit: @@ -488,6 +554,8 @@ unsigned int aq_nic_map_skb(struct aq_nic_s *self, struct sk_buff *skb, struct aq_ring_s *ring) { unsigned int nr_frags = skb_shinfo(skb)->nr_frags; + struct aq_nic_cfg_s *cfg = aq_nic_get_cfg(self); + struct device *dev = aq_nic_get_dev(self); struct aq_ring_buff_s *first = NULL; u8 ipver = ip_hdr(skb)->version; struct aq_ring_buff_s *dx_buff; @@ -529,7 +597,7 @@ unsigned int aq_nic_map_skb(struct aq_nic_s *self, struct sk_buff *skb, need_context_tag = true; } - if (self->aq_nic_cfg.is_vlan_tx_insert && skb_vlan_tag_present(skb)) { + if (cfg->is_vlan_tx_insert && skb_vlan_tag_present(skb)) { dx_buff->vlan_tx_tag = skb_vlan_tag_get(skb); dx_buff->len_pkt = skb->len; dx_buff->is_vlan = 1U; @@ -544,12 +612,12 @@ unsigned int aq_nic_map_skb(struct aq_nic_s *self, struct sk_buff *skb, } dx_buff->len = skb_headlen(skb); - dx_buff->pa = dma_map_single(aq_nic_get_dev(self), + dx_buff->pa = dma_map_single(dev, skb->data, dx_buff->len, DMA_TO_DEVICE); - if (unlikely(dma_mapping_error(aq_nic_get_dev(self), dx_buff->pa))) { + if (unlikely(dma_mapping_error(dev, dx_buff->pa))) { ret = 0; goto exit; } @@ -581,13 +649,13 @@ unsigned int aq_nic_map_skb(struct aq_nic_s *self, struct sk_buff *skb, else buff_size = frag_len; - frag_pa = skb_frag_dma_map(aq_nic_get_dev(self), + frag_pa = skb_frag_dma_map(dev, frag, buff_offset, buff_size, DMA_TO_DEVICE); - if (unlikely(dma_mapping_error(aq_nic_get_dev(self), + if (unlikely(dma_mapping_error(dev, frag_pa))) goto mapping_error; @@ -621,12 +689,12 @@ mapping_error: if (!(dx_buff->is_gso_tcp || dx_buff->is_gso_udp) && !dx_buff->is_vlan && dx_buff->pa) { if (unlikely(dx_buff->is_sop)) { - dma_unmap_single(aq_nic_get_dev(self), + dma_unmap_single(dev, dx_buff->pa, dx_buff->len, DMA_TO_DEVICE); } else { - dma_unmap_page(aq_nic_get_dev(self), + dma_unmap_page(dev, dx_buff->pa, dx_buff->len, DMA_TO_DEVICE); @@ -640,15 +708,16 @@ exit: int aq_nic_xmit(struct aq_nic_s *self, struct sk_buff *skb) { - unsigned int vec = skb->queue_mapping % self->aq_nic_cfg.vecs; + struct aq_nic_cfg_s *cfg = aq_nic_get_cfg(self); + unsigned int vec = skb->queue_mapping % cfg->vecs; + unsigned int tc = skb->queue_mapping / cfg->vecs; struct aq_ring_s *ring = NULL; unsigned int frags = 0U; int err = NETDEV_TX_OK; - unsigned int tc = 0U; frags = skb_shinfo(skb)->nr_frags + 1; - ring = self->aq_ring_tx[AQ_NIC_TCVEC2RING(self, tc, vec)]; + ring = self->aq_ring_tx[AQ_NIC_CFG_TCVEC2RING(cfg, tc, vec)]; if (frags > AQ_CFG_SKB_FRAGS_MAX) { dev_kfree_skb_any(skb); @@ -657,13 +726,14 @@ int aq_nic_xmit(struct aq_nic_s *self, struct sk_buff *skb) aq_ring_update_queue_state(ring); - if (self->aq_nic_cfg.priv_flags & BIT(AQ_HW_LOOPBACK_DMA_NET)) { + if (cfg->priv_flags & BIT(AQ_HW_LOOPBACK_DMA_NET)) { err = NETDEV_TX_BUSY; goto err_exit; } /* Above status update may stop the queue. Check this. */ - if (__netif_subqueue_stopped(self->ndev, ring->idx)) { + if (__netif_subqueue_stopped(self->ndev, + AQ_NIC_RING2QMAP(self, ring->idx))) { err = NETDEV_TX_BUSY; goto err_exit; } @@ -764,6 +834,9 @@ int aq_nic_get_regs(struct aq_nic_s *self, struct ethtool_regs *regs, void *p) u32 *regs_buff = p; int err = 0; + if (unlikely(!self->aq_hw_ops->hw_get_regs)) + return -EOPNOTSUPP; + regs->version = 1; err = self->aq_hw_ops->hw_get_regs(self->aq_hw, @@ -778,6 +851,9 @@ err_exit: int aq_nic_get_regs_count(struct aq_nic_s *self) { + if (unlikely(!self->aq_hw_ops->hw_get_regs)) + return 0; + return self->aq_nic_cfg.aq_hw_caps->mac_regs_count; } @@ -787,6 +863,7 @@ u64 *aq_nic_get_stats(struct aq_nic_s *self, u64 *data) struct aq_stats_s *stats; unsigned int count = 0U; unsigned int i = 0U; + unsigned int tc; if (self->aq_fw_ops->update_stats) { mutex_lock(&self->fwreq_mutex); @@ -825,10 +902,13 @@ u64 *aq_nic_get_stats(struct aq_nic_s *self, u64 *data) data += i; - for (i = 0U, aq_vec = self->aq_vec[0]; - aq_vec && self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i]) { - data += count; - aq_vec_get_sw_stats(aq_vec, data, &count); + for (tc = 0U; tc < self->aq_nic_cfg.tcs; tc++) { + for (i = 0U, aq_vec = self->aq_vec[0]; + aq_vec && self->aq_vecs > i; + ++i, aq_vec = self->aq_vec[i]) { + data += count; + aq_vec_get_sw_stats(aq_vec, tc, data, &count); + } } data += count; @@ -873,7 +953,7 @@ void aq_nic_get_link_ksettings(struct aq_nic_s *self, ethtool_link_ksettings_add_link_mode(cmd, supported, 5000baseT_Full); - if (self->aq_nic_cfg.aq_hw_caps->link_speed_msk & AQ_NIC_RATE_2GS) + if (self->aq_nic_cfg.aq_hw_caps->link_speed_msk & AQ_NIC_RATE_2G5) ethtool_link_ksettings_add_link_mode(cmd, supported, 2500baseT_Full); @@ -885,6 +965,10 @@ void aq_nic_get_link_ksettings(struct aq_nic_s *self, ethtool_link_ksettings_add_link_mode(cmd, supported, 100baseT_Full); + if (self->aq_nic_cfg.aq_hw_caps->link_speed_msk & AQ_NIC_RATE_10M) + ethtool_link_ksettings_add_link_mode(cmd, supported, + 10baseT_Full); + if (self->aq_nic_cfg.aq_hw_caps->flow_control) { ethtool_link_ksettings_add_link_mode(cmd, supported, Pause); @@ -912,7 +996,7 @@ void aq_nic_get_link_ksettings(struct aq_nic_s *self, ethtool_link_ksettings_add_link_mode(cmd, advertising, 5000baseT_Full); - if (self->aq_nic_cfg.link_speed_msk & AQ_NIC_RATE_2GS) + if (self->aq_nic_cfg.link_speed_msk & AQ_NIC_RATE_2G5) ethtool_link_ksettings_add_link_mode(cmd, advertising, 2500baseT_Full); @@ -924,6 +1008,10 @@ void aq_nic_get_link_ksettings(struct aq_nic_s *self, ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Full); + if (self->aq_nic_cfg.link_speed_msk & AQ_NIC_RATE_10M) + ethtool_link_ksettings_add_link_mode(cmd, advertising, + 10baseT_Full); + if (self->aq_nic_cfg.fc.cur & AQ_NIC_FC_RX) ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause); @@ -954,6 +1042,10 @@ int aq_nic_set_link_ksettings(struct aq_nic_s *self, speed = cmd->base.speed; switch (speed) { + case SPEED_10: + rate = AQ_NIC_RATE_10M; + break; + case SPEED_100: rate = AQ_NIC_RATE_100M; break; @@ -963,7 +1055,7 @@ int aq_nic_set_link_ksettings(struct aq_nic_s *self, break; case SPEED_2500: - rate = AQ_NIC_RATE_2GS; + rate = AQ_NIC_RATE_2G5; break; case SPEED_5000: @@ -1006,11 +1098,7 @@ struct aq_nic_cfg_s *aq_nic_get_cfg(struct aq_nic_s *self) u32 aq_nic_get_fw_version(struct aq_nic_s *self) { - u32 fw_version = 0U; - - self->aq_hw_ops->hw_get_fw_version(self->aq_hw, &fw_version); - - return fw_version; + return self->aq_hw_ops->hw_get_fw_version(self->aq_hw); } int aq_nic_set_loopback(struct aq_nic_s *self) @@ -1101,9 +1189,11 @@ void aq_nic_deinit(struct aq_nic_s *self, bool link_down) if (!self) goto err_exit; - for (i = 0U, aq_vec = self->aq_vec[0]; - self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i]) + for (i = 0U; i < self->aq_vecs; i++) { + aq_vec = self->aq_vec[i]; aq_vec_deinit(aq_vec); + aq_vec_ring_free(aq_vec); + } aq_ptp_unregister(self); aq_ptp_ring_deinit(self); @@ -1136,6 +1226,22 @@ void aq_nic_free_vectors(struct aq_nic_s *self) err_exit:; } +int aq_nic_realloc_vectors(struct aq_nic_s *self) +{ + struct aq_nic_cfg_s *cfg = aq_nic_get_cfg(self); + + aq_nic_free_vectors(self); + + for (self->aq_vecs = 0; self->aq_vecs < cfg->vecs; self->aq_vecs++) { + self->aq_vec[self->aq_vecs] = aq_vec_alloc(self, self->aq_vecs, + cfg); + if (unlikely(!self->aq_vec[self->aq_vecs])) + return -ENOMEM; + } + + return 0; +} + void aq_nic_shutdown(struct aq_nic_s *self) { int err = 0; @@ -1201,3 +1307,98 @@ void aq_nic_release_filter(struct aq_nic_s *self, enum aq_rx_filter_type type, break; } } + +int aq_nic_setup_tc_mqprio(struct aq_nic_s *self, u32 tcs, u8 *prio_tc_map) +{ + struct aq_nic_cfg_s *cfg = &self->aq_nic_cfg; + const unsigned int prev_vecs = cfg->vecs; + bool ndev_running; + int err = 0; + int i; + + /* if already the same configuration or + * disable request (tcs is 0) and we already is disabled + */ + if (tcs == cfg->tcs || (tcs == 0 && !cfg->is_qos)) + return 0; + + ndev_running = netif_running(self->ndev); + if (ndev_running) + dev_close(self->ndev); + + cfg->tcs = tcs; + if (cfg->tcs == 0) + cfg->tcs = 1; + if (prio_tc_map) + memcpy(cfg->prio_tc_map, prio_tc_map, sizeof(cfg->prio_tc_map)); + else + for (i = 0; i < sizeof(cfg->prio_tc_map); i++) + cfg->prio_tc_map[i] = cfg->tcs * i / 8; + + cfg->is_qos = (tcs != 0 ? true : false); + cfg->is_ptp = (cfg->tcs <= AQ_HW_PTP_TC); + if (!cfg->is_ptp) + netdev_warn(self->ndev, "%s\n", + "PTP is auto disabled due to requested TC count."); + + netdev_set_num_tc(self->ndev, cfg->tcs); + + /* Changing the number of TCs might change the number of vectors */ + aq_nic_cfg_update_num_vecs(self); + if (prev_vecs != cfg->vecs) { + err = aq_nic_realloc_vectors(self); + if (err) + goto err_exit; + } + + if (ndev_running) + err = dev_open(self->ndev, NULL); + +err_exit: + return err; +} + +int aq_nic_setup_tc_max_rate(struct aq_nic_s *self, const unsigned int tc, + const u32 max_rate) +{ + struct aq_nic_cfg_s *cfg = &self->aq_nic_cfg; + + if (tc >= AQ_CFG_TCS_MAX) + return -EINVAL; + + if (max_rate && max_rate < 10) { + netdev_warn(self->ndev, + "Setting %s to the minimum usable value of %dMbps.\n", + "max rate", 10); + cfg->tc_max_rate[tc] = 10; + } else { + cfg->tc_max_rate[tc] = max_rate; + } + + return 0; +} + +int aq_nic_setup_tc_min_rate(struct aq_nic_s *self, const unsigned int tc, + const u32 min_rate) +{ + struct aq_nic_cfg_s *cfg = &self->aq_nic_cfg; + + if (tc >= AQ_CFG_TCS_MAX) + return -EINVAL; + + if (min_rate) + set_bit(tc, &cfg->tc_min_rate_msk); + else + clear_bit(tc, &cfg->tc_min_rate_msk); + + if (min_rate && min_rate < 20) { + netdev_warn(self->ndev, + "Setting %s to the minimum usable value of %dMbps.\n", + "min rate", 20); + cfg->tc_min_rate[tc] = 20; + } else { + cfg->tc_min_rate[tc] = min_rate; + } + + return 0; +} |