Linux Documentation
 help / color / mirror / Atom feed
* [PATCH net-next v05 6/6] hinic3: Remove unneeded coalesce parameters
From: Fan Gong @ 2026-04-11  3:37 UTC (permalink / raw)
  To: Fan Gong, Zhu Yikai, netdev, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Andrew Lunn,
	Ioana Ciornei, Mohsin Bashir
  Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
	Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1775711066.git.zhuyikai1@h-partners.com>

  Remove unneeded coalesce parameters in irq handling.

Co-developed-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
 drivers/net/ethernet/huawei/hinic3/hinic3_irq.c | 6 +-----
 drivers/net/ethernet/huawei/hinic3/hinic3_rx.h  | 3 ---
 2 files changed, 1 insertion(+), 8 deletions(-)

diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index d3b3927b5408..42464c007174 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -156,13 +156,9 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
 	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
 
 	err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
-	if (err) {
+	if (err)
 		netdev_err(netdev,
 			   "Failed to modify moderation for Queue: %u\n", q_id);
-	} else {
-		nic_dev->rxqs[q_id].last_coalesc_timer_cfg = coalesc_timer_cfg;
-		nic_dev->rxqs[q_id].last_pending_limit = pending_limit;
-	}
 
 	return err;
 }
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
index c11d080408a7..2ab691ed11a9 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
@@ -111,9 +111,6 @@ struct hinic3_rxq {
 	dma_addr_t             cqe_start_paddr;
 
 	struct dim             dim;
-
-	u8                     last_coalesc_timer_cfg;
-	u8                     last_pending_limit;
 } ____cacheline_aligned;
 
 struct hinic3_dyna_rxq_res {
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v05 4/6] hinic3: Add ethtool rss ops
From: Fan Gong @ 2026-04-11  3:37 UTC (permalink / raw)
  To: Fan Gong, Zhu Yikai, netdev, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Andrew Lunn,
	Ioana Ciornei, Mohsin Bashir
  Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
	Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1775711066.git.zhuyikai1@h-partners.com>

  Implement following ethtool callback function:
.get_rxnfc
.set_rxnfc
.get_channels
.set_channels
.get_rxfh_indir_size
.get_rxfh_key_size
.get_rxfh
.set_rxfh

  These callbacks allow users to utilize ethtool for detailed
RSS parameters configuration and monitoring.

Co-developed-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
 .../ethernet/huawei/hinic3/hinic3_ethtool.c   |   9 +
 .../huawei/hinic3/hinic3_mgmt_interface.h     |   2 +
 .../net/ethernet/huawei/hinic3/hinic3_rss.c   | 487 +++++++++++++++++-
 .../net/ethernet/huawei/hinic3/hinic3_rss.h   |  19 +
 4 files changed, 515 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index f0fb9a30840b..69663ee70cbd 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -15,6 +15,7 @@
 #include "hinic3_hw_comm.h"
 #include "hinic3_nic_dev.h"
 #include "hinic3_nic_cfg.h"
+#include "hinic3_rss.h"
 
 #define HINIC3_MGMT_VERSION_MAX_LEN     32
 /* Coalesce time properties in microseconds */
@@ -1231,6 +1232,14 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
 	.get_pause_stats                = hinic3_get_pause_stats,
 	.get_coalesce                   = hinic3_get_coalesce,
 	.set_coalesce                   = hinic3_set_coalesce,
+	.get_rxnfc                      = hinic3_get_rxnfc,
+	.set_rxnfc                      = hinic3_set_rxnfc,
+	.get_channels                   = hinic3_get_channels,
+	.set_channels                   = hinic3_set_channels,
+	.get_rxfh_indir_size            = hinic3_get_rxfh_indir_size,
+	.get_rxfh_key_size              = hinic3_get_rxfh_key_size,
+	.get_rxfh                       = hinic3_get_rxfh,
+	.set_rxfh                       = hinic3_set_rxfh,
 };
 
 void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
index 76c691f82703..3c1263ff99ff 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
@@ -282,6 +282,7 @@ enum l2nic_cmd {
 	L2NIC_CMD_SET_VLAN_FILTER_EN  = 26,
 	L2NIC_CMD_SET_RX_VLAN_OFFLOAD = 27,
 	L2NIC_CMD_CFG_RSS             = 60,
+	L2NIC_CMD_GET_RSS_CTX_TBL     = 62,
 	L2NIC_CMD_CFG_RSS_HASH_KEY    = 63,
 	L2NIC_CMD_CFG_RSS_HASH_ENGINE = 64,
 	L2NIC_CMD_SET_RSS_CTX_TBL     = 65,
@@ -301,6 +302,7 @@ enum l2nic_ucode_cmd {
 	L2NIC_UCODE_CMD_MODIFY_QUEUE_CTX  = 0,
 	L2NIC_UCODE_CMD_CLEAN_QUEUE_CTX   = 1,
 	L2NIC_UCODE_CMD_SET_RSS_INDIR_TBL = 4,
+	L2NIC_UCODE_CMD_GET_RSS_INDIR_TBL = 6,
 };
 
 /* hilink mac group command */
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
index 25db74d8c7dd..b40d5fa885c2 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
@@ -155,7 +155,7 @@ static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
 				       L2NIC_CMD_SET_RSS_CTX_TBL, &msg_params);
 
 	if (ctx_tbl.msg_head.status == MGMT_STATUS_CMD_UNSUPPORTED) {
-		return MGMT_STATUS_CMD_UNSUPPORTED;
+		return -EOPNOTSUPP;
 	} else if (err || ctx_tbl.msg_head.status) {
 		dev_err(hwdev->dev, "mgmt Failed to set rss context offload, err: %d, status: 0x%x\n",
 			err, ctx_tbl.msg_head.status);
@@ -165,6 +165,39 @@ static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
 	return 0;
 }
 
+static int hinic3_get_rss_type(struct hinic3_hwdev *hwdev,
+			       struct hinic3_rss_type *rss_type)
+{
+	struct l2nic_cmd_rss_ctx_tbl ctx_tbl = {};
+	struct mgmt_msg_params msg_params = {};
+	int err;
+
+	ctx_tbl.func_id = hinic3_global_func_id(hwdev);
+
+	mgmt_msg_params_init_default(&msg_params, &ctx_tbl, sizeof(ctx_tbl));
+
+	err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
+				       L2NIC_CMD_GET_RSS_CTX_TBL,
+				       &msg_params);
+	if (err || ctx_tbl.msg_head.status) {
+		dev_err(hwdev->dev, "Failed to get hash type, err: %d, status: 0x%x\n",
+			err, ctx_tbl.msg_head.status);
+		return -EINVAL;
+	}
+
+	rss_type->ipv4         = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV4);
+	rss_type->ipv6         = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV6);
+	rss_type->ipv6_ext     = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV6_EXT);
+	rss_type->tcp_ipv4     = L2NIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV4);
+	rss_type->tcp_ipv6     = L2NIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV6);
+	rss_type->tcp_ipv6_ext = L2NIC_RSS_TYPE_GET(ctx_tbl.context,
+						    TCP_IPV6_EXT);
+	rss_type->udp_ipv4     = L2NIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV4);
+	rss_type->udp_ipv6     = L2NIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV6);
+
+	return 0;
+}
+
 static int hinic3_rss_cfg_hash_type(struct hinic3_hwdev *hwdev, u8 opcode,
 				    enum hinic3_rss_hash_type *type)
 {
@@ -264,7 +297,8 @@ static int hinic3_set_hw_rss_parameters(struct net_device *netdev, u8 rss_en)
 	if (err)
 		return err;
 
-	hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
+	if (!netif_is_rxfh_configured(netdev))
+		hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
 
 	err = hinic3_config_rss_hw_resource(netdev, nic_dev->rss_indir);
 	if (err)
@@ -334,3 +368,452 @@ void hinic3_try_to_enable_rss(struct net_device *netdev)
 	clear_bit(HINIC3_RSS_ENABLE, &nic_dev->flags);
 	nic_dev->q_params.num_qps = nic_dev->max_qps;
 }
+
+static int hinic3_set_l4_rss_hash_ops(const struct ethtool_rxnfc *cmd,
+				      struct hinic3_rss_type *rss_type)
+{
+	u8 rss_l4_en;
+
+	switch (cmd->data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)) {
+	case 0:
+		rss_l4_en = 0;
+		break;
+	case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
+		rss_l4_en = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (cmd->flow_type) {
+	case TCP_V4_FLOW:
+		rss_type->tcp_ipv4 = rss_l4_en;
+		break;
+	case TCP_V6_FLOW:
+		rss_type->tcp_ipv6 = rss_l4_en;
+		break;
+	case UDP_V4_FLOW:
+		rss_type->udp_ipv4 = rss_l4_en;
+		break;
+	case UDP_V6_FLOW:
+		rss_type->udp_ipv6 = rss_l4_en;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hinic3_update_rss_hash_opts(struct net_device *netdev,
+				       struct ethtool_rxnfc *cmd,
+				       struct hinic3_rss_type *rss_type)
+{
+	int err;
+
+	switch (cmd->flow_type) {
+	case TCP_V4_FLOW:
+	case TCP_V6_FLOW:
+	case UDP_V4_FLOW:
+	case UDP_V6_FLOW:
+		err = hinic3_set_l4_rss_hash_ops(cmd, rss_type);
+		if (err)
+			return err;
+
+		break;
+	case IPV4_FLOW:
+		rss_type->ipv4 = 1;
+		break;
+	case IPV6_FLOW:
+		rss_type->ipv6 = 1;
+		break;
+	default:
+		netdev_err(netdev, "Unsupported flow type\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hinic3_set_rss_hash_opts(struct net_device *netdev,
+				    struct ethtool_rxnfc *cmd)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic3_rss_type rss_type;
+	int err;
+
+	if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+		cmd->data = 0;
+		netdev_err(netdev, "RSS is disable, not support to set flow-hash\n");
+		return -EOPNOTSUPP;
+	}
+
+	/* RSS only supports hashing of IP addresses and L4 ports */
+	if (cmd->data & ~(RXH_IP_SRC | RXH_IP_DST |
+			  RXH_L4_B_0_1 | RXH_L4_B_2_3))
+		return -EINVAL;
+
+	/* Both IP addresses must be part of the hash tuple */
+	if (!(cmd->data & RXH_IP_SRC) || !(cmd->data & RXH_IP_DST))
+		return -EINVAL;
+
+	err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
+	if (err) {
+		netdev_err(netdev, "Failed to get rss type\n");
+		return err;
+	}
+
+	err = hinic3_update_rss_hash_opts(netdev, cmd, &rss_type);
+	if (err)
+		return err;
+
+	err = hinic3_set_rss_type(nic_dev->hwdev, rss_type);
+	if (err) {
+		netdev_err(netdev, "Failed to set rss type\n");
+		return err;
+	}
+
+	nic_dev->rss_type = rss_type;
+
+	return 0;
+}
+
+static void convert_rss_type(u8 rss_opt, struct ethtool_rxnfc *cmd)
+{
+	if (rss_opt)
+		cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
+}
+
+static int hinic3_convert_rss_type(struct net_device *netdev,
+				   struct hinic3_rss_type *rss_type,
+				   struct ethtool_rxnfc *cmd)
+{
+	cmd->data = RXH_IP_SRC | RXH_IP_DST;
+	switch (cmd->flow_type) {
+	case TCP_V4_FLOW:
+		convert_rss_type(rss_type->tcp_ipv4, cmd);
+		break;
+	case TCP_V6_FLOW:
+		convert_rss_type(rss_type->tcp_ipv6, cmd);
+		break;
+	case UDP_V4_FLOW:
+		convert_rss_type(rss_type->udp_ipv4, cmd);
+		break;
+	case UDP_V6_FLOW:
+		convert_rss_type(rss_type->udp_ipv6, cmd);
+		break;
+	case IPV4_FLOW:
+	case IPV6_FLOW:
+		break;
+	default:
+		netdev_err(netdev, "Unsupported flow type\n");
+		cmd->data = 0;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hinic3_get_rss_hash_opts(struct net_device *netdev,
+				    struct ethtool_rxnfc *cmd)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic3_rss_type rss_type;
+	int err;
+
+	cmd->data = 0;
+
+	if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags))
+		return 0;
+
+	err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
+	if (err) {
+		netdev_err(netdev, "Failed to get rss type\n");
+		return err;
+	}
+
+	return hinic3_convert_rss_type(netdev, &rss_type, cmd);
+}
+
+int hinic3_get_rxnfc(struct net_device *netdev,
+		     struct ethtool_rxnfc *cmd, u32 *rule_locs)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	int err = 0;
+
+	switch (cmd->cmd) {
+	case ETHTOOL_GRXRINGS:
+		cmd->data = nic_dev->q_params.num_qps;
+		break;
+	case ETHTOOL_GRXFH:
+		err = hinic3_get_rss_hash_opts(netdev, cmd);
+		break;
+	default:
+		err = -EOPNOTSUPP;
+		break;
+	}
+
+	return err;
+}
+
+int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
+{
+	int err;
+
+	switch (cmd->cmd) {
+	case ETHTOOL_SRXFH:
+		err = hinic3_set_rss_hash_opts(netdev, cmd);
+		break;
+	default:
+		err = -EOPNOTSUPP;
+		break;
+	}
+
+	return err;
+}
+
+static u16 hinic3_max_channels(struct net_device *netdev)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	u8 tcs = netdev_get_num_tc(netdev);
+
+	return tcs ? nic_dev->max_qps / tcs : nic_dev->max_qps;
+}
+
+static u16 hinic3_curr_channels(struct net_device *netdev)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+	if (netif_running(netdev))
+		return nic_dev->q_params.num_qps ?
+				nic_dev->q_params.num_qps : 1;
+	else
+		return min_t(u16, hinic3_max_channels(netdev),
+			     nic_dev->q_params.num_qps);
+}
+
+void hinic3_get_channels(struct net_device *netdev,
+			 struct ethtool_channels *channels)
+{
+	channels->max_rx = 0;
+	channels->max_tx = 0;
+	channels->max_other = 0;
+	/* report maximum channels */
+	channels->max_combined = hinic3_max_channels(netdev);
+	channels->rx_count = 0;
+	channels->tx_count = 0;
+	channels->other_count = 0;
+	/* report flow director queues as maximum channels */
+	channels->combined_count = hinic3_curr_channels(netdev);
+}
+
+static int
+hinic3_validate_channel_parameter(struct net_device *netdev,
+				  const struct ethtool_channels *channels)
+{
+	u16 max_channel = hinic3_max_channels(netdev);
+	unsigned int count = channels->combined_count;
+
+	if (!count) {
+		netdev_err(netdev, "Unsupported combined_count=0\n");
+		return -EINVAL;
+	}
+
+	if (channels->tx_count || channels->rx_count || channels->other_count) {
+		netdev_err(netdev, "Setting rx/tx/other count not supported\n");
+		return -EINVAL;
+	}
+
+	if (count > max_channel) {
+		netdev_err(netdev, "Combined count %u exceed limit %u\n", count,
+			   max_channel);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int hinic3_set_channels(struct net_device *netdev,
+			struct ethtool_channels *channels)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	unsigned int count = channels->combined_count;
+	struct hinic3_dyna_txrxq_params q_params;
+	int err;
+
+	if (hinic3_validate_channel_parameter(netdev, channels))
+		return -EINVAL;
+
+	if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+		netdev_err(netdev, "This function doesn't support RSS, only support 1 queue pair\n");
+		return -EOPNOTSUPP;
+	}
+
+	netdev_dbg(netdev, "Set max combined queue number from %u to %u\n",
+		   nic_dev->q_params.num_qps, count);
+
+	if (netif_running(netdev)) {
+		q_params = nic_dev->q_params;
+		q_params.num_qps = (u16)count;
+		q_params.txqs_res = NULL;
+		q_params.rxqs_res = NULL;
+		q_params.irq_cfg = NULL;
+
+		err = hinic3_change_channel_settings(netdev, &q_params);
+		if (err) {
+			netdev_err(netdev, "Failed to change channel settings\n");
+			return err;
+		}
+	} else {
+		nic_dev->q_params.num_qps = (u16)count;
+	}
+
+	return 0;
+}
+
+u32 hinic3_get_rxfh_indir_size(struct net_device *netdev)
+{
+	return L2NIC_RSS_INDIR_SIZE;
+}
+
+static int hinic3_set_rss_rxfh(struct net_device *netdev,
+			       const u32 *indir, u8 *key)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	int err;
+	u32 i;
+
+	if (indir) {
+		for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
+			nic_dev->rss_indir[i] = (u16)indir[i];
+
+		err = hinic3_rss_set_indir_tbl(nic_dev->hwdev,
+					       nic_dev->rss_indir);
+		if (err) {
+			netdev_err(netdev, "Failed to set rss indir table\n");
+			return err;
+		}
+	}
+
+	if (key) {
+		err = hinic3_rss_set_hash_key(nic_dev->hwdev, key);
+		if (err) {
+			netdev_err(netdev, "Failed to set rss key\n");
+			return err;
+		}
+
+		memcpy(nic_dev->rss_hkey, key, L2NIC_RSS_KEY_SIZE);
+	}
+
+	return 0;
+}
+
+u32 hinic3_get_rxfh_key_size(struct net_device *netdev)
+{
+	return L2NIC_RSS_KEY_SIZE;
+}
+
+static int hinic3_rss_get_indir_tbl(struct hinic3_hwdev *hwdev,
+				    u32 *indir_table)
+{
+	struct hinic3_cmd_buf_pair pair;
+	__le16 *indir_tbl = NULL;
+	int err, i;
+
+	err = hinic3_cmd_buf_pair_init(hwdev, &pair);
+	if (err) {
+		dev_err(hwdev->dev, "Failed to allocate cmd_buf.\n");
+		return err;
+	}
+
+	err = hinic3_cmdq_detail_resp(hwdev, MGMT_MOD_L2NIC,
+				      L2NIC_UCODE_CMD_GET_RSS_INDIR_TBL,
+				      pair.in, pair.out, NULL);
+	if (err) {
+		dev_err(hwdev->dev, "Failed to get rss indir table\n");
+		goto err_get_indir_tbl;
+	}
+
+	indir_tbl = (__le16 *)pair.out->buf;
+	for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
+		indir_table[i] = le16_to_cpu(*(indir_tbl + i));
+
+err_get_indir_tbl:
+	hinic3_cmd_buf_pair_uninit(hwdev, &pair);
+
+	return err;
+}
+
+int hinic3_get_rxfh(struct net_device *netdev,
+		    struct ethtool_rxfh_param *rxfh)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	int err = 0;
+
+	if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+		netdev_err(netdev, "Rss is disabled\n");
+		return -EOPNOTSUPP;
+	}
+
+	rxfh->hfunc =
+		nic_dev->rss_hash_type == HINIC3_RSS_HASH_ENGINE_TYPE_XOR ?
+		ETH_RSS_HASH_XOR : ETH_RSS_HASH_TOP;
+
+	if (rxfh->indir) {
+		err = hinic3_rss_get_indir_tbl(nic_dev->hwdev, rxfh->indir);
+		if (err)
+			return err;
+	}
+
+	if (rxfh->key)
+		memcpy(rxfh->key, nic_dev->rss_hkey, L2NIC_RSS_KEY_SIZE);
+
+	return err;
+}
+
+static int hinic3_update_hash_func_type(struct net_device *netdev, u8 hfunc)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	enum hinic3_rss_hash_type new_rss_hash_type;
+
+	switch (hfunc) {
+	case ETH_RSS_HASH_NO_CHANGE:
+		return 0;
+	case ETH_RSS_HASH_XOR:
+		new_rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_XOR;
+		break;
+	case ETH_RSS_HASH_TOP:
+		new_rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_TOEP;
+		break;
+	default:
+		netdev_err(netdev, "Unsupported hash func %u\n", hfunc);
+		return -EOPNOTSUPP;
+	}
+
+	if (new_rss_hash_type == nic_dev->rss_hash_type)
+		return 0;
+
+	nic_dev->rss_hash_type = new_rss_hash_type;
+	return hinic3_rss_set_hash_type(nic_dev->hwdev, nic_dev->rss_hash_type);
+}
+
+int hinic3_set_rxfh(struct net_device *netdev,
+		    struct ethtool_rxfh_param *rxfh,
+		    struct netlink_ext_ack *extack)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	int err;
+
+	if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+		netdev_err(netdev, "Not support to set rss parameters when rss is disable\n");
+		return -EOPNOTSUPP;
+	}
+
+	err = hinic3_update_hash_func_type(netdev, rxfh->hfunc);
+	if (err)
+		return err;
+
+	err = hinic3_set_rss_rxfh(netdev, rxfh->indir, rxfh->key);
+
+	return err;
+}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
index 78d82c2aca06..9f1b77780cd4 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
@@ -5,10 +5,29 @@
 #define _HINIC3_RSS_H_
 
 #include <linux/netdevice.h>
+#include <linux/ethtool.h>
 
 int hinic3_rss_init(struct net_device *netdev);
 void hinic3_rss_uninit(struct net_device *netdev);
 void hinic3_try_to_enable_rss(struct net_device *netdev);
 void hinic3_clear_rss_config(struct net_device *netdev);
 
+int hinic3_get_rxnfc(struct net_device *netdev,
+		     struct ethtool_rxnfc *cmd, u32 *rule_locs);
+int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd);
+
+void hinic3_get_channels(struct net_device *netdev,
+			 struct ethtool_channels *channels);
+int hinic3_set_channels(struct net_device *netdev,
+			struct ethtool_channels *channels);
+
+u32 hinic3_get_rxfh_indir_size(struct net_device *netdev);
+u32 hinic3_get_rxfh_key_size(struct net_device *netdev);
+
+int hinic3_get_rxfh(struct net_device *netdev,
+		    struct ethtool_rxfh_param *rxfh);
+int hinic3_set_rxfh(struct net_device *netdev,
+		    struct ethtool_rxfh_param *rxfh,
+		    struct netlink_ext_ack *extack);
+
 #endif
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v05 5/6] hinic3: Configure netdev->watchdog_timeo to set nic tx timeout
From: Fan Gong @ 2026-04-11  3:37 UTC (permalink / raw)
  To: Fan Gong, Zhu Yikai, netdev, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Andrew Lunn,
	Ioana Ciornei, Mohsin Bashir
  Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
	Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1775711066.git.zhuyikai1@h-partners.com>

  Configure netdev watchdog timeout to improve transmission reliability.

Co-developed-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
 drivers/net/ethernet/huawei/hinic3/hinic3_main.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
index 3b470978714a..7e09b4b2da9f 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
@@ -33,6 +33,8 @@
 #define HINIC3_RX_PENDING_LIMIT_LOW   2
 #define HINIC3_RX_PENDING_LIMIT_HIGH  8
 
+#define HINIC3_WATCHDOG_TIMEOUT       5
+
 static void init_intr_coal_param(struct net_device *netdev)
 {
 	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
@@ -246,6 +248,8 @@ static void hinic3_assign_netdev_ops(struct net_device *netdev)
 {
 	hinic3_set_netdev_ops(netdev);
 	hinic3_set_ethtool_ops(netdev);
+
+	netdev->watchdog_timeo = HINIC3_WATCHDOG_TIMEOUT * HZ;
 }
 
 static void netdev_feature_init(struct net_device *netdev)
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v05 3/6] hinic3: Add ethtool coalesce ops
From: Fan Gong @ 2026-04-11  3:37 UTC (permalink / raw)
  To: Fan Gong, Zhu Yikai, netdev, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Andrew Lunn,
	Ioana Ciornei, Mohsin Bashir
  Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
	Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1775711066.git.zhuyikai1@h-partners.com>

  Implement following ethtool callback function:
.get_coalesce
.set_coalesce

  These callbacks allow users to utilize ethtool for detailed
RX coalesce configuration and monitoring.

Co-developed-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
 .../ethernet/huawei/hinic3/hinic3_ethtool.c   | 232 +++++++++++++++++-
 1 file changed, 230 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index be26698fc658..f0fb9a30840b 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -17,6 +17,11 @@
 #include "hinic3_nic_cfg.h"
 
 #define HINIC3_MGMT_VERSION_MAX_LEN     32
+/* Coalesce time properties in microseconds */
+#define COALESCE_PENDING_LIMIT_UNIT     8
+#define COALESCE_TIMER_CFG_UNIT         5
+#define COALESCE_MAX_PENDING_LIMIT      (255 * COALESCE_PENDING_LIMIT_UNIT)
+#define COALESCE_MAX_TIMER_CFG          (255 * COALESCE_TIMER_CFG_UNIT)
 
 static void hinic3_get_drvinfo(struct net_device *netdev,
 			       struct ethtool_drvinfo *info)
@@ -985,9 +990,230 @@ static void hinic3_get_pause_stats(struct net_device *netdev,
 	kfree(ps);
 }
 
+static int hinic3_set_queue_coalesce(struct net_device *netdev, u16 q_id,
+				     struct hinic3_intr_coal_info *coal)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic3_intr_coal_info *intr_coal;
+	struct hinic3_interrupt_info info = {};
+	int err;
+
+	intr_coal = &nic_dev->intr_coalesce[q_id];
+
+	intr_coal->coalesce_timer_cfg = coal->coalesce_timer_cfg;
+	intr_coal->pending_limit = coal->pending_limit;
+	intr_coal->rx_pending_limit_low = coal->rx_pending_limit_low;
+	intr_coal->rx_pending_limit_high = coal->rx_pending_limit_high;
+
+	if (!test_bit(HINIC3_INTF_UP, &nic_dev->flags) ||
+	    q_id >= nic_dev->q_params.num_qps || nic_dev->adaptive_rx_coal)
+		return 0;
+
+	info.msix_index = nic_dev->q_params.irq_cfg[q_id].msix_entry_idx;
+	info.interrupt_coalesc_set = 1;
+	info.coalesc_timer_cfg = intr_coal->coalesce_timer_cfg;
+	info.pending_limit = intr_coal->pending_limit;
+	info.resend_timer_cfg = intr_coal->resend_timer_cfg;
+	err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
+	if (err) {
+		netdev_warn(netdev, "Failed to set queue%u coalesce\n", q_id);
+		return err;
+	}
+
+	return 0;
+}
+
+static int is_coalesce_exceed_limit(struct net_device *netdev,
+				    const struct ethtool_coalesce *coal)
+{
+	const struct {
+		const char *name;
+		u32 value;
+		u32 limit;
+	} coalesce_limits[] = {
+		{"rx_coalesce_usecs",
+		 coal->rx_coalesce_usecs,
+		 COALESCE_MAX_TIMER_CFG},
+		{"rx_max_coalesced_frames",
+		 coal->rx_max_coalesced_frames,
+		 COALESCE_MAX_PENDING_LIMIT},
+		{"rx_max_coalesced_frames_low",
+		 coal->rx_max_coalesced_frames_low,
+		 COALESCE_MAX_PENDING_LIMIT},
+		{"rx_max_coalesced_frames_high",
+		 coal->rx_max_coalesced_frames_high,
+		 COALESCE_MAX_PENDING_LIMIT},
+	};
+
+	for (int i = 0; i < ARRAY_SIZE(coalesce_limits); i++) {
+		if (coalesce_limits[i].value > coalesce_limits[i].limit) {
+			netdev_err(netdev, "%s out of range %d-%d\n",
+				   coalesce_limits[i].name, 0,
+				   coalesce_limits[i].limit);
+			return -ERANGE;
+		}
+	}
+	return 0;
+}
+
+static int is_coalesce_legal(struct net_device *netdev,
+			     const struct ethtool_coalesce *coal)
+{
+	int err;
+
+	err = is_coalesce_exceed_limit(netdev, coal);
+	if (err)
+		return err;
+
+	if (coal->rx_max_coalesced_frames_low >=
+	    coal->rx_max_coalesced_frames_high) {
+		netdev_err(netdev, "invalid coalesce frame high %u, low %u, unit %d\n",
+			   coal->rx_max_coalesced_frames_high,
+			   coal->rx_max_coalesced_frames_low,
+			   COALESCE_PENDING_LIMIT_UNIT);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void check_coalesce_align(struct net_device *netdev,
+				 u32 item, u32 unit, const char *str)
+{
+	if (item % unit)
+		netdev_warn(netdev, "%s in %d units, change to %u\n",
+			    str, unit, item - item % unit);
+}
+
+#define CHECK_COALESCE_ALIGN(member, unit) \
+	check_coalesce_align(netdev, member, unit, #member)
+
+static void check_coalesce_changed(struct net_device *netdev,
+				   u32 item, u32 unit, u32 ori_val,
+				   const char *obj_str, const char *str)
+{
+	if ((item / unit) != ori_val)
+		netdev_dbg(netdev, "Change %s from %d to %u %s\n",
+			   str, ori_val * unit, item - item % unit, obj_str);
+}
+
+#define CHECK_COALESCE_CHANGED(member, unit, ori_val, obj_str) \
+	check_coalesce_changed(netdev, member, unit, ori_val, obj_str, #member)
+
+static int hinic3_set_hw_coal_param(struct net_device *netdev,
+				    struct hinic3_intr_coal_info *intr_coal)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	int err;
+	u16 i;
+
+	for (i = 0; i < nic_dev->max_qps; i++) {
+		err = hinic3_set_queue_coalesce(netdev, i, intr_coal);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int hinic3_get_coalesce(struct net_device *netdev,
+			       struct ethtool_coalesce *coal,
+			       struct kernel_ethtool_coalesce *kernel_coal,
+			       struct netlink_ext_ack *extack)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic3_intr_coal_info *interrupt_info;
+
+	interrupt_info = &nic_dev->intr_coalesce[0];
+
+	/* TX/RX uses the same interrupt.
+	 * So we only declare RX ethtool_coalesce parameters.
+	 */
+	coal->rx_coalesce_usecs = interrupt_info->coalesce_timer_cfg *
+				  COALESCE_TIMER_CFG_UNIT;
+	coal->rx_max_coalesced_frames = interrupt_info->pending_limit *
+					COALESCE_PENDING_LIMIT_UNIT;
+
+	coal->use_adaptive_rx_coalesce = nic_dev->adaptive_rx_coal;
+
+	coal->rx_max_coalesced_frames_high =
+		interrupt_info->rx_pending_limit_high *
+		COALESCE_PENDING_LIMIT_UNIT;
+
+	coal->rx_max_coalesced_frames_low =
+		interrupt_info->rx_pending_limit_low *
+		COALESCE_PENDING_LIMIT_UNIT;
+
+	return 0;
+}
+
+static int hinic3_set_coalesce(struct net_device *netdev,
+			       struct ethtool_coalesce *coal,
+			       struct kernel_ethtool_coalesce *kernel_coal,
+			       struct netlink_ext_ack *extack)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic3_intr_coal_info *ori_intr_coal;
+	struct hinic3_intr_coal_info intr_coal = {};
+	char obj_str[32];
+	int err;
+
+	err = is_coalesce_legal(netdev, coal);
+	if (err)
+		return err;
+
+	CHECK_COALESCE_ALIGN(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT);
+	CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames,
+			     COALESCE_PENDING_LIMIT_UNIT);
+	CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_high,
+			     COALESCE_PENDING_LIMIT_UNIT);
+	CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_low,
+			     COALESCE_PENDING_LIMIT_UNIT);
+
+	ori_intr_coal = &nic_dev->intr_coalesce[0];
+	snprintf(obj_str, sizeof(obj_str), "for netdev");
+
+	CHECK_COALESCE_CHANGED(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT,
+			       ori_intr_coal->coalesce_timer_cfg, obj_str);
+	CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames,
+			       COALESCE_PENDING_LIMIT_UNIT,
+			       ori_intr_coal->pending_limit, obj_str);
+	CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_high,
+			       COALESCE_PENDING_LIMIT_UNIT,
+			       ori_intr_coal->rx_pending_limit_high, obj_str);
+	CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_low,
+			       COALESCE_PENDING_LIMIT_UNIT,
+			       ori_intr_coal->rx_pending_limit_low, obj_str);
+
+	intr_coal.coalesce_timer_cfg =
+		(u8)(coal->rx_coalesce_usecs / COALESCE_TIMER_CFG_UNIT);
+	intr_coal.pending_limit = (u8)(coal->rx_max_coalesced_frames /
+				      COALESCE_PENDING_LIMIT_UNIT);
+
+	nic_dev->adaptive_rx_coal = coal->use_adaptive_rx_coalesce;
+
+	intr_coal.rx_pending_limit_high =
+		(u8)(coal->rx_max_coalesced_frames_high /
+		     COALESCE_PENDING_LIMIT_UNIT);
+
+	intr_coal.rx_pending_limit_low =
+		(u8)(coal->rx_max_coalesced_frames_low /
+		     COALESCE_PENDING_LIMIT_UNIT);
+
+	/* coalesce timer or pending set to zero will disable coalesce */
+	if (!nic_dev->adaptive_rx_coal &&
+	    (!intr_coal.coalesce_timer_cfg || !intr_coal.pending_limit))
+		netdev_warn(netdev, "Coalesce will be disabled\n");
+
+	return hinic3_set_hw_coal_param(netdev, &intr_coal);
+}
+
 static const struct ethtool_ops hinic3_ethtool_ops = {
-	.supported_coalesce_params      = ETHTOOL_COALESCE_USECS |
-					  ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
+	.supported_coalesce_params      = ETHTOOL_COALESCE_RX_USECS |
+					  ETHTOOL_COALESCE_RX_MAX_FRAMES |
+					  ETHTOOL_COALESCE_USE_ADAPTIVE_RX |
+					  ETHTOOL_COALESCE_RX_MAX_FRAMES_LOW |
+					  ETHTOOL_COALESCE_RX_MAX_FRAMES_HIGH,
 	.get_link_ksettings             = hinic3_get_link_ksettings,
 	.get_drvinfo                    = hinic3_get_drvinfo,
 	.get_msglevel                   = hinic3_get_msglevel,
@@ -1003,6 +1229,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
 	.get_eth_ctrl_stats             = hinic3_get_eth_ctrl_stats,
 	.get_rmon_stats                 = hinic3_get_rmon_stats,
 	.get_pause_stats                = hinic3_get_pause_stats,
+	.get_coalesce                   = hinic3_get_coalesce,
+	.set_coalesce                   = hinic3_set_coalesce,
 };
 
 void hinic3_set_ethtool_ops(struct net_device *netdev)
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v05 2/6] hinic3: Add ethtool statistic ops
From: Fan Gong @ 2026-04-11  3:37 UTC (permalink / raw)
  To: Fan Gong, Zhu Yikai, netdev, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Andrew Lunn,
	Ioana Ciornei, Mohsin Bashir
  Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
	Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1775711066.git.zhuyikai1@h-partners.com>

  Add PF/VF statistics functions in TX and RX processing.
  Implement following ethtool callback function:
.get_sset_count
.get_ethtool_stats
.get_strings
.get_eth_phy_stats
.get_eth_mac_stats
.get_eth_ctrl_stats
.get_rmon_stats
.get_pause_stats

  These callbacks allow users to utilize ethtool for detailed
TX and RX netdev stats monitoring.

Co-developed-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
 .../ethernet/huawei/hinic3/hinic3_ethtool.c   | 485 ++++++++++++++++++
 .../ethernet/huawei/hinic3/hinic3_hw_intf.h   |  13 +-
 .../huawei/hinic3/hinic3_mgmt_interface.h     |  37 ++
 .../ethernet/huawei/hinic3/hinic3_nic_cfg.c   |  64 +++
 .../ethernet/huawei/hinic3/hinic3_nic_cfg.h   | 109 ++++
 .../net/ethernet/huawei/hinic3/hinic3_rx.c    |  59 ++-
 .../net/ethernet/huawei/hinic3/hinic3_rx.h    |  15 +-
 .../net/ethernet/huawei/hinic3/hinic3_tx.c    |  71 ++-
 .../net/ethernet/huawei/hinic3/hinic3_tx.h    |   2 +
 9 files changed, 845 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index e47c3f43e7b9..be26698fc658 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -508,6 +508,483 @@ static int hinic3_set_ringparam(struct net_device *netdev,
 	return 0;
 }
 
+struct hinic3_stats {
+	char name[ETH_GSTRING_LEN];
+	u32  size;
+	int  offset;
+};
+
+#define HINIC3_RXQ_STAT(_stat_item) { \
+	.name   = "rxq%d_"#_stat_item, \
+	.size   = sizeof_field(struct hinic3_rxq_stats, _stat_item), \
+	.offset = offsetof(struct hinic3_rxq_stats, _stat_item) \
+}
+
+#define HINIC3_TXQ_STAT(_stat_item) { \
+	.name   = "txq%d_"#_stat_item, \
+	.size   = sizeof_field(struct hinic3_txq_stats, _stat_item), \
+	.offset = offsetof(struct hinic3_txq_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_rx_queue_stats[] = {
+	HINIC3_RXQ_STAT(csum_errors),
+	HINIC3_RXQ_STAT(other_errors),
+	HINIC3_RXQ_STAT(rx_buf_empty),
+	HINIC3_RXQ_STAT(alloc_skb_err),
+	HINIC3_RXQ_STAT(alloc_rx_buf_err),
+};
+
+static struct hinic3_stats hinic3_tx_queue_stats[] = {
+	HINIC3_TXQ_STAT(busy),
+	HINIC3_TXQ_STAT(skb_pad_err),
+	HINIC3_TXQ_STAT(frag_len_overflow),
+	HINIC3_TXQ_STAT(offload_cow_skb_err),
+	HINIC3_TXQ_STAT(map_frag_err),
+	HINIC3_TXQ_STAT(unknown_tunnel_pkt),
+	HINIC3_TXQ_STAT(frag_size_err),
+};
+
+#define HINIC3_FUNC_STAT(_stat_item) {	\
+	.name   = #_stat_item, \
+	.size   = sizeof_field(struct l2nic_vport_stats, _stat_item), \
+	.offset = offsetof(struct l2nic_vport_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_function_stats[] = {
+	HINIC3_FUNC_STAT(tx_unicast_pkts_vport),
+	HINIC3_FUNC_STAT(tx_unicast_bytes_vport),
+	HINIC3_FUNC_STAT(tx_multicast_pkts_vport),
+	HINIC3_FUNC_STAT(tx_multicast_bytes_vport),
+	HINIC3_FUNC_STAT(tx_broadcast_pkts_vport),
+	HINIC3_FUNC_STAT(tx_broadcast_bytes_vport),
+
+	HINIC3_FUNC_STAT(rx_unicast_pkts_vport),
+	HINIC3_FUNC_STAT(rx_unicast_bytes_vport),
+	HINIC3_FUNC_STAT(rx_multicast_pkts_vport),
+	HINIC3_FUNC_STAT(rx_multicast_bytes_vport),
+	HINIC3_FUNC_STAT(rx_broadcast_pkts_vport),
+	HINIC3_FUNC_STAT(rx_broadcast_bytes_vport),
+
+	HINIC3_FUNC_STAT(tx_discard_vport),
+	HINIC3_FUNC_STAT(rx_discard_vport),
+	HINIC3_FUNC_STAT(tx_err_vport),
+	HINIC3_FUNC_STAT(rx_err_vport),
+};
+
+#define HINIC3_PORT_STAT(_stat_item) { \
+	.name   = #_stat_item, \
+	.size   = sizeof_field(struct mag_cmd_port_stats, _stat_item), \
+	.offset = offsetof(struct mag_cmd_port_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_port_stats[] = {
+	HINIC3_PORT_STAT(mac_tx_fragment_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_undersize_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_undermin_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_1519_max_bad_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_1519_max_good_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_oversize_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_jabber_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_bad_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_bad_oct_num),
+	HINIC3_PORT_STAT(mac_tx_good_oct_num),
+	HINIC3_PORT_STAT(mac_tx_total_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_uni_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pri0_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pri1_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pri2_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pri3_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pri4_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pri5_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pri6_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_pfc_pri7_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_err_all_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_from_app_good_pkt_num),
+	HINIC3_PORT_STAT(mac_tx_from_app_bad_pkt_num),
+
+	HINIC3_PORT_STAT(mac_rx_undermin_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_1519_max_bad_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_1519_max_good_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_bad_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_bad_oct_num),
+	HINIC3_PORT_STAT(mac_rx_good_oct_num),
+	HINIC3_PORT_STAT(mac_rx_total_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_uni_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pri0_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pri1_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pri2_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pri3_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pri4_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pri5_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pri6_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_pfc_pri7_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_send_app_good_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_send_app_bad_pkt_num),
+	HINIC3_PORT_STAT(mac_rx_unfilter_pkt_num),
+};
+
+static int hinic3_get_sset_count(struct net_device *netdev, int sset)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	int count, q_num;
+
+	switch (sset) {
+	case ETH_SS_STATS:
+		q_num = nic_dev->q_params.num_qps;
+		count = ARRAY_SIZE(hinic3_function_stats) +
+			(ARRAY_SIZE(hinic3_tx_queue_stats) +
+			 ARRAY_SIZE(hinic3_rx_queue_stats)) *
+			q_num;
+
+		if (!HINIC3_IS_VF(nic_dev->hwdev))
+			count += ARRAY_SIZE(hinic3_port_stats);
+
+		return count;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static u64 get_val_of_ptr(u32 size, const void *ptr)
+{
+	u64 ret = size == sizeof(u64) ? *(u64 *)ptr :
+		  size == sizeof(u32) ? *(u32 *)ptr :
+		  size == sizeof(u16) ? *(u16 *)ptr :
+		  *(u8 *)ptr;
+
+	return ret;
+}
+
+static void hinic3_get_drv_queue_stats(struct net_device *netdev, u64 *data)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic3_txq_stats txq_stats = {};
+	struct hinic3_rxq_stats rxq_stats = {};
+	u16 i = 0, j, qid;
+	char *p;
+
+	u64_stats_init(&txq_stats.syncp);
+	u64_stats_init(&rxq_stats.syncp);
+
+	for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
+		if (!nic_dev->txqs)
+			break;
+
+		hinic3_txq_get_stats(&nic_dev->txqs[qid], &txq_stats);
+		for (j = 0; j < ARRAY_SIZE(hinic3_tx_queue_stats); j++, i++) {
+			p = (char *)&txq_stats +
+			    hinic3_tx_queue_stats[j].offset;
+			data[i] = get_val_of_ptr(hinic3_tx_queue_stats[j].size,
+						 p);
+		}
+	}
+
+	for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
+		if (!nic_dev->rxqs)
+			break;
+
+		hinic3_rxq_get_stats(&nic_dev->rxqs[qid], &rxq_stats);
+		for (j = 0; j < ARRAY_SIZE(hinic3_rx_queue_stats); j++, i++) {
+			p = (char *)&rxq_stats +
+			    hinic3_rx_queue_stats[j].offset;
+			data[i] = get_val_of_ptr(hinic3_rx_queue_stats[j].size,
+						 p);
+		}
+	}
+}
+
+static u16 hinic3_get_ethtool_port_stats(struct net_device *netdev, u64 *data)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct mag_cmd_port_stats *ps;
+	u16 i = 0, j;
+	char *p;
+	int err;
+
+	ps = kmalloc_obj(*ps);
+	if (!ps)
+		goto err_zero_stats;
+
+	err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+	if (err) {
+		kfree(ps);
+		netdev_err(netdev, "Failed to get port stats from fw\n");
+		goto err_zero_stats;
+	}
+
+	for (j = 0; j < ARRAY_SIZE(hinic3_port_stats); j++, i++) {
+		p = (char *)ps + hinic3_port_stats[j].offset;
+		data[i] = get_val_of_ptr(hinic3_port_stats[j].size, p);
+	}
+
+	kfree(ps);
+
+	return i;
+
+err_zero_stats:
+	memset(&data[i], 0, ARRAY_SIZE(hinic3_port_stats) * sizeof(*data));
+
+	return i + ARRAY_SIZE(hinic3_port_stats);
+}
+
+static void hinic3_get_ethtool_stats(struct net_device *netdev,
+				     struct ethtool_stats *stats, u64 *data)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct l2nic_vport_stats vport_stats = {};
+	u16 i = 0, j;
+	char *p;
+	int err;
+
+	err = hinic3_get_vport_stats(nic_dev->hwdev,
+				     hinic3_global_func_id(nic_dev->hwdev),
+				     &vport_stats);
+	if (err)
+		netdev_err(netdev, "Failed to get function stats from fw\n");
+
+	for (j = 0; j < ARRAY_SIZE(hinic3_function_stats); j++, i++) {
+		p = (char *)&vport_stats + hinic3_function_stats[j].offset;
+		data[i] = get_val_of_ptr(hinic3_function_stats[j].size, p);
+	}
+
+	if (!HINIC3_IS_VF(nic_dev->hwdev))
+		i += hinic3_get_ethtool_port_stats(netdev, data + i);
+
+	hinic3_get_drv_queue_stats(netdev, data + i);
+}
+
+static u16 hinic3_get_hw_stats_strings(struct net_device *netdev, char *p)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	u16 i, cnt = 0;
+
+	for (i = 0; i < ARRAY_SIZE(hinic3_function_stats); i++) {
+		memcpy(p, hinic3_function_stats[i].name, ETH_GSTRING_LEN);
+		p += ETH_GSTRING_LEN;
+		cnt++;
+	}
+
+	if (!HINIC3_IS_VF(nic_dev->hwdev)) {
+		for (i = 0; i < ARRAY_SIZE(hinic3_port_stats); i++) {
+			memcpy(p, hinic3_port_stats[i].name, ETH_GSTRING_LEN);
+			p += ETH_GSTRING_LEN;
+			cnt++;
+		}
+	}
+
+	return cnt;
+}
+
+static void hinic3_get_qp_stats_strings(struct net_device *netdev, char *p)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	u8 *data = p;
+	u16 i, j;
+
+	for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+		for (j = 0; j < ARRAY_SIZE(hinic3_tx_queue_stats); j++)
+			ethtool_sprintf(&data,
+					hinic3_tx_queue_stats[j].name, i);
+	}
+
+	for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+		for (j = 0; j < ARRAY_SIZE(hinic3_rx_queue_stats); j++)
+			ethtool_sprintf(&data,
+					hinic3_rx_queue_stats[j].name, i);
+	}
+}
+
+static void hinic3_get_strings(struct net_device *netdev,
+			       u32 stringset, u8 *data)
+{
+	char *p = (char *)data;
+	u16 offset;
+
+	switch (stringset) {
+	case ETH_SS_STATS:
+		offset = hinic3_get_hw_stats_strings(netdev, p);
+		hinic3_get_qp_stats_strings(netdev,
+					    p + offset * ETH_GSTRING_LEN);
+
+		return;
+	default:
+		netdev_err(netdev, "Invalid string set %u.\n", stringset);
+		return;
+	}
+}
+
+static void hinic3_get_eth_phy_stats(struct net_device *netdev,
+				     struct ethtool_eth_phy_stats *phy_stats)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct mag_cmd_port_stats *ps;
+	int err;
+
+	ps = kmalloc_obj(*ps);
+	if (!ps)
+		return;
+
+	err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+	if (err) {
+		kfree(ps);
+		netdev_err(netdev, "Failed to get eth phy stats from fw\n");
+		return;
+	}
+
+	phy_stats->SymbolErrorDuringCarrier = ps->mac_rx_sym_err_pkt_num;
+
+	kfree(ps);
+}
+
+static void hinic3_get_eth_mac_stats(struct net_device *netdev,
+				     struct ethtool_eth_mac_stats *mac_stats)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct mag_cmd_port_stats *ps;
+	int err;
+
+	ps = kmalloc_obj(*ps);
+	if (!ps)
+		return;
+
+	err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+	if (err) {
+		kfree(ps);
+		netdev_err(netdev, "Failed to get eth mac stats from fw\n");
+		return;
+	}
+
+	mac_stats->FramesTransmittedOK = ps->mac_tx_good_pkt_num;
+	mac_stats->FramesReceivedOK = ps->mac_rx_good_pkt_num;
+	mac_stats->FrameCheckSequenceErrors = ps->mac_rx_fcs_err_pkt_num;
+	mac_stats->OctetsTransmittedOK = ps->mac_tx_total_oct_num;
+	mac_stats->OctetsReceivedOK = ps->mac_rx_total_oct_num;
+	mac_stats->MulticastFramesXmittedOK = ps->mac_tx_multi_pkt_num;
+	mac_stats->BroadcastFramesXmittedOK = ps->mac_tx_broad_pkt_num;
+	mac_stats->MulticastFramesReceivedOK = ps->mac_rx_multi_pkt_num;
+	mac_stats->BroadcastFramesReceivedOK = ps->mac_rx_broad_pkt_num;
+
+	kfree(ps);
+}
+
+static void hinic3_get_eth_ctrl_stats(struct net_device *netdev,
+				      struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct mag_cmd_port_stats *ps;
+	int err;
+
+	ps = kmalloc_obj(*ps);
+	if (!ps)
+		return;
+
+	err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+	if (err) {
+		kfree(ps);
+		netdev_err(netdev, "Failed to get eth ctrl stats from fw\n");
+		return;
+	}
+
+	ctrl_stats->MACControlFramesTransmitted = ps->mac_tx_control_pkt_num;
+	ctrl_stats->MACControlFramesReceived = ps->mac_rx_control_pkt_num;
+
+	kfree(ps);
+}
+
+static const struct ethtool_rmon_hist_range hinic3_rmon_ranges[] = {
+	{     0,    64 },
+	{    65,   127 },
+	{   128,   255 },
+	{   256,   511 },
+	{   512,  1023 },
+	{  1024,  1518 },
+	{  1519,  2047 },
+	{  2048,  4095 },
+	{  4096,  8191 },
+	{  8192,  9216 },
+	{  9217, 12287 },
+	{}
+};
+
+static void hinic3_get_rmon_stats(struct net_device *netdev,
+				  struct ethtool_rmon_stats *rmon_stats,
+				  const struct ethtool_rmon_hist_range **ranges)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct mag_cmd_port_stats *ps;
+	int err;
+
+	ps = kmalloc_obj(*ps);
+	if (!ps)
+		return;
+
+	err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+	if (err) {
+		kfree(ps);
+		netdev_err(netdev, "Failed to get eth rmon stats from fw\n");
+		return;
+	}
+
+	rmon_stats->undersize_pkts	= ps->mac_rx_undersize_pkt_num;
+	rmon_stats->oversize_pkts	= ps->mac_rx_oversize_pkt_num;
+	rmon_stats->fragments		= ps->mac_rx_fragment_pkt_num;
+	rmon_stats->jabbers		= ps->mac_rx_jabber_pkt_num;
+
+	rmon_stats->hist[0]		= ps->mac_rx_64_oct_pkt_num;
+	rmon_stats->hist[1]		= ps->mac_rx_65_127_oct_pkt_num;
+	rmon_stats->hist[2]		= ps->mac_rx_128_255_oct_pkt_num;
+	rmon_stats->hist[3]		= ps->mac_rx_256_511_oct_pkt_num;
+	rmon_stats->hist[4]		= ps->mac_rx_512_1023_oct_pkt_num;
+	rmon_stats->hist[5]		= ps->mac_rx_1024_1518_oct_pkt_num;
+	rmon_stats->hist[6]		= ps->mac_rx_1519_2047_oct_pkt_num;
+	rmon_stats->hist[7]		= ps->mac_rx_2048_4095_oct_pkt_num;
+	rmon_stats->hist[8]		= ps->mac_rx_4096_8191_oct_pkt_num;
+	rmon_stats->hist[9]		= ps->mac_rx_8192_9216_oct_pkt_num;
+	rmon_stats->hist[10]		= ps->mac_rx_9217_12287_oct_pkt_num;
+
+	rmon_stats->hist_tx[0]		= ps->mac_tx_64_oct_pkt_num;
+	rmon_stats->hist_tx[1]		= ps->mac_tx_65_127_oct_pkt_num;
+	rmon_stats->hist_tx[2]		= ps->mac_tx_128_255_oct_pkt_num;
+	rmon_stats->hist_tx[3]		= ps->mac_tx_256_511_oct_pkt_num;
+	rmon_stats->hist_tx[4]		= ps->mac_tx_512_1023_oct_pkt_num;
+	rmon_stats->hist_tx[5]		= ps->mac_tx_1024_1518_oct_pkt_num;
+	rmon_stats->hist_tx[6]		= ps->mac_tx_1519_2047_oct_pkt_num;
+	rmon_stats->hist_tx[7]		= ps->mac_tx_2048_4095_oct_pkt_num;
+	rmon_stats->hist_tx[8]		= ps->mac_tx_4096_8191_oct_pkt_num;
+	rmon_stats->hist_tx[9]		= ps->mac_tx_8192_9216_oct_pkt_num;
+	rmon_stats->hist_tx[10]		= ps->mac_tx_9217_12287_oct_pkt_num;
+
+	*ranges = hinic3_rmon_ranges;
+
+	kfree(ps);
+}
+
+static void hinic3_get_pause_stats(struct net_device *netdev,
+				   struct ethtool_pause_stats *pause_stats)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct mag_cmd_port_stats *ps;
+	int err;
+
+	ps = kmalloc_obj(*ps);
+	if (!ps)
+		return;
+
+	err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+	if (err) {
+		kfree(ps);
+		netdev_err(netdev, "Failed to get eth pause stats from fw\n");
+		return;
+	}
+
+	pause_stats->tx_pause_frames = ps->mac_tx_pause_num;
+	pause_stats->rx_pause_frames = ps->mac_rx_pause_num;
+
+	kfree(ps);
+}
+
 static const struct ethtool_ops hinic3_ethtool_ops = {
 	.supported_coalesce_params      = ETHTOOL_COALESCE_USECS |
 					  ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
@@ -518,6 +995,14 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
 	.get_link                       = ethtool_op_get_link,
 	.get_ringparam                  = hinic3_get_ringparam,
 	.set_ringparam                  = hinic3_set_ringparam,
+	.get_sset_count                 = hinic3_get_sset_count,
+	.get_ethtool_stats              = hinic3_get_ethtool_stats,
+	.get_strings                    = hinic3_get_strings,
+	.get_eth_phy_stats              = hinic3_get_eth_phy_stats,
+	.get_eth_mac_stats              = hinic3_get_eth_mac_stats,
+	.get_eth_ctrl_stats             = hinic3_get_eth_ctrl_stats,
+	.get_rmon_stats                 = hinic3_get_rmon_stats,
+	.get_pause_stats                = hinic3_get_pause_stats,
 };
 
 void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h b/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
index cfc9daa3034f..0b2ebef04c02 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
@@ -51,7 +51,18 @@ static inline void mgmt_msg_params_init_default(struct mgmt_msg_params *msg_para
 	msg_params->in_size = buf_size;
 	msg_params->expected_out_size = buf_size;
 	msg_params->timeout_ms = 0;
-}
+};
+
+static inline void
+mgmt_msg_params_init_in_out(struct mgmt_msg_params *msg_params, void *in_buf,
+			    void *out_buf, u32 in_buf_size, u32 out_buf_size)
+{
+	msg_params->buf_in = in_buf;
+	msg_params->buf_out = out_buf;
+	msg_params->in_size = in_buf_size;
+	msg_params->expected_out_size = out_buf_size;
+	msg_params->timeout_ms = 0;
+};
 
 enum cfg_cmd {
 	CFG_CMD_GET_DEV_CAP = 0,
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
index c5bca3c4af96..76c691f82703 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
@@ -143,6 +143,41 @@ struct l2nic_cmd_set_dcb_state {
 	u8                   rsvd[7];
 };
 
+struct l2nic_port_stats_info {
+	struct mgmt_msg_head msg_head;
+	u16                  func_id;
+	u16                  rsvd1;
+};
+
+struct l2nic_vport_stats {
+	u64 tx_unicast_pkts_vport;
+	u64 tx_unicast_bytes_vport;
+	u64 tx_multicast_pkts_vport;
+	u64 tx_multicast_bytes_vport;
+	u64 tx_broadcast_pkts_vport;
+	u64 tx_broadcast_bytes_vport;
+
+	u64 rx_unicast_pkts_vport;
+	u64 rx_unicast_bytes_vport;
+	u64 rx_multicast_pkts_vport;
+	u64 rx_multicast_bytes_vport;
+	u64 rx_broadcast_pkts_vport;
+	u64 rx_broadcast_bytes_vport;
+
+	u64 tx_discard_vport;
+	u64 rx_discard_vport;
+	u64 tx_err_vport;
+	u64 rx_err_vport;
+};
+
+struct l2nic_cmd_vport_stats {
+	struct mgmt_msg_head     msg_head;
+	u32                      stats_size;
+	u32                      rsvd1;
+	struct l2nic_vport_stats stats;
+	u64                      rsvd2[6];
+};
+
 struct l2nic_cmd_lro_config {
 	struct mgmt_msg_head msg_head;
 	u16                  func_id;
@@ -234,6 +269,7 @@ enum l2nic_cmd {
 	L2NIC_CMD_SET_VPORT_ENABLE    = 6,
 	L2NIC_CMD_SET_RX_MODE         = 7,
 	L2NIC_CMD_SET_SQ_CI_ATTR      = 8,
+	L2NIC_CMD_GET_VPORT_STAT      = 9,
 	L2NIC_CMD_CLEAR_QP_RESOURCE   = 11,
 	L2NIC_CMD_CFG_RX_LRO          = 13,
 	L2NIC_CMD_CFG_LRO_TIMER       = 14,
@@ -272,6 +308,7 @@ enum mag_cmd {
 	MAG_CMD_SET_PORT_ENABLE = 6,
 	MAG_CMD_GET_LINK_STATUS = 7,
 
+	MAG_CMD_GET_PORT_STAT   = 151,
 	MAG_CMD_GET_PORT_INFO   = 153,
 };
 
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
index de5a7984d2cb..1b14dc824ce1 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
@@ -639,6 +639,42 @@ int hinic3_get_link_status(struct hinic3_hwdev *hwdev, bool *link_status_up)
 	return 0;
 }
 
+int hinic3_get_phy_port_stats(struct hinic3_hwdev *hwdev,
+			      struct mag_cmd_port_stats *stats)
+{
+	struct mag_cmd_port_stats_info stats_info = {};
+	struct mag_cmd_get_port_stat *ps;
+	struct mgmt_msg_params msg_params = {};
+	int err;
+
+	ps = kzalloc_obj(*ps);
+	if (!ps)
+		return -ENOMEM;
+
+	stats_info.port_id = hinic3_physical_port_id(hwdev);
+
+	mgmt_msg_params_init_in_out(&msg_params, &stats_info, ps,
+				    sizeof(stats_info), sizeof(*ps));
+
+	err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_HILINK,
+				       MAG_CMD_GET_PORT_STAT, &msg_params);
+
+	if (err || ps->head.status) {
+		dev_err(hwdev->dev,
+			"Failed to get port statistics, err: %d, status: 0x%x\n",
+			err, ps->head.status);
+		err = -EFAULT;
+		goto out;
+	}
+
+	memcpy(stats, &ps->counter, sizeof(*stats));
+
+out:
+	kfree(ps);
+
+	return err;
+}
+
 int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
 			 struct hinic3_nic_port_info *port_info)
 {
@@ -738,3 +774,31 @@ int hinic3_get_pause_info(struct hinic3_nic_dev *nic_dev,
 	return hinic3_cfg_hw_pause(nic_dev->hwdev, MGMT_MSG_CMD_OP_GET,
 				   nic_pause);
 }
+
+int hinic3_get_vport_stats(struct hinic3_hwdev *hwdev, u16 func_id,
+			   struct l2nic_vport_stats *stats)
+{
+	struct l2nic_cmd_vport_stats vport_stats = {};
+	struct l2nic_port_stats_info stats_info = {};
+	struct mgmt_msg_params msg_params = {};
+	int err;
+
+	stats_info.func_id = func_id;
+
+	mgmt_msg_params_init_in_out(&msg_params, &stats_info, &vport_stats,
+				    sizeof(stats_info), sizeof(vport_stats));
+
+	err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
+				       L2NIC_CMD_GET_VPORT_STAT, &msg_params);
+
+	if (err || vport_stats.msg_head.status) {
+		dev_err(hwdev->dev,
+			"Failed to get function statistics, err: %d, status: 0x%x\n",
+			err, vport_stats.msg_head.status);
+		return -EFAULT;
+	}
+
+	memcpy(stats, &vport_stats.stats, sizeof(*stats));
+
+	return 0;
+}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
index 5d52202a8d4e..80573c121539 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
@@ -129,6 +129,110 @@ struct mag_cmd_get_xsfp_present {
 	u8                   rsvd[2];
 };
 
+struct mag_cmd_port_stats {
+	u64 mac_tx_fragment_pkt_num;
+	u64 mac_tx_undersize_pkt_num;
+	u64 mac_tx_undermin_pkt_num;
+	u64 mac_tx_64_oct_pkt_num;
+	u64 mac_tx_65_127_oct_pkt_num;
+	u64 mac_tx_128_255_oct_pkt_num;
+	u64 mac_tx_256_511_oct_pkt_num;
+	u64 mac_tx_512_1023_oct_pkt_num;
+	u64 mac_tx_1024_1518_oct_pkt_num;
+	u64 mac_tx_1519_2047_oct_pkt_num;
+	u64 mac_tx_2048_4095_oct_pkt_num;
+	u64 mac_tx_4096_8191_oct_pkt_num;
+	u64 mac_tx_8192_9216_oct_pkt_num;
+	u64 mac_tx_9217_12287_oct_pkt_num;
+	u64 mac_tx_12288_16383_oct_pkt_num;
+	u64 mac_tx_1519_max_bad_pkt_num;
+	u64 mac_tx_1519_max_good_pkt_num;
+	u64 mac_tx_oversize_pkt_num;
+	u64 mac_tx_jabber_pkt_num;
+	u64 mac_tx_bad_pkt_num;
+	u64 mac_tx_bad_oct_num;
+	u64 mac_tx_good_pkt_num;
+	u64 mac_tx_good_oct_num;
+	u64 mac_tx_total_pkt_num;
+	u64 mac_tx_total_oct_num;
+	u64 mac_tx_uni_pkt_num;
+	u64 mac_tx_multi_pkt_num;
+	u64 mac_tx_broad_pkt_num;
+	u64 mac_tx_pause_num;
+	u64 mac_tx_pfc_pkt_num;
+	u64 mac_tx_pfc_pri0_pkt_num;
+	u64 mac_tx_pfc_pri1_pkt_num;
+	u64 mac_tx_pfc_pri2_pkt_num;
+	u64 mac_tx_pfc_pri3_pkt_num;
+	u64 mac_tx_pfc_pri4_pkt_num;
+	u64 mac_tx_pfc_pri5_pkt_num;
+	u64 mac_tx_pfc_pri6_pkt_num;
+	u64 mac_tx_pfc_pri7_pkt_num;
+	u64 mac_tx_control_pkt_num;
+	u64 mac_tx_err_all_pkt_num;
+	u64 mac_tx_from_app_good_pkt_num;
+	u64 mac_tx_from_app_bad_pkt_num;
+
+	u64 mac_rx_fragment_pkt_num;
+	u64 mac_rx_undersize_pkt_num;
+	u64 mac_rx_undermin_pkt_num;
+	u64 mac_rx_64_oct_pkt_num;
+	u64 mac_rx_65_127_oct_pkt_num;
+	u64 mac_rx_128_255_oct_pkt_num;
+	u64 mac_rx_256_511_oct_pkt_num;
+	u64 mac_rx_512_1023_oct_pkt_num;
+	u64 mac_rx_1024_1518_oct_pkt_num;
+	u64 mac_rx_1519_2047_oct_pkt_num;
+	u64 mac_rx_2048_4095_oct_pkt_num;
+	u64 mac_rx_4096_8191_oct_pkt_num;
+	u64 mac_rx_8192_9216_oct_pkt_num;
+	u64 mac_rx_9217_12287_oct_pkt_num;
+	u64 mac_rx_12288_16383_oct_pkt_num;
+	u64 mac_rx_1519_max_bad_pkt_num;
+	u64 mac_rx_1519_max_good_pkt_num;
+	u64 mac_rx_oversize_pkt_num;
+	u64 mac_rx_jabber_pkt_num;
+	u64 mac_rx_bad_pkt_num;
+	u64 mac_rx_bad_oct_num;
+	u64 mac_rx_good_pkt_num;
+	u64 mac_rx_good_oct_num;
+	u64 mac_rx_total_pkt_num;
+	u64 mac_rx_total_oct_num;
+	u64 mac_rx_uni_pkt_num;
+	u64 mac_rx_multi_pkt_num;
+	u64 mac_rx_broad_pkt_num;
+	u64 mac_rx_pause_num;
+	u64 mac_rx_pfc_pkt_num;
+	u64 mac_rx_pfc_pri0_pkt_num;
+	u64 mac_rx_pfc_pri1_pkt_num;
+	u64 mac_rx_pfc_pri2_pkt_num;
+	u64 mac_rx_pfc_pri3_pkt_num;
+	u64 mac_rx_pfc_pri4_pkt_num;
+	u64 mac_rx_pfc_pri5_pkt_num;
+	u64 mac_rx_pfc_pri6_pkt_num;
+	u64 mac_rx_pfc_pri7_pkt_num;
+	u64 mac_rx_control_pkt_num;
+	u64 mac_rx_sym_err_pkt_num;
+	u64 mac_rx_fcs_err_pkt_num;
+	u64 mac_rx_send_app_good_pkt_num;
+	u64 mac_rx_send_app_bad_pkt_num;
+	u64 mac_rx_unfilter_pkt_num;
+};
+
+struct mag_cmd_port_stats_info {
+	struct mgmt_msg_head head;
+
+	u8                   port_id;
+	u8                   rsvd0[3];
+};
+
+struct mag_cmd_get_port_stat {
+	struct mgmt_msg_head      head;
+
+	struct mag_cmd_port_stats counter;
+	u64                       rsvd1[15];
+};
+
 enum link_err_type {
 	LINK_ERR_MODULE_UNRECOGENIZED,
 	LINK_ERR_NUM,
@@ -209,6 +313,11 @@ int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
 			 struct hinic3_nic_port_info *port_info);
 int hinic3_set_vport_enable(struct hinic3_hwdev *hwdev, u16 func_id,
 			    bool enable);
+int hinic3_get_phy_port_stats(struct hinic3_hwdev *hwdev,
+			      struct mag_cmd_port_stats *stats);
+int hinic3_get_vport_stats(struct hinic3_hwdev *hwdev, u16 func_id,
+			   struct l2nic_vport_stats *stats);
+
 int hinic3_add_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
 int hinic3_del_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
 
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
index 309ab5901379..7fadb88ff722 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
@@ -29,7 +29,7 @@
 #define HINIC3_LRO_PKT_HDR_LEN_IPV4     66
 #define HINIC3_LRO_PKT_HDR_LEN_IPV6     86
 #define HINIC3_LRO_PKT_HDR_LEN(cqe) \
-	(RQ_CQE_OFFOLAD_TYPE_GET((cqe)->offload_type, IP_TYPE) == \
+	(RQ_CQE_OFFOLAD_TYPE_GET(le32_to_cpu((cqe)->offload_type), IP_TYPE) == \
 	 HINIC3_RX_IPV6_PKT ? HINIC3_LRO_PKT_HDR_LEN_IPV6 : \
 	 HINIC3_LRO_PKT_HDR_LEN_IPV4)
 
@@ -46,7 +46,6 @@ static void hinic3_rxq_clean_stats(struct hinic3_rxq_stats *rxq_stats)
 
 	rxq_stats->alloc_skb_err = 0;
 	rxq_stats->alloc_rx_buf_err = 0;
-	rxq_stats->restore_drop_sge = 0;
 	u64_stats_update_end(&rxq_stats->syncp);
 }
 
@@ -155,8 +154,12 @@ static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
 
 		err = rx_alloc_mapped_page(rxq->page_pool, rx_info,
 					   rxq->buf_len);
-		if (unlikely(err))
+		if (unlikely(err)) {
+			u64_stats_update_begin(&rxq->rxq_stats.syncp);
+			rxq->rxq_stats.alloc_rx_buf_err++;
+			u64_stats_update_end(&rxq->rxq_stats.syncp);
 			break;
+		}
 
 		dma_addr = page_pool_get_dma_addr(rx_info->page) +
 			rx_info->page_offset;
@@ -170,6 +173,10 @@ static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
 				rxq->next_to_update << HINIC3_NORMAL_RQ_WQE);
 		rxq->delta -= i;
 		rxq->next_to_alloc = rxq->next_to_update;
+	} else if (free_wqebbs == rxq->q_depth - 1) {
+		u64_stats_update_begin(&rxq->rxq_stats.syncp);
+		rxq->rxq_stats.rx_buf_empty++;
+		u64_stats_update_end(&rxq->rxq_stats.syncp);
 	}
 
 	return i;
@@ -330,11 +337,23 @@ static void hinic3_rx_csum(struct hinic3_rxq *rxq, u32 offload_type,
 	struct net_device *netdev = rxq->netdev;
 	bool l2_tunnel;
 
+	if (unlikely(csum_err == HINIC3_RX_CSUM_IPSU_OTHER_ERR)) {
+		u64_stats_update_begin(&rxq->rxq_stats.syncp);
+		rxq->rxq_stats.other_errors++;
+		u64_stats_update_end(&rxq->rxq_stats.syncp);
+	}
+
 	if (!(netdev->features & NETIF_F_RXCSUM))
 		return;
 
 	if (unlikely(csum_err)) {
 		/* pkt type is recognized by HW, and csum is wrong */
+		if (!(csum_err & (HINIC3_RX_CSUM_HW_CHECK_NONE |
+				  HINIC3_RX_CSUM_IPSU_OTHER_ERR))) {
+			u64_stats_update_begin(&rxq->rxq_stats.syncp);
+			rxq->rxq_stats.csum_errors++;
+			u64_stats_update_end(&rxq->rxq_stats.syncp);
+		}
 		skb->ip_summed = CHECKSUM_NONE;
 		return;
 	}
@@ -387,8 +406,12 @@ static int recv_one_pkt(struct hinic3_rxq *rxq, struct hinic3_rq_cqe *rx_cqe,
 	u16 num_lro;
 
 	skb = hinic3_fetch_rx_buffer(rxq, pkt_len);
-	if (unlikely(!skb))
+	if (unlikely(!skb)) {
+		u64_stats_update_begin(&rxq->rxq_stats.syncp);
+		rxq->rxq_stats.alloc_skb_err++;
+		u64_stats_update_end(&rxq->rxq_stats.syncp);
 		return -ENOMEM;
+	}
 
 	/* place header in linear portion of buffer */
 	if (skb_is_nonlinear(skb))
@@ -550,11 +573,28 @@ int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
 	return 0;
 }
 
+void hinic3_rxq_get_stats(struct hinic3_rxq *rxq,
+			  struct hinic3_rxq_stats *stats)
+{
+	struct hinic3_rxq_stats *rxq_stats = &rxq->rxq_stats;
+	unsigned int start;
+
+	do {
+		start = u64_stats_fetch_begin(&rxq_stats->syncp);
+		stats->csum_errors = rxq_stats->csum_errors;
+		stats->other_errors = rxq_stats->other_errors;
+		stats->rx_buf_empty = rxq_stats->rx_buf_empty;
+		stats->alloc_skb_err = rxq_stats->alloc_skb_err;
+		stats->alloc_rx_buf_err = rxq_stats->alloc_rx_buf_err;
+	} while (u64_stats_fetch_retry(&rxq_stats->syncp, start));
+}
+
 int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
 {
 	struct hinic3_nic_dev *nic_dev = netdev_priv(rxq->netdev);
 	u32 sw_ci, status, pkt_len, vlan_len;
 	struct hinic3_rq_cqe *rx_cqe;
+	u64 rx_bytes = 0;
 	u32 num_wqe = 0;
 	int nr_pkts = 0;
 	u16 num_lro;
@@ -574,10 +614,14 @@ int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
 		if (recv_one_pkt(rxq, rx_cqe, pkt_len, vlan_len, status))
 			break;
 
+		rx_bytes += pkt_len;
 		nr_pkts++;
 		num_lro = RQ_CQE_STATUS_GET(status, NUM_LRO);
-		if (num_lro)
+		if (num_lro) {
+			rx_bytes += (num_lro - 1) *
+				    HINIC3_LRO_PKT_HDR_LEN(rx_cqe);
 			num_wqe += hinic3_get_sge_num(rxq, pkt_len);
+		}
 
 		rx_cqe->status = 0;
 
@@ -588,5 +632,10 @@ int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
 	if (rxq->delta >= HINIC3_RX_BUFFER_WRITE)
 		hinic3_rx_fill_buffers(rxq);
 
+	u64_stats_update_begin(&rxq->rxq_stats.syncp);
+	rxq->rxq_stats.packets += (u64)nr_pkts;
+	rxq->rxq_stats.bytes += rx_bytes;
+	u64_stats_update_end(&rxq->rxq_stats.syncp);
+
 	return nr_pkts;
 }
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
index 06d1b3299e7c..c11d080408a7 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
@@ -8,6 +8,17 @@
 #include <linux/dim.h>
 #include <linux/netdevice.h>
 
+/* rx cqe checksum err */
+#define HINIC3_RX_CSUM_IP_CSUM_ERR      BIT(0)
+#define HINIC3_RX_CSUM_TCP_CSUM_ERR     BIT(1)
+#define HINIC3_RX_CSUM_UDP_CSUM_ERR     BIT(2)
+#define HINIC3_RX_CSUM_IGMP_CSUM_ERR    BIT(3)
+#define HINIC3_RX_CSUM_ICMPV4_CSUM_ERR  BIT(4)
+#define HINIC3_RX_CSUM_ICMPV6_CSUM_ERR  BIT(5)
+#define HINIC3_RX_CSUM_SCTP_CRC_ERR     BIT(6)
+#define HINIC3_RX_CSUM_HW_CHECK_NONE    BIT(7)
+#define HINIC3_RX_CSUM_IPSU_OTHER_ERR   BIT(8)
+
 #define RQ_CQE_OFFOLAD_TYPE_PKT_TYPE_MASK           GENMASK(4, 0)
 #define RQ_CQE_OFFOLAD_TYPE_IP_TYPE_MASK            GENMASK(6, 5)
 #define RQ_CQE_OFFOLAD_TYPE_TUNNEL_PKT_FORMAT_MASK  GENMASK(11, 8)
@@ -39,7 +50,6 @@ struct hinic3_rxq_stats {
 	u64                   rx_buf_empty;
 	u64                   alloc_skb_err;
 	u64                   alloc_rx_buf_err;
-	u64                   restore_drop_sge;
 	struct u64_stats_sync syncp;
 };
 
@@ -123,6 +133,9 @@ void hinic3_free_rxqs_res(struct net_device *netdev, u16 num_rq,
 			  u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
 int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
 			  u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
+
+void hinic3_rxq_get_stats(struct hinic3_rxq *rxq,
+			  struct hinic3_rxq_stats *stats);
 int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget);
 
 #endif
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
index 9306bf0020ca..3fbbfa5d96b6 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
@@ -97,8 +97,12 @@ static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
 
 	dma_info[0].dma = dma_map_single(&pdev->dev, skb->data,
 					 skb_headlen(skb), DMA_TO_DEVICE);
-	if (dma_mapping_error(&pdev->dev, dma_info[0].dma))
+	if (dma_mapping_error(&pdev->dev, dma_info[0].dma)) {
+		u64_stats_update_begin(&txq->txq_stats.syncp);
+		txq->txq_stats.map_frag_err++;
+		u64_stats_update_end(&txq->txq_stats.syncp);
 		return -EFAULT;
+	}
 
 	dma_info[0].len = skb_headlen(skb);
 
@@ -117,6 +121,9 @@ static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
 						     skb_frag_size(frag),
 						     DMA_TO_DEVICE);
 		if (dma_mapping_error(&pdev->dev, dma_info[idx].dma)) {
+			u64_stats_update_begin(&txq->txq_stats.syncp);
+			txq->txq_stats.map_frag_err++;
+			u64_stats_update_end(&txq->txq_stats.syncp);
 			err = -EFAULT;
 			goto err_unmap_page;
 		}
@@ -260,6 +267,9 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task,
 		if (l4_proto != IPPROTO_UDP ||
 		    ((struct udphdr *)skb_transport_header(skb))->dest !=
 		    VXLAN_OFFLOAD_PORT_LE) {
+			u64_stats_update_begin(&txq->txq_stats.syncp);
+			txq->txq_stats.unknown_tunnel_pkt++;
+			u64_stats_update_end(&txq->txq_stats.syncp);
 			/* Unsupported tunnel packet, disable csum offload */
 			skb_checksum_help(skb);
 			return 0;
@@ -433,6 +443,27 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task,
 	return offload;
 }
 
+static void hinic3_get_pkt_stats(struct hinic3_txq *txq, struct sk_buff *skb)
+{
+	u32 hdr_len, tx_bytes;
+	unsigned short pkts;
+
+	if (skb_is_gso(skb)) {
+		hdr_len = (skb_shinfo(skb)->gso_segs - 1) *
+			  skb_tcp_all_headers(skb);
+		tx_bytes = skb->len + hdr_len;
+		pkts = skb_shinfo(skb)->gso_segs;
+	} else {
+		tx_bytes = skb->len > ETH_ZLEN ? skb->len : ETH_ZLEN;
+		pkts = 1;
+	}
+
+	u64_stats_update_begin(&txq->txq_stats.syncp);
+	txq->txq_stats.bytes += tx_bytes;
+	txq->txq_stats.packets += pkts;
+	u64_stats_update_end(&txq->txq_stats.syncp);
+}
+
 static u16 hinic3_get_and_update_sq_owner(struct hinic3_io_queue *sq,
 					  u16 curr_pi, u16 wqebb_cnt)
 {
@@ -539,8 +570,12 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
 	int err;
 
 	if (unlikely(skb->len < MIN_SKB_LEN)) {
-		if (skb_pad(skb, MIN_SKB_LEN - skb->len))
+		if (skb_pad(skb, MIN_SKB_LEN - skb->len)) {
+			u64_stats_update_begin(&txq->txq_stats.syncp);
+			txq->txq_stats.skb_pad_err++;
+			u64_stats_update_end(&txq->txq_stats.syncp);
 			goto err_out;
+		}
 
 		skb->len = MIN_SKB_LEN;
 	}
@@ -595,6 +630,7 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
 				  txq->tx_stop_thrs,
 				  txq->tx_start_thrs);
 
+	hinic3_get_pkt_stats(txq, skb);
 	hinic3_prepare_sq_ctrl(&wqe_combo, queue_info, num_sge, owner);
 	hinic3_write_db(txq->sq, 0, DB_CFLAG_DP_SQ,
 			hinic3_get_sq_local_pi(txq->sq));
@@ -604,6 +640,10 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
 err_drop_pkt:
 	dev_kfree_skb_any(skb);
 err_out:
+	u64_stats_update_begin(&txq->txq_stats.syncp);
+	txq->txq_stats.dropped++;
+	u64_stats_update_end(&txq->txq_stats.syncp);
+
 	return NETDEV_TX_OK;
 }
 
@@ -611,12 +651,19 @@ netdev_tx_t hinic3_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
 {
 	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
 	u16 q_id = skb_get_queue_mapping(skb);
+	struct hinic3_txq *txq;
 
 	if (unlikely(!netif_carrier_ok(netdev)))
 		goto err_drop_pkt;
 
-	if (unlikely(q_id >= nic_dev->q_params.num_qps))
+	if (unlikely(q_id >= nic_dev->q_params.num_qps)) {
+		txq = &nic_dev->txqs[0];
+		u64_stats_update_begin(&txq->txq_stats.syncp);
+		txq->txq_stats.dropped++;
+		u64_stats_update_end(&txq->txq_stats.syncp);
+
 		goto err_drop_pkt;
+	}
 
 	return hinic3_send_one_skb(skb, netdev, &nic_dev->txqs[q_id]);
 
@@ -754,6 +801,24 @@ int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
 	return 0;
 }
 
+void hinic3_txq_get_stats(struct hinic3_txq *txq,
+			  struct hinic3_txq_stats *stats)
+{
+	struct hinic3_txq_stats *txq_stats = &txq->txq_stats;
+	unsigned int start;
+
+	do {
+		start = u64_stats_fetch_begin(&txq_stats->syncp);
+		stats->busy = txq_stats->busy;
+		stats->skb_pad_err = txq_stats->skb_pad_err;
+		stats->frag_len_overflow = txq_stats->frag_len_overflow;
+		stats->offload_cow_skb_err = txq_stats->offload_cow_skb_err;
+		stats->map_frag_err = txq_stats->map_frag_err;
+		stats->unknown_tunnel_pkt = txq_stats->unknown_tunnel_pkt;
+		stats->frag_size_err = txq_stats->frag_size_err;
+	} while (u64_stats_fetch_retry(&txq_stats->syncp, start));
+}
+
 bool hinic3_tx_poll(struct hinic3_txq *txq, int budget)
 {
 	struct net_device *netdev = txq->netdev;
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
index 00194f2a1bcc..0a21c423618f 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
@@ -157,6 +157,8 @@ int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
 			  u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res);
 
 netdev_tx_t hinic3_xmit_frame(struct sk_buff *skb, struct net_device *netdev);
+void hinic3_txq_get_stats(struct hinic3_txq *txq,
+			  struct hinic3_txq_stats *stats);
 bool hinic3_tx_poll(struct hinic3_txq *txq, int budget);
 void hinic3_flush_txqs(struct net_device *netdev);
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v05 1/6] hinic3: Add ethtool queue ops
From: Fan Gong @ 2026-04-11  3:36 UTC (permalink / raw)
  To: Fan Gong, Zhu Yikai, netdev, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Andrew Lunn,
	Ioana Ciornei, Mohsin Bashir
  Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
	Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1775711066.git.zhuyikai1@h-partners.com>

  Implement following ethtool callback function:
.get_ringparam
.set_ringparam

  These callbacks allow users to utilize ethtool for detailed
queue depth configuration and monitoring.

Co-developed-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Zhu Yikai <zhuyikai1@h-partners.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
 .../ethernet/huawei/hinic3/hinic3_ethtool.c   | 101 +++++++++++++++++
 .../net/ethernet/huawei/hinic3/hinic3_irq.c   |  10 +-
 .../net/ethernet/huawei/hinic3/hinic3_main.c  |  11 ++
 .../huawei/hinic3/hinic3_netdev_ops.c         | 104 +++++++++++++++++-
 .../ethernet/huawei/hinic3/hinic3_nic_dev.h   |  16 +++
 .../ethernet/huawei/hinic3/hinic3_nic_io.h    |   4 +
 6 files changed, 241 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index 90fc16288de9..e47c3f43e7b9 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -409,6 +409,105 @@ hinic3_get_link_ksettings(struct net_device *netdev,
 	return 0;
 }
 
+static void hinic3_get_ringparam(struct net_device *netdev,
+				 struct ethtool_ringparam *ring,
+				 struct kernel_ethtool_ringparam *kernel_ring,
+				 struct netlink_ext_ack *extack)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+	ring->rx_max_pending = HINIC3_MAX_RX_QUEUE_DEPTH;
+	ring->tx_max_pending = HINIC3_MAX_TX_QUEUE_DEPTH;
+	ring->rx_pending = nic_dev->rxqs[0].q_depth;
+	ring->tx_pending = nic_dev->txqs[0].q_depth;
+}
+
+static void hinic3_update_qp_depth(struct net_device *netdev,
+				   u32 sq_depth, u32 rq_depth)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	u16 i;
+
+	nic_dev->q_params.sq_depth = sq_depth;
+	nic_dev->q_params.rq_depth = rq_depth;
+	for (i = 0; i < nic_dev->max_qps; i++) {
+		nic_dev->txqs[i].q_depth = sq_depth;
+		nic_dev->txqs[i].q_mask = sq_depth - 1;
+		nic_dev->rxqs[i].q_depth = rq_depth;
+		nic_dev->rxqs[i].q_mask = rq_depth - 1;
+	}
+}
+
+static int hinic3_check_ringparam_valid(struct net_device *netdev,
+					const struct ethtool_ringparam *ring)
+{
+	if (ring->rx_jumbo_pending || ring->rx_mini_pending) {
+		netdev_err(netdev, "Unsupported rx_jumbo_pending/rx_mini_pending\n");
+		return -EINVAL;
+	}
+
+	if (ring->tx_pending > HINIC3_MAX_TX_QUEUE_DEPTH ||
+	    ring->tx_pending < HINIC3_MIN_QUEUE_DEPTH ||
+	    ring->rx_pending > HINIC3_MAX_RX_QUEUE_DEPTH ||
+	    ring->rx_pending < HINIC3_MIN_QUEUE_DEPTH) {
+		netdev_err(netdev,
+			   "Queue depth out of range tx[%d-%d] rx[%d-%d]\n",
+			   HINIC3_MIN_QUEUE_DEPTH, HINIC3_MAX_TX_QUEUE_DEPTH,
+			   HINIC3_MIN_QUEUE_DEPTH, HINIC3_MAX_RX_QUEUE_DEPTH);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int hinic3_set_ringparam(struct net_device *netdev,
+				struct ethtool_ringparam *ring,
+				struct kernel_ethtool_ringparam *kernel_ring,
+				struct netlink_ext_ack *extack)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic3_dyna_txrxq_params q_params = {};
+	u32 new_sq_depth, new_rq_depth;
+	int err;
+
+	err = hinic3_check_ringparam_valid(netdev, ring);
+	if (err)
+		return err;
+
+	new_sq_depth = 1U << ilog2(ring->tx_pending);
+	new_rq_depth = 1U << ilog2(ring->rx_pending);
+	if (new_sq_depth == nic_dev->q_params.sq_depth &&
+	    new_rq_depth == nic_dev->q_params.rq_depth)
+		return 0;
+
+	if (new_sq_depth != ring->tx_pending)
+		netdev_info(netdev, "Requested Tx depth trimmed to %d\n",
+			    new_sq_depth);
+	if (new_rq_depth != ring->rx_pending)
+		netdev_info(netdev, "Requested Rx depth trimmed to %d\n",
+			    new_rq_depth);
+
+	netdev_info(netdev, "Change Tx/Rx ring depth from %u/%u to %u/%u\n",
+		    nic_dev->q_params.sq_depth, nic_dev->q_params.rq_depth,
+		    new_sq_depth, new_rq_depth);
+
+	if (!netif_running(netdev)) {
+		hinic3_update_qp_depth(netdev, new_sq_depth, new_rq_depth);
+	} else {
+		q_params = nic_dev->q_params;
+		q_params.sq_depth = new_sq_depth;
+		q_params.rq_depth = new_rq_depth;
+
+		err = hinic3_change_channel_settings(netdev, &q_params);
+		if (err) {
+			netdev_err(netdev, "Failed to change channel settings\n");
+			return err;
+		}
+	}
+
+	return 0;
+}
+
 static const struct ethtool_ops hinic3_ethtool_ops = {
 	.supported_coalesce_params      = ETHTOOL_COALESCE_USECS |
 					  ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
@@ -417,6 +516,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
 	.get_msglevel                   = hinic3_get_msglevel,
 	.set_msglevel                   = hinic3_set_msglevel,
 	.get_link                       = ethtool_op_get_link,
+	.get_ringparam                  = hinic3_get_ringparam,
+	.set_ringparam                  = hinic3_set_ringparam,
 };
 
 void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index e7d6c2033b45..d3b3927b5408 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -135,10 +135,16 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
 {
 	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
 	struct hinic3_interrupt_info info = {};
+	unsigned long flags;
 	int err;
 
-	if (q_id >= nic_dev->q_params.num_qps)
+	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
+
+	if (!HINIC3_CHANNEL_RES_VALID(nic_dev) ||
+	    q_id >= nic_dev->q_params.num_qps) {
+		spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
 		return 0;
+	}
 
 	info.interrupt_coalesc_set = 1;
 	info.coalesc_timer_cfg = coalesc_timer_cfg;
@@ -147,6 +153,8 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
 	info.resend_timer_cfg =
 		nic_dev->intr_coalesce[q_id].resend_timer_cfg;
 
+	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
+
 	err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
 	if (err) {
 		netdev_err(netdev,
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
index 0a888fe4c975..3b470978714a 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
@@ -179,6 +179,8 @@ static int hinic3_sw_init(struct net_device *netdev)
 	int err;
 
 	mutex_init(&nic_dev->port_state_mutex);
+	mutex_init(&nic_dev->channel_cfg_lock);
+	spin_lock_init(&nic_dev->channel_res_lock);
 
 	nic_dev->q_params.sq_depth = HINIC3_SQ_DEPTH;
 	nic_dev->q_params.rq_depth = HINIC3_RQ_DEPTH;
@@ -314,6 +316,15 @@ static void hinic3_link_status_change(struct net_device *netdev,
 				      bool link_status_up)
 {
 	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	unsigned long flags;
+	bool valid;
+
+	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
+	valid = HINIC3_CHANNEL_RES_VALID(nic_dev);
+	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
+
+	if (!valid)
+		return;
 
 	if (link_status_up) {
 		if (netif_carrier_ok(netdev))
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
index da73811641a9..d652a5ffdc2c 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
@@ -428,6 +428,85 @@ static void hinic3_vport_down(struct net_device *netdev)
 	}
 }
 
+int
+hinic3_change_channel_settings(struct net_device *netdev,
+			       struct hinic3_dyna_txrxq_params *trxq_params)
+{
+	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic3_dyna_txrxq_params old_qp_params = {};
+	struct hinic3_dyna_qp_params new_qp_params = {};
+	struct hinic3_dyna_qp_params cur_qp_params = {};
+	bool need_teardown = false;
+	unsigned long flags;
+	int err;
+
+	mutex_lock(&nic_dev->channel_cfg_lock);
+
+	hinic3_config_num_qps(netdev, trxq_params);
+
+	err = hinic3_alloc_channel_resources(netdev, &new_qp_params,
+					     trxq_params);
+	if (err) {
+		netdev_err(netdev, "Failed to alloc channel resources\n");
+		mutex_unlock(&nic_dev->channel_cfg_lock);
+		return err;
+	}
+
+	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
+	if (!test_and_set_bit(HINIC3_CHANGE_RES_INVALID, &nic_dev->flags))
+		need_teardown = true;
+	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
+
+	if (need_teardown) {
+		hinic3_vport_down(netdev);
+		hinic3_close_channel(netdev);
+		hinic3_uninit_qps(nic_dev, &cur_qp_params);
+		hinic3_free_channel_resources(netdev, &cur_qp_params,
+					      &nic_dev->q_params);
+	}
+
+	if (nic_dev->num_qp_irq > trxq_params->num_qps)
+		hinic3_qp_irq_change(netdev, trxq_params->num_qps);
+
+	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
+	old_qp_params = nic_dev->q_params;
+	nic_dev->q_params = *trxq_params;
+	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
+
+	hinic3_init_qps(nic_dev, &new_qp_params);
+
+	err = hinic3_open_channel(netdev);
+	if (err)
+		goto err_uninit_qps;
+
+	err = hinic3_vport_up(netdev);
+	if (err)
+		goto err_close_channel;
+
+	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
+	clear_bit(HINIC3_CHANGE_RES_INVALID, &nic_dev->flags);
+	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
+
+	mutex_unlock(&nic_dev->channel_cfg_lock);
+
+	return 0;
+
+err_close_channel:
+	hinic3_close_channel(netdev);
+err_uninit_qps:
+	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
+	nic_dev->q_params = old_qp_params;
+	clear_bit(HINIC3_CHANGE_RES_INVALID, &nic_dev->flags);
+	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
+
+	hinic3_uninit_qps(nic_dev, &new_qp_params);
+	hinic3_free_channel_resources(netdev, &new_qp_params, trxq_params);
+
+	mutex_unlock(&nic_dev->channel_cfg_lock);
+
+	return err;
+}
+
 static int hinic3_open(struct net_device *netdev)
 {
 	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
@@ -487,16 +566,33 @@ static int hinic3_close(struct net_device *netdev)
 {
 	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
 	struct hinic3_dyna_qp_params qp_params;
+	bool need_teardown = false;
+	unsigned long flags;
 
 	if (!test_and_clear_bit(HINIC3_INTF_UP, &nic_dev->flags)) {
 		netdev_dbg(netdev, "Netdev already close, do nothing\n");
 		return 0;
 	}
 
-	hinic3_vport_down(netdev);
-	hinic3_close_channel(netdev);
-	hinic3_uninit_qps(nic_dev, &qp_params);
-	hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
+	mutex_lock(&nic_dev->channel_cfg_lock);
+
+	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
+	if (!test_and_set_bit(HINIC3_CHANGE_RES_INVALID, &nic_dev->flags))
+		need_teardown = true;
+	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
+
+	if (need_teardown) {
+		hinic3_vport_down(netdev);
+		hinic3_close_channel(netdev);
+		hinic3_uninit_qps(nic_dev, &qp_params);
+		hinic3_free_channel_resources(netdev, &qp_params,
+					      &nic_dev->q_params);
+	}
+
+	hinic3_free_nicio_res(nic_dev);
+	hinic3_destroy_num_qps(netdev);
+
+	mutex_unlock(&nic_dev->channel_cfg_lock);
 
 	return 0;
 }
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
index 9502293ff710..55b280888ad8 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
@@ -10,6 +10,9 @@
 #include "hinic3_hw_cfg.h"
 #include "hinic3_hwdev.h"
 #include "hinic3_mgmt_interface.h"
+#include "hinic3_nic_io.h"
+#include "hinic3_tx.h"
+#include "hinic3_rx.h"
 
 #define HINIC3_VLAN_BITMAP_BYTE_SIZE(nic_dev)  (sizeof(*(nic_dev)->vlan_bitmap))
 #define HINIC3_VLAN_BITMAP_SIZE(nic_dev)  \
@@ -20,8 +23,13 @@ enum hinic3_flags {
 	HINIC3_MAC_FILTER_CHANGED,
 	HINIC3_RSS_ENABLE,
 	HINIC3_UPDATE_MAC_FILTER,
+	HINIC3_CHANGE_RES_INVALID,
 };
 
+#define HINIC3_CHANNEL_RES_VALID(nic_dev) \
+	(test_bit(HINIC3_INTF_UP, &(nic_dev)->flags) && \
+	 !test_bit(HINIC3_CHANGE_RES_INVALID, &(nic_dev)->flags))
+
 enum hinic3_event_work_flags {
 	HINIC3_EVENT_WORK_TX_TIMEOUT,
 };
@@ -129,6 +137,10 @@ struct hinic3_nic_dev {
 	struct work_struct              rx_mode_work;
 	/* lock for enable/disable port */
 	struct mutex                    port_state_mutex;
+	/* lock for channel configuration */
+	struct mutex                    channel_cfg_lock;
+	/* lock for channel resources */
+	spinlock_t                      channel_res_lock;
 
 	struct list_head                uc_filter_list;
 	struct list_head                mc_filter_list;
@@ -143,6 +155,10 @@ struct hinic3_nic_dev {
 
 void hinic3_set_netdev_ops(struct net_device *netdev);
 int hinic3_set_hw_features(struct net_device *netdev);
+int
+hinic3_change_channel_settings(struct net_device *netdev,
+			       struct hinic3_dyna_txrxq_params *trxq_params);
+
 int hinic3_qps_irq_init(struct net_device *netdev);
 void hinic3_qps_irq_uninit(struct net_device *netdev);
 
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
index 12eefabcf1db..3791b9bc865b 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
@@ -14,6 +14,10 @@ struct hinic3_nic_dev;
 #define HINIC3_RQ_WQEBB_SHIFT      3
 #define HINIC3_SQ_WQEBB_SIZE       BIT(HINIC3_SQ_WQEBB_SHIFT)
 
+#define HINIC3_MAX_TX_QUEUE_DEPTH  65536
+#define HINIC3_MAX_RX_QUEUE_DEPTH  16384
+#define HINIC3_MIN_QUEUE_DEPTH     128
+
 /* ******************** RQ_CTRL ******************** */
 enum hinic3_rq_wqe_type {
 	HINIC3_NORMAL_RQ_WQE = 1,
-- 
2.43.0


^ permalink raw reply related

* [PATCH net-next v05 0/6] net: hinic3: PF initialization
From: Fan Gong @ 2026-04-11  3:36 UTC (permalink / raw)
  To: Fan Gong, Zhu Yikai, netdev, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Andrew Lunn,
	Ioana Ciornei, Mohsin Bashir
  Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
	Shi Jing, Zheng Jiezhen, Maxime Chevallier

This is [3/3] part of hinic3 Ethernet driver second submission.
With this patch hinic3 becomes a complete Ethernet driver with
pf and vf.

Add 20 ethtool ops for information of queue, rss, coalesce and eth data.
Add MTU size validation
Config netdev watchdog timeout.
Remove unneed coalesce parameters.

Changes:

PATCH 03 V01: https://lore.kernel.org/netdev/cover.1773387649.git.zhuyikai1@h-partners.com/
* Add rmon/pause/phy/mac/ctrl stats (Ioana Ciornei)

PATCH 03 V02: https://lore.kernel.org/netdev/cover.1774684571.git.zhuyikai1@h-partners.com/
* Modify "return -EINVAL" intension problem (AI review)
* Use le16_to_cpu for rss_indir pair.out->buf (AI review)
* Use u32 instead of int in coalesce_limits to avoid overflow (AI review)
* Remove redundant u64_stats_update_begin/end when reading stats without
  concurrent reader (AI review)
* Modify nic_dev->stats.syncp logic (AI review)
* Complete rxq/txq stats stats fileds in hinic3_rx/txq_get_stats (AI review)
* Remove statistics values in rtnl_link_stats64 from ethtool statistics
  values (AI review)
* Add channel_cfg_lock & channel_res_lock to protect resources access (AI review)
* Remove OutOfRangeLengthField, FrameToolong and InRangeLengthErrors (Ioana Ciornei)
* Remove redundant mtu commit (Maxime Chevialler)

PATCH 03 V03: https://lore.kernel.org/netdev/cover.1774940117.git.zhuyikai1@h-partners.com/
* Change unnedd to unneeded (AI review)
* Remove packets,bytes,errors and dropped in hinic3_rx/tx_queue_stats (AI review)
* Remove duplicated entried in hinic3_port_stats[] (AI review)
* change stats_info.head.status to ps->head.status (AI review)

PATCH 03 V04: https://lore.kernel.org/netdev/cover.1775618797.git.zhuyikai1@h-partners.com/
* Remove restore_drop_sge in hinic3_rx_queue_stats (AI review)
* Remove hinic3_nic_stats (AI review)
* Use old_q_param to store old config and use it in error handling (Mohsin Bashir)
* Add netdev_info to inform the user that depth is trimmed (Mohsin Bashir)
* Remove const in hinic3_get_qp_stats_strings parameters (Mohsin Bashir)
* Change EOPNOTSUPP to ERANGE in is_coalesce_exceed_limit (Mohsin Bashir)
* Update nic_dev->rss_type after hinic3_set_rss_type (Mohsin Bashir)
* Modify MGMT_STATUS_CMD_UNSUPPORTED to EOPNOTSUPP for complying with the
  error code specifications (Mohsin Bashir)

PATCH 03 V05:
* Clear HINIC3_CHANGE_RES_INVALID bit in error handling (AI review)
* Use low >= high to avoid low=high in is_coalesce_legal (AI review)
* As tx and rx share interrupts, we only use ETHTOOL_COALESCE_RX_USECS for
user setting to avoid user misunderstanding. So we do not add
ETHTOOL_COALESCE_TX_USECS. (Mohsin Bashir & AI review)

Fan Gong (6):
  hinic3: Add ethtool queue ops
  hinic3: Add ethtool statistic ops
  hinic3: Add ethtool coalesce ops
  hinic3: Add ethtool rss ops
  hinic3: Configure netdev->watchdog_timeo to set nic tx timeout
  hinic3: Remove unneeded coalesce parameters

 .../ethernet/huawei/hinic3/hinic3_ethtool.c   | 827 +++++++++++++++++-
 .../ethernet/huawei/hinic3/hinic3_hw_intf.h   |  13 +-
 .../net/ethernet/huawei/hinic3/hinic3_irq.c   |  16 +-
 .../net/ethernet/huawei/hinic3/hinic3_main.c  |  15 +
 .../huawei/hinic3/hinic3_mgmt_interface.h     |  39 +
 .../huawei/hinic3/hinic3_netdev_ops.c         | 104 ++-
 .../ethernet/huawei/hinic3/hinic3_nic_cfg.c   |  64 ++
 .../ethernet/huawei/hinic3/hinic3_nic_cfg.h   | 109 +++
 .../ethernet/huawei/hinic3/hinic3_nic_dev.h   |  16 +
 .../ethernet/huawei/hinic3/hinic3_nic_io.h    |   4 +
 .../net/ethernet/huawei/hinic3/hinic3_rss.c   | 487 ++++++++++-
 .../net/ethernet/huawei/hinic3/hinic3_rss.h   |  19 +
 .../net/ethernet/huawei/hinic3/hinic3_rx.c    |  59 +-
 .../net/ethernet/huawei/hinic3/hinic3_rx.h    |  18 +-
 .../net/ethernet/huawei/hinic3/hinic3_tx.c    |  71 +-
 .../net/ethernet/huawei/hinic3/hinic3_tx.h    |   2 +
 16 files changed, 1836 insertions(+), 27 deletions(-)


base-commit: 8e7adcf81564a3fe886a6270eea7558f063e5538
-- 
2.43.0


^ permalink raw reply

* Re: [PATCH v9 2/3] hwmon: ltc4283: Add support for the LTC4283 Swap Controller
From: Guenter Roeck @ 2026-04-10 23:27 UTC (permalink / raw)
  To: nuno.sa, linux-gpio, linux-hwmon, devicetree, linux-doc
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Linus Walleij, Bartosz Golaszewski
In-Reply-To: <20260406-ltc4283-support-v9-2-b66cfc749261@analog.com>

On 4/6/26 07:31, Nuno Sá via B4 Relay wrote:
> From: Nuno Sá <nuno.sa@analog.com>
> 
> Support the LTC4283 Hot Swap Controller. The device features programmable
> current limit with foldback and independently adjustable inrush current to
> optimize the MOSFET safe operating area (SOA). The SOA timer limits MOSFET
> temperature rise for reliable protection against overstresses.
> 
> An I2C interface and onboard ADC allow monitoring of board current,
> voltage, power, energy, and fault status.
> 
> Signed-off-by: Nuno Sá <nuno.sa@analog.com>

The patch still has some issues. Please see

https://sashiko.dev/#/patchset/20260406-ltc4283-support-v9-0-b66cfc749261%40analog.com

Specifically:

- regmap_clear_bits() may not cause problems, but it is not the best
   choice either because the register was already read.
   It might be better to just write the value to be masked since
   both the register value and the mask are known.

- I can't comment on the energy accuracy lost. That is your call.

- Clamping before multiplying is indeed wrong.
   You'll need to clamp before multiplying (and then possibly
   clamp again).

-  %*ph: The AI seems to have a point.

- debugfs: False positive. I'll need to check if the guidance ever made it into the
   Agent's prompts.

Thanks,
Guenter

> ---
>   Documentation/hwmon/index.rst   |    1 +
>   Documentation/hwmon/ltc4283.rst |  266 ++++++
>   MAINTAINERS                     |    1 +
>   drivers/hwmon/Kconfig           |   12 +
>   drivers/hwmon/Makefile          |    1 +
>   drivers/hwmon/ltc4283.c         | 1808 +++++++++++++++++++++++++++++++++++++++
>   6 files changed, 2089 insertions(+)
> 
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 199f35a75282..d54dda83ab6e 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -144,6 +144,7 @@ Hardware Monitoring Kernel Drivers
>      ltc4260
>      ltc4261
>      ltc4282
> +   ltc4283
>      ltc4286
>      macsmc-hwmon
>      max127
> diff --git a/Documentation/hwmon/ltc4283.rst b/Documentation/hwmon/ltc4283.rst
> new file mode 100644
> index 000000000000..ba88445e45f4
> --- /dev/null
> +++ b/Documentation/hwmon/ltc4283.rst
> @@ -0,0 +1,266 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +
> +Kernel drivers ltc4283
> +==========================================
> +
> +Supported chips:
> +
> +  * Analog Devices LTC4283
> +
> +    Prefix: 'ltc4283'
> +
> +    Addresses scanned: -
> +
> +    Datasheet:
> +
> +        https://www.analog.com/media/en/technical-documentation/data-sheets/ltc4283.pdf
> +
> +Author: Nuno Sá <nuno.sa@analog.com>
> +
> +Description
> +___________
> +
> +The LTC4283 negative voltage hot swap controller drives an external N-channel
> +MOSFET to allow a board to be safely inserted and removed from a live backplane.
> +The device features programmable current limit with foldback and independently
> +adjustable inrush current to optimize the MOSFET safe operating area (SOA). The
> +SOA timer limits MOSFET temperature rise for reliable protection against
> +overstresses. An I2C interface and onboard gear-shift ADC allow monitoring of
> +board current, voltage, power, energy, and fault status.  Additional features
> +respond to input UV/OV, interrupt the host when a fault has occurred, notify
> +when output power is good, detect insertion of a board, turn off the MOSFET
> +if an external supply monitor fails to indicate power good within a timeout
> +period, and auto-reboot after a programmable delay following a host commanded
> +turn-off.
> +
> +Sysfs entries
> +_____________
> +
> +The following attributes are supported. Limits are read-write and all the other
> +attributes are read-only. Note that the VADIOx channels might not be available
> +if the ADIO pins are used as GPIOs (naturally also affects the respective
> +differential channels).
> +
> +======================= ==========================================
> +in0_lcrit_alarm         Critical Undervoltage alarm
> +in0_crit_alarm          Critical Overvoltage alarm
> +in0_label		Channel label (VIN)
> +
> +in1_input		Output voltage (mV).
> +in1_min			Undervoltage threshold
> +in1_max			Overvoltage threshold
> +in1_lowest		Lowest measured voltage
> +in1_highest		Highest measured voltage
> +in1_reset_history	Write 1 to reset history.
> +in1_min_alarm		Undervoltage alarm
> +in1_max_alarm		Overvoltage alarm
> +in1_label		Channel label (VPWR)
> +
> +in2_input		Output voltage (mV).
> +in2_min			Undervoltage threshold
> +in2_max			Overvoltage threshold
> +in2_lowest		Lowest measured voltage
> +in2_highest		Highest measured voltage
> +in2_reset_history	Write 1 to reset history.
> +in2_min_alarm		Undervoltage alarm
> +in2_max_alarm		Overvoltage alarm
> +in2_enable		Enable/Disable monitoring.
> +in2_label		Channel label (VADI1)
> +
> +in3_input		Output voltage (mV).
> +in3_min			Undervoltage threshold
> +in3_max			Overvoltage threshold
> +in3_lowest		Lowest measured voltage
> +in3_highest		Highest measured voltage
> +in3_reset_history	Write 1 to reset history.
> +in3_min_alarm		Undervoltage alarm
> +in3_max_alarm		Overvoltage alarm
> +in3_enable		Enable/Disable monitoring.
> +in3_label		Channel label (VADI2)
> +
> +in4_input		Output voltage (mV).
> +in4_min			Undervoltage threshold
> +in4_max			Overvoltage threshold
> +in4_lowest		Lowest measured voltage
> +in4_highest		Highest measured voltage
> +in4_reset_history	Write 1 to reset history.
> +in4_min_alarm		Undervoltage alarm
> +in4_max_alarm		Overvoltage alarm
> +in4_enable		Enable/Disable monitoring.
> +in4_label		Channel label (VADI3)
> +
> +in5_input		Output voltage (mV).
> +in5_min			Undervoltage threshold
> +in5_max			Overvoltage threshold
> +in5_lowest		Lowest measured voltage
> +in5_highest		Highest measured voltage
> +in5_reset_history	Write 1 to reset history.
> +in5_min_alarm		Undervoltage alarm
> +in5_max_alarm		Overvoltage alarm
> +in5_enable		Enable/Disable monitoring.
> +in5_label		Channel label (VADI4)
> +
> +in6_input		Output voltage (mV).
> +in6_min			Undervoltage threshold
> +in6_max			Overvoltage threshold
> +in6_lowest		Lowest measured voltage
> +in6_highest		Highest measured voltage
> +in6_reset_history	Write 1 to reset history.
> +in6_min_alarm		Undervoltage alarm
> +in6_max_alarm		Overvoltage alarm
> +in6_enable		Enable/Disable monitoring.
> +in6_label		Channel label (VADIO1)
> +
> +in7_input		Output voltage (mV).
> +in7_min			Undervoltage threshold
> +in7_max			Overvoltage threshold
> +in7_lowest		Lowest measured voltage
> +in7_highest		Highest measured voltage
> +in7_reset_history	Write 1 to reset history.
> +in7_min_alarm		Undervoltage alarm
> +in7_max_alarm		Overvoltage alarm
> +in7_enable		Enable/Disable monitoring.
> +in7_label		Channel label (VADIO2)
> +
> +in8_input		Output voltage (mV).
> +in8_min			Undervoltage threshold
> +in8_max			Overvoltage threshold
> +in8_lowest		Lowest measured voltage
> +in8_highest		Highest measured voltage
> +in8_reset_history	Write 1 to reset history.
> +in8_min_alarm		Undervoltage alarm
> +in8_max_alarm		Overvoltage alarm
> +in8_enable		Enable/Disable monitoring.
> +in8_label		Channel label (VADIO3)
> +
> +in9_input		Output voltage (mV).
> +in9_min			Undervoltage threshold
> +in9_max			Overvoltage threshold
> +in9_lowest		Lowest measured voltage
> +in9_highest		Highest measured voltage
> +in9_reset_history	Write 1 to reset history.
> +in9_min_alarm		Undervoltage alarm
> +in9_max_alarm		Overvoltage alarm
> +in9_enable		Enable/Disable monitoring.
> +in9_label		Channel label (VADIO4)
> +
> +in10_input		Output voltage (mV).
> +in10_min		Undervoltage threshold
> +in10_max		Overvoltage threshold
> +in10_lowest		Lowest measured voltage
> +in10_highest		Highest measured voltage
> +in10_reset_history	Write 1 to reset history.
> +in10_min_alarm		Undervoltage alarm
> +in10_max_alarm		Overvoltage alarm
> +in10_enable		Enable/Disable monitoring.
> +in10_label		Channel label (DRNS)
> +
> +in11_input		Output voltage (mV).
> +in11_min		Undervoltage threshold
> +in11_max		Overvoltage threshold
> +in11_lowest		Lowest measured voltage
> +in11_highest		Highest measured voltage
> +in11_reset_history	Write 1 to reset history.
> +			Also clears fet bad and short fault logs.
> +in11_min_alarm		Undervoltage alarm
> +in11_max_alarm		Overvoltage alarm
> +in11_enable		Enable/Disable monitoring
> +in11_fault		Failure in the MOSFET. Either bad or shorted FET.
> +in11_label		Channel label (DRAIN)
> +
> +in12_input		Output voltage (mV).
> +in12_min		Undervoltage threshold
> +in12_max		Overvoltage threshold
> +in12_lowest		Lowest measured voltage
> +in12_highest		Highest measured voltage
> +in12_reset_history	Write 1 to reset history.
> +in12_min_alarm		Undervoltage alarm
> +in12_max_alarm		Overvoltage alarm
> +in12_enable		Enable/Disable monitoring.
> +in12_label		Channel label (ADIN2-ADIN1)
> +
> +in13_input		Output voltage (mV).
> +in13_min		Undervoltage threshold
> +in13_max		Overvoltage threshold
> +in13_lowest		Lowest measured voltage
> +in13_highest		Highest measured voltage
> +in13_reset_history	Write 1 to reset history.
> +in13_min_alarm		Undervoltage alarm
> +in13_max_alarm		Overvoltage alarm
> +in13_enable		Enable/Disable monitoring.
> +in13_label		Channel label (ADIN4-ADIN3)
> +
> +in14_input		Output voltage (mV).
> +in14_min		Undervoltage threshold
> +in14_max		Overvoltage threshold
> +in14_lowest		Lowest measured voltage
> +in14_highest		Highest measured voltage
> +in14_reset_history	Write 1 to reset history.
> +in14_min_alarm		Undervoltage alarm
> +in14_max_alarm		Overvoltage alarm
> +in14_enable		Enable/Disable monitoring.
> +in14_label		Channel label (ADIO2-ADIO1)
> +
> +in15_input		Output voltage (mV).
> +in15_min		Undervoltage threshold
> +in15_max		Overvoltage threshold
> +in15_lowest		Lowest measured voltage
> +in15_highest		Highest measured voltage
> +in15_reset_history	Write 1 to reset history.
> +in15_min_alarm		Undervoltage alarm
> +in15_max_alarm		Overvoltage alarm
> +in15_enable		Enable/Disable monitoring.
> +in15_label		Channel label (ADIO4-ADIO3)
> +
> +curr1_input		Sense current (mA)
> +curr1_min		Undercurrent threshold
> +curr1_max		Overcurrent threshold
> +curr1_lowest		Lowest measured current
> +curr1_highest		Highest measured current
> +curr1_reset_history	Write 1 to reset curr1 history.
> +			Also clears overcurrent fault logs.
> +curr1_min_alarm		Undercurrent alarm
> +curr1_max_alarm		Overcurrent alarm
> +curr1_crit_alarm        Critical Overcurrent alarm
> +curr1_label		Channel label (ISENSE)
> +
> +power1_input		Power (in uW)
> +power1_min		Low power threshold
> +power1_max		High power threshold
> +power1_input_lowest	Historical minimum power use
> +power1_input_highest	Historical maximum power use
> +power1_reset_history	Write 1 to reset power1 history.
> +			Also clears power fault logs.
> +power1_min_alarm	Low power alarm
> +power1_max_alarm	High power alarm
> +power1_label		Channel label (Power)
> +
> +energy1_input		Measured energy over time (in microJoule)
> +energy1_enable		Enable/Disable Energy accumulation
> +======================= ==========================================
> +
> +DebugFs entries
> +_______________
> +
> +The chip also has a fault log register where failures can be logged. Hence,
> +as these are logging events, we give access to them in debugfs. Note that
> +even if some failure is detected in these logs, it does necessarily mean
> +that the failure is still present. As mentioned in the proper Sysfs entries,
> +these logs can be cleared by writing in the proper reset_history attribute.
> +
> +.. warning:: The debugfs interface is subject to change without notice
> +             and is only available when the kernel is compiled with
> +             ``CONFIG_DEBUG_FS`` defined.
> +
> +``/sys/kernel/debug/i2c/i2c-[X]/[X]-addr/``
> +contains the following attributes:
> +
> +=======================		==========================================
> +power1_failed_fault_log		Set to 1 by a power1 fault occurring.
> +power1_good_input_fault_log	Set to 1 by a power1 good input fault occurring at PGIO3.
> +in11_fet_short_fault_log	Set to 1 when a FET-short fault occurs.
> +in11_fet_bad_fault_log		Set to 1 when a FET-BAD fault occurs.
> +in0_lcrit_fault_log		Set to 1 by a VIN undervoltage fault occurring.
> +in0_crit_fault_log		Set to 1 by a VIN overvoltage fault occurring.
> +curr1_crit_fault_log		Set to 1 by an overcurrent fault occurring.
> +======================= 	==========================================
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 3f727d7fdfa4..a63833b6fe8b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15166,6 +15166,7 @@ M:	Nuno Sá <nuno.sa@analog.com>
>   L:	linux-hwmon@vger.kernel.org
>   S:	Supported
>   F:	Documentation/devicetree/bindings/hwmon/adi,ltc4283.yaml
> +F:	drivers/hwmon/ltc4283.c
>   
>   LTC4286 HARDWARE MONITOR DRIVER
>   M:	Delphine CC Chiu <Delphine_CC_Chiu@Wiwynn.com>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index fb847ab40ab4..4d9f500ae6ee 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1157,6 +1157,18 @@ config SENSORS_LTC4282
>   	  This driver can also be built as a module. If so, the module will
>   	  be called ltc4282.
>   
> +config SENSORS_LTC4283
> +	tristate "Analog Devices LTC4283"
> +	depends on I2C
> +	select REGMAP_I2C
> +	select AUXILIARY_BUS
> +	help
> +	  If you say yes here you get support for Analog Devices LTC4283
> +	  Negative Voltage Hot Swap Controller I2C interface.
> +
> +	  This driver can also be built as a module. If so, the module will
> +	  be called ltc4283.
> +
>   config SENSORS_LTQ_CPUTEMP
>   	bool "Lantiq cpu temperature sensor driver"
>   	depends on SOC_XWAY
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 0fce31b43eb1..b9d7b0287b9c 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -147,6 +147,7 @@ obj-$(CONFIG_SENSORS_LTC4245)	+= ltc4245.o
>   obj-$(CONFIG_SENSORS_LTC4260)	+= ltc4260.o
>   obj-$(CONFIG_SENSORS_LTC4261)	+= ltc4261.o
>   obj-$(CONFIG_SENSORS_LTC4282)	+= ltc4282.o
> +obj-$(CONFIG_SENSORS_LTC4283)	+= ltc4283.o
>   obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
>   obj-$(CONFIG_SENSORS_MACSMC_HWMON)	+= macsmc-hwmon.o
>   obj-$(CONFIG_SENSORS_MAX1111)	+= max1111.o
> diff --git a/drivers/hwmon/ltc4283.c b/drivers/hwmon/ltc4283.c
> new file mode 100644
> index 000000000000..2a2674a55167
> --- /dev/null
> +++ b/drivers/hwmon/ltc4283.c
> @@ -0,0 +1,1808 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Analog Devices LTC4283 I2C Negative Voltage Hot Swap Controller (HWMON)
> + *
> + * Copyright 2025 Analog Devices Inc.
> + */
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitmap.h>
> +#include <linux/bitops.h>
> +#include <linux/bits.h>
> +
> +#include <linux/debugfs.h>
> +#include <linux/device.h>
> +#include <linux/device/devres.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/math.h>
> +#include <linux/math64.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +
> +#include <linux/mod_devicetable.h>
> +#include <linux/overflow.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/unaligned.h>
> +#include <linux/units.h>
> +
> +#define LTC4283_SYSTEM_STATUS		0x00
> +#define LTC4283_FAULT_STATUS		0x03
> +#define   LTC4283_OV_MASK		BIT(0)
> +#define   LTC4283_UV_MASK		BIT(1)
> +#define   LTC4283_OC_MASK		BIT(2)
> +#define   LTC4283_FET_BAD_MASK		BIT(3)
> +#define   LTC4283_FET_SHORT_MASK	BIT(6)
> +#define LTC4283_FAULT_LOG		0x04
> +#define   LTC4283_OV_FAULT_MASK		BIT(0)
> +#define   LTC4283_UV_FAULT_MASK		BIT(1)
> +#define   LTC4283_OC_FAULT_MASK		BIT(2)
> +#define   LTC4283_FET_BAD_FAULT_MASK	BIT(3)
> +#define   LTC4283_PGI_FAULT_MASK	BIT(4)
> +#define   LTC4283_PWR_FAIL_FAULT_MASK	BIT(5)
> +#define   LTC4283_FET_SHORT_FAULT_MASK	BIT(6)
> +#define LTC4283_ADC_ALM_LOG_1		0x05
> +#define   LTC4283_POWER_LOW_ALM		BIT(0)
> +#define   LTC4283_POWER_HIGH_ALM	BIT(1)
> +#define   LTC4283_SENSE_LOW_ALM		BIT(4)
> +#define   LTC4283_SENSE_HIGH_ALM	BIT(5)
> +#define LTC4283_ADC_ALM_LOG_2		0x06
> +#define LTC4283_ADC_ALM_LOG_3		0x07
> +#define LTC4283_ADC_ALM_LOG_4		0x08
> +#define LTC4283_ADC_ALM_LOG_5		0x09
> +#define LTC4283_CONTROL_1		0x0a
> +#define   LTC4283_RW_PAGE_MASK		BIT(0)
> +#define   LTC4283_PIGIO2_ACLB_MASK	BIT(2)
> +#define   LTC4283_PWRGD_RST_CTRL_MASK	BIT(3)
> +#define   LTC4283_FET_BAD_OFF_MASK	BIT(4)
> +#define   LTC4283_THERM_TMR_MASK	BIT(5)
> +#define   LTC4283_DVDT_MASK		BIT(6)
> +#define LTC4283_CONTROL_2		0x0b
> +#define   LTC4283_OV_RETRY_MASK		BIT(0)
> +#define   LTC4283_UV_RETRY_MASK		BIT(1)
> +#define   LTC4283_OC_RETRY_MASK		GENMASK(3, 2)
> +#define   LTC4283_FET_BAD_RETRY_MASK	GENMASK(5, 4)
> +#define   LTC4283_EXT_FAULT_RETRY_MASK	BIT(7)
> +#define LTC4283_RESERVED_OC		0x0c
> +#define LTC4283_CONFIG_1		0x0d
> +#define   LTC4283_FB_MASK		GENMASK(3, 2)
> +#define   LTC4283_ILIM_MASK		GENMASK(7, 4)
> +#define LTC4283_CONFIG_2		0x0e
> +#define   LTC4283_COOLING_DL_MASK	GENMASK(3, 1)
> +#define   LTC4283_FTBD_DL_MASK		GENMASK(5, 4)
> +#define LTC4283_CONFIG_3		0x0f
> +#define   LTC4283_VPWR_DRNS_MASK	BIT(6)
> +#define   LTC4283_EXTFLT_TURN_OFF_MASK	BIT(7)
> +#define LTC4283_PGIO_CONFIG		0x10
> +#define   LTC4283_PGIO1_CFG_MASK	GENMASK(1, 0)
> +#define   LTC4283_PGIO2_CFG_MASK	GENMASK(3, 2)
> +#define   LTC4283_PGIO3_CFG_MASK	GENMASK(5, 4)
> +#define   LTC4283_PGIO4_CFG_MASK	GENMASK(7, 6)
> +#define LTC4283_PGIO_CONFIG_2		0x11
> +#define   LTC4283_ADC_MASK		GENMASK(2, 0)
> +#define LTC4283_ADC_SELECT(c)		(0x13 + (c) / 8)
> +#define   LTC4283_ADC_SELECT_MASK(c)	BIT((c) % 8)
> +#define LTC4283_SENSE_MIN_TH		0x1b
> +#define LTC4283_SENSE_MAX_TH		0x1c
> +#define LTC4283_VPWR_MIN_TH		0x1d
> +#define LTC4283_VPWR_MAX_TH		0x1e
> +#define LTC4283_POWER_MIN_TH		0x1f
> +#define LTC4283_POWER_MAX_TH		0x20
> +#define LTC4283_ADC_2_MIN_TH(c)		(0x21 + (c) * 2)
> +#define LTC4283_ADC_2_MAX_TH(c)		(0x22 + (c) * 2)
> +#define LTC4283_ADC_2_MIN_TH_DIFF(c)	(0x39 + (c) * 2)
> +#define LTC4283_ADC_2_MAX_TH_DIFF(c)	(0x3a + (c) * 2)
> +#define LTC4283_SENSE			0x41
> +#define LTC4283_SENSE_MIN		0x42
> +#define LTC4283_SENSE_MAX		0x43
> +#define LTC4283_VPWR			0x44
> +#define LTC4283_VPWR_MIN		0x45
> +#define LTC4283_VPWR_MAX		0x46
> +#define LTC4283_POWER			0x47
> +#define LTC4283_POWER_MIN		0x48
> +#define LTC4283_POWER_MAX		0x49
> +#define LTC4283_RESERVED_68		0x68
> +#define LTC4283_RESERVED_6D		0x6D
> +/* get channels from ADC 2 */
> +#define LTC4283_ADC_2(c)		(0x4a + (c) * 3)
> +#define LTC4283_ADC_2_MIN(c)		(0x4b + (c) * 3)
> +#define LTC4283_ADC_2_MAX(c)		(0x4c + (c) * 3)
> +#define LTC4283_ADC_2_DIFF(c)		(0x6e + (c) * 3)
> +#define LTC4283_ADC_2_MIN_DIFF(c)	(0x6f + (c) * 3)
> +#define LTC4283_ADC_2_MAX_DIFF(c)	(0x70 + (c) * 3)
> +#define LTC4283_ENERGY			0x7a
> +#define LTC4283_METER_CONTROL		0x84
> +#define   LTC4283_INTEGRATE_I_MASK	BIT(0)
> +#define   LTC4283_METER_HALT_MASK	BIT(6)
> +#define LTC4283_RESERVED_86		0x86
> +#define LTC4283_RESERVED_8F		0x8F
> +#define LTC4283_FAULT_LOG_CTRL		0x90
> +#define   LTC4283_FAULT_LOG_EN_MASK	BIT(7)
> +#define LTC4283_RESERVED_91		0x91
> +#define LTC4283_RESERVED_A1		0xA1
> +#define LTC4283_RESERVED_A3		0xA3
> +#define LTC4283_RESERVED_AC		0xAC
> +#define LTC4283_POWER_PLAY_MSB		0xE7
> +#define LTC4283_POWER_PLAY_LSB		0xE8
> +#define LTC4283_RESERVED_F1		0xF1
> +#define LTC4283_RESERVED_FF		0xFF
> +
> +/* also applies for differential channels */
> +#define LTC4283_ADC1_FS_uV		32768
> +#define LTC4283_ADC2_FS_mV		2048
> +#define LTC4283_TCONV_uS		64103
> +#define LTC4283_VILIM_MIN_uV		15000
> +#define LTC4283_VILIM_MAX_uV		30000
> +#define LTC4283_VILIM_RANGE	\
> +	(LTC4283_VILIM_MAX_uV - LTC4283_VILIM_MIN_uV + 1)
> +
> +#define LTC4283_PGIO_FUNC_GPIO		2
> +#define LTC4283_PGIO2_FUNC_ACLB		3
> +
> +/*
> + * Maximum value for rsense in nano ohms. The reasoning for this value is that
> + * it's the max value for which multiplying by 256 does not overflow long on
> + * 32bits. For the minimum value, is a sane minimum rsense for which power_max
> + * does not overflow 32bits.
> + */
> +#define LTC4283_MAX_RSENSE	1677721599
> +#define LTC4283_MIN_RSENSE	50000
> +
> +/* voltage channels */
> +enum {
> +	LTC4283_CHAN_VIN,
> +	LTC4283_CHAN_VPWR,
> +	LTC4283_CHAN_ADI_1,
> +	LTC4283_CHAN_ADI_2,
> +	LTC4283_CHAN_ADI_3,
> +	LTC4283_CHAN_ADI_4,
> +	LTC4283_CHAN_ADIO_1,
> +	LTC4283_CHAN_ADIO_2,
> +	LTC4283_CHAN_ADIO_3,
> +	LTC4283_CHAN_ADIO_4,
> +	LTC4283_CHAN_DRNS,
> +	LTC4283_CHAN_DRAIN,
> +	/* differential channels */
> +	LTC4283_CHAN_ADIN12,
> +	LTC4283_CHAN_ADIN34,
> +	LTC4283_CHAN_ADIO12,
> +	LTC4283_CHAN_ADIO34,
> +	LTC4283_CHAN_MAX
> +};
> +
> +/* Just for ease of use on the regmap  */
> +#define LTC4283_ADIO34_MAX \
> +	LTC4283_ADC_2_MAX_DIFF(LTC4283_CHAN_ADIO34 - LTC4283_CHAN_ADIN12)
> +
> +struct ltc4283_hwmon {
> +	struct regmap *map;
> +	struct i2c_client *client;
> +	unsigned long gpio_mask;
> +	unsigned long ch_enable_mask;
> +	/* in microwatt */
> +	long power_max;
> +	/* in millivolt */
> +	u32 vsense_max;
> +	/* in tenths of microohm*/
> +	u32 rsense;
> +	bool energy_en;
> +	bool ext_fault;
> +};
> +
> +static int ltc4283_read_voltage_word(const struct ltc4283_hwmon *st,
> +				     u32 reg, u32 fs, long *val)
> +{
> +	unsigned int __raw;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &__raw);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST(__raw * fs, BIT(16));
> +	return 0;
> +}
> +
> +static int ltc4283_read_voltage_byte(const struct ltc4283_hwmon *st,
> +				     u32 reg, u32 fs, long *val)
> +{
> +	int ret;
> +	u32 in;
> +
> +	ret = regmap_read(st->map, reg, &in);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST(in * fs, BIT(8));
> +	return 0;
> +}
> +
> +static u32 ltc4283_in_reg(u32 attr, u32 channel)
> +{
> +	switch (attr) {
> +	case hwmon_in_input:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	case hwmon_in_highest:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR_MAX;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2_MAX(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_MAX_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	case hwmon_in_lowest:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR_MIN;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	case hwmon_in_max:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR_MAX_TH;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	default:
> +		if (channel == LTC4283_CHAN_VPWR)
> +			return LTC4283_VPWR_MIN_TH;
> +		if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN)
> +			return LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
> +		return LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	}
> +}
> +
> +static int ltc4283_read_in_vals(const struct ltc4283_hwmon *st,
> +				u32 attr, u32 channel, long *val)
> +{
> +	u32 reg = ltc4283_in_reg(attr, channel);
> +	int ret;
> +
> +	if (channel < LTC4283_CHAN_ADIN12) {
> +		if (attr != hwmon_in_max && attr != hwmon_in_min)
> +			return ltc4283_read_voltage_word(st, reg,
> +							 LTC4283_ADC2_FS_mV,
> +							 val);
> +
> +		return ltc4283_read_voltage_byte(st, reg,
> +						 LTC4283_ADC2_FS_mV, val);
> +	}
> +
> +	if (attr != hwmon_in_max && attr != hwmon_in_min)
> +		ret = ltc4283_read_voltage_word(st, reg,
> +						LTC4283_ADC1_FS_uV, val);
> +	else
> +		ret = ltc4283_read_voltage_byte(st, reg,
> +						LTC4283_ADC1_FS_uV, val);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST(*val, MILLI);
> +	return 0;
> +}
> +
> +static int ltc4283_read_alarm(struct ltc4283_hwmon *st, u32 reg,
> +			      u32 mask, long *val)
> +{
> +	u32 alarm;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &alarm);
> +	if (ret)
> +		return ret;
> +
> +	*val = !!(alarm & mask);
> +
> +	/* If not status/fault logs, clear the alarm after reading it. */
> +	if (reg != LTC4283_FAULT_STATUS && reg != LTC4283_FAULT_LOG)
> +		return regmap_clear_bits(st->map, reg, mask);
> +
> +	return 0;
> +}
> +
> +static int ltc4283_read_in_alarm(struct ltc4283_hwmon *st, u32 channel,
> +				 bool max_alm, long *val)
> +{
> +	if (channel == LTC4283_VPWR)
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  BIT(2 + max_alm), val);
> +
> +	if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_ADI_4) {
> +		u32 bit = (channel - LTC4283_CHAN_ADI_1) * 2;
> +		/*
> +		 * Lower channels go to higher bits. We also want to go +1 down
> +		 * in the min_alarm case.
> +		 */
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_2,
> +					  BIT(7 - bit - !max_alm), val);
> +	}
> +
> +	if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
> +		u32 bit = (channel - LTC4283_CHAN_ADIO_1) * 2;
> +
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_3,
> +					  BIT(7 - bit - !max_alm), val);
> +	}
> +
> +	if (channel >= LTC4283_CHAN_ADIN12 && channel <= LTC4283_CHAN_ADIO34) {
> +		u32 bit = (channel - LTC4283_CHAN_ADIN12) * 2;
> +
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_5,
> +					  BIT(7 - bit - !max_alm), val);
> +	}
> +
> +	if (channel == LTC4283_CHAN_DRNS)
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4,
> +					  BIT(6 + max_alm), val);
> +
> +	return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_4, BIT(4 + max_alm),
> +				  val);
> +}
> +
> +static int ltc4283_read_in(struct ltc4283_hwmon *st, u32 attr, u32 channel,
> +			   long *val)
> +{
> +	switch (attr) {
> +	case hwmon_in_input:
> +		if (!test_bit(channel, &st->ch_enable_mask))
> +			return -ENODATA;
> +
> +		return ltc4283_read_in_vals(st, attr, channel, val);
> +	case hwmon_in_highest:
> +	case hwmon_in_lowest:
> +	case hwmon_in_max:
> +	case hwmon_in_min:
> +		return ltc4283_read_in_vals(st, attr, channel, val);
> +	case hwmon_in_max_alarm:
> +		return ltc4283_read_in_alarm(st, channel, true, val);
> +	case hwmon_in_min_alarm:
> +		return ltc4283_read_in_alarm(st, channel, false, val);
> +	case hwmon_in_crit_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> +					  LTC4283_OV_MASK, val);
> +	case hwmon_in_lcrit_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> +					  LTC4283_UV_MASK, val);
> +	case hwmon_in_fault:
> +		/*
> +		 * We report failure if we detect either a fer_bad or a
> +		 * fet_short in the status register.
> +		 */
> +		return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> +					  LTC4283_FET_BAD_MASK | LTC4283_FET_SHORT_MASK, val);
> +	case hwmon_in_enable:
> +		*val = test_bit(channel, &st->ch_enable_mask);
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +	return 0;
> +}
> +
> +static int ltc4283_read_current_word(const struct ltc4283_hwmon *st, u32 reg,
> +				     long *val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
> +	unsigned int __raw;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &__raw);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV64_U64_ROUND_CLOSEST(__raw * temp,
> +				       BIT_ULL(16) * st->rsense);
> +
> +	return 0;
> +}
> +
> +static int ltc4283_read_current_byte(const struct ltc4283_hwmon *st, u32 reg,
> +				     long *val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * DECA * MILLI;
> +	u32 curr;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &curr);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST_ULL(curr * temp, BIT(8) * st->rsense);
> +	return 0;
> +}
> +
> +static int ltc4283_read_curr(struct ltc4283_hwmon *st, u32 attr, long *val)
> +{
> +	switch (attr) {
> +	case hwmon_curr_input:
> +		return ltc4283_read_current_word(st, LTC4283_SENSE, val);
> +	case hwmon_curr_highest:
> +		return ltc4283_read_current_word(st, LTC4283_SENSE_MAX, val);
> +	case hwmon_curr_lowest:
> +		return ltc4283_read_current_word(st, LTC4283_SENSE_MIN, val);
> +	case hwmon_curr_max:
> +		return ltc4283_read_current_byte(st, LTC4283_SENSE_MAX_TH, val);
> +	case hwmon_curr_min:
> +		return ltc4283_read_current_byte(st, LTC4283_SENSE_MIN_TH, val);
> +	case hwmon_curr_max_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  LTC4283_SENSE_HIGH_ALM, val);
> +	case hwmon_curr_min_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  LTC4283_SENSE_LOW_ALM, val);
> +	case hwmon_curr_crit_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_FAULT_STATUS,
> +					  LTC4283_OC_MASK, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_read_power_word(const struct ltc4283_hwmon *st,
> +				   u32 reg, long *val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> +	unsigned int __raw;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &__raw);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Power is given by:
> +	 *     P = CODE(16b) * 32.768mV * 2.048V / (2^16 * Rsense)
> +	 */
> +	*val = DIV64_U64_ROUND_CLOSEST(temp * __raw, BIT_ULL(16) * st->rsense);
> +
> +	return 0;
> +}
> +
> +static int ltc4283_read_power_byte(const struct ltc4283_hwmon *st,
> +				   u32 reg, long *val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> +	u32 power;
> +	int ret;
> +
> +	ret = regmap_read(st->map, reg, &power);
> +	if (ret)
> +		return ret;
> +
> +	*val = DIV_ROUND_CLOSEST_ULL(power * temp, BIT(8) * st->rsense);
> +
> +	return 0;
> +}
> +
> +static int ltc4283_read_power(struct ltc4283_hwmon *st, u32 attr, long *val)
> +{
> +	switch (attr) {
> +	case hwmon_power_input:
> +		return ltc4283_read_power_word(st, LTC4283_POWER, val);
> +	case hwmon_power_input_highest:
> +		return ltc4283_read_power_word(st, LTC4283_POWER_MAX, val);
> +	case hwmon_power_input_lowest:
> +		return ltc4283_read_power_word(st, LTC4283_POWER_MIN, val);
> +	case hwmon_power_max_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  LTC4283_POWER_HIGH_ALM, val);
> +	case hwmon_power_min_alarm:
> +		return ltc4283_read_alarm(st, LTC4283_ADC_ALM_LOG_1,
> +					  LTC4283_POWER_LOW_ALM, val);
> +	case hwmon_power_max:
> +		return ltc4283_read_power_byte(st, LTC4283_POWER_MAX_TH, val);
> +	case hwmon_power_min:
> +		return ltc4283_read_power_byte(st, LTC4283_POWER_MIN_TH, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_read_energy(struct ltc4283_hwmon *st, u32 attr, s64 *val)
> +{
> +	u64 temp = LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV, energy, temp_2;
> +	u8 raw[8] = {};
> +	int ret;
> +
> +	if (!st->energy_en)
> +		return -ENODATA;
> +
> +	ret = i2c_smbus_read_i2c_block_data(st->client, LTC4283_ENERGY, 6, raw);
> +	if (ret < 0)
> +		return ret;
> +	if (ret != 6)
> +		return -EIO;
> +
> +	energy = get_unaligned_be64(raw) >> 16;
> +
> +	/*
> +	 * The formula for energy is given by:
> +	 *	E = CODE(48b) * 32.768mV * 2.048V * Tconv / 2^24 * Rsense
> +	 *
> +	 * As Rsense can have tenths of micro-ohm resolution, we need to
> +	 * multiply by DECA to get microjoule.
> +	 */
> +	if (check_mul_overflow(temp * LTC4283_TCONV_uS, energy, &temp_2)) {
> +		/*
> +		 * We multiply again by 1000 to make sure that we don't get 0
> +		 * in the following division which could happen for big rsense
> +		 * values. OTOH, we then divide energy first by 1000 so that
> +		 * we do not overflow u64 again for very small rsense values.
> +		 * We add 100 factor for proper conversion to microjoule.
> +		 */
> +		temp_2 = DIV64_U64_ROUND_CLOSEST(temp * LTC4283_TCONV_uS * MILLI,
> +						 BIT_ULL(24) * st->rsense);
> +		energy = DIV_ROUND_CLOSEST_ULL(energy, MILLI * CENTI) * temp_2;
> +	} else {
> +		/* Put rsense back into nanoohm so we get microjoule. */
> +		energy = DIV64_U64_ROUND_CLOSEST(temp_2, BIT_ULL(24) * st->rsense * CENTI);
> +	}
> +
> +	*val = energy;
> +	return 0;
> +}
> +
> +static int ltc4283_read(struct device *dev, enum hwmon_sensor_types type,
> +			u32 attr, int channel, long *val)
> +{
> +	struct ltc4283_hwmon *st = dev_get_drvdata(dev);
> +
> +	switch (type) {
> +	case hwmon_in:
> +		return ltc4283_read_in(st, attr, channel, val);
> +	case hwmon_curr:
> +		return ltc4283_read_curr(st, attr, val);
> +	case hwmon_power:
> +		return ltc4283_read_power(st, attr, val);
> +	case hwmon_energy:
> +		*val = st->energy_en;
> +		return 0;
> +	case hwmon_energy64:
> +		return ltc4283_read_energy(st, attr, (s64 *)val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_write_power_byte(const struct ltc4283_hwmon *st, u32 reg,
> +				    long val)
> +{
> +	u64 temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> +	u32 __raw;
> +
> +	val = clamp_val(val, 0, st->power_max);
> +	__raw = DIV64_U64_ROUND_CLOSEST(val * BIT_ULL(8) * st->rsense, temp);
> +
> +	return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_write_power_word(const struct ltc4283_hwmon *st,
> +				    u32 reg, long val)
> +{
> +	u64 temp = st->rsense * BIT_ULL(16), temp_2;
> +	u16 __raw;
> +
> +	if (check_mul_overflow(val, temp, &temp_2)) {
> +		temp = DIV_ROUND_CLOSEST_ULL(temp, DECA * MILLI);
> +		__raw = DIV_ROUND_CLOSEST_ULL(temp * val, LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV);
> +	} else {
> +		temp = (u64)LTC4283_ADC1_FS_uV * LTC4283_ADC2_FS_mV * DECA * MILLI;
> +		__raw = DIV64_U64_ROUND_CLOSEST(temp_2, temp);
> +	}
> +
> +	return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_reset_power_hist(struct ltc4283_hwmon *st)
> +{
> +	int ret;
> +
> +	ret = ltc4283_write_power_word(st, LTC4283_POWER_MIN, st->power_max);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_write_power_word(st, LTC4283_POWER_MAX, 0);
> +	if (ret)
> +		return ret;
> +
> +	/* Clear possible power faults. */
> +	return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> +				 LTC4283_PWR_FAIL_FAULT_MASK | LTC4283_PGI_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_power(struct ltc4283_hwmon *st, u32 attr, long val)
> +{
> +	switch (attr) {
> +	case hwmon_power_max:
> +		return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, val);
> +	case hwmon_power_min:
> +		return ltc4283_write_power_byte(st, LTC4283_POWER_MIN_TH, val);
> +	case hwmon_power_reset_history:
> +		return ltc4283_reset_power_hist(st);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_write_in_history(struct ltc4283_hwmon *st, u32 reg,
> +				    long lowest, u32 fs)
> +{
> +	u32 __raw;
> +	int ret;
> +
> +	__raw = DIV_ROUND_CLOSEST(BIT(16) * lowest, fs);
> +	if (__raw == BIT(16))
> +		__raw = U16_MAX;
> +
> +	ret = regmap_write(st->map, reg, __raw);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_write(st->map, reg + 1, 0);
> +}
> +
> +static int ltc4283_write_in_byte(const struct ltc4283_hwmon *st,
> +				 u32 reg, u32 fs, long val)
> +{
> +	u32 __raw;
> +
> +	val = clamp_val(val, 0, fs);
> +	__raw = DIV_ROUND_CLOSEST(val * BIT(8), fs);
> +	if (__raw == BIT(8))
> +		__raw = U8_MAX;
> +
> +	return regmap_write(st->map, reg, __raw);
> +}
> +
> +static int ltc4283_reset_in_hist(struct ltc4283_hwmon *st, u32 channel)
> +{
> +	u32 reg, fs;
> +	int ret;
> +
> +	/*
> +	 * Make sure to clear possible under/over voltage faults. Otherwise the
> +	 * chip won't latch on again.
> +	 */
> +	if (channel == LTC4283_CHAN_VIN)
> +		return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> +					 LTC4283_OV_FAULT_MASK | LTC4283_UV_FAULT_MASK);
> +
> +	if (channel == LTC4283_CHAN_VPWR)
> +		return ltc4283_write_in_history(st, LTC4283_VPWR_MIN,
> +						LTC4283_ADC2_FS_mV,
> +						LTC4283_ADC2_FS_mV);
> +
> +	if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
> +		fs = LTC4283_ADC2_FS_mV;
> +		reg = LTC4283_ADC_2_MIN(channel - LTC4283_CHAN_ADI_1);
> +	} else {
> +		fs = LTC4283_ADC1_FS_uV;
> +		reg = LTC4283_ADC_2_MIN_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	}
> +
> +	ret = ltc4283_write_in_history(st, reg, fs, fs);
> +	if (ret)
> +		return ret;
> +	if (channel != LTC4283_CHAN_DRAIN)
> +		return 0;
> +
> +	/* Then, let's also clear possible fet faults. Same as above. */
> +	return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> +				 LTC4283_FET_BAD_FAULT_MASK | LTC4283_FET_SHORT_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_in_en(struct ltc4283_hwmon *st, u32 channel, bool en)
> +{
> +	unsigned int bit, adc_idx = channel - LTC4283_CHAN_ADI_1;
> +	unsigned int reg = LTC4283_ADC_SELECT(adc_idx);
> +	int ret;
> +
> +	bit = LTC4283_ADC_SELECT_MASK(adc_idx);
> +	if (channel > LTC4283_CHAN_DRAIN)
> +		/* Account for two reserved fields after DRAIN. */
> +		bit <<= 2;
> +
> +	if (en)
> +		ret = regmap_set_bits(st->map, reg, bit);
> +	else
> +		ret = regmap_clear_bits(st->map, reg, bit);
> +	if (ret)
> +		return ret;
> +
> +	__assign_bit(channel, &st->ch_enable_mask, en);
> +	return 0;
> +}
> +
> +static int ltc4283_write_minmax(struct ltc4283_hwmon *st, long val,
> +				u32 channel, bool is_max)
> +{
> +	u32 reg;
> +
> +	if (channel == LTC4283_CHAN_VPWR) {
> +		if (is_max)
> +			return ltc4283_write_in_byte(st, LTC4283_VPWR_MAX_TH,
> +						     LTC4283_ADC2_FS_mV, val);
> +
> +		return ltc4283_write_in_byte(st, LTC4283_VPWR_MIN_TH,
> +					     LTC4283_ADC2_FS_mV, val);
> +	}
> +
> +	if (channel >= LTC4283_CHAN_ADI_1 && channel <= LTC4283_CHAN_DRAIN) {
> +		if (is_max) {
> +			reg = LTC4283_ADC_2_MAX_TH(channel - LTC4283_CHAN_ADI_1);
> +			return ltc4283_write_in_byte(st, reg,
> +						     LTC4283_ADC2_FS_mV, val);
> +		}
> +
> +		reg = LTC4283_ADC_2_MIN_TH(channel - LTC4283_CHAN_ADI_1);
> +		return ltc4283_write_in_byte(st, reg, LTC4283_ADC2_FS_mV, val);
> +	}
> +
> +	/* Just sanity check we do not overflow val for 32bit */
> +	val = clamp_val(val * MILLI, 0, LTC4283_ADC1_FS_uV);
> +
> +	if (is_max) {
> +		reg = LTC4283_ADC_2_MAX_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> +		return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV, val);
> +	}
> +
> +	reg = LTC4283_ADC_2_MIN_TH_DIFF(channel - LTC4283_CHAN_ADIN12);
> +	return ltc4283_write_in_byte(st, reg, LTC4283_ADC1_FS_uV, val);
> +}
> +
> +static int ltc4283_write_in(struct ltc4283_hwmon *st, u32 attr, long val,
> +			    int channel)
> +{
> +	switch (attr) {
> +	case hwmon_in_max:
> +		return ltc4283_write_minmax(st, val, channel, true);
> +	case hwmon_in_min:
> +		return ltc4283_write_minmax(st, val, channel, false);
> +	case hwmon_in_reset_history:
> +		return ltc4283_reset_in_hist(st, channel);
> +	case hwmon_in_enable:
> +		return ltc4283_write_in_en(st, channel, !!val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_write_curr_byte(const struct ltc4283_hwmon *st,
> +				   u32 reg, long val)
> +{
> +	u32 temp = LTC4283_ADC1_FS_uV * DECA * MILLI;
> +	u32 reg_val, isense_max;
> +
> +	isense_max = DIV_ROUND_CLOSEST(st->vsense_max * MICRO * DECA, st->rsense);
> +	val = clamp_val(val, 0, isense_max);
> +	reg_val = DIV_ROUND_CLOSEST_ULL(val * BIT_ULL(8) * st->rsense, temp);
> +
> +	return regmap_write(st->map, reg, reg_val);
> +}
> +
> +static int ltc4283_write_curr_history(struct ltc4283_hwmon *st)
> +{
> +	int ret;
> +
> +	ret = ltc4283_write_in_history(st, LTC4283_SENSE_MIN,
> +				       st->vsense_max * MILLI,
> +				       LTC4283_ADC1_FS_uV);
> +	if (ret)
> +		return ret;
> +
> +	/* Now, let's also clear possible overcurrent logs. */
> +	return regmap_clear_bits(st->map, LTC4283_FAULT_LOG,
> +				 LTC4283_OC_FAULT_MASK);
> +}
> +
> +static int ltc4283_write_curr(struct ltc4283_hwmon *st, u32 attr, long val)
> +{
> +	switch (attr) {
> +	case hwmon_curr_max:
> +		return ltc4283_write_curr_byte(st, LTC4283_SENSE_MAX_TH, val);
> +	case hwmon_curr_min:
> +		return ltc4283_write_curr_byte(st, LTC4283_SENSE_MIN_TH, val);
> +	case hwmon_curr_reset_history:
> +		return ltc4283_write_curr_history(st);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static int ltc4283_energy_enable_set(struct ltc4283_hwmon *st, long val)
> +{
> +	int ret;
> +
> +	/* Setting the bit halts the meter. */
> +	val = !!val;
> +	ret = regmap_update_bits(st->map, LTC4283_METER_CONTROL,
> +				 LTC4283_METER_HALT_MASK,
> +				 FIELD_PREP(LTC4283_METER_HALT_MASK, !val));
> +	if (ret)
> +		return ret;
> +
> +	st->energy_en = val;
> +
> +	return 0;
> +}
> +
> +static int ltc4283_write(struct device *dev, enum hwmon_sensor_types type,
> +			 u32 attr, int channel, long val)
> +{
> +	struct ltc4283_hwmon *st = dev_get_drvdata(dev);
> +
> +	switch (type) {
> +	case hwmon_power:
> +		return ltc4283_write_power(st, attr, val);
> +	case hwmon_in:
> +		return ltc4283_write_in(st, attr, val, channel);
> +	case hwmon_curr:
> +		return ltc4283_write_curr(st, attr, val);
> +	case hwmon_energy:
> +		return ltc4283_energy_enable_set(st, val);
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +static umode_t ltc4283_in_is_visible(const struct ltc4283_hwmon *st,
> +				     u32 attr, int channel)
> +{
> +	/* If ADIO is set as a GPIO, don´t make it visible. */
> +	if (channel >= LTC4283_CHAN_ADIO_1 && channel <= LTC4283_CHAN_ADIO_4) {
> +		/* ADIOX pins come at index 0 in the gpio mask. */
> +		channel -= LTC4283_CHAN_ADIO_1;
> +		if (test_bit(channel, &st->gpio_mask))
> +			return 0;
> +	}
> +
> +	/* Also take care of differential channels. */
> +	if (channel >= LTC4283_CHAN_ADIO12 && channel <= LTC4283_CHAN_ADIO34) {
> +		channel -= LTC4283_CHAN_ADIO12;
> +		/* If one channel in the pair is used, make it invisible. */
> +		if (test_bit(channel * 2, &st->gpio_mask) ||
> +		    test_bit(channel * 2 + 1, &st->gpio_mask))
> +			return 0;
> +	}
> +
> +	switch (attr) {
> +	case hwmon_in_input:
> +	case hwmon_in_highest:
> +	case hwmon_in_lowest:
> +	case hwmon_in_max_alarm:
> +	case hwmon_in_min_alarm:
> +	case hwmon_in_label:
> +	case hwmon_in_lcrit_alarm:
> +	case hwmon_in_crit_alarm:
> +	case hwmon_in_fault:
> +		return 0444;
> +	case hwmon_in_max:
> +	case hwmon_in_min:
> +	case hwmon_in_enable:
> +		return 0644;
> +	case hwmon_in_reset_history:
> +		return 0200;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static umode_t ltc4283_curr_is_visible(u32 attr)
> +{
> +	switch (attr) {
> +	case hwmon_curr_input:
> +	case hwmon_curr_highest:
> +	case hwmon_curr_lowest:
> +	case hwmon_curr_max_alarm:
> +	case hwmon_curr_min_alarm:
> +	case hwmon_curr_crit_alarm:
> +	case hwmon_curr_label:
> +		return 0444;
> +	case hwmon_curr_max:
> +	case hwmon_curr_min:
> +		return 0644;
> +	case hwmon_curr_reset_history:
> +		return 0200;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static umode_t ltc4283_power_is_visible(u32 attr)
> +{
> +	switch (attr) {
> +	case hwmon_power_input:
> +	case hwmon_power_input_highest:
> +	case hwmon_power_input_lowest:
> +	case hwmon_power_label:
> +	case hwmon_power_max_alarm:
> +	case hwmon_power_min_alarm:
> +		return 0444;
> +	case hwmon_power_max:
> +	case hwmon_power_min:
> +		return 0644;
> +	case hwmon_power_reset_history:
> +		return 0200;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static umode_t ltc4283_is_visible(const void *data,
> +				  enum hwmon_sensor_types type,
> +				  u32 attr, int channel)
> +{
> +	switch (type) {
> +	case hwmon_in:
> +		return ltc4283_in_is_visible(data, attr, channel);
> +	case hwmon_curr:
> +		return ltc4283_curr_is_visible(attr);
> +	case hwmon_power:
> +		return ltc4283_power_is_visible(attr);
> +	case hwmon_energy:
> +		/* hwmon_energy_enable */
> +		return 0644;
> +	case hwmon_energy64:
> +		/* hwmon_energy_input */
> +		return 0444;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static const char * const ltc4283_in_strs[] = {
> +	"VIN", "VPWR", "VADI1", "VADI2", "VADI3", "VADI4", "VADIO1", "VADIO2",
> +	"VADIO3", "VADIO4", "DRNS", "DRAIN", "ADIN2-ADIN1", "ADIN4-ADIN3",
> +	"ADIO2-ADIO1", "ADIO4-ADIO3"
> +};
> +
> +static int ltc4283_read_labels(struct device *dev,
> +			       enum hwmon_sensor_types type,
> +			       u32 attr, int channel, const char **str)
> +{
> +	switch (type) {
> +	case hwmon_in:
> +		*str = ltc4283_in_strs[channel];
> +		return 0;
> +	case hwmon_curr:
> +		*str = "ISENSE";
> +		return 0;
> +	case hwmon_power:
> +		*str = "Power";
> +		return 0;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +}
> +
> +/*
> + * Set max limits for ISENSE and Power as that depends on the max voltage on
> + * rsense that is defined in ILIM_ADJUST. This is specially important for power
> + * because for some rsense and vfsout values, if we allow the default raw 255
> + * value, that would overflow long in 32bit archs when reading back the max
> + * power limit.
> + */
> +static int ltc4283_set_max_limits(struct ltc4283_hwmon *st, struct device *dev)
> +{
> +	u32 temp = st->vsense_max * DECA * MICRO;
> +	int ret;
> +
> +	ret = ltc4283_write_in_byte(st, LTC4283_SENSE_MAX_TH, LTC4283_ADC1_FS_uV,
> +				    st->vsense_max * MILLI);
> +	if (ret)
> +		return ret;
> +
> +	/* Power is given by ISENSE * Vout. */
> +	st->power_max = DIV_ROUND_CLOSEST(temp, st->rsense) * LTC4283_ADC2_FS_mV;
> +	return ltc4283_write_power_byte(st, LTC4283_POWER_MAX_TH, st->power_max);
> +}
> +
> +static int ltc4283_parse_array_prop(const struct ltc4283_hwmon *st,
> +				    struct device *dev, const char *prop,
> +				    const u32 *vals, u32 n_vals)
> +{
> +	u32 prop_val;
> +	int ret;
> +	u32 i;
> +
> +	ret = device_property_read_u32(dev, prop, &prop_val);
> +	if (ret)
> +		return n_vals;
> +
> +	for (i = 0; i < n_vals; i++) {
> +		if (prop_val != vals[i])
> +			continue;
> +
> +		return i;
> +	}
> +
> +	return dev_err_probe(dev, -EINVAL,
> +			     "Invalid %s property value %u, expected one of: %*ph\n",
> +			     prop, prop_val, n_vals, vals);
> +}
> +
> +static int ltc4283_get_defaults(struct ltc4283_hwmon *st)
> +{
> +	u32 reg_val, ilm_adjust, c;
> +	int ret;
> +
> +	ret = regmap_read(st->map, LTC4283_METER_CONTROL, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	st->energy_en = !FIELD_GET(LTC4283_METER_HALT_MASK, reg_val);
> +
> +	ret = regmap_read(st->map, LTC4283_CONFIG_1, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	ilm_adjust = FIELD_GET(LTC4283_ILIM_MASK, reg_val);
> +	st->vsense_max = LTC4283_VILIM_MIN_uV / MILLI + ilm_adjust;
> +
> +	ret = regmap_read(st->map, LTC4283_PGIO_CONFIG, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	/* Can be latter overwritten in ltc4283_pgio_config() */
> +	if (FIELD_GET(LTC4283_PGIO4_CFG_MASK, reg_val) < LTC4283_PGIO_FUNC_GPIO)
> +		st->ext_fault = true;
> +
> +	/* VPWR and VIN are always enabled */
> +	__set_bit(LTC4283_CHAN_VIN, &st->ch_enable_mask);
> +	__set_bit(LTC4283_CHAN_VPWR, &st->ch_enable_mask);
> +	for (c = LTC4283_CHAN_ADI_1; c < LTC4283_CHAN_MAX; c++) {
> +		u32 chan = c - LTC4283_CHAN_ADI_1, bit;
> +
> +		ret = regmap_read(st->map, LTC4283_ADC_SELECT(chan), &reg_val);
> +		if (ret)
> +			return ret;
> +
> +		bit = LTC4283_ADC_SELECT_MASK(chan);
> +		if (c > LTC4283_CHAN_DRAIN)
> +			/* account for two reserved fields after DRAIN */
> +			bit <<= 2;
> +
> +		if (!(bit & reg_val))
> +			continue;
> +
> +		__set_bit(c, &st->ch_enable_mask);
> +	}
> +
> +	return 0;
> +}
> +
> +static const char * const ltc4283_pgio1_funcs[] = {
> +	"inverted_power_good", "power_good", "gpio"
> +};
> +
> +static const char * const ltc4283_pgio2_funcs[] = {
> +	 "inverted_power_good", "power_good", "gpio", "active_current_limiting"
> +};
> +
> +static const char * const ltc4283_pgio3_funcs[] = {
> +	"inverted_power_good_input", "power_good_input", "gpio"
> +};
> +
> +static const char * const ltc4283_pgio4_funcs[] = {
> +	"inverted_external_fault", "external_fault", "gpio"
> +};
> +
> +enum {
> +	LTC4283_PIN_ADIO1,
> +	LTC4283_PIN_ADIO2,
> +	LTC4283_PIN_ADIO3,
> +	LTC4283_PIN_ADIO4,
> +	LTC4283_PIN_PGIO1,
> +	LTC4283_PIN_PGIO2,
> +	LTC4283_PIN_PGIO3,
> +	LTC4283_PIN_PGIO4,
> +};
> +
> +static int ltc4283_pgio_config(struct ltc4283_hwmon *st, struct device *dev)
> +{
> +	int ret, func;
> +
> +	func = device_property_match_property_string(dev, "adi,pgio1-func",
> +						     ltc4283_pgio1_funcs,
> +						     ARRAY_SIZE(ltc4283_pgio1_funcs));
> +	if (func < 0 && func != -EINVAL)
> +		return dev_err_probe(dev, func,
> +				     "Invalid adi,pgio1-func property\n");
> +	if (func >= 0) {
> +		if (func == LTC4283_PGIO_FUNC_GPIO) {
> +			__set_bit(LTC4283_PIN_PGIO1, &st->gpio_mask);
> +			/* If GPIO, default to an input pin. */
> +			func++;
> +		}
> +
> +		ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> +					 LTC4283_PGIO1_CFG_MASK,
> +					 FIELD_PREP(LTC4283_PGIO1_CFG_MASK, func));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	func = device_property_match_property_string(dev, "adi,pgio2-func",
> +						     ltc4283_pgio2_funcs,
> +						     ARRAY_SIZE(ltc4283_pgio2_funcs));
> +
> +	if (func < 0 && func != -EINVAL)
> +		return dev_err_probe(dev, func,
> +				     "Invalid adi,pgio2-func property\n");
> +	if (func >= 0) {
> +		if (func != LTC4283_PGIO2_FUNC_ACLB) {
> +			if (func == LTC4283_PGIO_FUNC_GPIO)  {
> +				__set_bit(LTC4283_PIN_PGIO2, &st->gpio_mask);
> +				func++;
> +			}
> +
> +			ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> +						 LTC4283_PGIO2_CFG_MASK,
> +						 FIELD_PREP(LTC4283_PGIO2_CFG_MASK, func));
> +		} else {
> +			ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
> +					      LTC4283_PIGIO2_ACLB_MASK);
> +		}
> +
> +		if (ret)
> +			return ret;
> +	}
> +
> +	func = device_property_match_property_string(dev, "adi,pgio3-func",
> +						     ltc4283_pgio3_funcs,
> +						     ARRAY_SIZE(ltc4283_pgio3_funcs));
> +
> +	if (func < 0 && func != -EINVAL)
> +		return dev_err_probe(dev, func,
> +				     "Invalid adi,pgio3-func property\n");
> +	if (func >= 0) {
> +		if (func == LTC4283_PGIO_FUNC_GPIO) {
> +			__set_bit(LTC4283_PIN_PGIO3, &st->gpio_mask);
> +			func++;
> +		}
> +
> +		ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> +					 LTC4283_PGIO3_CFG_MASK,
> +					 FIELD_PREP(LTC4283_PGIO3_CFG_MASK, func));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	func = device_property_match_property_string(dev, "adi,pgio4-func",
> +						     ltc4283_pgio4_funcs,
> +						     ARRAY_SIZE(ltc4283_pgio4_funcs));
> +
> +	if (func < 0 && func != -EINVAL)
> +		return dev_err_probe(dev, func,
> +				     "Invalid adi,pgio4-func property\n");
> +	if (func >= 0) {
> +		if (func == LTC4283_PGIO_FUNC_GPIO) {
> +			__set_bit(LTC4283_PIN_PGIO4, &st->gpio_mask);
> +			func++;
> +			st->ext_fault = false;
> +		} else {
> +			st->ext_fault = true;
> +		}
> +
> +		ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG,
> +					 LTC4283_PGIO4_CFG_MASK,
> +					 FIELD_PREP(LTC4283_PGIO4_CFG_MASK, func));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ltc4283_adio_config(struct ltc4283_hwmon *st, struct device *dev,
> +			       const char *prop, u32 pin)
> +{
> +	u32 adc_idx;
> +	int ret;
> +
> +	if (!device_property_read_bool(dev, prop))
> +		return 0;
> +
> +	adc_idx = LTC4283_CHAN_ADIO_1 - LTC4283_CHAN_ADI_1 + pin;
> +	ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(adc_idx),
> +				LTC4283_ADC_SELECT_MASK(adc_idx));
> +	if (ret)
> +		return ret;
> +
> +	__set_bit(pin, &st->gpio_mask);
> +	return 0;
> +}
> +
> +static int ltc4283_pin_config(struct ltc4283_hwmon *st, struct device *dev)
> +{
> +	int ret;
> +
> +	ret = ltc4283_pgio_config(st, dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio1", LTC4283_PIN_ADIO1);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio2", LTC4283_PIN_ADIO2);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_adio_config(st, dev, "adi,gpio-on-adio3", LTC4283_PIN_ADIO3);
> +	if (ret)
> +		return ret;
> +
> +	return ltc4283_adio_config(st, dev, "adi,gpio-on-adio4", LTC4283_PIN_ADIO4);
> +}
> +
> +static const char * const ltc4283_oc_fet_retry[] = {
> +	"latch-off", "1", "7", "unlimited"
> +};
> +
> +static const u32 ltc4283_fb_factor[] = {
> +	100, 50, 20, 10
> +};
> +
> +static const u32 ltc4283_cooling_dl[] = {
> +	512, 1002, 2005, 4100, 8190, 16400, 32800, 65600
> +};
> +
> +static const u32 ltc4283_fet_bad_delay[] = {
> +	256, 512, 1002, 2005
> +};
> +
> +static int ltc4283_setup(struct ltc4283_hwmon *st, struct device *dev)
> +{
> +	u32 val;
> +	int ret;
> +
> +	/* The part has an eeprom so let's get the needed defaults from it */
> +	ret = ltc4283_get_defaults(st);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Default to LTC4283_MIN_RSENSE so we can probe without FW properties.
> +	 */
> +	st->rsense = LTC4283_MIN_RSENSE;
> +	ret = device_property_read_u32(dev, "adi,rsense-nano-ohms",
> +				       &st->rsense);
> +	if (!ret) {
> +		if (st->rsense < LTC4283_MIN_RSENSE || st->rsense > LTC4283_MAX_RSENSE)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "adi,rsense-nano-ohms(%u) too small or too large [%u %u]\n",
> +					     st->rsense, LTC4283_MIN_RSENSE, LTC4283_MAX_RSENSE);
> +	}
> +
> +	/*
> +	 * The resolution for rsense is tenths of micro (eg: 62.5 uOhm) which
> +	 * means we need nano in the bindings. However, to make things easier to
> +	 * handle (with respect to overflows) we divide it by 100 as we don't
> +	 * really need the last two digits.
> +	 */
> +	st->rsense /= CENTI;
> +
> +	ret = device_property_read_u32(dev, "adi,current-limit-sense-microvolt",
> +				       &st->vsense_max);
> +	if (!ret) {
> +		u32 reg_val;
> +
> +		if (!in_range(st->vsense_max, LTC4283_VILIM_MIN_uV,
> +			      LTC4283_VILIM_RANGE)) {
> +			return dev_err_probe(dev, -EINVAL,
> +					     "adi,current-limit-sense-microvolt (%u) out of range [%u %u]\n",
> +					     st->vsense_max, LTC4283_VILIM_MIN_uV,
> +					     LTC4283_VILIM_MAX_uV);
> +		}
> +
> +		st->vsense_max /= MILLI;
> +		reg_val = FIELD_PREP(LTC4283_ILIM_MASK,
> +				     st->vsense_max - LTC4283_VILIM_MIN_uV / MILLI);
> +		ret = regmap_update_bits(st->map, LTC4283_CONFIG_1,
> +					 LTC4283_ILIM_MASK, reg_val);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ltc4283_parse_array_prop(st, dev, "adi,current-limit-foldback-factor",
> +				       ltc4283_fb_factor, ARRAY_SIZE(ltc4283_fb_factor));
> +	if (ret < 0)
> +		return ret;
> +	if (ret < ARRAY_SIZE(ltc4283_fb_factor)) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONFIG_1, LTC4283_FB_MASK,
> +					 FIELD_PREP(LTC4283_FB_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ltc4283_parse_array_prop(st, dev, "adi,cooling-delay-ms",
> +				       ltc4283_cooling_dl, ARRAY_SIZE(ltc4283_cooling_dl));
> +	if (ret < 0)
> +		return ret;
> +	if (ret < ARRAY_SIZE(ltc4283_cooling_dl)) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_COOLING_DL_MASK,
> +					 FIELD_PREP(LTC4283_COOLING_DL_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ltc4283_parse_array_prop(st, dev, "adi,fet-bad-timer-delay-ms",
> +				       ltc4283_fet_bad_delay, ARRAY_SIZE(ltc4283_fet_bad_delay));
> +	if (ret < 0)
> +		return ret;
> +	if (ret < ARRAY_SIZE(ltc4283_fet_bad_delay)) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONFIG_2, LTC4283_FTBD_DL_MASK,
> +					 FIELD_PREP(LTC4283_FTBD_DL_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = ltc4283_set_max_limits(st, dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc4283_pin_config(st, dev);
> +	if (ret)
> +		return ret;
> +
> +	if (device_property_read_bool(dev, "adi,power-good-reset-on-fet")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> +					LTC4283_PWRGD_RST_CTRL_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,fet-turn-off-disable")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> +					LTC4283_FET_BAD_OFF_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,tmr-pull-down-disable")) {
> +		ret = regmap_set_bits(st->map, LTC4283_CONTROL_1,
> +				      LTC4283_THERM_TMR_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,dvdt-inrush-control-disable")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_1,
> +					LTC4283_DVDT_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,undervoltage-retry-disable")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
> +					LTC4283_UV_RETRY_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,overvoltage-retry-disable")) {
> +		ret = regmap_clear_bits(st->map, LTC4283_CONTROL_2,
> +					LTC4283_OV_RETRY_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,external-fault-retry-enable")) {
> +		if (!st->ext_fault)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "adi,external-fault-retry-enable set but PGIO4 not configured\n");
> +		ret = regmap_set_bits(st->map, LTC4283_CONTROL_2,
> +				      LTC4283_EXT_FAULT_RETRY_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,fault-log-enable")) {
> +		ret = regmap_set_bits(st->map, LTC4283_FAULT_LOG_CTRL,
> +				      LTC4283_FAULT_LOG_EN_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = device_property_match_property_string(dev, "adi,overcurrent-retries",
> +						    ltc4283_oc_fet_retry,
> +						    ARRAY_SIZE(ltc4283_oc_fet_retry));
> +	/* We still want to catch when an invalid string is given. */
> +	if (ret < 0 && ret != -EINVAL)
> +		return dev_err_probe(dev, ret,
> +				     "adi,overcurrent-retries invalid value\n");
> +	if (ret >= 0) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
> +					 LTC4283_OC_RETRY_MASK,
> +					 FIELD_PREP(LTC4283_OC_RETRY_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = device_property_match_property_string(dev, "adi,fet-bad-retries",
> +						    ltc4283_oc_fet_retry,
> +						    ARRAY_SIZE(ltc4283_oc_fet_retry));
> +	if (ret < 0 && ret != -EINVAL)
> +		return dev_err_probe(dev, ret,
> +				     "adi,fet-bad-retries invalid value\n");
> +	if (ret >= 0) {
> +		ret = regmap_update_bits(st->map, LTC4283_CONTROL_2,
> +					 LTC4283_FET_BAD_RETRY_MASK,
> +					 FIELD_PREP(LTC4283_FET_BAD_RETRY_MASK, ret));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,external-fault-fet-off-enable")) {
> +		if (!st->ext_fault)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "adi,external-fault-fet-off-enable set but PGIO4 not configured\n");
> +		ret = regmap_set_bits(st->map, LTC4283_CONFIG_3,
> +				      LTC4283_EXTFLT_TURN_OFF_MASK);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (device_property_read_bool(dev, "adi,vpower-drns-enable")) {
> +		u32 chan = LTC4283_CHAN_DRNS - LTC4283_CHAN_ADI_1;
> +
> +		__clear_bit(LTC4283_CHAN_DRNS, &st->ch_enable_mask);
> +		/*
> +		 * Then, let's by default disable DRNS from ADC2 given that it
> +		 * is already being monitored by the VPWR channel. One can still
> +		 * enable it later on if needed.
> +		 */
> +		ret = regmap_clear_bits(st->map, LTC4283_ADC_SELECT(chan),
> +					LTC4283_ADC_SELECT_MASK(chan));
> +		if (ret)
> +			return ret;
> +
> +		val = 1;
> +	} else {
> +		val = 0;
> +	}
> +
> +	ret = regmap_update_bits(st->map, LTC4283_CONFIG_3,
> +				 LTC4283_VPWR_DRNS_MASK,
> +				 FIELD_PREP(LTC4283_VPWR_DRNS_MASK, val));
> +	if (ret)
> +		return ret;
> +
> +	/* Make sure the ADC has 12bit resolution since we're assuming that. */
> +	ret = regmap_update_bits(st->map, LTC4283_PGIO_CONFIG_2,
> +				 LTC4283_ADC_MASK,
> +				 FIELD_PREP(LTC4283_ADC_MASK, 3));
> +	if (ret)
> +		return ret;
> +
> +	/* Energy reads (which are 6 byte block reads) rely on page access */
> +	ret = regmap_set_bits(st->map, LTC4283_CONTROL_1, LTC4283_RW_PAGE_MASK);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Make sure we are integrating power as we only support reporting
> +	 * consumed energy.
> +	 */
> +	return regmap_clear_bits(st->map, LTC4283_METER_CONTROL,
> +				 LTC4283_INTEGRATE_I_MASK);
> +}
> +
> +static const struct hwmon_channel_info * const ltc4283_info[] = {
> +	HWMON_CHANNEL_INFO(in,
> +			   HWMON_I_LCRIT_ALARM | HWMON_I_CRIT_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_MAX_ALARM | HWMON_I_RESET_HISTORY |
> +			   HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_FAULT | HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL,
> +			   HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST |
> +			   HWMON_I_MAX | HWMON_I_MIN | HWMON_I_MIN_ALARM |
> +			   HWMON_I_RESET_HISTORY | HWMON_I_MAX_ALARM |
> +			   HWMON_I_ENABLE | HWMON_I_LABEL),
> +	HWMON_CHANNEL_INFO(curr,
> +			   HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST |
> +			   HWMON_C_MAX | HWMON_C_MIN | HWMON_C_MIN_ALARM |
> +			   HWMON_C_MAX_ALARM | HWMON_C_CRIT_ALARM |
> +			   HWMON_C_RESET_HISTORY | HWMON_C_LABEL),
> +	HWMON_CHANNEL_INFO(power,
> +			   HWMON_P_INPUT | HWMON_P_INPUT_LOWEST |
> +			   HWMON_P_INPUT_HIGHEST | HWMON_P_MAX | HWMON_P_MIN |
> +			   HWMON_P_MAX_ALARM | HWMON_P_MIN_ALARM |
> +			   HWMON_P_RESET_HISTORY | HWMON_P_LABEL),
> +	HWMON_CHANNEL_INFO(energy,
> +			   HWMON_E_ENABLE),
> +	HWMON_CHANNEL_INFO(energy64,
> +			   HWMON_E_INPUT),
> +	NULL
> +};
> +
> +static const struct hwmon_ops ltc4283_ops = {
> +	.read = ltc4283_read,
> +	.write = ltc4283_write,
> +	.is_visible = ltc4283_is_visible,
> +	.read_string = ltc4283_read_labels,
> +};
> +
> +static const struct hwmon_chip_info ltc4283_chip_info = {
> +	.ops = &ltc4283_ops,
> +	.info = ltc4283_info,
> +};
> +
> +static int ltc4283_show_fault_log(void *arg, u64 *val, u32 mask)
> +{
> +	struct ltc4283_hwmon *st = arg;
> +	long alarm;
> +	int ret;
> +
> +	ret = ltc4283_read_alarm(st, LTC4283_FAULT_LOG, mask, &alarm);
> +	if (ret)
> +		return ret;
> +
> +	*val = alarm;
> +
> +	return 0;
> +}
> +
> +static int ltc4283_show_in0_lcrit_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_UV_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_lcrit_fault_log,
> +			 ltc4283_show_in0_lcrit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_in0_crit_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_OV_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_in0_crit_fault_log,
> +			 ltc4283_show_in0_crit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_fet_bad_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_FET_BAD_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_bad_fault_log,
> +			 ltc4283_show_fet_bad_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_fet_short_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_FET_SHORT_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_fet_short_fault_log,
> +			 ltc4283_show_fet_short_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_curr1_crit_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_OC_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_curr1_crit_fault_log,
> +			 ltc4283_show_curr1_crit_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_power1_failed_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_PWR_FAIL_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_failed_fault_log,
> +			 ltc4283_show_power1_failed_fault_log, NULL, "%llu\n");
> +
> +static int ltc4283_show_power1_good_input_fault_log(void *arg, u64 *val)
> +{
> +	return ltc4283_show_fault_log(arg, val, LTC4283_PGI_FAULT_MASK);
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(ltc4283_power1_good_input_fault_log,
> +			 ltc4283_show_power1_good_input_fault_log, NULL, "%llu\n");
> +
> +static void ltc4283_debugfs_init(struct ltc4283_hwmon *st, struct i2c_client *i2c)
> +{
> +	debugfs_create_file_unsafe("in0_crit_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_in0_crit_fault_log);
> +	debugfs_create_file_unsafe("in0_lcrit_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_in0_lcrit_fault_log);
> +	debugfs_create_file_unsafe("in0_fet_bad_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_fet_bad_fault_log);
> +	debugfs_create_file_unsafe("in0_fet_short_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_fet_short_fault_log);
> +	debugfs_create_file_unsafe("curr1_crit_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_curr1_crit_fault_log);
> +	debugfs_create_file_unsafe("power1_failed_fault_log", 0400, i2c->debugfs, st,
> +				   &ltc4283_power1_failed_fault_log);
> +	debugfs_create_file_unsafe("power1_good_input_fault_log", 0400, i2c->debugfs,
> +				   st, &ltc4283_power1_good_input_fault_log);
> +}
> +
> +static bool ltc4283_is_word_reg(unsigned int reg)
> +{
> +	return reg >= LTC4283_SENSE && reg <= LTC4283_ADIO34_MAX;
> +}
> +
> +static int ltc4283_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> +	struct i2c_client *client = context;
> +	int ret;
> +
> +	if (ltc4283_is_word_reg(reg))
> +		ret = i2c_smbus_read_word_swapped(client, reg);
> +	else
> +		ret = i2c_smbus_read_byte_data(client, reg);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	*val = ret;
> +	return 0;
> +}
> +
> +static int ltc4283_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> +	struct i2c_client *client = context;
> +
> +	if (ltc4283_is_word_reg(reg))
> +		return i2c_smbus_write_word_swapped(client, reg, val);
> +
> +	return i2c_smbus_write_byte_data(client, reg, val);
> +}
> +
> +static const struct regmap_bus ltc4283_regmap_bus = {
> +	.reg_read = ltc4283_reg_read,
> +	.reg_write = ltc4283_reg_write,
> +};
> +
> +static bool ltc4283_writable_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case LTC4283_SYSTEM_STATUS ... LTC4283_FAULT_STATUS:
> +		return false;
> +	case LTC4283_RESERVED_OC:
> +		return false;
> +	case LTC4283_RESERVED_86 ... LTC4283_RESERVED_8F:
> +		return false;
> +	case LTC4283_RESERVED_91 ... LTC4283_RESERVED_A1:
> +		return false;
> +	case LTC4283_RESERVED_A3:
> +		return false;
> +	case LTC4283_RESERVED_AC:
> +		return false;
> +	case LTC4283_POWER_PLAY_MSB ... LTC4283_POWER_PLAY_LSB:
> +		return false;
> +	case LTC4283_RESERVED_F1 ... LTC4283_RESERVED_FF:
> +		return false;
> +	default:
> +		return true;
> +	}
> +}
> +
> +static const struct regmap_config ltc4283_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 16,
> +	.max_register = 0xFF,
> +	.writeable_reg = ltc4283_writable_reg,
> +};
> +
> +static int ltc4283_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev, *hwmon;
> +	struct auxiliary_device *adev;
> +	struct ltc4283_hwmon *st;
> +	int ret, id;
> +
> +	st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
> +	if (!st)
> +		return -ENOMEM;
> +
> +	if (!i2c_check_functionality(client->adapter,
> +				     I2C_FUNC_SMBUS_BYTE_DATA |
> +				     I2C_FUNC_SMBUS_WORD_DATA |
> +				     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
> +		return -EOPNOTSUPP;
> +
> +	st->client = client;
> +	st->map = devm_regmap_init(dev, &ltc4283_regmap_bus, client,
> +				   &ltc4283_regmap_config);
> +	if (IS_ERR(st->map))
> +		return dev_err_probe(dev, PTR_ERR(st->map),
> +				     "Failed to create regmap\n");
> +
> +	ret = ltc4283_setup(st, dev);
> +	if (ret)
> +		return ret;
> +
> +	hwmon = devm_hwmon_device_register_with_info(dev, "ltc4283", st,
> +						     &ltc4283_chip_info, NULL);
> +
> +	if (IS_ERR(hwmon))
> +		return PTR_ERR(hwmon);
> +
> +	ltc4283_debugfs_init(st, client);
> +
> +	if (!st->gpio_mask)
> +		return 0;
> +
> +	id = (client->adapter->nr << 10) | client->addr;
> +	adev = __devm_auxiliary_device_create(dev, KBUILD_MODNAME, "gpio",
> +					      NULL, id);
> +	if (!adev)
> +		return dev_err_probe(dev, -ENODEV, "Failed to add GPIO device\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id ltc4283_of_match[] = {
> +	{ .compatible = "adi,ltc4283" },
> +	{ }
> +};
> +
> +static const struct i2c_device_id ltc4283_i2c_id[] = {
> +	{ "ltc4283" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ltc4283_i2c_id);
> +
> +static struct i2c_driver ltc4283_driver = {
> +	.driver	= {
> +		.name = "ltc4283",
> +		.of_match_table = ltc4283_of_match,
> +	},
> +	.probe = ltc4283_probe,
> +	.id_table = ltc4283_i2c_id,
> +};
> +module_i2c_driver(ltc4283_driver);
> +
> +MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
> +MODULE_DESCRIPTION("LTC4283 Hot Swap Controller driver");
> +MODULE_LICENSE("GPL");
> 


^ permalink raw reply

* Re: [PATCH net-next v4 0/3] net: bridge: add stp_mode attribute for STP mode selection
From: patchwork-bot+netdevbpf @ 2026-04-10 23:10 UTC (permalink / raw)
  To: Andy Roulin
  Cc: netdev, bridge, razor, idosch, andrew+netdev, davem, edumazet,
	kuba, pabeni, horms, corbet, shuah, petrm, donald.hunter,
	jonas.gorski, linux-doc, linux-kselftest, linux-kernel
In-Reply-To: <20260405205224.3163000-1-aroulin@nvidia.com>

Hello:

This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Sun,  5 Apr 2026 13:52:21 -0700 you wrote:
> The bridge-stp usermode helper is currently restricted to the initial
> network namespace, preventing userspace STP daemons like mstpd from
> operating on bridges in other namespaces. Since commit ff62198553e4
> ("bridge: Only call /sbin/bridge-stp for the initial network
> namespace"), bridges in non-init namespaces silently fall back to
> kernel STP with no way to request userspace STP.
> 
> [...]

Here is the summary with links:
  - [net-next,v4,1/3] net: bridge: add stp_mode attribute for STP mode selection
    https://git.kernel.org/netdev/net-next/c/54fc83a17285
  - [net-next,v4,2/3] docs: net: bridge: document stp_mode attribute
    https://git.kernel.org/netdev/net-next/c/c4f2aab121cd
  - [net-next,v4,3/3] selftests: net: add bridge STP mode selection test
    https://git.kernel.org/netdev/net-next/c/20ae6d76e381

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH v2 00/16] fs,x86/resctrl: Add kernel-mode (e.g., PLZA) support to the resctrl subsystem
From: Moger, Babu @ 2026-04-10 22:52 UTC (permalink / raw)
  To: Reinette Chatre, Babu Moger, corbet@lwn.net, tony.luck@intel.com,
	Dave.Martin@arm.com, james.morse@arm.com, tglx@kernel.org,
	mingo@redhat.com, bp@alien8.de, dave.hansen@linux.intel.com
  Cc: skhan@linuxfoundation.org, x86@kernel.org, hpa@zytor.com,
	peterz@infradead.org, juri.lelli@redhat.com,
	vincent.guittot@linaro.org, dietmar.eggemann@arm.com,
	rostedt@goodmis.org, bsegall@google.com, mgorman@suse.de,
	vschneid@redhat.com, kas@kernel.org, rick.p.edgecombe@intel.com,
	akpm@linux-foundation.org, pmladek@suse.com,
	rdunlap@infradead.org, dapeng1.mi@linux.intel.com,
	kees@kernel.org, elver@google.com, paulmck@kernel.org,
	lirongqing@baidu.com, safinaskar@gmail.com, fvdl@google.com,
	seanjc@google.com, pawan.kumar.gupta@linux.intel.com,
	xin@zytor.com, tiala@microsoft.com, chang.seok.bae@intel.com,
	Lendacky, Thomas, elena.reshetova@intel.com,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-coco@lists.linux.dev, kvm@vger.kernel.org,
	eranian@google.com, peternewman@google.com
In-Reply-To: <68a551ea-d9f0-436a-9bef-e35fd027bb95@intel.com>

Hi Reinette,

On 4/9/2026 10:41 PM, Reinette Chatre wrote:
> Hi Babu,
> 
> On 4/9/26 4:42 PM, Moger, Babu wrote:
>> Hi Reinette,
>>
>> On 4/9/2026 3:50 PM, Reinette Chatre wrote:
>>> Hi Babu,
>>>
>>> On 4/9/26 11:05 AM, Moger, Babu wrote:
>>>> On 4/9/2026 12:26 PM, Reinette Chatre wrote:
>>>>> On 4/9/26 10:19 AM, Moger, Babu wrote:
>>>>>> On 4/8/2026 6:41 PM, Reinette Chatre wrote:
>>>>>
>>>>>>> When the user switches to either "global_assign_ctrl_inherit_mon_per_cpu" or
>>>>>>> 'global_assign_ctrl_assign_mon_per_cpu" then "info/kernel_mode_assignment" is created
>>>>>>> (or made visible to user space) and is expected to point to default group.
>>>>>>> User can change the group using "info/kernel_mode_assignment" at this point.
>>>>>>>
>>>>>>> If the current scenario is below ...
>>>>>>>        # cat info/kernel_mode
>>>>>>>        [global_assign_ctrl_inherit_mon_per_cpu]
>>>>>>>        inherit_ctrl_and_mon
>>>>>>>        global_assign_ctrl_assign_mon_per_cpu
>>>>>>>
>>>>>>> ... then "info/kernel_mode_assignment" will exist but what it should contain if
>>>>>>> user switches mode at this point may be up for discussion.
>>>>>>>
>>>>>>> option 1)
>>>>>>> When user switches mode to "global_assign_ctrl_assign_mon_per_cpu" then
>>>>>>> the resource group in "info/kernel_mode_assignment" is reset to the
>>>>>>> default group and all CPUs PLZA state reset to match. The kernel_mode_cpus
>>>>>>> and kernel_mode_cpuslist files become visible in default resource group
>>>>>>> and they contain "all online CPUs".
>>>>>>>
>>>>>>> option 2)
>>>>>>> When user switches mode to "global_assign_ctrl_assign_mon_per_cpu" then
>>>>>>> the resource group in "info/kernel_mode_assignment" is kept and all
>>>>>>> CPUs PLZA state set to match it while also keeping the current
>>>>>>> values of that resource group's kernel_mode_cpus and kernel_mode_cpuslist
>>>>>>> files.
>>>>>>>
>>>>>>> I am leaning towards "option 1" to keep it consistent with a switch from
>>>>>>> "inherit_ctrl_and_mon" and being deterministic about how a mode is started with
>>>>>>
>>>>>> Yes. The "option 1" seems appropriate.
>>>>>>
>>>>>>> a clean slate. What are your thoughts? What would be use case where a user would
>>>>>>> want to switch between "global_assign_ctrl_inherit_mon_per_cpu" and
>>>>>>> "global_assign_ctrl_assign_mon_per_cpu" to just switch rmid_en on and off?
>>>>>>
>>>>>>
>>>>>> This is a bit tricky.
>>>>>>
>>>>>> Currently, our requirement is to have a CTRL_MON group for
>>>>>> global_assign_ctrl_inherit_mon_per_cpu. In this scenario, we use the
>>>>>> group’s CLOSID for PLZA configuration, and RMID is not used (rmid_en
>>>>>> = 0) when setting up PLZA.
>>>>>>
>>>>>> Our requirement is also to have a CTRL_MON/MON group for
>>>>>> global_assign_ctrl_assign_mon_per_cpu. In this case as well, the
>>>>>> group’s CLOSID and RMID (rmid_en = 1)  both are used configure PLZA.
>>>>>
>>>>> ah, right. Good catch.
>>>>>
>>>>>>
>>>>>> Actually, we should not allow these changes from
>>>>>> global_assign_ctrl_inherit_mon_per_cpu  to
>>>>>> global_assign_ctrl_assign_mon_per_cpu or visa versa.
>>>>>
>>>>> resctrl could allow it but as part of the switch it resets the "kernel mode group" to
>>>>> be the default group every time? This would be the "option 1" above.
>>>>
>>>> Other options.
>>>>
>>>> Allow global_assign_ctrl_inherit_mon_per_cpu -> global_assign_ctrl_assign_mon_per_cpu. As part of the switch, reset the "kernel mode group" to the default group.
>>>>
>>>> Allow global_assign_ctrl_assign_mon_per_cpu -> global_assign_ctrl_inherit_mon_per_cpu. In this case switch
>>>> to CTRL_MON/MON -> CTRL_MON.
>>>>
>>>
>>> ok. Could you please return the courtesy of providing feedback on the
>>> suggestion you are responding to and also include the motivation why your
>>> suggestion is the better option?
>>
>> Yea. Sure.
>>
>> We need to allow the switch between the modes. Otherwise only way to reset is to remount the resctrl filesystem. That is not a good option.
>>
>> Allow global_assign_ctrl_inherit_mon_per_cpu -> global_assign_ctrl_assign_mon_per_cpu. As part of the switch, reset the "kernel mode group" to the default group.
>>
>> This option is same as you suggested.
>>
>> Allow global_assign_ctrl_assign_mon_per_cpu -> global_assign_ctrl_inherit_mon_per_cpu. In this case switch
>> to CTRL_MON/MON -> CTRL_MON. This option basically disables monitor (rmid_en=0). It is less disruptive. Move is between child group to parent group.
> 
> ok. I am concerned that this creates an inconsistent interface. Specifically, sometimes
> when switching the mode the kernel group will reset and sometimes it won't. This inconsistency
> may be more apparent when writing the user documentation as part of this work. If you are
> able to clearly explain how this resctrl fs interface behaves (this cannot be about PLZA
> internals as above) then this could work.
> 

Yes, certainly. I’ll begin work on v3, and we can continue refining it 
as we move forward.

Thanks
Babu

^ permalink raw reply

* [PATCH] kbuild: document generation of offset header files
From: Piyush Patle @ 2026-04-10 22:12 UTC (permalink / raw)
  To: Nathan Chancellor, Nicolas Schier, Jonathan Corbet, linux-kbuild,
	linux-doc
  Cc: Shuah Khan, Mark Rutland, Chen Pei, Randy Dunlap, Arnd Bergmann,
	Masahiro Yamada, linux-kernel

Replace the placeholder reference with a description of how Kbuild
generates offset header files such as include/generated/asm-offsets.h.

Remove the corresponding TODO entry now that this is documented.

Signed-off-by: Piyush Patle <piyushpatle228@gmail.com>
---
 Documentation/kbuild/makefiles.rst | 41 ++++++++++++++++++++++++------
 1 file changed, 33 insertions(+), 8 deletions(-)

diff --git a/Documentation/kbuild/makefiles.rst b/Documentation/kbuild/makefiles.rst
index 24a4708d26e8..7521cae7d56f 100644
--- a/Documentation/kbuild/makefiles.rst
+++ b/Documentation/kbuild/makefiles.rst
@@ -1285,8 +1285,39 @@ Example::
 In this example, the file target maketools will be processed
 before descending down in the subdirectories.
 
-See also chapter XXX-TODO that describes how kbuild supports
-generating offset header files.
+Generating offset header files
+------------------------------
+
+The ``include/generated/asm-offsets.h`` header exposes C structure
+member offsets and other compile-time constants to assembly code. It
+is generated from ``arch/$(SRCARCH)/kernel/asm-offsets.c``.
+
+The source file uses ``DEFINE()``, ``OFFSET()``, ``BLANK()`` and
+``COMMENT()`` from ``<linux/kbuild.h>``. These emit marker strings
+through inline asm that Kbuild extracts from the compiled assembly
+output.
+
+Example::
+
+  #include <linux/kbuild.h>
+  #include <linux/sched.h>
+
+  int main(void)
+  {
+          OFFSET(TSK_ACTIVE_MM, task_struct, active_mm);
+          DEFINE(THREAD_SIZE, THREAD_SIZE);
+          BLANK();
+          return 0;
+  }
+
+The rules are defined in the top-level ``Kbuild`` and
+``scripts/Makefile.lib``. The header is built during Kbuild's
+``prepare`` phase, after ``archprepare`` and before descending into
+subdirectories.
+
+The same mechanism generates ``include/generated/bounds.h`` from
+``kernel/bounds.c`` and ``include/generated/rq-offsets.h`` from
+``kernel/sched/rq-offsets.c``.
 
 List directories to visit when descending
 -----------------------------------------
@@ -1690,9 +1721,3 @@ Credits
 - Updates by Kai Germaschewski <kai@tp1.ruhr-uni-bochum.de>
 - Updates by Sam Ravnborg <sam@ravnborg.org>
 - Language QA by Jan Engelhardt <jengelh@gmx.de>
-
-TODO
-====
-
-- Generating offset header files.
-- Add more variables to chapters 7 or 9?
-- 
2.43.0


^ permalink raw reply related

* Re: [syzbot ci] Re: hugetlb: normalize exported interfaces to use base-page indices
From: jane.chu @ 2026-04-10 21:54 UTC (permalink / raw)
  To: syzbot ci, akpm, baolin.wang, corbet, david, hughd, liam.howlett,
	linux-doc, linux-kernel, linux-mm, lorenzo.stoakes, mhocko,
	muchun.song, osalvador, peterx, rppt, skhan, surenb, vbabka
  Cc: syzbot, syzkaller-bugs
In-Reply-To: <69d89c97.050a0220.3030df.0026.GAE@google.com>



On 4/9/2026 11:45 PM, syzbot ci wrote:
> syzbot ci has tested the following series
> 
> [v1] hugetlb: normalize exported interfaces to use base-page indices
> https://lore.kernel.org/all/20260409234158.837786-1-jane.chu@oracle.com
> * [PATCH 1/6] hugetlb: open-code hugetlb folio lookup index conversion
> * [PATCH 2/6] hugetlb: remove the hugetlb_linear_page_index() helper
> * [PATCH 3/6] hugetlb: make hugetlb_fault_mutex_hash() take PAGE_SIZE index
> * [PATCH 4/6] hugetlb: drop vma_hugecache_offset() in favor of linear_page_index()
> * [PATCH 5/6] hugetlb: make hugetlb_add_to_page_cache() use PAGE_SIZE-based index
> * [PATCH 6/6] hugetlb: pass hugetlb reservation ranges in base-page indices
> 
> and found the following issue:
> WARNING: bad unlock balance in hugetlb_no_page

Thanks for catching the bug. I was able to reproduce by turning on a few 
configs.  It appears below change fixed the issue, please confirm.

$ diff -c mm/hugetlb.c-BAD mm/hugetlb.c
*** mm/hugetlb.c-BAD    2026-04-10 13:36:52.417044993 -0600
--- mm/hugetlb.c        2026-04-10 14:33:31.637033381 -0600
***************
*** 5659,5665 ****
         u32 hash;
         pgoff_t index;

!       index = linear_page_index((const struct vm_area_struct *)vmf, 
vmf->address);
         hash = hugetlb_fault_mutex_hash(mapping, index);

         /*
--- 5659,5665 ----
         u32 hash;
         pgoff_t index;

!       index = linear_page_index(vmf->vma, vmf->address);
         hash = hugetlb_fault_mutex_hash(mapping, index);


thanks,
-jane

> 
> Full report is available here:
> https://ci.syzbot.org/series/95c5ba82-0135-4026-b7c7-b0819e1ca4d6
> 
> ***
> 
> WARNING: bad unlock balance in hugetlb_no_page
> 
> tree:      mm-new
> URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/akpm/mm.git
> base:      06a6cfb92448a97ef429a7fbd395a20a9d388acc
> arch:      amd64
> compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
> config:    https://ci.syzbot.org/builds/cefe8576-3c99-42d3-9b51-1e70d62a64a7/config
> syz repro: https://ci.syzbot.org/findings/3a14cc12-14a8-4fac-9614-ae7ae2555e58/syz_repro
> 
> =====================================
> WARNING: bad unlock balance detected!
> syzkaller #0 Not tainted
> -------------------------------------
> syz.0.17/5971 is trying to release lock (&hugetlb_fault_mutex_table[i]) at:
> [<ffffffff8229b876>] hugetlb_handle_userfault mm/hugetlb.c:5686 [inline]
> [<ffffffff8229b876>] hugetlb_no_page+0x1986/0x1da0 mm/hugetlb.c:5770
> but there are no more locks to release!
> 
> other info that might help us debug this:
> 2 locks held by syz.0.17/5971:
>   #0: ffff88816b85fb88 (vm_lock){++++}-{0:0}, at: lock_vma_under_rcu+0x1d1/0x500 mm/mmap_lock.c:310
>   #1: ffff88816079e338 (&hugetlb_fault_mutex_table[i]){+.+.}-{4:4}, at: hugetlb_fault+0x317/0x1440 mm/hugetlb.c:5991
> 
> stack backtrace:
> CPU: 0 UID: 0 PID: 5971 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full)
> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
> Call Trace:
>   <TASK>
>   dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
>   print_unlock_imbalance_bug+0xdc/0xf0 kernel/locking/lockdep.c:5298
>   __lock_release kernel/locking/lockdep.c:5537 [inline]
>   lock_release+0x248/0x3d0 kernel/locking/lockdep.c:5889
>   __mutex_unlock_slowpath+0xd3/0x7d0 kernel/locking/mutex.c:938
>   hugetlb_handle_userfault mm/hugetlb.c:5686 [inline]
>   hugetlb_no_page+0x1986/0x1da0 mm/hugetlb.c:5770
>   hugetlb_fault+0x67f/0x1440 mm/hugetlb.c:-1
>   handle_mm_fault+0x2007/0x3170 mm/memory.c:6716
>   do_user_addr_fault+0xa73/0x1340 arch/x86/mm/fault.c:1334
>   handle_page_fault arch/x86/mm/fault.c:1474 [inline]
>   exc_page_fault+0x6a/0xc0 arch/x86/mm/fault.c:1527
>   asm_exc_page_fault+0x26/0x30 arch/x86/include/asm/idtentry.h:618
> RIP: 0033:0x7fa742251964
> Code: 41 89 00 31 c0 c3 b9 40 00 00 00 bf 40 00 00 00 eb bc 0f 1f 40 00 48 89 7c 24 f8 48 89 74 24 f0 48 8b 7c 24 f8 4c 8b 44 24 f0 <8b> 4f 50 8b 47 58 4c 01 c1 41 8b 34 00 8b 11 21 d6 89 f0 8d 72 01
> RSP: 002b:00007fa7431fd018 EFLAGS: 00010212
> RAX: 00007fa742251950 RBX: 00007fa742615fa0 RCX: 0000000000000000
> RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000200000400000
> RBP: 00007fa742432c91 R08: 0000000000000000 R09: 0000000000000000
> R10: 0000200000400000 R11: 0000000000000000 R12: 0000000000000000
> R13: 00007fa742616038 R14: 00007fa742615fa0 R15: 00007ffe952c6908
>   </TASK>
> 
> 
> ***
> 
> If these findings have caused you to resend the series or submit a
> separate fix, please add the following tag to your commit message:
>    Tested-by: syzbot@syzkaller.appspotmail.com
> 
> ---
> This report is generated by a bot. It may contain errors.
> syzbot ci engineers can be reached at syzkaller@googlegroups.com.
> 
> To test a patch for this bug, please reply with `#syz test`
> (should be on a separate line).
> 
> The patch should be attached to the email.
> Note: arguments like custom git repos and branches are not supported.


^ permalink raw reply

* Re: [PATCH v7 6/6] docs: iio: adc: ad4691: add driver documentation
From: David Lechner @ 2026-04-10 21:38 UTC (permalink / raw)
  To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc
In-Reply-To: <20260409-ad4692-multichannel-sar-adc-driver-v7-6-be375d4df2c5@analog.com>

On 4/9/26 10:28 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add RST documentation for the AD4691 family ADC driver covering
> supported devices, IIO channels, operating modes, oversampling,
> reference voltage, LDO supply, reset, GP pins, SPI offload support,
> and buffer data format.
> 
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
>  Documentation/iio/ad4691.rst | 283 +++++++++++++++++++++++++++++++++++++++++++
>  Documentation/iio/index.rst  |   1 +
>  MAINTAINERS                  |   1 +
>  3 files changed, 285 insertions(+)
> 
> diff --git a/Documentation/iio/ad4691.rst b/Documentation/iio/ad4691.rst
> new file mode 100644
> index 000000000000..a1012c8b78a3
> --- /dev/null
> +++ b/Documentation/iio/ad4691.rst
> @@ -0,0 +1,283 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +
> +=============
> +AD4691 driver
> +=============


One overall comment. This goes into driver implementation details quite a bit.
I think that is really better done as comments in the driver itself. And this
document should just focus on how to use the driver from the userspace point
of view.


> +Buffer data format
> +==================
> +
> +The IIO buffer data format (``in_voltageN_type``) is the same across all
> +paths: 16-bit unsigned big-endian samples with no shift.
> +
> ++-------------------------+-------------+----------+-------+
> +| Path                    | storagebits | realbits | shift |
> ++=========================+=============+==========+=======+
> +| Triggered buffer        | 16          | 16       | 0     |
> ++-------------------------+-------------+----------+-------+
> +| CNV Burst offload (DMA) | 16          | 16       | 0     |
> ++-------------------------+-------------+----------+-------+
> +| Manual offload (DMA)    | 16          | 16       | 0     |
> ++-------------------------+-------------+----------+-------+

Not sure this table is helpful since all values are the same everywhere.

Also, doesn't SPI offload have storagebits == 32?

> +
> +In the triggered-buffer path the SPI rx_buf for each transfer points directly
> +into the scan buffer, so the 16-bit big-endian result is written in place with
> +no additional copying.
> +


^ permalink raw reply

* Re: [PATCH v7 5/6] iio: adc: ad4691: add oversampling support
From: David Lechner @ 2026-04-10 21:15 UTC (permalink / raw)
  To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc
In-Reply-To: <20260409-ad4692-multichannel-sar-adc-driver-v7-5-be375d4df2c5@analog.com>

On 4/9/26 10:28 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add per-channel oversampling ratio (OSR) support for CNV burst mode.
> The accumulator depth register (ACC_DEPTH_IN) is programmed with the
> selected OSR at buffer enable time and before each single-shot read.
> 
> Supported OSR values: 1, 2, 4, 8, 16, 32.
> 
> Introduce AD4691_MANUAL_CHANNEL() for manual mode channels, which do
> not expose the oversampling ratio attribute since OSR is not applicable
> in that mode. A separate manual_channels array is added to
> struct ad4691_channel_info and selected at probe time; offload paths
> reuse the same arrays with num_channels capping access before the soft
> timestamp entry.
> 
> The reported sampling frequency accounts for the active OSR:
> effective_freq = oscillator_freq / osr

Technically, the way this is implemented is fine according to IIO ABI
rules. Writing any attribute can cause others to change. It does
introduce a potential pitfall though. Currently, changing the OSR will
change the sampling frequency, so you have to always write oversampling_ratio
first, then write sampling_frequency to get what you asked for. If you want
to change the OSR and keep the same sample rate, you still have to write both
attributes again.

In other drivers, I've implemented it so that the requested sampling frequency
is stored any you always get the closest sampling frequency available based on
the oversampling ratio. This way, it doesn't matter which order you write
the attributes. In that case, the actual periodic trigger source isn't set up
until we actually start sampling.

> 
> OSR defaults to 1 (no accumulation) for all channels.
> 

...

> @@ -499,7 +570,7 @@ static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val)
>  	if (ret)
>  		return ret;
>  
> -	*val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
> +	*val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)] / osr;

I guess we don't have to worry about fractional values here?

>  	return IIO_VAL_INT;
>  }
>  
> @@ -536,6 +607,11 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
>  		*type = IIO_VAL_INT;
>  		*length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
>  		return IIO_AVAIL_LIST;
> +	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> +		*vals = ad4691_oversampling_ratios;
> +		*type = IIO_VAL_INT;
> +		*length = ARRAY_SIZE(ad4691_oversampling_ratios);
> +		return IIO_AVAIL_LIST;
>  	default:
>  		return -EINVAL;
>  	}
> @@ -566,6 +642,11 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
>  	if (ret)
>  		return ret;
>  
> +	ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(chan->channel),
> +			   st->osr[chan->channel]);
> +	if (ret)
> +		return ret;
> +
>  	ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, &reg_val);
>  	if (ret)
>  		return ret;
> @@ -575,8 +656,9 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
>  		return ret;
>  
>  	osc_idx = FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val);
> -	/* Wait 2 oscillator periods for the conversion to complete. */
> -	period_us = DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_idx]);
> +	/* Wait osr oscillator periods for all accumulator samples to complete. */

Why did we need to way 2 before and only 1 now when OSR == 1?

> +	period_us = DIV_ROUND_UP((unsigned long)st->osr[chan->channel] * USEC_PER_SEC,
> +				 ad4691_osc_freqs_Hz[osc_idx]);

^ permalink raw reply

* Re: [PATCH v7 4/6] iio: adc: ad4691: add SPI offload support
From: David Lechner @ 2026-04-10 21:00 UTC (permalink / raw)
  To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc
In-Reply-To: <20260409-ad4692-multichannel-sar-adc-driver-v7-4-be375d4df2c5@analog.com>

On 4/9/26 10:28 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add SPI offload support to enable DMA-based, CPU-independent data
> acquisition using the SPI Engine offload framework.
> 
> When an SPI offload is available (devm_spi_offload_get() succeeds),
> the driver registers a DMA engine IIO buffer and uses dedicated buffer
> setup operations. If no offload is available the existing software
> triggered buffer path is used unchanged.
> 
> Both CNV Burst Mode and Manual Mode support offload, but use different
> trigger mechanisms:
> 
> CNV Burst Mode: the SPI Engine is triggered by the ADC's DATA_READY
> signal on the GP pin specified by the trigger-source consumer reference
> in the device tree (one cell = GP pin number 0-3). For this mode the
> driver acts as both an SPI offload consumer (DMA RX stream, message
> optimization) and a trigger source provider: it registers the
> GP/DATA_READY output via devm_spi_offload_trigger_register() so the
> offload framework can match the '#trigger-source-cells' phandle and
> automatically fire the SPI Engine DMA transfer at end-of-conversion.
> 
> Manual Mode: the SPI Engine is triggered by a periodic trigger at
> the configured sampling frequency. The pre-built SPI message uses
> the pipelined CNV-on-CS protocol: N+1 16-bit transfers are issued
> for N active channels (the first result is discarded as garbage from
> the pipeline flush) and the remaining N results are captured by DMA.
> 
> All offload transfers use 16-bit frames (bits_per_word=16, len=2).
> The channel scan_type (storagebits=16, shift=0, IIO_BE) is shared
> between the software triggered-buffer and offload paths; no separate
> scan_type or channel array is needed for the offload case. The
> ad4691_manual_channels[] array introduced in the triggered-buffer
> commit is reused here: it hides the IIO_CHAN_INFO_OVERSAMPLING_RATIO
> attribute, which is not applicable in Manual Mode.
> 
> Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
> 
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
>  drivers/iio/adc/Kconfig  |   2 +
>  drivers/iio/adc/ad4691.c | 398 ++++++++++++++++++++++++++++++++++++++++++++++-
>  2 files changed, 395 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index d498f16c0816..fdc6565933c5 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -143,8 +143,10 @@ config AD4691
>  	tristate "Analog Devices AD4691 Family ADC Driver"
>  	depends on SPI
>  	select IIO_BUFFER
> +	select IIO_BUFFER_DMAENGINE
>  	select IIO_TRIGGERED_BUFFER
>  	select REGMAP
> +	select SPI_OFFLOAD
>  	help
>  	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
>  	  SPI analog to digital converters (ADC).
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index 3e5caa0972eb..839ea7f44c78 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -22,6 +22,8 @@
>  #include <linux/regulator/consumer.h>
>  #include <linux/reset.h>
>  #include <linux/spi/spi.h>
> +#include <linux/spi/offload/consumer.h>
> +#include <linux/spi/offload/provider.h>
>  #include <linux/units.h>
>  #include <linux/unaligned.h>
>  
> @@ -43,6 +45,11 @@
>  
>  #define AD4691_CNV_DUTY_CYCLE_NS		380
>  #define AD4691_CNV_HIGH_TIME_NS			430
> +/*
> + * Conservative default for the manual offload periodic trigger. Low enough
> + * to work safely out of the box across all OSR and channel count combinations.
> + */
> +#define AD4691_OFFLOAD_INITIAL_TRIGGER_HZ	(100 * HZ_PER_KHZ)
>  
>  #define AD4691_SPI_CONFIG_A_REG			0x000
>  #define AD4691_SW_RESET				(BIT(7) | BIT(0))
> @@ -95,6 +102,8 @@
>  #define AD4691_ACC_IN(n)			(0x252 + (3 * (n)))
>  #define AD4691_ACC_STS_DATA(n)			(0x283 + (4 * (n)))
>  
> +#define AD4691_OFFLOAD_BITS_PER_WORD		16

This is just the same as realbits in scan info. So could use that
directly instead.

> +
>  static const char * const ad4691_supplies[] = { "avdd", "vio" };
>  
>  enum ad4691_ref_ctrl {
> @@ -114,6 +123,7 @@ struct ad4691_chip_info {
>  	const char *name;
>  	unsigned int max_rate;
>  	const struct ad4691_channel_info *sw_info;
> +	const struct ad4691_channel_info *offload_info;
>  };
>  
>  #define AD4691_CHANNEL(ch)						\
> @@ -177,6 +187,18 @@ static const struct ad4691_channel_info ad4693_sw_info = {
>  	.num_channels = ARRAY_SIZE(ad4693_channels),
>  };
>  
> +static const struct ad4691_channel_info ad4691_offload_info = {
> +	.channels = ad4691_channels,
> +	/* No soft timestamp; num_channels caps access to 16. */
> +	.num_channels = 16,

`ARRAY_SIZE(ad4691_channels) - 1` would make sense too.

> +};
> +
> +static const struct ad4691_channel_info ad4693_offload_info = {
> +	.channels = ad4693_channels,
> +	/* No soft timestamp; num_channels caps access to 8. */
> +	.num_channels = 8,
> +};
> +
>  /*
>   * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
>   * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
> @@ -207,24 +229,36 @@ static const struct ad4691_chip_info ad4691_chip_info = {
>  	.name = "ad4691",
>  	.max_rate = 500 * HZ_PER_KHZ,
>  	.sw_info = &ad4691_sw_info,
> +	.offload_info = &ad4691_offload_info,
>  };
>  
>  static const struct ad4691_chip_info ad4692_chip_info = {
>  	.name = "ad4692",
>  	.max_rate = 1 * HZ_PER_MHZ,
>  	.sw_info = &ad4691_sw_info,
> +	.offload_info = &ad4691_offload_info,
>  };
>  
>  static const struct ad4691_chip_info ad4693_chip_info = {
>  	.name = "ad4693",
>  	.max_rate = 500 * HZ_PER_KHZ,
>  	.sw_info = &ad4693_sw_info,
> +	.offload_info = &ad4693_offload_info,
>  };
>  
>  static const struct ad4691_chip_info ad4694_chip_info = {
>  	.name = "ad4694",
>  	.max_rate = 1 * HZ_PER_MHZ,
>  	.sw_info = &ad4693_sw_info,
> +	.offload_info = &ad4693_offload_info,
> +};
> +
> +struct ad4691_offload_state {
> +	struct spi_offload *spi;

I would call this "offload" or "instance". "spi" is usally the SPI
device handle.

> +	struct spi_offload_trigger *trigger;
> +	u64 trigger_hz;
> +	u8 tx_cmd[17][2];
> +	u8 tx_reset[4];
>  };
>  

...

> +
> +static int ad4691_cnv_burst_offload_buffer_predisable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	struct ad4691_offload_state *offload = st->offload;
> +	int ret;
> +
> +	spi_offload_trigger_disable(offload->spi, offload->trigger);
> +
> +	ret = ad4691_sampling_enable(st, false);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> +			   AD4691_SEQ_ALL_CHANNELS_OFF);

Why this extra step? We don't have it when unwinding in the
error path of the postenable function.

> +	if (ret)
> +		return ret;
> +
> +	spi_unoptimize_message(&st->scan_msg);
> +
> +	return ad4691_exit_conversion_mode(st);
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_cnv_burst_offload_buffer_setup_ops = {
> +	.postenable = &ad4691_cnv_burst_offload_buffer_postenable,
> +	.predisable = &ad4691_cnv_burst_offload_buffer_predisable,
> +};
> +
>  static ssize_t sampling_frequency_show(struct device *dev,
>  				       struct device_attribute *attr,
>  				       char *buf)

^ permalink raw reply

* Re: [PATCH v7 3/6] iio: adc: ad4691: add triggered buffer support
From: David Lechner @ 2026-04-10 20:46 UTC (permalink / raw)
  To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
	Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
	Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
	Philipp Zabel, Jonathan Corbet, Shuah Khan
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
	linux-doc
In-Reply-To: <20260409-ad4692-multichannel-sar-adc-driver-v7-3-be375d4df2c5@analog.com>

On 4/9/26 10:28 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add buffered capture support using the IIO triggered buffer framework.
> 

...

> @@ -201,8 +245,45 @@ struct ad4691_state {
>  	 * atomicity of consecutive SPI operations.
>  	 */
>  	struct mutex lock;
> +	/*
> +	 * Per-buffer-enable lifetime resources:
> +	 * Manual Mode - a pre-built SPI message that clocks out N+1
> +	 *		 transfers in one go.
> +	 * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
> +	 *		    transfers in one go.
> +	 */
> +	struct spi_message scan_msg;
> +	/* max 16 + 1 NOOP (manual) or 2*16 + 2 (CNV burst). */
> +	struct spi_transfer scan_xfers[34];
> +	/*
> +	 * CNV burst: 16 AVG_IN addresses + state-reset address + state-reset
> +	 * value = 18.  Manual: 16 channel cmds + 1 NOOP = 17.
> +	 */
> +	__be16 scan_tx[18];

Needs __aligned(IIO_DMA_MINALIGN) since it is used with SPI.

> +	/* Scan buffer: one BE16 slot per channel (rx'd directly), plus timestamp */
> +	struct {
> +		__be16 vals[16];
> +		aligned_s64 ts;
> +	} scan;

Unless it is required that all channels are always enabled:

	IIO_DECLARE_BUFFER_WITH_TS(__be16, scan_rx, 16);

In any case, needs to be DMA-safe for SPI.

>  };
>  



> +static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	unsigned int n_active;
> +	unsigned int k, i;
> +	int ret;
> +
> +	n_active = bitmap_weight(indio_dev->active_scan_mask, iio_get_masklength(indio_dev));
> +
> +	memset(st->scan_xfers, 0, (2 * n_active + 2) * sizeof(st->scan_xfers[0]));
> +	memset(st->scan_tx, 0, (n_active + 2) * sizeof(st->scan_tx[0]));

Maybe simpler to just clear the whole thing? (same with other preenable)

> +
> +	spi_message_init(&st->scan_msg);
> +

^ permalink raw reply

* Re: [PATCH net-next v04 3/6] hinic3: Add ethtool coalesce ops
From: Simon Horman @ 2026-04-10 20:12 UTC (permalink / raw)
  To: gongfan1
  Cc: Simon Horman, edumazet, zhengjiezhen, guoxin09, netdev,
	andrew+netdev, maxime.chevallier, zhoushuai28, mohsin.bashr,
	ioana.ciornei, zhuyikai1, luosifu, linux-kernel, kuba, davem,
	linux-doc, pabeni, shijing34, wulike1
In-Reply-To: <f9f5c1239d17675283a2da8b307f5aca8f46864c.1775618798.git.zhuyikai1@h-partners.com>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
hinic3: Add ethtool coalesce ops

This patch implements ethtool .get_coalesce and .set_coalesce callback
functions to allow users to configure and monitor RX coalesce settings.

> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
> index be26698fc658..a4b2d5ba81f8 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c

[ ... ]

> @@ -985,9 +990,230 @@ static void hinic3_get_pause_stats(struct net_device *netdev,
>  	kfree(ps);
>  }
>
> +static int hinic3_set_queue_coalesce(struct net_device *netdev, u16 q_id,
> +				     struct hinic3_intr_coal_info *coal)
> +{
> +	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
> +	struct hinic3_intr_coal_info *intr_coal;
> +	struct hinic3_interrupt_info info = {};
> +	int err;
> +
> +	intr_coal = &nic_dev->intr_coalesce[q_id];
> +
> +	intr_coal->coalesce_timer_cfg = coal->coalesce_timer_cfg;
> +	intr_coal->pending_limit = coal->pending_limit;
> +	intr_coal->rx_pending_limit_low = coal->rx_pending_limit_low;
> +	intr_coal->rx_pending_limit_high = coal->rx_pending_limit_high;
> +
> +	if (!test_bit(HINIC3_INTF_UP, &nic_dev->flags) ||
> +	    q_id >= nic_dev->q_params.num_qps || nic_dev->adaptive_rx_coal)
> +		return 0;
> +
> +	info.msix_index = nic_dev->q_params.irq_cfg[q_id].msix_entry_idx;
> +	info.interrupt_coalesc_set = 1;
> +	info.coalesc_timer_cfg = intr_coal->coalesce_timer_cfg;
> +	info.pending_limit = intr_coal->pending_limit;
> +	info.resend_timer_cfg = intr_coal->resend_timer_cfg;
> +	err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
> +	if (err) {
> +		netdev_warn(netdev, "Failed to set queue%u coalesce\n", q_id);
> +		return err;
> +	}
> +
> +	return 0;
> +}
> +
> +static int is_coalesce_exceed_limit(struct net_device *netdev,
> +				    const struct ethtool_coalesce *coal)
> +{
> +	const struct {
> +		const char *name;
> +		u32 value;
> +		u32 limit;
> +	} coalesce_limits[] = {
> +		{"rx_coalesce_usecs",
> +		 coal->rx_coalesce_usecs,
> +		 COALESCE_MAX_TIMER_CFG},
> +		{"rx_max_coalesced_frames",
> +		 coal->rx_max_coalesced_frames,
> +		 COALESCE_MAX_PENDING_LIMIT},
> +		{"rx_max_coalesced_frames_low",
> +		 coal->rx_max_coalesced_frames_low,
> +		 COALESCE_MAX_PENDING_LIMIT},
> +		{"rx_max_coalesced_frames_high",
> +		 coal->rx_max_coalesced_frames_high,
> +		 COALESCE_MAX_PENDING_LIMIT},
> +	};
> +
> +	for (int i = 0; i < ARRAY_SIZE(coalesce_limits); i++) {
> +		if (coalesce_limits[i].value > coalesce_limits[i].limit) {
> +			netdev_err(netdev, "%s out of range %d-%d\n",
> +				   coalesce_limits[i].name, 0,
> +				   coalesce_limits[i].limit);
> +			return -ERANGE;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int is_coalesce_legal(struct net_device *netdev,
> +			     const struct ethtool_coalesce *coal)
> +{
> +	int err;
> +
> +	err = is_coalesce_exceed_limit(netdev, coal);
> +	if (err)
> +		return err;
> +
> +	if (coal->rx_max_coalesced_frames_low >
> +	    coal->rx_max_coalesced_frames_high) {
> +		netdev_err(netdev, "invalid coalesce frame high %u, low %u, unit %d\n",
> +			   coal->rx_max_coalesced_frames_high,
> +			   coal->rx_max_coalesced_frames_low,
> +			   COALESCE_PENDING_LIMIT_UNIT);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}

Does this validation allow non-zero low with zero high? For example, if
rx_max_coalesced_frames_low=10 and rx_max_coalesced_frames_high=0, the
condition 'low > high' would be true and return -EINVAL, which is correct.

However, this validation still accepts the case where low equals high, including
when both are zero. In v03, a reviewer asked: "So this would allow non-zero low
and zero high. For example, low = 10, high = 0. Is this expected?" The original
v03 logic was 'low >= high && high > 0' which rejected the case where low
equals high. In v04, this was changed to 'low > high' only, but the reviewer's
concern about whether allowing low=high (including low=0, high=0) is expected
behavior was not directly addressed.

Reference: https://lore.kernel.org/netdev/18d5a9b7-9c8d-4fc4-9332-fab27449dc58@gmail.com/

> +
> +static void check_coalesce_align(struct net_device *netdev,
> +				 u32 item, u32 unit, const char *str)
> +{
> +	if (item % unit)
> +		netdev_warn(netdev, "%s in %d units, change to %u\n",
> +			    str, unit, item - item % unit);
> +}

[ ... ]

> @@ -1003,6 +1229,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
>  	.get_eth_ctrl_stats             = hinic3_get_eth_ctrl_stats,
>  	.get_rmon_stats                 = hinic3_get_rmon_stats,
>  	.get_pause_stats                = hinic3_get_pause_stats,
> +	.get_coalesce                   = hinic3_get_coalesce,
> +	.set_coalesce                   = hinic3_set_coalesce,
>  };
>
>  void hinic3_set_ethtool_ops(struct net_device *netdev)

[ ... ]

> @@ -985,9 +990,230 @@ static void hinic3_get_pause_stats(struct net_device *netdev,
>
>  static const struct ethtool_ops hinic3_ethtool_ops = {
> -	.supported_coalesce_params      = ETHTOOL_COALESCE_USECS |
> -					  ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
> +	.supported_coalesce_params      = ETHTOOL_COALESCE_RX_USECS |
> +					  ETHTOOL_COALESCE_RX_MAX_FRAMES |
> +					  ETHTOOL_COALESCE_USE_ADAPTIVE_RX |
> +					  ETHTOOL_COALESCE_RX_MAX_FRAMES_LOW |
> +					  ETHTOOL_COALESCE_RX_MAX_FRAMES_HIGH,

Was the removal of ETHTOOL_COALESCE_TX_USECS intentional? In v02, the code
included 'ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_PKT_RATE_RX_USECS', but
v03 and v04 changed this to only RX-specific parameters. A reviewer asked if
this was intentional, but there was no response from the author and the v04
changelog does not mention this concern.

Reference: https://lore.kernel.org/netdev/18d5a9b7-9c8d-4fc4-9332-fab27449dc58@gmail.com/

^ permalink raw reply

* Re: [PATCH net-next v04 1/6] hinic3: Add ethtool queue ops
From: Simon Horman @ 2026-04-10 20:10 UTC (permalink / raw)
  To: gongfan1
  Cc: Simon Horman, linux-kernel, maxime.chevallier, zhoushuai28, kuba,
	shijing34, pabeni, wulike1, mohsin.bashr, zhuyikai1, edumazet,
	guoxin09, netdev, zhengjiezhen, linux-doc, ioana.ciornei, luosifu,
	andrew+netdev, davem
In-Reply-To: <7deda08d48b7f894c5aa0537f70897871e11d012.1775618798.git.zhuyikai1@h-partners.com>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
hinic3: Add ethtool queue ops

This commit implements .get_ringparam and .set_ringparam ethtool callbacks,
allowing users to configure and monitor queue depths.

> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
> index 90fc16288de9..e47c3f43e7b9 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c

[ ... ]

> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
> index e7d6c2033b45..d3b3927b5408 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c

[ ... ]

> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
> index 0a888fe4c975..3b470978714a 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c

[ ... ]

> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
> index da73811641a9..cec501a9dd43 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
> @@ -428,6 +428,84 @@ static void hinic3_vport_down(struct net_device *netdev)
>  	}
>  }
>
> +int
> +hinic3_change_channel_settings(struct net_device *netdev,
> +			       struct hinic3_dyna_txrxq_params *trxq_params)
> +{
> +	struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
> +	struct hinic3_dyna_txrxq_params old_qp_params = {};
> +	struct hinic3_dyna_qp_params new_qp_params = {};
> +	struct hinic3_dyna_qp_params cur_qp_params = {};
> +	bool need_teardown = false;
> +	unsigned long flags;
> +	int err;
> +
> +	mutex_lock(&nic_dev->channel_cfg_lock);
> +
> +	hinic3_config_num_qps(netdev, trxq_params);
> +
> +	err = hinic3_alloc_channel_resources(netdev, &new_qp_params,
> +					     trxq_params);
> +	if (err) {
> +		netdev_err(netdev, "Failed to alloc channel resources\n");
> +		mutex_unlock(&nic_dev->channel_cfg_lock);
> +		return err;
> +	}
> +
> +	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
> +	if (!test_and_set_bit(HINIC3_CHANGE_RES_INVALID, &nic_dev->flags))
> +		need_teardown = true;
> +	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
> +
> +	if (need_teardown) {
> +		hinic3_vport_down(netdev);
> +		hinic3_close_channel(netdev);
> +		hinic3_uninit_qps(nic_dev, &cur_qp_params);
> +		hinic3_free_channel_resources(netdev, &cur_qp_params,
> +					      &nic_dev->q_params);
> +	}
> +
> +	if (nic_dev->num_qp_irq > trxq_params->num_qps)
> +		hinic3_qp_irq_change(netdev, trxq_params->num_qps);
> +
> +	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
> +	old_qp_params = nic_dev->q_params;
> +	nic_dev->q_params = *trxq_params;
> +	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
> +
> +	hinic3_init_qps(nic_dev, &new_qp_params);
> +
> +	err = hinic3_open_channel(netdev);
> +	if (err)
> +		goto err_uninit_qps;
> +
> +	err = hinic3_vport_up(netdev);
> +	if (err)
> +		goto err_close_channel;
> +
> +	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
> +	clear_bit(HINIC3_CHANGE_RES_INVALID, &nic_dev->flags);
> +	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
> +
> +	mutex_unlock(&nic_dev->channel_cfg_lock);
> +
> +	return 0;
> +
> +err_close_channel:
> +	hinic3_close_channel(netdev);
> +err_uninit_qps:
> +	spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
> +	nic_dev->q_params = old_qp_params;
> +	spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
> +
> +	hinic3_uninit_qps(nic_dev, &new_qp_params);
> +	hinic3_free_channel_resources(netdev, &new_qp_params, trxq_params);
> +
> +	mutex_unlock(&nic_dev->channel_cfg_lock);
> +
> +	return err;
> +}

Does the error path properly clear the HINIC3_CHANGE_RES_INVALID flag?

On the success path, clear_bit(HINIC3_CHANGE_RES_INVALID) is called under
channel_res_lock. However, both error paths (err_close_channel and
err_uninit_qps) skip this, leaving the flag permanently set.

After a failed ring parameter change, the device enters an unrecoverable
state:

1. HINIC3_INTF_UP remains set, HINIC3_CHANGE_RES_INVALID remains set.

2. User does ifdown: hinic3_close() clears HINIC3_INTF_UP but skips
   teardown since HINIC3_CHANGE_RES_INVALID is already set:

   hinic3_close() {
       ...
       spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
       if (!test_and_set_bit(HINIC3_CHANGE_RES_INVALID, &nic_dev->flags))
           need_teardown = true;   // Already set, need_teardown = false
       spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);

       if (need_teardown) {
           // Skipped
       }
   }

3. User does ifup: hinic3_open() allocates fresh resources and sets
   HINIC3_INTF_UP, but never clears HINIC3_CHANGE_RES_INVALID.

4. HINIC3_CHANNEL_RES_VALID() now returns false permanently:

   #define HINIC3_CHANNEL_RES_VALID(nic_dev) \
       (test_bit(HINIC3_INTF_UP, &(nic_dev)->flags) && \
        !test_bit(HINIC3_CHANGE_RES_INVALID, &(nic_dev)->flags))

5. hinic3_link_status_change() in hinic3_main.c silently returns, so the
   link never comes up:

   static void hinic3_link_status_change(struct net_device *netdev,
                                         bool link_status_up)
   {
       ...
       spin_lock_irqsave(&nic_dev->channel_res_lock, flags);
       valid = HINIC3_CHANNEL_RES_VALID(nic_dev);
       spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);

       if (!valid)
           return;   // Always returns here
       ...
   }

6. hinic3_set_interrupt_moder() in hinic3_irq.c returns 0 without
   configuring interrupts:

   static int hinic3_set_interrupt_moder(struct net_device *netdev, ...)
   {
       ...
       spin_lock_irqsave(&nic_dev->channel_res_lock, flags);

       if (!HINIC3_CHANNEL_RES_VALID(nic_dev) ||
           q_id >= nic_dev->q_params.num_qps) {
           spin_unlock_irqrestore(&nic_dev->channel_res_lock, flags);
           return 0;   // Always returns here
       }
       ...
   }

The device appears up but is permanently non-functional until module reload.

Should the error paths clear HINIC3_CHANGE_RES_INVALID under
channel_res_lock before returning?

> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
> index 9502293ff710..55b280888ad8 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h

[ ... ]

> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
> index 12eefabcf1db..3791b9bc865b 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h

[ ... ]

^ permalink raw reply

* Re: [PATCH V10 00/10] famfs: port into fuse
From: Joanne Koong @ 2026-04-10 19:44 UTC (permalink / raw)
  To: John Groves
  Cc: Bernd Schubert, John Groves, Miklos Szeredi, Dan Williams,
	Bernd Schubert, Alison Schofield, John Groves, Jonathan Corbet,
	Shuah Khan, Vishal Verma, Dave Jiang, Matthew Wilcox, Jan Kara,
	Alexander Viro, David Hildenbrand, Christian Brauner,
	Darrick J . Wong, Randy Dunlap, Jeff Layton, Amir Goldstein,
	Jonathan Cameron, Stefan Hajnoczi, Josef Bacik, Bagas Sanjaya,
	Chen Linxuan, James Morse, Fuad Tabba, Sean Christopherson,
	Shivank Garg, Ackerley Tng, Gregory Price, Aravind Ramesh,
	Ajay Joshi, venkataravis@micron.com, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
	linux-cxl@vger.kernel.org, linux-fsdevel@vger.kernel.org, djbw
In-Reply-To: <adlBcwJjLOQDAR65@groves.net>

On Fri, Apr 10, 2026 at 11:38 AM John Groves <John@groves.net> wrote:
>
> On 26/04/10 05:24PM, Bernd Schubert wrote:
> >
> >
> > On 4/10/26 16:46, John Groves wrote:
> > > On 26/04/06 10:43AM, Joanne Koong wrote:
> > >> On Tue, Mar 31, 2026 at 5:37 AM John Groves <john@jagalactic.com> wrote:
> > >>>
> > >>> From: John Groves <john@groves.net>
> > >>>
> > >>> NOTE: this series depends on the famfs dax series in Ira's for-7.1/dax-famfs
> > >>> branch [0]
> > >>>
> > >>> Description:
> > >>>
> > >>> This patch series introduces famfs into the fuse file system framework.
> > >>> Famfs depends on the bundled dax patch set.
> > >>>
> > >>> The famfs user space code can be found at [1].
> > >>>
> > >>> John Groves (10):
> > >>>   famfs_fuse: Update macro s/FUSE_IS_DAX/FUSE_IS_VIRTIO_DAX/
> > >>>   famfs_fuse: Basic fuse kernel ABI enablement for famfs
> > >>>   famfs_fuse: Plumb the GET_FMAP message/response
> > >>>   famfs_fuse: Create files with famfs fmaps
> > >>>   famfs_fuse: GET_DAXDEV message and daxdev_table
> > >>>   famfs_fuse: Plumb dax iomap and fuse read/write/mmap
> > >>>   famfs_fuse: Add holder_operations for dax notify_failure()
> > >>>   famfs_fuse: Add DAX address_space_operations with noop_dirty_folio
> > >>>   famfs_fuse: Add famfs fmap metadata documentation
> > >>>   famfs_fuse: Add documentation
> > >>>
> > >>>  Documentation/filesystems/famfs.rst |  142 ++++
> > >>>  Documentation/filesystems/index.rst |    1 +
> > >>>  MAINTAINERS                         |   10 +
> > >>>  fs/fuse/Kconfig                     |   13 +
> > >>>  fs/fuse/Makefile                    |    1 +
> > >>>  fs/fuse/dir.c                       |    2 +-
> > >>>  fs/fuse/famfs.c                     | 1180 +++++++++++++++++++++++++++
> > >>>  fs/fuse/famfs_kfmap.h               |  167 ++++
> > >>>  fs/fuse/file.c                      |   45 +-
> > >>>  fs/fuse/fuse_i.h                    |  116 ++-
> > >>>  fs/fuse/inode.c                     |   35 +-
> > >>>  fs/fuse/iomode.c                    |    2 +-
> > >>>  fs/namei.c                          |    1 +
> > >>>  include/uapi/linux/fuse.h           |   88 ++
> > >>>  14 files changed, 1790 insertions(+), 13 deletions(-)
> > >>>  create mode 100644 Documentation/filesystems/famfs.rst
> > >>>  create mode 100644 fs/fuse/famfs.c
> > >>>  create mode 100644 fs/fuse/famfs_kfmap.h
> > >>>
> > >>>
> > >>> base-commit: 2ae624d5a555d47a735fb3f4d850402859a4db77
> > >>> --
> > >>> 2.53.0
> > >>>
> > >>
> > >> Hi John,
> > >>
> > >> I’m curious to hear your thoughts on whether you think it makes sense
> > >> for the famfs-specific logic in this series to be moved to a bpf
> > >> program that goes through a generic fuse iomap dax layer.
> > >>
> > >> Based on [1], this gives feature-parity with the famfs logic in this
> > >> series. In my opinion, having famfs go through a generic fuse iomap
> > >> dax layer makes the fuse kernel code more extensible for future
> > >> servers that will also want to use dax iomap, and keeps the fuse code
> > >> cleaner by not having famfs-specific logic hardcoded in and having to
> > >> introduce new fuse uapis for something famfs-specific. In my
> > >> understanding of it, fuse is meant to be generic and it feels like
> > >> adding server-specific logic goes against that design philosophy and
> > >> sets a precedent for other servers wanting similar special-casing in
> > >> the future. I'd like to explore whether the bpf and generic fuse iomap
> > >> dax layer approach can preserve that philosophy while still giving
> > >> famfs the flexibility it needs.
> > >>
> > >> I think moving the famfs logic to bpf benefits famfs as well:
> > >> - Instead of needing to issue a FUSE_GET_FMAP request after a file is
> > >> opened, the server can directly populate the metadata map from
> > >> userspace with the mapping info when it processes the FUSE_OPEN
> > >> request, which gets rid of the roundtrip cost
> > >> - The server can dynamically update the metadata / bpf maps during
> > >> runtime from userspace if any mapping info needs to change
> > >> - Future code changes / updates for famfs are all server-side and can
> > >> be deployed immediately instead of needing to go through the upstream
> > >> kernel mailing list process
> > >> - Famfs updates / new releases can ship independently of kernel releases
> > >>
> > >> I'd appreciate the chance to discuss tradeoffs or if you'd rather
> > >> discuss this at the fuse BoF at lsf, that sounds great too.
> > >>
> > >> Thanks,
> > >> Joanne
> > >>
> > >
> > > Hi Joanne,
> > >
> > > I'm definitely up for discussing it, and talking before LSFMM would be
> > > good because then I'd have some time to think about before we discuss
> > > at LSFMM.
> > >
> > > I have not had a chance to really study this, in part since I've never even
> > > written a "hello world" BPF program.
> > >
> > > I'll ping off-list about times to talk.
> > >
> > > However...
> > >
> > > I would object vehemently to this sort of re-write prior to going upstream,
> > > as would users and vendors who need famfs now that the memory products it
> > > enables have started to ship.
> > >
> > > This work started over 3 years ago, initial patches over 2 years ago,
> > > community decision that it should go into fuse 2 years ago, first fuse
> > > patches a year ago.
> > >
> > > This implementation is pretty much exactly in line with expectation-setting
> > > starting two years ago. Famfs is a complicated orchestration between dax,
> > > fuse, ndctl (for daxctl), libfuse and the extensive famfs user space. Famfs
> > > has a fairly small kernel footprint, but its user space is much larger.
> > > This could set it back a year if we re-write now.


Hi John,

Thanks for your email. I totally understand where you're coming from -
having already spent a year reworking your initial patches to go
through fuse, the last thing needed is another change. Bernd brought
up the question of whether after merging this series in, it could then
be converted to bpf. If this is an option on the table, I'd say we
should just merge your series now and do any conversions later, but I
don't think this is possible. As I understand it, the policy is that
all kernel releases must be backwards-compatible, which means once
this series lands, the famfs code will live within fuse permanently.

I definitely do not want to see famfs set back by yet another year.
With the bpf approach, I don't think it requires another rewrite. I'm
not sure if you had the chance to look at my email from February [1],
but it contains the prototype code for the generic fuse iomap dax
layer with your famfs logic in this series moved to bpf. I think it's
pretty technically straightforward as it uses the famfs code logic
you've already written in this series and just moves it to bpf.

Overall, my intention with bringing this up is just to make sure we're
at least aware of this alternative before anything is merged and
permanent. If Miklos and you think we should land this series, then
I'm on board with that.

Thanks,
Joanne

[1] https://lore.kernel.org/linux-fsdevel/CAJnrk1YMqDKA5gDZasrxGjJtfdbhmjxX5uhUv=OSPyA=G5EE+Q@mail.gmail.com/

> > >
> > > Two things are true at once: I think this is a serious idea worth
> > > considering, and I think it's too late to make this sort of change before
> > > going upstream. Products need this enablement, and quite a long process has
> > > run in order to make it available in a timely fashion (which means soon
> > > now). I hope we can avoid making the perfect the enemy of the good.
> > >
> > > I believe the risk of merging famfs soon is quite low, because famfs will
> > > not affect anybody who doesn't use it. I hope we can run this discussion and
> > > analysis in parallel with merging the current implementation of famfs soon.
> >
> >
> > Hi John,
> >
> > one question, assuming most of these things can be done with eBPF, would
> > you convert to eBPF after the merge?
>
> Hi Bernd,
>
> Stipulating that I've never even written 'hello world' with BPF, if it's
> a nicer solution with no downsides we would migrate there. I don't know
> enough yet to put a time frame on it, but I'll certainly understand more
> by LSFMM. Will you be there?
>
> I'm hoping for a call with Joanne and Darrick in the next few days to get
> better educated on it.
>
> >
> > (I also need to find the time to review at least all of your libfuse
> > changes, I feel guilty that still haven't done it.).
> >
> >
> > Thanks,
> > Bernd
>
> The libfuse changes are pretty small now. Two new messages - GET_FMAP and
> GET_DAXDEV - plus a few more bits and bobs. If a future BPF migration took
> place, there is a chance that those message numbers could be retired and
> reused in the future.
>
> Watch this space...
>
> Thanks,
> John
>

^ permalink raw reply

* htmldocs: Documentation/scheduler/sched-arch.rst:108: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils]
From: kernel test robot @ 2026-04-10 19:32 UTC (permalink / raw)
  To: Shrikanth Hegde; +Cc: oe-kbuild-all, 0day robot, linux-doc

tree:   https://github.com/intel-lab-lkp/linux/commits/Shrikanth-Hegde/sched-debug-Remove-unused-schedstats/20260410-192441
head:   ab0b22aae278e62b46dd1b9bbc54f81d48eb7922
commit: d4c12cd241b1a892722ed07feb9114c23717202e sched/docs: Document cpu_preferred_mask and Preferred CPU concept
date:   8 hours ago
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
docutils: docutils (Docutils 0.21.2, Python 3.13.5, on linux)
reproduce: (https://download.01.org/0day-ci/archive/20260410/202604102139.zj1yx5qc-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604102139.zj1yx5qc-lkp@intel.com/

All warnings (new ones prefixed by >>):

   Checksumming on output with GSO
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [docutils]
   Documentation/scheduler/sched-arch.rst:107: ERROR: Unexpected indentation. [docutils]
>> Documentation/scheduler/sched-arch.rst:108: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils]
   Documentation/userspace-api/landlock:495: ./security/landlock/errata/abi-4.h:5: ERROR: Unexpected section title.


vim +108 Documentation/scheduler/sched-arch.rst

   102	
   103	Notes:
   104	1. This feature is available under CONFIG_PARAVIRT.
   105	2. preferred CPUs is same as online CPUs until STEAL_MONITOR is enabled.
   106	3. A task pinned, which can't be moved to preferred CPUs will continue
   107	   to run based on its affinity. But no load balancing happens
 > 108	4. If needed, steal time based governors/arch dependent method
   109	   could be used to cater to different types of cpu numbers.
   110	5. Decision to use/not use is driven by kernel. Hence it shouldn't
   111	   break user affinities. One of the main reason why CPU hotplug
   112	   or Isolated cpuset partitions was not a solution.
   113	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH V10 00/10] famfs: port into fuse
From: John Groves @ 2026-04-10 18:38 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Joanne Koong, John Groves, Miklos Szeredi, Dan Williams,
	Bernd Schubert, Alison Schofield, John Groves, Jonathan Corbet,
	Shuah Khan, Vishal Verma, Dave Jiang, Matthew Wilcox, Jan Kara,
	Alexander Viro, David Hildenbrand, Christian Brauner,
	Darrick J . Wong, Randy Dunlap, Jeff Layton, Amir Goldstein,
	Jonathan Cameron, Stefan Hajnoczi, Josef Bacik, Bagas Sanjaya,
	Chen Linxuan, James Morse, Fuad Tabba, Sean Christopherson,
	Shivank Garg, Ackerley Tng, Gregory Price, Aravind Ramesh,
	Ajay Joshi, venkataravis@micron.com, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
	linux-cxl@vger.kernel.org, linux-fsdevel@vger.kernel.org, djbw
In-Reply-To: <38744253-efa3-41c5-a491-b177a4a4c835@bsbernd.com>

On 26/04/10 05:24PM, Bernd Schubert wrote:
> 
> 
> On 4/10/26 16:46, John Groves wrote:
> > On 26/04/06 10:43AM, Joanne Koong wrote:
> >> On Tue, Mar 31, 2026 at 5:37 AM John Groves <john@jagalactic.com> wrote:
> >>>
> >>> From: John Groves <john@groves.net>
> >>>
> >>> NOTE: this series depends on the famfs dax series in Ira's for-7.1/dax-famfs
> >>> branch [0]
> >>>
> >>> Changes v9 -> v10
> >>> - Rebased to Ira's for-7.1/dax-famfs branch [0], which contains the required
> >>>   dax patches
> >>> - Add parentheses to FUSE_IS_VIRTIO_DAX() macro, in case something bad is
> >>>   passed in as fuse_inode (thanks Jonathan's AI)
> >>>
> >>> Description:
> >>>
> >>> This patch series introduces famfs into the fuse file system framework.
> >>> Famfs depends on the bundled dax patch set.
> >>>
> >>> The famfs user space code can be found at [1].
> >>>
> >>> Fuse Overview:
> >>>
> >>> Famfs started as a standalone file system, but this series is intended to
> >>> permanently supersede that implementation. At a high level, famfs adds
> >>> two new fuse server messages:
> >>>
> >>> GET_FMAP   - Retrieves a famfs fmap (the file-to-dax map for a famfs
> >>>              file)
> >>> GET_DAXDEV - Retrieves the details of a particular daxdev that was
> >>>              referenced by an fmap
> >>>
> >>> Famfs Overview
> >>>
> >>> Famfs exposes shared memory as a file system. Famfs consumes shared
> >>> memory from dax devices, and provides memory-mappable files that map
> >>> directly to the memory - no page cache involvement. Famfs differs from
> >>> conventional file systems in fs-dax mode, in that it handles in-memory
> >>> metadata in a sharable way (which begins with never caching dirty shared
> >>> metadata).
> >>>
> >>> Famfs started as a standalone file system [2,3], but the consensus at
> >>> LSFMM was that it should be ported into fuse [4,5].
> >>>
> >>> The key performance requirement is that famfs must resolve mapping faults
> >>> without upcalls. This is achieved by fully caching the file-to-devdax
> >>> metadata for all active files. This is done via two fuse client/server
> >>> message/response pairs: GET_FMAP and GET_DAXDEV.
> >>>
> >>> Famfs remains the first fs-dax file system that is backed by devdax
> >>> rather than pmem in fs-dax mode (hence the need for the new dax mode).
> >>>
> >>> Notes
> >>>
> >>> - When a file is opened in a famfs mount, the OPEN is followed by a
> >>>   GET_FMAP message and response. The "fmap" is the full file-to-dax
> >>>   mapping, allowing the fuse/famfs kernel code to handle
> >>>   read/write/fault without any upcalls.
> >>>
> >>> - After each GET_FMAP, the fmap is checked for extents that reference
> >>>   previously-unknown daxdevs. Each such occurrence is handled with a
> >>>   GET_DAXDEV message and response.
> >>>
> >>> - Daxdevs are stored in a table (which might become an xarray at some
> >>>   point). When entries are added to the table, we acquire exclusive
> >>>   access to the daxdev via the fs_dax_get() call (modeled after how
> >>>   fs-dax handles this with pmem devices). Famfs provides
> >>>   holder_operations to devdax, providing a notification path in the
> >>>   event of memory errors or forced reconfiguration.
> >>>
> >>> - If devdax notifies famfs of memory errors on a dax device, famfs
> >>>   currently blocks all subsequent accesses to data on that device. The
> >>>   recovery is to re-initialize the memory and file system. Famfs is
> >>>   memory, not storage...
> >>>
> >>> - Because famfs uses backing (devdax) devices, only privileged mounts are
> >>>   supported (i.e. the fuse server requires CAP_SYS_RAWIO).
> >>>
> >>> - The famfs kernel code never accesses the memory directly - it only
> >>>   facilitates read, write and mmap on behalf of user processes, using
> >>>   fmap metadata provided by its privileged fuse server. As such, the
> >>>   RAS of the shared memory affects applications, but not the kernel.
> >>>
> >>> - Famfs has backing device(s), but they are devdax (char) rather than
> >>>   block. Right now there is no way to tell the vfs layer that famfs has a
> >>>   char backing device (unless we say it's block, but it's not). Currently
> >>>   we use the standard anonymous fuse fs_type - but I'm not sure that's
> >>>   ultimately optimal (thoughts?)
> >>>
> >>> Changes v8 -> v9
> >>> - Kconfig: fs/fuse/Kconfig:CONFIG_FUSE_FAMFS_DAX now depends on the
> >>>   new CONFIG_DEV_DAX_FSDEV (from drivers/dax/Kconfig) rather than
> >>>   just CONFIG_DEV_DAX and CONFIG_FS_DAX. (CONFIG_FUSE_FAMFS_DAX
> >>>   depends on those...)
> >>>
> >>> Changes v7 -> v8
> >>> - Moved to inline __free declaration in fuse_get_fmap() and
> >>>   famfs_fuse_meta_alloc(), famfs_teardown()
> >>> - Adopted FIELD_PREP() macro rather than manual bitfield manipulation
> >>> - Minor doc edits
> >>> - I dropped adding magic numbers to include/uapi/linux/magic.h. That
> >>>   can be done later if appropriate
> >>>
> >>> Changes v6 -> v7
> >>> - Fixed a regression in famfs_interleave_fileofs_to_daxofs() that
> >>>   was reported by Intel's kernel test robot
> >>> - Added a check in __fsdev_dax_direct_access() for negative return
> >>>   from pgoff_to_phys(), which would indicate an out-of-range offset
> >>> - Fixed a bug in __famfs_meta_free(), where not all interleaved
> >>>   extents were freed
> >>> - Added chunksize alignment checks in famfs_fuse_meta_alloc() and
> >>>   famfs_interleave_fileofs_to_daxofs() as interleaved chunks must
> >>>   be PTE or PMD aligned
> >>> - Simplified famfs_file_init_dax() a bit
> >>> - Re-ran CM's kernel code review prompts on the entire series and
> >>>   fixed several minor issues
> >>>
> >>> Changes v4 -> v5 -> v6
> >>> - None. Re-sending due to technical difficulties
> >>>
> >>> Changes v3 [9] -> v4
> >>> - The patch "dax: prevent driver unbind while filesystem holds device"
> >>>   has been dropped. Dan Williams indicated that the favored behavior is
> >>>   for a file system to stop working if an underlying driver is unbound,
> >>>   rather than preventing the unbind.
> >>> - The patch "famfs_fuse: Famfs mount opt: -o shadow=<shadowpath>" has
> >>>   been dropped. Found a way for the famfs user space to do without the
> >>>   -o opt (via getxattr).
> >>> - Squashed the fs/fuse/Kconfig patch into the first subsequent patch
> >>>   that needed the change
> >>>   ("famfs_fuse: Basic fuse kernel ABI enablement for famfs")
> >>> - Many review comments addressed.
> >>> - Addressed minor kerneldoc infractions reported by test robot.
> >>>
> >>> Changes v2 [7] -> v3
> >>> - Dax: Completely new fsdev driver (drivers/dax/fsdev.c) replaces the
> >>>   dev_dax_iomap modifications to bus.c/device.c. Devdax devices can now
> >>>   be switched among 'devdax', 'famfs' and 'system-ram' modes via daxctl
> >>>   or sysfs.
> >>> - Dax: fsdev uses MEMORY_DEVICE_FS_DAX type and leaves folios at order-0
> >>>   (no vmemmap_shift), allowing fs-dax to manage folio lifecycles
> >>>   dynamically like pmem does.
> >>> - Dax: The "poisoned page" problem is properly fixed via
> >>>   fsdev_clear_folio_state(), which clears stale mapping/compound state
> >>>   when fsdev binds. The temporary WARN_ON_ONCE workaround in fs/dax.c
> >>>   has been removed.
> >>> - Dax: Added dax_set_ops() so fsdev can set dax_operations at bind time
> >>>   (and clear them on unbind), since the dax_device is created before we
> >>>   know which driver will bind.
> >>> - Dax: Added custom bind/unbind sysfs handlers; unbind return -EBUSY if a
> >>>   filesystem holds the device, preventing unbind while famfs is mounted.
> >>> - Fuse: Famfs mounts now require that the fuse server/daemon has
> >>>   CAP_SYS_RAWIO because they expose raw memory devices.
> >>> - Fuse: Added DAX address_space_operations with noop_dirty_folio since
> >>>   famfs is memory-backed with no writeback required.
> >>> - Rebased to latest kernels, fully compatible with Alistair Popple
> >>>   et. al's recent dax refactoring.
> >>> - Ran this series through Chris Mason's code review AI prompts to check
> >>>   for issues - several subtle problems found and fixed.
> >>> - Dropped RFC status - this version is intended to be mergeable.
> >>>
> >>> Changes v1 [8] -> v2:
> >>>
> >>> - The GET_FMAP message/response has been moved from LOOKUP to OPEN, as
> >>>   was the pretty much unanimous consensus.
> >>> - Made the response payload to GET_FMAP variable sized (patch 12)
> >>> - Dodgy kerneldoc comments cleaned up or removed.
> >>> - Fixed memory leak of fc->shadow in patch 11 (thanks Joanne)
> >>> - Dropped many pr_debug and pr_notice calls
> >>>
> >>>
> >>> References
> >>>
> >>> [0] - https://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm.git/
> >>> [1] - https://famfs.org (famfs user space)
> >>> [2] - https://lore.kernel.org/linux-cxl/cover.1708709155.git.john@groves.net/
> >>> [3] - https://lore.kernel.org/linux-cxl/cover.1714409084.git.john@groves.net/
> >>> [4] - https://lwn.net/Articles/983105/ (lsfmm 2024)
> >>> [5] - https://lwn.net/Articles/1020170/ (lsfmm 2025)
> >>> [6] - https://lore.kernel.org/linux-cxl/cover.8068ad144a7eea4a813670301f4d2a86a8e68ec4.1740713401.git-series.apopple@nvidia.com/
> >>> [7] - https://lore.kernel.org/linux-fsdevel/20250703185032.46568-1-john@groves.net/ (famfs fuse v2)
> >>> [8] - https://lore.kernel.org/linux-fsdevel/20250421013346.32530-1-john@groves.net/ (famfs fuse v1)
> >>> [9] - https://lore.kernel.org/linux-fsdevel/20260107153244.64703-1-john@groves.net/T/#mb2c868801be16eca82dab239a1d201628534aea7 (famfs fuse v3)
> >>>
> >>>
> >>> John Groves (10):
> >>>   famfs_fuse: Update macro s/FUSE_IS_DAX/FUSE_IS_VIRTIO_DAX/
> >>>   famfs_fuse: Basic fuse kernel ABI enablement for famfs
> >>>   famfs_fuse: Plumb the GET_FMAP message/response
> >>>   famfs_fuse: Create files with famfs fmaps
> >>>   famfs_fuse: GET_DAXDEV message and daxdev_table
> >>>   famfs_fuse: Plumb dax iomap and fuse read/write/mmap
> >>>   famfs_fuse: Add holder_operations for dax notify_failure()
> >>>   famfs_fuse: Add DAX address_space_operations with noop_dirty_folio
> >>>   famfs_fuse: Add famfs fmap metadata documentation
> >>>   famfs_fuse: Add documentation
> >>>
> >>>  Documentation/filesystems/famfs.rst |  142 ++++
> >>>  Documentation/filesystems/index.rst |    1 +
> >>>  MAINTAINERS                         |   10 +
> >>>  fs/fuse/Kconfig                     |   13 +
> >>>  fs/fuse/Makefile                    |    1 +
> >>>  fs/fuse/dir.c                       |    2 +-
> >>>  fs/fuse/famfs.c                     | 1180 +++++++++++++++++++++++++++
> >>>  fs/fuse/famfs_kfmap.h               |  167 ++++
> >>>  fs/fuse/file.c                      |   45 +-
> >>>  fs/fuse/fuse_i.h                    |  116 ++-
> >>>  fs/fuse/inode.c                     |   35 +-
> >>>  fs/fuse/iomode.c                    |    2 +-
> >>>  fs/namei.c                          |    1 +
> >>>  include/uapi/linux/fuse.h           |   88 ++
> >>>  14 files changed, 1790 insertions(+), 13 deletions(-)
> >>>  create mode 100644 Documentation/filesystems/famfs.rst
> >>>  create mode 100644 fs/fuse/famfs.c
> >>>  create mode 100644 fs/fuse/famfs_kfmap.h
> >>>
> >>>
> >>> base-commit: 2ae624d5a555d47a735fb3f4d850402859a4db77
> >>> --
> >>> 2.53.0
> >>>
> >>
> >> Hi John,
> >>
> >> I’m curious to hear your thoughts on whether you think it makes sense
> >> for the famfs-specific logic in this series to be moved to a bpf
> >> program that goes through a generic fuse iomap dax layer.
> >>
> >> Based on [1], this gives feature-parity with the famfs logic in this
> >> series. In my opinion, having famfs go through a generic fuse iomap
> >> dax layer makes the fuse kernel code more extensible for future
> >> servers that will also want to use dax iomap, and keeps the fuse code
> >> cleaner by not having famfs-specific logic hardcoded in and having to
> >> introduce new fuse uapis for something famfs-specific. In my
> >> understanding of it, fuse is meant to be generic and it feels like
> >> adding server-specific logic goes against that design philosophy and
> >> sets a precedent for other servers wanting similar special-casing in
> >> the future. I'd like to explore whether the bpf and generic fuse iomap
> >> dax layer approach can preserve that philosophy while still giving
> >> famfs the flexibility it needs.
> >>
> >> I think moving the famfs logic to bpf benefits famfs as well:
> >> - Instead of needing to issue a FUSE_GET_FMAP request after a file is
> >> opened, the server can directly populate the metadata map from
> >> userspace with the mapping info when it processes the FUSE_OPEN
> >> request, which gets rid of the roundtrip cost
> >> - The server can dynamically update the metadata / bpf maps during
> >> runtime from userspace if any mapping info needs to change
> >> - Future code changes / updates for famfs are all server-side and can
> >> be deployed immediately instead of needing to go through the upstream
> >> kernel mailing list process
> >> - Famfs updates / new releases can ship independently of kernel releases
> >>
> >> I'd appreciate the chance to discuss tradeoffs or if you'd rather
> >> discuss this at the fuse BoF at lsf, that sounds great too.
> >>
> >> Thanks,
> >> Joanne
> >>
> > 
> > Hi Joanne,
> > 
> > I'm definitely up for discussing it, and talking before LSFMM would be
> > good because then I'd have some time to think about before we discuss
> > at LSFMM.
> > 
> > I have not had a chance to really study this, in part since I've never even
> > written a "hello world" BPF program.
> > 
> > I'll ping off-list about times to talk.
> > 
> > However... 
> > 
> > I would object vehemently to this sort of re-write prior to going upstream, 
> > as would users and vendors who need famfs now that the memory products it 
> > enables have started to ship.
> > 
> > This work started over 3 years ago, initial patches over 2 years ago, 
> > community decision that it should go into fuse 2 years ago, first fuse 
> > patches a year ago.
> > 
> > This implementation is pretty much exactly in line with expectation-setting
> > starting two years ago. Famfs is a complicated orchestration between dax, 
> > fuse, ndctl (for daxctl), libfuse and the extensive famfs user space. Famfs 
> > has a fairly small kernel footprint, but its user space is much larger.
> > This could set it back a year if we re-write now.
> > 
> > Two things are true at once: I think this is a serious idea worth 
> > considering, and I think it's too late to make this sort of change before
> > going upstream. Products need this enablement, and quite a long process has
> > run in order to make it available in a timely fashion (which means soon 
> > now). I hope we can avoid making the perfect the enemy of the good.
> > 
> > I believe the risk of merging famfs soon is quite low, because famfs will 
> > not affect anybody who doesn't use it. I hope we can run this discussion and
> > analysis in parallel with merging the current implementation of famfs soon.
> 
> 
> Hi John,
> 
> one question, assuming most of these things can be done with eBPF, would
> you convert to eBPF after the merge?

Hi Bernd,

Stipulating that I've never even written 'hello world' with BPF, if it's 
a nicer solution with no downsides we would migrate there. I don't know
enough yet to put a time frame on it, but I'll certainly understand more
by LSFMM. Will you be there?

I'm hoping for a call with Joanne and Darrick in the next few days to get
better educated on it.

> 
> (I also need to find the time to review at least all of your libfuse
> changes, I feel guilty that still haven't done it.).
> 
> 
> Thanks,
> Bernd

The libfuse changes are pretty small now. Two new messages - GET_FMAP and 
GET_DAXDEV - plus a few more bits and bobs. If a future BPF migration took 
place, there is a chance that those message numbers could be retired and 
reused in the future.

Watch this space...

Thanks,
John


^ permalink raw reply

* Re: [PATCH 3/6] hugetlb: make hugetlb_fault_mutex_hash() take PAGE_SIZE index
From: jane.chu @ 2026-04-10 17:51 UTC (permalink / raw)
  To: Usama Arif
  Cc: akpm, david, muchun.song, osalvador, lorenzo.stoakes,
	Liam.Howlett, vbabka, rppt, surenb, mhocko, corbet, skhan, hughd,
	baolin.wang, peterx, linux-mm, linux-doc, linux-kernel
In-Reply-To: <20260410112433.3248586-1-usama.arif@linux.dev>



On 4/10/2026 4:24 AM, Usama Arif wrote:
> On Thu,  9 Apr 2026 17:41:54 -0600 Jane Chu <jane.chu@oracle.com> wrote:
> 
[..]

>> @@ -5664,6 +5665,10 @@ static inline vm_fault_t hugetlb_handle_userfault(struct vm_fault *vmf,
>>   						  unsigned long reason)
>>   {
>>   	u32 hash;
>> +	pgoff_t index;
>> +
>> +	index = linear_page_index((const struct vm_area_struct *)vmf, vmf->address);
> 
> This is supposed to be linear_page_index(vmf->vma, vmf->address), right?
>   
> 

Indeed, sysbot also complained.
Will fix soon.

Thanks a lot!
-jane

^ permalink raw reply

* Re: [PATCH 5/5] types: Add standard __ob_trap and __ob_wrap scalar types
From: Justin Stitt @ 2026-04-10 17:48 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Kees Cook, Linus Torvalds, Miguel Ojeda, Nathan Chancellor,
	Andrew Morton, Andy Shevchenko, Arnd Bergmann, Mark Rutland,
	Matthew Wilcox (Oracle), Suren Baghdasaryan, Thomas Gleixner,
	Finn Thain, Geert Uytterhoeven, Thomas Weißschuh, llvm,
	Marco Elver, Jonathan Corbet, Nicolas Schier, Greg Kroah-Hartman,
	linux-kernel, kasan-dev, linux-hardening, linux-doc, linux-kbuild
In-Reply-To: <20260402053840.GD3254421@noisy.programming.kicks-ass.net>

Hi,

On Wed, Apr 1, 2026 at 10:38 PM Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Wed, Apr 01, 2026 at 01:52:26PM -0700, Kees Cook wrote:
>
> > (Though I would note that GCC does _not_ refuse the jump when there is a
> > cleanup; it only see the other two uninitialized values.)
>
> Yeah.. I know, but since we also build with clang, any such issue will
> get discovered.
>
> > So that makes it not totally broken, but it does make it a bit fragile.
>
> Right.
>
> > Another concern I have is dealing with older compilers and how to
> > "hide" the label and its code. e.g. if I remove the "goto" from above:
> >
> > ../drivers/misc/lkdtm/bugs.c:1060:1: warning: label 'weird' defined but not used [-Wunused-label]
> >  1060 | weird:
> >       | ^~~~~
> >
> > Oddly, the unreachable code isn't a problem, so we could just wrap the
> > label is some macro like:
> >
> > #define force_label(x) if (0) goto x; x
> >
> > force_label(weird):
> >         pr_info("value: %lu\n", value);
> >         pr_info("outcome: %zu\n", outcome);
> >
>
> __maybe_unused also works on labels. Like:
>
> __overflow: __maybe_unused
>         dead-code-here;
>
>

Completing the loop: Here's the Clang RFC discussing obt label handler
design [1]

[1]: https://discourse.llvm.org/t/rfc-linux-kernel-discusses-overflowbehaviortypes/90486

Jusin

^ permalink raw reply

* Re: [PATCH v3 09/11] dt-bindings: input: Document hid-over-spi DT schema
From: Conor Dooley @ 2026-04-10 17:35 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Jingyuan Liang, Jiri Kosina, Benjamin Tissoires, Jonathan Corbet,
	Mark Brown, Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-input,
	linux-doc, linux-kernel, linux-spi, linux-trace-kernel,
	devicetree, hbarnor, tfiga, Dmitry Antipov, Jarrett Schultz
In-Reply-To: <adfdkwq_bF9dirAq@google.com>

[-- Attachment #1: Type: text/plain, Size: 6021 bytes --]

On Thu, Apr 09, 2026 at 10:16:46AM -0700, Dmitry Torokhov wrote:
> On Thu, Apr 09, 2026 at 05:02:11PM +0100, Conor Dooley wrote:
> > On Thu, Apr 02, 2026 at 01:59:46AM +0000, Jingyuan Liang wrote:
> > > Documentation describes the required and optional properties for
> > > implementing Device Tree for a Microsoft G6 Touch Digitizer that
> > > supports HID over SPI Protocol 1.0 specification.
> > > 
> > > The properties are common to HID over SPI.
> > > 
> > > Signed-off-by: Dmitry Antipov <dmanti@microsoft.com>
> > > Signed-off-by: Jarrett Schultz <jaschultz@microsoft.com>
> > > Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
> > > ---
> > >  .../devicetree/bindings/input/hid-over-spi.yaml    | 126 +++++++++++++++++++++
> > >  1 file changed, 126 insertions(+)
> > > 
> > > diff --git a/Documentation/devicetree/bindings/input/hid-over-spi.yaml b/Documentation/devicetree/bindings/input/hid-over-spi.yaml
> > > new file mode 100644
> > > index 000000000000..d1b0a2e26c32
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/input/hid-over-spi.yaml
> > > @@ -0,0 +1,126 @@
> > > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > > +%YAML 1.2
> > > +---
> > > +$id: http://devicetree.org/schemas/input/hid-over-spi.yaml#
> > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > +
> > > +title: HID over SPI Devices
> > > +
> > > +maintainers:
> > > +  - Benjamin Tissoires <benjamin.tissoires@redhat.com>
> > > +  - Jiri Kosina <jkosina@suse.cz>
> > 
> > Why them and not you, the developers of the series?
> > 
> > > +
> > > +description: |+
> > > +  HID over SPI provides support for various Human Interface Devices over the
> > > +  SPI bus. These devices can be for example touchpads, keyboards, touch screens
> > > +  or sensors.
> > > +
> > > +  The specification has been written by Microsoft and is currently available
> > > +  here: https://www.microsoft.com/en-us/download/details.aspx?id=103325
> > > +
> > > +  If this binding is used, the kernel module spi-hid will handle the
> > > +  communication with the device and the generic hid core layer will handle the
> > > +  protocol.
> > 
> > This is not relevant to the binding, please remove it.
> > 
> > > +
> > > +allOf:
> > > +  - $ref: /schemas/input/touchscreen/touchscreen.yaml#
> > > +
> > > +properties:
> > > +  compatible:
> > > +    oneOf:
> > > +      - items:
> > > +          - enum:
> > > +              - microsoft,g6-touch-digitizer
> > > +          - const: hid-over-spi
> > > +      - description: Just "hid-over-spi" alone is allowed, but not recommended.
> > > +        const: hid-over-spi
> > 
> > Why is it allowed but not recommended? Seems to me like we should
> > require device-specific compatibles.
> 
> Why would we want to change the driver code to add a new compatible each
> time a vendor decides to create a chip that is fully hid-spi-protocol
> compliant? Or is the plan to still allow "hid-over-spi" fallback but
> require device-specific compatible that will be ignored unless there is
> device-specific quirk needed?

This has nothing to do with the driver, just the oddity of having a
comment saying that not having a device specific compatible was
permitted by not recommended in a binding. Requiring device-specific
compatibles is the norm after all and a comment like this makes draws
more attention to the fact that this is abnormal. Regardless of what the
driver does, device-specific compatibles should be required.

> > > +
> > > +  reg:
> > > +    maxItems: 1
> > > +
> > > +  interrupts:
> > > +    maxItems: 1
> > > +
> > > +  reset-gpios:
> > > +    maxItems: 1
> > > +    description:
> > > +      GPIO specifier for the digitizer's reset pin (active low). The line must
> > > +      be flagged with GPIO_ACTIVE_LOW.
> > > +
> > > +  vdd-supply:
> > > +    description:
> > > +      Regulator for the VDD supply voltage.
> > > +
> > > +  input-report-header-address:
> > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > > +    minimum: 0
> > > +    maximum: 0xffffff
> > > +    description:
> > > +      A value to be included in the Read Approval packet, listing an address of
> > > +      the input report header to be put on the SPI bus. This address has 24
> > > +      bits.
> > > +
> > > +  input-report-body-address:
> > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > > +    minimum: 0
> > > +    maximum: 0xffffff
> > > +    description:
> > > +      A value to be included in the Read Approval packet, listing an address of
> > > +      the input report body to be put on the SPI bus. This address has 24 bits.
> > > +
> > > +  output-report-address:
> > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > > +    minimum: 0
> > > +    maximum: 0xffffff
> > > +    description:
> > > +      A value to be included in the Output Report sent by the host, listing an
> > > +      address where the output report on the SPI bus is to be written to. This
> > > +      address has 24 bits.
> > > +
> > > +  read-opcode:
> > > +    $ref: /schemas/types.yaml#/definitions/uint8
> > > +    description:
> > > +      Value to be used in Read Approval packets. 1 byte.
> > > +
> > > +  write-opcode:
> > > +    $ref: /schemas/types.yaml#/definitions/uint8
> > > +    description:
> > > +      Value to be used in Write Approval packets. 1 byte.
> > 
> > Why can none of these things be determined from the device's compatible?
> > On the surface, they like the kinds of things that could/should be.
> 
> Why would we want to keep tables of these values in the kernel and again
> have to update the driver for each new chip?

That's pretty normal though innit? It's what match data does.
If someone wants to have properties that communicate data that
can be determined from the compatible, they need to provide
justification why it is being done.

> It also probably firmware-dependent.


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH v2] Documentation: Refactored watchdog old doc
From: Guenter Roeck @ 2026-04-10 17:27 UTC (permalink / raw)
  To: Jonathan Corbet
  Cc: Sunny Patel, Wim Van Sebroeck, Shuah Khan, linux-watchdog,
	linux-doc, linux-kernel
In-Reply-To: <87ik9y229e.fsf@trenco.lwn.net>

On Fri, Apr 10, 2026 at 10:45:01AM -0600, Jonathan Corbet wrote:
> Guenter Roeck <linux@roeck-us.net> writes:
> 
> > On Fri, Apr 10, 2026 at 12:58:11PM +0530, Sunny Patel wrote:
> >> Good Point. So again revisited the watchdog core
> >> api and list out the deprecated one and marked
> >> as deprecated in doc and also mentioned it just
> >> for legacy driver and not for newer one.
> >> 
> >> As someof the legacy driver still have reference 
> >> to old api so just marked as deprecated in doc.
> >> 
> >> Also checked with other watchdog related api
> >> which are deprecated in driver but still present 
> >> in doc but didn't find any.
> >> 
> >> ---
> >
> > The above would show up as commit message, there is no change log, and
> > this e-mail was sent as response to v1. And I can see that without even
> > looking at the patch itself.
> >
> > That makes me wonder what Documentation/process/submitting-patches.rst
> > is useful for. No one seems to bother reading it. We might as well
> > just remove it.
> 
> It's good to point people at.
> 
> I do think it needs a serious rewrite to, among other things, turn it
> into less of an intimidating tome.  On my list of things to do.  Now if
> I could only buy a larger drive to hold that whole list...
> 
Let's have some fun:

1st AI prompt:

You are an experienced Linux kernel developer and an AI prompt expert.
Read linux/Documentation/process/submitting-patches.rst and generate an
AI prompt file named review.md which can be used by an AI agent to review
a patch submission and determine if it follows the guidance in
submitting-patches.rst.

2nd AI prompt:

Using @review.md, review the patch in index.html, which targets the
repository in the linux/ directory. Provide review output in review.log.

Result is below (raw file as generated by the AI agent, not reformatted).

Guenter

---
Status: NEEDS WORK

Summary:
The patch updates the watchdog API documentation by marking deprecated ioctls and adding descriptions for features like MAGICCLOSE and PRETIMEOUT. While the technical content is useful, the patch requires revisions to the commit 
message and documentation text to fix typos, grammatical errors, and formatting issues.

Detailed Findings:
1. Commit Message & Description:
   - The description starts with conversational filler ("Good Point. So again revisited...") which should be removed.
   - The message is not written in the imperative mood (e.g., use "Mark WDIOC_GETTEMP as deprecated" instead of "Revisited... and marked...").
   - Typos in the commit message: "someof" (some of), "Reierate" (Reiterate).
   - The subject line "Documentation: Refactored watchdog old doc" should use the imperative mood: "Documentation: Refactor watchdog documentation".

2. Technical Content & Style:
   - Several typos in the documentation text:
     - "reboot tiemout" -> "reboot timeout" (Multiple occurrences).
     - "characted 'V'" -> "character 'V'".
     - "small number if legacy drivers" -> "small number of legacy drivers".
   - Grammatical/Formatting issues:
     - Missing space: "WDIOS_DISABLECARD,this" -> "WDIOS_DISABLECARD, this".
     - Inconsistent capitalization: "USE WDIOC_SETPRETIMEOUT" should be "Use WDIOC_SETPRETIMEOUT".
     - "New Drivers" should be "New drivers".
   - The patch results in "No newline at end of file" for Documentation/watchdog/watchdog-api.rst.

3. Canonical Patch Format:
   - The patch follows the basic canonical format (Subject, From, Sign-off, separators, changelog, and diffstat are all present).

Actionable Advice:
1. Rewrite the commit message to be professional and in the imperative mood. Remove any conversational references to previous discussions.
2. Perform a spell-check on the documentation changes. Specifically, fix "tiemout", "characted", and "if" (where "of" was intended).
3. Ensure proper spacing after punctuation (e.g., after the comma in the WDIOS_DISABLECARD description).
4. Add a newline at the end of Documentation/watchdog/watchdog-api.rst.
5. Use consistent sentence-case for instructions (e.g., "Use" instead of "USE").


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox