diff --git a/platform/linux-generic/pktio/dpdk.c b/platform/linux-generic/pktio/dpdk.c
index 3845448..9089715 100644
--- a/platform/linux-generic/pktio/dpdk.c
+++ b/platform/linux-generic/pktio/dpdk.c
@@ -485,6 +485,27 @@ static int dpdk_output_queues_config(pktio_entry_t *pktio_entry,
 	return 0;
 }
 
+static void dpdk_init_capability(pktio_entry_t *pktio_entry)
+{
+	pkt_dpdk_t *pkt_dpdk = &pktio_entry->s.pkt_dpdk;
+	odp_pktio_capability_t *capa = &pkt_dpdk->capa;
+	struct rte_eth_dev_info dev_info;
+
+	memset(&dev_info, 0, sizeof(struct rte_eth_dev_info));
+	memset(capa, 0, sizeof(odp_pktio_capability_t));
+
+	rte_eth_dev_info_get(pkt_dpdk->port_id, &dev_info);
+	capa->max_input_queues = RTE_MIN(dev_info.max_rx_queues,
+					 PKTIO_MAX_QUEUES);
+	capa->max_output_queues = RTE_MIN(dev_info.max_tx_queues,
+					  PKTIO_MAX_QUEUES);
+	capa->set_op.op.promisc_mode = 1;
+
+	odp_pktio_config_init(&capa->config);
+	capa->config.pktin.bit.ts_all = 1;
+	capa->config.pktin.bit.ts_ptp = 1;
+}
+
 static int dpdk_open(odp_pktio_t id ODP_UNUSED,
 		     pktio_entry_t *pktio_entry,
 		     const char *netdev,
@@ -535,13 +556,7 @@ static int dpdk_open(odp_pktio_t id ODP_UNUSED,
 		return -1;
 	}
 
-	memset(&dev_info, 0, sizeof(struct rte_eth_dev_info));
-	rte_eth_dev_info_get(pkt_dpdk->port_id, &dev_info);
-	pkt_dpdk->capa.max_input_queues = RTE_MIN(dev_info.max_rx_queues,
-						  PKTIO_MAX_QUEUES);
-	pkt_dpdk->capa.max_output_queues = RTE_MIN(dev_info.max_tx_queues,
-						   PKTIO_MAX_QUEUES);
-	pkt_dpdk->capa.set_op.op.promisc_mode = 1;
+	dpdk_init_capability(pktio_entry);
 
 	mtu = dpdk_mtu_get(pktio_entry);
 	if (mtu == 0) {
@@ -652,7 +667,7 @@ static int dpdk_stop(pktio_entry_t *pktio_entry)
 static inline int mbuf_to_pkt(pktio_entry_t *pktio_entry,
 			      odp_packet_t pkt_table[],
 			      struct rte_mbuf *mbuf_table[],
-			      uint16_t num)
+			      uint16_t num, odp_time_t *ts)
 {
 	odp_packet_t pkt;
 	odp_packet_hdr_t *pkt_hdr;
@@ -677,7 +692,7 @@ static inline int mbuf_to_pkt(pktio_entry_t *pktio_entry,
 		if (pktio_cls_enabled(pktio_entry)) {
 			if (_odp_packet_cls_enq(pktio_entry,
 						(const uint8_t *)buf, pkt_len,
-						NULL, &pkt_table[nb_pkts]))
+						ts, &pkt_table[nb_pkts]))
 				nb_pkts++;
 		} else {
 			pkt = packet_alloc(pktio_entry->s.pkt_dpdk.pool,
@@ -704,6 +719,8 @@ static inline int mbuf_to_pkt(pktio_entry_t *pktio_entry,
 			if (mbuf->ol_flags & PKT_RX_RSS_HASH)
 				odp_packet_flow_hash_set(pkt, mbuf->hash.rss);
 
+			packet_set_ts(pkt_hdr, ts);
+
 			pkt_table[nb_pkts++] = pkt;
 		}
 		rte_pktmbuf_free(mbuf);
@@ -765,6 +782,8 @@ static int dpdk_recv_queue(pktio_entry_t *pktio_entry,
 {
 	pkt_dpdk_t *pkt_dpdk = &pktio_entry->s.pkt_dpdk;
 	pkt_cache_t *rx_cache = &pkt_dpdk->rx_cache[index];
+	odp_time_t ts_val;
+	odp_time_t *ts = NULL;
 	int nb_rx;
 	struct rte_mbuf *rx_mbufs[num];
 	int i;
@@ -795,7 +814,6 @@ static int dpdk_recv_queue(pktio_entry_t *pktio_entry,
 
 		nb_rx = rte_eth_rx_burst(pktio_entry->s.pkt_dpdk.port_id, index,
 					 new_mbufs, pkt_dpdk->min_rx_burst);
-
 		rx_cache->s.idx = 0;
 		for (i = 0; i < nb_rx; i++) {
 			if (i < num) {
@@ -813,8 +831,15 @@ static int dpdk_recv_queue(pktio_entry_t *pktio_entry,
 					 rx_mbufs, num);
 	}
 
-	if (nb_rx > 0)
-		nb_rx = mbuf_to_pkt(pktio_entry, pkt_table, rx_mbufs, nb_rx);
+	if (nb_rx > 0) {
+		if (pktio_entry->s.config.pktin.bit.ts_all ||
+		    pktio_entry->s.config.pktin.bit.ts_ptp) {
+			ts_val = odp_time_global();
+			ts = &ts_val;
+		}
+		nb_rx = mbuf_to_pkt(pktio_entry, pkt_table, rx_mbufs, nb_rx,
+				    ts);
+	}
 
 	if (!pktio_entry->s.pkt_dpdk.lockless_rx)
 		odp_ticketlock_unlock(&pkt_dpdk->rx_lock[index]);
diff --git a/platform/linux-generic/pktio/loop.c b/platform/linux-generic/pktio/loop.c
index 2fc1381..f04824f 100644
--- a/platform/linux-generic/pktio/loop.c
+++ b/platform/linux-generic/pktio/loop.c
@@ -56,6 +56,8 @@ static int loopback_recv(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 	queue_entry_t *qentry;
 	odp_packet_hdr_t *pkt_hdr;
 	odp_packet_t pkt;
+	odp_time_t ts_val;
+	odp_time_t *ts = NULL;
 
 	if (odp_unlikely(len > QUEUE_MULTI_MAX))
 		len = QUEUE_MULTI_MAX;
@@ -63,6 +65,12 @@ static int loopback_recv(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 	qentry = queue_to_qentry(pktio_entry->s.pkt_loop.loopq);
 	nbr = queue_deq_multi(qentry, hdr_tbl, len);
 
+	if (pktio_entry->s.config.pktin.bit.ts_all ||
+	    pktio_entry->s.config.pktin.bit.ts_ptp) {
+		ts_val = odp_time_global();
+		ts = &ts_val;
+	}
+
 	if (pktio_cls_enabled(pktio_entry)) {
 		for (i = 0, j = 0; i < nbr; i++) {
 			pkt = _odp_packet_from_buffer(odp_hdr_to_buf
@@ -71,6 +79,7 @@ static int loopback_recv(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 			packet_parse_reset(pkt_hdr);
 			packet_parse_l2(pkt_hdr);
 			if (0 > _odp_packet_classifier(pktio_entry, pkt)) {
+				packet_set_ts(pkt_hdr, ts);
 				pkts[j++] = pkt;
 				pktio_entry->s.stats.in_octets +=
 					odp_packet_len(pkts[i]);
@@ -84,6 +93,7 @@ static int loopback_recv(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 			pkt_hdr = odp_packet_hdr(pkts[i]);
 			packet_parse_reset(pkt_hdr);
 			packet_parse_l2(pkt_hdr);
+			packet_set_ts(pkt_hdr, ts);
 			pktio_entry->s.stats.in_octets +=
 				odp_packet_len(pkts[i]);
 		}
@@ -140,6 +150,21 @@ static int loopback_link_status(pktio_entry_t *pktio_entry ODP_UNUSED)
 	return 1;
 }
 
+static int loopback_capability(pktio_entry_t *pktio_entry ODP_UNUSED,
+			       odp_pktio_capability_t *capa)
+{
+	memset(capa, 0, sizeof(odp_pktio_capability_t));
+
+	capa->max_input_queues  = 1;
+	capa->max_output_queues = 1;
+	capa->set_op.op.promisc_mode = 1;
+
+	odp_pktio_config_init(&capa->config);
+	capa->config.pktin.bit.ts_all = 1;
+	capa->config.pktin.bit.ts_ptp = 1;
+	return 0;
+}
+
 static int loopback_promisc_mode_set(pktio_entry_t *pktio_entry,
 				     odp_bool_t enable)
 {
@@ -183,7 +208,7 @@ const pktio_if_ops_t loopback_pktio_ops = {
 	.promisc_mode_get = loopback_promisc_mode_get,
 	.mac_get = loopback_mac_addr_get,
 	.link_status = loopback_link_status,
-	.capability = NULL,
+	.capability = loopback_capability,
 	.pktin_ts_res = NULL,
 	.pktin_ts_from_ns = NULL,
 	.config = NULL,
diff --git a/platform/linux-generic/pktio/netmap.c b/platform/linux-generic/pktio/netmap.c
index 9112ba3..60e8d8e 100644
--- a/platform/linux-generic/pktio/netmap.c
+++ b/platform/linux-generic/pktio/netmap.c
@@ -251,6 +251,47 @@ static inline int netmap_wait_for_link(pktio_entry_t *pktio_entry)
 }
 
 /**
+ * Initialize netmap capability values
+ *
+ * @param pktio_entry    Packet IO entry
+ */
+static void netmap_init_capability(pktio_entry_t *pktio_entry)
+{
+	pkt_netmap_t *pkt_nm = &pktio_entry->s.pkt_nm;
+	odp_pktio_capability_t *capa = &pkt_nm->capa;
+
+	memset(&pkt_nm->capa, 0, sizeof(odp_pktio_capability_t));
+
+	capa->max_input_queues = PKTIO_MAX_QUEUES;
+	if (pkt_nm->num_rx_rings < PKTIO_MAX_QUEUES)
+		capa->max_input_queues = pkt_nm->num_rx_rings;
+	if (capa->max_input_queues > NM_MAX_DESC) {
+		/* Have to use a single descriptor to fetch packets from all
+		 * netmap rings */
+		capa->max_input_queues = 1;
+		ODP_DBG("Unable to store all %" PRIu32 " rx rings (max %d)\n"
+			"  max input queues: %u\n", pkt_nm->num_rx_rings,
+			NM_MAX_DESC, capa->max_input_queues);
+	}
+
+	capa->max_output_queues = PKTIO_MAX_QUEUES;
+	if (pkt_nm->num_tx_rings < PKTIO_MAX_QUEUES)
+		capa->max_output_queues = pkt_nm->num_tx_rings;
+	if (capa->max_output_queues > NM_MAX_DESC) {
+		capa->max_output_queues = NM_MAX_DESC;
+		ODP_DBG("Unable to store all %" PRIu32 " tx rings (max %d)\n"
+			"  max output queues: %u\n", pkt_nm->num_tx_rings,
+			NM_MAX_DESC, capa->max_output_queues);
+	}
+
+	capa->set_op.op.promisc_mode = 1;
+
+	odp_pktio_config_init(&capa->config);
+	capa->config.pktin.bit.ts_all = 1;
+	capa->config.pktin.bit.ts_ptp = 1;
+}
+
+/**
  * Open a netmap interface
  *
  * In addition to standard interfaces (with or without modified netmap drivers)
@@ -326,29 +367,9 @@ static int netmap_open(odp_pktio_t id ODP_UNUSED, pktio_entry_t *pktio_entry,
 		goto error;
 	}
 	pkt_nm->num_rx_rings = desc->nifp->ni_rx_rings;
-	pkt_nm->capa.max_input_queues = PKTIO_MAX_QUEUES;
-	if (pkt_nm->num_rx_rings < PKTIO_MAX_QUEUES)
-		pkt_nm->capa.max_input_queues = pkt_nm->num_rx_rings;
-	if (pkt_nm->capa.max_input_queues > NM_MAX_DESC) {
-		/* Have to use a single descriptor to fetch packets from all
-		 * netmap rings */
-		pkt_nm->capa.max_input_queues = 1;
-		ODP_DBG("Unable to store all %" PRIu32 " rx rings (max %d)\n"
-			"  max input queues: %u\n", pkt_nm->num_rx_rings,
-			NM_MAX_DESC, pkt_nm->capa.max_input_queues);
-	}
 	pkt_nm->num_tx_rings = desc->nifp->ni_tx_rings;
-	pkt_nm->capa.max_output_queues = PKTIO_MAX_QUEUES;
-	if (pkt_nm->num_tx_rings < PKTIO_MAX_QUEUES)
-		pkt_nm->capa.max_output_queues = pkt_nm->num_tx_rings;
-	if (pkt_nm->capa.max_output_queues > NM_MAX_DESC) {
-		pkt_nm->capa.max_output_queues = NM_MAX_DESC;
-		ODP_DBG("Unable to store all %" PRIu32 " tx rings (max %d)\n"
-			"  max output queues: %u\n", pkt_nm->num_tx_rings,
-			NM_MAX_DESC, pkt_nm->capa.max_output_queues);
-	}
 
-	pkt_nm->capa.set_op.op.promisc_mode = 1;
+	netmap_init_capability(pktio_entry);
 
 	ring = NETMAP_RXRING(desc->nifp, desc->cur_rx_ring);
 	buf_size = ring->nr_buf_size;
@@ -562,13 +583,14 @@ static int netmap_stop(pktio_entry_t *pktio_entry ODP_UNUSED)
  * @param pkt_out        Storage for new ODP packet handle
  * @param buf            Netmap buffer address
  * @param len            Netmap buffer length
+ * @param ts             Pointer to pktin timestamp
  *
  * @retval 0 on success
  * @retval <0 on failure
  */
 static inline int netmap_pkt_to_odp(pktio_entry_t *pktio_entry,
 				    odp_packet_t *pkt_out, const char *buf,
-				    uint16_t len)
+				    uint16_t len, odp_time_t *ts)
 {
 	odp_packet_t pkt;
 	int ret;
@@ -586,7 +608,7 @@ static inline int netmap_pkt_to_odp(pktio_entry_t *pktio_entry,
 
 	if (pktio_cls_enabled(pktio_entry)) {
 		ret = _odp_packet_cls_enq(pktio_entry, (const uint8_t *)buf,
-					  len, NULL, pkt_out);
+					  len, ts, pkt_out);
 		if (ret)
 			return 0;
 		return -1;
@@ -610,6 +632,8 @@ static inline int netmap_pkt_to_odp(pktio_entry_t *pktio_entry,
 
 		pkt_hdr->input = pktio_entry->s.handle;
 
+		packet_set_ts(pkt_hdr, ts);
+
 		*pkt_out = pkt;
 	}
 
@@ -621,6 +645,8 @@ static inline int netmap_recv_desc(pktio_entry_t *pktio_entry,
 				   odp_packet_t pkt_table[], int num)
 {
 	struct netmap_ring *ring;
+	odp_time_t ts_val;
+	odp_time_t *ts = NULL;
 	char *buf;
 	uint32_t slot_id;
 	int i;
@@ -628,12 +654,20 @@ static inline int netmap_recv_desc(pktio_entry_t *pktio_entry,
 	int num_rx = 0;
 	int num_rings = desc->last_rx_ring - desc->first_rx_ring + 1;
 
+	if (pktio_entry->s.config.pktin.bit.ts_all ||
+	    pktio_entry->s.config.pktin.bit.ts_ptp)
+		ts = &ts_val;
+
 	for (i = 0; i < num_rings && num_rx != num; i++) {
 		if (ring_id > desc->last_rx_ring)
 			ring_id = desc->first_rx_ring;
 
 		ring = NETMAP_RXRING(desc->nifp, ring_id);
 
+		/* Take timestamp beforehand per ring to improve performance */
+		if (ts != NULL)
+			ts_val = odp_time_global();
+
 		while (!nm_ring_empty(ring) && num_rx != num) {
 			slot_id = ring->cur;
 			buf = NETMAP_BUF(ring, ring->slot[slot_id].buf_idx);
@@ -641,7 +675,8 @@ static inline int netmap_recv_desc(pktio_entry_t *pktio_entry,
 			odp_prefetch(buf);
 
 			if (!netmap_pkt_to_odp(pktio_entry, &pkt_table[num_rx],
-					       buf, ring->slot[slot_id].len))
+					       buf, ring->slot[slot_id].len,
+					       ts))
 				num_rx++;
 
 			ring->cur = nm_ring_next(ring, slot_id);
diff --git a/platform/linux-generic/pktio/pcap.c b/platform/linux-generic/pktio/pcap.c
index a47d2cc..2b5a63c 100644
--- a/platform/linux-generic/pktio/pcap.c
+++ b/platform/linux-generic/pktio/pcap.c
@@ -211,6 +211,8 @@ static int pcapif_recv_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 	odp_packet_hdr_t *pkt_hdr;
 	uint32_t pkt_len;
 	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+	odp_time_t ts_val;
+	odp_time_t *ts = NULL;
 
 	if (pktio_entry->s.state != STATE_STARTED)
 		return 0;
@@ -218,6 +220,10 @@ static int pcapif_recv_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 	if (!pcap->rx)
 		return 0;
 
+	if (pktio_entry->s.config.pktin.bit.ts_all ||
+	    pktio_entry->s.config.pktin.bit.ts_ptp)
+		ts = &ts_val;
+
 	pkt = ODP_PACKET_INVALID;
 	pkt_len = 0;
 
@@ -240,6 +246,9 @@ static int pcapif_recv_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 		if (ret != 1)
 			break;
 
+		if (ts != NULL)
+			ts_val = odp_time_global();
+
 		pkt_hdr = odp_packet_hdr(pkt);
 
 		if (!odp_packet_pull_tail(pkt, pkt_len - hdr->caplen)) {
@@ -256,6 +265,8 @@ static int pcapif_recv_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 		packet_parse_l2(pkt_hdr);
 		pktio_entry->s.stats.in_octets += pkt_hdr->frame_len;
 
+		packet_set_ts(pkt_hdr, ts);
+
 		pkts[i] = pkt;
 		pkt = ODP_PACKET_INVALID;
 
@@ -331,6 +342,21 @@ static int pcapif_mac_addr_get(pktio_entry_t *pktio_entry ODP_UNUSED,
 	return ODPH_ETHADDR_LEN;
 }
 
+static int pcapif_capability(pktio_entry_t *pktio_entry ODP_UNUSED,
+			     odp_pktio_capability_t *capa)
+{
+	memset(capa, 0, sizeof(odp_pktio_capability_t));
+
+	capa->max_input_queues  = 1;
+	capa->max_output_queues = 1;
+	capa->set_op.op.promisc_mode = 1;
+
+	odp_pktio_config_init(&capa->config);
+	capa->config.pktin.bit.ts_all = 1;
+	capa->config.pktin.bit.ts_ptp = 1;
+	return 0;
+}
+
 static int pcapif_promisc_mode_set(pktio_entry_t *pktio_entry,
 				   odp_bool_t enable)
 {
@@ -406,7 +432,7 @@ const pktio_if_ops_t pcap_pktio_ops = {
 	.promisc_mode_set = pcapif_promisc_mode_set,
 	.promisc_mode_get = pcapif_promisc_mode_get,
 	.mac_get = pcapif_mac_addr_get,
-	.capability = NULL,
+	.capability = pcapif_capability,
 	.pktin_ts_res = NULL,
 	.pktin_ts_from_ns = NULL,
 	.config = NULL,
diff --git a/platform/linux-generic/pktio/socket.c b/platform/linux-generic/pktio/socket.c
index e28fd07..da5c4ec 100644
--- a/platform/linux-generic/pktio/socket.c
+++ b/platform/linux-generic/pktio/socket.c
@@ -610,6 +610,8 @@ static int sock_mmsg_recv(pktio_entry_t *pktio_entry,
 			  odp_packet_t pkt_table[], unsigned len)
 {
 	pkt_sock_t *pkt_sock = &pktio_entry->s.pkt_sock;
+	odp_time_t ts_val;
+	odp_time_t *ts = NULL;
 	const int sockfd = pkt_sock->sockfd;
 	int msgvec_len;
 	struct mmsghdr msgvec[ODP_PACKET_SOCKET_MAX_BURST_RX];
@@ -622,6 +624,10 @@ static int sock_mmsg_recv(pktio_entry_t *pktio_entry,
 	if (odp_unlikely(len > ODP_PACKET_SOCKET_MAX_BURST_RX))
 		return -1;
 
+	if (pktio_entry->s.config.pktin.bit.ts_all ||
+	    pktio_entry->s.config.pktin.bit.ts_ptp)
+		ts = &ts_val;
+
 	memset(msgvec, 0, sizeof(msgvec));
 	recv_cache = pkt_sock->cache_ptr;
 
@@ -639,6 +645,10 @@ static int sock_mmsg_recv(pktio_entry_t *pktio_entry,
 
 		recv_msgs = recvmmsg(sockfd, msgvec, msgvec_len,
 				     MSG_DONTWAIT, NULL);
+
+		if (ts != NULL)
+			ts_val = odp_time_global();
+
 		for (i = 0; i < recv_msgs; i++) {
 			void *base = msgvec[i].msg_hdr.msg_iov->iov_base;
 			struct ethhdr *eth_hdr = base;
@@ -650,7 +660,7 @@ static int sock_mmsg_recv(pktio_entry_t *pktio_entry,
 				continue;
 
 			ret = _odp_packet_cls_enq(pktio_entry, base,
-						  pkt_len, NULL,
+						  pkt_len, ts,
 						  &pkt_table[nb_rx]);
 			if (ret)
 				nb_rx++;
@@ -677,6 +687,9 @@ static int sock_mmsg_recv(pktio_entry_t *pktio_entry,
 		recv_msgs = recvmmsg(sockfd, msgvec, msgvec_len,
 				     MSG_DONTWAIT, NULL);
 
+		if (ts != NULL)
+			ts_val = odp_time_global();
+
 		for (i = 0; i < recv_msgs; i++) {
 			void *base = msgvec[i].msg_hdr.msg_iov->iov_base;
 			struct ethhdr *eth_hdr = base;
@@ -693,8 +706,9 @@ static int sock_mmsg_recv(pktio_entry_t *pktio_entry,
 			odp_packet_pull_tail(pkt_table[i],
 					     odp_packet_len(pkt_table[i]) -
 					     msgvec[i].msg_len);
-
 			packet_parse_l2(pkt_hdr);
+			packet_set_ts(pkt_hdr, ts);
+
 			pkt_table[nb_rx] = pkt_table[i];
 			nb_rx++;
 		}
@@ -813,6 +827,21 @@ static int sock_link_status(pktio_entry_t *pktio_entry)
 			      pktio_entry->s.name);
 }
 
+static int sock_capability(pktio_entry_t *pktio_entry ODP_UNUSED,
+			   odp_pktio_capability_t *capa)
+{
+	memset(capa, 0, sizeof(odp_pktio_capability_t));
+
+	capa->max_input_queues  = 1;
+	capa->max_output_queues = 1;
+	capa->set_op.op.promisc_mode = 1;
+
+	odp_pktio_config_init(&capa->config);
+	capa->config.pktin.bit.ts_all = 1;
+	capa->config.pktin.bit.ts_ptp = 1;
+	return 0;
+}
+
 static int sock_stats(pktio_entry_t *pktio_entry,
 		      odp_pktio_stats_t *stats)
 {
@@ -856,7 +885,7 @@ const pktio_if_ops_t sock_mmsg_pktio_ops = {
 	.promisc_mode_get = sock_promisc_mode_get,
 	.mac_get = sock_mac_addr_get,
 	.link_status = sock_link_status,
-	.capability = NULL,
+	.capability = sock_capability,
 	.pktin_ts_res = NULL,
 	.pktin_ts_from_ns = NULL,
 	.config = NULL,
diff --git a/platform/linux-generic/pktio/socket_mmap.c b/platform/linux-generic/pktio/socket_mmap.c
index 5cd1ad6..8fadc5a 100644
--- a/platform/linux-generic/pktio/socket_mmap.c
+++ b/platform/linux-generic/pktio/socket_mmap.c
@@ -116,6 +116,8 @@ static inline unsigned pkt_mmap_v2_rx(pktio_entry_t *pktio_entry,
 				      unsigned char if_mac[])
 {
 	union frame_map ppd;
+	odp_time_t ts_val;
+	odp_time_t *ts = NULL;
 	unsigned frame_num, next_frame_num;
 	uint8_t *pkt_buf;
 	int pkt_len;
@@ -125,6 +127,10 @@ static inline unsigned pkt_mmap_v2_rx(pktio_entry_t *pktio_entry,
 	struct ring *ring;
 	int ret;
 
+	if (pktio_entry->s.config.pktin.bit.ts_all ||
+	    pktio_entry->s.config.pktin.bit.ts_ptp)
+		ts = &ts_val;
+
 	ring  = &pkt_sock->rx_ring;
 	frame_num = ring->frame_num;
 
@@ -132,6 +138,9 @@ static inline unsigned pkt_mmap_v2_rx(pktio_entry_t *pktio_entry,
 		if (!mmap_rx_kernel_ready(ring->rd[frame_num].iov_base))
 			break;
 
+		if (ts != NULL)
+			ts_val = odp_time_global();
+
 		ppd.raw = ring->rd[frame_num].iov_base;
 		next_frame_num = (frame_num + 1) % ring->rd_num;
 
@@ -149,7 +158,7 @@ static inline unsigned pkt_mmap_v2_rx(pktio_entry_t *pktio_entry,
 
 		if (pktio_cls_enabled(pktio_entry)) {
 			ret = _odp_packet_cls_enq(pktio_entry, pkt_buf,
-						  pkt_len, NULL,
+						  pkt_len, ts,
 						  &pkt_table[nb_rx]);
 			if (ret)
 				nb_rx++;
@@ -173,6 +182,8 @@ static inline unsigned pkt_mmap_v2_rx(pktio_entry_t *pktio_entry,
 			}
 
 			packet_parse_l2(hdr);
+			packet_set_ts(hdr, ts);
+
 			nb_rx++;
 		}
 
@@ -582,6 +593,21 @@ static int sock_mmap_link_status(pktio_entry_t *pktio_entry)
 			      pktio_entry->s.name);
 }
 
+static int sock_mmap_capability(pktio_entry_t *pktio_entry ODP_UNUSED,
+				odp_pktio_capability_t *capa)
+{
+	memset(capa, 0, sizeof(odp_pktio_capability_t));
+
+	capa->max_input_queues  = 1;
+	capa->max_output_queues = 1;
+	capa->set_op.op.promisc_mode = 1;
+
+	odp_pktio_config_init(&capa->config);
+	capa->config.pktin.bit.ts_all = 1;
+	capa->config.pktin.bit.ts_ptp = 1;
+	return 0;
+}
+
 static int sock_mmap_stats(pktio_entry_t *pktio_entry,
 			   odp_pktio_stats_t *stats)
 {
@@ -625,7 +651,7 @@ const pktio_if_ops_t sock_mmap_pktio_ops = {
 	.promisc_mode_get = sock_mmap_promisc_mode_get,
 	.mac_get = sock_mmap_mac_addr_get,
 	.link_status = sock_mmap_link_status,
-	.capability = NULL,
+	.capability = sock_mmap_capability,
 	.pktin_ts_res = NULL,
 	.pktin_ts_from_ns = NULL,
 	.config = NULL,
diff --git a/platform/linux-generic/pktio/tap.c b/platform/linux-generic/pktio/tap.c
index e02d311..8baf1f3 100644
--- a/platform/linux-generic/pktio/tap.c
+++ b/platform/linux-generic/pktio/tap.c
@@ -179,11 +179,11 @@ static int tap_pktio_close(pktio_entry_t *pktio_entry)
 	return ret;
 }
 
-static odp_packet_t pack_odp_pkt(odp_pool_t pool,
-				 const void *data,
-				 unsigned int len)
+static odp_packet_t pack_odp_pkt(odp_pool_t pool, const void *data,
+				 unsigned int len, odp_time_t *ts)
 {
 	odp_packet_t pkt;
+	odp_packet_hdr_t *pkt_hdr;
 
 	pkt = packet_alloc(pool, len, 1);
 
@@ -196,7 +196,9 @@ static odp_packet_t pack_odp_pkt(odp_pool_t pool,
 		return ODP_PACKET_INVALID;
 	}
 
-	packet_parse_l2(odp_packet_hdr(pkt));
+	pkt_hdr = odp_packet_hdr(pkt);
+	packet_parse_l2(pkt_hdr);
+	packet_set_ts(pkt_hdr, ts);
 
 	return pkt;
 }
@@ -208,18 +210,27 @@ static int tap_pktio_recv(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
 	unsigned i;
 	uint8_t buf[BUF_SIZE];
 	pkt_tap_t *tap = &pktio_entry->s.pkt_tap;
+	odp_time_t ts_val;
+	odp_time_t *ts = NULL;
+
+	if (pktio_entry->s.config.pktin.bit.ts_all ||
+	    pktio_entry->s.config.pktin.bit.ts_ptp)
+		ts = &ts_val;
 
 	for (i = 0; i < len; i++) {
 		do {
 			retval = read(tap->fd, buf, BUF_SIZE);
 		} while (retval < 0 && errno == EINTR);
 
+		if (ts != NULL)
+			ts_val = odp_time_global();
+
 		if (retval < 0) {
 			__odp_errno = errno;
 			break;
 		}
 
-		pkts[i] = pack_odp_pkt(tap->pool, buf, retval);
+		pkts[i] = pack_odp_pkt(tap->pool, buf, retval, ts);
 		if (pkts[i] == ODP_PACKET_INVALID)
 			break;
 	}
@@ -310,6 +321,21 @@ static int tap_mac_addr_get(pktio_entry_t *pktio_entry, void *mac_addr)
 	return ETH_ALEN;
 }
 
+static int tap_capability(pktio_entry_t *pktio_entry ODP_UNUSED,
+			  odp_pktio_capability_t *capa)
+{
+	memset(capa, 0, sizeof(odp_pktio_capability_t));
+
+	capa->max_input_queues  = 1;
+	capa->max_output_queues = 1;
+	capa->set_op.op.promisc_mode = 1;
+
+	odp_pktio_config_init(&capa->config);
+	capa->config.pktin.bit.ts_all = 1;
+	capa->config.pktin.bit.ts_ptp = 1;
+	return 0;
+}
+
 const pktio_if_ops_t tap_pktio_ops = {
 	.init_global = NULL,
 	.init_local = NULL,
@@ -324,6 +350,7 @@ const pktio_if_ops_t tap_pktio_ops = {
 	.promisc_mode_set = tap_promisc_mode_set,
 	.promisc_mode_get = tap_promisc_mode_get,
 	.mac_get = tap_mac_addr_get,
+	.capability = tap_capability,
 	.pktin_ts_res = NULL,
 	.pktin_ts_from_ns = NULL,
 	.config = NULL
