Linux-mediatek Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next v6 07/12] net: airoha: Move {cpu,fwd}_tx_packets in airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-11 10:49 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lorenzo Bianconi
  Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
	linux-mediatek, netdev, devicetree
In-Reply-To: <20260511-airoha-eth-multi-serdes-v6-0-c899462c4f75@kernel.org>

Since now multiple net_devices connected to different QDMA blocks can
share the same GDM port, cpu_tx_packets and fwd_tx_packets fields can
be overwritten with the value from a different QDMA block. In order to
fix the issue move cpu_tx_packets and fwd_tx_packets fields from
airoha_gdm_port struct to airoha_gdm_dev one.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c | 16 +++++++---------
 drivers/net/ethernet/airoha/airoha_eth.h |  7 +++----
 2 files changed, 10 insertions(+), 13 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 033541df2f83..f0d5f28dd731 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2312,19 +2312,17 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
 					struct tc_ets_qopt_offload *opt)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
+	struct airoha_qdma *qdma = dev->qdma;
 
-	u64 cpu_tx_packets = airoha_qdma_rr(dev->qdma,
-					    REG_CNTR_VAL(channel << 1));
-	u64 fwd_tx_packets = airoha_qdma_rr(dev->qdma,
+	u64 cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
+	u64 fwd_tx_packets = airoha_qdma_rr(qdma,
 					    REG_CNTR_VAL((channel << 1) + 1));
-	u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
-			 (fwd_tx_packets - port->fwd_tx_packets);
+	u64 tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
+			 (fwd_tx_packets - dev->fwd_tx_packets);
 
 	_bstats_update(opt->stats.bstats, 0, tx_packets);
-
-	port->cpu_tx_packets = cpu_tx_packets;
-	port->fwd_tx_packets = fwd_tx_packets;
+	dev->cpu_tx_packets = cpu_tx_packets;
+	dev->fwd_tx_packets = fwd_tx_packets;
 
 	return 0;
 }
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index a308a770116b..fbb50dc73af8 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -544,6 +544,9 @@ struct airoha_gdm_dev {
 	struct airoha_eth *eth;
 
 	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
+	/* qos stats counters */
+	u64 cpu_tx_packets;
+	u64 fwd_tx_packets;
 };
 
 struct airoha_gdm_port {
@@ -553,10 +556,6 @@ struct airoha_gdm_port {
 
 	struct airoha_hw_stats stats;
 
-	/* qos stats counters */
-	u64 cpu_tx_packets;
-	u64 fwd_tx_packets;
-
 	struct metadata_dst *dsa_meta[AIROHA_MAX_DSA_PORTS];
 };
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v6 06/12] net: airoha: Move qos_sq_bmap in airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-11 10:49 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lorenzo Bianconi
  Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
	linux-mediatek, netdev, devicetree
In-Reply-To: <20260511-airoha-eth-multi-serdes-v6-0-c899462c4f75@kernel.org>

Since now multiple net_devices connected to different QDMA blocks can
share the same GDM port, qos_sq_bmap field can be overwritten with the
configuration obtained from a net_device connected to a different QDMA
block. In order to fix the issue move qos_sq_bmap field from
airoha_gdm_port struct to airoha_gdm_dev one.
Add qos_channel_map bitmap in airoha_qdma struct to track if a shared
QDMA channel is already in use by another net_device.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c | 59 ++++++++++++++++++++------------
 drivers/net/ethernet/airoha/airoha_eth.h |  6 ++--
 2 files changed, 42 insertions(+), 23 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 3510b088ad15..033541df2f83 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -2599,44 +2599,61 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
 	return 0;
 }
 
-static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
-					  struct tc_htb_qopt_offload *opt)
+static int airoha_tc_htb_modify_queue(struct net_device *dev,
+				      struct tc_htb_qopt_offload *opt)
 {
 	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
 	u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
-	int err, num_tx_queues = netdev->real_num_tx_queues;
-	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
 
 	if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
 		NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
 		return -EINVAL;
 	}
 
-	err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
-					    opt->quantum);
+	return airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
+}
+
+static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
+					  struct tc_htb_qopt_offload *opt)
+{
+	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
+	int err, num_tx_queues = netdev->real_num_tx_queues;
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_qdma *qdma = dev->qdma;
+
+	/* Here we need to check the requested QDMA channel is not already
+	 * in use by another net_device running on the same QDMA block.
+	 */
+	if (test_and_set_bit(channel, qdma->qos_channel_map)) {
+		NL_SET_ERR_MSG_MOD(opt->extack,
+				   "qdma qos channel already in use");
+		return -EBUSY;
+	}
+
+	err = airoha_tc_htb_modify_queue(netdev, opt);
 	if (err) {
 		NL_SET_ERR_MSG_MOD(opt->extack,
 				   "failed configuring htb offload");
-		return err;
+		goto error;
 	}
 
-	if (opt->command == TC_HTB_NODE_MODIFY)
-		return 0;
-
 	err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
 	if (err) {
 		airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
 					      opt->quantum);
 		NL_SET_ERR_MSG_MOD(opt->extack,
 				   "failed setting real_num_tx_queues");
-		return err;
+		goto error;
 	}
 
-	set_bit(channel, port->qos_sq_bmap);
+	set_bit(channel, dev->qos_sq_bmap);
 	opt->qid = AIROHA_NUM_TX_RING + channel;
 
 	return 0;
+error:
+	clear_bit(channel, qdma->qos_channel_map);
+
+	return err;
 }
 
 static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
@@ -2817,11 +2834,13 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
 static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
+	struct airoha_qdma *qdma = dev->qdma;
 
 	netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
 	airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
-	clear_bit(queue, port->qos_sq_bmap);
+
+	clear_bit(queue, qdma->qos_channel_map);
+	clear_bit(queue, dev->qos_sq_bmap);
 }
 
 static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
@@ -2829,9 +2848,8 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
 {
 	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
 
-	if (!test_bit(channel, port->qos_sq_bmap)) {
+	if (!test_bit(channel, dev->qos_sq_bmap)) {
 		NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
 		return -EINVAL;
 	}
@@ -2844,10 +2862,9 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
 static int airoha_tc_htb_destroy(struct net_device *netdev)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
 	int q;
 
-	for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
+	for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
 		airoha_tc_remove_htb_queue(netdev, q);
 
 	return 0;
@@ -2858,9 +2875,8 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
 {
 	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
 
-	if (!test_bit(channel, port->qos_sq_bmap)) {
+	if (!test_bit(channel, dev->qos_sq_bmap)) {
 		NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
 		return -EINVAL;
 	}
@@ -2879,6 +2895,7 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
 	case TC_HTB_DESTROY:
 		return airoha_tc_htb_destroy(dev);
 	case TC_HTB_NODE_MODIFY:
+		return airoha_tc_htb_modify_queue(dev, opt);
 	case TC_HTB_LEAF_ALLOC_QUEUE:
 		return airoha_tc_htb_alloc_leaf_queue(dev, opt);
 	case TC_HTB_LEAF_DEL:
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index f6f59d25abd9..a308a770116b 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -533,6 +533,8 @@ struct airoha_qdma {
 
 	struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
 	struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
+
+	DECLARE_BITMAP(qos_channel_map, AIROHA_NUM_QOS_CHANNELS);
 };
 
 struct airoha_gdm_dev {
@@ -540,6 +542,8 @@ struct airoha_gdm_dev {
 	struct airoha_qdma *qdma;
 	struct net_device *dev;
 	struct airoha_eth *eth;
+
+	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
 };
 
 struct airoha_gdm_port {
@@ -549,8 +553,6 @@ struct airoha_gdm_port {
 
 	struct airoha_hw_stats stats;
 
-	DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
-
 	/* qos stats counters */
 	u64 cpu_tx_packets;
 	u64 fwd_tx_packets;

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v6 05/12] net: airoha: Rely on airoha_gdm_dev pointer in airhoa_is_lan_gdm_port()
From: Lorenzo Bianconi @ 2026-05-11 10:49 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lorenzo Bianconi
  Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
	linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260511-airoha-eth-multi-serdes-v6-0-c899462c4f75@kernel.org>

Rename airhoa_is_lan_gdm_port in airhoa_is_lan_gdm_dev. Moreover, rely
on airoha_gdm_dev pointer in airhoa_is_lan_gdm_dev() instead of
airoha_gdm_port one.
This is a preliminary patch to support multiple net_devices connected to
the same GDM{3,4} port via an external hw arbiter.

Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c | 6 ++----
 drivers/net/ethernet/airoha/airoha_eth.h | 4 +++-
 drivers/net/ethernet/airoha/airoha_ppe.c | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index e0cd401bbf67..3510b088ad15 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -73,12 +73,10 @@ static void airoha_qdma_irq_disable(struct airoha_irq_bank *irq_bank,
 
 static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
 {
-	struct airoha_gdm_port *port = dev->port;
 	struct airoha_eth *eth = dev->eth;
 	u32 val, reg;
 
-	reg = airoha_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
-					   : REG_FE_WAN_MAC_H;
+	reg = airoha_is_lan_gdm_dev(dev) ? REG_FE_LAN_MAC_H : REG_FE_WAN_MAC_H;
 	val = (addr[0] << 16) | (addr[1] << 8) | addr[2];
 	airoha_fe_wr(eth, reg, val);
 
@@ -1865,7 +1863,7 @@ static int airoha_dev_init(struct net_device *netdev)
 	int i;
 
 	/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
-	dev->qdma = &eth->qdma[!airoha_is_lan_gdm_port(port)];
+	dev->qdma = &eth->qdma[!airoha_is_lan_gdm_dev(dev)];
 	dev->dev->irq = dev->qdma->irq_banks[0].irq;
 	airoha_set_macaddr(dev, netdev->dev_addr);
 
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index f1eea492217c..f6f59d25abd9 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -647,8 +647,10 @@ static inline u16 airoha_qdma_get_txq(struct airoha_qdma *qdma, u16 qid)
 	return qid % ARRAY_SIZE(qdma->q_tx);
 }
 
-static inline bool airoha_is_lan_gdm_port(struct airoha_gdm_port *port)
+static inline bool airoha_is_lan_gdm_dev(struct airoha_gdm_dev *dev)
 {
+	struct airoha_gdm_port *port = dev->port;
+
 	/* GDM1 port on EN7581 SoC is connected to the lan dsa switch.
 	 * GDM{2,3,4} can be used as wan port connected to an external
 	 * phy module.
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 22f5f1bae730..047141b2d6d8 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -362,7 +362,7 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
 			/* For downlink traffic consume SRAM memory for hw
 			 * forwarding descriptors queue.
 			 */
-			if (airoha_is_lan_gdm_port(port))
+			if (airoha_is_lan_gdm_dev(dev))
 				val |= AIROHA_FOE_IB2_FAST_PATH;
 			if (dsa_port >= 0)
 				val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ,

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v6 04/12] net: airoha: Move airoha_qdma pointer in airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-11 10:49 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lorenzo Bianconi
  Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
	linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260511-airoha-eth-multi-serdes-v6-0-c899462c4f75@kernel.org>

Move airoha_qdma pointer from airoha_gdm_port struct to airoha_gdm_dev
one since the QDMA block used depends on the particular net_device
WAN/LAN configuration and in the current codebase net_device pointer is
associated to airoha_gdm_dev struct.
This is a preliminary patch to support multiple net_devices connected
to the same GDM{3,4} port via an external hw arbiter.

Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c | 105 +++++++++++++++----------------
 drivers/net/ethernet/airoha/airoha_eth.h |   9 ++-
 drivers/net/ethernet/airoha/airoha_ppe.c |  17 ++---
 3 files changed, 64 insertions(+), 67 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 18a89de4d58a..e0cd401bbf67 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -71,9 +71,10 @@ static void airoha_qdma_irq_disable(struct airoha_irq_bank *irq_bank,
 	airoha_qdma_set_irqmask(irq_bank, index, mask, 0);
 }
 
-static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
+static void airoha_set_macaddr(struct airoha_gdm_dev *dev, const u8 *addr)
 {
-	struct airoha_eth *eth = port->qdma->eth;
+	struct airoha_gdm_port *port = dev->port;
+	struct airoha_eth *eth = dev->eth;
 	u32 val, reg;
 
 	reg = airoha_is_lan_gdm_port(port) ? REG_FE_LAN_MAC_H
@@ -85,7 +86,7 @@ static void airoha_set_macaddr(struct airoha_gdm_port *port, const u8 *addr)
 	airoha_fe_wr(eth, REG_FE_MAC_LMIN(reg), val);
 	airoha_fe_wr(eth, REG_FE_MAC_LMAX(reg), val);
 
-	airoha_ppe_init_upd_mem(port);
+	airoha_ppe_init_upd_mem(dev);
 }
 
 static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
@@ -101,10 +102,10 @@ static void airoha_set_gdm_port_fwd_cfg(struct airoha_eth *eth, u32 addr,
 		      FIELD_PREP(GDM_UCFQ_MASK, val));
 }
 
-static int airoha_set_vip_for_gdm_port(struct airoha_gdm_port *port,
-				       bool enable)
+static int airoha_set_vip_for_gdm_port(struct airoha_gdm_dev *dev, bool enable)
 {
-	struct airoha_eth *eth = port->qdma->eth;
+	struct airoha_gdm_port *port = dev->port;
+	struct airoha_eth *eth = dev->eth;
 	u32 vip_port;
 
 	vip_port = eth->soc->ops.get_vip_port(port, port->nbq);
@@ -858,10 +859,13 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
 		if (!port)
 			continue;
 
-		if (port->qdma != qdma)
+		dev = port->dev;
+		if (!dev)
+			continue;
+
+		if (dev->qdma != qdma)
 			continue;
 
-		dev = port->dev;
 		for (j = 0; j < dev->dev->num_tx_queues; j++) {
 			if (airoha_qdma_get_txq(qdma, j) != qid)
 				continue;
@@ -1562,9 +1566,10 @@ static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
 	}
 }
 
-static void airoha_update_hw_stats(struct airoha_gdm_port *port)
+static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
 {
-	struct airoha_eth *eth = port->qdma->eth;
+	struct airoha_gdm_port *port = dev->port;
+	struct airoha_eth *eth = dev->eth;
 	u32 val, i = 0;
 
 	spin_lock(&port->stats.lock);
@@ -1711,11 +1716,11 @@ static int airoha_dev_open(struct net_device *netdev)
 	int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
 	struct airoha_gdm_port *port = dev->port;
-	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_qdma *qdma = dev->qdma;
 	u32 pse_port = FE_PSE_PORT_PPE1;
 
 	netif_tx_start_all_queues(netdev);
-	err = airoha_set_vip_for_gdm_port(port, true);
+	err = airoha_set_vip_for_gdm_port(dev, true);
 	if (err)
 		return err;
 
@@ -1751,11 +1756,11 @@ static int airoha_dev_stop(struct net_device *netdev)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
 	struct airoha_gdm_port *port = dev->port;
-	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_qdma *qdma = dev->qdma;
 	int i;
 
 	netif_tx_disable(netdev);
-	airoha_set_vip_for_gdm_port(port, false);
+	airoha_set_vip_for_gdm_port(dev, false);
 	for (i = 0; i < netdev->num_tx_queues; i++)
 		netdev_tx_reset_subqueue(netdev, i);
 
@@ -1781,21 +1786,21 @@ static int airoha_dev_stop(struct net_device *netdev)
 static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
 	int err;
 
 	err = eth_mac_addr(netdev, p);
 	if (err)
 		return err;
 
-	airoha_set_macaddr(port, netdev->dev_addr);
+	airoha_set_macaddr(dev, netdev->dev_addr);
 
 	return 0;
 }
 
-static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
+static int airoha_set_gdm2_loopback(struct airoha_gdm_dev *dev)
 {
-	struct airoha_eth *eth = port->qdma->eth;
+	struct airoha_gdm_port *port = dev->port;
+	struct airoha_eth *eth = dev->eth;
 	u32 val, pse_port, chan;
 	int i, src_port;
 
@@ -1840,7 +1845,7 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
 		      __field_prep(SP_CPORT_MASK(val), FE_PSE_PORT_CDM2));
 
 	for (i = 0; i < eth->soc->num_ppe; i++)
-		airoha_ppe_set_cpu_port(port, i, AIROHA_GDM2_IDX);
+		airoha_ppe_set_cpu_port(dev, i, AIROHA_GDM2_IDX);
 
 	if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
 		u32 mask = FC_ID_OF_SRC_PORT_MASK(port->nbq);
@@ -1860,9 +1865,9 @@ static int airoha_dev_init(struct net_device *netdev)
 	int i;
 
 	/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
-	port->qdma = &eth->qdma[!airoha_is_lan_gdm_port(port)];
-	dev->dev->irq = port->qdma->irq_banks[0].irq;
-	airoha_set_macaddr(port, netdev->dev_addr);
+	dev->qdma = &eth->qdma[!airoha_is_lan_gdm_port(port)];
+	dev->dev->irq = dev->qdma->irq_banks[0].irq;
+	airoha_set_macaddr(dev, netdev->dev_addr);
 
 	switch (port->id) {
 	case AIROHA_GDM3_IDX:
@@ -1871,7 +1876,7 @@ static int airoha_dev_init(struct net_device *netdev)
 		if (!eth->ports[1]) {
 			int err;
 
-			err = airoha_set_gdm2_loopback(port);
+			err = airoha_set_gdm2_loopback(dev);
 			if (err)
 				return err;
 		}
@@ -1881,8 +1886,7 @@ static int airoha_dev_init(struct net_device *netdev)
 	}
 
 	for (i = 0; i < eth->soc->num_ppe; i++)
-		airoha_ppe_set_cpu_port(port, i,
-					airoha_get_fe_port(port));
+		airoha_ppe_set_cpu_port(dev, i, airoha_get_fe_port(dev));
 
 	return 0;
 }
@@ -1894,7 +1898,7 @@ static void airoha_dev_get_stats64(struct net_device *netdev,
 	struct airoha_gdm_port *port = dev->port;
 	unsigned int start;
 
-	airoha_update_hw_stats(port);
+	airoha_update_hw_stats(dev);
 	do {
 		start = u64_stats_fetch_begin(&port->stats.syncp);
 		storage->rx_packets = port->stats.rx_ok_pkts;
@@ -1914,8 +1918,8 @@ static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
 	struct airoha_gdm_port *port = dev->port;
-	struct airoha_eth *eth = port->qdma->eth;
 	u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
+	struct airoha_eth *eth = dev->eth;
 
 	airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
 		      GDM_LONG_LEN_MASK,
@@ -1989,10 +1993,10 @@ static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
 #endif
 }
 
-int airoha_get_fe_port(struct airoha_gdm_port *port)
+int airoha_get_fe_port(struct airoha_gdm_dev *dev)
 {
-	struct airoha_qdma *qdma = port->qdma;
-	struct airoha_eth *eth = qdma->eth;
+	struct airoha_gdm_port *port = dev->port;
+	struct airoha_eth *eth = dev->eth;
 
 	switch (eth->soc->version) {
 	case 0x7583:
@@ -2009,8 +2013,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 				   struct net_device *netdev)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
-	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_qdma *qdma = dev->qdma;
 	u32 nr_frags, tag, msg0, msg1, len;
 	struct airoha_queue_entry *e;
 	struct netdev_queue *txq;
@@ -2048,7 +2051,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 		}
 	}
 
-	fport = airoha_get_fe_port(port);
+	fport = airoha_get_fe_port(dev);
 	msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
 	       FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
 
@@ -2151,8 +2154,7 @@ static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
 				       struct ethtool_drvinfo *info)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
-	struct airoha_eth *eth = port->qdma->eth;
+	struct airoha_eth *eth = dev->eth;
 
 	strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
 	strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
@@ -2165,7 +2167,7 @@ static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
 	struct airoha_gdm_port *port = dev->port;
 	unsigned int start;
 
-	airoha_update_hw_stats(port);
+	airoha_update_hw_stats(dev);
 	do {
 		start = u64_stats_fetch_begin(&port->stats.syncp);
 		stats->FramesTransmittedOK = port->stats.tx_ok_pkts;
@@ -2205,7 +2207,7 @@ airoha_ethtool_get_rmon_stats(struct net_device *netdev,
 		     ARRAY_SIZE(hw_stats->rx_len) + 1);
 
 	*ranges = airoha_ethtool_rmon_ranges;
-	airoha_update_hw_stats(port);
+	airoha_update_hw_stats(dev);
 	do {
 		int i;
 
@@ -2225,18 +2227,17 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
 					 const u16 *weights, u8 n_weights)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
 	int i;
 
 	for (i = 0; i < AIROHA_NUM_TX_RING; i++)
-		airoha_qdma_clear(port->qdma, REG_QUEUE_CLOSE_CFG(channel),
+		airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel),
 				  TXQ_DISABLE_CHAN_QUEUE_MASK(channel, i));
 
 	for (i = 0; i < n_weights; i++) {
 		u32 status;
 		int err;
 
-		airoha_qdma_wr(port->qdma, REG_TXWRR_WEIGHT_CFG,
+		airoha_qdma_wr(dev->qdma, REG_TXWRR_WEIGHT_CFG,
 			       TWRR_RW_CMD_MASK |
 			       FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
 			       FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
@@ -2244,13 +2245,12 @@ static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
 		err = read_poll_timeout(airoha_qdma_rr, status,
 					status & TWRR_RW_CMD_DONE,
 					USEC_PER_MSEC, 10 * USEC_PER_MSEC,
-					true, port->qdma,
-					REG_TXWRR_WEIGHT_CFG);
+					true, dev->qdma, REG_TXWRR_WEIGHT_CFG);
 		if (err)
 			return err;
 	}
 
-	airoha_qdma_rmw(port->qdma, REG_CHAN_QOS_MODE(channel >> 3),
+	airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
 			CHAN_QOS_MODE_MASK(channel),
 			__field_prep(CHAN_QOS_MODE_MASK(channel), mode));
 
@@ -2316,9 +2316,9 @@ static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
 	struct airoha_gdm_port *port = dev->port;
 
-	u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
+	u64 cpu_tx_packets = airoha_qdma_rr(dev->qdma,
 					    REG_CNTR_VAL(channel << 1));
-	u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
+	u64 fwd_tx_packets = airoha_qdma_rr(dev->qdma,
 					    REG_CNTR_VAL((channel << 1) + 1));
 	u64 tx_packets = (cpu_tx_packets - port->cpu_tx_packets) +
 			 (fwd_tx_packets - port->fwd_tx_packets);
@@ -2582,17 +2582,16 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
 					 u32 bucket_size)
 {
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
 	int i, err;
 
 	for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
-		err = airoha_qdma_set_trtcm_config(port->qdma, channel,
+		err = airoha_qdma_set_trtcm_config(dev->qdma, channel,
 						   REG_EGRESS_TRTCM_CFG, i,
 						   !!rate, TRTCM_METER_MODE);
 		if (err)
 			return err;
 
-		err = airoha_qdma_set_trtcm_token_bucket(port->qdma, channel,
+		err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
 							 REG_EGRESS_TRTCM_CFG,
 							 i, rate, bucket_size);
 		if (err)
@@ -2642,11 +2641,11 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
 	return 0;
 }
 
-static int airoha_qdma_set_rx_meter(struct airoha_gdm_port *port,
+static int airoha_qdma_set_rx_meter(struct airoha_gdm_dev *dev,
 				    u32 rate, u32 bucket_size,
 				    enum trtcm_unit_type unit_type)
 {
-	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_qdma *qdma = dev->qdma;
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
@@ -2725,7 +2724,6 @@ static int airoha_dev_tc_matchall(struct net_device *netdev,
 {
 	enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
 	u32 rate = 0, bucket_size = 0;
 
 	switch (f->command) {
@@ -2750,7 +2748,7 @@ static int airoha_dev_tc_matchall(struct net_device *netdev,
 		fallthrough;
 	}
 	case TC_CLSMATCHALL_DESTROY:
-		return airoha_qdma_set_rx_meter(port, rate, bucket_size,
+		return airoha_qdma_set_rx_meter(dev, rate, bucket_size,
 						unit_type);
 	default:
 		return -EOPNOTSUPP;
@@ -2762,8 +2760,7 @@ static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
 {
 	struct net_device *netdev = cb_priv;
 	struct airoha_gdm_dev *dev = netdev_priv(netdev);
-	struct airoha_gdm_port *port = dev->port;
-	struct airoha_eth *eth = port->qdma->eth;
+	struct airoha_eth *eth = dev->eth;
 
 	if (!tc_can_offload(netdev))
 		return -EOPNOTSUPP;
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index c78cabbec753..f1eea492217c 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -537,12 +537,12 @@ struct airoha_qdma {
 
 struct airoha_gdm_dev {
 	struct airoha_gdm_port *port;
+	struct airoha_qdma *qdma;
 	struct net_device *dev;
 	struct airoha_eth *eth;
 };
 
 struct airoha_gdm_port {
-	struct airoha_qdma *qdma;
 	struct airoha_gdm_dev *dev;
 	int id;
 	int nbq;
@@ -666,19 +666,18 @@ static inline bool airoha_is_7583(struct airoha_eth *eth)
 	return eth->soc->version == 0x7583;
 }
 
-int airoha_get_fe_port(struct airoha_gdm_port *port);
+int airoha_get_fe_port(struct airoha_gdm_dev *dev);
 bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
 			     struct airoha_gdm_dev *dev);
 
-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
-			     u8 fport);
+void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport);
 bool airoha_ppe_is_enabled(struct airoha_eth *eth, int index);
 void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
 			  u16 hash, bool rx_wlan);
 int airoha_ppe_setup_tc_block_cb(struct airoha_ppe_dev *dev, void *type_data);
 int airoha_ppe_init(struct airoha_eth *eth);
 void airoha_ppe_deinit(struct airoha_eth *eth);
-void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port);
+void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev);
 u32 airoha_ppe_get_total_num_entries(struct airoha_ppe *ppe);
 struct airoha_foe_entry *airoha_ppe_foe_get_entry(struct airoha_ppe *ppe,
 						  u32 hash);
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index af7af4097b98..22f5f1bae730 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -84,9 +84,9 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
 			     AIROHA_FOE_IB1_BIND_TIMESTAMP);
 }
 
-void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id, u8 fport)
+void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
 {
-	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_qdma *qdma = dev->qdma;
 	struct airoha_eth *eth = qdma->eth;
 	u8 qdma_id = qdma - &eth->qdma[0];
 	u32 fe_cpu_port;
@@ -180,8 +180,8 @@ static void airoha_ppe_hw_init(struct airoha_ppe *ppe)
 			if (!port)
 				continue;
 
-			airoha_ppe_set_cpu_port(port, i,
-						airoha_get_fe_port(port));
+			airoha_ppe_set_cpu_port(port->dev, i,
+						airoha_get_fe_port(port->dev));
 		}
 	}
 }
@@ -1473,11 +1473,12 @@ void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
 	airoha_ppe_foe_insert_entry(ppe, skb, hash, rx_wlan);
 }
 
-void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
+void airoha_ppe_init_upd_mem(struct airoha_gdm_dev *dev)
 {
-	struct airoha_eth *eth = port->qdma->eth;
-	struct net_device *dev = port->dev->dev;
-	const u8 *addr = dev->dev_addr;
+	struct airoha_gdm_port *port = dev->port;
+	struct net_device *netdev = dev->dev;
+	struct airoha_eth *eth = dev->eth;
+	const u8 *addr = netdev->dev_addr;
 	u32 val;
 
 	val = (addr[2] << 24) | (addr[3] << 16) | (addr[4] << 8) | addr[5];

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v6 03/12] net: airoha: Introduce airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-11 10:49 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lorenzo Bianconi
  Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
	linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260511-airoha-eth-multi-serdes-v6-0-c899462c4f75@kernel.org>

EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
As a result multiple net_devices can connect to the same GDM{3,4} port
and there is a theoretical "1:n" relation between GDM port and
net_devices.
Introduce airoha_gdm_dev struct to collect net_device related info (e.g.
net_device and external phy pointer). Please note this is just a
preliminary patch and we are still supporting a single net_device for
each GDM port. Subsequent patches will add support for multiple net_devices
connected to the same GDM port.

Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c | 309 ++++++++++++++++++-------------
 drivers/net/ethernet/airoha/airoha_eth.h |  13 +-
 drivers/net/ethernet/airoha/airoha_ppe.c |  17 +-
 3 files changed, 203 insertions(+), 136 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 3fe2561c85f1..18a89de4d58a 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -600,6 +600,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 		struct airoha_qdma_desc *desc = &q->desc[q->tail];
 		u32 hash, reason, msg1, desc_ctrl;
 		struct airoha_gdm_port *port;
+		struct net_device *netdev;
 		int data_len, len, p;
 		struct page *page;
 
@@ -628,6 +629,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 			goto free_frag;
 
 		port = eth->ports[p];
+		netdev = port->dev->dev;
 		if (!q->skb) { /* first buffer */
 			q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
 						q->buf_size);
@@ -637,8 +639,8 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 			skb_reserve(q->skb, AIROHA_RX_HEADROOM);
 			__skb_put(q->skb, len);
 			skb_mark_for_recycle(q->skb);
-			q->skb->dev = port->dev;
-			q->skb->protocol = eth_type_trans(q->skb, port->dev);
+			q->skb->dev = netdev;
+			q->skb->protocol = eth_type_trans(q->skb, netdev);
 			q->skb->ip_summed = CHECKSUM_UNNECESSARY;
 			skb_record_rx_queue(q->skb, qid);
 		} else { /* scattered frame */
@@ -656,7 +658,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 		if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
 			continue;
 
-		if (netdev_uses_dsa(port->dev)) {
+		if (netdev_uses_dsa(netdev)) {
 			/* PPE module requires untagged packets to work
 			 * properly and it provides DSA port index via the
 			 * DMA descriptor. Report DSA tag to the DSA stack
@@ -850,6 +852,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
 
 	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
 		struct airoha_gdm_port *port = eth->ports[i];
+		struct airoha_gdm_dev *dev;
 		int j;
 
 		if (!port)
@@ -858,11 +861,12 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
 		if (port->qdma != qdma)
 			continue;
 
-		for (j = 0; j < port->dev->num_tx_queues; j++) {
+		dev = port->dev;
+		for (j = 0; j < dev->dev->num_tx_queues; j++) {
 			if (airoha_qdma_get_txq(qdma, j) != qid)
 				continue;
 
-			netif_wake_subqueue(port->dev, j);
+			netif_wake_subqueue(dev->dev, j);
 		}
 	}
 	q->txq_stopped = false;
@@ -1702,19 +1706,20 @@ static void airoha_update_hw_stats(struct airoha_gdm_port *port)
 	spin_unlock(&port->stats.lock);
 }
 
-static int airoha_dev_open(struct net_device *dev)
+static int airoha_dev_open(struct net_device *netdev)
 {
-	int err, len = ETH_HLEN + dev->mtu + ETH_FCS_LEN;
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	struct airoha_qdma *qdma = port->qdma;
 	u32 pse_port = FE_PSE_PORT_PPE1;
 
-	netif_tx_start_all_queues(dev);
+	netif_tx_start_all_queues(netdev);
 	err = airoha_set_vip_for_gdm_port(port, true);
 	if (err)
 		return err;
 
-	if (netdev_uses_dsa(dev))
+	if (netdev_uses_dsa(netdev))
 		airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
 			      GDM_STAG_EN_MASK);
 	else
@@ -1742,16 +1747,17 @@ static int airoha_dev_open(struct net_device *dev)
 	return 0;
 }
 
-static int airoha_dev_stop(struct net_device *dev)
+static int airoha_dev_stop(struct net_device *netdev)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	struct airoha_qdma *qdma = port->qdma;
 	int i;
 
-	netif_tx_disable(dev);
+	netif_tx_disable(netdev);
 	airoha_set_vip_for_gdm_port(port, false);
-	for (i = 0; i < dev->num_tx_queues; i++)
-		netdev_tx_reset_subqueue(dev, i);
+	for (i = 0; i < netdev->num_tx_queues; i++)
+		netdev_tx_reset_subqueue(netdev, i);
 
 	airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
 				    FE_PSE_PORT_DROP);
@@ -1772,16 +1778,17 @@ static int airoha_dev_stop(struct net_device *dev)
 	return 0;
 }
 
-static int airoha_dev_set_macaddr(struct net_device *dev, void *p)
+static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	int err;
 
-	err = eth_mac_addr(dev, p);
+	err = eth_mac_addr(netdev, p);
 	if (err)
 		return err;
 
-	airoha_set_macaddr(port, dev->dev_addr);
+	airoha_set_macaddr(port, netdev->dev_addr);
 
 	return 0;
 }
@@ -1845,16 +1852,17 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
 	return 0;
 }
 
-static int airoha_dev_init(struct net_device *dev)
+static int airoha_dev_init(struct net_device *netdev)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
-	struct airoha_eth *eth = port->eth;
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
+	struct airoha_eth *eth = dev->eth;
 	int i;
 
 	/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
 	port->qdma = &eth->qdma[!airoha_is_lan_gdm_port(port)];
-	port->dev->irq = port->qdma->irq_banks[0].irq;
-	airoha_set_macaddr(port, dev->dev_addr);
+	dev->dev->irq = port->qdma->irq_banks[0].irq;
+	airoha_set_macaddr(port, netdev->dev_addr);
 
 	switch (port->id) {
 	case AIROHA_GDM3_IDX:
@@ -1879,10 +1887,11 @@ static int airoha_dev_init(struct net_device *dev)
 	return 0;
 }
 
-static void airoha_dev_get_stats64(struct net_device *dev,
+static void airoha_dev_get_stats64(struct net_device *netdev,
 				   struct rtnl_link_stats64 *storage)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	unsigned int start;
 
 	airoha_update_hw_stats(port);
@@ -1901,36 +1910,39 @@ static void airoha_dev_get_stats64(struct net_device *dev,
 	} while (u64_stats_fetch_retry(&port->stats.syncp, start));
 }
 
-static int airoha_dev_change_mtu(struct net_device *dev, int mtu)
+static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	struct airoha_eth *eth = port->qdma->eth;
 	u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
 
 	airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
 		      GDM_LONG_LEN_MASK,
 		      FIELD_PREP(GDM_LONG_LEN_MASK, len));
-	WRITE_ONCE(dev->mtu, mtu);
+	WRITE_ONCE(netdev->mtu, mtu);
 
 	return 0;
 }
 
-static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
+static u16 airoha_dev_select_queue(struct net_device *netdev,
+				   struct sk_buff *skb,
 				   struct net_device *sb_dev)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	int queue, channel;
 
 	/* For dsa device select QoS channel according to the dsa user port
 	 * index, rely on port id otherwise. Select QoS queue based on the
 	 * skb priority.
 	 */
-	channel = netdev_uses_dsa(dev) ? skb_get_queue_mapping(skb) : port->id;
+	channel = netdev_uses_dsa(netdev) ? skb_get_queue_mapping(skb) : port->id;
 	channel = channel % AIROHA_NUM_QOS_CHANNELS;
 	queue = (skb->priority - 1) % AIROHA_NUM_QOS_QUEUES; /* QoS queue */
 	queue = channel * AIROHA_NUM_QOS_QUEUES + queue;
 
-	return queue < dev->num_tx_queues ? queue : 0;
+	return queue < netdev->num_tx_queues ? queue : 0;
 }
 
 static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
@@ -1994,9 +2006,10 @@ int airoha_get_fe_port(struct airoha_gdm_port *port)
 }
 
 static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
-				   struct net_device *dev)
+				   struct net_device *netdev)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	struct airoha_qdma *qdma = port->qdma;
 	u32 nr_frags, tag, msg0, msg1, len;
 	struct airoha_queue_entry *e;
@@ -2009,7 +2022,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 	u8 fport;
 
 	qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
-	tag = airoha_get_dsa_tag(skb, dev);
+	tag = airoha_get_dsa_tag(skb, netdev);
 
 	msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
 			  qid / AIROHA_NUM_QOS_QUEUES) |
@@ -2045,7 +2058,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 
 	spin_lock_bh(&q->lock);
 
-	txq = skb_get_tx_queue(dev, skb);
+	txq = skb_get_tx_queue(netdev, skb);
 	nr_frags = 1 + skb_shinfo(skb)->nr_frags;
 
 	if (q->queued + nr_frags >= q->ndesc) {
@@ -2069,9 +2082,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 		dma_addr_t addr;
 		u32 val;
 
-		addr = dma_map_single(dev->dev.parent, data, len,
+		addr = dma_map_single(netdev->dev.parent, data, len,
 				      DMA_TO_DEVICE);
-		if (unlikely(dma_mapping_error(dev->dev.parent, addr)))
+		if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
 			goto error_unmap;
 
 		list_move_tail(&e->list, &tx_list);
@@ -2120,7 +2133,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 
 error_unmap:
 	list_for_each_entry(e, &tx_list, list) {
-		dma_unmap_single(dev->dev.parent, e->dma_addr, e->dma_len,
+		dma_unmap_single(netdev->dev.parent, e->dma_addr, e->dma_len,
 				 DMA_TO_DEVICE);
 		e->dma_addr = 0;
 	}
@@ -2129,25 +2142,27 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
 	spin_unlock_bh(&q->lock);
 error:
 	dev_kfree_skb_any(skb);
-	dev->stats.tx_dropped++;
+	netdev->stats.tx_dropped++;
 
 	return NETDEV_TX_OK;
 }
 
-static void airoha_ethtool_get_drvinfo(struct net_device *dev,
+static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
 				       struct ethtool_drvinfo *info)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	struct airoha_eth *eth = port->qdma->eth;
 
 	strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
 	strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
 }
 
-static void airoha_ethtool_get_mac_stats(struct net_device *dev,
+static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
 					 struct ethtool_eth_mac_stats *stats)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	unsigned int start;
 
 	airoha_update_hw_stats(port);
@@ -2175,11 +2190,12 @@ static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
 };
 
 static void
-airoha_ethtool_get_rmon_stats(struct net_device *dev,
+airoha_ethtool_get_rmon_stats(struct net_device *netdev,
 			      struct ethtool_rmon_stats *stats,
 			      const struct ethtool_rmon_hist_range **ranges)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	struct airoha_hw_stats *hw_stats = &port->stats;
 	unsigned int start;
 
@@ -2204,11 +2220,12 @@ airoha_ethtool_get_rmon_stats(struct net_device *dev,
 	} while (u64_stats_fetch_retry(&port->stats.syncp, start));
 }
 
-static int airoha_qdma_set_chan_tx_sched(struct net_device *dev,
+static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
 					 int channel, enum tx_sched_mode mode,
 					 const u16 *weights, u8 n_weights)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	int i;
 
 	for (i = 0; i < AIROHA_NUM_TX_RING; i++)
@@ -2293,10 +2310,12 @@ static int airoha_qdma_set_tx_ets_sched(struct net_device *dev, int channel,
 					     ARRAY_SIZE(w));
 }
 
-static int airoha_qdma_get_tx_ets_stats(struct net_device *dev, int channel,
+static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
 					struct tc_ets_qopt_offload *opt)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
+
 	u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
 					    REG_CNTR_VAL(channel << 1));
 	u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
@@ -2558,11 +2577,12 @@ static int airoha_qdma_set_trtcm_token_bucket(struct airoha_qdma *qdma,
 					   mode, val);
 }
 
-static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
+static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
 					 int channel, u32 rate,
 					 u32 bucket_size)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	int i, err;
 
 	for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
@@ -2582,20 +2602,22 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
 	return 0;
 }
 
-static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
+static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
 					  struct tc_htb_qopt_offload *opt)
 {
 	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
 	u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
-	int err, num_tx_queues = dev->real_num_tx_queues;
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	int err, num_tx_queues = netdev->real_num_tx_queues;
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 
 	if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
 		NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
 		return -EINVAL;
 	}
 
-	err = airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
+	err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
+					    opt->quantum);
 	if (err) {
 		NL_SET_ERR_MSG_MOD(opt->extack,
 				   "failed configuring htb offload");
@@ -2605,9 +2627,10 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
 	if (opt->command == TC_HTB_NODE_MODIFY)
 		return 0;
 
-	err = netif_set_real_num_tx_queues(dev, num_tx_queues + 1);
+	err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
 	if (err) {
-		airoha_qdma_set_tx_rate_limit(dev, channel, 0, opt->quantum);
+		airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
+					      opt->quantum);
 		NL_SET_ERR_MSG_MOD(opt->extack,
 				   "failed setting real_num_tx_queues");
 		return err;
@@ -2697,11 +2720,12 @@ static int airoha_tc_matchall_act_validate(struct tc_cls_matchall_offload *f)
 	return 0;
 }
 
-static int airoha_dev_tc_matchall(struct net_device *dev,
+static int airoha_dev_tc_matchall(struct net_device *netdev,
 				  struct tc_cls_matchall_offload *f)
 {
 	enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	u32 rate = 0, bucket_size = 0;
 
 	switch (f->command) {
@@ -2736,18 +2760,19 @@ static int airoha_dev_tc_matchall(struct net_device *dev,
 static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
 					void *type_data, void *cb_priv)
 {
-	struct net_device *dev = cb_priv;
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct net_device *netdev = cb_priv;
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	struct airoha_eth *eth = port->qdma->eth;
 
-	if (!tc_can_offload(dev))
+	if (!tc_can_offload(netdev))
 		return -EOPNOTSUPP;
 
 	switch (type) {
 	case TC_SETUP_CLSFLOWER:
 		return airoha_ppe_setup_tc_block_cb(&eth->ppe->dev, type_data);
 	case TC_SETUP_CLSMATCHALL:
-		return airoha_dev_tc_matchall(dev, type_data);
+		return airoha_dev_tc_matchall(netdev, type_data);
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -2794,47 +2819,51 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
 	}
 }
 
-static void airoha_tc_remove_htb_queue(struct net_device *dev, int queue)
+static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 
-	netif_set_real_num_tx_queues(dev, dev->real_num_tx_queues - 1);
-	airoha_qdma_set_tx_rate_limit(dev, queue + 1, 0, 0);
+	netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
+	airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
 	clear_bit(queue, port->qos_sq_bmap);
 }
 
-static int airoha_tc_htb_delete_leaf_queue(struct net_device *dev,
+static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
 					   struct tc_htb_qopt_offload *opt)
 {
 	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 
 	if (!test_bit(channel, port->qos_sq_bmap)) {
 		NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
 		return -EINVAL;
 	}
 
-	airoha_tc_remove_htb_queue(dev, channel);
+	airoha_tc_remove_htb_queue(netdev, channel);
 
 	return 0;
 }
 
-static int airoha_tc_htb_destroy(struct net_device *dev)
+static int airoha_tc_htb_destroy(struct net_device *netdev)
 {
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 	int q;
 
 	for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
-		airoha_tc_remove_htb_queue(dev, q);
+		airoha_tc_remove_htb_queue(netdev, q);
 
 	return 0;
 }
 
-static int airoha_tc_get_htb_get_leaf_queue(struct net_device *dev,
+static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
 					    struct tc_htb_qopt_offload *opt)
 {
 	u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
-	struct airoha_gdm_port *port = netdev_priv(dev);
+	struct airoha_gdm_dev *dev = netdev_priv(netdev);
+	struct airoha_gdm_port *port = dev->port;
 
 	if (!test_bit(channel, port->qos_sq_bmap)) {
 		NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
@@ -2870,8 +2899,8 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
 	return 0;
 }
 
-static int airoha_dev_tc_setup(struct net_device *dev, enum tc_setup_type type,
-			       void *type_data)
+static int airoha_dev_tc_setup(struct net_device *dev,
+			       enum tc_setup_type type, void *type_data)
 {
 	switch (type) {
 	case TC_SETUP_QDISC_ETS:
@@ -2937,25 +2966,81 @@ static void airoha_metadata_dst_free(struct airoha_gdm_port *port)
 	}
 }
 
-bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
-			      struct airoha_gdm_port *port)
+bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+			     struct airoha_gdm_dev *dev)
 {
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
-		if (eth->ports[i] == port)
+		struct airoha_gdm_port *port = eth->ports[i];
+
+		if (!port)
+			continue;
+
+		if (port->dev == dev)
 			return true;
 	}
 
 	return false;
 }
 
+static int airoha_alloc_gdm_device(struct airoha_eth *eth,
+				   struct airoha_gdm_port *port,
+				   struct device_node *np)
+{
+	struct airoha_gdm_dev *dev;
+	struct net_device *netdev;
+	int err;
+
+	netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
+					 AIROHA_NUM_NETDEV_TX_RINGS,
+					 AIROHA_NUM_RX_RING);
+	if (!netdev) {
+		dev_err(eth->dev, "alloc_etherdev failed\n");
+		return -ENOMEM;
+	}
+
+	netdev->netdev_ops = &airoha_netdev_ops;
+	netdev->ethtool_ops = &airoha_ethtool_ops;
+	netdev->max_mtu = AIROHA_MAX_MTU;
+	netdev->watchdog_timeo = 5 * HZ;
+	netdev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO6 |
+			      NETIF_F_IPV6_CSUM | NETIF_F_SG | NETIF_F_TSO |
+			      NETIF_F_HW_TC;
+	netdev->features |= netdev->hw_features;
+	netdev->vlan_features = netdev->hw_features;
+	netdev->dev.of_node = np;
+	SET_NETDEV_DEV(netdev, eth->dev);
+
+	/* reserve hw queues for HTB offloading */
+	err = netif_set_real_num_tx_queues(netdev, AIROHA_NUM_TX_RING);
+	if (err)
+		return err;
+
+	err = of_get_ethdev_address(np, netdev);
+	if (err) {
+		if (err == -EPROBE_DEFER)
+			return err;
+
+		eth_hw_addr_random(netdev);
+		dev_info(eth->dev, "generated random MAC address %pM\n",
+			 netdev->dev_addr);
+	}
+
+	dev = netdev_priv(netdev);
+	dev->dev = netdev;
+	dev->port = port;
+	port->dev = dev;
+	dev->eth = eth;
+
+	return 0;
+}
+
 static int airoha_alloc_gdm_port(struct airoha_eth *eth,
 				 struct device_node *np)
 {
 	const __be32 *id_ptr = of_get_property(np, "reg", NULL);
 	struct airoha_gdm_port *port;
-	struct net_device *dev;
 	int err, p;
 	u32 id;
 
@@ -2977,53 +3062,22 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
 		return -EINVAL;
 	}
 
-	dev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*port),
-				      AIROHA_NUM_NETDEV_TX_RINGS,
-				      AIROHA_NUM_RX_RING);
-	if (!dev) {
-		dev_err(eth->dev, "alloc_etherdev failed\n");
+	port = devm_kzalloc(eth->dev, sizeof(*port), GFP_KERNEL);
+	if (!port)
 		return -ENOMEM;
-	}
-
-	dev->netdev_ops = &airoha_netdev_ops;
-	dev->ethtool_ops = &airoha_ethtool_ops;
-	dev->max_mtu = AIROHA_MAX_MTU;
-	dev->watchdog_timeo = 5 * HZ;
-	dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
-			   NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
-			   NETIF_F_SG | NETIF_F_TSO |
-			   NETIF_F_HW_TC;
-	dev->features |= dev->hw_features;
-	dev->vlan_features = dev->hw_features;
-	dev->dev.of_node = np;
-	SET_NETDEV_DEV(dev, eth->dev);
-
-	/* reserve hw queues for HTB offloading */
-	err = netif_set_real_num_tx_queues(dev, AIROHA_NUM_TX_RING);
-	if (err)
-		return err;
-
-	err = of_get_ethdev_address(np, dev);
-	if (err) {
-		if (err == -EPROBE_DEFER)
-			return err;
-
-		eth_hw_addr_random(dev);
-		dev_info(eth->dev, "generated random MAC address %pM\n",
-			 dev->dev_addr);
-	}
 
-	port = netdev_priv(dev);
 	u64_stats_init(&port->stats.syncp);
 	spin_lock_init(&port->stats.lock);
-	port->eth = eth;
-	port->dev = dev;
 	port->id = id;
 	/* XXX: Read nbq from DTS */
 	port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
 	eth->ports[p] = port;
 
-	return airoha_metadata_dst_alloc(port);
+	err = airoha_metadata_dst_alloc(port);
+	if (err)
+		return err;
+
+	return airoha_alloc_gdm_device(eth, port, np);
 }
 
 static int airoha_register_gdm_devices(struct airoha_eth *eth)
@@ -3037,7 +3091,7 @@ static int airoha_register_gdm_devices(struct airoha_eth *eth)
 		if (!port)
 			continue;
 
-		err = register_netdev(port->dev);
+		err = register_netdev(port->dev->dev);
 		if (err)
 			return err;
 	}
@@ -3146,12 +3200,14 @@ static int airoha_probe(struct platform_device *pdev)
 
 	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
 		struct airoha_gdm_port *port = eth->ports[i];
+		struct airoha_gdm_dev *dev;
 
 		if (!port)
 			continue;
 
-		if (port->dev->reg_state == NETREG_REGISTERED)
-			unregister_netdev(port->dev);
+		dev = port->dev;
+		if (dev && dev->dev->reg_state == NETREG_REGISTERED)
+			unregister_netdev(dev->dev);
 		airoha_metadata_dst_free(port);
 	}
 	airoha_hw_cleanup(eth);
@@ -3172,11 +3228,14 @@ static void airoha_remove(struct platform_device *pdev)
 
 	for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
 		struct airoha_gdm_port *port = eth->ports[i];
+		struct airoha_gdm_dev *dev;
 
 		if (!port)
 			continue;
 
-		unregister_netdev(port->dev);
+		dev = port->dev;
+		if (dev)
+			unregister_netdev(dev->dev);
 		airoha_metadata_dst_free(port);
 	}
 	airoha_hw_cleanup(eth);
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index d3781103abb5..c78cabbec753 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -535,10 +535,15 @@ struct airoha_qdma {
 	struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
 };
 
+struct airoha_gdm_dev {
+	struct airoha_gdm_port *port;
+	struct net_device *dev;
+	struct airoha_eth *eth;
+};
+
 struct airoha_gdm_port {
 	struct airoha_qdma *qdma;
-	struct airoha_eth *eth;
-	struct net_device *dev;
+	struct airoha_gdm_dev *dev;
 	int id;
 	int nbq;
 
@@ -662,8 +667,8 @@ static inline bool airoha_is_7583(struct airoha_eth *eth)
 }
 
 int airoha_get_fe_port(struct airoha_gdm_port *port);
-bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
-			      struct airoha_gdm_port *port);
+bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
+			     struct airoha_gdm_dev *dev);
 
 void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
 			     u8 fport);
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 26da519236bf..af7af4097b98 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -298,12 +298,12 @@ static void airoha_ppe_foe_set_bridge_addrs(struct airoha_foe_bridge *br,
 
 static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
 					struct airoha_foe_entry *hwe,
-					struct net_device *dev, int type,
+					struct net_device *netdev, int type,
 					struct airoha_flow_data *data,
 					int l4proto)
 {
 	u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
-	int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&dev);
+	int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev);
 	struct airoha_foe_mac_info_common *l2;
 	u8 smac_id = 0xf;
 
@@ -319,10 +319,11 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
 	hwe->ib1 = val;
 
 	val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f);
-	if (dev) {
+	if (netdev) {
 		struct airoha_wdma_info info = {};
 
-		if (!airoha_ppe_get_wdma_info(dev, data->eth.h_dest, &info)) {
+		if (!airoha_ppe_get_wdma_info(netdev, data->eth.h_dest,
+					      &info)) {
 			val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) |
 			       FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT,
 					  FE_PSE_PORT_CDM4);
@@ -332,12 +333,14 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
 				     FIELD_PREP(AIROHA_FOE_MAC_WDMA_WCID,
 						info.wcid);
 		} else {
-			struct airoha_gdm_port *port = netdev_priv(dev);
+			struct airoha_gdm_dev *dev = netdev_priv(netdev);
+			struct airoha_gdm_port *port;
 			u8 pse_port, channel;
 
-			if (!airoha_is_valid_gdm_port(eth, port))
+			if (!airoha_is_valid_gdm_dev(eth, dev))
 				return -EINVAL;
 
+			port = dev->port;
 			if (dsa_port >= 0 || eth->ports[1])
 				pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
 							 : port->id;
@@ -1473,7 +1476,7 @@ void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
 void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
 {
 	struct airoha_eth *eth = port->qdma->eth;
-	struct net_device *dev = port->dev;
+	struct net_device *dev = port->dev->dev;
 	const u8 *addr = dev->dev_addr;
 	u32 val;
 

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v6 02/12] net: airoha: Reserve RX headroom to avoid skb reallocation
From: Lorenzo Bianconi @ 2026-05-11 10:49 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lorenzo Bianconi
  Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
	linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260511-airoha-eth-multi-serdes-v6-0-c899462c4f75@kernel.org>

Reserve NET_SKB_PAD + NET_IP_ALIGN bytes of headroom for received packets
to avoid skb head reallocation when pushing protocol headers into the skb.

Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 drivers/net/ethernet/airoha/airoha_eth.c | 12 ++++++++----
 drivers/net/ethernet/airoha/airoha_eth.h |  2 ++
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index f71fb18197ec..3fe2561c85f1 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -543,9 +543,10 @@ static int airoha_qdma_fill_rx_queue(struct airoha_queue *q)
 		q->queued++;
 		nframes++;
 
+		offset += AIROHA_RX_HEADROOM;
 		e->buf = page_address(page) + offset;
 		e->dma_addr = page_pool_get_dma_addr(page) + offset;
-		e->dma_len = SKB_WITH_OVERHEAD(q->buf_size);
+		e->dma_len = SKB_WITH_OVERHEAD(AIROHA_RX_LEN(q->buf_size));
 
 		val = FIELD_PREP(QDMA_DESC_LEN_MASK, e->dma_len);
 		WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
@@ -616,8 +617,9 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 
 		page = virt_to_head_page(e->buf);
 		len = FIELD_GET(QDMA_DESC_LEN_MASK, desc_ctrl);
-		data_len = q->skb ? q->buf_size
-				  : SKB_WITH_OVERHEAD(q->buf_size);
+		data_len = q->skb
+			   ? AIROHA_RX_LEN(q->buf_size)
+			   : SKB_WITH_OVERHEAD(AIROHA_RX_LEN(q->buf_size));
 		if (!len || data_len < len)
 			goto free_frag;
 
@@ -627,10 +629,12 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
 
 		port = eth->ports[p];
 		if (!q->skb) { /* first buffer */
-			q->skb = napi_build_skb(e->buf, q->buf_size);
+			q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
+						q->buf_size);
 			if (!q->skb)
 				goto free_frag;
 
+			skb_reserve(q->skb, AIROHA_RX_HEADROOM);
 			__skb_put(q->skb, len);
 			skb_mark_for_recycle(q->skb);
 			q->skb->dev = port->dev;
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 58530d096de7..d3781103abb5 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -32,6 +32,8 @@
 #define AIROHA_FE_MC_MAX_VLAN_TABLE	64
 #define AIROHA_FE_MC_MAX_VLAN_PORT	16
 #define AIROHA_NUM_TX_IRQ		2
+#define AIROHA_RX_HEADROOM		(NET_SKB_PAD + NET_IP_ALIGN)
+#define AIROHA_RX_LEN(_n)		((_n) - AIROHA_RX_HEADROOM)
 #define HW_DSCP_NUM			2048
 #define IRQ_QUEUE_LEN(_n)		((_n) ? 1024 : 2048)
 #define TX_DSCP_NUM			1024

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v6 01/12] dt-bindings: net: airoha: Add EN7581 ethernet-ports properties
From: Lorenzo Bianconi @ 2026-05-11 10:49 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lorenzo Bianconi
  Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
	linux-mediatek, netdev, devicetree
In-Reply-To: <20260511-airoha-eth-multi-serdes-v6-0-c899462c4f75@kernel.org>

EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
As a result multiple net_devices can connect to the same GDM{3,4} port
and there is a theoretical "1:n" relation between GDM ports and
net_devices.
Introduce the ethernet-port property in order to model a given net_device
that is connected via the external arbiter to the GDM{3,4} port (that
is represented by the ethernet property. Please note GDM1 or GDM2 does not
support the connection with the external arbiter and are represented
by ethernet property.

Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 .../devicetree/bindings/net/airoha,en7581-eth.yaml | 64 +++++++++++++++++++++-
 1 file changed, 63 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml b/Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
index fbe2ddcdd909..642f300c0945 100644
--- a/Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
+++ b/Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
@@ -130,6 +130,50 @@ patternProperties:
         maximum: 4
         description: GMAC port identifier
 
+      '#address-cells':
+        const: 1
+      '#size-cells':
+        const: 0
+
+    allOf:
+      - if:
+          properties:
+            reg:
+              contains:
+                items:
+                  - enum:
+                      - 3
+                      - 4
+        then:
+          properties:
+            '#address-cells':
+              const: 1
+            '#size-cells':
+              const: 0
+
+          patternProperties:
+            "^ethernet-port@[0-5]$":
+              type: object
+              unevaluatedProperties: false
+              $ref: ethernet-controller.yaml#
+              description: External ethernet port ID available on the GDM port
+
+              properties:
+                compatible:
+                  const: airoha,eth-port
+
+                reg:
+                  maxItems: 1
+                  description: External ethernet port identifier
+
+              required:
+                - reg
+                - compatible
+
+          required:
+            - "#address-cells"
+            - "#size-cells"
+
     required:
       - reg
       - compatible
@@ -191,9 +235,27 @@ examples:
         #address-cells = <1>;
         #size-cells = <0>;
 
-        mac: ethernet@1 {
+        mac1: ethernet@1 {
           compatible = "airoha,eth-mac";
           reg = <1>;
         };
+
+        mac4: ethernet@4 {
+          compatible = "airoha,eth-mac";
+          reg = <4>;
+
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          ethernet-port@0 {
+            compatible = "airoha,eth-port";
+            reg = <0>;
+          };
+
+          ethernet-port@1 {
+            compatible = "airoha,eth-port";
+            reg = <1>;
+          };
+        };
       };
     };

-- 
2.54.0



^ permalink raw reply related

* [PATCH net-next v6 00/12] net: airoha: Support multiple net_devices connected to the same GDM port
From: Lorenzo Bianconi @ 2026-05-11 10:49 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Lorenzo Bianconi
  Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
	linux-mediatek, netdev, devicetree, Xuegang Lu, Madhur Agrawal,
	Brown Huang

EN7581 or AN7583 SoCs support connecting multiple external SerDes (e.g.
Ethernet or USB SerDes) to GDM3 or GDM4 ports via a hw arbiter that
manages the traffic in a TDM manner. As a result multiple net_devices can
connect to the same GDM{3,4} port and there is a theoretical "1:n"
relation between GDM ports and net_devices.

           ┌─────────────────────────────────┐
           │                                 │    ┌──────┐
           │                         P1 GDM1 ├────►MT7530│
           │                                 │    └──────┘
           │                                 │      ETH0 (DSA conduit)
           │                                 │
           │              PSE/FE             │
           │                                 │
           │                                 │
           │                                 │    ┌─────┐
           │                         P0 CDM1 ├────►QDMA0│
           │  P4                     P9 GDM4 │    └─────┘
           └──┬─────────────────────────┬────┘
              │                         │
           ┌──▼──┐                 ┌────▼────┐
           │ PPE │                 │   ARB   │
           └─────┘                 └─┬─────┬─┘
                                     │     │
                                  ┌──▼──┐┌─▼───┐
                                  │ ETH ││ USB │
                                  └─────┘└─────┘
                                   ETH1   ETH2

This series introduces support for multiple net_devices connected to the
same Frame Engine (FE) GDM port (GDM3 or GDM4) via an external hw
arbiter. Please note GDM1 or GDM2 does not support the connection with
the external arbiter.

---
Changes in v6:
- Reconfigure REG_GDM_LEN_CFG() whit max 'running' MTU in
  airoha_dev_stop().
- Fix port staring MIB counters in airoha_update_hw_stats().
- Fix regression in TC_HTB_NODE_MODIFY command.
- Fix length check in airoha_qdma_rx_process().
- Fix dts schema.
- Link to v5: https://lore.kernel.org/r/20260509-airoha-eth-multi-serdes-v5-0-805e38edc2aa@kernel.org

Changes in v5:
- Move qos_sq_bmap bitmap in airoha_gdm_dev struct.
- Unregister netdevice before running of_node_put().
- Move stat MIB counters in airoha_gdm_dev struct.
- Fix airoha_ppe_init_upd_mem() mac address configuration.
- Do not return -EBUSY if we try to decrease configured MTU of a shared
  GDM port, just skip hw configuration.
- use int instead of atomic_t for GDM port users.
- Add patch "net: airoha: Reserve RX headroom to avoid skb reallocation"
- Fix typos.
- Link to v4: https://lore.kernel.org/r/20260507-airoha-eth-multi-serdes-v4-0-af613b61ae02@kernel.org

Changes in v4:
- Make ethernet-port property available just for GDM3 and GDM4 in DTS
  specification
- Move cpu_tx_packets, fwd_tx_packets qos_sq_bmap fields in airoha_qdma
  struct
- Fix of_node leak removing the net_device in airoha_remove() or
  airoha_probe() error path
- Fix nbq backward compatibility
- Link to v3: https://lore.kernel.org/r/20260406-airoha-eth-multi-serdes-v3-0-ab6ea49d59ff@kernel.org

Changes in v3:
- Fix MTU and VIP configuration when the GDM port is shared between
  multiple net_devices.
- Add sanity check for nbq parameter.
- Add missing of_node_get() for net_device np node.
- Check if GDM port is shared before decresing device MTU.
- Move port forward configuration in airoha_dev_stop() before
  configuring DMA tx/rx engine.
- Introduce PRIV_FLAG_WAN parameter.
- Link to v2: https://lore.kernel.org/r/20260401-airoha-eth-multi-serdes-v2-0-ac427ae4beeb@kernel.org

Changes in v2:
- Rename multiplexer in arbiter in the commit logs.
- Rebase on top of net-next main branch.
- Add missing PPE cpu port configuration for GDM2 when loopback is
  enabled.
- Link to v1: https://lore.kernel.org/r/20260329-airoha-eth-multi-serdes-v1-0-00f52dc360ca@kernel.org

---
Christian Marangi (1):
      net: airoha: Better handle MIB for GDM with multiple port attached

Lorenzo Bianconi (11):
      dt-bindings: net: airoha: Add EN7581 ethernet-ports properties
      net: airoha: Reserve RX headroom to avoid skb reallocation
      net: airoha: Introduce airoha_gdm_dev struct
      net: airoha: Move airoha_qdma pointer in airoha_gdm_dev struct
      net: airoha: Rely on airoha_gdm_dev pointer in airhoa_is_lan_gdm_port()
      net: airoha: Move qos_sq_bmap in airoha_gdm_dev struct
      net: airoha: Move {cpu,fwd}_tx_packets in airoha_gdm_dev struct
      net: airoha: Support multiple net_devices for a single FE GDM port
      net: airoha: Do not stop GDM port if it is shared
      net: airoha: Introduce WAN device flag
      net: airoha: Support multiple LAN/WAN interfaces for hw MAC address configuration

 .../devicetree/bindings/net/airoha,en7581-eth.yaml |   64 +-
 drivers/net/ethernet/airoha/airoha_eth.c           | 1013 +++++++++++++-------
 drivers/net/ethernet/airoha/airoha_eth.h           |   54 +-
 drivers/net/ethernet/airoha/airoha_ppe.c           |   43 +-
 4 files changed, 809 insertions(+), 365 deletions(-)
---
base-commit: 63751099502d10f0aa6bb35273e56c5800cc4e3a
change-id: 20260324-airoha-eth-multi-serdes-fb4b556ee756

Best regards,
-- 
Lorenzo Bianconi <lorenzo@kernel.org>



^ permalink raw reply

* [PATCH v12 5/5] mfd: Add support for MediaTek SPMI PMICs and MT6363/73
From: AngeloGioacchino Del Regno @ 2026-05-11 10:13 UTC (permalink / raw)
  To: linux-mediatek
  Cc: lee, robh, krzk+dt, conor+dt, matthias.bgg,
	angelogioacchino.delregno, lgirdwood, broonie, devicetree,
	linux-kernel, linux-arm-kernel, kernel, wenst,
	Nícolas F. R. A. Prado
In-Reply-To: <20260511101355.122478-1-angelogioacchino.delregno@collabora.com>

This driver adds support for the MediaTek SPMI PMICs and their
interrupt controller (which is present in 95% of the cases).

Other than probing all of the sub-devices of a SPMI PMIC, this
sets up a regmap from the relevant SPMI bus and initializes an
interrupt controller with its irq domain and irqchip to handle
chained interrupts, with the SPMI bus itself being its parent
irq controller, and the PMIC being the outmost device.

This driver hence holds all of the information about a specific
PMIC's interrupts and will properly handle them, calling the
ISR for any subdevice that requested an interrupt.

As for the interrupt spec, this driver wants either three or
two interrupt cells, but in the case 3 were given it ignores
the first one: this is because of how this first revision of
of the MediaTek SPMI 2.0 Controller works, which doesn't hold
hold irq number information in its register, but delegates
that to the SPMI device - it's possible that this will change
in the future with a newer revision of the controller IP, and
this is the main reason for that.

To make use of this implementation, this driver also adds the
required bits to support MediaTek MT6363 and MT6373 SPMI PMICs.

Reviewed-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/mfd/Kconfig                       |  16 +
 drivers/mfd/Makefile                      |   1 +
 drivers/mfd/mtk-spmi-pmic.c               | 427 ++++++++++++++++++++++
 include/linux/mfd/mt63x3_spmi/registers.h |  34 ++
 4 files changed, 478 insertions(+)
 create mode 100644 drivers/mfd/mtk-spmi-pmic.c
 create mode 100644 include/linux/mfd/mt63x3_spmi/registers.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..3e9acdf648b7 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1148,6 +1148,22 @@ config MFD_MT6397
 	  accessing the device; additional drivers must be enabled in order
 	  to use the functionality of the device.
 
+config MFD_MTK_SPMI_PMIC
+	tristate "MediaTek SPMI PMICs"
+	depends on ARCH_MEDIATEK || COMPILE_TEST
+	depends on OF
+	depends on SPMI
+	select REGMAP_SPMI
+	help
+	  Say yes here to enable support for MediaTek's SPMI PMICs.
+	  These PMICs made their first appearance in board designs using the
+	  MediaTek Dimensity 9400 series of SoCs.
+	  Note that this will only be useful when paired with descriptions
+	  of the independent functions as child nodes in the device tree.
+
+	  Say M here if you want to include support for the MediaTek SPMI
+	  PMICs as a module. The module will be called "mtk-spmi-pmic".
+
 config MFD_MENF21BMC
 	tristate "MEN 14F021P00 Board Management Controller Support"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..e00d283450c6 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -190,6 +190,7 @@ obj-$(CONFIG_MFD_MT6360)	+= mt6360-core.o
 obj-$(CONFIG_MFD_MT6370)	+= mt6370.o
 mt6397-objs			:= mt6397-core.o mt6397-irq.o mt6358-irq.o
 obj-$(CONFIG_MFD_MT6397)	+= mt6397.o
+obj-$(CONFIG_MFD_MTK_SPMI_PMIC)	+= mtk-spmi-pmic.o
 
 obj-$(CONFIG_RZ_MTU3)		+= rz-mtu3.o
 obj-$(CONFIG_ABX500_CORE)	+= abx500-core.o
diff --git a/drivers/mfd/mtk-spmi-pmic.c b/drivers/mfd/mtk-spmi-pmic.c
new file mode 100644
index 000000000000..d1fc8156e696
--- /dev/null
+++ b/drivers/mfd/mtk-spmi-pmic.c
@@ -0,0 +1,427 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MediaTek SPMI PMICs Driver
+ *
+ * Copyright (c) 2024 MediaTek Inc.
+ * Copyright (c) 2025 Collabora Ltd
+ *
+ * Authors:
+ * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/mt63x3_spmi/registers.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/spmi.h>
+#include <linux/types.h>
+
+#define MTK_SPMI_PMIC_VAL_BITS		8
+#define MTK_SPMI_PMIC_REG_CHIP_ID	0xb
+#define MTK_SPMI_PMIC_RCS_IRQ_DONE	0x41b
+
+/**
+ * struct mtk_spmi_pmic_irq_group - Group of interrupts in SPMI PMIC
+ * @num_int_regs: Number of registers for this group of interrupts
+ * @con_reg:      PMIC Interrupt Group Control 0 register
+ * @sta_reg:      PMIC Interrupt Group Status 0 register
+ * @group_num:    PMIC Interrupt Group number - also corresponds to the
+ *                status bit in the global IRQ Control register
+ */
+struct mtk_spmi_pmic_irq_grp {
+	u8 hwirq_base;
+	u8 num_int_regs;
+	u16 con_reg;
+	u16 sta_reg;
+	u8 group_num;
+};
+
+/**
+ * struct mtk_spmi_pmic_variant - SPMI PMIC variant-specific data
+ * @pmic_irq:    Group of interrupts in SPMI PMIC
+ * @num_groups:  Number of groups of interrupts
+ * @con_reg_len: Length in bytes of Control registers, depends on
+ *               existence of SET and CLR registers in the layout
+ * @irq_grp_reg: Global interrupt status register, explains which
+ *               group needs attention because of a group IRQ;
+ *               if this is zero, it means that there is only one
+ *               group and the device has no irqgroup register
+ * @chip_id_reg: Chip ID Register
+ */
+struct mtk_spmi_pmic_variant {
+	const struct mtk_spmi_pmic_irq_grp *pmic_irq;
+	u8 num_groups;
+	u8 con_reg_len;
+	u8 irq_grp_reg;
+	u8 chip_id_reg;
+};
+
+/**
+ * struct mtk_spmi_pmic - Main driver structure
+ * @variant:  SPMI PMIC variant-specific data
+ * @dev:      Handle to SPMI Device
+ * @dom:      IRQ Domain of the PMIC's interrupt controller
+ * @regmap:   Handle to PMIC regmap
+ * @irq:      PMIC chained interrupt
+ */
+struct mtk_spmi_pmic {
+	const struct mtk_spmi_pmic_variant *variant;
+	struct device *dev;
+	struct irq_domain *dom;
+	struct regmap *regmap;
+	int irq;
+};
+
+static void mtk_spmi_pmic_irq_set_unmasking(struct irq_data *d, bool unmask)
+{
+	struct mtk_spmi_pmic *pmic = irq_data_get_irq_chip_data(d);
+	const struct mtk_spmi_pmic_variant *variant = pmic->variant;
+	struct regmap *regmap = pmic->regmap;
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+	unsigned short i;
+
+	for (i = 0; i < variant->num_groups; i++) {
+		const struct mtk_spmi_pmic_irq_grp *irq_grp = &variant->pmic_irq[i];
+		u32 con_reg;
+		u8 irq_en_bit;
+
+		if (hwirq < irq_grp->hwirq_base)
+			continue;
+
+		con_reg = irq_grp->con_reg + (variant->con_reg_len * i);
+		irq_en_bit = hwirq - irq_grp->hwirq_base;
+		regmap_assign_bits(regmap, con_reg, BIT(irq_en_bit), unmask);
+
+		break;
+	}
+}
+
+static void mtk_spmi_pmic_irq_mask(struct irq_data *d)
+{
+	mtk_spmi_pmic_irq_set_unmasking(d, false);
+}
+
+static void mtk_spmi_pmic_irq_unmask(struct irq_data *d)
+{
+	mtk_spmi_pmic_irq_set_unmasking(d, true);
+}
+
+static struct irq_chip mtk_spmi_pmic_irq_chip = {
+	.name = "mtk-spmi-pmic",
+	.irq_mask = mtk_spmi_pmic_irq_mask,
+	.irq_unmask = mtk_spmi_pmic_irq_unmask,
+	.flags = IRQCHIP_SKIP_SET_WAKE,
+};
+
+static struct lock_class_key mtk_spmi_pmic_irq_lock_class, mtk_spmi_pmic_irq_request_class;
+
+static int mtk_spmi_pmic_irq_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
+				       unsigned long *out_hwirq, unsigned int *out_type)
+{
+	struct mtk_spmi_pmic *pmic = d->host_data;
+	u32 intsize = fwspec->param_count;
+	u32 *intspec = fwspec->param;
+	unsigned int irq_type_index;
+	unsigned int irq_num_index;
+
+	/*
+	 * Interrupt cell index - For interrupt size 3:
+	 * [0] - SID Interrupt number
+	 * [1] - SPMI PMIC (Sub-)Device Interrupt number
+	 * [2] - Interrupt Type mask
+	 *
+	 * When only two cells are specified the SID Interrupt is not present.
+	 */
+	if (intsize != 2 && intsize != 3) {
+		dev_err(pmic->dev, "Expected IRQ specifier of size 2 or 3, got %u\n", intsize);
+		return -EINVAL;
+	}
+
+	/* irq_num_index refers to the SPMI (Sub-)Device Interrupt number */
+	irq_num_index = intsize - 2;
+	irq_type_index = irq_num_index + 1;
+
+	/*
+	 * For 3 cells, the IRQ number in intspec[0] is ignored on purpose here!
+	 *
+	 * This is because of how at least the first revision of the SPMI 2.0
+	 * controller works in MediaTek SoCs: the controller will raise an
+	 * interrupt for each SID (but doesn't know the details), and the
+	 * specific IRQ number that got raised must be read from the PMIC or
+	 * its sub-device driver.
+	 * It's possible that this will change in the future with a newer
+	 * revision of the SPMI controller, and this is why the device tree
+	 * holds the full interrupt specifier.
+	 *
+	 * out_hwirq: SPMI PMIC (Sub-)Device Interrupt number
+	 * out_type:  Interrupt type sense mask
+	 */
+	*out_hwirq = intspec[irq_num_index];
+	*out_type = intspec[irq_type_index] & IRQ_TYPE_SENSE_MASK;
+
+	return 0;
+}
+
+static int mtk_spmi_pmic_irq_alloc(struct irq_domain *d, unsigned int virq,
+				   unsigned int nr_irqs, void *data)
+{
+	struct mtk_spmi_pmic *pmic = d->host_data;
+	struct irq_fwspec *fwspec = data;
+	irq_hw_number_t hwirq;
+	unsigned int irqtype;
+	int i, ret;
+
+	ret = mtk_spmi_pmic_irq_translate(d, fwspec, &hwirq, &irqtype);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < nr_irqs; i++) {
+		irq_set_lockdep_class(virq, &mtk_spmi_pmic_irq_lock_class,
+				      &mtk_spmi_pmic_irq_request_class);
+		irq_domain_set_info(d, virq, hwirq, &mtk_spmi_pmic_irq_chip,
+				    pmic, handle_level_irq, NULL, NULL);
+	}
+
+	return 0;
+}
+
+
+static const struct irq_domain_ops mtk_spmi_pmic_irq_domain_ops = {
+	.alloc = mtk_spmi_pmic_irq_alloc,
+	.free = irq_domain_free_irqs_common,
+	.translate = mtk_spmi_pmic_irq_translate,
+};
+
+static int mtk_spmi_pmic_handle_group_irq(struct mtk_spmi_pmic *pmic, int group)
+{
+	const struct mtk_spmi_pmic_irq_grp *irq_grp = &pmic->variant->pmic_irq[group];
+	struct regmap *regmap = pmic->regmap;
+	struct device *dev = pmic->dev;
+	int i, ret;
+
+	for (i = 0; i < irq_grp->num_int_regs; i++) {
+		u32 status, saved_status;
+
+		ret = regmap_read(regmap, irq_grp->sta_reg + i, &status);
+		if (ret) {
+			dev_err(dev, "Could not read IRQ status register: %d", ret);
+			return ret;
+		}
+
+		if (status == 0)
+			continue;
+
+		saved_status = status;
+		do {
+			irq_hw_number_t hwirq;
+			u8 bit = __ffs(status);
+
+			/* Each register has 8 bits: this is the first IRQ of this group */
+			hwirq = MTK_SPMI_PMIC_VAL_BITS * i;
+
+			/* Offset by this group's start interrupt */
+			hwirq += irq_grp->hwirq_base;
+
+			/* Finally, offset by the fired IRQ's bit number */
+			hwirq += bit;
+
+			status &= ~BIT(bit);
+
+			generic_handle_domain_irq_safe(pmic->dom, hwirq);
+		} while (status);
+
+		/* Clear the interrupts by writing the previous status */
+		regmap_write(regmap, irq_grp->sta_reg + i, saved_status);
+	}
+
+	return 0;
+}
+
+static void mtk_spmi_pmic_handle_chained_irq(struct irq_desc *desc)
+{
+	struct mtk_spmi_pmic *pmic = irq_desc_get_handler_data(desc);
+	const struct mtk_spmi_pmic_variant *variant = pmic->variant;
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct regmap *regmap = pmic->regmap;
+	bool irq_handled = false;
+	int i, ret;
+	u32 val;
+
+	chained_irq_enter(chip, desc);
+
+	/* If irq_grp_reg is present there are multiple IRQ groups */
+	if (variant->irq_grp_reg > 0) {
+		ret = regmap_read(regmap, variant->irq_grp_reg, &val);
+		if (ret)
+			handle_bad_irq(desc);
+
+		/* This is very unlikely to happen */
+		if (val == 0) {
+			chained_irq_exit(chip, desc);
+			return;
+		}
+	} else {
+		val = BIT(0);
+	}
+
+	for (i = 0; i < variant->num_groups; i++) {
+		const struct mtk_spmi_pmic_irq_grp *irq_grp = &variant->pmic_irq[i];
+		u8 group_bit = BIT(irq_grp[i].group_num);
+
+		if (val & group_bit) {
+			ret = mtk_spmi_pmic_handle_group_irq(pmic, i);
+			if (ret == 0)
+				irq_handled = true;
+		}
+	}
+
+	/* The RCS flag has to be cleared even if the IRQ was not handled. */
+	ret = regmap_write(regmap, MTK_SPMI_PMIC_RCS_IRQ_DONE, 1);
+	if (ret)
+		dev_warn(pmic->dev, "Could not clear RCS flag!\n");
+
+	if (!irq_handled)
+		handle_bad_irq(desc);
+
+	chained_irq_exit(chip, desc);
+}
+
+static void mtk_spmi_pmic_irq_remove(void *data)
+{
+	struct mtk_spmi_pmic *pmic = (struct mtk_spmi_pmic *)data;
+
+	irq_set_chained_handler_and_data(pmic->irq, NULL, NULL);
+	irq_domain_remove(pmic->dom);
+}
+
+static int mtk_spmi_pmic_irq_init(struct device *dev, struct regmap *regmap,
+				  const struct mtk_spmi_pmic_variant *variant)
+{
+	struct fwnode_handle *fwnode = of_fwnode_handle(dev->of_node);
+	struct mtk_spmi_pmic *pmic;
+	int ret;
+
+	pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
+	if (!pmic)
+		return -ENOMEM;
+
+	pmic->irq = of_irq_get(dev->of_node, 0);
+	if (pmic->irq < 0)
+		return dev_err_probe(dev, pmic->irq, "Cannot get IRQ\n");
+
+	pmic->dev = dev;
+	pmic->regmap = regmap;
+	pmic->variant = variant;
+
+	pmic->dom = irq_domain_create_tree(fwnode, &mtk_spmi_pmic_irq_domain_ops, pmic);
+	if (!pmic->dom)
+		return dev_err_probe(dev, -ENOMEM, "Cannot create IRQ domain\n");
+
+	ret = devm_add_action_or_reset(dev, mtk_spmi_pmic_irq_remove, pmic);
+	if (ret) {
+		irq_domain_remove(pmic->dom);
+		return ret;
+	}
+
+	irq_set_chained_handler_and_data(pmic->irq, mtk_spmi_pmic_handle_chained_irq, pmic);
+
+	return 0;
+}
+
+#define MTK_SPMI_PMIC_IRQ_GROUP(pmic, group_name, group_index, first_irq, last_irq)	\
+{											\
+	.hwirq_base = first_irq,							\
+	.num_int_regs = ((last_irq - first_irq) / MTK_SPMI_PMIC_VAL_BITS) + 1,		\
+	.con_reg = pmic##_REG_##group_name##_TOP_INT_CON0,				\
+	.sta_reg = pmic##_REG_##group_name##_TOP_INT_STATUS0,				\
+	.group_num = group_index,							\
+}
+
+static const struct mtk_spmi_pmic_irq_grp mt6363_irq_groups[] = {
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6363, BUCK,	0,  0,   9),
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6363, LDO,	1, 16,  40),
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6363, PSC,	2, 48,  57),
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6363, MISC,	3, 64,  79),
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6363, HK,	4, 80,  87),
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6363, BM,	6, 88, 107)
+};
+
+static const struct mtk_spmi_pmic_irq_grp mt6373_irq_groups[] = {
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6373, BUCK,	0,  0,  9),
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6373, LDO,	1, 16, 39),
+	MTK_SPMI_PMIC_IRQ_GROUP(MT6373, MISC,	3, 56, 71),
+};
+
+static const struct mtk_spmi_pmic_variant mt6363_variant = {
+	.pmic_irq = mt6363_irq_groups,
+	.num_groups = ARRAY_SIZE(mt6363_irq_groups),
+	.con_reg_len = 3,
+	.irq_grp_reg = MT6363_REG_TOP_INT_STATUS1,
+	.chip_id_reg = MTK_SPMI_PMIC_REG_CHIP_ID,
+};
+
+static const struct mtk_spmi_pmic_variant mt6373_variant = {
+	.pmic_irq = mt6373_irq_groups,
+	.num_groups = ARRAY_SIZE(mt6373_irq_groups),
+	.con_reg_len = 3,
+	.irq_grp_reg = MT6373_REG_TOP_INT_STATUS1,
+	.chip_id_reg = MTK_SPMI_PMIC_REG_CHIP_ID,
+};
+
+static const struct regmap_config mtk_spmi_regmap_config = {
+	.reg_bits	= 16,
+	.val_bits	= MTK_SPMI_PMIC_VAL_BITS,
+	.max_register	= 0xffff,
+	.fast_io	= true,
+};
+
+static int mtk_spmi_pmic_probe(struct spmi_device *sdev)
+{
+	const struct mtk_spmi_pmic_variant *variant;
+	struct device *dev = &sdev->dev;
+	struct regmap *regmap;
+	int ret;
+
+	regmap = devm_regmap_init_spmi_ext(sdev, &mtk_spmi_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	variant = (const struct mtk_spmi_pmic_variant *)device_get_match_data(dev);
+	if (variant && variant->num_groups) {
+		ret = mtk_spmi_pmic_irq_init(dev, regmap, variant);
+		if (ret)
+			return ret;
+	}
+
+	return devm_of_platform_populate(dev);
+}
+
+static const struct of_device_id mtk_pmic_spmi_id_table[] = {
+	{ .compatible = "mediatek,mt6363", .data = &mt6363_variant },
+	{ .compatible = "mediatek,mt6373", .data = &mt6373_variant },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mtk_pmic_spmi_id_table);
+
+static struct spmi_driver mtk_spmi_pmic_driver = {
+	.probe = mtk_spmi_pmic_probe,
+	.driver = {
+		.name = "mtk-spmi-pmic",
+		.of_match_table = mtk_pmic_spmi_id_table,
+	},
+};
+module_spmi_driver(mtk_spmi_pmic_driver);
+
+MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
+MODULE_DESCRIPTION("MediaTek SPMI PMIC driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/mt63x3_spmi/registers.h b/include/linux/mfd/mt63x3_spmi/registers.h
new file mode 100644
index 000000000000..808927280b40
--- /dev/null
+++ b/include/linux/mfd/mt63x3_spmi/registers.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 MediaTek Inc.
+ * Copyright (c) 2025 Collabora Ltd
+ */
+
+#ifndef __MFD_MT63X3_SPMI_REGISTERS_H__
+#define __MFD_MT63X3_SPMI_REGISTERS_H__
+
+/* MT6363 PMIC Registers */
+#define MT6363_REG_MISC_TOP_INT_CON0		0x37
+#define MT6363_REG_MISC_TOP_INT_STATUS0		0x43
+#define MT6363_REG_TOP_INT_STATUS1		0x4e
+#define MT6363_REG_PSC_TOP_INT_CON0		0x90f
+#define MT6363_REG_PSC_TOP_INT_STATUS0		0x91b
+#define MT6363_REG_BM_TOP_INT_CON0		0xc24
+#define MT6363_REG_BM_TOP_INT_STATUS0		0xc36
+#define MT6363_REG_HK_TOP_INT_CON0		0xf92
+#define MT6363_REG_HK_TOP_INT_STATUS0		0xf9e
+#define MT6363_REG_BUCK_TOP_INT_CON0		0x1411
+#define MT6363_REG_BUCK_TOP_INT_STATUS0		0x141d
+#define MT6363_REG_LDO_TOP_INT_CON0		0x1b11
+#define MT6363_REG_LDO_TOP_INT_STATUS0		0x1b29
+
+/* MT6373 PMIC Registers */
+#define MT6373_REG_MISC_TOP_INT_CON0		0x3c
+#define MT6373_REG_MISC_TOP_INT_STATUS0		0x48
+#define MT6373_REG_TOP_INT_STATUS1		0x53
+#define MT6373_REG_BUCK_TOP_INT_CON0		0x1411
+#define MT6373_REG_BUCK_TOP_INT_STATUS0		0x141d
+#define MT6373_REG_LDO_TOP_INT_CON0		0x1b10
+#define MT6373_REG_LDO_TOP_INT_STATUS0		0x1b22
+
+#endif /* __MFD_MT63X3_SPMI_REGISTERS_H__ */
-- 
2.53.0



^ permalink raw reply related

* [PATCH v12 2/5] regulator: Add support for MediaTek MT6373 SPMI PMIC Regulators
From: AngeloGioacchino Del Regno @ 2026-05-11 10:13 UTC (permalink / raw)
  To: linux-mediatek
  Cc: lee, robh, krzk+dt, conor+dt, matthias.bgg,
	angelogioacchino.delregno, lgirdwood, broonie, devicetree,
	linux-kernel, linux-arm-kernel, kernel, wenst
In-Reply-To: <20260511101355.122478-1-angelogioacchino.delregno@collabora.com>

Add a driver for the regulators found on the MediaTek MT6373 PMIC,
fully controlled by SPMI interface.
Similarly to MT6363, this PMIC regulates voltage with input range
of 2.6-5.0V, and features 10 buck converters and 25 LDOs.

This PMIC is usually found on board designs using the MT6991 or
MT8196 SoC, in combination with the MT6363 PMIC.

Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 drivers/regulator/Kconfig                  |  10 +
 drivers/regulator/Makefile                 |   1 +
 drivers/regulator/mt6373-regulator.c       | 772 +++++++++++++++++++++
 include/linux/regulator/mt6373-regulator.h | 161 +++++
 4 files changed, 944 insertions(+)
 create mode 100644 drivers/regulator/mt6373-regulator.c
 create mode 100644 include/linux/regulator/mt6373-regulator.h

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index d71dac9436e3..d1ccc1da2f32 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -991,6 +991,16 @@ config REGULATOR_MT6370
 	  This driver supports the control for DisplayBias voltages and one
 	  general purpose LDO which is commonly used to drive the vibrator.
 
+config REGULATOR_MT6373
+	tristate "MT6373 SPMI PMIC regulator driver"
+	depends on SPMI
+	select REGMAP_SPMI
+	help
+	  Say Y here to enable support for buck and LDO regulators found in
+	  the MediaTek MT6373 SPMI PMIC and its variants.
+	  This driver supports the control of different power rails of device
+	  through regulator interface.
+
 config REGULATOR_MT6380
 	tristate "MediaTek MT6380 PMIC"
 	depends on MTK_PMIC_WRAP
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 35639f3115fd..a0195e28c8e6 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -117,6 +117,7 @@ obj-$(CONFIG_REGULATOR_MT6359)	+= mt6359-regulator.o
 obj-$(CONFIG_REGULATOR_MT6360) += mt6360-regulator.o
 obj-$(CONFIG_REGULATOR_MT6363) += mt6363-regulator.o
 obj-$(CONFIG_REGULATOR_MT6370) += mt6370-regulator.o
+obj-$(CONFIG_REGULATOR_MT6373) += mt6373-regulator.o
 obj-$(CONFIG_REGULATOR_MT6380)	+= mt6380-regulator.o
 obj-$(CONFIG_REGULATOR_MT6397)	+= mt6397-regulator.o
 obj-$(CONFIG_REGULATOR_MTK_DVFSRC) += mtk-dvfsrc-regulator.o
diff --git a/drivers/regulator/mt6373-regulator.c b/drivers/regulator/mt6373-regulator.c
new file mode 100644
index 000000000000..90672ae1eb80
--- /dev/null
+++ b/drivers/regulator/mt6373-regulator.c
@@ -0,0 +1,772 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2024 MediaTek Inc.
+// Copyright (c) 2025 Collabora Ltd
+//                    AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/devm-helpers.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/spmi.h>
+
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/mt6373-regulator.h>
+#include <linux/regulator/of_regulator.h>
+
+#define MT6373_REGULATOR_MODE_NORMAL	0
+#define MT6373_REGULATOR_MODE_FCCM	1
+#define MT6373_REGULATOR_MODE_LP	2
+#define MT6373_REGULATOR_MODE_ULP	3
+
+#define EN_SET_OFFSET			0x1
+#define EN_CLR_OFFSET			0x2
+
+#define OC_IRQ_ENABLE_DELAY_MS		10
+
+/* Unlock key for mode setting */
+#define MT6373_BUCK_TOP_UNLOCK_VALUE	0x5543
+
+enum {
+	MT6373_ID_VBUCK0,
+	MT6373_ID_VBUCK1,
+	MT6373_ID_VBUCK2,
+	MT6373_ID_VBUCK3,
+	MT6373_ID_VBUCK4,
+	MT6373_ID_VBUCK5,
+	MT6373_ID_VBUCK6,
+	MT6373_ID_VBUCK7,
+	MT6373_ID_VBUCK8,
+	MT6373_ID_VBUCK9,
+	MT6373_ID_VANT18,
+	MT6373_ID_VAUD18,
+	MT6373_ID_VAUX18,
+	MT6373_ID_VCN18IO,
+	MT6373_ID_VCN33_1,
+	MT6373_ID_VCN33_2,
+	MT6373_ID_VCN33_3,
+	MT6373_ID_VEFUSE,
+	MT6373_ID_VFP,
+	MT6373_ID_VIBR,
+	MT6373_ID_VIO28,
+	MT6373_ID_VMC,
+	MT6373_ID_VMCH,
+	MT6373_ID_VMCH_EINT_HIGH,
+	MT6373_ID_VMCH_EINT_LOW,
+	MT6373_ID_VRF09_AIF,
+	MT6373_ID_VRF12_AIF,
+	MT6373_ID_VRF13_AIF,
+	MT6373_ID_VRF18_AIF,
+	MT6373_ID_VRFIO18_AIF,
+	MT6373_ID_VSRAM_DIGRF_AIF,
+	MT6373_ID_VTP,
+	MT6373_ID_VUSB,
+};
+
+/**
+ * struct mt6373_regulator_info - MT6373 regulators information
+ * @desc: Regulator description structure
+ * @lp_mode_reg: Low Power mode register (normal/idle)
+ * @lp_mode_mask: Low Power mode regulator mask
+ * @modeset_reg: AUTO/PWM mode register
+ * @modeset_mask: AUTO/PWM regulator mask
+ * @oc_work: Delayed work for enabling overcurrent IRQ
+ * @hwirq: PMIC-Internal HW Interrupt for overcurrent event
+ * @virq: Mapped Interrupt for overcurrent event
+ */
+struct mt6373_regulator_info {
+	struct regulator_desc desc;
+	u16 lp_mode_reg;
+	u16 lp_mode_mask;
+	u16 modeset_reg;
+	u16 modeset_mask;
+	struct delayed_work oc_work;
+	u8 hwirq;
+	int virq;
+};
+
+#define MT6373_BUCK(match, vreg, min, max, step, en_reg, lp_reg,	\
+		    mset_reg, ocp_intn)					\
+[MT6373_ID_##vreg] = {							\
+	.desc = {							\
+		.name = match,						\
+		.supply_name = "vsys-"match,				\
+		.of_match = of_match_ptr(match),			\
+		.ops = &mt6373_vreg_setclr_ops,				\
+		.type = REGULATOR_VOLTAGE,				\
+		.id = MT6373_ID_##vreg,					\
+		.owner = THIS_MODULE,					\
+		.n_voltages = (max - min) / step + 1,			\
+		.min_uV = min,						\
+		.uV_step = step,					\
+		.enable_reg = en_reg,					\
+		.enable_mask = BIT(MT6373_PMIC_RG_BUCK_##vreg##_EN_BIT),\
+		.vsel_reg = MT6373_PMIC_RG_BUCK_##vreg##_VOSEL_ADDR,	\
+		.vsel_mask = MT6373_PMIC_RG_BUCK_VOSEL_MASK,		\
+		.of_map_mode = mt6373_map_mode,				\
+	},								\
+	.lp_mode_reg = lp_reg,						\
+	.lp_mode_mask = BIT(MT6373_PMIC_RG_BUCK_##vreg##_LP_BIT),	\
+	.modeset_reg = mset_reg,					\
+	.modeset_mask = BIT(MT6373_PMIC_RG_##vreg##_FCCM_BIT),		\
+	.hwirq = ocp_intn,						\
+}
+
+
+#define MT6373_LDO_L(match, vreg, in_sup, min, max, step, ocp_intn)	\
+[MT6373_ID_##vreg] = {							\
+	.desc = {							\
+		.name = match,						\
+		.supply_name = in_sup,					\
+		.of_match = of_match_ptr(match),			\
+		.ops = &mt6373_ldo_linear_ops,				\
+		.type = REGULATOR_VOLTAGE,				\
+		.id = MT6373_ID_##vreg,					\
+		.owner = THIS_MODULE,					\
+		.n_voltages = (max - min) / step + 1,			\
+		.min_uV = min,						\
+		.uV_step = step,					\
+		.enable_reg = MT6373_PMIC_RG_LDO_##vreg##_ADDR,		\
+		.enable_mask = BIT(0),					\
+		.vsel_reg = MT6373_PMIC_RG_##vreg##_VOSEL_ADDR,		\
+		.vsel_mask = MT6373_PMIC_RG_##vreg##_VOSEL_MASK,	\
+		.of_map_mode = mt6373_map_mode,				\
+	},								\
+	.lp_mode_reg = MT6373_PMIC_RG_LDO_##vreg##_ADDR,		\
+	.lp_mode_mask = BIT(1),						\
+	.hwirq = ocp_intn,						\
+}
+
+#define MT6373_LDO_VT_OPS(match, vreg, in_sup, vops, vrnum, ocp_intn)	\
+[MT6373_ID_##vreg] = {							\
+	.desc = {							\
+		.name = match,						\
+		.supply_name = in_sup,					\
+		.of_match = of_match_ptr(match),			\
+		.ops = &vops,						\
+		.type = REGULATOR_VOLTAGE,				\
+		.id = MT6373_ID_##vreg,					\
+		.owner = THIS_MODULE,					\
+		.n_voltages = ARRAY_SIZE(ldo_volt_ranges##vrnum) * 11,	\
+		.linear_ranges = ldo_volt_ranges##vrnum,		\
+		.n_linear_ranges = ARRAY_SIZE(ldo_volt_ranges##vrnum),	\
+		.linear_range_selectors_bitfield = ldos_cal_selectors,	\
+		.enable_reg = MT6373_PMIC_RG_LDO_##vreg##_ADDR,		\
+		.enable_mask = BIT(0),					\
+		.vsel_reg = MT6373_PMIC_RG_##vreg##_VOCAL_ADDR,		\
+		.vsel_mask = MT6373_PMIC_RG_LDO_VT_VOCALSEL_MASK,	\
+		.vsel_range_reg = MT6373_PMIC_RG_##vreg##_VOSEL_ADDR,	\
+		.vsel_range_mask = MT6373_PMIC_RG_LDO_VT_VOCALSEL_MASK,	\
+		.of_map_mode = mt6373_map_mode,				\
+	},								\
+	.lp_mode_reg = MT6373_PMIC_RG_LDO_##vreg##_ADDR,		\
+	.lp_mode_mask = BIT(1),						\
+	.hwirq = ocp_intn,						\
+}
+
+#define MT6373_LDO_VT(match, vreg, inp, vrnum, ocp_intn)		\
+	MT6373_LDO_VT_OPS(match, vreg, inp, mt6373_ldo_vtable_ops,	\
+			  vrnum, ocp_intn)
+
+#define MT6373_LDO_EI(match, vreg, inp, vrnum, ocp_intn)		\
+	MT6373_LDO_VT_OPS(match, vreg, inp, mt6373_vmch_eint_ops,	\
+			  vrnum, ocp_intn)
+
+static const unsigned int ldos_cal_selectors[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+};
+
+static const struct linear_range ldo_volt_ranges1[] = {
+	REGULATOR_LINEAR_RANGE(1200000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1300000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1500000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1700000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1800000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2100000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2200000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2700000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2800000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2900000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3100000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3300000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3400000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3500000, 0, 10, 10000)
+};
+
+static const struct linear_range ldo_volt_ranges2[] = {
+	REGULATOR_LINEAR_RANGE(1800000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1900000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2100000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2200000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2300000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2400000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2500000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2600000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2700000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2800000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2900000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3100000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3200000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3300000, 0, 10, 10000)
+};
+
+static const struct linear_range ldo_volt_ranges3[] = {
+	REGULATOR_LINEAR_RANGE(600000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(700000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(800000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(900000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1100000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1200000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1300000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1400000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1500000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1600000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1700000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1800000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1900000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2100000, 0, 10, 10000)
+};
+
+static const struct linear_range ldo_volt_ranges4[] = {
+	REGULATOR_LINEAR_RANGE(1200000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1300000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1500000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1700000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1800000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2500000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2600000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2700000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2800000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(2900000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3100000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3300000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3400000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(3500000, 0, 10, 10000)
+};
+
+static const struct linear_range ldo_volt_ranges5[] = {
+	REGULATOR_LINEAR_RANGE(900000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1000000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1100000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1200000, 0, 10, 10000),
+	REGULATOR_LINEAR_RANGE(1300000, 0, 10, 10000),
+};
+
+static int mt6373_vreg_enable_setclr(struct regulator_dev *rdev)
+{
+	return regmap_write(rdev->regmap, rdev->desc->enable_reg + EN_SET_OFFSET,
+			    rdev->desc->enable_mask);
+}
+
+static int mt6373_vreg_disable_setclr(struct regulator_dev *rdev)
+{
+	return regmap_write(rdev->regmap, rdev->desc->enable_reg + EN_CLR_OFFSET,
+			    rdev->desc->enable_mask);
+}
+
+static inline unsigned int mt6373_map_mode(unsigned int mode)
+{
+	switch (mode) {
+	case MT6373_REGULATOR_MODE_NORMAL:
+		return REGULATOR_MODE_NORMAL;
+	case MT6373_REGULATOR_MODE_FCCM:
+		return REGULATOR_MODE_FAST;
+	case MT6373_REGULATOR_MODE_LP:
+		return REGULATOR_MODE_IDLE;
+	case MT6373_REGULATOR_MODE_ULP:
+		return REGULATOR_MODE_STANDBY;
+	default:
+		return REGULATOR_MODE_INVALID;
+	}
+}
+
+static int mt6373_vmch_eint_enable(struct regulator_dev *rdev)
+{
+	const struct regulator_desc *rdesc = rdev->desc;
+	unsigned int val;
+	int ret;
+
+	if (rdesc->id == MT6373_ID_VMCH_EINT_HIGH)
+		val = MT6373_PMIC_RG_LDO_VMCH_EINT_POL_BIT;
+	else
+		val = 0;
+
+	ret = regmap_update_bits(rdev->regmap,
+				 MT6373_PMIC_RG_LDO_VMCH_EINT_ADDR,
+				 MT6373_PMIC_RG_LDO_VMCH_EINT_POL_BIT, val);
+	if (ret)
+		return ret;
+
+	ret = regmap_set_bits(rdev->regmap,
+			      MT6373_PMIC_RG_LDO_VMCH_ADDR,
+			      rdesc->enable_mask);
+	if (ret)
+		return ret;
+
+	return regmap_set_bits(rdev->regmap, rdesc->enable_reg, rdesc->enable_mask);
+}
+
+static int mt6373_vmch_eint_disable(struct regulator_dev *rdev)
+{
+	const struct regulator_desc *rdesc = rdev->desc;
+	int ret;
+
+	ret = regmap_clear_bits(rdev->regmap,
+				MT6373_PMIC_RG_LDO_VMCH_ADDR,
+				rdesc->enable_mask);
+	if (ret)
+		return ret;
+
+	/* Wait for VMCH discharging */
+	usleep_range(1500, 1600);
+
+	return regmap_clear_bits(rdev->regmap, rdesc->enable_reg, rdesc->enable_mask);
+}
+
+static unsigned int mt6373_regulator_get_mode(struct regulator_dev *rdev)
+{
+	struct mt6373_regulator_info *info = rdev_get_drvdata(rdev);
+	unsigned int val;
+	int ret;
+
+	if (info->modeset_reg) {
+		ret = regmap_read(rdev->regmap, info->modeset_reg, &val);
+		if (ret) {
+			dev_err(&rdev->dev, "Failed to get mt6373 mode: %d\n", ret);
+			return ret;
+		}
+
+		if (val & info->modeset_mask)
+			return REGULATOR_MODE_FAST;
+	} else {
+		val = 0;
+	};
+
+	ret = regmap_read(rdev->regmap, info->lp_mode_reg, &val);
+	val &= info->lp_mode_mask;
+	if (ret) {
+		dev_err(&rdev->dev, "Failed to get lp mode: %d\n", ret);
+		return ret;
+	}
+
+	if (val)
+		return REGULATOR_MODE_IDLE;
+	else
+		return REGULATOR_MODE_NORMAL;
+}
+
+static int mt6373_buck_unlock(struct regmap *map, bool unlock)
+{
+	u16 buf = unlock ? MT6373_BUCK_TOP_UNLOCK_VALUE : 0;
+
+	return regmap_bulk_write(map, MT6373_BUCK_TOP_KEY_PROT_LO, &buf, sizeof(buf));
+}
+
+static int mt6373_regulator_set_mode(struct regulator_dev *rdev,
+				     unsigned int mode)
+{
+	struct mt6373_regulator_info *info = rdev_get_drvdata(rdev);
+	struct regmap *regmap = rdev->regmap;
+	int cur_mode, ret;
+
+	if (!info->modeset_reg && mode == REGULATOR_MODE_FAST)
+		return -EOPNOTSUPP;
+
+	switch (mode) {
+	case REGULATOR_MODE_FAST:
+		ret = mt6373_buck_unlock(regmap, true);
+		if (ret)
+			break;
+
+		ret = regmap_set_bits(regmap, info->modeset_reg, info->modeset_mask);
+
+		mt6373_buck_unlock(regmap, false);
+		break;
+	case REGULATOR_MODE_NORMAL:
+		cur_mode = mt6373_regulator_get_mode(rdev);
+		if (cur_mode < 0) {
+			ret = cur_mode;
+			break;
+		}
+
+		if (cur_mode == REGULATOR_MODE_FAST) {
+			ret = mt6373_buck_unlock(regmap, true);
+			if (ret)
+				break;
+
+			ret = regmap_clear_bits(regmap, info->modeset_reg, info->modeset_mask);
+
+			mt6373_buck_unlock(regmap, false);
+			break;
+		} else if (cur_mode == REGULATOR_MODE_IDLE) {
+			ret = regmap_clear_bits(regmap, info->lp_mode_reg, info->lp_mode_mask);
+			if (ret == 0)
+				usleep_range(100, 200);
+		} else {
+			ret = 0;
+		}
+		break;
+	case REGULATOR_MODE_IDLE:
+		ret = regmap_set_bits(regmap, info->lp_mode_reg, info->lp_mode_mask);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (ret) {
+		dev_err(&rdev->dev, "Failed to set mode %u: %d\n", mode, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void mt6373_oc_irq_enable_work(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct mt6373_regulator_info *info =
+		container_of(dwork, struct mt6373_regulator_info, oc_work);
+
+	enable_irq(info->virq);
+}
+
+static irqreturn_t mt6373_oc_isr(int irq, void *data)
+{
+	struct regulator_dev *rdev = (struct regulator_dev *)data;
+	struct mt6373_regulator_info *info = rdev_get_drvdata(rdev);
+
+	disable_irq_nosync(info->virq);
+
+	if (regulator_is_enabled_regmap(rdev))
+		regulator_notifier_call_chain(rdev, REGULATOR_EVENT_OVER_CURRENT, NULL);
+
+	schedule_delayed_work(&info->oc_work, msecs_to_jiffies(OC_IRQ_ENABLE_DELAY_MS));
+
+	return IRQ_HANDLED;
+}
+
+static int mt6373_set_ocp(struct regulator_dev *rdev, int lim, int severity, bool enable)
+{
+	struct mt6373_regulator_info *info = rdev_get_drvdata(rdev);
+
+	/* MT6373 supports only enabling protection and does not support limits */
+	if (lim || severity != REGULATOR_SEVERITY_PROT || !enable)
+		return -EINVAL;
+
+	/* If there is no OCP interrupt, there's nothing to set */
+	if (info->virq <= 0)
+		return -EINVAL;
+
+	return devm_request_threaded_irq(&rdev->dev, info->virq, NULL,
+					 mt6373_oc_isr, IRQF_ONESHOT,
+					 info->desc.name, rdev);
+}
+
+static const struct regulator_ops mt6373_vreg_setclr_ops = {
+	.list_voltage = regulator_list_voltage_linear,
+	.map_voltage = regulator_map_voltage_linear,
+	.set_voltage_sel = regulator_set_voltage_sel_regmap,
+	.get_voltage_sel = regulator_get_voltage_sel_regmap,
+	.set_voltage_time_sel = regulator_set_voltage_time_sel,
+	.enable = mt6373_vreg_enable_setclr,
+	.disable = mt6373_vreg_disable_setclr,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_mode = mt6373_regulator_set_mode,
+	.get_mode = mt6373_regulator_get_mode,
+	.set_over_current_protection = mt6373_set_ocp,
+};
+
+static const struct regulator_ops mt6373_ldo_linear_ops = {
+	.list_voltage = regulator_list_voltage_linear,
+	.map_voltage = regulator_map_voltage_linear,
+	.set_voltage_sel = regulator_set_voltage_sel_regmap,
+	.get_voltage_sel = regulator_get_voltage_sel_regmap,
+	.set_voltage_time_sel = regulator_set_voltage_time_sel,
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_mode = mt6373_regulator_set_mode,
+	.get_mode = mt6373_regulator_get_mode,
+	.set_over_current_protection = mt6373_set_ocp,
+};
+
+static const struct regulator_ops mt6373_ldo_vtable_ops = {
+	.list_voltage = regulator_list_voltage_pickable_linear_range,
+	.map_voltage = regulator_map_voltage_pickable_linear_range,
+	.set_voltage_sel = regulator_set_voltage_sel_pickable_regmap,
+	.get_voltage_sel = regulator_get_voltage_sel_pickable_regmap,
+	.set_voltage_time_sel = regulator_set_voltage_time_sel,
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_mode = mt6373_regulator_set_mode,
+	.get_mode = mt6373_regulator_get_mode,
+	.set_over_current_protection = mt6373_set_ocp,
+};
+
+static const struct regulator_ops mt6373_vmch_eint_ops = {
+	.list_voltage = regulator_list_voltage_pickable_linear_range,
+	.map_voltage = regulator_map_voltage_pickable_linear_range,
+	.set_voltage_sel = regulator_set_voltage_sel_pickable_regmap,
+	.get_voltage_sel = regulator_get_voltage_sel_pickable_regmap,
+	.set_voltage_time_sel = regulator_set_voltage_time_sel,
+	.enable = mt6373_vmch_eint_enable,
+	.disable = mt6373_vmch_eint_disable,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_mode = mt6373_regulator_set_mode,
+	.get_mode = mt6373_regulator_get_mode,
+	.set_over_current_protection = mt6373_set_ocp,
+};
+
+/* The array is indexed by id(MT6373_ID_XXX) */
+static struct mt6373_regulator_info mt6373_regulators[] = {
+	MT6373_BUCK("vbuck0", VBUCK0, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK0_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK0_LP_ADDR, MT6373_PMIC_RG_BUCK0_FCCM_ADDR, 0),
+	MT6373_BUCK("vbuck1", VBUCK1, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK0_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK0_LP_ADDR, MT6373_PMIC_RG_BUCK0_FCCM_ADDR, 1),
+	MT6373_BUCK("vbuck2", VBUCK2, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK0_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK0_LP_ADDR, MT6373_PMIC_RG_BUCK0_FCCM_ADDR, 2),
+	MT6373_BUCK("vbuck3", VBUCK3, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK0_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK0_LP_ADDR, MT6373_PMIC_RG_BUCK0_FCCM_ADDR, 3),
+	MT6373_BUCK("vbuck4", VBUCK4, 0, 0, 1, MT6373_PMIC_RG_BUCK0_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK0_LP_ADDR, MT6373_PMIC_RG_BUCK0_1_FCCM_ADDR, 4),
+	MT6373_BUCK("vbuck5", VBUCK5, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK0_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK0_LP_ADDR, MT6373_PMIC_RG_BUCK0_1_FCCM_ADDR, 5),
+	MT6373_BUCK("vbuck6", VBUCK6, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK0_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK0_LP_ADDR, MT6373_PMIC_RG_BUCK0_1_FCCM_ADDR, 6),
+	MT6373_BUCK("vbuck7", VBUCK7, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK0_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK0_LP_ADDR, MT6373_PMIC_RG_BUCK0_1_FCCM_ADDR, 7),
+	MT6373_BUCK("vbuck8", VBUCK8, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK1_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK1_LP_ADDR, MT6373_PMIC_RG_BUCK1_FCCM_ADDR, 8),
+	MT6373_BUCK("vbuck9", VBUCK9, 0, 1193750, 6250, MT6373_PMIC_RG_BUCK1_EN_ADDR,
+		    MT6373_PMIC_RG_BUCK1_LP_ADDR, MT6373_PMIC_RG_BUCK1_FCCM_ADDR, 9),
+	MT6373_LDO_VT("vant18", VANT18, "vs1-ldo1", 3, 28),
+	MT6373_LDO_VT("vaud18", VAUD18, "vs1-ldo1", 3, 16),
+	MT6373_LDO_VT("vaux18", VAUX18, "vsys-ldo2", 2, 18),
+	MT6373_LDO_VT("vcn18io", VCN18IO, "vs1-ldo1", 3, 25),
+	MT6373_LDO_VT("vcn33-1", VCN33_1, "vsys-ldo1", 4, 22),
+	MT6373_LDO_VT("vcn33-2", VCN33_2, "vsys-ldo1", 4, 23),
+	MT6373_LDO_VT("vcn33-3", VCN33_3, "vsys-ldo2", 4, 24),
+	MT6373_LDO_VT("vefuse", VEFUSE, "vsys-ldo2", 1, 31),
+	MT6373_LDO_VT("vfp", VFP, "vsys-ldo2", 1, 36),
+	MT6373_LDO_VT("vibr", VIBR, "vsys-ldo2", 1, 34),
+	MT6373_LDO_VT("vio28", VIO28, "vsys-ldo2", 1, 35),
+	MT6373_LDO_VT("vmc", VMC, "vsys-ldo1", 1, 33),
+	MT6373_LDO_VT("vmch", VMCH, "vsys-ldo3", 4, 32),
+	MT6373_LDO_EI("vmch-eint-high", VMCH_EINT_HIGH, "vsys-ldo3", 4, 0),
+	MT6373_LDO_EI("vmch-eint-low", VMCH_EINT_LOW, "vsys-ldo3", 4, 0),
+	MT6373_LDO_VT("vrf09-aif", VRF09_AIF, "vs3-ldo1", 3, 26),
+	MT6373_LDO_VT("vrf12-aif", VRF12_AIF, "vs2-ldo1", 5, 27),
+	MT6373_LDO_VT("vrf13-aif", VRF13_AIF, "vs2-ldo1", 3, 19),
+	MT6373_LDO_VT("vrf18-aif", VRF18_AIF, "vs1-ldo1", 3, 20),
+	MT6373_LDO_VT("vrfio18-aif", VRFIO18_AIF, "vs1-ldo1", 3, 25),
+	MT6373_LDO_L("vsram-digrf-aif", VSRAM_DIGRF_AIF, "vs3-ldo1", 400000, 1193750, 6250, 29),
+	MT6373_LDO_VT("vtp", VTP, "vsys-ldo2", 1, 37),
+	MT6373_LDO_VT("vusb", VUSB, "vsys-ldo2", 1, 17)
+};
+
+static void mt6373_irq_remove(void *data)
+{
+	int *virq = data;
+
+	irq_dispose_mapping(*virq);
+}
+
+static void mt6373_spmi_remove(void *data)
+{
+	struct spmi_device *sdev = data;
+
+	spmi_device_remove(sdev);
+};
+
+static struct regmap *mt6373_spmi_register_regmap(struct device *dev)
+{
+	struct regmap_config mt6373_regmap_config = {
+		.reg_bits = 16,
+		.val_bits = 16,
+		.max_register = 0x1f90,
+		.fast_io = true,
+	};
+	struct spmi_device *sdev, *sparent;
+	u32 base;
+	int ret;
+
+	if (!dev->parent)
+		return ERR_PTR(-ENODEV);
+
+	ret = device_property_read_u32(dev, "reg", &base);
+	if (ret)
+		return ERR_PTR(ret);
+
+	sparent = to_spmi_device(dev->parent);
+	if (!sparent)
+		return ERR_PTR(-ENODEV);
+
+	sdev = spmi_device_alloc(sparent->ctrl);
+	if (!sdev)
+		return ERR_PTR(-ENODEV);
+
+	sdev->usid = sparent->usid;
+	dev_set_name(&sdev->dev, "%d-%02x-regulator", sdev->ctrl->nr, sdev->usid);
+	ret = device_add(&sdev->dev);
+	if (ret) {
+		put_device(&sdev->dev);
+		return ERR_PTR(ret);
+	};
+
+	ret = devm_add_action_or_reset(dev, mt6373_spmi_remove, sdev);
+	if (ret)
+		return ERR_PTR(ret);
+
+	mt6373_regmap_config.reg_base = base;
+
+	return devm_regmap_init_spmi_ext(sdev, &mt6373_regmap_config);
+}
+
+static int mt6373_regulator_probe(struct platform_device *pdev)
+{
+	struct device_node *interrupt_parent;
+	struct regulator_config config = {};
+	struct mt6373_regulator_info *info;
+	struct device *dev = &pdev->dev;
+	struct regulator_dev *rdev;
+	struct irq_domain *domain;
+	struct irq_fwspec fwspec;
+	struct spmi_device *sdev;
+	bool is_vbuck4_hw_ctrl;
+	bool is_cw_variant;
+	int i, ret;
+	u32 val;
+
+	config.regmap = mt6373_spmi_register_regmap(dev);
+	if (IS_ERR(config.regmap))
+		return dev_err_probe(dev, PTR_ERR(config.regmap),
+				     "Cannot get regmap\n");
+	config.dev = dev;
+	sdev = to_spmi_device(dev->parent);
+	dev_set_drvdata(dev, config.regmap);
+
+	interrupt_parent = of_irq_find_parent(dev->of_node);
+	if (!interrupt_parent)
+		return -EINVAL;
+
+	domain = irq_find_host(interrupt_parent);
+	of_node_put(interrupt_parent);
+	fwspec.fwnode = domain->fwnode;
+
+	fwspec.param_count = 3;
+	fwspec.param[0] = sdev->usid;
+	fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
+
+	/*
+	 * The first read may fail if the bootloader sets sleep mode: wake up
+	 * this PMIC with W/R on the SPMI bus and ignore the first result.
+	 */
+	regmap_read(config.regmap, MT6373_PLG_CFG_ELR1, &val);
+
+	/* Read PMIC variant information */
+	ret = regmap_read(config.regmap, MT6373_PLG_CFG_ELR1, &val);
+	if (ret)
+		return dev_err_probe(dev, ret, "Cannot read ID register\n");
+
+	val = FIELD_GET(MT6373_ELR_VARIANT_MASK, val);
+	is_cw_variant = (val == MT6373_ELR_VARIANT_MT6373CW);
+
+	/* Read Reserved-SW information */
+	ret = regmap_read(config.regmap, MT6373_RG_RSV_SWREG_H, &val);
+	if (ret)
+		return dev_err_probe(dev, ret, "Cannot read RSV_SW register\n");
+
+	is_vbuck4_hw_ctrl = val & MT6373_RG_RSV_SWREG_VBUCK4_HW_CTRL;
+
+	for (i = 0; i < ARRAY_SIZE(mt6373_regulators); i++) {
+		info = &mt6373_regulators[i];
+
+		/* MT6373CW VBUCK4 constraints are different */
+		if (info->desc.id == MT6373_ID_VBUCK4) {
+			unsigned int vbuck4_max_uV;
+
+			/* VBUCK4 vreg software control not allowed in hw_ctrl mode */
+			if (is_vbuck4_hw_ctrl)
+				continue;
+
+			if (is_cw_variant) {
+				info->desc.uV_step = 6250;
+				vbuck4_max_uV = 1193750;
+			} else {
+				info->desc.uV_step = 13875;
+				vbuck4_max_uV = 2650125;
+			}
+			info->desc.n_voltages = vbuck4_max_uV / info->desc.uV_step + 1;
+		}
+
+		fwspec.param[0] = to_spmi_device(dev->parent)->usid;
+		fwspec.param[1] = info->hwirq;
+		info->virq = irq_create_fwspec_mapping(&fwspec);
+		if (!info->virq)
+			return dev_err_probe(dev, -EINVAL,
+					     "Failed to map IRQ%d\n", info->hwirq);
+
+		ret = devm_add_action_or_reset(dev, mt6373_irq_remove, &info->virq);
+		if (ret) {
+			irq_dispose_mapping(info->virq);
+			return ret;
+		}
+
+		config.driver_data = info;
+		INIT_DELAYED_WORK(&info->oc_work, mt6373_oc_irq_enable_work);
+
+		rdev = devm_regulator_register(dev, &info->desc, &config);
+		if (IS_ERR(rdev))
+			return dev_err_probe(dev, PTR_ERR(rdev),
+					     "failed to register %s\n", info->desc.name);
+	}
+
+	return 0;
+}
+
+static void mt6373_regulator_shutdown(struct platform_device *pdev)
+{
+	struct regmap *regmap = dev_get_drvdata(&pdev->dev);
+
+	regmap_write(regmap, MT6373_TOP_CFG_ELR5, MT6373_TOP_CFG_ELR5_SHUTDOWN);
+}
+
+static const struct of_device_id mt6373_regulator_match[] = {
+	{ .compatible = "mediatek,mt6373-regulator" },
+	{ /* sentinel */ }
+};
+
+static struct platform_driver mt6373_regulator_driver = {
+	.driver = {
+		.name = "mt6373-regulator",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+		.of_match_table = mt6373_regulator_match,
+	},
+	.probe = mt6373_regulator_probe,
+	.shutdown = mt6373_regulator_shutdown
+};
+module_platform_driver(mt6373_regulator_driver);
+
+MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
+MODULE_DESCRIPTION("MediaTek MT6373 PMIC Regulator Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/regulator/mt6373-regulator.h b/include/linux/regulator/mt6373-regulator.h
new file mode 100644
index 000000000000..dd791717d2a1
--- /dev/null
+++ b/include/linux/regulator/mt6373-regulator.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2024 MediaTek Inc.
+ * Copyright (c) 2025 Collabora Ltd
+ */
+
+#include <linux/bits.h>
+
+#ifndef __LINUX_REGULATOR_MT6373_H
+#define __LINUX_REGULATOR_MT6373_H
+
+/* Register */
+#define MT6373_TOP_CFG_ELR5			0x117
+#define MT6373_TOP_CFG_ELR5_SHUTDOWN		BIT(0)
+
+#define MT6373_PMIC_RG_BUCK0_EN_ADDR		0x210
+#define MT6373_PMIC_RG_BUCK_VBUCK0_EN_BIT	0
+#define MT6373_PMIC_RG_BUCK_VBUCK1_EN_BIT	1
+#define MT6373_PMIC_RG_BUCK_VBUCK2_EN_BIT	2
+#define MT6373_PMIC_RG_BUCK_VBUCK3_EN_BIT	3
+#define MT6373_PMIC_RG_BUCK_VBUCK4_EN_BIT	4
+#define MT6373_PMIC_RG_BUCK_VBUCK5_EN_BIT	5
+#define MT6373_PMIC_RG_BUCK_VBUCK6_EN_BIT	6
+#define MT6373_PMIC_RG_BUCK_VBUCK7_EN_BIT	7
+
+#define MT6373_PMIC_RG_BUCK1_EN_ADDR		0x213
+#define MT6373_PMIC_RG_BUCK_VBUCK8_EN_BIT	0
+#define MT6373_PMIC_RG_BUCK_VBUCK9_EN_BIT	1
+
+#define MT6373_PMIC_RG_BUCK0_LP_ADDR		0x216
+#define MT6373_PMIC_RG_BUCK_VBUCK0_LP_BIT	0
+#define MT6373_PMIC_RG_BUCK_VBUCK1_LP_BIT	1
+#define MT6373_PMIC_RG_BUCK_VBUCK2_LP_BIT	2
+#define MT6373_PMIC_RG_BUCK_VBUCK3_LP_BIT	3
+#define MT6373_PMIC_RG_BUCK_VBUCK4_LP_BIT	4
+#define MT6373_PMIC_RG_BUCK_VBUCK5_LP_BIT	5
+#define MT6373_PMIC_RG_BUCK_VBUCK6_LP_BIT	6
+#define MT6373_PMIC_RG_BUCK_VBUCK7_LP_BIT	7
+
+#define MT6373_PMIC_RG_BUCK1_LP_ADDR		0x219
+#define MT6373_PMIC_RG_BUCK_VBUCK8_LP_BIT	0
+#define MT6373_PMIC_RG_BUCK_VBUCK9_LP_BIT	1
+
+#define MT6373_PMIC_RG_BUCK_VBUCK0_VOSEL_ADDR	0x21c
+#define MT6373_PMIC_RG_BUCK_VBUCK1_VOSEL_ADDR	0x21d
+#define MT6373_PMIC_RG_BUCK_VBUCK2_VOSEL_ADDR	0x21e
+#define MT6373_PMIC_RG_BUCK_VBUCK3_VOSEL_ADDR	0x21f
+#define MT6373_PMIC_RG_BUCK_VBUCK4_VOSEL_ADDR	0x220
+#define MT6373_PMIC_RG_BUCK_VBUCK5_VOSEL_ADDR	0x221
+#define MT6373_PMIC_RG_BUCK_VBUCK6_VOSEL_ADDR	0x222
+#define MT6373_PMIC_RG_BUCK_VBUCK7_VOSEL_ADDR	0x223
+#define MT6373_PMIC_RG_BUCK_VBUCK8_VOSEL_ADDR	0x224
+#define MT6373_PMIC_RG_BUCK_VBUCK9_VOSEL_ADDR	0x225
+#define MT6373_PMIC_RG_BUCK_VOSEL_MASK		GENMASK(8, 0)
+
+#define MT6373_PLG_CFG_ELR1			0x37b
+#define MT6373_ELR_VARIANT_MASK			GENMASK(3, 2)
+#define MT6373_ELR_VARIANT_MT6373CW		1
+#define MT6373_RG_RSV_SWREG_H			0x9d9
+#define MT6373_RG_RSV_SWREG_VBUCK4_HW_CTRL	BIT(0)
+
+#define MT6373_BUCK_TOP_KEY_PROT_LO		0x13fa
+
+#define MT6373_PMIC_RG_BUCK1_FCCM_ADDR		0x196d
+#define MT6373_PMIC_RG_VBUCK8_FCCM_BIT		6
+#define MT6373_PMIC_RG_VBUCK9_FCCM_BIT		7
+
+#define MT6373_PMIC_RG_BUCK0_FCCM_ADDR		0x1a02
+#define MT6373_PMIC_RG_VBUCK0_FCCM_BIT		0
+#define MT6373_PMIC_RG_VBUCK1_FCCM_BIT		1
+#define MT6373_PMIC_RG_VBUCK2_FCCM_BIT		2
+#define MT6373_PMIC_RG_VBUCK3_FCCM_BIT		3
+
+#define MT6373_PMIC_RG_BUCK0_1_FCCM_ADDR	0x1a82
+#define MT6373_PMIC_RG_VBUCK4_FCCM_BIT		0
+#define MT6373_PMIC_RG_VBUCK5_FCCM_BIT		1
+#define MT6373_PMIC_RG_VBUCK6_FCCM_BIT		2
+#define MT6373_PMIC_RG_VBUCK7_FCCM_BIT		3
+
+#define MT6373_PMIC_RG_VSRAM_DIGRF_AIF_VOSEL_ADDR 0x1b09
+#define MT6373_PMIC_RG_VSRAM_DIGRF_AIF_VOSEL_MASK GENMASK(6, 0)
+
+#define MT6373_PMIC_RG_LDO_VAUD18_ADDR		0x1b57
+#define MT6373_PMIC_RG_LDO_VUSB_ADDR		0x1b65
+#define MT6373_PMIC_RG_LDO_VAUX18_ADDR		0x1b73
+#define MT6373_PMIC_RG_LDO_VRF13_AIF_ADDR	0x1b81
+#define MT6373_PMIC_RG_LDO_VRF18_AIF_ADDR	0x1b8f
+#define MT6373_PMIC_RG_LDO_VRFIO18_AIF_ADDR	0x1b9d
+#define MT6373_PMIC_RG_LDO_VCN33_1_ADDR		0x1bd7
+#define MT6373_PMIC_RG_LDO_VCN33_2_ADDR		0x1be5
+#define MT6373_PMIC_RG_LDO_VCN33_3_ADDR		0x1bf3
+#define MT6373_PMIC_RG_LDO_VCN18IO_ADDR		0x1c01
+#define MT6373_PMIC_RG_LDO_VRF09_AIF_ADDR	0x1c0f
+#define MT6373_PMIC_RG_LDO_VRF12_AIF_ADDR	0x1c1d
+#define MT6373_PMIC_RG_LDO_VANT18_ADDR		0x1c57
+#define MT6373_PMIC_RG_LDO_VEFUSE_ADDR		0x1c73
+#define MT6373_PMIC_RG_LDO_VMCH_ADDR		0x1c81
+#define MT6373_PMIC_RG_LDO_VMCH_EINT_ADDR	0x1c8f
+#define MT6373_PMIC_RG_LDO_VMCH_EINT_HIGH_ADDR	MT6373_PMIC_RG_LDO_VMCH_EINT_ADDR
+#define MT6373_PMIC_RG_LDO_VMCH_EINT_LOW_ADDR	MT6373_PMIC_RG_LDO_VMCH_EINT_ADDR
+#define MT6373_PMIC_RG_LDO_VMCH_EINT_POL_BIT	BIT(2)
+#define MT6373_PMIC_RG_LDO_VMC_ADDR		0x1c90
+#define MT6373_PMIC_RG_LDO_VIBR_ADDR		0x1c9e
+#define MT6373_PMIC_RG_LDO_VIO28_ADDR		0x1cd7
+#define MT6373_PMIC_RG_LDO_VFP_ADDR		0x1ce5
+#define MT6373_PMIC_RG_LDO_VTP_ADDR		0x1cf3
+#define MT6373_PMIC_RG_LDO_VSIM1_ADDR		0x1d01
+#define MT6373_PMIC_RG_LDO_VSIM2_ADDR		0x1d10
+#define MT6373_PMIC_RG_LDO_VSIM2_LP_ADDR	0x1d10
+#define MT6373_PMIC_RG_LDO_VSRAM_DIGRF_AIF_ADDR	0x1d57
+#define MT6373_PMIC_RG_VAUX18_VOCAL_ADDR	0x1dd8
+#define MT6373_PMIC_RG_VAUX18_VOSEL_ADDR	0x1dd9
+#define MT6373_PMIC_RG_VUSB_VOCAL_ADDR		0x1ddc
+#define MT6373_PMIC_RG_VUSB_VOSEL_ADDR		0x1ddd
+#define MT6373_PMIC_RG_VCN33_1_VOCAL_ADDR	0x1de0
+#define MT6373_PMIC_RG_VCN33_1_VOSEL_ADDR	0x1de1
+#define MT6373_PMIC_RG_VCN33_2_VOCAL_ADDR	0x1de4
+#define MT6373_PMIC_RG_VCN33_2_VOSEL_ADDR	0x1de5
+#define MT6373_PMIC_RG_VCN33_3_VOCAL_ADDR	0x1de8
+#define MT6373_PMIC_RG_VCN33_3_VOSEL_ADDR	0x1de9
+#define MT6373_PMIC_RG_VMCH_VOCAL_ADDR		0x1dec
+#define MT6373_PMIC_RG_VMCH_VOSEL_ADDR		0x1ded
+#define MT6373_PMIC_RG_VMCH_EINT_HIGH_VOSEL_ADDR MT6373_PMIC_RG_VMCH_VOSEL_ADDR
+#define MT6373_PMIC_RG_VMCH_EINT_LOW_VOSEL_ADDR	MT6373_PMIC_RG_VMCH_VOSEL_ADDR
+#define MT6373_PMIC_RG_VEFUSE_VOCAL_ADDR	0x1df0
+#define MT6373_PMIC_RG_VEFUSE_VOSEL_ADDR	0x1df1
+#define MT6373_PMIC_RG_VMC_VOCAL_ADDR		0x1df4
+#define MT6373_PMIC_RG_VMCH_EINT_HIGH_VOCAL_ADDR MT6373_PMIC_RG_VMC_VOCAL_ADDR
+#define MT6373_PMIC_RG_VMCH_EINT_LOW_VOCAL_ADDR	MT6373_PMIC_RG_VMC_VOCAL_ADDR
+#define MT6373_PMIC_RG_VMC_VOSEL_ADDR		0x1df5
+#define MT6373_PMIC_RG_VIBR_VOCAL_ADDR		0x1df8
+#define MT6373_PMIC_RG_VIBR_VOSEL_ADDR		0x1df9
+#define MT6373_PMIC_RG_VIO28_VOCAL_ADDR		0x1dfc
+#define MT6373_PMIC_RG_VIO28_VOSEL_ADDR		0x1dfd
+#define MT6373_PMIC_RG_VFP_VOCAL_ADDR		0x1e00
+#define MT6373_PMIC_RG_VFP_VOSEL_ADDR		0x1e01
+#define MT6373_PMIC_RG_VTP_VOCAL_ADDR		0x1e04
+#define MT6373_PMIC_RG_VTP_VOSEL_ADDR		0x1e05
+#define MT6373_PMIC_RG_VSIM1_VOCAL_ADDR		0x1e08
+#define MT6373_PMIC_RG_VSIM1_VOSEL_ADDR		0x1e09
+#define MT6373_PMIC_RG_VSIM2_VOCAL_ADDR		0x1e0c
+#define MT6373_PMIC_RG_VSIM2_VOSEL_ADDR		0x1e0d
+#define MT6373_PMIC_RG_VAUD18_VOCAL_ADDR	0x1e58
+#define MT6373_PMIC_RG_VAUD18_VOSEL_ADDR	0x1e59
+#define MT6373_PMIC_RG_VRF18_AIF_VOCAL_ADDR	0x1e5c
+#define MT6373_PMIC_RG_VRF18_AIF_VOSEL_ADDR	0x1e5d
+#define MT6373_PMIC_RG_VCN18IO_VOCAL_ADDR	0x1e60
+#define MT6373_PMIC_RG_VCN18IO_VOSEL_ADDR	0x1e61
+#define MT6373_PMIC_RG_VRFIO18_AIF_VOCAL_ADDR	0x1e64
+#define MT6373_PMIC_RG_VRFIO18_AIF_VOSEL_ADDR	0x1e65
+#define MT6373_PMIC_RG_VANT18_VOCAL_ADDR	0x1e68
+#define MT6373_PMIC_RG_VANT18_VOSEL_ADDR	0x1e69
+#define MT6373_PMIC_RG_VRF13_AIF_VOCAL_ADDR	0x1ed8
+#define MT6373_PMIC_RG_VRF13_AIF_VOSEL_ADDR	0x1ed9
+#define MT6373_PMIC_RG_VRF12_AIF_VOCAL_ADDR	0x1edc
+#define MT6373_PMIC_RG_VRF12_AIF_VOSEL_ADDR	0x1edd
+#define MT6373_PMIC_RG_VRF09_AIF_VOCAL_ADDR	0x1f58
+#define MT6373_PMIC_RG_VRF09_AIF_VOSEL_ADDR	0x1f59
+#define MT6373_PMIC_RG_LDO_VT_VOCALSEL_MASK	GENMASK(7, 0)
+
+#endif /* __LINUX_REGULATOR_MT6373_H */
-- 
2.53.0



^ permalink raw reply related

* [PATCH v12 4/5] dt-bindings: mfd: Add binding for MediaTek MT6363 series SPMI PMIC
From: AngeloGioacchino Del Regno @ 2026-05-11 10:13 UTC (permalink / raw)
  To: linux-mediatek
  Cc: lee, robh, krzk+dt, conor+dt, matthias.bgg,
	angelogioacchino.delregno, lgirdwood, broonie, devicetree,
	linux-kernel, linux-arm-kernel, kernel, wenst,
	Nícolas F. R. A. Prado
In-Reply-To: <20260511101355.122478-1-angelogioacchino.delregno@collabora.com>

Add a binding for the MediaTek MT6363/6373 (and similar) multi
function PMICs connected over SPMI.

These PMICs are found on board designs using newer MediaTek SoCs,
such as the Dimensity 9400 Smartphone chip, or the Chromebook
MT8196 chip.

Reviewed-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 .../bindings/mfd/mediatek,mt6363.yaml         | 109 ++++++++++++++++++
 1 file changed, 109 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/mediatek,mt6363.yaml

diff --git a/Documentation/devicetree/bindings/mfd/mediatek,mt6363.yaml b/Documentation/devicetree/bindings/mfd/mediatek,mt6363.yaml
new file mode 100644
index 000000000000..ee90d16053e9
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/mediatek,mt6363.yaml
@@ -0,0 +1,109 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/mediatek,mt6363.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek MT6363 series SPMI PMICs multi-function device
+
+maintainers:
+  - AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+
+description: |
+  Some MediaTek Power Management ICs (PMICs) found in board designs with
+  the Helio, Dimensity and/or Kompanio series of SoCs are interfaced to
+  the chip via the System Power Management Interface (SPMI) bus.
+
+  These PMICs are multi-function devices with various sub modules.
+  For example, those may include one, or more of the following:
+  - Auxiliary ADC Controller
+  - Clock Controller
+  - eFuses
+  - GPIO Controller
+  - Interrupt Controller
+  - Keys
+  - LEDs Controller
+  - Regulators
+  - RTC
+
+properties:
+  compatible:
+    enum:
+      - mediatek,mt6363
+      - mediatek,mt6373
+
+  reg:
+    maxItems: 1
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+  interrupts:
+    maxItems: 1
+
+  interrupt-controller: true
+
+  "#interrupt-cells":
+    const: 3
+
+patternProperties:
+  "^adc@[0-9a-f]+$":
+    type: object
+    $ref: /schemas/iio/adc/mediatek,mt6359-auxadc.yaml#
+
+  "^regulators@[0-9a-f]+$":
+    type: object
+    oneOf:
+      - $ref: /schemas/regulator/mediatek,mt6363-regulator.yaml#
+      - $ref: /schemas/regulator/mediatek,mt6373-regulator.yaml#
+
+required:
+  - compatible
+  - reg
+  - '#address-cells'
+  - '#size-cells'
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/spmi/spmi.h>
+
+    spmi {
+      #address-cells = <2>;
+      #size-cells = <0>;
+
+      pmic@4 {
+        compatible = "mediatek,mt6363";
+        reg = <0x4 SPMI_USID>;
+        interrupts = <4 64 IRQ_TYPE_LEVEL_HIGH>;
+        interrupt-controller;
+        #address-cells = <1>;
+        #interrupt-cells = <3>;
+        #size-cells = <0>;
+
+        regulators@30 {
+          compatible = "mediatek,mt6363-regulator";
+          reg = <0x30>;
+
+          vio18 {
+            regulator-name = "pp1800-vio18-s3";
+            regulator-min-microvolt = <1800000>;
+            regulator-max-microvolt = <1800000>;
+            regulator-allowed-modes = <0 2>;
+            regulator-allow-set-load;
+            regulator-over-current-protection;
+          };
+        };
+
+        adc@1000 {
+          compatible = "mediatek,mt6363-auxadc";
+          reg = <0x1000>;
+          #io-channel-cells = <1>;
+        };
+      };
+    };
-- 
2.53.0



^ permalink raw reply related

* [PATCH v12 3/5] dt-bindings: iio: adc: mt6359: Allow reg for SPMI PMICs AuxADC
From: AngeloGioacchino Del Regno @ 2026-05-11 10:13 UTC (permalink / raw)
  To: linux-mediatek
  Cc: lee, robh, krzk+dt, conor+dt, matthias.bgg,
	angelogioacchino.delregno, lgirdwood, broonie, devicetree,
	linux-kernel, linux-arm-kernel, kernel, wenst
In-Reply-To: <20260511101355.122478-1-angelogioacchino.delregno@collabora.com>

When one or multiple Auxiliary ADC IPs are embedded in a SPMI PMIC
it is reachable at a specific address.

Allow specifying the `reg` property and make it a required one for
MediaTek MT6363/6373 PMICs as those communicate over SPMI and also
disallow it for the others (as it wouldn't be applicable).

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 .../iio/adc/mediatek,mt6359-auxadc.yaml         | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/mediatek,mt6359-auxadc.yaml b/Documentation/devicetree/bindings/iio/adc/mediatek,mt6359-auxadc.yaml
index 5d4ab701f51a..e685e5ee224a 100644
--- a/Documentation/devicetree/bindings/iio/adc/mediatek,mt6359-auxadc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/mediatek,mt6359-auxadc.yaml
@@ -25,6 +25,9 @@ properties:
       - mediatek,mt6363-auxadc
       - mediatek,mt6373-auxadc
 
+  reg:
+    maxItems: 1
+
   "#io-channel-cells":
     const: 1
 
@@ -33,3 +36,17 @@ required:
   - "#io-channel-cells"
 
 additionalProperties: false
+
+if:
+  properties:
+    compatible:
+      contains:
+        enum:
+          - mediatek,mt6363-auxadc
+          - mediatek,mt6373-auxadc
+then:
+  required:
+    - reg
+else:
+  properties:
+    reg: false
-- 
2.53.0



^ permalink raw reply related

* [PATCH PARTIAL-RESEND v12 0/5] Add support MT6316/6363/MT6373 PMICs regulators and MFD
From: AngeloGioacchino Del Regno @ 2026-05-11 10:13 UTC (permalink / raw)
  To: linux-mediatek
  Cc: lee, robh, krzk+dt, conor+dt, matthias.bgg,
	angelogioacchino.delregno, lgirdwood, broonie, devicetree,
	linux-kernel, linux-arm-kernel, kernel, wenst

Changes in v12:
 - This is a partial resend. MT6373 regulators and MFD patches were not picked.
 - Rebased over next-20260508

Changes in v11:
 - Removed unnecessary #address-cells in all mt6316 bindings

Changes in v10:
 - Added "struct" prefix to structs kerneldoc
 - Renamed struct mtk_spmi_pmic_pdata to mtk_spmi_pmic_variant
 - Added "REG_" to MT6363/73 mfd register definitions to disambiguate
 - Expanded MTK_SPMI_PMIC_IRQ_GROUP macro parameter names as suggested
 - Some rewording of comments as suggested, addition of more comments
 - Refactored IRQ domain handling due to deprecation of function
   irq_domain_add_tree() to use the new irq_domain_create_tree()
 - Fixed to use generic_handle_domain_irq_safe() to avoid races
 - Added support for two interrupt cells in translation
 - Removed .irq_lock() and .irq_unlock() in favor of lockdep classes
 - Added support for handling PMICs without IRQ Group register for
   upcoming MT6685 implementation

Changes in v9:
 - Applied more bindings fixes as pointed out by Rob
 - Changed irq fwspec to have 3 cells as the mfd driver handles 3
 - Using intsize instead of fwspec.param_count in xlate (thanks Nicolas!)

Changes in v8:
 - Added REGMAP_SPMI selection in Kconfig for all of MT6316/6363/6373
   to satisfy __devm_regmap_init_spmi_ext() dependency in case they
   are built with COMPILE_TEST (+randconfig) configuration
 - Fixed indentation in Kconfig on help lines (some were using spaces
   instead of tab + 2 spaces, don't know how that happened)
 - Removed forgotten final blank line on mt63{6,7}3-regulator.h header
 - Fixed error checks in mt6363-regulator, mt6373-regulator for call
   to mt63{6,7}e_spmi_register_regmap()
 - Tested again on MT8196 Chromebook.

Changes in v7:
 - Removed unintentionally added, useless Link tags from all patches
 - #size-cells is now required in mfd mt6363 binding
 - Further fixes in mt6363/73 regulator bindings
 - Mentioned weird 9-bits BE format and usage of undocumented set/clr
   registers in commit description for the MT6316 regulator driver
 - Refactored bindings for MT6316 PMIC (regulators):
   - Added reg, #address-cells as required properties
   - Added regulator-allowed-modes and its description
   - Changed mt6316b/mt6316c to use patternProperties instead, as it
     now makes sense to avoid duplication while keeping documentation
     for the regulator-allowed-modes property in all vbuck entries
   - Added decent examples that correctly describes the MT6316 PMICs

Changes in v6:
 - Added missing bitfield.h header inclusion in mt6363-regulator.c
 - Added commit "dt-bindings: iio: adc: mt6359: Allow reg for SPMI PMICs AuxADC"
   to fix warnings on specifying reg property in adc node
 - Added $ref in mt6363/73 regulator bindings to reduce duplication on LDOs
 - Moved MT6363 regulators example to MFD binding
 - Rebased on next-20250929

Changes in v5:
 - This time the dt-bindings commits are the right ones... sorry again :-)
 - Removed accidentally added Link: tags in all patches.

Changes in v4:
 - Rewritten all register definitions for both MT6363 and MT6373
   regulators to be register offsets instead
 - Added the appropriate supply_name to all vregs in 6363 and 6373
 - Simplified the macro parameters for all vregs in 6363 and 6373
   - Added common definitions pattern in macros to avoid plain writing
     register definitions in every macro call
 - Added registration of SPMI sub-device in MT6363/73 and setup of
   regmap reg_base based on `reg` parsed from devicetree
 - Removed interrupts parsing from devicetree
   - Moved (pmic-internal) IRQs to macros
 - mtk-spmi-pmic: Added parsing if irqspec with param_count=2 for
   easier irqs registration from regulator drivers

Changes in v3:
 - Added buck and ldo supplies to mt6363 and mt6373 drivers and bindings;
 - Removed interrupts from mt6363 and mt6373 bindings;
 - Added registering interrupts in mt6363/73 drivers instead:
   this avoids big arrays in the mfd driver, which will grow
   uncontrollably (as it already happened in multiple MediaTek
   drivers) and with each new(future) supported PMIC;
 - Removed "ldo-" and "buck-" prefixes from mt6363 regulators
   - Renamed "vbX" to "vbuckX", reflecting datasheet name
 - Changed all LDOs in MT6363 and MT6373 to add VOCAL usage, both
   increasing the number of voltage steps (2.5 or 10mV increments
   depending on the LDO) and the accuracy of the reported voltages
 - Tested again on MT8196 board

Changes in v2:
 - Merged MFD and regulator in one series
 - Split mediatek,mt6316-regulator.yaml in three files as
   suggested by krzk
 - Added interrupt-names list in MT6363/MT6373 bindings as
   suggested by krzk
 - Documented regulator modes in MT6363/73 as suggested by krzk
 - Fixed interrupt and interrupt-names maxItems in both 6363/73
   because, well... I miscounted them in v1 :-)
 - Removed keys from mt6363 binding: the compatible was not yet
   added to the keys binding and doing that will take quite a
   while, as I have to find a way to test the code before that
   as unfortunately my HW does not provide any way to test the
   PMIC keys (thought it did, but then turns out it doesn't...)
 - Completed the mt6363 MFD example with ADC as suggested by Rob
 - Avoided applying regulator schemas multiple times as pointed
   out by Rob (in mfd binding)
 - Fixed MT6363/73 issues pointed out by lkp (eh, sorry, that
   happened during a last minute cleanup... ugh!).
 - Brewed some more coffee :-)


This series adds support for three new MediaTek PMICs: MT6316, MT6363
and MT6373 and their variants - used in board designs featuring the
MediaTek MT8196 Chromebook SoC, or the MT6991 Dimensity 9400 Smartphone
SoC.

In particular, MT6316 is a regulator, but the MT6363 and MT6373 PMICs
are multi-function devices, as they have and expose multiple sub-devices;
moreover, some of those also contain an interrupt controller, managing
internal IPs interrupts: for those, a chained interrupt handler is
registered, which parent is the SPMI controller itself.

This series adds support for all of the MT6316 regulator variants and
for MT6363, MT6373 SPMI PMICs and their interrupt controller.

AngeloGioacchino Del Regno (5):
  dt-bindings: regulator: Document MediaTek MT6373 PMIC Regulators
  regulator: Add support for MediaTek MT6373 SPMI PMIC Regulators
  dt-bindings: iio: adc: mt6359: Allow reg for SPMI PMICs AuxADC
  dt-bindings: mfd: Add binding for MediaTek MT6363 series SPMI PMIC
  mfd: Add support for MediaTek SPMI PMICs and MT6363/73

 .../iio/adc/mediatek,mt6359-auxadc.yaml       |  17 +
 .../bindings/mfd/mediatek,mt6363.yaml         | 109 +++
 .../regulator/mediatek,mt6373-regulator.yaml  | 137 ++++
 drivers/mfd/Kconfig                           |  16 +
 drivers/mfd/Makefile                          |   1 +
 drivers/mfd/mtk-spmi-pmic.c                   | 427 ++++++++++
 drivers/regulator/Kconfig                     |  10 +
 drivers/regulator/Makefile                    |   1 +
 drivers/regulator/mt6373-regulator.c          | 772 ++++++++++++++++++
 include/linux/mfd/mt63x3_spmi/registers.h     |  34 +
 include/linux/regulator/mt6373-regulator.h    | 161 ++++
 11 files changed, 1685 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/mediatek,mt6363.yaml
 create mode 100644 Documentation/devicetree/bindings/regulator/mediatek,mt6373-regulator.yaml
 create mode 100644 drivers/mfd/mtk-spmi-pmic.c
 create mode 100644 drivers/regulator/mt6373-regulator.c
 create mode 100644 include/linux/mfd/mt63x3_spmi/registers.h
 create mode 100644 include/linux/regulator/mt6373-regulator.h

-- 
2.53.0



^ permalink raw reply

* [PATCH v12 1/5] dt-bindings: regulator: Document MediaTek MT6373 PMIC Regulators
From: AngeloGioacchino Del Regno @ 2026-05-11 10:13 UTC (permalink / raw)
  To: linux-mediatek
  Cc: lee, robh, krzk+dt, conor+dt, matthias.bgg,
	angelogioacchino.delregno, lgirdwood, broonie, devicetree,
	linux-kernel, linux-arm-kernel, kernel, wenst
In-Reply-To: <20260511101355.122478-1-angelogioacchino.delregno@collabora.com>

Add bindings for the regulators found in the MediaTek MT6363 PMIC,
usually found in board designs using the MT6991 Dimensity 9400 and
on MT8196 Kompanio SoC for Chromebooks, along with the MT6316 and
MT6363 PMICs.

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
 .../regulator/mediatek,mt6373-regulator.yaml  | 137 ++++++++++++++++++
 1 file changed, 137 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/regulator/mediatek,mt6373-regulator.yaml

diff --git a/Documentation/devicetree/bindings/regulator/mediatek,mt6373-regulator.yaml b/Documentation/devicetree/bindings/regulator/mediatek,mt6373-regulator.yaml
new file mode 100644
index 000000000000..4562f291fc0a
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/mediatek,mt6373-regulator.yaml
@@ -0,0 +1,137 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/regulator/mediatek,mt6373-regulator.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek MT6373 PMIC Regulators
+
+maintainers:
+  - AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+
+description:
+  The MT6373 SPMI PMIC provides 10 BUCK and 23 LDO (Low DropOut) regulators
+  and can optionally provide overcurrent warnings with one ocp interrupt
+  for each voltage regulator.
+
+properties:
+  compatible:
+    const: mediatek,mt6373-regulator
+
+  reg:
+    maxItems: 1
+
+  vsys-vbuck0-supply:
+    description: Input supply for vbuck0
+
+  vsys-vbuck1-supply:
+    description: Input supply for vbuck1
+
+  vsys-vbuck2-supply:
+    description: Input supply for vbuck2
+
+  vsys-vbuck3-supply:
+    description: Input supply for vbuck3
+
+  vsys-vbuck4-supply:
+    description: Input supply for vbuck4
+
+  vsys-vbuck5-supply:
+    description: Input supply for vbuck5
+
+  vsys-vbuck6-supply:
+    description: Input supply for vbuck6
+
+  vsys-vbuck7-supply:
+    description: Input supply for vbuck7
+
+  vsys-vbuck8-supply:
+    description: Input supply for vbuck8
+
+  vsys-vbuck9-supply:
+    description: Input supply for vbuck9
+
+  vs1-ldo1-supply:
+    description: Input supply for vant18, vaud18, vcn18io
+
+  vs2-ldo1-supply:
+    description: Input supply for vrf12-aif, vrf13-aif
+
+  vs3-ldo1-supply:
+    description: Input supply for vrf09-aif, vsram-digrf-aif
+
+  vsys-ldo1-supply:
+    description: Input supply for vcn33-1, vcn33-2, vmc
+
+  vsys-ldo2-supply:
+    description:
+      Input supply for vaux18, vcn33-3, vefuse, vfp, vibr, vio28, vtp, vusb
+
+  vsys-ldo3-supply:
+    description: Input supply for vmch, vmch-eint-high/low
+
+patternProperties:
+  "^v(ant|aud|aux)18$":
+    $ref: "#/$defs/ldo-common"
+
+  "^vbuck[0-9]$":
+    type: object
+    $ref: regulator.yaml#
+    properties:
+      regulator-allowed-modes:
+        description: |
+          Allowed Buck regulator operating modes allowed. Valid values below.
+            0 - Normal mode with automatic power saving, reducing the switching
+                frequency when light load conditions are detected
+            1 - Forced Continuous Conduction mode (FCCM) for improved voltage
+                regulation accuracy with constant switching frequency but lower
+                regulator efficiency
+            2 - Forced Low Power mode for improved regulator efficiency, used
+                when no heavy load is expected, does not limit the maximum out
+                current but unless only a light load is applied, there will be
+                regulation accuracy and efficiency losses.
+            3 - Forced Ultra Low Power mode for ultra low load, this greatly
+                reduces the maximum output power, makes the regulator to be
+                efficient only for ultra light load, and greatly reduces the
+                quiescent current (Iq) of the buck.
+        maxItems: 3
+        items:
+          enum: [ 0, 1, 2, 3 ]
+    unevaluatedProperties: false
+
+  "^v(cn18io|cn33-[123]|efuse|fp|tp|ibr|io28|sram-digrf-aif|usb)$":
+    $ref: "#/$defs/ldo-common"
+
+  "^vmc(h)?$":
+    $ref: "#/$defs/ldo-common"
+
+  "^vmch-eint-(low|high)$":
+    $ref: "#/$defs/ldo-common"
+
+  "^vrf(09|12|13|18|io18)-aif$":
+    $ref: "#/$defs/ldo-common"
+
+$defs:
+  ldo-common:
+    type: object
+    $ref: regulator.yaml#
+    unevaluatedProperties: false
+    properties:
+      regulator-allowed-modes:
+        description: |
+          Allowed LDO regulator operating modes allowed. Valid values below.
+            0 - Normal mode with automatic power saving, reducing the switching
+                frequency when light load conditions are detected
+            2 - Forced Low Power mode for improved regulator efficiency, used
+                when no heavy load is expected, does not limit the maximum out
+                current but unless only a light load is applied, there will be
+                regulation accuracy and efficiency losses.
+        maxItems: 2
+        items:
+          enum: [ 0, 2 ]
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
-- 
2.53.0



^ permalink raw reply related

* Re: [PATCH] dt-bindings: PCI: mediatek-gen3: Allow memory-region for restricted DMA buffer
From: AngeloGioacchino Del Regno @ 2026-05-11 10:04 UTC (permalink / raw)
  To: Chen-Yu Tsai, Matthias Brugger, Ryder Lee, Lorenzo Pieralisi,
	Krzysztof Wilczyński, Manivannan Sadhasivam, Rob Herring,
	Bjorn Helgaas, Krzysztof Kozlowski, Conor Dooley
  Cc: devicetree, linux-pci, linux-mediatek, linux-kernel
In-Reply-To: <20260508063633.3894348-1-wenst@chromium.org>

On 5/8/26 08:36, Chen-Yu Tsai wrote:
> On some SoCs without an IOMMU behind the PCIe controller, the PCIe
> controller memory access could be limited to a small region by the
> firmware configuring a memory protection unit. This memory region
> must be assigned to the PCIe controller so that the OS knows to
> use that region. Otherwise PCIe devices would not work properly.
> 
> Allow the memory-region property with one item pointing to a
> restricted DMA buffer.
> 
> Signed-off-by: Chen-Yu Tsai <wenst@chromium.org>

Makes a lot of sense, and actually makes us able to provide a correct hardware
description in the devicetrees.

Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>




^ permalink raw reply

* Re: [PATCH] arm64: dts: mediatek: mt8192-asurada: Move PCIe DMA bounce buffer to host
From: Chen-Yu Tsai @ 2026-05-11  9:49 UTC (permalink / raw)
  To: AngeloGioacchino Del Regno
  Cc: Matthias Brugger, linux-mediatek, devicetree, linux-arm-kernel,
	linux-kernel
In-Reply-To: <177849219930.115964.12285046748812181326.b4-ty@collabora.com>

On Mon, May 11, 2026 at 5:36 PM AngeloGioacchino Del Regno
<angelogioacchino.delregno@collabora.com> wrote:
>
> On Thu, 30 Apr 2026 20:07:24 +0800, Chen-Yu Tsai wrote:
> > The DMA bounce buffer is attached to the PCIe host controller, i.e. all
> > PCIe DMA transfers should use it.
> >
> > Move it from the PCIe (WiFi) device node down to the PCIe host
> > controller node.
> >
> >
> > [...]
>
> Applied to v7.1-next/dts64, thanks!
>
> [1/1] arm64: dts: mediatek: mt8192-asurada: Move PCIe DMA bounce buffer to host
>       commit: 0f91911b61d06fc02a058ff7fb0a27e53f7b1136

Sorry, but we actually have to wait for a matching binding change [1] to
land, or we'll get new DT warnings.


ChenYu

[1] https://lore.kernel.org/all/20260508063633.3894348-1-wenst@chromium.org/


^ permalink raw reply

* Re: [PATCH] arm64: dts: mediatek: mt8390-tungsten-smarc: add HDMI support
From: AngeloGioacchino Del Regno @ 2026-05-11  9:49 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	Gary Bisson
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260310-mtkhdmi-v1-1-841e834c8abc@gmail.com>

On Tue, 10 Mar 2026 16:20:13 +0100, Gary Bisson wrote:
> Add HDMI display out support to both Tungsten510 & Tungsten700
> platforms. HDMI audio is not covered by this patch, audio (HDMI & I2S)
> will be added as a follow-up patch.
> 
> 

Applied to v7.1-next/dts64, thanks!

[1/1] arm64: dts: mediatek: mt8390-tungsten-smarc: add HDMI support
      commit: a74ceec38c76901baa2fc09a1a744689b696a01a

Cheers,
Angelo




^ permalink raw reply

* Re: [PATCH] arm: dts: mediatek: mt6589: Add Arm Generic Timer node
From: AngeloGioacchino Del Regno @ 2026-05-11  9:47 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, matthias.bgg, Akari Tsuyukusa
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260325144500.667385-1-akkun11.open@gmail.com>

On Wed, 25 Mar 2026 23:45:00 +0900, Akari Tsuyukusa wrote:
> Add the Arm Generic Timer node to the MT6589 SoC.
> "arm,cpu-registers-not-fw-configured;" is required
> because the bootloader does not initialize the Arm Generic Timer.
> 
> 

Applied to v7.1-next/dts32, thanks!

[1/1] arm: dts: mediatek: mt6589: Add Arm Generic Timer node
      commit: 2a5d54507c68c2a8de9d6f6746f23771d38f4dbc

Cheers,
Angelo




^ permalink raw reply

* Re: [PATCH v2 0/3] arm: dts: mediatek: fix pinctl node names
From: AngeloGioacchino Del Regno @ 2026-05-11  9:47 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	David Lechner
  Cc: devicetree, linux-kernel, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260305-mtk-fix-mt7623-pinctl-name-v2-0-a68854a51065@baylibre.com>

On Thu, 05 Mar 2026 15:03:15 -0600, David Lechner wrote:
> While passing by, I noticed that the pinctrl nodes in a couple of dtsi
> files did not match the addresses in their reg properties. Here are some
> patches to fix that.
> 
> 

Applied to v7.1-next/dts32, thanks!

[1/3] arm: dts: mediatek: mt7623: fix pinctrl child node names
      commit: 23d304b97e8e20b8d786b82b4d56257dde95139c
[2/3] arm: dts: mediatek: mt7623: fix pinctrl controller node name
      commit: 85fdc6458535d234e54d0968cc816dec9003d341
[3/3] arm: dts: mediatek: mt8135: fix pinctrl node name
      commit: ba6afff1d9b70028a5fc3df2d3acbee501c20a53

Cheers,
Angelo




^ permalink raw reply

* Re: [PATCH v4 2/2] drm/mediatek: dsi: Add compatible for mt8167-dsi
From: AngeloGioacchino Del Regno @ 2026-05-11  9:46 UTC (permalink / raw)
  To: Luca Leonardo Scorcia, linux-mediatek
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	Chun-Kuang Hu, Philipp Zabel, David Airlie, Simona Vetter,
	devicetree, linux-kernel, linux-arm-kernel, dri-devel
In-Reply-To: <20260505214541.333657-3-l.scorcia@gmail.com>

On 5/5/26 23:45, Luca Leonardo Scorcia wrote:
> The mt8167 DSI controller is fully compatible with the one found in
> mt2701. Unfortunately the device tree has a dedicated compatible for
> mt8167 since 2022 and it cannot be changed with a fallback nor removed at
> this point. The only way to get the device to work is to add the
> compatible to the driver.
> 
> Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>


^ permalink raw reply

* Re: [PATCH v3 0/9] ASoC: mediatek: mt2701: HDMI audio support
From: AngeloGioacchino Del Regno @ 2026-05-11  9:43 UTC (permalink / raw)
  To: Daniel Golle, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	Jaroslav Kysela, Takashi Iwai, Cyril Chao, Arnd Bergmann,
	Nícolas F. R. A. Prado, Kuninori Morimoto, Eugen Hristev,
	linux-sound, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek
In-Reply-To: <cover.1776998727.git.daniel@makrotopia.org>

On 4/24/26 04:48, Daniel Golle wrote:
> This series wires up on-chip HDMI audio on MT2701 and MT7623N, from the
> DRM bridge down through the AFE into a small machine driver that binds
> the AFE HDMI BE to the HDMI TX codec already exposed by the
> mediatek-drm-hdmi driver. Bindings, DT and a BananaPi R2 board node
> are included.
> 
> In order to survive vblank or late hotplug of the monitor, the fix
> submitted separately [1] is required as well.
> 
> Everything here was developed for and tested on a BananaPi R2
> (MT7623N), which turns ten years old this year -- a nice occasion to
> finally land HDMI audio for a SoC which was truly ahead of its time.
> 
> [1]: https://patchwork.kernel.org/project/linux-mediatek/patch/a3e22cbae528c9a38d854a586d1736b860998d41.1776265222.git.daniel@makrotopia.org/
> 

Whole series is

Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>

I'll pick the devicetree bits after the ASoC and bindings bits get picked.

Cheers,
Angelo





^ permalink raw reply

* Re: [PATCH v3] arm64: dts: mediatek: add crypto offload support on MT7981
From: AngeloGioacchino Del Regno @ 2026-05-11  9:36 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, matthias.bgg, devicetree, linux-kernel,
	linux-arm-kernel, linux-mediatek, Aleksander Jan Bajkowski
In-Reply-To: <20260428224755.336843-1-olek2@wp.pl>

On Wed, 29 Apr 2026 00:47:43 +0200, Aleksander Jan Bajkowski wrote:
> The MT7981 as well as the MT7986 have a built-in EIP-97 rev 2.3p0 crypto
> accelerator. This commit adds the missing entry in the dts.
> 
> 

Applied to v7.1-next/dts64, thanks!

[1/1] arm64: dts: mediatek: add crypto offload support on MT7981
      commit: bd25a2e3b0768b1526a8120790453a5880f11ede

Cheers,
Angelo




^ permalink raw reply

* Re: [PATCH] arm64: dts: mediatek: mt8192-asurada: Move PCIe DMA bounce buffer to host
From: AngeloGioacchino Del Regno @ 2026-05-11  9:36 UTC (permalink / raw)
  To: Matthias Brugger, Chen-Yu Tsai
  Cc: linux-mediatek, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <20260430120725.241779-1-wenst@chromium.org>

On Thu, 30 Apr 2026 20:07:24 +0800, Chen-Yu Tsai wrote:
> The DMA bounce buffer is attached to the PCIe host controller, i.e. all
> PCIe DMA transfers should use it.
> 
> Move it from the PCIe (WiFi) device node down to the PCIe host
> controller node.
> 
> 
> [...]

Applied to v7.1-next/dts64, thanks!

[1/1] arm64: dts: mediatek: mt8192-asurada: Move PCIe DMA bounce buffer to host
      commit: 0f91911b61d06fc02a058ff7fb0a27e53f7b1136

Cheers,
Angelo




^ permalink raw reply

* Re: (subset) [PATCH 0/3] Mediatek Genio 1200-EVK: MT6315/MT6360 PMIC regulator supply cleanup
From: AngeloGioacchino Del Regno @ 2026-05-11  9:36 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	Liam Girdwood, Mark Brown, Gene Chen, Louis-Alexis Eyraud
  Cc: kernel, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek
In-Reply-To: <20260414-mtk-g1200-pmic-cleanup-v1-0-2a7193ed4e93@collabora.com>

On Tue, 14 Apr 2026 13:44:09 +0200, Louis-Alexis Eyraud wrote:
> This series goal is to cleanup the power supplies of MT6315 and MT6360
> PMIC regulators, that are either missing or incorrect in the Mediatek
> Genio 1200-EVK board devicetree.
> 
> Patch 1 completes the MT6360 dt-bindings by adding the missing power
> supply descriptions for its buck regulators, that already handled by
> the mt6360 regulator driver.
> Patch 2 adds for the board the MT6315 regulator supply properties, that
> were added in the dt-bindings by [1].
> Patch 3 adds for the board the MT6360 regulator supply properties and
> fixes the existing one.
> 
> [...]

Applied to v7.1-next/dts64, thanks!

[2/3] arm64: dts: mediatek: mt8395-genio-common: add MT6315 PMIC supplies
      commit: 96d997e13892479b5bb852e42ac65bbaef86ba9a
[3/3] arm64: dts: mediatek: mt8395-genio-common: add MT6360 PMIC supplies
      commit: 2585bab2cdf4331068c5bf626f6625c38c8d299e

Cheers,
Angelo




^ permalink raw reply

* Re: [PATCH 0/3] Mediatek Genio 510/700-EVK: add CPU power supplies
From: AngeloGioacchino Del Regno @ 2026-05-11  9:36 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	Louis-Alexis Eyraud
  Cc: kernel, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek
In-Reply-To: <20260414-mtk-g510-700-cpu-supplies-v1-0-3b8313e5ca8d@collabora.com>

On Tue, 14 Apr 2026 12:33:30 +0200, Louis-Alexis Eyraud wrote:
> This series adds for the Mediatek Genio 510-EVK (MT8370) and 700-EVK
> (MT8390) boards the CPU power supply definitions in their devicetree
> that are missing for all their CPU cores.
> 
> On the boards, the big core power is supplied by a MT6319 (sub PMIC)
> and little core power by a MT6365 (main PMIC).
> 
> [...]

Applied to v7.1-next/dts64, thanks!

[1/3] arm64: dts: mediatek: mt8390-genio-common: add MT6319 PMIC support
      commit: b049df10aea8222a42d42433b675b7b0d013c4dd
[2/3] arm64: dts: mediatek: mt8390-genio-common: add CPU power supplies
      commit: 728a38437d8268a9a43405d476fb678d37f86cd7
[3/3] arm64: dts: mediatek: mt8390-genio-700-evk: add specific CPU power supplies
      commit: e6bc454bb7ec547ccd371482b8692a87c5b58245

Cheers,
Angelo




^ 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