* [PATCH net-next v5 1/3] net: airoha: use int instead of atomic_t for qdma users counter
From: Lorenzo Bianconi @ 2026-06-11 21:55 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260611-airoha-ethtool-priv_flags-v5-0-c11de08486d1@kernel.org>
QDMA users counter is always accessed holding RTNL lock so we do not
require atomic_t for it.
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 4 ++--
drivers/net/ethernet/airoha/airoha_eth.h | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 504247c8bae9..9eb9b6a139b3 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1812,7 +1812,7 @@ static int airoha_dev_open(struct net_device *netdev)
airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
GLOBAL_CFG_TX_DMA_EN_MASK |
GLOBAL_CFG_RX_DMA_EN_MASK);
- atomic_inc(&qdma->users);
+ qdma->users++;
if (!airoha_is_lan_gdm_dev(dev) &&
airoha_ppe_is_enabled(qdma->eth, 1))
@@ -1866,7 +1866,7 @@ static int airoha_dev_stop(struct net_device *netdev)
REG_GDM_FWD_CFG(port->id),
FE_PSE_PORT_DROP);
- if (atomic_dec_and_test(&qdma->users)) {
+ if (!--qdma->users) {
airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
GLOBAL_CFG_TX_DMA_EN_MASK |
GLOBAL_CFG_RX_DMA_EN_MASK);
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 8f42973f9cf5..24fd8dcf7fca 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -526,7 +526,7 @@ struct airoha_qdma {
struct airoha_eth *eth;
void __iomem *regs;
- atomic_t users;
+ int users;
struct airoha_irq_bank irq_banks[AIROHA_MAX_NUM_IRQ_BANKS];
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v5 2/3] net: airoha: refactor QDMA start/stop into reusable helpers
From: Lorenzo Bianconi @ 2026-06-11 21:55 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal,
Alexander Lobakin
In-Reply-To: <20260611-airoha-ethtool-priv_flags-v5-0-c11de08486d1@kernel.org>
Factor out airoha_qdma_start() and airoha_qdma_stop() from
airoha_dev_open() and airoha_dev_stop(). These helpers will be reused
by the QDMA hot-migration logic introduced in the next patch to
dynamically switch GDM3/GDM4 ports between LAN and WAN QDMA blocks.
Add a DMA engine busy poll in airoha_qdma_stop() to wait for in-flight
DMA transfers to complete before cleaning up TX queues.
Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 53 ++++++++++++++++++++++----------
1 file changed, 36 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 9eb9b6a139b3..72ec6c888948 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1774,6 +1774,40 @@ static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
spin_unlock(&port->stats.lock);
}
+static void airoha_qdma_start(struct airoha_qdma *qdma)
+{
+ airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+ qdma->users++;
+}
+
+static void airoha_qdma_stop(struct airoha_qdma *qdma)
+{
+ u32 status;
+
+ if (--qdma->users)
+ return;
+
+ airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+
+ if (read_poll_timeout(airoha_qdma_rr, status,
+ !(status & (GLOBAL_CFG_TX_DMA_BUSY_MASK |
+ GLOBAL_CFG_RX_DMA_BUSY_MASK)),
+ USEC_PER_MSEC, 50 * USEC_PER_MSEC, true,
+ qdma, REG_QDMA_GLOBAL_CFG))
+ dev_warn(qdma->eth->dev, "QDMA DMA engine busy timeout\n");
+
+ for (int i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+ if (!qdma->q_tx[i].ndesc)
+ continue;
+
+ airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
+ }
+}
+
static int airoha_dev_open(struct net_device *netdev)
{
int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
@@ -1809,10 +1843,7 @@ static int airoha_dev_open(struct net_device *netdev)
}
port->users++;
- airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
- GLOBAL_CFG_TX_DMA_EN_MASK |
- GLOBAL_CFG_RX_DMA_EN_MASK);
- qdma->users++;
+ airoha_qdma_start(qdma);
if (!airoha_is_lan_gdm_dev(dev) &&
airoha_ppe_is_enabled(qdma->eth, 1))
@@ -1865,19 +1896,7 @@ static int airoha_dev_stop(struct net_device *netdev)
airoha_set_gdm_port_fwd_cfg(qdma->eth,
REG_GDM_FWD_CFG(port->id),
FE_PSE_PORT_DROP);
-
- if (!--qdma->users) {
- airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
- GLOBAL_CFG_TX_DMA_EN_MASK |
- GLOBAL_CFG_RX_DMA_EN_MASK);
-
- for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
- if (!qdma->q_tx[i].ndesc)
- continue;
-
- airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
- }
- }
+ airoha_qdma_stop(qdma);
return 0;
}
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v5 3/3] net: airoha: defer GDM3/GDM4 WAN mode and GDM2 loopback to QoS offload
From: Lorenzo Bianconi @ 2026-06-11 21:55 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Lorenzo Bianconi
Cc: linux-arm-kernel, linux-mediatek, netdev, Madhur Agrawal,
Alexander Lobakin
In-Reply-To: <20260611-airoha-ethtool-priv_flags-v5-0-c11de08486d1@kernel.org>
GDM3 and GDM4 ports require GDM2 loopback to be enabled for hardware
QoS offload to function. Without it, HTB and ETS offload on these ports
do not work.
Previously, GDM3/GDM4 ports were automatically configured as WAN with
GDM2 loopback enabled during ndo_init(). Add the capability to configure
GDM3/GDM4 as WAN/LAN on demand when QoS offload is created or destroyed.
Hook airoha_enable_qos_for_gdm34() into TC_HTB_CREATE so that requesting
HTB offload on a GDM3/GDM4 LAN port switches it to WAN mode and enables
GDM2 loopback, with proper rollback on failure. Introduce the
AIROHA_PRIV_F_QOS flag to track whether a device has an active HTB
qdisc; clear it on TC_HTB_DESTROY. The device keeps its WAN role after
qdisc teardown so that its configuration is preserved until another
device explicitly needs the WAN role for QoS offload.
If another GDM3/GDM4 device already holds the WAN role without an active
QoS qdisc, demote it to LAN before promoting the requesting device. Skip
the demotion when the requesting device is itself already the WAN device.
Since airoha_dev_set_qdma() can now be called on a running device to
migrate between QDMA blocks, make dev->qdma an RCU pointer so the TX
path can safely dereference it without holding RTNL.
Hold flow_offload_mutex in airoha_enable_qos_for_gdm34() and
airoha_disable_qos_for_gdm34() around the dev->flags update,
airoha_dev_set_qdma() and GDM2 loopback configuration, serializing
against concurrent airoha_ppe_hw_init() in the TC_SETUP_CLSFLOWER
offload path.
Introduce airoha_qdma_deref() helper that wraps rcu_dereference_protected()
with a lockdep condition accepting either rtnl_lock or flow_offload_mutex,
and use it across all control-path dereferences of the RCU-protected
dev->qdma pointer.
Add airoha_disable_gdm2_loopback() to disable GDM2 hw loopback.
Tested-by: Madhur Agrawal <madhur.agrawal@airoha.com>
Reviewed-by: Alexander Lobakin <aleksander.lobakin@intel.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
drivers/net/ethernet/airoha/airoha_eth.c | 234 ++++++++++++++++++++++++++----
drivers/net/ethernet/airoha/airoha_eth.h | 13 +-
drivers/net/ethernet/airoha/airoha_ppe.c | 9 +-
drivers/net/ethernet/airoha/airoha_regs.h | 1 +
4 files changed, 225 insertions(+), 32 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 72ec6c888948..490da17e3dc6 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -921,7 +921,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
if (!dev)
continue;
- if (dev->qdma != qdma)
+ if (rcu_access_pointer(dev->qdma) != qdma)
continue;
netdev = netdev_from_priv(dev);
@@ -1814,13 +1814,14 @@ static int airoha_dev_open(struct net_device *netdev)
struct airoha_gdm_dev *dev = netdev_priv(netdev);
struct airoha_gdm_port *port = dev->port;
u32 cur_len, pse_port = FE_PSE_PORT_PPE1;
- struct airoha_qdma *qdma = dev->qdma;
+ struct airoha_qdma *qdma;
netif_tx_start_all_queues(netdev);
err = airoha_set_vip_for_gdm_port(dev, true);
if (err)
return err;
+ qdma = airoha_qdma_deref(dev);
if (netdev_uses_dsa(netdev))
airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
GDM_STAG_EN_MASK);
@@ -1882,7 +1883,7 @@ 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 = dev->qdma;
+ struct airoha_qdma *qdma;
int i;
netif_tx_disable(netdev);
@@ -1890,6 +1891,7 @@ static int airoha_dev_stop(struct net_device *netdev)
for (i = 0; i < netdev->num_tx_queues; i++)
netdev_tx_reset_subqueue(netdev, i);
+ qdma = airoha_qdma_deref(dev);
if (--port->users)
airoha_set_port_mtu(dev->eth, port);
else
@@ -1982,6 +1984,53 @@ static int airoha_enable_gdm2_loopback(struct airoha_gdm_dev *dev)
return 0;
}
+static int airoha_disable_gdm2_loopback(struct airoha_gdm_dev *dev)
+{
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ int i, src_port;
+ u32 pse_port;
+
+ src_port = eth->soc->ops.get_sport(dev->port, dev->nbq);
+ if (src_port < 0)
+ return src_port;
+
+ airoha_fe_clear(eth,
+ REG_SP_DFT_CPORT(src_port >> fls(SP_CPORT_DFT_MASK)),
+ SP_CPORT_MASK(src_port & SP_CPORT_DFT_MASK));
+
+ airoha_fe_set(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+ GDM_STRIP_CRC_MASK);
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+ FE_PSE_PORT_DROP);
+ airoha_fe_clear(eth, REG_GDM_LPBK_CFG(AIROHA_GDM2_IDX),
+ LPBK_CHAN_MASK | LPBK_MODE_MASK | LPBK_EN_MASK);
+ pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
+ : FE_PSE_PORT_PPE1;
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX),
+ pse_port);
+
+ airoha_fe_rmw(eth, REG_FE_WAN_PORT, WAN0_MASK,
+ FIELD_PREP(WAN0_MASK, AIROHA_GDM2_IDX));
+
+ for (i = 0; i < eth->soc->num_ppe; i++)
+ airoha_fe_clear(eth, REG_PPE_DFT_CPORT(i, AIROHA_GDM2_IDX),
+ DFT_CPORT_MASK(AIROHA_GDM2_IDX));
+
+ /* Enable VIP and IFC for GDM2 */
+ airoha_fe_set(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX));
+ airoha_fe_set(eth, REG_FE_IFC_PORT_EN, BIT(AIROHA_GDM2_IDX));
+
+ if (port->id == AIROHA_GDM4_IDX && airoha_is_7581(eth)) {
+ u32 mask = FC_ID_OF_SRC_PORT_MASK(dev->nbq);
+
+ airoha_fe_rmw(eth, REG_SRC_PORT_FC_MAP6, mask,
+ FC_MAP6_DEF_VALUE & mask);
+ }
+
+ return 0;
+}
+
static struct airoha_gdm_dev *
airoha_get_wan_gdm_dev(struct airoha_eth *eth)
{
@@ -2008,15 +2057,30 @@ airoha_get_wan_gdm_dev(struct airoha_eth *eth)
static void airoha_dev_set_qdma(struct airoha_gdm_dev *dev)
{
struct net_device *netdev = netdev_from_priv(dev);
+ struct airoha_qdma *cur_qdma, *qdma;
struct airoha_eth *eth = dev->eth;
int ppe_id;
/* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
- dev->qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
- netdev->irq = dev->qdma->irq_banks[0].irq;
+ qdma = ð->qdma[!airoha_is_lan_gdm_dev(dev)];
+ cur_qdma = airoha_qdma_deref(dev);
+ if (netif_running(netdev))
+ airoha_qdma_start(qdma);
+
+ rcu_assign_pointer(dev->qdma, qdma);
+ netdev->irq = qdma->irq_banks[0].irq;
ppe_id = !airoha_is_lan_gdm_dev(dev) && airoha_ppe_is_enabled(eth, 1);
airoha_ppe_set_cpu_port(dev, ppe_id, airoha_get_fe_port(dev));
+
+ if (!cur_qdma)
+ return;
+
+ synchronize_rcu();
+ netif_tx_wake_all_queues(netdev);
+
+ if (netif_running(netdev))
+ airoha_qdma_stop(cur_qdma);
}
static int airoha_dev_init(struct net_device *netdev)
@@ -2180,9 +2244,9 @@ 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_qdma *qdma = dev->qdma;
u32 nr_frags, tag, msg0, msg1, len;
struct airoha_queue_entry *e;
+ struct airoha_qdma *qdma;
struct netdev_queue *txq;
struct airoha_queue *q;
LIST_HEAD(tx_list);
@@ -2191,6 +2255,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
u16 index;
u8 fport;
+ rcu_read_lock();
+ qdma = rcu_dereference(dev->qdma);
qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
tag = airoha_get_dsa_tag(skb, netdev);
@@ -2237,6 +2303,8 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
netif_tx_stop_queue(txq);
q->txq_stopped = true;
spin_unlock_bh(&q->lock);
+ rcu_read_unlock();
+
return NETDEV_TX_BUSY;
}
@@ -2299,6 +2367,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
spin_unlock_bh(&q->lock);
+ rcu_read_unlock();
return NETDEV_TX_OK;
@@ -2314,6 +2383,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
error:
dev_kfree_skb_any(skb);
netdev->stats.tx_dropped++;
+ rcu_read_unlock();
return NETDEV_TX_OK;
}
@@ -2395,17 +2465,19 @@ 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_qdma *qdma;
int i;
+ qdma = airoha_qdma_deref(dev);
for (i = 0; i < AIROHA_NUM_TX_RING; i++)
- airoha_qdma_clear(dev->qdma, REG_QUEUE_CLOSE_CFG(channel),
+ airoha_qdma_clear(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(dev->qdma, REG_TXWRR_WEIGHT_CFG,
+ airoha_qdma_wr(qdma, REG_TXWRR_WEIGHT_CFG,
TWRR_RW_CMD_MASK |
FIELD_PREP(TWRR_CHAN_IDX_MASK, channel) |
FIELD_PREP(TWRR_QUEUE_IDX_MASK, i) |
@@ -2413,12 +2485,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, dev->qdma, REG_TXWRR_WEIGHT_CFG);
+ true, qdma, REG_TXWRR_WEIGHT_CFG);
if (err)
return err;
}
- airoha_qdma_rmw(dev->qdma, REG_CHAN_QOS_MODE(channel >> 3),
+ airoha_qdma_rmw(qdma, REG_CHAN_QOS_MODE(channel >> 3),
CHAN_QOS_MODE_MASK(channel),
__field_prep(CHAN_QOS_MODE_MASK(channel), mode));
@@ -2482,13 +2554,15 @@ 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_qdma *qdma = dev->qdma;
+ u64 cpu_tx_packets, fwd_tx_packets, tx_packets;
+ struct airoha_qdma *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 - dev->cpu_tx_packets) +
- (fwd_tx_packets - dev->fwd_tx_packets);
+ qdma = airoha_qdma_deref(dev);
+ cpu_tx_packets = airoha_qdma_rr(qdma, REG_CNTR_VAL(channel << 1));
+ fwd_tx_packets = airoha_qdma_rr(qdma,
+ REG_CNTR_VAL((channel << 1) + 1));
+ tx_packets = (cpu_tx_packets - dev->cpu_tx_packets) +
+ (fwd_tx_packets - dev->fwd_tx_packets);
_bstats_update(opt->stats.bstats, 0, tx_packets);
dev->cpu_tx_packets = cpu_tx_packets;
@@ -2748,16 +2822,18 @@ 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_qdma *qdma;
int i, err;
+ qdma = airoha_qdma_deref(dev);
for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
- err = airoha_qdma_set_trtcm_config(dev->qdma, channel,
+ err = airoha_qdma_set_trtcm_config(qdma, channel,
REG_EGRESS_TRTCM_CFG, i,
!!rate, TRTCM_METER_MODE);
if (err)
return err;
- err = airoha_qdma_set_trtcm_token_bucket(dev->qdma, channel,
+ err = airoha_qdma_set_trtcm_token_bucket(qdma, channel,
REG_EGRESS_TRTCM_CFG,
i, rate, bucket_size);
if (err)
@@ -2793,11 +2869,12 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
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;
+ struct airoha_qdma *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.
*/
+ qdma = airoha_qdma_deref(dev);
if (test_and_set_bit(channel, qdma->qos_channel_map)) {
NL_SET_ERR_MSG_MOD(opt->extack,
"qdma qos channel already in use");
@@ -2831,7 +2908,7 @@ 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 = dev->qdma;
+ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
int i;
for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
@@ -3005,11 +3082,12 @@ 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_qdma *qdma = dev->qdma;
+ struct airoha_qdma *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);
+ qdma = airoha_qdma_deref(dev);
clear_bit(queue, qdma->qos_channel_map);
clear_bit(queue, dev->qos_sq_bmap);
}
@@ -3030,6 +3108,95 @@ static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
return 0;
}
+static void airoha_disable_qos_for_gdm34(struct net_device *netdev)
+{
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+ int err;
+
+ if (port->id != AIROHA_GDM3_IDX &&
+ port->id != AIROHA_GDM4_IDX)
+ return;
+
+ err = airoha_disable_gdm2_loopback(dev);
+ if (err)
+ netdev_warn(netdev,
+ "failed disabling GDM2 loopback: %d\n", err);
+
+ dev->flags &= ~AIROHA_PRIV_F_WAN;
+ airoha_dev_set_qdma(dev);
+
+ airoha_set_macaddr(dev, netdev->dev_addr);
+ if (netif_running(netdev))
+ airoha_set_gdm_port_fwd_cfg(dev->eth,
+ REG_GDM_FWD_CFG(port->id),
+ FE_PSE_PORT_PPE1);
+}
+
+static int airoha_enable_qos_for_gdm34(struct net_device *netdev,
+ struct netlink_ext_ack *extack)
+{
+ struct airoha_gdm_dev *wan_dev, *dev = netdev_priv(netdev);
+ struct airoha_gdm_port *port = dev->port;
+ struct airoha_eth *eth = dev->eth;
+ int err = -EBUSY;
+
+ if (port->id != AIROHA_GDM3_IDX &&
+ port->id != AIROHA_GDM4_IDX) {
+ /* HW QoS is always supported by GDM1 and GDM2 */
+ return 0;
+ }
+
+ if (!airoha_is_lan_gdm_dev(dev)) /* Already enabled */
+ return 0;
+
+ mutex_lock(&flow_offload_mutex);
+
+ wan_dev = airoha_get_wan_gdm_dev(eth);
+ if (wan_dev) {
+ if ((wan_dev->flags & AIROHA_PRIV_F_QOS) ||
+ wan_dev->port->id == AIROHA_GDM2_IDX) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "QoS configured for WAN device");
+ goto error_unlock;
+ }
+ airoha_disable_qos_for_gdm34(netdev_from_priv(wan_dev));
+ }
+
+ dev->flags |= AIROHA_PRIV_F_WAN;
+ airoha_dev_set_qdma(dev);
+ err = airoha_enable_gdm2_loopback(dev);
+ if (err)
+ goto error_disable_wan;
+
+ err = airoha_set_macaddr(dev, netdev->dev_addr);
+ if (err)
+ goto error_disable_loopback;
+
+ if (netif_running(netdev)) {
+ u32 pse_port;
+
+ pse_port = airoha_ppe_is_enabled(eth, 1) ? FE_PSE_PORT_PPE2
+ : FE_PSE_PORT_PPE1;
+ airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(port->id),
+ pse_port);
+ }
+
+ mutex_unlock(&flow_offload_mutex);
+
+ return 0;
+
+error_disable_loopback:
+ airoha_disable_gdm2_loopback(dev);
+error_disable_wan:
+ dev->flags &= ~AIROHA_PRIV_F_WAN;
+ airoha_dev_set_qdma(dev);
+error_unlock:
+ mutex_unlock(&flow_offload_mutex);
+
+ return err;
+}
+
static int airoha_tc_htb_destroy(struct net_device *netdev)
{
struct airoha_gdm_dev *dev = netdev_priv(netdev);
@@ -3038,6 +3205,8 @@ static int airoha_tc_htb_destroy(struct net_device *netdev)
for_each_set_bit(q, dev->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
airoha_tc_remove_htb_queue(netdev, q);
+ dev->flags &= ~AIROHA_PRIV_F_QOS;
+
return 0;
}
@@ -3057,24 +3226,33 @@ static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
return 0;
}
-static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
+static int airoha_tc_setup_qdisc_htb(struct net_device *netdev,
struct tc_htb_qopt_offload *opt)
{
switch (opt->command) {
- case TC_HTB_CREATE:
+ case TC_HTB_CREATE: {
+ struct airoha_gdm_dev *dev = netdev_priv(netdev);
+ int err;
+
+ err = airoha_enable_qos_for_gdm34(netdev, opt->extack);
+ if (err)
+ return err;
+
+ dev->flags |= AIROHA_PRIV_F_QOS;
break;
+ }
case TC_HTB_DESTROY:
- return airoha_tc_htb_destroy(dev);
+ return airoha_tc_htb_destroy(netdev);
case TC_HTB_NODE_MODIFY:
- return airoha_tc_htb_modify_queue(dev, opt);
+ return airoha_tc_htb_modify_queue(netdev, opt);
case TC_HTB_LEAF_ALLOC_QUEUE:
- return airoha_tc_htb_alloc_leaf_queue(dev, opt);
+ return airoha_tc_htb_alloc_leaf_queue(netdev, opt);
case TC_HTB_LEAF_DEL:
case TC_HTB_LEAF_DEL_LAST:
case TC_HTB_LEAF_DEL_LAST_FORCE:
- return airoha_tc_htb_delete_leaf_queue(dev, opt);
+ return airoha_tc_htb_delete_leaf_queue(netdev, opt);
case TC_HTB_LEAF_QUERY_QUEUE:
- return airoha_tc_get_htb_get_leaf_queue(dev, opt);
+ return airoha_tc_get_htb_get_leaf_queue(netdev, opt);
default:
return -EOPNOTSUPP;
}
diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
index 24fd8dcf7fca..d1390ffcea7c 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.h
+++ b/drivers/net/ethernet/airoha/airoha_eth.h
@@ -540,11 +540,12 @@ struct airoha_qdma {
enum airoha_priv_flags {
AIROHA_PRIV_F_WAN = BIT(0),
+ AIROHA_PRIV_F_QOS = BIT(1),
};
struct airoha_gdm_dev {
+ struct airoha_qdma __rcu *qdma;
struct airoha_gdm_port *port;
- struct airoha_qdma *qdma;
struct airoha_eth *eth;
DECLARE_BITMAP(qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS);
@@ -676,6 +677,16 @@ 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);
+extern struct mutex flow_offload_mutex;
+
+static inline struct airoha_qdma *
+airoha_qdma_deref(struct airoha_gdm_dev *dev)
+{
+ return rcu_dereference_protected(dev->qdma,
+ lockdep_rtnl_is_held() ||
+ lockdep_is_held(&flow_offload_mutex));
+}
+
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,
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 91bcc55a6ac6..1d1b1a57d795 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -15,7 +15,10 @@
#include "airoha_regs.h"
#include "airoha_eth.h"
-static DEFINE_MUTEX(flow_offload_mutex);
+/* Serialize airoha_gdm_dev flags, QDMA pointer and PPE CPU port
+ * configuration.
+ */
+DEFINE_MUTEX(flow_offload_mutex);
static DEFINE_SPINLOCK(ppe_lock);
static const struct rhashtable_params airoha_flow_table_params = {
@@ -86,8 +89,8 @@ static u32 airoha_ppe_get_timestamp(struct airoha_ppe *ppe)
void airoha_ppe_set_cpu_port(struct airoha_gdm_dev *dev, u8 ppe_id, u8 fport)
{
- struct airoha_qdma *qdma = dev->qdma;
- struct airoha_eth *eth = qdma->eth;
+ struct airoha_qdma *qdma = airoha_qdma_deref(dev);
+ struct airoha_eth *eth = dev->eth;
u8 qdma_id = qdma - ð->qdma[0];
u32 fe_cpu_port;
diff --git a/drivers/net/ethernet/airoha/airoha_regs.h b/drivers/net/ethernet/airoha/airoha_regs.h
index 436f3c8779c1..4e17dfbcf2b8 100644
--- a/drivers/net/ethernet/airoha/airoha_regs.h
+++ b/drivers/net/ethernet/airoha/airoha_regs.h
@@ -376,6 +376,7 @@
#define REG_SRC_PORT_FC_MAP6 0x2298
#define FC_ID_OF_SRC_PORT_MASK(_n) GENMASK(4 + ((_n) << 3), ((_n) << 3))
+#define FC_MAP6_DEF_VALUE 0x1b1a1918
#define REG_CDM5_RX_OQ1_DROP_CNT 0x29d4
--
2.54.0
^ permalink raw reply related
* [PATCH mt76] wifi: mt76: mt7603: free beacon SKB on error
From: David Bauer @ 2026-06-11 21:56 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno
Cc: linux-wireless, linux-kernel, linux-arm-kernel, linux-mediatek
The SKB containing the generated beacon is not freed when the beacon
queue is deected stuck and scheduled for recovery.
Fixes potential memory leaks in case the beacon queue is detected stuck.
Signed-off-by: David Bauer <mail@david-bauer.net>
---
drivers/net/wireless/mediatek/mt76/mt7603/beacon.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c b/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c
index 300a7f9c2ef11..acca98139f92e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7603/beacon.c
@@ -56,6 +56,7 @@ mt7603_update_beacon_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
FIELD_PREP(MT_DMA_FQCR0_TARGET_QID, MT_TX_HW_QUEUE_BCN));
if (!mt76_poll(dev, MT_DMA_FQCR0, MT_DMA_FQCR0_BUSY, 0, 5000)) {
dev->beacon_check = MT7603_WATCHDOG_TIMEOUT;
+ dev_kfree_skb(skb);
goto out;
}
@@ -63,6 +64,7 @@ mt7603_update_beacon_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
FIELD_PREP(MT_DMA_FQCR0_TARGET_QID, MT_TX_HW_QUEUE_BMC));
if (!mt76_poll(dev, MT_DMA_FQCR0, MT_DMA_FQCR0_BUSY, 0, 5000)) {
dev->beacon_check = MT7603_WATCHDOG_TIMEOUT;
+ dev_kfree_skb(skb);
goto out;
}
--
2.53.0
^ permalink raw reply related
* Re: [PATCH 1/2] PCI: dwc: Guard RAS DES debugfs deinit
From: Bjorn Helgaas @ 2026-06-11 22:10 UTC (permalink / raw)
To: Shuvam Pandey
Cc: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Bjorn Helgaas, Yue Wang,
Neil Armstrong, Rob Herring, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Fan Ni, Shradha Todi, Hanjie Lin, linux-pci,
linux-amlogic, linux-arm-kernel, linux-kernel
In-Reply-To: <0f97352506d8d813f70f441de4d63fcd5b7d1c3e.1779123847.git.shuvampandey1@gmail.com>
On Mon, May 18, 2026 at 10:44:17PM +0545, Shuvam Pandey wrote:
> dwc_pcie_rasdes_debugfs_init() returns success when the controller has
> no RAS DES capability, leaving pci->debugfs->rasdes_info unset. The
> common debugfs teardown path still calls
> dwc_pcie_rasdes_debugfs_deinit(), which dereferences rasdes_info
> unconditionally.
>
> Return early when no RAS DES state was allocated. In that case no RAS DES
> mutex was initialized, so there is nothing to destroy.
>
> Fixes: 4fbfa17f9a07 ("PCI: dwc: Add debugfs based Silicon Debug support for DWC")
> Signed-off-by: Shuvam Pandey <shuvampandey1@gmail.com>
> ---
> drivers/pci/controller/dwc/pcie-designware-debugfs.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c
> index d0884253b..c3671cb2f 100644
> --- a/drivers/pci/controller/dwc/pcie-designware-debugfs.c
> +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c
> @@ -557,6 +557,9 @@ static void dwc_pcie_rasdes_debugfs_deinit(struct dw_pcie *pci)
> {
> struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info;
>
> + if (!rinfo)
> + return;
I guess this prevents a NULL pointer dereference, right?
> mutex_destroy(&rinfo->reg_event_lock);
> }
>
> --
> 2.50.1 (Apple Git-155)
>
>
> _______________________________________________
> linux-amlogic mailing list
> linux-amlogic@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply
* Re: [PATCH 2/2] PCI: meson: Add missing remove callback
From: Bjorn Helgaas @ 2026-06-11 22:26 UTC (permalink / raw)
To: Shuvam Pandey
Cc: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Bjorn Helgaas, Yue Wang,
Neil Armstrong, Rob Herring, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Fan Ni, Shradha Todi, Hanjie Lin, linux-pci,
linux-amlogic, linux-arm-kernel, linux-kernel
In-Reply-To: <1a0c86ab264cdc1c79c917e984b90991af51d827.1779123847.git.shuvampandey1@gmail.com>
On Mon, May 18, 2026 at 10:44:18PM +0545, Shuvam Pandey wrote:
> meson_pcie_probe() powers on the PHY and registers the DesignWare host
> bridge with dw_pcie_host_init(), but the driver has no remove callback.
> On driver unbind or module unload, the driver core therefore proceeds to
> devres cleanup without first unregistering the host bridge or powering off
> the PHY.
>
> Add a remove callback that deinitializes the DesignWare host bridge and
> powers off the PHY while device-managed resources are still valid.
What's the user-visible effect of this? Does it avoid an oops?
Reduce power usage?
Of the 34 instances of .probe() in drivers/pci/controller/dwc/, on 12
implement .remove(), so if this fixes a problem, I'm wondering whether
other drivers have the same problem.
> Fixes: 9c0ef6d34fdb ("PCI: amlogic: Add the Amlogic Meson PCIe controller driver")
> Signed-off-by: Shuvam Pandey <shuvampandey1@gmail.com>
> ---
> drivers/pci/controller/dwc/pci-meson.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c
> index 0694084f6..c96e2244a 100644
> --- a/drivers/pci/controller/dwc/pci-meson.c
> +++ b/drivers/pci/controller/dwc/pci-meson.c
> @@ -451,6 +451,14 @@ static int meson_pcie_probe(struct platform_device *pdev)
> return ret;
> }
>
> +static void meson_pcie_remove(struct platform_device *pdev)
> +{
> + struct meson_pcie *mp = platform_get_drvdata(pdev);
> +
> + dw_pcie_host_deinit(&mp->pci.pp);
> + meson_pcie_power_off(mp);
> +}
> +
> static const struct of_device_id meson_pcie_of_match[] = {
> {
> .compatible = "amlogic,axg-pcie",
> @@ -464,6 +472,7 @@ MODULE_DEVICE_TABLE(of, meson_pcie_of_match);
>
> static struct platform_driver meson_pcie_driver = {
> .probe = meson_pcie_probe,
> + .remove = meson_pcie_remove,
> .driver = {
> .name = "meson-pcie",
> .of_match_table = meson_pcie_of_match,
> --
> 2.50.1 (Apple Git-155)
>
>
> _______________________________________________
> linux-amlogic mailing list
> linux-amlogic@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply
* Re: [PATCH resend] arm64: dts: intel: keembay: Always use decimal interrupts
From: Dinh Nguyen @ 2026-06-11 22:35 UTC (permalink / raw)
To: Geert Uytterhoeven, Arnd Bergmann, Krzysztof Kozlowski,
Paul J . Murphy
Cc: linux-arm-kernel, linux-kernel
In-Reply-To: <abff7cc5aa2e66524a90523e07e11a42bd3e9e58.1781103222.git.geert+renesas@glider.be>
On 6/10/26 23:55, Geert Uytterhoeven wrote:
> Replace the sole hexadecimal interrupt number by a decimal number, for
> consistency with all other interrupt numbers.
>
> Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
> ---
> arch/arm64/boot/dts/intel/keembay-soc.dtsi | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/arm64/boot/dts/intel/keembay-soc.dtsi b/arch/arm64/boot/dts/intel/keembay-soc.dtsi
> index ae00e9e54e82cbb9..b6323ebe7a5f46bd 100644
> --- a/arch/arm64/boot/dts/intel/keembay-soc.dtsi
> +++ b/arch/arm64/boot/dts/intel/keembay-soc.dtsi
> @@ -71,7 +71,7 @@ timer {
>
> pmu {
> compatible = "arm,cortex-a53-pmu";
> - interrupts = <GIC_PPI 0x7 IRQ_TYPE_LEVEL_HIGH>;
> + interrupts = <GIC_PPI 7 IRQ_TYPE_LEVEL_HIGH>;
> };
>
> soc {
Applied!
Thanks,
Dinh
^ permalink raw reply
* Re: [PATCH net-next v2] net: airoha: simplify WAN device check in airoha_dev_init()
From: patchwork-bot+netdevbpf @ 2026-06-11 23:10 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-arm-kernel,
linux-mediatek, netdev
In-Reply-To: <20260610-airoha-eth-simplify-dev-init-v2-1-8f244e69b0d4@kernel.org>
Hello:
This patch was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Wed, 10 Jun 2026 15:25:13 +0200 you wrote:
> airoha_register_gdm_devices() iterates eth->ports[] in order, so GDM2's
> netdev is always registered before GDM3/GDM4. This means the explicit
> check for eth->ports[1] && eth->ports[1]->devs[0] is a redundant
> special-case of what airoha_get_wan_gdm_dev() already covers, since
> GDM2 is always marked as WAN during its own ndo_init.
> Remove the redundant check and rely solely on airoha_get_wan_gdm_dev()
> which handles both the GDM2-present and GDM2-absent cases.
>
> [...]
Here is the summary with links:
- [net-next,v2] net: airoha: simplify WAN device check in airoha_dev_init()
https://git.kernel.org/netdev/net-next/c/9912dfa2d46a
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [PATCH] net: airoha: Fix variable shadowing in airoha_ppe_flush_sram_entries()
From: Wayen.Yan @ 2026-06-11 23:08 UTC (permalink / raw)
To: netdev; +Cc: lorenzo, linux-arm-kernel, linux-mediatek
In airoha_ppe_flush_sram_entries(), the inner "int err" declaration
shadows the outer "err" variable. When airoha_ppe_foe_commit_sram_entry()
fails and the loop breaks, the function returns the outer err which is
always 0, silently swallowing the error.
Remove the inner declaration so the assignment writes to the outer
variable and errors are properly propagated.
Fixes: 620d7b91aadb ("net: airoha: ppe: Flush PPE SRAM table during PPE setup")
Signed-off-by: Wayen.Yan <win847@gmail.com>
---
drivers/net/ethernet/airoha/airoha_ppe.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 5c9dff6..2b849d4 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -1333,8 +1333,6 @@ static int airoha_ppe_flush_sram_entries(struct airoha_ppe *ppe)
int i, err = 0;
for (i = 0; i < sram_num_entries; i++) {
- int err;
-
memset(&hwe[i], 0, sizeof(*hwe));
err = airoha_ppe_foe_commit_sram_entry(ppe, i);
if (err)
--
2.51.0
^ permalink raw reply related
* [PATCH] net: airoha: Fix register index for Tx-fwd counter configuration
From: Wayen.Yan @ 2026-06-11 23:09 UTC (permalink / raw)
To: netdev; +Cc: lorenzo, linux-arm-kernel, linux-mediatek
In airoha_qdma_init_qos_stats(), the Tx-fwd counter configuration
register uses the same index (i << 1) as the Tx-cpu counter, which
overwrites the Tx-cpu configuration. The Tx-fwd counter value register
correctly uses (i << 1) + 1, so the configuration register should use
the same index.
Fix the REG_CNTR_CFG index from (i << 1) to ((i << 1) + 1) so that
the Tx-fwd counter is properly configured instead of clobbering the
Tx-cpu counter config.
Fixes: 20bf7d07c956 ("net: airoha: Add sched ETS offload support")
Signed-off-by: Wayen.Yan <win847@gmail.com>
---
drivers/net/ethernet/airoha/airoha_eth.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 31cdb11..329988a 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1256,7 +1256,7 @@ static void airoha_qdma_init_qos_stats(struct airoha_qdma *qdma)
FIELD_PREP(CNTR_CHAN_MASK, i));
/* Tx-fwd transferred count */
airoha_qdma_wr(qdma, REG_CNTR_VAL((i << 1) + 1), 0);
- airoha_qdma_wr(qdma, REG_CNTR_CFG(i << 1),
+ airoha_qdma_wr(qdma, REG_CNTR_CFG((i << 1) + 1),
CNTR_EN_MASK | CNTR_ALL_QUEUE_EN_MASK |
CNTR_ALL_DSCP_RING_EN_MASK |
FIELD_PREP(CNTR_SRC_MASK, 1) |
--
2.51.0
^ permalink raw reply related
* [PATCH] net: airoha: Fix debugfs new-tuple display for IPv4 ROUTE entries
From: Wayen.Yan @ 2026-06-11 23:09 UTC (permalink / raw)
To: netdev; +Cc: lorenzo, linux-arm-kernel, linux-mediatek
In airoha_ppe_debugfs_foe_show(), the second switch statement falls
through from PPE_PKT_TYPE_IPV4_HNAPT/DSLITE to PPE_PKT_TYPE_IPV4_ROUTE,
accessing hwe->ipv4.new_tuple for all three types. However, IPv4 ROUTE
(3-tuple) entries do not contain a valid new_tuple — this field is only
meaningful for NATted flows (HNAPT/DSLITE). For ROUTE entries, the
memory at the new_tuple offset holds routing information, not NAT data,
so displaying "new=" produces garbage output.
Split the fallthrough: display new_tuple only for HNAPT and DSLITE, and
add an explicit empty case for IPV4_ROUTE.
Fixes: 3fe15c640f38 ("net: airoha: Introduce PPE debugfs support")
Signed-off-by: Wayen.Yan <win847@gmail.com>
---
drivers/net/ethernet/airoha/airoha_ppe_debugfs.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/airoha/airoha_ppe_debugfs.c b/drivers/net/ethernet/airoha/airoha_ppe_debugfs.c
index 0112c41..f9c12e7 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe_debugfs.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe_debugfs.c
@@ -121,8 +121,6 @@ static int airoha_ppe_debugfs_foe_show(struct seq_file *m, void *private,
case PPE_PKT_TYPE_IPV4_DSLITE:
src_port = &hwe->ipv4.new_tuple.src_port;
dest_port = &hwe->ipv4.new_tuple.dest_port;
- fallthrough;
- case PPE_PKT_TYPE_IPV4_ROUTE:
src_addr = &hwe->ipv4.new_tuple.src_ip;
dest_addr = &hwe->ipv4.new_tuple.dest_ip;
seq_puts(m, " new=");
@@ -130,6 +128,8 @@ static int airoha_ppe_debugfs_foe_show(struct seq_file *m, void *private,
src_port, dest_port,
ipv6);
break;
+ case PPE_PKT_TYPE_IPV4_ROUTE:
+ break;
default:
break;
}
--
2.51.0
^ permalink raw reply related
* [PATCH v2 0/3] ufs: Add callback for vendor-specific RTT capability
From: ed.tsai @ 2026-06-11 23:26 UTC (permalink / raw)
To: alim.akhtar, avri.altman, bvanassche, James.Bottomley,
martin.petersen, linux-scsi
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
peter.wang, alice.chao, naomi.chu, chun-hung.wu, Ed Tsai
From: Ed Tsai <ed.tsai@mediatek.com>
The first patch adds the get_hba_nortt() callback to the UFS core layer,
allowing vendor drivers to provide dynamic, platform-specific RTT
capability handling.
The second patch implements this callback in the MediaTek UFS driver,
distinguishing between legacy platforms (which require the RTT to be
limited to 2) and newer MT6995 B0+ platforms (which can use the value
from the capability register directly).
The third patch removes the max_num_rtt field from ufs_hba_variant_ops
as it is now replaced by the get_hba_nortt() callback.
Changes in v2:
- Keep max_num_rtt field in patch 1 to maintain bisectability
- Split removal of max_num_rtt into a separate patch (patch 3)
Ed Tsai (3):
ufs: core: Add get_hba_nortt callback for vendor-specific RTT
capability
ufs: mediatek: Implement get_hba_nortt callback for RTT capability
ufs: core: Remove max_num_rtt field from ufs_hba_variant_ops
drivers/ufs/core/ufshcd.c | 9 +++++----
drivers/ufs/host/ufs-mediatek.c | 12 +++++++++++-
drivers/ufs/host/ufs-mediatek.h | 4 ++--
include/ufs/ufshcd.h | 4 +++-
4 files changed, 21 insertions(+), 8 deletions(-)
--
2.45.2
^ permalink raw reply
* [PATCH v2 1/3] ufs: core: Add get_hba_nortt callback for vendor-specific RTT capability
From: ed.tsai @ 2026-06-11 23:26 UTC (permalink / raw)
To: alim.akhtar, avri.altman, bvanassche, James.Bottomley,
martin.petersen, linux-scsi
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
peter.wang, alice.chao, naomi.chu, chun-hung.wu, Ed Tsai
In-Reply-To: <20260611232632.2324422-1-ed.tsai@mediatek.com>
From: Ed Tsai <ed.tsai@mediatek.com>
The number of outstanding RTTs read from host controller capability
register is problematic on some platforms. Add a new vendor callback
get_hba_nortt() to allow platform vendors to override the default RTT
capability value with platform-specific handling.
This patch keeps max_num_rtt field for bisectability and will be removed
in a later patch once all platforms are migrated.
Signed-off-by: Ed Tsai <ed.tsai@mediatek.com>
---
drivers/ufs/core/ufshcd.c | 5 ++++-
include/ufs/ufshcd.h | 3 +++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index c3f08957d179..382b6041c716 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -2529,7 +2529,10 @@ static inline int ufshcd_hba_capabilities(struct ufs_hba *hba)
hba->nutmrs =
((hba->capabilities & MASK_TASK_MANAGEMENT_REQUEST_SLOTS) >> 16) + 1;
- hba->nortt = FIELD_GET(MASK_NUMBER_OUTSTANDING_RTT, hba->capabilities) + 1;
+ if (hba->vops && hba->vops->get_hba_nortt)
+ hba->nortt = hba->vops->get_hba_nortt(hba);
+ else
+ hba->nortt = FIELD_GET(MASK_NUMBER_OUTSTANDING_RTT, hba->capabilities) + 1;
/* Read crypto capabilities */
err = ufshcd_hba_init_crypto_capabilities(hba);
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index cfbc75d8df83..421c286481e8 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -415,6 +415,8 @@ struct ufshcd_tx_eq_params {
* @get_rx_fom: called to get Figure of Merit (FOM) value.
* @tx_eqtr_notify: called before and after TX Equalization Training procedure
* to allow platform vendor specific configs to take place.
+ * @get_hba_nortt: called to get maximum number of outstanding RTTs supported by
+ * the controller.
*/
struct ufs_hba_variant_ops {
const char *name;
@@ -477,6 +479,7 @@ struct ufs_hba_variant_ops {
int (*tx_eqtr_notify)(struct ufs_hba *hba,
enum ufs_notify_change_status status,
struct ufs_pa_layer_attr *pwr_mode);
+ int (*get_hba_nortt)(struct ufs_hba *hba);
};
/* clock gating state */
--
2.45.2
^ permalink raw reply related
* [PATCH v2 3/3] ufs: core: Remove max_num_rtt field from ufs_hba_variant_ops
From: ed.tsai @ 2026-06-11 23:26 UTC (permalink / raw)
To: alim.akhtar, avri.altman, bvanassche, James.Bottomley,
martin.petersen, linux-scsi
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
peter.wang, alice.chao, naomi.chu, chun-hung.wu, Ed Tsai
In-Reply-To: <20260611232632.2324422-1-ed.tsai@mediatek.com>
From: Ed Tsai <ed.tsai@mediatek.com>
Remove the max_num_rtt field from ufs_hba_variant_ops as it has been
replaced by the get_hba_nortt() callback which provides more flexible
platform-specific RTT capability handling.
Signed-off-by: Ed Tsai <ed.tsai@mediatek.com>
---
drivers/ufs/core/ufshcd.c | 4 +---
include/ufs/ufshcd.h | 1 -
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 382b6041c716..00072bff9dcd 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -8557,8 +8557,6 @@ static void ufshcd_set_rtt(struct ufs_hba *hba)
struct ufs_dev_info *dev_info = &hba->dev_info;
u32 rtt = 0;
u32 dev_rtt = 0;
- int host_rtt_cap = hba->vops && hba->vops->max_num_rtt ?
- hba->vops->max_num_rtt : hba->nortt;
/* RTT override makes sense only for UFS-4.0 and above */
if (dev_info->wspecversion < 0x400)
@@ -8574,7 +8572,7 @@ static void ufshcd_set_rtt(struct ufs_hba *hba)
if (dev_rtt != DEFAULT_MAX_NUM_RTT)
return;
- rtt = min_t(int, dev_info->rtt_cap, host_rtt_cap);
+ rtt = min_t(int, dev_info->rtt_cap, hba->nortt);
if (rtt == dev_rtt)
return;
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index 421c286481e8..5d72b7f2dc68 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -370,7 +370,6 @@ struct ufshcd_tx_eq_params {
/**
* struct ufs_hba_variant_ops - variant specific callbacks
* @name: variant name
- * @max_num_rtt: maximum RTT supported by the host
* @init: called when the driver is initialized
* @exit: called to cleanup everything done in init
* @set_dma_mask: For setting another DMA mask than indicated by the 64AS
--
2.45.2
^ permalink raw reply related
* [PATCH v2 2/3] ufs: mediatek: Implement get_hba_nortt callback for RTT capability
From: ed.tsai @ 2026-06-11 23:26 UTC (permalink / raw)
To: alim.akhtar, avri.altman, bvanassche, James.Bottomley,
martin.petersen, linux-scsi
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
peter.wang, alice.chao, naomi.chu, chun-hung.wu, Ed Tsai
In-Reply-To: <20260611232632.2324422-1-ed.tsai@mediatek.com>
From: Ed Tsai <ed.tsai@mediatek.com>
Implement the get_hba_nortt callback to handle platform-specific RTT
capability differences:
- For legacy platforms and IP versions before MT6995 B0, the RTT
capability from host controller register is problematic, so limit
it to 2 (MTK_MAX_NUM_RTT_LEGACY).
- For MT6995 B0 and later platforms, the issue is fixed and the
value from host controller capability register can be used directly.
This replaces the previous max_num_rtt field in ufs_hba_variant_ops
with dynamic platform-specific logic.
Signed-off-by: Ed Tsai <ed.tsai@mediatek.com>
Reviewed-by: Peter Wang <peter.wang@mediatek.com>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
---
drivers/ufs/host/ufs-mediatek.c | 12 +++++++++++-
drivers/ufs/host/ufs-mediatek.h | 4 ++--
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c
index 3991a51263a6..58701ca95edd 100644
--- a/drivers/ufs/host/ufs-mediatek.c
+++ b/drivers/ufs/host/ufs-mediatek.c
@@ -2183,6 +2183,16 @@ static int ufs_mtk_clk_scale_notify(struct ufs_hba *hba, bool scale_up,
return 0;
}
+static int ufs_mtk_get_hba_nortt(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (host->legacy_ip_ver || host->ip_ver < IP_VER_MT6995_B0)
+ return MTK_MAX_NUM_RTT_LEGACY;
+
+ return FIELD_GET(MASK_NUMBER_OUTSTANDING_RTT, hba->capabilities) + 1;
+}
+
static int ufs_mtk_get_hba_mac(struct ufs_hba *hba)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
@@ -2322,7 +2332,6 @@ static void ufs_mtk_config_scsi_dev(struct scsi_device *sdev)
*/
static const struct ufs_hba_variant_ops ufs_hba_mtk_vops = {
.name = "mediatek.ufshci",
- .max_num_rtt = MTK_MAX_NUM_RTT,
.init = ufs_mtk_init,
.get_ufs_hci_version = ufs_mtk_get_ufs_hci_version,
.setup_clocks = ufs_mtk_setup_clocks,
@@ -2339,6 +2348,7 @@ static const struct ufs_hba_variant_ops ufs_hba_mtk_vops = {
.event_notify = ufs_mtk_event_notify,
.config_scaling_param = ufs_mtk_config_scaling_param,
.clk_scale_notify = ufs_mtk_clk_scale_notify,
+ .get_hba_nortt = ufs_mtk_get_hba_nortt,
/* mcq vops */
.get_hba_mac = ufs_mtk_get_hba_mac,
.op_runtime_config = ufs_mtk_op_runtime_config,
diff --git a/drivers/ufs/host/ufs-mediatek.h b/drivers/ufs/host/ufs-mediatek.h
index 8547a6f04990..73cdc726f290 100644
--- a/drivers/ufs/host/ufs-mediatek.h
+++ b/drivers/ufs/host/ufs-mediatek.h
@@ -203,8 +203,8 @@ struct ufs_mtk_host {
/* MTK delay of autosuspend: 500 ms */
#define MTK_RPM_AUTOSUSPEND_DELAY_MS 500
-/* MTK RTT support number */
-#define MTK_MAX_NUM_RTT 2
+/* MTK RTT support number for platforms before MT6995 B0 */
+#define MTK_MAX_NUM_RTT_LEGACY 2
/* UFSHCI MTK ip version value */
enum {
--
2.45.2
^ permalink raw reply related
* [PATCH v4 0/8] phy: rockchip: samsung-hdptx: Clock fixes and API transition cleanups
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright, Sashiko
This series provides a set of bug fixes and cleanups for the Rockchip
Samsung HDPTX PHY driver.
The first part of the series addresses clock rate calculation and
synchronization issues. Specifically, it fixes edge cases where the PHY
PLL is pre-programmed by an external component (like a bootloader) or
when changing the color depth (bpc) while keeping the modeline constant.
Because the Common Clock Framework .set_rate() callback might not be
invoked if the pixel clock remains unchanged, this previously led to
out-of-sync states between CCF and the actual HDMI PHY configuration.
The second part focuses on code cleanups and modernizing the register
access. Now that dw_hdmi_qp driver has fully switched to using
phy_configure(), we can drop the deprecated TMDS rate setup workarounds
and the restrict_rate_change flag logic. Finally, it refactors the
driver to consistently use standard bitfield macros.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
Changes in v4:
- Added new patches to address new findings from Sashiko:
* Prevent divide-by-zero when computing clk rate
* Fix rate recalculation for 3.2GHz FRL
- Updated patch "Consistently use bitfield macros" to handle a few more
bit operations
- Link to v3: https://patch.msgid.link/20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com
Changes in v3:
- Replaced div_u64() with DIV_ROUND_CLOSEST_ULL() in Patch 1 (Sashiko)
- Fixed theoretical usage_count unbalanced issue in Patch 2 (Sashiko)
- Rebased series onto latest phy/next
- Link to v2: https://patch.msgid.link/20260511-hdptx-clk-fixes-v2-0-664e41379cab@collabora.com
Changes in v2:
- Collected Tested-by tags from Thomas and Simon
- Fixed a typo in commit description of patch 1
- Added a comment in patch 2 explaining why PLL config errors are
ignored for rk_hdptx_phy_consumer_get()
- Added a missed FIELD_GET conversion for lcpll_hw.pms_sdiv in patch 6
- Rebased onto latest phy/fixes
- Link to v1: https://lore.kernel.org/r/20260227-hdptx-clk-fixes-v1-0-f998f2762d0f@collabora.com
---
Cristian Ciocaltea (8):
phy: rockchip: samsung-hdptx: Fix rate recalculation for high bpc
phy: rockchip: samsung-hdptx: Prevent divide-by-zero when computing clk rate
phy: rockchip: samsung-hdptx: Fix rate recalculation for 3.2GHz FRL
phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes
phy: rockchip: samsung-hdptx: Drop TMDS rate setup workaround
phy: rockchip: samsung-hdptx: Drop restrict_rate_change handling
phy: rockchip: samsung-hdptx: Simplify GRF access with FIELD_PREP_WM16()
phy: rockchip: samsung-hdptx: Consistently use bitfield macros
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 266 +++++++++++-----------
1 file changed, 130 insertions(+), 136 deletions(-)
---
base-commit: 293e19f416fa3f233a2fb013258f7abcb39ad6ed
change-id: 20260227-hdptx-clk-fixes-47426632f862
^ permalink raw reply
* [PATCH v4 1/8] phy: rockchip: samsung-hdptx: Fix rate recalculation for high bpc
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com>
The PHY PLL can be programmed by an external component, e.g. the
bootloader, just before the recalc_rate() callback is invoked during
devm_clk_hw_register() in the probe path.
Therefore rk_hdptx_phy_clk_recalc_rate() finds the PLL enabled and
attempts to compute the clock rate, while making use of the bpc value
from the HDMI PHY configuration, which always defaults to 8 because
phy_configure() was not run at that point. As a consequence, the
(re)calculated rate is incorrect when the actual bpc was higher than 8.
Do not rely on any of the hdmi_cfg members when computing the clock rate
and, instead, read the required input data (i.e. bpc), directly from the
hardware registers.
Fixes: 3481fc04d969 ("phy: rockchip: samsung-hdptx: Compute clk rate from PLL config")
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 2d973bc37f07..710603afff86 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -2168,7 +2168,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
struct lcpll_config lcpll_hw;
struct ropll_config ropll_hw;
u64 fout, sdm;
- u32 mode, val;
+ u32 mode, bpc, val;
int ret, i;
ret = regmap_read(hdptx->regmap, CMN_REG(0008), &mode);
@@ -2266,6 +2266,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
if (ret)
return 0;
ropll_hw.pms_sdiv = ((val & PLL_PCG_POSTDIV_SEL_MASK) >> 4) + 1;
+ bpc = (FIELD_GET(PLL_PCG_CLK_SEL_MASK, val) << 1) + 8;
fout = PLL_REF_CLK * ropll_hw.pms_mdiv;
if (ropll_hw.sdm_en) {
@@ -2280,7 +2281,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
fout = fout + sdm;
}
- return div_u64(fout * 2, ropll_hw.pms_sdiv * 10);
+ return DIV_ROUND_CLOSEST_ULL(fout * 2 * 8, ropll_hw.pms_sdiv * 10 * bpc);
}
static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw,
@@ -2288,19 +2289,13 @@ static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw,
{
struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
u32 status;
- u64 rate;
int ret;
ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &status);
if (ret || !(status & HDPTX_I_PLL_EN))
return 0;
- rate = rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx);
-
- if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
- return rate;
-
- return DIV_ROUND_CLOSEST_ULL(rate * 8, hdptx->hdmi_cfg.bpc);
+ return rk_hdptx_phy_clk_calc_rate_from_pll_cfg(hdptx);
}
static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw,
--
2.54.0
^ permalink raw reply related
* [PATCH v4 2/8] phy: rockchip: samsung-hdptx: Prevent divide-by-zero when computing clk rate
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Sashiko
In-Reply-To: <20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com>
Calculating 'sdm' fraction in rk_hdptx_phy_clk_calc_rate_from_pll_cfg()
could trigger a divide-by-zero, as it uses div_u64() with a denominator
read directly from hardware: the values ropll_hw.sdm_deno,
ropll_hw.sdc_deno, ropll_hw.sdc_n, and ropll_hw.sdc_num are populated
from PLL registers which, in theory, could be left by the bootloader
uninitialized/misconfigured.
Provide the necessary sanitization to avoid trusting the hardware state.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com?part=1
Fixes: 3481fc04d969 ("phy: rockchip: samsung-hdptx: Compute clk rate from PLL config")
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 710603afff86..8c044381b83a 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -2270,10 +2270,15 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
fout = PLL_REF_CLK * ropll_hw.pms_mdiv;
if (ropll_hw.sdm_en) {
+ val = 16U * ropll_hw.sdm_deno *
+ (ropll_hw.sdc_deno * ropll_hw.sdc_n - ropll_hw.sdc_num);
+ if (!val) {
+ dev_dbg(hdptx->dev, "%s invalid ROPLL hw state\n", __func__);
+ return 0;
+ }
+
sdm = div_u64(PLL_REF_CLK * ropll_hw.sdc_deno *
- ropll_hw.pms_mdiv * ropll_hw.sdm_num,
- 16 * ropll_hw.sdm_deno *
- (ropll_hw.sdc_deno * ropll_hw.sdc_n - ropll_hw.sdc_num));
+ ropll_hw.pms_mdiv * ropll_hw.sdm_num, val);
if (ropll_hw.sdm_num_sign)
fout = fout - sdm;
--
2.54.0
^ permalink raw reply related
* [PATCH v4 3/8] phy: rockchip: samsung-hdptx: Fix rate recalculation for 3.2GHz FRL
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Sashiko
In-Reply-To: <20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com>
rk_hdptx_phy_clk_calc_rate_from_pll_cfg() is currently unable to handle
cascade mode for the 3.2GHz FRL operating mode, as it relies solely on
LCPLL_LCVCO_MODE_EN_MASK to determinate the rate from the
rk_hdptx_frl_lcpll_cfg array. Since there is no entry for this
particular rate, the function returns 0.
This is the only rate which requires LC_REF_CLK_SEL to be set in
GRF_HDPTX_CON0, hence extend the FRL matching accordingly.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com?part=1
Fixes: de5dba833118 ("phy: rockchip: samsung-hdptx: Add HDMI 2.1 FRL support")
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 33 ++++++++++++++++-------
1 file changed, 24 insertions(+), 9 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 8c044381b83a..b210c1a88b25 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -2206,16 +2206,31 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
return 0;
lcpll_hw.sdc_n = (val & LCPLL_SDC_N_MASK) >> 1;
- for (i = 0; i < ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg); i++) {
- const struct lcpll_config *cfg = &rk_hdptx_frl_lcpll_cfg[i];
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &val);
+ if (ret)
+ return 0;
- if (cfg->pms_mdiv == lcpll_hw.pms_mdiv &&
- cfg->pms_sdiv == lcpll_hw.pms_sdiv &&
- cfg->sdm_num_sign == lcpll_hw.sdm_num_sign &&
- cfg->sdm_num == lcpll_hw.sdm_num &&
- cfg->sdm_deno == lcpll_hw.sdm_deno &&
- cfg->sdc_n == lcpll_hw.sdc_n)
- return cfg->rate;
+ if (val & LC_REF_CLK_SEL) {
+ if (lcpll_hw.pms_mdiv == 0x6b &&
+ lcpll_hw.sdm_num_sign == 0x01 &&
+ lcpll_hw.sdm_num == 0x02 &&
+ lcpll_hw.sdm_deno == 0x09 &&
+ lcpll_hw.sdc_n == FIELD_GET(LCPLL_SDC_N_MASK, 0x02))
+ return FRL_8G4L_RATE;
+ } else {
+ const struct lcpll_config *cfg;
+
+ for (i = 0; i < ARRAY_SIZE(rk_hdptx_frl_lcpll_cfg); i++) {
+ cfg = &rk_hdptx_frl_lcpll_cfg[i];
+
+ if (cfg->pms_mdiv == lcpll_hw.pms_mdiv &&
+ cfg->pms_sdiv == lcpll_hw.pms_sdiv &&
+ cfg->sdm_num_sign == lcpll_hw.sdm_num_sign &&
+ cfg->sdm_num == lcpll_hw.sdm_num &&
+ cfg->sdm_deno == lcpll_hw.sdm_deno &&
+ cfg->sdc_n == lcpll_hw.sdc_n)
+ return cfg->rate;
+ }
}
dev_dbg(hdptx->dev, "%s no FRL match found\n", __func__);
--
2.54.0
^ permalink raw reply related
* [PATCH v4 5/8] phy: rockchip: samsung-hdptx: Drop TMDS rate setup workaround
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com>
Since commit ba9c2fe18c17 ("drm/rockchip: dw_hdmi_qp: Switch to
phy_configure()") the TMDS rate setup doesn't rely anymore on the
unconventional usage of the bus width, instead it is managed exclusively
through the HDMI PHY configuration API.
Drop the now obsolete workaround to retrieve the TMDS character rate via
phy_get_bus_width() during power_on().
While at it, get rid of the extra call to rk_hdptx_phy_consumer_put() by
moving the statement at the end of the function.
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 27 +++++------------------
1 file changed, 6 insertions(+), 21 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 25bd821cd039..35997087d61c 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -1660,22 +1660,6 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
enum phy_mode mode = phy_get_mode(phy);
int ret, lane;
- if (mode != PHY_MODE_DP) {
- if (!hdptx->hdmi_cfg.rate && hdptx->hdmi_cfg.mode != PHY_HDMI_MODE_FRL) {
- /*
- * FIXME: Temporary workaround to setup TMDS char rate
- * from the RK DW HDMI QP bridge driver.
- * Will be removed as soon the switch to the HDMI PHY
- * configuration API has been completed on both ends.
- */
- hdptx->hdmi_cfg.rate = phy_get_bus_width(hdptx->phy) & 0xfffffff;
- hdptx->hdmi_cfg.rate *= 100;
- }
-
- dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
- hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
- }
-
ret = rk_hdptx_phy_consumer_get(hdptx);
if (ret)
return ret;
@@ -1701,9 +1685,10 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
rk_hdptx_dp_pll_init(hdptx);
ret = rk_hdptx_dp_aux_init(hdptx);
- if (ret)
- rk_hdptx_phy_consumer_put(hdptx, true);
} else {
+ dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
+ hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
+
if (hdptx->pll_config_dirty)
ret = rk_hdptx_pll_cmn_config(hdptx);
@@ -1716,11 +1701,11 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
else
ret = rk_hdptx_tmds_ropll_mode_config(hdptx);
}
-
- if (ret)
- rk_hdptx_phy_consumer_put(hdptx, true);
}
+ if (ret)
+ rk_hdptx_phy_consumer_put(hdptx, true);
+
return ret;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v4 8/8] phy: rockchip: samsung-hdptx: Consistently use bitfield macros
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com>
Make the code more robust and improve readability by using the available
bitfield macros (e.g. FIELD_PREP, FIELD_GET) whenever possible, instead
of open coding the related bit operations.
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 30 +++++++++++++++--------
1 file changed, 20 insertions(+), 10 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 88b48f5f946d..d8646f24e05e 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -53,6 +53,12 @@
/* CMN_REG(001e) */
#define LCPLL_PI_EN_MASK BIT(5)
#define LCPLL_100M_CLK_EN_MASK BIT(0)
+/* CMN_REG(0022) */
+#define ANA_LCPLL_PMS_PDIV_MASK GENMASK(7, 4)
+#define ANA_LCPLL_PMS_REFDIV_MASK GENMASK(3, 0)
+/* CMN_REG(0023) */
+#define LCPLL_PMS_SDIV_RBR_MASK GENMASK(7, 4)
+#define LCPLL_PMS_SDIV_HBR_MASK GENMASK(3, 0)
/* CMN_REG(0025) */
#define LCPLL_PMS_IQDIV_RSTN_MASK BIT(4)
/* CMN_REG(0028) */
@@ -1157,9 +1163,11 @@ static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx)
regmap_write(hdptx->regmap, CMN_REG(0020), cfg->pms_mdiv);
regmap_write(hdptx->regmap, CMN_REG(0021), cfg->pms_mdiv_afc);
regmap_write(hdptx->regmap, CMN_REG(0022),
- (cfg->pms_pdiv << 4) | cfg->pms_refdiv);
+ FIELD_PREP(ANA_LCPLL_PMS_PDIV_MASK, cfg->pms_pdiv) |
+ FIELD_PREP(ANA_LCPLL_PMS_REFDIV_MASK, cfg->pms_refdiv));
regmap_write(hdptx->regmap, CMN_REG(0023),
- (cfg->pms_sdiv << 4) | cfg->pms_sdiv);
+ FIELD_PREP(LCPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv) |
+ FIELD_PREP(LCPLL_PMS_SDIV_HBR_MASK, cfg->pms_sdiv));
regmap_write(hdptx->regmap, CMN_REG(002a), cfg->sdm_deno);
regmap_write(hdptx->regmap, CMN_REG(002b), cfg->sdm_num_sign);
regmap_write(hdptx->regmap, CMN_REG(002c), cfg->sdm_num);
@@ -1229,8 +1237,10 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
regmap_write(hdptx->regmap, CMN_REG(0051), cfg->pms_mdiv);
regmap_write(hdptx->regmap, CMN_REG(0055), cfg->pms_mdiv_afc);
regmap_write(hdptx->regmap, CMN_REG(0059),
- (cfg->pms_pdiv << 4) | cfg->pms_refdiv);
- regmap_write(hdptx->regmap, CMN_REG(005a), cfg->pms_sdiv << 4);
+ FIELD_PREP(ANA_ROPLL_PMS_PDIV_MASK, cfg->pms_pdiv) |
+ FIELD_PREP(ANA_ROPLL_PMS_REFDIV_MASK, cfg->pms_refdiv));
+ regmap_write(hdptx->regmap, CMN_REG(005a),
+ FIELD_PREP(ROPLL_PMS_SDIV_RBR_MASK, cfg->pms_sdiv));
regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDM_EN_MASK,
FIELD_PREP(ROPLL_SDM_EN_MASK, cfg->sdm_en));
@@ -2177,7 +2187,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
ret = regmap_read(hdptx->regmap, CMN_REG(0023), &val);
if (ret)
return 0;
- lcpll_hw.pms_sdiv = val & 0xf;
+ lcpll_hw.pms_sdiv = FIELD_GET(LCPLL_PMS_SDIV_HBR_MASK, val);
ret = regmap_read(hdptx->regmap, CMN_REG(002B), &val);
if (ret)
@@ -2197,7 +2207,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
ret = regmap_read(hdptx->regmap, CMN_REG(002D), &val);
if (ret)
return 0;
- lcpll_hw.sdc_n = (val & LCPLL_SDC_N_MASK) >> 1;
+ lcpll_hw.sdc_n = FIELD_GET(LCPLL_SDC_N_MASK, val);
ret = regmap_read(hdptx->grf, GRF_HDPTX_CON0, &val);
if (ret)
@@ -2238,12 +2248,12 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
ret = regmap_read(hdptx->regmap, CMN_REG(005E), &val);
if (ret)
return 0;
- ropll_hw.sdm_en = val & ROPLL_SDM_EN_MASK;
+ ropll_hw.sdm_en = FIELD_GET(ROPLL_SDM_EN_MASK, val);
ret = regmap_read(hdptx->regmap, CMN_REG(0064), &val);
if (ret)
return 0;
- ropll_hw.sdm_num_sign = val & ROPLL_SDM_NUM_SIGN_RBR_MASK;
+ ropll_hw.sdm_num_sign = FIELD_GET(ROPLL_SDM_NUM_SIGN_RBR_MASK, val);
ret = regmap_read(hdptx->regmap, CMN_REG(0065), &val);
if (ret)
@@ -2258,7 +2268,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
ret = regmap_read(hdptx->regmap, CMN_REG(0069), &val);
if (ret)
return 0;
- ropll_hw.sdc_n = (val & ROPLL_SDC_N_RBR_MASK) + 3;
+ ropll_hw.sdc_n = FIELD_GET(ROPLL_SDC_N_RBR_MASK, val) + 3;
ret = regmap_read(hdptx->regmap, CMN_REG(006c), &val);
if (ret)
@@ -2273,7 +2283,7 @@ static u64 rk_hdptx_phy_clk_calc_rate_from_pll_cfg(struct rk_hdptx_phy *hdptx)
ret = regmap_read(hdptx->regmap, CMN_REG(0086), &val);
if (ret)
return 0;
- ropll_hw.pms_sdiv = ((val & PLL_PCG_POSTDIV_SEL_MASK) >> 4) + 1;
+ ropll_hw.pms_sdiv = FIELD_GET(PLL_PCG_POSTDIV_SEL_MASK, val) + 1;
bpc = (FIELD_GET(PLL_PCG_CLK_SEL_MASK, val) << 1) + 8;
fout = PLL_REF_CLK * ropll_hw.pms_mdiv;
--
2.54.0
^ permalink raw reply related
* [PATCH v4 6/8] phy: rockchip: samsung-hdptx: Drop restrict_rate_change handling
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com>
Since commit 6efbd0f46dd8 ("phy: rockchip: samsung-hdptx: Restrict
altering TMDS char rate via CCF"), adjusting the rate via the Common
Clock Framework API has been disallowed.
To avoid breaking existing users until switching to the PHY config API,
it introduced a temporary exception to the rule, controlled via the
'restrict_rate_change' flag.
As the API transition completed, remove the now deprecated exception
logic.
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 42 +++++------------------
1 file changed, 8 insertions(+), 34 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 35997087d61c..b74a433c7e53 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -414,7 +414,6 @@ struct rk_hdptx_phy {
/* clk provider */
struct clk_hw hw;
bool pll_config_dirty;
- bool restrict_rate_change;
atomic_t usage_count;
@@ -2074,7 +2073,6 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt
if (ret) {
dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
} else {
- hdptx->restrict_rate_change = true;
hdptx->pll_config_dirty = true;
dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__,
@@ -2321,41 +2319,17 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw,
struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
/*
- * Invalidate current clock rate to ensure rk_hdptx_phy_clk_set_rate()
- * will be invoked to commit PLL configuration.
+ * For uncommitted PLL configuration, invalidate the current clock rate
+ * to ensure rk_hdptx_phy_clk_set_rate() will be always invoked.
+ * Otherwise, restrict the rate according to the PHY link setup.
*/
- if (hdptx->pll_config_dirty) {
+ if (hdptx->pll_config_dirty)
req->rate = 0;
- return 0;
- }
-
- if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) {
+ else if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
req->rate = hdptx->hdmi_cfg.rate;
- return 0;
- }
-
- /*
- * FIXME: Temporarily allow altering TMDS char rate via CCF.
- * To be dropped as soon as the RK DW HDMI QP bridge driver
- * switches to make use of phy_configure().
- */
- if (!hdptx->restrict_rate_change && req->rate != hdptx->hdmi_cfg.rate) {
- struct phy_configure_opts_hdmi hdmi = {
- .tmds_char_rate = req->rate,
- };
-
- int ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &hdmi, &hdptx->hdmi_cfg);
-
- if (ret)
- return ret;
- }
-
- /*
- * The TMDS char rate shall be adjusted via phy_configure() only,
- * hence ensure rk_hdptx_phy_clk_set_rate() won't be invoked with
- * a different rate argument.
- */
- req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8, hdptx->hdmi_cfg.bpc);
+ else
+ req->rate = DIV_ROUND_CLOSEST_ULL(hdptx->hdmi_cfg.rate * 8,
+ hdptx->hdmi_cfg.bpc);
return 0;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v4 7/8] phy: rockchip: samsung-hdptx: Simplify GRF access with FIELD_PREP_WM16()
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com>
The 16 most significant bits of the general-purpose register (GRF) are
used as a write-enable mask for the remaining 16 bits.
Make use of the recently introduced FIELD_PREP_WM16() macro to avoid
open-coding the bit shift operations and improve code readability.
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 52 +++++++++++------------
1 file changed, 25 insertions(+), 27 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index b74a433c7e53..88b48f5f946d 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
- * Copyright (c) 2024 Collabora Ltd.
+ * Copyright (c) 2024-2026 Collabora Ltd.
*
* Author: Algea Cao <algea.cao@rock-chips.com>
* Author: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
@@ -10,6 +10,7 @@
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
+#include <linux/hw_bitfield.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -949,7 +950,9 @@ static void rk_hdptx_pre_power_up(struct rk_hdptx_phy *hdptx)
reset_control_assert(hdptx->rsts[RST_CMN].rstc);
reset_control_assert(hdptx->rsts[RST_INIT].rstc);
- val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16;
+ val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
}
@@ -960,8 +963,8 @@ static int rk_hdptx_post_enable_lane(struct rk_hdptx_phy *hdptx)
reset_control_deassert(hdptx->rsts[RST_LANE].rstc);
- val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 |
- HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN;
+ val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1));
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
/* 3 lanes FRL mode */
@@ -990,16 +993,15 @@ static int rk_hdptx_post_enable_pll(struct rk_hdptx_phy *hdptx)
u32 val;
int ret;
- val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 |
- HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN;
+ val = (FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1));
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
usleep_range(10, 15);
reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
usleep_range(10, 15);
- val = HDPTX_I_PLL_EN << 16 | HDPTX_I_PLL_EN;
- regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1));
usleep_range(10, 15);
reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
@@ -1037,7 +1039,9 @@ static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx)
reset_control_assert(hdptx->rsts[RST_CMN].rstc);
reset_control_assert(hdptx->rsts[RST_INIT].rstc);
- val = (HDPTX_I_PLL_EN | HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16;
+ val = (FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
}
@@ -1135,7 +1139,7 @@ static int rk_hdptx_frl_lcpll_cmn_config(struct rk_hdptx_phy *hdptx)
rk_hdptx_pre_power_up(hdptx);
- regmap_write(hdptx->grf, GRF_HDPTX_CON0, LC_REF_CLK_SEL << 16);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0));
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_cmn_init_seq);
@@ -1178,8 +1182,7 @@ static int rk_hdptx_frl_lcpll_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
rk_hdptx_pre_power_up(hdptx);
/* ROPLL input reference clock from LCPLL (cascade mode) */
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- (LC_REF_CLK_SEL << 16) | LC_REF_CLK_SEL);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 1));
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_frl_lcpll_ropll_cmn_init_seq);
@@ -1218,7 +1221,7 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
rk_hdptx_pre_power_up(hdptx);
- regmap_write(hdptx->grf, GRF_HDPTX_CON0, LC_REF_CLK_SEL << 16);
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(LC_REF_CLK_SEL, 0));
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_common_cmn_init_seq);
rk_hdptx_multi_reg_write(hdptx, rk_hdptx_tmds_cmn_init_seq);
@@ -1336,11 +1339,9 @@ static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx)
FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0));
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x0));
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x0));
+ FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 0) |
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 0));
}
static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx)
@@ -1616,9 +1617,8 @@ static int rk_hdptx_dp_aux_init(struct rk_hdptx_phy *hdptx)
FIELD_PREP(OVRD_SB_VREG_EN_MASK, 0x1));
regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x1));
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x1));
+ FIELD_PREP_WM16(HDPTX_I_BGR_EN, 1) |
+ FIELD_PREP_WM16(HDPTX_I_BIAS_EN, 1));
usleep_range(20, 25);
reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
@@ -1665,7 +1665,7 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
if (mode == PHY_MODE_DP) {
regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x1));
+ FIELD_PREP_WM16(HDPTX_MODE_SEL, 1));
for (lane = 0; lane < 4; lane++) {
regmap_update_bits(hdptx->regmap, LANE_REG(031e) + 0x400 * lane,
@@ -1693,7 +1693,7 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
if (!ret) {
regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
+ FIELD_PREP_WM16(HDPTX_MODE_SEL, 0));
if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
ret = rk_hdptx_frl_lcpll_mode_config(hdptx);
@@ -1828,8 +1828,7 @@ static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx,
u32 bw, status;
int ret;
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0));
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 0));
switch (dp->link_rate) {
case 1620:
@@ -1885,8 +1884,7 @@ static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx,
regmap_update_bits(hdptx->regmap, CMN_REG(0095), DP_TX_LINK_BW_MASK,
FIELD_PREP(DP_TX_LINK_BW_MASK, bw));
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x1));
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, FIELD_PREP_WM16(HDPTX_I_PLL_EN, 1));
ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS,
status, FIELD_GET(HDPTX_O_PLL_LOCK_DONE, status),
--
2.54.0
^ permalink raw reply related
* [PATCH v4 4/8] phy: rockchip: samsung-hdptx: Handle uncommitted PHY config changes
From: Cristian Ciocaltea @ 2026-06-11 23:45 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com>
Any changes to the PHY link rate and/or color depth done via the HDMI
PHY configuration API are not immediately programmed into the hardware,
but are delayed until the PHY usage count gets incremented from 0 to 1,
that is when it is powered on or when the PLL clock exposed through
the CCF API is prepared, whichever comes first.
Since the clock might remain in prepared state after subsequent PHY
config changes, the programming can also be triggered via
clk_ops.set_rate(). However, from the clock consumer perspective (i.e.
VOP2 display controller), the (pixel) clock rate doesn't vary with bpc,
as that is handled internally by the PHY and reflected in the TDMS
character rate only.
As a consequence, changing the bpc while preserving the modeline may
lead to out-of-sync issues between CCF and HDMI PHY config state,
because the .set_rate() callback is not invoked when clock rate remains
constant. This may also happen when the PHY PLL has been pre-programmed
by an external entity, e.g. the bootloader, which is actually a
regression introduced by the recent FRL patches.
Introduce a pll_config_dirty flag to keep track of uncommitted PHY
config changes and use it in clk_ops.determine_rate() to invalidate the
current clock rate (as known by CCF) and, consequently, ensure those
changes are programmed into hardware via clk_ops.set_rate().
Moreover, proceed with a similar fix in phy_ops.power_on() callback, to
handle the scenario where the CCF API is not used due to operating in
FRL mode, while the clock is still in a prepared state and thus
preventing rk_hdptx_phy_consumer_get() to apply the updated PHY
configuration.
Fixes: de5dba833118 ("phy: rockchip: samsung-hdptx: Add HDMI 2.1 FRL support")
Fixes: 9d0ec51d7c22 ("phy: rockchip: samsung-hdptx: Add high color depth management")
Tested-by: Thomas Niederprüm <dubito@online.de>
Tested-by: Simon Wright <simon@symple.nz>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 84 +++++++++++++----------
1 file changed, 48 insertions(+), 36 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index b210c1a88b25..25bd821cd039 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -413,6 +413,7 @@ struct rk_hdptx_phy {
/* clk provider */
struct clk_hw hw;
+ bool pll_config_dirty;
bool restrict_rate_change;
atomic_t usage_count;
@@ -1260,13 +1261,19 @@ static int rk_hdptx_tmds_ropll_cmn_config(struct rk_hdptx_phy *hdptx)
static int rk_hdptx_pll_cmn_config(struct rk_hdptx_phy *hdptx)
{
+ int ret;
+
if (hdptx->hdmi_cfg.rate <= HDMI20_MAX_RATE)
- return rk_hdptx_tmds_ropll_cmn_config(hdptx);
+ ret = rk_hdptx_tmds_ropll_cmn_config(hdptx);
+ else if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE)
+ ret = rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx);
+ else
+ ret = rk_hdptx_frl_lcpll_cmn_config(hdptx);
- if (hdptx->hdmi_cfg.rate == FRL_8G4L_RATE)
- return rk_hdptx_frl_lcpll_ropll_cmn_config(hdptx);
+ if (!ret)
+ hdptx->pll_config_dirty = false;
- return rk_hdptx_frl_lcpll_cmn_config(hdptx);
+ return ret;
}
static int rk_hdptx_frl_lcpll_mode_config(struct rk_hdptx_phy *hdptx)
@@ -1347,25 +1354,22 @@ static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx)
return 0;
ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status);
- if (ret)
- goto dec_usage;
-
- if (status & HDPTX_O_PLL_LOCK_DONE)
- dev_warn(hdptx->dev, "PLL locked by unknown consumer!\n");
+ if (ret) {
+ atomic_dec(&hdptx->usage_count);
+ return ret;
+ }
if (mode == PHY_MODE_DP) {
rk_hdptx_dp_reset(hdptx);
} else {
- ret = rk_hdptx_pll_cmn_config(hdptx);
- if (ret)
- goto dec_usage;
+ /*
+ * Ignore PLL config errors at this point as pll_config_dirty
+ * was not reset and, therefore, operation will be retried.
+ */
+ rk_hdptx_pll_cmn_config(hdptx);
}
return 0;
-
-dec_usage:
- atomic_dec(&hdptx->usage_count);
- return ret;
}
static int rk_hdptx_phy_consumer_put(struct rk_hdptx_phy *hdptx, bool force)
@@ -1700,13 +1704,18 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
if (ret)
rk_hdptx_phy_consumer_put(hdptx, true);
} else {
- regmap_write(hdptx->grf, GRF_HDPTX_CON0,
- HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
+ if (hdptx->pll_config_dirty)
+ ret = rk_hdptx_pll_cmn_config(hdptx);
- if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
- ret = rk_hdptx_frl_lcpll_mode_config(hdptx);
- else
- ret = rk_hdptx_tmds_ropll_mode_config(hdptx);
+ if (!ret) {
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
+ HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
+
+ if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
+ ret = rk_hdptx_frl_lcpll_mode_config(hdptx);
+ else
+ ret = rk_hdptx_tmds_ropll_mode_config(hdptx);
+ }
if (ret)
rk_hdptx_phy_consumer_put(hdptx, true);
@@ -2081,7 +2090,10 @@ static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opt
dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
} else {
hdptx->restrict_rate_change = true;
- dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
+ hdptx->pll_config_dirty = true;
+
+ dev_dbg(hdptx->dev, "%s %s rate=%llu bpc=%u\n", __func__,
+ hdptx->hdmi_cfg.mode ? "FRL" : "TMDS",
hdptx->hdmi_cfg.rate, hdptx->hdmi_cfg.bpc);
}
@@ -2323,8 +2335,19 @@ static int rk_hdptx_phy_clk_determine_rate(struct clk_hw *hw,
{
struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
- if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL)
- return hdptx->hdmi_cfg.rate;
+ /*
+ * Invalidate current clock rate to ensure rk_hdptx_phy_clk_set_rate()
+ * will be invoked to commit PLL configuration.
+ */
+ if (hdptx->pll_config_dirty) {
+ req->rate = 0;
+ return 0;
+ }
+
+ if (hdptx->hdmi_cfg.mode == PHY_HDMI_MODE_FRL) {
+ req->rate = hdptx->hdmi_cfg.rate;
+ return 0;
+ }
/*
* FIXME: Temporarily allow altering TMDS char rate via CCF.
@@ -2356,17 +2379,6 @@ static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
- unsigned long long link_rate = rate;
-
- if (hdptx->hdmi_cfg.mode != PHY_HDMI_MODE_FRL)
- link_rate = DIV_ROUND_CLOSEST_ULL(rate * hdptx->hdmi_cfg.bpc, 8);
-
- /* Revert any unlikely link rate change since determine_rate() */
- if (hdptx->hdmi_cfg.rate != link_rate) {
- dev_warn(hdptx->dev, "Reverting unexpected rate change from %llu to %llu\n",
- link_rate, hdptx->hdmi_cfg.rate);
- hdptx->hdmi_cfg.rate = link_rate;
- }
/*
* The link rate would be normally programmed in HW during
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v3 0/6] phy: rockchip: samsung-hdptx: Clock fixes and API transition cleanups
From: Cristian Ciocaltea @ 2026-06-11 23:58 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong, Heiko Stuebner, Algea Cao,
Dmitry Baryshkov
Cc: kernel, linux-phy, linux-arm-kernel, linux-rockchip, linux-kernel,
Thomas Niederprüm, Simon Wright
In-Reply-To: <20260611-hdptx-clk-fixes-v3-0-67b1b0c00e16@collabora.com>
Hi,
On 6/11/26 3:31 PM, Cristian Ciocaltea wrote:
> This series provides a set of bug fixes and cleanups for the Rockchip
> Samsung HDPTX PHY driver.
>
> The first part of the series (i.e. PATCH 1 & 2) addresses clock rate
> calculation and synchronization issues. Specifically, it fixes edge
> cases where the PHY PLL is pre-programmed by an external component (like
> a bootloader) or when changing the color depth (bpc) while keeping the
> modeline constant. Because the Common Clock Framework .set_rate()
> callback might not be invoked if the pixel clock remains unchanged, this
> previously led to out-of-sync states between CCF and the actual HDMI PHY
> configuration.
>
> The second part focuses on code cleanups and modernizing the register
> access. Now that dw_hdmi_qp driver has fully switched to using
> phy_configure(), we can drop the deprecated TMDS rate setup workarounds
> and the restrict_rate_change flag logic. Finally, it refactors the
> driver to consistently use standard bitfield macros.
>
> Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
> ---
> Changes in v3:
> - Replaced div_u64() with DIV_ROUND_CLOSEST_ULL() in Patch 1 (Sashiko)
> - Fixed theoretical usage_count unbalanced issue in Patch 2 (Sashiko)
> - Rebased series onto latest phy/next
> - Link to v2: https://patch.msgid.link/20260511-hdptx-clk-fixes-v2-0-664e41379cab@collabora.com
Please use v4 as I added two more patches to address latest findings from Sashiko:
https://lore.kernel.org/all/20260612-hdptx-clk-fixes-v4-0-ce5e1d456cda@collabora.com/
Thanks,
Cristian
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox