diff --git a/doc/guides/nics/mlx5.rst b/doc/guides/nics/mlx5.rst
index f9558da89..1942eda47 100644
--- a/doc/guides/nics/mlx5.rst
+++ b/doc/guides/nics/mlx5.rst
@@ -253,8 +253,10 @@ Run-time configuration
   Enhanced MPS supports hybrid mode - mixing inlined packets and pointers
   in the same descriptor.
 
-  This option cannot be used in conjunction with ``tso`` below. When ``tso``
-  is set, ``txq_mpw_en`` is disabled.
+  This option cannot be used with certain offloads such as ``DEV_TX_OFFLOAD_TCP_TSO,
+  DEV_TX_OFFLOAD_VXLAN_TNL_TSO, DEV_TX_OFFLOAD_GRE_TNL_TSO,
+  DEV_TX_OFFLOAD_OUTER_IPV4_CKSUM, DEV_TX_OFFLOAD_VLAN_INSERT``.
+  When those offloads enabled the mpw send function will be disabled.
 
   It is currently only supported on the ConnectX-4 Lx and ConnectX-5
   families of adapters. Enabled by default.
@@ -275,12 +277,6 @@ Run-time configuration
 
   Effective only when Enhanced MPS is supported. The default value is 256.
 
-- ``tso`` parameter [int]
-
-  A nonzero value enables hardware TSO.
-  When hardware TSO is enabled, packets marked with TCP segmentation
-  offload will be divided into segments by the hardware. Disabled by default.
-
 - ``tx_vec_en`` parameter [int]
 
   A nonzero value enables Tx vector on ConnectX-5 only NIC if the number of
diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c
index be21c72e8..03839271c 100644
--- a/drivers/net/mlx5/mlx5.c
+++ b/drivers/net/mlx5/mlx5.c
@@ -85,9 +85,6 @@
 /* Device parameter to limit the size of inlining packet. */
 #define MLX5_TXQ_MAX_INLINE_LEN "txq_max_inline_len"
 
-/* Device parameter to enable hardware TSO offload. */
-#define MLX5_TSO "tso"
-
 /* Device parameter to enable hardware Tx vector. */
 #define MLX5_TX_VEC_EN "tx_vec_en"
 
@@ -411,8 +408,6 @@ mlx5_args_check(const char *key, const char *val, void *opaque)
 		args->mpw_hdr_dseg = !!tmp;
 	} else if (strcmp(MLX5_TXQ_MAX_INLINE_LEN, key) == 0) {
 		args->inline_max_packet_sz = tmp;
-	} else if (strcmp(MLX5_TSO, key) == 0) {
-		args->tso = !!tmp;
 	} else if (strcmp(MLX5_TX_VEC_EN, key) == 0) {
 		args->tx_vec_en = !!tmp;
 	} else if (strcmp(MLX5_RX_VEC_EN, key) == 0) {
@@ -445,7 +440,6 @@ mlx5_args(struct mlx5_args *args, struct rte_devargs *devargs)
 		MLX5_TXQ_MPW_EN,
 		MLX5_TXQ_MPW_HDR_DSEG_EN,
 		MLX5_TXQ_MAX_INLINE_LEN,
-		MLX5_TSO,
 		MLX5_TX_VEC_EN,
 		MLX5_RX_VEC_EN,
 		NULL,
@@ -483,11 +477,22 @@ static struct rte_pci_driver mlx5_driver;
  * @param[in/out] priv
  *   Pointer to private structure.
  */
-static void
+void
 mlx5_args_update(struct priv *priv)
 {
 	struct mlx5_args *args_def = &priv->args_default;
 	struct mlx5_args *args = &priv->args;
+	uint64_t supp_tx_offloads = mlx5_priv_get_tx_port_offloads(priv);
+	uint64_t tx_offloads = priv->dev ?
+			       priv->dev->data->dev_conf.txmode.offloads :
+			       0;
+	int tso = !!(tx_offloads & supp_tx_offloads & DEV_TX_OFFLOAD_TCP_TSO);
+	int vlan_insert = !!(tx_offloads & supp_tx_offloads &
+			     DEV_TX_OFFLOAD_VLAN_INSERT);
+	int tunnel = !!(tx_offloads & supp_tx_offloads &
+			(DEV_TX_OFFLOAD_VXLAN_TNL_TSO |
+			 DEV_TX_OFFLOAD_GRE_TNL_TSO |
+			 DEV_TX_OFFLOAD_OUTER_IPV4_CKSUM));
 
 	if (args_def->cqe_comp != MLX5_ARG_UNSET) {
 		if (!priv->cqe_comp && args_def->cqe_comp) {
@@ -498,30 +503,28 @@ mlx5_args_update(struct priv *priv)
 	} else {
 		args->cqe_comp = priv->cqe_comp;
 	}
-	if (args_def->tso != MLX5_ARG_UNSET) {
-		if (!priv->tso && args_def->tso) {
-			WARN("TSO is not supported");
-			args_def->tso = 0;
-		}
-		args->tso = args_def->tso;
-	} else {
-		args->tso = 0;
-	}
 	if (args_def->mps != MLX5_ARG_UNSET) {
 		if (!priv->mps && args_def->mps) {
 			WARN("multi-packet send not supported");
 			args_def->mps = MLX5_MPW_DISABLED;
-		}
-		if (args->tso && args_def->mps) {
+		} else if (tso && args_def->mps) {
 			WARN("multi-packet send not supported in conjunction "
 			      "with TSO. MPS disabled");
 			args->mps = MLX5_MPW_DISABLED;
+		} else if (vlan_insert && args_def->mps) {
+			WARN("multi-packet send not supported in conjunction "
+			      "with vlan insertion. MPS disabled");
+			args->mps = MLX5_MPW_DISABLED;
+		} else if (tunnel && args_def->mps) {
+			WARN("multi-packet send not supported in conjunction "
+			      "with tunnel offloads. MPS disabled");
+			args->mps = MLX5_MPW_DISABLED;
 		} else {
 			args->mps = args_def->mps ? priv->mps :
 						    MLX5_MPW_DISABLED;
 		}
 	} else {
-		if (args->tso)
+		if (tso || vlan_insert || tunnel)
 			args->mps = MLX5_MPW_DISABLED;
 		else
 			args->mps = priv->mps;
@@ -725,7 +728,6 @@ mlx5_pci_probe(struct rte_pci_driver *pci_drv, struct rte_pci_device *pci_dev)
 			.mps = MLX5_ARG_UNSET,
 			.mpw_hdr_dseg = MLX5_ARG_UNSET,
 			.inline_max_packet_sz = MLX5_ARG_UNSET,
-			.tso = MLX5_ARG_UNSET,
 			.tx_vec_en = MLX5_ARG_UNSET,
 			.rx_vec_en = MLX5_ARG_UNSET,
 		};
@@ -882,10 +884,9 @@ mlx5_pci_probe(struct rte_pci_driver *pci_drv, struct rte_pci_device *pci_dev)
 
 		priv_get_num_vfs(priv, &num_vfs);
 		priv->sriov = (num_vfs || sriov);
-		priv->tso = ((priv->tso) &&
-			    (device_attr_ex.tso_caps.max_tso > 0) &&
-			    (device_attr_ex.tso_caps.supported_qpts &
-			    (1 << IBV_QPT_RAW_PACKET)));
+		priv->tso = ((device_attr_ex.tso_caps.max_tso > 0) &&
+			     (device_attr_ex.tso_caps.supported_qpts &
+			     (1 << IBV_QPT_RAW_PACKET)));
 		if (priv->tso)
 			priv->max_tso_payload_sz =
 				device_attr_ex.tso_caps.max_tso;
diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index 5e943000e..cba6d3ceb 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -97,7 +97,6 @@ struct mlx5_args {
 	int mps;
 	int mpw_hdr_dseg;
 	int inline_max_packet_sz;
-	int tso;
 	int tx_vec_en;
 	int rx_vec_en;
 };
@@ -187,6 +186,7 @@ priv_unlock(struct priv *priv)
 /* mlx5.c */
 
 int mlx5_getenv_int(const char *);
+void mlx5_args_update(struct priv *);
 
 /* mlx5_ethdev.c */
 
diff --git a/drivers/net/mlx5/mlx5_ethdev.c b/drivers/net/mlx5/mlx5_ethdev.c
index 5c59bc45e..decc6edfa 100644
--- a/drivers/net/mlx5/mlx5_ethdev.c
+++ b/drivers/net/mlx5/mlx5_ethdev.c
@@ -578,7 +578,15 @@ dev_configure(struct rte_eth_dev *dev)
 	unsigned int reta_idx_n;
 	const uint8_t use_app_rss_key =
 		!!dev->data->dev_conf.rx_adv_conf.rss_conf.rss_key_len;
-
+	uint64_t supp_tx_offloads = mlx5_priv_get_tx_port_offloads(priv);
+	uint64_t tx_offloads = dev->data->dev_conf.txmode.offloads;
+
+	if ((tx_offloads & supp_tx_offloads) != tx_offloads) {
+		ERROR("Some Tx offloads are not supported "
+		      "requested 0x%lx supported 0x%lx\n",
+		      tx_offloads, supp_tx_offloads);
+		return ENOTSUP;
+	}
 	if (use_app_rss_key &&
 	    (dev->data->dev_conf.rx_adv_conf.rss_conf.rss_key_len !=
 	     rss_hash_default_key_len)) {
@@ -610,6 +618,8 @@ dev_configure(struct rte_eth_dev *dev)
 		ERROR("cannot handle this many RX queues (%u)", rxqs_n);
 		return EINVAL;
 	}
+	/* Update args according to selected offloads. */
+	mlx5_args_update(priv);
 	if (rxqs_n == priv->rxqs_n)
 		return 0;
 	INFO("%p: RX queues number update: %u -> %u",
@@ -700,20 +710,7 @@ mlx5_dev_infos_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *info)
 		 0) |
 		(priv->hw_vlan_strip ? DEV_RX_OFFLOAD_VLAN_STRIP : 0) |
 		DEV_RX_OFFLOAD_TIMESTAMP;
-
-	if (!priv->args.mps)
-		info->tx_offload_capa = DEV_TX_OFFLOAD_VLAN_INSERT;
-	if (priv->hw_csum)
-		info->tx_offload_capa |=
-			(DEV_TX_OFFLOAD_IPV4_CKSUM |
-			 DEV_TX_OFFLOAD_UDP_CKSUM |
-			 DEV_TX_OFFLOAD_TCP_CKSUM);
-	if (priv->args.tso)
-		info->tx_offload_capa |= DEV_TX_OFFLOAD_TCP_TSO;
-	if (priv->tunnel_en)
-		info->tx_offload_capa |= (DEV_TX_OFFLOAD_OUTER_IPV4_CKSUM |
-					  DEV_TX_OFFLOAD_VXLAN_TNL_TSO |
-					  DEV_TX_OFFLOAD_GRE_TNL_TSO);
+	info->tx_offload_capa = mlx5_priv_get_tx_port_offloads(priv);
 	if (priv_get_ifname(priv, &ifname) == 0)
 		info->if_index = if_nametoindex(ifname);
 	info->reta_size = priv->reta_idx_n ?
diff --git a/drivers/net/mlx5/mlx5_rxtx.h b/drivers/net/mlx5/mlx5_rxtx.h
index d34f3cc04..1e0a9875f 100644
--- a/drivers/net/mlx5/mlx5_rxtx.h
+++ b/drivers/net/mlx5/mlx5_rxtx.h
@@ -200,7 +200,7 @@ struct mlx5_txq_data {
 	uint16_t inline_max_packet_sz; /* Max packet size for inlining. */
 	uint16_t mr_cache_idx; /* Index of last hit entry. */
 	uint32_t qp_num_8s; /* QP number shifted by 8. */
-	uint32_t flags; /* Flags for Tx Queue. */
+	uint64_t offloads; /* Offloads for Tx Queue. */
 	volatile struct mlx5_cqe (*cqes)[]; /* Completion queue. */
 	volatile void *wqes; /* Work queue (use volatile to write into). */
 	volatile uint32_t *qp_db; /* Work queue doorbell. */
@@ -292,6 +292,7 @@ int mlx5_priv_txq_release(struct priv *, uint16_t);
 int mlx5_priv_txq_releasable(struct priv *, uint16_t);
 int mlx5_priv_txq_verify(struct priv *);
 void txq_alloc_elts(struct mlx5_txq_ctrl *);
+uint64_t mlx5_priv_get_tx_port_offloads(struct priv *);
 
 /* mlx5_rxtx.c */
 
diff --git a/drivers/net/mlx5/mlx5_rxtx_vec.c b/drivers/net/mlx5/mlx5_rxtx_vec.c
index 2556f5ebf..4e09a959d 100644
--- a/drivers/net/mlx5/mlx5_rxtx_vec.c
+++ b/drivers/net/mlx5/mlx5_rxtx_vec.c
@@ -202,15 +202,18 @@ mlx5_tx_burst_vec(void *dpdk_txq, struct rte_mbuf **pkts, uint16_t pkts_n)
 		uint16_t ret;
 
 		/* Transmit multi-seg packets in the head of pkts list. */
-		if (!(txq->flags & ETH_TXQ_FLAGS_NOMULTSEGS) &&
+		if ((txq->offloads & DEV_TX_OFFLOAD_MULTI_SEGS) &&
 		    NB_SEGS(pkts[nb_tx]) > 1)
 			nb_tx += txq_scatter_v(txq,
 					       &pkts[nb_tx],
 					       pkts_n - nb_tx);
 		n = RTE_MIN((uint16_t)(pkts_n - nb_tx), MLX5_VPMD_TX_MAX_BURST);
-		if (!(txq->flags & ETH_TXQ_FLAGS_NOMULTSEGS))
+		if (txq->offloads & DEV_TX_OFFLOAD_MULTI_SEGS)
 			n = txq_check_multiseg(&pkts[nb_tx], n);
-		if (!(txq->flags & ETH_TXQ_FLAGS_NOOFFLOADS))
+		if (txq->offloads &
+		    (DEV_TX_OFFLOAD_VLAN_INSERT |
+		     DEV_TX_OFFLOAD_UDP_CKSUM |
+		     DEV_TX_OFFLOAD_TCP_CKSUM))
 			n = txq_calc_offload(txq, &pkts[nb_tx], n, &cs_flags);
 		ret = txq_burst_v(txq, &pkts[nb_tx], n, cs_flags);
 		nb_tx += ret;
@@ -308,8 +311,12 @@ priv_check_raw_vec_tx_support(struct priv *priv)
 	for (i = 0; i < priv->txqs_n; ++i) {
 		struct mlx5_txq_data *txq = (*priv->txqs)[i];
 
-		if (!(txq->flags & ETH_TXQ_FLAGS_NOMULTSEGS) ||
-		    !(txq->flags & ETH_TXQ_FLAGS_NOOFFLOADS))
+		if (txq->offloads &
+		    (DEV_TX_OFFLOAD_MULTI_SEGS |
+		     DEV_TX_OFFLOAD_VLAN_INSERT |
+		     DEV_TX_OFFLOAD_UDP_CKSUM |
+		     DEV_TX_OFFLOAD_TCP_CKSUM |
+		     DEV_TX_OFFLOAD_IPV4_CKSUM))
 			break;
 	}
 	if (i != priv->txqs_n)
@@ -329,10 +336,13 @@ priv_check_raw_vec_tx_support(struct priv *priv)
 int __attribute__((cold))
 priv_check_vec_tx_support(struct priv *priv)
 {
+	uint64_t offloads = priv->dev->data->dev_conf.txmode.offloads;
+	int tso = !!(offloads & DEV_TX_OFFLOAD_TCP_TSO);
+
 	if (!priv->args.tx_vec_en ||
 	    priv->txqs_n > MLX5_VPMD_MIN_TXQS ||
 	    priv->args.mps != MLX5_MPW_ENHANCED ||
-	    priv->args.tso)
+	    tso)
 		return -ENOTSUP;
 	return 1;
 }
diff --git a/drivers/net/mlx5/mlx5_txq.c b/drivers/net/mlx5/mlx5_txq.c
index 28fc90e2e..4d9c7d697 100644
--- a/drivers/net/mlx5/mlx5_txq.c
+++ b/drivers/net/mlx5/mlx5_txq.c
@@ -116,6 +116,62 @@ txq_free_elts(struct mlx5_txq_ctrl *txq_ctrl)
 }
 
 /**
+ * Returns the per-port supported offloads.
+ *
+ * @param priv
+ *   Pointer to private structure.
+ *
+ * @return
+ *   Supported Tx offloads.
+ */
+uint64_t
+mlx5_priv_get_tx_port_offloads(struct priv *priv)
+{
+	uint64_t offloads = (DEV_TX_OFFLOAD_MULTI_SEGS |
+			     DEV_TX_OFFLOAD_VLAN_INSERT);
+
+	if (priv->hw_csum)
+		offloads |= (DEV_TX_OFFLOAD_IPV4_CKSUM |
+			     DEV_TX_OFFLOAD_UDP_CKSUM |
+			     DEV_TX_OFFLOAD_TCP_CKSUM);
+	if (priv->tso)
+		offloads |= DEV_TX_OFFLOAD_TCP_TSO;
+	if (priv->tunnel_en) {
+		if (priv->hw_csum)
+			offloads |= DEV_TX_OFFLOAD_OUTER_IPV4_CKSUM;
+		if (priv->tso)
+			offloads |= (DEV_TX_OFFLOAD_VXLAN_TNL_TSO |
+				     DEV_TX_OFFLOAD_GRE_TNL_TSO);
+	}
+	return offloads;
+}
+
+/**
+ * Checks if the per-queue offload configuration is valid.
+ *
+ * @param priv
+ *   Pointer to private structure.
+ * @param offloads
+ *   Per-queue offloads configuration.
+ *
+ * @return
+ *   1 if the configuration is valid, 0 otherwise.
+ */
+static int
+priv_is_tx_queue_offloads_allowed(struct priv *priv, uint64_t offloads)
+{
+	uint64_t port_offloads = priv->dev->data->dev_conf.txmode.offloads;
+	uint64_t port_supp_offloads = mlx5_priv_get_tx_port_offloads(priv);
+
+	/* There are no Tx offloads which are per queue. */
+	if ((offloads & port_supp_offloads) != offloads)
+		return 0;
+	if ((port_offloads ^ offloads) & port_supp_offloads)
+		return 0;
+	return 1;
+}
+
+/**
  * DPDK callback to configure a TX queue.
  *
  * @param dev
@@ -146,6 +202,15 @@ mlx5_tx_queue_setup(struct rte_eth_dev *dev, uint16_t idx, uint16_t desc,
 		return -E_RTE_SECONDARY;
 
 	priv_lock(priv);
+	if (!priv_is_tx_queue_offloads_allowed(priv, conf->offloads)) {
+		ret = ENOTSUP;
+		ERROR("%p: Tx queue offloads 0x%lx don't match port "
+		      "offloads 0x%lx or supported offloads 0x%lx",
+		      (void *)dev, conf->offloads,
+		      dev->data->dev_conf.txmode.offloads,
+		      mlx5_priv_get_tx_port_offloads(priv));
+		goto out;
+	}
 	if (desc <= MLX5_TX_COMP_THRESH) {
 		WARN("%p: number of descriptors requested for TX queue %u"
 		     " must be higher than MLX5_TX_COMP_THRESH, using"
@@ -570,6 +635,7 @@ mlx5_priv_txq_new(struct priv *priv, uint16_t idx, uint16_t desc,
 		((MLX5_MAX_TSO_HEADER + (RTE_CACHE_LINE_SIZE - 1)) /
 		 RTE_CACHE_LINE_SIZE);
 	struct mlx5_txq_ctrl *tmpl;
+	int tso = !!(conf->offloads & DEV_TX_OFFLOAD_TCP_TSO);
 
 	tmpl = rte_calloc_socket("TXQ", 1,
 				 sizeof(*tmpl) +
@@ -578,7 +644,7 @@ mlx5_priv_txq_new(struct priv *priv, uint16_t idx, uint16_t desc,
 	if (!tmpl)
 		return NULL;
 	assert(desc > MLX5_TX_COMP_THRESH);
-	tmpl->txq.flags = conf->txq_flags;
+	tmpl->txq.offloads = conf->offloads;
 	tmpl->priv = priv;
 	tmpl->socket = socket;
 	tmpl->txq.elts_n = log2above(desc);
@@ -597,8 +663,6 @@ mlx5_priv_txq_new(struct priv *priv, uint16_t idx, uint16_t desc,
 			((priv->args.txq_inline + (RTE_CACHE_LINE_SIZE - 1)) /
 			 RTE_CACHE_LINE_SIZE);
 		tmpl->txq.inline_en = 1;
-		/* TSO and MPS can't be enabled concurrently. */
-		assert(!priv->args.tso || !priv->args.mps);
 		if (priv->args.mps == MLX5_MPW_ENHANCED) {
 			tmpl->txq.inline_max_packet_sz =
 				priv->args.inline_max_packet_sz;
@@ -610,7 +674,7 @@ mlx5_priv_txq_new(struct priv *priv, uint16_t idx, uint16_t desc,
 					  priv->args.inline_max_packet_sz) +
 				  (RTE_CACHE_LINE_SIZE - 1)) /
 				 RTE_CACHE_LINE_SIZE) * RTE_CACHE_LINE_SIZE;
-		} else if (priv->args.tso) {
+		} else if (tso) {
 			int inline_diff = tmpl->txq.max_inline - max_tso_inline;
 
 			/*
@@ -646,7 +710,7 @@ mlx5_priv_txq_new(struct priv *priv, uint16_t idx, uint16_t desc,
 			tmpl->txq.max_inline = max_inline / RTE_CACHE_LINE_SIZE;
 		}
 	}
-	if (priv->args.tso) {
+	if (tso) {
 		tmpl->max_tso_header = max_tso_inline * RTE_CACHE_LINE_SIZE;
 		tmpl->txq.max_inline = RTE_MAX(tmpl->txq.max_inline,
 					       max_tso_inline);
