* [PATCH v3 7/7] net: wwan: t9xx: Add maintainers entry
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
Add MAINTAINERS entry for the MediaTek T9XX 5G WWAN modem device
driver.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
MAINTAINERS | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 461a3eed6129..8155d26bff03 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16494,6 +16494,15 @@ L: netdev@vger.kernel.org
S: Supported
F: drivers/net/wwan/t7xx/
+MEDIATEK T9XX 5G WWAN MODEM DRIVER
+M: Jack Wu <jackbb_wu@compal.com>
+R: Wen-Zhi Huang <wen-zhi.huang@mediatek.com>
+R: Shi-Wei Yeh <shi-wei.yeh@mediatek.com>
+R: Minano Tseng <Minano.tseng@mediatek.com>
+L: netdev@vger.kernel.org
+S: Supported
+F: drivers/net/wwan/t9xx/
+
MEDIATEK USB3 DRD IP DRIVER
M: Chunfeng Yun <chunfeng.yun@mediatek.com>
L: linux-usb@vger.kernel.org
--
2.34.1
^ permalink raw reply related
* [PATCH v3 6/7] net: wwan: t9xx: Add AT & MBIM WWAN ports
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
Add AT & MBIM ports to the port infrastructure.
The WWAN initialization method is responsible for creating the
corresponding ports using the WWAN framework infrastructure. The
implemented WWAN port operations are start, stop, tx, tx_blocking
and tx_poll.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
drivers/net/wwan/t9xx/mtk_port.c | 26 ++
drivers/net/wwan/t9xx/mtk_port.h | 13 +
drivers/net/wwan/t9xx/mtk_port_io.c | 336 ++++++++++++++++++++++++-
drivers/net/wwan/t9xx/mtk_port_io.h | 5 +
drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c | 8 +
5 files changed, 387 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c
index fbc2fb7bdc2d..9015be090023 100644
--- a/drivers/net/wwan/t9xx/mtk_port.c
+++ b/drivers/net/wwan/t9xx/mtk_port.c
@@ -819,6 +819,29 @@ int mtk_port_ch_disable(struct mtk_port *port)
return ret;
}
+static int mtk_port_enable_by_type(struct mtk_port_mngr *port_mngr, int tbl_type)
+{
+ struct mtk_port **ports;
+ int ret, idx;
+
+ if (tbl_type < 0 || tbl_type >= PORT_TBL_MAX)
+ return -EINVAL;
+
+ ports = kcalloc(port_mngr->port_cnt, sizeof(struct mtk_port *), GFP_KERNEL);
+ if (!ports)
+ return -ENOMEM;
+
+ ret = radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
+ (void **)ports, 0, port_mngr->port_cnt);
+ for (idx = 0; idx < ret; idx++) {
+ if (ports[idx]->enable)
+ ports_ops[ports[idx]->info.type]->enable(ports[idx]);
+ }
+
+ kfree(ports);
+ return 0;
+}
+
static void mtk_port_disable(struct mtk_port_mngr *port_mngr)
{
struct mtk_port **ports;
@@ -852,6 +875,9 @@ void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg)
case FSM_STATE_OFF:
mtk_port_disable(port_mngr);
break;
+ case FSM_STATE_READY:
+ mtk_port_enable_by_type(port_mngr, PORT_TBL_MD);
+ break;
default:
break;
}
diff --git a/drivers/net/wwan/t9xx/mtk_port.h b/drivers/net/wwan/t9xx/mtk_port.h
index a201c0007878..4846354981d7 100644
--- a/drivers/net/wwan/t9xx/mtk_port.h
+++ b/drivers/net/wwan/t9xx/mtk_port.h
@@ -56,6 +56,10 @@ enum mtk_ccci_ch {
/* to MD */
CCCI_CONTROL_RX = 0x2000,
CCCI_CONTROL_TX = 0x2001,
+ CCCI_UART2_RX = 0x200A,
+ CCCI_UART2_TX = 0x200C,
+ CCCI_MBIM_RX = 0x20D0,
+ CCCI_MBIM_TX = 0x20D1,
};
enum mtk_port_flag {
@@ -73,6 +77,7 @@ enum mtk_port_tbl {
enum mtk_port_type {
PORT_TYPE_INTERNAL,
+ PORT_TYPE_WWAN,
PORT_TYPE_MAX
};
@@ -81,6 +86,13 @@ struct mtk_internal_port {
int (*recv_cb)(void *arg, struct sk_buff *skb);
};
+struct mtk_wwan_port {
+ /* w_lock protects wwan_port when recv data and disable port at the same time */
+ struct mutex w_lock;
+ int w_type;
+ void *w_port;
+};
+
struct mtk_port_cfg {
enum mtk_ccci_ch tx_ch;
enum mtk_ccci_ch rx_ch;
@@ -110,6 +122,7 @@ struct mtk_port {
char dev_str[MTK_DEV_STR_LEN];
struct mtk_port_mngr *port_mngr;
struct mtk_internal_port i_priv;
+ struct mtk_wwan_port w_priv;
};
struct mtk_port_mngr {
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.c b/drivers/net/wwan/t9xx/mtk_port_io.c
index e3a2de6d2f29..882254b74026 100644
--- a/drivers/net/wwan/t9xx/mtk_port_io.c
+++ b/drivers/net/wwan/t9xx/mtk_port_io.c
@@ -3,6 +3,10 @@
* Copyright (c) 2022, MediaTek Inc.
*/
#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/wwan.h>
#include "mtk_port_io.h"
@@ -41,6 +45,145 @@ static void mtk_port_struct_init(struct mtk_port *port)
init_waitqueue_head(&port->rx_wq);
}
+static int mtk_port_copy_data_from(void *to, union user_buf from, unsigned int len,
+ unsigned int offset, bool from_user_space)
+{
+ if (from_user_space) {
+ if (copy_from_user(to, from.ubuf + offset, len))
+ return -EINVAL;
+ } else {
+ memcpy(to, from.kbuf + offset, len);
+ }
+
+ return 0;
+}
+
+static int mtk_port_common_write_frag_skb(struct mtk_port *port, struct sk_buff *skb,
+ union user_buf buf, u32 packet_size,
+ u32 cur_pos, bool from_user_space)
+{
+ struct sk_buff *frag_skb, *tmp = NULL;
+ u32 frag_size;
+ int ret;
+
+ frag_size = min(packet_size, port->tx_frag_size);
+ ret = mtk_port_copy_data_from(skb_put(skb, frag_size),
+ buf, frag_size,
+ cur_pos, from_user_space);
+ if (ret) {
+ dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to copy skb for port(%s)\n", port->info.name);
+ goto err_reset_skb;
+ }
+ cur_pos += frag_size;
+ packet_size -= frag_size;
+ if (!packet_size)
+ return cur_pos;
+
+ while (packet_size > 0) {
+ frag_skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
+ if (!frag_skb) {
+ ret = -ENOMEM;
+ goto err_free_frag_list;
+ }
+
+ frag_size = min(packet_size, port->tx_frag_size);
+ ret = mtk_port_copy_data_from(skb_put(frag_skb, frag_size),
+ buf, frag_size,
+ cur_pos, from_user_space);
+ if (ret) {
+ dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to copy frag_skb for port(%s)\n", port->info.name);
+ dev_kfree_skb_any(frag_skb);
+ goto err_free_frag_list;
+ }
+ skb->data_len += frag_size;
+ skb->len += frag_size;
+ cur_pos += frag_size;
+ packet_size -= frag_size;
+ if (!tmp)
+ skb_shinfo(skb)->frag_list = frag_skb;
+ else
+ tmp->next = frag_skb;
+ tmp = frag_skb;
+ }
+ return cur_pos;
+
+err_free_frag_list:
+ frag_skb = skb_shinfo(skb)->frag_list;
+ while (frag_skb) {
+ tmp = frag_skb->next;
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = tmp;
+ }
+ skb_shinfo(skb)->frag_list = NULL;
+err_reset_skb:
+ skb->data_len = 0;
+ return ret;
+}
+
+static int mtk_port_common_write(struct mtk_port *port, union user_buf buf, unsigned int len,
+ bool from_user_space)
+{
+ u32 packet_size, left_cnt = len, cur_pos;
+ struct sk_buff *skb;
+ int ret;
+
+ if (len == 0)
+ return -EINVAL;
+
+start_write:
+ ret = mtk_port_status_check(port);
+ if (ret)
+ goto end_write;
+
+ skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto end_write;
+ }
+
+ skb_reserve(skb, sizeof(struct mtk_ccci_header));
+
+ packet_size = min_t(u32, left_cnt, port->tx_mtu - sizeof(struct mtk_ccci_header));
+ cur_pos = len - left_cnt;
+ /* Support scatter gather transmission */
+ if (port->tx_mtu > port->tx_frag_size) {
+ ret = mtk_port_common_write_frag_skb(port, skb, buf, packet_size,
+ cur_pos, from_user_space);
+ if (ret < 0)
+ goto err_free_skb;
+ } else {
+ ret = mtk_port_copy_data_from(skb_put(skb, packet_size),
+ buf, packet_size,
+ cur_pos, from_user_space);
+ if (ret) {
+ dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to copy data for port(%s)\n", port->info.name);
+ goto err_free_skb;
+ }
+ }
+
+ ret = mtk_port_send_data(port, skb);
+ if (ret < 0) {
+ if (ret == -EINTR)
+ left_cnt -= packet_size;
+ goto end_write;
+ }
+
+ left_cnt -= ret;
+ if (left_cnt)
+ goto start_write;
+ else
+ goto end_write;
+
+err_free_skb:
+ dev_kfree_skb_any(skb);
+end_write:
+ return (len > left_cnt) ? (len - left_cnt) : ret;
+}
+
static int mtk_port_internal_init(struct mtk_port *port)
{
mtk_port_struct_init(port);
@@ -101,7 +244,6 @@ static int mtk_port_internal_recv(struct mtk_port *port, struct sk_buff *skb)
return ret;
drop_data:
- dev_kfree_skb_any(skb);
return ret;
}
@@ -234,6 +376,198 @@ static const struct port_ops port_internal_ops = {
.recv = mtk_port_internal_recv,
};
+static int mtk_port_wwan_open(struct wwan_port *w_port)
+{
+ struct mtk_port *port;
+ int ret;
+
+ port = wwan_port_get_drvdata(w_port);
+ ret = mtk_port_get_locked(port);
+ if (ret)
+ return ret;
+
+ ret = mtk_port_common_open(port);
+ if (ret)
+ mtk_port_put_locked(port);
+
+ return ret;
+}
+
+static void mtk_port_wwan_close(struct wwan_port *w_port)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+
+ mtk_port_common_close(port);
+ mtk_port_put_locked(port);
+}
+
+static int mtk_port_wwan_write(struct wwan_port *w_port, struct sk_buff *skb)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+ union user_buf user_buf;
+ int ret;
+
+ if (unlikely(!skb->len)) {
+ consume_skb(skb);
+ return 0;
+ }
+
+ port->info.flags &= ~PORT_F_BLOCKING;
+ user_buf.kbuf = (void *)skb->data;
+ ret = mtk_port_common_write(port, user_buf, skb->len, false);
+ if (ret < 0)
+ return ret;
+
+ consume_skb(skb);
+ return 0;
+}
+
+static int mtk_port_wwan_write_blocking(struct wwan_port *w_port, struct sk_buff *skb)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+ union user_buf user_buf;
+ int ret;
+
+ if (unlikely(!skb->len)) {
+ consume_skb(skb);
+ return 0;
+ }
+
+ port->info.flags |= PORT_F_BLOCKING;
+ user_buf.kbuf = (void *)skb->data;
+ ret = mtk_port_common_write(port, user_buf, skb->len, false);
+ if (ret < 0)
+ return ret;
+
+ consume_skb(skb);
+ return 0;
+}
+
+static __poll_t mtk_port_wwan_poll(struct wwan_port *w_port, struct file *file,
+ struct poll_table_struct *poll)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+ union ctrl_hif_cmd_data hif_cmd;
+ struct mtk_ctrl_blk *ctrl_blk;
+ __poll_t mask = 0;
+
+ poll_wait(file, &port->trb_wq, poll);
+ if (mtk_port_status_check(port))
+ return EPOLLERR | EPOLLHUP;
+
+ ctrl_blk = port->port_mngr->ctrl_blk;
+ hif_cmd.rx_ch = port->info.rx_ch;
+ if (!ctrl_blk->ops->send_cmd(ctrl_blk->mdev, HIF_CTRL_CMD_CHECK_TX_FULL, &hif_cmd))
+ mask |= EPOLLOUT | EPOLLWRNORM;
+
+ return mask;
+}
+
+static const struct wwan_port_ops wwan_ops = {
+ .start = mtk_port_wwan_open,
+ .stop = mtk_port_wwan_close,
+ .tx = mtk_port_wwan_write,
+ .tx_blocking = mtk_port_wwan_write_blocking,
+ .tx_poll = mtk_port_wwan_poll,
+};
+
+static int mtk_port_wwan_init(struct mtk_port *port)
+{
+ mtk_port_struct_init(port);
+ port->enable = false;
+
+ mutex_init(&port->w_priv.w_lock);
+
+ switch (port->info.rx_ch) {
+ case CCCI_MBIM_RX:
+ port->w_priv.w_type = WWAN_PORT_MBIM;
+ break;
+ case CCCI_UART2_RX:
+ port->w_priv.w_type = WWAN_PORT_AT;
+ break;
+ default:
+ port->w_priv.w_type = WWAN_PORT_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static void mtk_port_wwan_exit(struct mtk_port *port)
+{
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ ports_ops[port->info.type]->disable(port);
+}
+
+static void mtk_port_wwan_enable(struct mtk_port *port)
+{
+ struct mtk_port_mngr *port_mngr;
+ int ret;
+
+ port_mngr = port->port_mngr;
+
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ ret = mtk_port_ch_enable(port);
+ if (ret && ret != -EBUSY)
+ return;
+
+ port->w_priv.w_port = wwan_create_port(port_mngr->ctrl_blk->mdev->dev,
+ port->w_priv.w_type,
+ &wwan_ops, NULL, port);
+ if (IS_ERR(port->w_priv.w_port)) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to create wwan port for (%s)\n", port->info.name);
+ port->w_priv.w_port = NULL;
+ mtk_port_ch_disable(port);
+ return;
+ }
+
+ set_bit(PORT_S_WR, &port->status);
+ set_bit(PORT_S_ENABLE, &port->status);
+}
+
+static void mtk_port_wwan_disable(struct mtk_port *port)
+{
+ struct wwan_port *w_port;
+
+ if (!test_and_clear_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ clear_bit(PORT_S_WR, &port->status);
+ w_port = port->w_priv.w_port;
+ mutex_lock(&port->w_priv.w_lock);
+ port->w_priv.w_port = NULL;
+ mutex_unlock(&port->w_priv.w_lock);
+
+ mtk_port_ch_disable(port);
+ wwan_remove_port(w_port);
+}
+
+static int mtk_port_wwan_recv(struct mtk_port *port, struct sk_buff *skb)
+{
+ mutex_lock(&port->w_priv.w_lock);
+ if (!port->w_priv.w_port) {
+ mutex_unlock(&port->w_priv.w_lock);
+ return -ENXIO;
+ }
+
+ wwan_port_rx(port->w_priv.w_port, skb);
+ mutex_unlock(&port->w_priv.w_lock);
+ return 0;
+}
+
+static const struct port_ops port_wwan_ops = {
+ .init = mtk_port_wwan_init,
+ .exit = mtk_port_wwan_exit,
+ .reset = mtk_port_reset,
+ .enable = mtk_port_wwan_enable,
+ .disable = mtk_port_wwan_disable,
+ .recv = mtk_port_wwan_recv,
+};
+
const struct port_ops *ports_ops[PORT_TYPE_MAX] = {
&port_internal_ops,
+ &port_wwan_ops,
};
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.h b/drivers/net/wwan/t9xx/mtk_port_io.h
index 0c10e893b7e0..ea92cd22dba0 100644
--- a/drivers/net/wwan/t9xx/mtk_port_io.h
+++ b/drivers/net/wwan/t9xx/mtk_port_io.h
@@ -23,6 +23,11 @@ struct port_ops {
int (*recv)(struct mtk_port *port, struct sk_buff *skb);
};
+union user_buf {
+ void __user *ubuf;
+ void *kbuf;
+};
+
void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag);
int mtk_port_internal_close(void *i_port);
int mtk_port_internal_write(void *i_port, struct sk_buff *skb);
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
index 8611561dd67c..aab09cab360c 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
@@ -16,6 +16,10 @@ static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {
/* the number of RX GPDs should be at last two */
static const struct queue_info mtk_queue_info_m9xx[] = {
+ {CCCI_UART2_TX, CCCI_UART2_RX, CLDMA1, TXQ(5), RXQ(5),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+ {CCCI_MBIM_TX, CCCI_MBIM_RX, CLDMA1, TXQ(2), RXQ(2),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
{CCCI_CONTROL_TX, CCCI_CONTROL_RX, CLDMA1, TXQ(0), RXQ(0),
Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, CLDMA0, TXQ(0), RXQ(0),
@@ -23,6 +27,10 @@ static const struct queue_info mtk_queue_info_m9xx[] = {
};
static const struct mtk_port_cfg port_cfg_m9xx[] = {
+ {CCCI_UART2_TX, CCCI_UART2_RX, PORT_TYPE_WWAN, "AT",
+ PORT_F_ALLOW_DROP},
+ {CCCI_MBIM_TX, CCCI_MBIM_RX, PORT_TYPE_WWAN, "MBIM",
+ PORT_F_ALLOW_DROP},
{CCCI_CONTROL_TX, CCCI_CONTROL_RX, PORT_TYPE_INTERNAL, "MDCTRL",
PORT_F_ALLOW_DROP},
{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, PORT_TYPE_INTERNAL, "SAPCTRL",
--
2.34.1
^ permalink raw reply related
* [PATCH v3 4/7] net: wwan: t9xx: Add control port
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
The control port consists of port I/O and port manager.
Port I/O provides a common operation as defined by "struct port_ops",
and the operation is managed by the "port manager". It provides
interfaces to internal users, the implemented internal interfaces are
open, close, write and recv_register.
The port manager defines and implements port management interfaces and
structures. It is responsible for port creation, destroying, and managing
port states. It sends data from port I/O to CLDMA via TRB ( Transaction
Request Block ), and dispatches received data from CLDMA to port I/O.
The using port will be held in the "stale list" when the driver destroys
it, and after creating it again, the user can continue to use it.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
drivers/net/wwan/t9xx/Makefile | 4 +-
drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 19 +-
drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 20 +-
drivers/net/wwan/t9xx/mtk_dev.c | 13 +-
drivers/net/wwan/t9xx/mtk_port.c | 877 +++++++++++++++++++++++++
drivers/net/wwan/t9xx/mtk_port.h | 159 +++++
drivers/net/wwan/t9xx/mtk_port_io.c | 239 +++++++
drivers/net/wwan/t9xx/mtk_port_io.h | 36 +
drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c | 25 +-
drivers/net/wwan/t9xx/pcie/mtk_pci.c | 2 +
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c | 24 +-
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 1 +
12 files changed, 1407 insertions(+), 12 deletions(-)
diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
index ae9d6f2344ab..db3b1aa1928b 100644
--- a/drivers/net/wwan/t9xx/Makefile
+++ b/drivers/net/wwan/t9xx/Makefile
@@ -8,4 +8,6 @@ obj-$(CONFIG_MTK_T9XX_PCI) += pcie/
mtk_t9xx-y := \
mtk_dev.o \
- mtk_ctrl_plane.o
+ mtk_ctrl_plane.o \
+ mtk_port.o \
+ mtk_port_io.o
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
index 70348696ac44..b9a0443ce8ec 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
@@ -7,20 +7,23 @@
#include <linux/device.h>
#include "mtk_ctrl_plane.h"
+#include "mtk_port.h"
/**
* mtk_ctrl_init() - Initialize the control plane block.
* @mdev: Pointer to the MTK modem device.
* @ops: HIF operations for the control plane.
+ * @cfg: Control plane configuration.
*
* Allocates and initializes the control plane block
* associated with @mdev.
*
- * Return: 0 on success, -ENOMEM on allocation failure.
+ * Return: 0 on success, negative error code on failure.
*/
-int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops)
+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops, struct mtk_ctrl_cfg *cfg)
{
struct mtk_ctrl_blk *ctrl_blk;
+ int err;
ctrl_blk = devm_kzalloc(mdev->dev, sizeof(*ctrl_blk), GFP_KERNEL);
if (!ctrl_blk)
@@ -29,8 +32,19 @@ int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops)
ctrl_blk->mdev = mdev;
mdev->ctrl_blk = ctrl_blk;
ctrl_blk->ops = ops;
+ ctrl_blk->cfg = cfg;
+
+ err = mtk_port_mngr_init(ctrl_blk, cfg->port_layer_cfg->port_cfg,
+ cfg->port_layer_cfg->port_cnt);
+ if (err)
+ goto err_free_mem;
return 0;
+
+err_free_mem:
+ devm_kfree(mdev->dev, ctrl_blk);
+
+ return err;
}
EXPORT_SYMBOL(mtk_ctrl_init);
@@ -44,6 +58,7 @@ void mtk_ctrl_exit(struct mtk_md_dev *mdev)
{
struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+ mtk_port_mngr_exit(ctrl_blk);
devm_kfree(mdev->dev, ctrl_blk);
mdev->ctrl_blk = NULL;
}
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
index 88d71ac92084..d7fcccde8a1b 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
@@ -11,6 +11,17 @@
#include "mtk_dev.h"
+#define Q_MTU_2K (0x800)
+#define Q_MTU_3_5K (0xE00)
+#define Q_MTU_7K (0x1C00)
+#define Q_MTU_32K (0x8000)
+#define Q_MTU_63K (0xFC00)
+#define Q_FRAG_2K (0x800)
+#define Q_FRAG_3_5K (0xE00)
+#define Q_FRAG_7K (0x1C00)
+#define Q_FRAG_32K (0x8000)
+#define Q_FRAG_63K (0xFC00)
+
enum mtk_trb_cmd_type {
TRB_CMD_MIN,
TRB_CMD_ENABLE,
@@ -54,17 +65,22 @@ struct mtk_ctrl_hif_ops {
int (*send_cmd)(struct mtk_md_dev *mdev, int cmd, void *data);
};
-struct mtk_ctrl_cfg;
+struct mtk_ctrl_cfg {
+ struct mtk_port_layer_cfg *port_layer_cfg;
+};
+
struct mtk_ctrl_trans;
struct mtk_ctrl_blk {
struct mtk_md_dev *mdev;
+ struct mtk_port_mngr *port_mngr;
struct mtk_ctrl_hif_ops *ops;
void *ctrl_hw_priv;
struct mtk_ctrl_cfg *cfg;
};
-int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops);
+int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops,
+ struct mtk_ctrl_cfg *cfg);
void mtk_ctrl_exit(struct mtk_md_dev *mdev);
#endif /* __MTK_CTRL_PLANE_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_dev.c b/drivers/net/wwan/t9xx/mtk_dev.c
index f254ca7ed877..8ba70d432e6f 100644
--- a/drivers/net/wwan/t9xx/mtk_dev.c
+++ b/drivers/net/wwan/t9xx/mtk_dev.c
@@ -6,6 +6,8 @@
#include <linux/module.h>
#include "mtk_dev.h"
+#include "mtk_port.h"
+#include "mtk_port_io.h"
struct mtk_md_dev *mtk_dev_alloc(struct device *pdev, const struct mtk_dev_ops *dev_ops)
{
@@ -31,12 +33,21 @@ EXPORT_SYMBOL(mtk_dev_free);
static int __init mtk_common_drv_init(void)
{
- return 0;
+ int ret;
+
+ ret = mtk_port_io_init();
+ if (ret)
+ goto err_init_devid;
+
+err_init_devid:
+ return ret;
}
module_init(mtk_common_drv_init);
static void __exit mtk_common_drv_exit(void)
{
+ mtk_port_io_exit();
+ mtk_port_stale_list_grp_cleanup();
}
module_exit(mtk_common_drv_exit);
diff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c
new file mode 100644
index 000000000000..034c9ad0f892
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port.c
@@ -0,0 +1,877 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include "mtk_port.h"
+#include "mtk_port_io.h"
+
+#define MTK_DFLT_TRB_TIMEOUT (5 * HZ)
+#define MTK_DFLT_TRB_STATUS (0x1)
+#define MTK_TRB_HEADER_ADDED (0xADDED)
+#define MTK_CHECK_RX_SEQ_MASK (0x7fff)
+
+#define MTK_PORT_ENUM_VER (0)
+#define MTK_PORT_ENUM_HEAD_PATTERN (0x5a5a5a5a)
+#define MTK_PORT_ENUM_TAIL_PATTERN (0xa5a5a5a5)
+
+#define MTK_PORT_SEARCH_FROM_RADIX_TREE(p, s) ({\
+ struct mtk_port *_p; \
+ _p = rcu_dereference_raw(*(s)); \
+ if (!_p) \
+ continue; \
+ p = _p; \
+})
+
+#define MTK_PORT_INTERNAL_NODE_CHECK(p, s, i) ({\
+ if (radix_tree_is_internal_node(p)) { \
+ s = radix_tree_iter_retry(&(i));\
+ continue; \
+ } \
+})
+
+struct mtk_port_info {
+ __le16 channel;
+ __le16 reserved;
+} __packed;
+
+struct mtk_port_enum_msg {
+ __le32 head_pattern;
+ __le16 port_cnt;
+ __le16 version;
+ __le32 tail_pattern;
+ u8 data[];
+} __packed;
+
+/* global group for stale ports */
+static LIST_HEAD(stale_list_grp);
+/* mutex lock for stale_list_group */
+DEFINE_MUTEX(port_mngr_grp_mtx);
+
+static DEFINE_IDA(ccci_dev_ids);
+
+/* This function working always under mutex lock port_mngr_grp_mtx */
+void mtk_port_release(struct kref *port_kref)
+{
+ struct mtk_stale_list *s_list;
+ struct mtk_port *port;
+
+ port = container_of(port_kref, struct mtk_port, kref);
+ if (!test_bit(PORT_S_ON_STALE_LIST, &port->status))
+ goto port_exit;
+
+ list_del(&port->stale_entry);
+ list_for_each_entry(s_list, &stale_list_grp, entry) {
+ if (!strncmp(s_list->dev_str, port->dev_str, MTK_DEV_STR_LEN) &&
+ list_empty(&s_list->ports) && s_list->dev_id >= 0) {
+ ida_free(&ccci_dev_ids, s_list->dev_id);
+ s_list->dev_id = -1;
+ break;
+ }
+ }
+port_exit:
+ ports_ops[port->info.type]->exit(port);
+ kfree(port);
+}
+
+static int mtk_port_tbl_add(struct mtk_port_mngr *port_mngr, struct mtk_port *port)
+{
+ int ret;
+
+ ret = radix_tree_insert(&port_mngr->port_tbl[MTK_PORT_TBL_TYPE(port->info.rx_ch)],
+ port->info.rx_ch & 0xFFF, port);
+ if (ret)
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "port(%s) add to port_tbl failed, return %d\n",
+ port->info.name, ret);
+ else
+ port_mngr->port_cnt++;
+
+ return ret;
+}
+
+static void mtk_port_tbl_del(struct mtk_port_mngr *port_mngr, struct mtk_port *port)
+{
+ radix_tree_delete(&port_mngr->port_tbl[MTK_PORT_TBL_TYPE(port->info.rx_ch)],
+ port->info.rx_ch & 0xFFF);
+ port_mngr->port_cnt--;
+}
+
+static struct mtk_port *mtk_port_restore_from_stale_list(struct mtk_port_mngr *port_mngr,
+ struct mtk_stale_list *s_list)
+{
+ struct mtk_port *port, *next_port;
+ int ret;
+
+ mutex_lock(&port_mngr_grp_mtx);
+ list_for_each_entry_safe(port, next_port, &s_list->ports, stale_entry) {
+ kref_get(&port->kref);
+ list_del(&port->stale_entry);
+ ret = mtk_port_tbl_add(port_mngr, port);
+ if (ret) {
+ list_add_tail(&port->stale_entry, &s_list->ports);
+ kref_put(&port->kref, mtk_port_release);
+ mutex_unlock(&port_mngr_grp_mtx);
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "Failed when adding (%s) to port mngr\n",
+ port->info.name);
+ return ERR_PTR(ret);
+ }
+
+ port->port_mngr = port_mngr;
+ clear_bit(PORT_S_ON_STALE_LIST, &port->status);
+ ports_ops[port->info.type]->reset(port);
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return NULL;
+}
+
+static struct mtk_port *mtk_port_alloc_and_add(struct mtk_port_mngr *port_mngr,
+ struct mtk_port_cfg *dflt_info)
+{
+ struct mtk_port *port;
+ int ret;
+
+ port = kzalloc_obj(*port, GFP_KERNEL);
+ if (!port) {
+ ret = -ENOMEM;
+ goto err_alloc_port;
+ }
+ memcpy(&port->info, dflt_info, sizeof(*dflt_info));
+
+ ret = mtk_port_tbl_add(port_mngr, port);
+ if (ret < 0) {
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to add port(%s) to port tbl\n", dflt_info->name);
+ goto err_free_port;
+ }
+
+ port->port_mngr = port_mngr;
+ ret = ports_ops[port->info.type]->init(port);
+ if (ret < 0) {
+ mtk_port_tbl_del(port_mngr, port);
+ goto err_free_port;
+ }
+
+ memcpy(port->dev_str, port_mngr->ctrl_blk->mdev->dev_str, MTK_DEV_STR_LEN);
+ return port;
+
+err_free_port:
+ kfree(port);
+err_alloc_port:
+ return ERR_PTR(ret);
+}
+
+static void mtk_port_free_or_backup(struct mtk_port_mngr *port_mngr,
+ struct mtk_port *port, struct mtk_stale_list *s_list)
+{
+ mutex_lock(&port_mngr_grp_mtx);
+ mtk_port_tbl_del(port_mngr, port);
+ if (port->info.type != PORT_TYPE_INTERNAL) {
+ if (test_bit(PORT_S_OPEN, &port->status)) {
+ list_add_tail(&port->stale_entry, &s_list->ports);
+ set_bit(PORT_S_ON_STALE_LIST, &port->status);
+ memcpy(port->dev_str, port_mngr->ctrl_blk->mdev->dev_str,
+ MTK_DEV_STR_LEN);
+ port->port_mngr = NULL;
+ }
+ kref_put(&port->kref, mtk_port_release);
+ } else {
+ mtk_port_release(&port->kref);
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static struct mtk_port *mtk_port_search_by_id(struct mtk_port_mngr *port_mngr, int rx_ch)
+{
+ int tbl_type = MTK_PORT_TBL_TYPE(rx_ch);
+
+ if (tbl_type < PORT_TBL_SAP || tbl_type >= PORT_TBL_MAX)
+ return NULL;
+
+ return radix_tree_lookup(&port_mngr->port_tbl[tbl_type], MTK_CH_ID(rx_ch));
+}
+
+struct mtk_port *mtk_port_search_by_name(struct mtk_port_mngr *port_mngr, char *name)
+{
+ int tbl_type = PORT_TBL_SAP;
+ struct radix_tree_iter iter;
+ struct mtk_port *port;
+ void __rcu **slot;
+
+ do {
+ radix_tree_for_each_slot(slot, &port_mngr->port_tbl[tbl_type], &iter, 0) {
+ MTK_PORT_SEARCH_FROM_RADIX_TREE(port, slot);
+ MTK_PORT_INTERNAL_NODE_CHECK(port, slot, iter);
+ if (!strncmp(port->info.name, name, MTK_DFLT_PORT_NAME_LEN))
+ return port;
+ }
+ tbl_type++;
+ } while (tbl_type < PORT_TBL_MAX);
+
+ return NULL;
+}
+
+static int mtk_port_tbl_create(struct mtk_port_mngr *port_mngr, struct mtk_port_cfg *cfg,
+ const int port_cnt, struct mtk_stale_list *s_list)
+{
+ struct mtk_port_cfg *dflt_port;
+ struct mtk_port *port;
+ int i;
+
+ INIT_RADIX_TREE(&port_mngr->port_tbl[PORT_TBL_SAP], GFP_KERNEL);
+ INIT_RADIX_TREE(&port_mngr->port_tbl[PORT_TBL_MD], GFP_KERNEL);
+
+ mtk_port_restore_from_stale_list(port_mngr, s_list);
+
+ /* copy ports from static port cfg table */
+ for (i = 0; i < port_cnt; i++) {
+ dflt_port = cfg + i;
+ if (!mtk_port_search_by_id(port_mngr, dflt_port->rx_ch)) {
+ port = mtk_port_alloc_and_add(port_mngr, dflt_port);
+ if (IS_ERR(port))
+ return PTR_ERR(port);
+ }
+ }
+
+ return 0;
+}
+
+static void mtk_port_tbl_destroy(struct mtk_port_mngr *port_mngr, struct mtk_stale_list *s_list)
+{
+ struct radix_tree_iter iter;
+ struct mtk_port *port;
+ void __rcu **slot;
+ int tbl_type;
+
+ tbl_type = PORT_TBL_SAP;
+ do {
+ radix_tree_for_each_slot(slot, &port_mngr->port_tbl[tbl_type], &iter, 0) {
+ port = radix_tree_deref_slot(slot);
+ if (!port)
+ continue;
+ ports_ops[port->info.type]->disable(port);
+ }
+
+ while (radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
+ (void **)&port, 0, 1))
+ mtk_port_free_or_backup(port_mngr, port, s_list);
+ } while (++tbl_type < PORT_TBL_MAX);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_create(struct mtk_ctrl_blk *ctrl_blk)
+{
+ struct mtk_stale_list *s_list;
+
+ s_list = kzalloc_obj(*s_list, GFP_KERNEL);
+ if (!s_list)
+ return NULL;
+
+ memcpy(s_list->dev_str, ctrl_blk->mdev->dev_str, MTK_DEV_STR_LEN);
+ s_list->dev_id = -1;
+ INIT_LIST_HEAD(&s_list->ports);
+ rwlock_init(&s_list->port_mngr_lock);
+
+ mutex_lock(&port_mngr_grp_mtx);
+ list_add_tail(&s_list->entry, &stale_list_grp);
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return s_list;
+}
+
+static void mtk_port_stale_list_destroy(struct mtk_stale_list *s_list)
+{
+ mutex_lock(&port_mngr_grp_mtx);
+ list_del(&s_list->entry);
+ mutex_unlock(&port_mngr_grp_mtx);
+ kfree(s_list);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_search(const char *dev_str)
+{
+ struct mtk_stale_list *tmp, *s_list = NULL;
+
+ mutex_lock(&port_mngr_grp_mtx);
+ list_for_each_entry(tmp, &stale_list_grp, entry) {
+ if (!strncmp(tmp->dev_str, dev_str, MTK_DEV_STR_LEN)) {
+ s_list = tmp;
+ break;
+ }
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return s_list;
+}
+
+void mtk_port_stale_list_grp_cleanup(void)
+{
+ struct mtk_stale_list *s_list, *next_s_list;
+ struct mtk_port *port, *next_port;
+
+ mutex_lock(&port_mngr_grp_mtx);
+ list_for_each_entry_safe(s_list, next_s_list, &stale_list_grp, entry) {
+ list_del(&s_list->entry);
+
+ list_for_each_entry_safe(port, next_port, &s_list->ports, stale_entry) {
+ clear_bit(PORT_S_ON_STALE_LIST, &port->status);
+ mtk_port_release(&port->kref);
+ }
+
+ kfree(s_list);
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static struct mtk_stale_list *mtk_port_stale_list_init(struct mtk_ctrl_blk *ctrl_blk, int *dev_id)
+{
+ struct mtk_stale_list *s_list;
+
+ s_list = mtk_port_stale_list_search(ctrl_blk->mdev->dev_str);
+ if (!s_list) {
+ s_list = mtk_port_stale_list_create(ctrl_blk);
+ if (unlikely(!s_list))
+ return NULL;
+ }
+
+ mutex_lock(&port_mngr_grp_mtx);
+ if (s_list->dev_id < 0) {
+ *dev_id = ida_alloc_range(&ccci_dev_ids, 0, MTK_DFLT_MAX_DEV_CNT - 1, GFP_KERNEL);
+ } else {
+ *dev_id = s_list->dev_id;
+ s_list->dev_id = -1;
+ }
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return s_list;
+}
+
+static void mtk_port_stale_list_exit(struct mtk_ctrl_blk *ctrl_blk,
+ struct mtk_stale_list *s_list, int dev_id)
+{
+ if (!s_list)
+ return;
+ mutex_lock(&port_mngr_grp_mtx);
+ if (list_empty(&s_list->ports)) {
+ ida_free(&ccci_dev_ids, dev_id);
+ mutex_unlock(&port_mngr_grp_mtx);
+ mtk_port_stale_list_destroy(s_list);
+ } else {
+ s_list->dev_id = dev_id;
+ mutex_unlock(&port_mngr_grp_mtx);
+ }
+}
+
+void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,
+ int (*trb_complete)(struct sk_buff *skb))
+{
+ kref_init(&trb->kref);
+ trb->channel_id = port->info.rx_ch;
+ trb->status = MTK_DFLT_TRB_STATUS;
+ trb->priv = port;
+ trb->cmd = cmd;
+ trb->trb_complete = trb_complete;
+}
+
+void mtk_port_trb_free(struct kref *trb_kref)
+{
+ struct trb *trb = container_of(trb_kref, struct trb, kref);
+ struct sk_buff *skb, *frag_skb, *next_skb;
+
+ skb = container_of((char *)trb, struct sk_buff, cb[0]);
+ /* Free frag_list for scatter gather TX */
+ if (trb->cmd == TRB_CMD_TX && skb_has_frag_list(skb)) {
+ frag_skb = skb_shinfo(skb)->frag_list;
+ while (frag_skb) {
+ next_skb = frag_skb->next;
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = next_skb;
+ }
+ skb_shinfo(skb)->frag_list = NULL;
+ skb->data_len = 0;
+ }
+ dev_kfree_skb_any(skb);
+}
+EXPORT_SYMBOL(mtk_port_trb_free);
+
+static int mtk_port_open_trb_complete(struct sk_buff *skb)
+{
+ struct trb_open_priv *trb_open_priv = (struct trb_open_priv *)skb->data;
+ struct trb *trb = (struct trb *)skb->cb;
+ struct mtk_port *port = trb->priv;
+
+ if (!trb->status) {
+ port->tx_mtu = trb_open_priv->tx_mtu;
+ port->rx_mtu = trb_open_priv->rx_mtu;
+ port->tx_frag_size = trb_open_priv->tx_frag_size;
+ port->rx_frag_size = trb_open_priv->rx_frag_size;
+ port->tx_mtu -= MTK_CCCI_H_ELEN;
+ port->rx_mtu -= MTK_CCCI_H_ELEN;
+ }
+
+ wake_up_interruptible_all(&port->trb_wq);
+
+ kref_put(&trb->kref, mtk_port_trb_free);
+ return 0;
+}
+
+static int mtk_port_close_trb_complete(struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct mtk_port *port = trb->priv;
+
+ wake_up_interruptible_all(&port->trb_wq);
+ wake_up_interruptible_all(&port->rx_wq);
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+ return 0;
+}
+
+static int mtk_port_tx_complete(struct sk_buff *skb)
+{
+ struct trb *trb = (struct trb *)skb->cb;
+ struct mtk_port *port = trb->priv;
+
+ if (trb->status < 0)
+ dev_warn(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to send data: status:%d, port:%s\n",
+ trb->status, port->info.name);
+
+ wake_up_interruptible_all(&port->trb_wq);
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+ return 0;
+}
+
+int mtk_port_status_check(struct mtk_port *port)
+{
+ if (!test_bit(PORT_S_ENABLE, &port->status))
+ return -ENODEV;
+
+ if (!test_bit(PORT_S_OPEN, &port->status) || test_bit(PORT_S_FLUSH, &port->status) ||
+ !test_bit(PORT_S_WR, &port->status))
+ return -EBADF;
+
+ return 0;
+}
+
+int mtk_port_send_data(struct mtk_port *port, void *data)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct sk_buff *skb = data;
+ bool force_send;
+ struct trb *trb;
+ int ret, len;
+
+ port_mngr = port->port_mngr;
+
+ force_send = !!(port->info.flags & (PORT_F_BLOCKING | PORT_F_FORCE_SEND));
+ trb = (struct trb *)skb->cb;
+ mtk_port_trb_init(port, trb, TRB_CMD_TX, mtk_port_tx_complete);
+ len = skb->len;
+ kref_get(&trb->kref); /* kref count 1->2 */
+
+ /* add ccci header */
+ mtk_port_add_header(skb);
+ ret = mtk_port_status_check(port);
+ if (!ret)
+ ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev,
+ skb, force_send);
+
+ if (ret < 0) {
+ kref_put(&trb->kref, mtk_port_trb_free); /* kref count 2->1 */
+ kref_put(&trb->kref, mtk_port_trb_free); /* kref count 1->0 */
+ port->tx_seq--;
+ goto out;
+ }
+
+ if (!(port->info.flags & PORT_F_BLOCKING)) {
+ kref_put(&trb->kref, mtk_port_trb_free);
+ ret = len;
+ goto out;
+ }
+start_wait:
+
+ /* wait trb done, and no timeout in tx blocking mode */
+ ret = wait_event_interruptible_timeout(port->trb_wq,
+ trb->status <= 0 ||
+ test_bit(PORT_S_FLUSH, &port->status) ||
+ !test_bit(PORT_S_WR, &port->status),
+ MTK_DFLT_TRB_TIMEOUT);
+ if (!ret) {
+ goto start_wait;
+ } else if (ret == -ERESTARTSYS) {
+ ret = -EINTR;
+ } else if (ret > 0) {
+ if (test_bit(PORT_S_FLUSH, &port->status))
+ ret = len;
+ else
+ ret = (!trb->status) ? len : trb->status;
+ }
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+out:
+ return ret;
+}
+
+static int mtk_port_check_rx_seq(struct mtk_port *port, struct mtk_ccci_header *ccci_h)
+{
+ u16 seq_num, assert_bit, channel;
+ struct mtk_md_dev *mdev;
+
+ seq_num = FIELD_GET(MTK_HDR_FLD_SEQ, le32_to_cpu(ccci_h->status));
+ assert_bit = FIELD_GET(MTK_HDR_FLD_AST, le32_to_cpu(ccci_h->status));
+ if (assert_bit && port->rx_seq &&
+ ((seq_num - port->rx_seq) & MTK_CHECK_RX_SEQ_MASK) != 1) {
+ mdev = port->port_mngr->ctrl_blk->mdev;
+ channel = FIELD_GET(MTK_HDR_FLD_CHN, le32_to_cpu(ccci_h->status));
+ dev_warn((mdev)->dev,
+ "<ch: %04x> seq num out-of-order %d->%d, len(%u)\n",
+ channel, seq_num, port->rx_seq,
+ le32_to_cpu(ccci_h->packet_len));
+
+ port->rx_seq = seq_num;
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static int mtk_port_rx_dispatch_frag_skb(struct mtk_port *port, struct sk_buff *skb)
+{
+ struct sk_buff *frag_skb, *frag_next;
+ int ret;
+
+ frag_skb = skb_shinfo(skb)->frag_list;
+ skb->len -= skb->data_len;
+ skb->data_len = 0;
+ skb_shinfo(skb)->frag_list = NULL;
+
+ ret = ports_ops[port->info.type]->recv(port, skb);
+ if (ret < 0) {
+ skb_shinfo(skb)->frag_list = frag_skb;
+ return ret;
+ }
+
+ while (frag_skb) {
+ frag_next = frag_skb->next;
+ if (!frag_skb->len) {
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = frag_next;
+ continue;
+ }
+ frag_skb->next = NULL;
+ ret = ports_ops[port->info.type]->recv(port, frag_skb);
+ if (ret < 0) {
+ frag_skb->next = frag_next;
+ while (frag_skb) {
+ frag_next = frag_skb->next;
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = frag_next;
+ }
+ return -EIO;
+ }
+ frag_skb = frag_next;
+ }
+
+ return 0;
+}
+
+static int mtk_port_rx_dispatch(struct sk_buff *skb, void *priv, bool force_recv)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_ccci_header *ccci_h;
+ struct mtk_port *port = priv;
+ int ret = -EPROTO;
+ u16 channel;
+
+ if (!skb || !priv) {
+ pr_err("Invalid input value in rx dispatch\n");
+ return -EINVAL;
+ }
+
+ port_mngr = port->port_mngr;
+
+ ccci_h = mtk_port_strip_header(skb);
+ if (unlikely(!ccci_h)) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Unsupported: skb length(%d) is less than ccci header\n",
+ skb->len);
+ goto drop_data;
+ }
+
+ channel = FIELD_GET(MTK_HDR_FLD_CHN, le32_to_cpu(ccci_h->status));
+ port = mtk_port_search_by_id(port_mngr, channel);
+ if (unlikely(!port)) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to find port by channel:%d\n", channel);
+ goto drop_data;
+ }
+
+ ret = mtk_port_check_rx_seq(port, ccci_h);
+ if (unlikely(ret))
+ goto drop_data;
+
+ port->rx_seq = FIELD_GET(MTK_HDR_FLD_SEQ, le32_to_cpu(ccci_h->status));
+ skb_pull(skb, sizeof(*ccci_h));
+
+ /* Support scatter gather transmission */
+ if (port->rx_mtu > port->rx_frag_size) {
+ ret = mtk_port_rx_dispatch_frag_skb(port, skb);
+ /* -EIO means partial data dispatch complete, does not goto drop flow */
+ if (ret < 0 && ret != -EIO)
+ goto drop_frag_skb;
+ } else {
+ ret = ports_ops[port->info.type]->recv(port, skb);
+ if (ret < 0)
+ goto drop_data;
+ }
+
+ return ret;
+
+drop_frag_skb:
+ {
+ struct sk_buff *frag_skb, *tmp;
+
+ frag_skb = skb_shinfo(skb)->frag_list;
+ while (frag_skb) {
+ tmp = frag_skb->next;
+ frag_skb->next = NULL;
+ dev_kfree_skb_any(frag_skb);
+ frag_skb = tmp;
+ }
+ skb_shinfo(skb)->frag_list = NULL;
+ }
+drop_data:
+ dev_kfree_skb_any(skb);
+ return ret;
+}
+
+int mtk_port_add_header(struct sk_buff *skb)
+{
+ struct mtk_ccci_header *ccci_h;
+ struct mtk_port *port;
+ struct trb *trb;
+
+ trb = (struct trb *)skb->cb;
+ if (trb->status == MTK_TRB_HEADER_ADDED)
+ return 0;
+
+ port = trb->priv;
+ if (!port)
+ return -EINVAL;
+
+ ccci_h = skb_push(skb, sizeof(*ccci_h));
+
+ ccci_h->packet_header = cpu_to_le32(0);
+ ccci_h->packet_len = cpu_to_le32(skb->len);
+ ccci_h->ex_msg = cpu_to_le32(0);
+ ccci_h->status = cpu_to_le32(FIELD_PREP(MTK_HDR_FLD_CHN, port->info.tx_ch) |
+ FIELD_PREP(MTK_HDR_FLD_SEQ, port->tx_seq++) |
+ FIELD_PREP(MTK_HDR_FLD_AST, 1));
+
+ trb->status = MTK_TRB_HEADER_ADDED;
+
+ return 0;
+}
+
+struct mtk_ccci_header *mtk_port_strip_header(struct sk_buff *skb)
+{
+ struct mtk_ccci_header *ccci_h;
+
+ if (skb->len < sizeof(*ccci_h)) {
+ pr_err("Invalid input value\n");
+ return NULL;
+ }
+
+ ccci_h = (struct mtk_ccci_header *)skb->data;
+
+ return ccci_h;
+}
+
+int mtk_port_status_update(struct mtk_md_dev *mdev, void *data)
+{
+ struct mtk_port_enum_msg *msg = data;
+ struct mtk_port_info *port_info;
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_port *port;
+ int port_id;
+ u16 ch_id;
+
+ if (unlikely(!mdev || !msg))
+ return -EINVAL;
+
+ ctrl_blk = mdev->ctrl_blk;
+ port_mngr = ctrl_blk->port_mngr;
+ if (le16_to_cpu(msg->version) != MTK_PORT_ENUM_VER ||
+ le32_to_cpu(msg->head_pattern) != MTK_PORT_ENUM_HEAD_PATTERN ||
+ le32_to_cpu(msg->tail_pattern) != MTK_PORT_ENUM_TAIL_PATTERN)
+ return -EPROTO;
+
+ for (port_id = 0; port_id < le16_to_cpu(msg->port_cnt); port_id++) {
+ port_info = (struct mtk_port_info *)(msg->data +
+ (sizeof(*port_info) * port_id));
+ ch_id = FIELD_GET(MTK_INFO_FLD_CHID, le16_to_cpu(port_info->channel));
+ port = mtk_port_search_by_id(port_mngr, ch_id);
+ if (!port)
+ continue;
+ port->enable = FIELD_GET(MTK_INFO_FLD_EN, le16_to_cpu(port_info->channel));
+ }
+
+ return 0;
+}
+
+int mtk_port_ch_enable(struct mtk_port *port)
+{
+ struct mtk_port_mngr *port_mngr = port->port_mngr;
+ struct trb_open_priv *trb_open_priv;
+ struct sk_buff *skb;
+ struct trb *trb;
+ int ret;
+
+ skb = __dev_alloc_skb(Q_MTU_3_5K, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ trb_open_priv = (struct trb_open_priv *)skb->data;
+ trb_open_priv->rx_done = mtk_port_rx_dispatch;
+
+ skb_put(skb, sizeof(struct trb_open_priv));
+ trb = (struct trb *)skb->cb;
+ mtk_port_trb_init(port, trb, TRB_CMD_ENABLE, mtk_port_open_trb_complete);
+ kref_get(&trb->kref);
+
+ ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev, skb, true);
+ if (ret) {
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to submit trb for port(%s), ret=%d\n",
+ port->info.name, ret);
+ kref_put(&trb->kref, mtk_port_trb_free);
+ kref_put(&trb->kref, mtk_port_trb_free);
+ return ret;
+ }
+
+start_wait:
+ ret = wait_event_interruptible_timeout(port->trb_wq, trb->status <= 0,
+ MTK_DFLT_TRB_TIMEOUT);
+ if (ret == -ERESTARTSYS)
+ goto start_wait;
+ else if (!ret)
+ ret = -ETIMEDOUT;
+ else
+ ret = trb->status;
+
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+ return ret;
+}
+
+int mtk_port_ch_disable(struct mtk_port *port)
+{
+ struct mtk_port_mngr *port_mngr = port->port_mngr;
+ struct sk_buff *skb;
+ struct trb *trb;
+ int ret;
+
+ skb = __dev_alloc_skb(Q_MTU_3_5K, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ trb = (struct trb *)skb->cb;
+ mtk_port_trb_init(port, trb, TRB_CMD_DISABLE, mtk_port_close_trb_complete);
+ kref_get(&trb->kref);
+
+ ret = port_mngr->ctrl_blk->ops->submit_skb(port_mngr->ctrl_blk->mdev, skb, true);
+ if (ret) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to submit trb for port(%s), ret=%d\n",
+ port->info.name, ret);
+ kref_put(&trb->kref, mtk_port_trb_free);
+ kref_put(&trb->kref, mtk_port_trb_free);
+ return ret;
+ }
+
+start_wait:
+ ret = wait_event_interruptible_timeout(port->trb_wq, trb->status <= 0,
+ MTK_DFLT_TRB_TIMEOUT);
+ if (ret == -ERESTARTSYS)
+ goto start_wait;
+ else if (!ret)
+ ret = -ETIMEDOUT;
+ else
+ ret = trb->status;
+
+ kref_put(&trb->kref, mtk_port_trb_free);
+
+ return ret;
+}
+
+int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_stale_list *s_list;
+ int ret = -ENOMEM;
+ int dev_id;
+
+ s_list = mtk_port_stale_list_init(ctrl_blk, &dev_id);
+ if (!s_list) {
+ dev_err((ctrl_blk->mdev)->dev, "Failed to init mtk_stale_list\n");
+ goto err_out;
+ }
+
+ port_mngr = devm_kzalloc(ctrl_blk->mdev->dev, sizeof(*port_mngr), GFP_KERNEL);
+ if (unlikely(!port_mngr)) {
+ dev_err((ctrl_blk->mdev)->dev, "Failed to alloc memory for port_mngr\n");
+ goto err_exit_stale_list;
+ }
+
+ port_mngr->ctrl_blk = ctrl_blk;
+ port_mngr->dev_id = dev_id;
+
+ ret = mtk_port_tbl_create(port_mngr, port_cfg, port_cnt, s_list);
+ if (unlikely(ret)) {
+ dev_err((ctrl_blk->mdev)->dev, "Failed to create port_tbl\n");
+ goto err_free_port_mngr;
+ }
+
+ ctrl_blk->port_mngr = port_mngr;
+
+ return ret;
+
+err_free_port_mngr:
+ mtk_port_tbl_destroy(port_mngr, s_list);
+ devm_kfree(ctrl_blk->mdev->dev, port_mngr);
+err_exit_stale_list:
+ mtk_port_stale_list_exit(ctrl_blk, s_list, dev_id);
+err_out:
+ return ret;
+}
+
+void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk)
+{
+ struct mtk_port_mngr *port_mngr = ctrl_blk->port_mngr;
+ struct mtk_stale_list *s_list;
+ int dev_id;
+
+ s_list = mtk_port_stale_list_search(port_mngr->ctrl_blk->mdev->dev_str);
+ dev_id = port_mngr->dev_id;
+
+ mtk_port_tbl_destroy(port_mngr, s_list);
+
+ devm_kfree(ctrl_blk->mdev->dev, port_mngr);
+ ctrl_blk->port_mngr = NULL;
+ mtk_port_stale_list_exit(ctrl_blk, s_list, dev_id);
+}
diff --git a/drivers/net/wwan/t9xx/mtk_port.h b/drivers/net/wwan/t9xx/mtk_port.h
new file mode 100644
index 000000000000..bd4291408bc2
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port.h
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PORT_H__
+#define __MTK_PORT_H__
+
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/radix-tree.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+#include "mtk_ctrl_plane.h"
+#include "mtk_dev.h"
+
+#define MTK_PEER_ID_MASK (0xF000)
+#define MTK_PEER_ID_SHIFT (12)
+#define MTK_PEER_ID(ch) (((ch) & MTK_PEER_ID_MASK) >> MTK_PEER_ID_SHIFT)
+#define MTK_PEER_ID_SAP (0x1)
+#define MTK_PEER_ID_MD (0x2)
+#define MTK_CH_ID_MASK (0x0FFF)
+#define MTK_CH_ID(ch) ((ch) & MTK_CH_ID_MASK)
+#define MTK_DFLT_MAX_DEV_CNT (10)
+#define MTK_DFLT_PORT_NAME_LEN (20)
+
+/* Mapping MTK_PEER_ID and mtk_port_tbl index */
+#define MTK_PORT_TBL_TYPE(ch) (MTK_PEER_ID(ch) - 1)
+
+/* ccci header length + reserved space that is used in exception flow */
+#define MTK_CCCI_H_ELEN (128)
+
+#define MTK_HDR_FLD_AST ((u32)BIT(31))
+#define MTK_HDR_FLD_SEQ GENMASK(30, 16)
+#define MTK_HDR_FLD_CHN GENMASK(15, 0)
+
+#define MTK_INFO_FLD_EN ((u16)BIT(15))
+#define MTK_INFO_FLD_CHID GENMASK(14, 0)
+
+enum mtk_port_status {
+ PORT_S_DFLT = 0,
+ PORT_S_ENABLE,
+ PORT_S_OPEN,
+ PORT_S_RD,
+ PORT_S_WR,
+ PORT_S_FLUSH,
+ PORT_S_ON_STALE_LIST,
+ PORT_S_STOP,
+};
+
+enum mtk_ccci_ch {
+ /* to sAP */
+ CCCI_SAP_CONTROL_RX = 0x1000,
+ CCCI_SAP_CONTROL_TX = 0x1001,
+ /* to MD */
+ CCCI_CONTROL_RX = 0x2000,
+ CCCI_CONTROL_TX = 0x2001,
+};
+
+enum mtk_port_flag {
+ PORT_F_DFLT = 0,
+ PORT_F_BLOCKING = BIT(1),
+ PORT_F_ALLOW_DROP = BIT(2),
+ PORT_F_FORCE_SEND = BIT(6),
+};
+
+enum mtk_port_tbl {
+ PORT_TBL_SAP,
+ PORT_TBL_MD,
+ PORT_TBL_MAX
+};
+
+enum mtk_port_type {
+ PORT_TYPE_INTERNAL,
+ PORT_TYPE_MAX
+};
+
+struct mtk_internal_port {
+ void *arg;
+ int (*recv_cb)(void *arg, struct sk_buff *skb);
+};
+
+struct mtk_port_cfg {
+ enum mtk_ccci_ch tx_ch;
+ enum mtk_ccci_ch rx_ch;
+ enum mtk_port_type type;
+ char name[MTK_DFLT_PORT_NAME_LEN];
+ unsigned char flags;
+};
+
+struct mtk_port {
+ struct mtk_port_cfg info;
+ struct kref kref;
+ bool enable;
+ unsigned long status;
+ unsigned int minor;
+ unsigned short tx_seq;
+ unsigned short rx_seq;
+ unsigned int tx_mtu;
+ unsigned int rx_mtu;
+ u32 tx_frag_size;
+ u32 rx_frag_size;
+ struct sk_buff_head rx_skb_list;
+ unsigned int rx_data_len;
+ unsigned int rx_buf_size;
+ wait_queue_head_t trb_wq;
+ wait_queue_head_t rx_wq;
+ struct list_head stale_entry;
+ char dev_str[MTK_DEV_STR_LEN];
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_internal_port i_priv;
+};
+
+struct mtk_port_mngr {
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct radix_tree_root port_tbl[PORT_TBL_MAX];
+ unsigned int port_cnt;
+ int dev_id;
+};
+
+struct mtk_stale_list {
+ struct list_head entry;
+ struct list_head ports;
+ char dev_str[MTK_DEV_STR_LEN];
+ int dev_id;
+ rwlock_t port_mngr_lock;
+};
+
+struct mtk_ccci_header {
+ __le32 packet_header;
+ __le32 packet_len;
+ __le32 status;
+ __le32 ex_msg;
+};
+
+struct mtk_port_layer_cfg {
+ struct mtk_port_cfg *port_cfg;
+ int port_cnt;
+};
+
+extern const struct port_ops *ports_ops[PORT_TYPE_MAX];
+
+void mtk_port_release(struct kref *port_kref);
+void mtk_port_trb_free(struct kref *trb_kref);
+struct mtk_port *mtk_port_search_by_name(struct mtk_port_mngr *port_mngr, char *name);
+void mtk_port_stale_list_grp_cleanup(void);
+int mtk_port_add_header(struct sk_buff *skb);
+struct mtk_ccci_header *mtk_port_strip_header(struct sk_buff *skb);
+int mtk_port_status_check(struct mtk_port *port);
+int mtk_port_send_data(struct mtk_port *port, void *data);
+int mtk_port_status_update(struct mtk_md_dev *mdev, void *data);
+int mtk_port_ch_enable(struct mtk_port *port);
+int mtk_port_ch_disable(struct mtk_port *port);
+int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt);
+void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk);
+void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,
+ int (*trb_complete)(struct sk_buff *skb));
+#endif /* __MTK_PORT_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.c b/drivers/net/wwan/t9xx/mtk_port_io.c
new file mode 100644
index 000000000000..e3a2de6d2f29
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port_io.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+#include <linux/netdevice.h>
+
+#include "mtk_port_io.h"
+
+static int mtk_port_get_locked(struct mtk_port *port)
+{
+ int ret = 0;
+
+ mutex_lock(&port_mngr_grp_mtx);
+ if (!port) {
+ mutex_unlock(&port_mngr_grp_mtx);
+ pr_err("Port does not exist\n");
+ return -ENODEV;
+ }
+ kref_get(&port->kref);
+ mutex_unlock(&port_mngr_grp_mtx);
+
+ return ret;
+}
+
+static void mtk_port_put_locked(struct mtk_port *port)
+{
+ mutex_lock(&port_mngr_grp_mtx);
+ kref_put(&port->kref, mtk_port_release);
+ mutex_unlock(&port_mngr_grp_mtx);
+}
+
+static void mtk_port_struct_init(struct mtk_port *port)
+{
+ port->tx_seq = 0;
+ port->rx_seq = -1;
+ clear_bit(PORT_S_ENABLE, &port->status);
+ kref_init(&port->kref);
+ skb_queue_head_init(&port->rx_skb_list);
+ port->rx_buf_size = MTK_RX_BUF_SIZE;
+ init_waitqueue_head(&port->trb_wq);
+ init_waitqueue_head(&port->rx_wq);
+}
+
+static int mtk_port_internal_init(struct mtk_port *port)
+{
+ mtk_port_struct_init(port);
+ port->enable = false;
+
+ return 0;
+}
+
+static void mtk_port_internal_exit(struct mtk_port *port)
+{
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ ports_ops[port->info.type]->disable(port);
+}
+
+static void mtk_port_reset(struct mtk_port *port)
+{
+ port->tx_seq = 0;
+ port->rx_seq = -1;
+}
+
+static void mtk_port_internal_enable(struct mtk_port *port)
+{
+ int ret;
+
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ ret = mtk_port_ch_enable(port);
+ if (ret && ret != -EBUSY)
+ return;
+
+ set_bit(PORT_S_WR, &port->status);
+ set_bit(PORT_S_ENABLE, &port->status);
+}
+
+static void mtk_port_internal_disable(struct mtk_port *port)
+{
+ if (!test_and_clear_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ clear_bit(PORT_S_WR, &port->status);
+ mtk_port_ch_disable(port);
+}
+
+static int mtk_port_internal_recv(struct mtk_port *port, struct sk_buff *skb)
+{
+ struct mtk_internal_port *priv;
+ int ret = -ENXIO;
+
+ if (!test_bit(PORT_S_OPEN, &port->status))
+ goto drop_data;
+
+ priv = &port->i_priv;
+ if (!priv->recv_cb || !priv->arg)
+ goto drop_data;
+
+ ret = priv->recv_cb(priv->arg, skb);
+ return ret;
+
+drop_data:
+ dev_kfree_skb_any(skb);
+ return ret;
+}
+
+static int mtk_port_common_open(struct mtk_port *port)
+{
+ int ret = 0;
+
+ if (!test_bit(PORT_S_ENABLE, &port->status))
+ return -ENODEV;
+
+ if (test_bit(PORT_S_OPEN, &port->status))
+ return -EBUSY;
+
+ skb_queue_purge(&port->rx_skb_list);
+ set_bit(PORT_S_OPEN, &port->status);
+ clear_bit(PORT_S_FLUSH, &port->status);
+
+ return ret;
+}
+
+static void mtk_port_common_close(struct mtk_port *port)
+{
+ clear_bit(PORT_S_OPEN, &port->status);
+
+ skb_queue_purge(&port->rx_skb_list);
+ port->rx_data_len = 0;
+
+ set_bit(PORT_S_FLUSH, &port->status);
+ wake_up_interruptible_all(&port->trb_wq);
+ wake_up_interruptible_all(&port->rx_wq);
+}
+
+void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_port *port;
+ int ret;
+
+ ctrl_blk = mdev->ctrl_blk;
+ port_mngr = ctrl_blk->port_mngr;
+
+ port = mtk_port_search_by_name(port_mngr, name);
+ if (port && port->info.type != PORT_TYPE_INTERNAL) {
+ port = NULL;
+ goto out;
+ }
+
+ ret = mtk_port_get_locked(port);
+ if (ret)
+ goto out;
+
+ ret = mtk_port_common_open(port);
+ if (ret) {
+ mtk_port_put_locked(port);
+ port = NULL;
+ goto out;
+ }
+
+ if (flag & O_NONBLOCK)
+ port->info.flags &= ~PORT_F_BLOCKING;
+ else
+ port->info.flags |= PORT_F_BLOCKING;
+out:
+ return port;
+}
+
+int mtk_port_internal_close(void *i_port)
+{
+ struct mtk_port *port = i_port;
+ int ret = 0;
+
+ if (!port) {
+ ret = -EINVAL;
+ goto end;
+ }
+
+ if (!test_bit(PORT_S_OPEN, &port->status)) {
+ pr_err("Port(%s) has been closed\n", port->info.name);
+ ret = -EBADF;
+ goto end;
+ }
+
+ mtk_port_common_close(port);
+ mtk_port_put_locked(port);
+end:
+ return ret;
+}
+
+int mtk_port_internal_write(void *i_port, struct sk_buff *skb)
+{
+ struct mtk_port *port = i_port;
+
+ if (!port || !skb) {
+ if (skb)
+ dev_kfree_skb_any(skb);
+ pr_err_ratelimited("Internal write: invalid input\n");
+ return -EINVAL;
+ }
+ return mtk_port_send_data(port, skb);
+}
+
+void mtk_port_internal_recv_register(void *i_port,
+ int (*cb)(void *priv, struct sk_buff *skb),
+ void *arg)
+{
+ struct mtk_port *port = i_port;
+ struct mtk_internal_port *priv;
+
+ priv = &port->i_priv;
+ priv->arg = arg;
+ priv->recv_cb = cb;
+}
+
+int mtk_port_io_init(void)
+{
+ return 0;
+}
+
+void mtk_port_io_exit(void)
+{
+}
+
+static const struct port_ops port_internal_ops = {
+ .init = mtk_port_internal_init,
+ .exit = mtk_port_internal_exit,
+ .reset = mtk_port_reset,
+ .enable = mtk_port_internal_enable,
+ .disable = mtk_port_internal_disable,
+ .recv = mtk_port_internal_recv,
+};
+
+const struct port_ops *ports_ops[PORT_TYPE_MAX] = {
+ &port_internal_ops,
+};
diff --git a/drivers/net/wwan/t9xx/mtk_port_io.h b/drivers/net/wwan/t9xx/mtk_port_io.h
new file mode 100644
index 000000000000..0c10e893b7e0
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_port_io.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PORT_IO_H__
+#define __MTK_PORT_IO_H__
+
+#include <linux/skbuff.h>
+
+#include "mtk_port.h"
+
+#define MTK_RX_BUF_SIZE (1024 * 1024)
+
+extern struct mutex port_mngr_grp_mtx;
+
+struct port_ops {
+ int (*init)(struct mtk_port *port);
+ void (*exit)(struct mtk_port *port);
+ void (*reset)(struct mtk_port *port);
+ void (*enable)(struct mtk_port *port);
+ void (*disable)(struct mtk_port *port);
+ int (*recv)(struct mtk_port *port, struct sk_buff *skb);
+};
+
+void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag);
+int mtk_port_internal_close(void *i_port);
+int mtk_port_internal_write(void *i_port, struct sk_buff *skb);
+void mtk_port_internal_recv_register(void *i_port,
+ int (*cb)(void *priv, struct sk_buff *skb),
+ void *arg);
+
+int mtk_port_io_init(void);
+void mtk_port_io_exit(void);
+
+#endif /* __MTK_PORT_IO_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
index c1bb787ee981..8611561dd67c 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
@@ -4,6 +4,7 @@
*/
#include "mtk_cldma.h"
+#include "mtk_port.h"
#include "mtk_trans_ctrl.h"
#define TRB_SRV_NUM (1)
@@ -13,12 +14,34 @@ static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {
{0},
};
+/* the number of RX GPDs should be at last two */
static const struct queue_info mtk_queue_info_m9xx[] = {
+ {CCCI_CONTROL_TX, CCCI_CONTROL_RX, CLDMA1, TXQ(0), RXQ(0),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+ {CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, CLDMA0, TXQ(0), RXQ(0),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
+};
+
+static const struct mtk_port_cfg port_cfg_m9xx[] = {
+ {CCCI_CONTROL_TX, CCCI_CONTROL_RX, PORT_TYPE_INTERNAL, "MDCTRL",
+ PORT_F_ALLOW_DROP},
+ {CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, PORT_TYPE_INTERNAL, "SAPCTRL",
+ PORT_F_ALLOW_DROP},
+};
+
+static struct mtk_port_layer_cfg port_layer_cfg_m9xx = {
+ .port_cfg = (struct mtk_port_cfg *)port_cfg_m9xx,
+ .port_cnt = ARRAY_SIZE(port_cfg_m9xx),
+};
+
+static struct mtk_ctrl_cfg mtk_ctrl_cfg_m9xx = {
+ .port_layer_cfg = &port_layer_cfg_m9xx,
};
struct mtk_ctrl_info mtk_ctrl_info_m9xx = {
+ .ctrl_cfg = &mtk_ctrl_cfg_m9xx,
+ .srv_cfg = (int **)mtk_srv_cfg_m9xx,
.queue_info = (struct queue_info *)mtk_queue_info_m9xx,
.queue_info_num = ARRAY_SIZE(mtk_queue_info_m9xx),
- .srv_cfg = (int **)mtk_srv_cfg_m9xx,
.trb_srv_num = TRB_SRV_NUM,
};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
index 6efbcd0cba73..d3f862098a1d 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_pci.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
@@ -17,6 +17,8 @@
#include "mtk_trans_ctrl.h"
#include "mtk_pci.h"
#include "mtk_pci_reg.h"
+#include "mtk_port.h"
+#include "mtk_port_io.h"
#define MTK_PCI_BAR_NUM 6
#define MTK_PCI_TRANSPARENT_ATR_SIZE (0x3F)
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
index 32f00c15f383..6eeed6935550 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
@@ -16,12 +16,13 @@
#include "mtk_ctrl_plane.h"
#include "mtk_dev.h"
#include "mtk_pci.h"
+#include "mtk_port.h"
#include "mtk_trans_ctrl.h"
#define MTK_DFLT_PORT_NAME_LEN (20)
static struct mtk_ctrl_info_desc mtk_ctrl_info_tbl[] = {
- {2304, &ctrl_info_name(m9xx)},
+ {0x01CA, &ctrl_info_name(m9xx)},
{0, NULL},
};
@@ -133,6 +134,7 @@ static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_l
if (!skb)
break;
trb = (struct trb *)skb->cb;
+ kref_get(&trb->kref);
switch (trb->cmd) {
case TRB_CMD_ENABLE:
@@ -152,8 +154,10 @@ static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_l
kick = true;
break;
}
- if (err == -EAGAIN)
+ if (err == -EAGAIN) {
+ kref_put(&trb->kref, mtk_port_trb_free);
return;
+ }
skb_unlink(skb, skb_list);
trb->status = err;
@@ -184,6 +188,8 @@ static void mtk_ctrl_trb_handler(struct trb_srv *srv, struct trans_list *trans_l
kick = false;
}
+ kref_put(&trb->kref, mtk_port_trb_free);
+
loop++;
} while (loop < TRB_NUM_PER_ROUND);
}
@@ -518,6 +524,7 @@ static void mtk_trans_get_ctrl_info(struct mtk_ctrl_cfg *cfg,
continue;
ctrl_info = ctrl_info_desc->ctrl_info;
+ cfg->port_layer_cfg = ctrl_info->ctrl_cfg->port_layer_cfg;
memcpy(trans->srv_cfg, ctrl_info->srv_cfg,
sizeof(int) * NR_CLDMA * HW_QUE_NUM);
trans->queue_info = ctrl_info->queue_info;
@@ -530,6 +537,7 @@ int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)
{
struct mtk_ctrl_trans *trans;
struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_ctrl_cfg *cfg;
int err;
trans = devm_kzalloc(mdev->dev, sizeof(*trans), GFP_KERNEL);
@@ -538,15 +546,19 @@ int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)
trans->mdev = mdev;
trans->queues_cnt = 0;
- mtk_trans_get_ctrl_info(NULL, trans, mdev->hw_ver);
- if (!trans->queue_info ||
+ cfg = devm_kzalloc(mdev->dev, sizeof(*cfg), GFP_KERNEL);
+ if (!cfg)
+ goto err_free_trans;
+
+ mtk_trans_get_ctrl_info(cfg, trans, mdev->hw_ver);
+ if (!cfg->port_layer_cfg || !trans->queue_info ||
trans->trb_srv_num <= 0 || trans->trb_srv_num > TRB_SRV_MAX_NUM ||
trans->queue_info_num <= 0) {
dev_err((mdev)->dev, "Failed to get ctrl info!\n");
goto err_free_cfg;
}
- err = mtk_ctrl_init(mdev, &pcie_ctrl_ops);
+ err = mtk_ctrl_init(mdev, &pcie_ctrl_ops, cfg);
if (err)
goto err_free_cfg;
@@ -556,6 +568,8 @@ int mtk_trans_ctrl_init(struct mtk_md_dev *mdev)
return 0;
err_free_cfg:
+ devm_kfree(mdev->dev, cfg);
+err_free_trans:
devm_kfree(mdev->dev, trans);
return -ENOMEM;
}
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
index 0d25d1f51671..a3ff56ddf86f 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
@@ -12,6 +12,7 @@
#include <linux/types.h>
#include "mtk_dev.h"
+#include "mtk_port.h"
#define TRB_SRV_MAX_NUM (1)
#define HW_QUE_NUM (8)
--
2.34.1
^ permalink raw reply related
* [PATCH v3 0/7] net: wwan: t9xx: Add MediaTek T9XX WWAN driver
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
T9XX is the PCIe host device driver for MediaTek's
t900 modem. The driver uses the WWAN framework
infrastructure to create the following control ports
and network interfaces for data transactions.
* /dev/wwan0at0 - Interface that supports AT commands.
* /dev/wwan0mbim0 - Interface conforming to the MBIM
protocol.
* wwan0-X - Primary network interface for IP traffic.
The main blocks in the T9XX driver are:
* HW layer - Abstracts the hardware bus operations for
the device, and provides generic interfaces for the
transaction layer to get the device's information and
control the device's behavior. It includes:
* PCIe - Implements probe, removal and interrupt
handling.
* MHCCIF (Modem Host Cross-Core Interface) - Provides
interrupt channels for bidirectional event
notification such as handshake and port enumeration.
* Transaction layer - Implements data transactions for
the control plane and the data plane. It includes:
* DPMAIF (Data Plane Modem AP Interface) - Controls
the hardware that provides uplink and downlink
queues for the data path. The data exchange takes
place using circular buffers to share data buffer
addresses and metadata to describe the packets.
* CLDMA (Cross Layer DMA) - Manages the hardware
used by the port layer to send control messages to
the device using MediaTek's CCCI (Cross-Core
Communication Interface) protocol.
* TX Services - Dispatch packets from the port layer
to the device.
* RX Services - Dispatch packets to the port layer
when receiving packets from the device.
* Port layer - Provides control plane and data plane
interfaces to userspace. It includes:
* Control Plane - Provides device node interfaces
for controlling data transactions.
* Data Plane - Provides network link interfaces
wwanX (0, 1, 2...) for IP data transactions.
* Core logic - Contains the core logic to keep the
device working. It includes:
* FSM (Finite State Machine) - Monitors the state
of the device, and notifies each module when the
state changes.
The compilation of the T9XX driver is enabled by the
CONFIG_MTK_T9XX and CONFIG_MTK_T9XX_PCI config option
which depends on CONFIG_WWAN.
This v2 submission covers the control plane only
(patches 1-6). The data plane will follow in a
separate series once the control plane is accepted.
---
Changes in v3:
- Address sashiko AI code review comments and fix sparse warnings
- Patch 1 (Add PCIe core):
- Move extern declaration of mtk_dev_cfg_0900 from mtk_pci.c to mtk_pci.h to fix sparse warning
- Remove mtk_pci_bar_exit(): pcim_iounmap_region() was called with bitmask instead of BAR index, and pcim_iomap_regions() is devres-managed so manual unmap is redundant [sashiko]
- Remove pci_disable_device() from probe error path and remove path: pcim_enable_device() registers devres cleanup, manual disable causes enable_cnt underflow [sashiko]
- Patch 3 (Add control DMA interface):
- Add #include "mtk_cldma.h" in mtk_cldma_drv_m9xx.c to fix sparse undeclared symbol warnings for mtk_cldma_regs_m9xx and cldma_drv_ops_m9xx
- Move extern declaration of mtk_ctrl_info_m9xx from mtk_trans_ctrl.c to mtk_trans_ctrl.h to fix sparse undeclared symbol warning
- Replace kcalloc + radix_tree_gang_lookup with radix_tree_for_each_slot() in mtk_ctrl_remove_radix_tree() to eliminate allocation in teardown path [sashiko]
- Add missing mtk_pci_dev_exit() call in mtk_pci_remove() to properly clean up FSM and trans_ctrl resources before device removal [sashiko]
- Patch 4 (Add control port):
- Replace kcalloc + radix_tree_gang_lookup with radix_tree_for_each_slot() and single-entry gang_lookup in mtk_port_tbl_destroy() to eliminate allocation failure in teardown path [sashiko]
- Add port = NULL after mtk_port_put_locked() in mtk_port_internal_open() error path to prevent returning un-refcounted pointer [sashiko]
- Restore skb_unlink + trb_complete for non-EAGAIN errors in mtk_ctrl_trb_handler() TX path to prevent infinite retry loop [sashiko]
- Patch 5 (Add FSM thread):
- Check mtk_pci_register_irq() return value in mtk_cldma_dev_init() and add err_destroy_wq error label to fix unreachable dead code [sashiko]
- Propagate specific error codes (ENOMEM/EIO/EINVAL) from mtk_cldma_dev_init() error paths instead of generic -EIO [sashiko]
- Free head SKB after detaching frag_list in mtk_cldma_rxq_free() scatter-gather mode to fix memory leak [sashiko]
- Patch 6 (Add AT & MBIM WWAN ports):
- Remove unused write_lock mutex from struct mtk_port and its mutex_init call [sashiko]
- Link to v2: https://patch.msgid.link/20260610-t9xx_driver_v1-v2-0-c65addf23b3f@compal.com
Changes in v2:
- Split series into control plane (this v2) and data plane (follow-up)
- Patch 1 (Add PCIe core):
- Rename BAR_NUM to MTK_PCI_BAR_NUM for driver prefix consistency
- Replace magic numbers in mtk_pci_setup_atr() with named defines
- Remove redundant ATR register comments, use blank line separators
- Add kernel-doc comments to all non-static functions
- Convert 4 MMIO wrapper functions to static inline in header [sashiko]
- Remove unnecessary unlikely() from IRQ validation paths
- Add irq_cnt == 0 and irq_id < 0 guards in mtk_pci_get_virq_id() [sashiko]
- Initialize hw_bits at declaration for consistency
- Merge same-type variable declarations into single lines
- Add #else/#endif comments for CONFIG_ACPI blocks
- Add newlines in mtk_pci_pldr() for readability
- Move return into default case in mtk_pci_dev_reset()
- Simplify mtk_mhccif_init() error path to use direct returns
- Change -EFAULT to -ENOLINK for PCIe link check failure
- Rename goto label "out" to "log_err" in mtk_pci_probe()
- Wrap long lines to stay within 80 columns
- Fix IRQ vector leak: add pci_free_irq_vectors() on error path [sashiko]
- Fix mtk_pci_remove() ordering: free IRQ before cancel_work_sync [sashiko]
- Fix mtk_pci_pldr() ACPI buffer leak: free first result before second call [sashiko]
- Replace msleep(500) with MTK_PLDR_POWER_OFF_DELAY_MS define
- Remove unused EXT_EVT_H2D_DRM_DISABLE_AP and related register define [sashiko]
- Increase MTK_IRQ_NAME_LEN from 20 to 32 to fix W=1 format-truncation warning [sashiko]
- Patch 2 (Add control plane transaction layer):
- Add kernel-doc comments to mtk_ctrl_init() and mtk_ctrl_exit()
- Change mtk_ctrl_exit() return type from int to void
- Set mdev->ctrl_blk to NULL after freeing in mtk_ctrl_exit() [sashiko]
- Change ctrl_blk from void* to typed struct mtk_ctrl_blk* [sashiko]
- Remove redundant "depends on MTK_T9XX" from MTK_T9XX_PCI Kconfig [sashiko]
- Use mtk_dev_free() instead of devm_kfree() in mtk_pci_probe() error path [sashiko]
- Patch 3 (Add control DMA interface):
- Add @ops kernel-doc parameter for mtk_ctrl_init()
- Rename 'err' to 'ret' consistently throughout the patch
- Reorder variable declarations to follow reverse Christmas tree style
- Change mtk_cldma_txq_free() return type from int to void
- Change mtk_cldma_rxq_free() return type from int to void
- Change mtk_cldma_exit() return type from int to void
- Remove unnecessary zero-initialization of ret in mtk_cldma_start_xfer()
- Remove unnecessary zero-initialization of ret in mtk_cldma_tx()
- Use direct return instead of goto out in mtk_cldma_submit_tx() error paths
- Move software state before HWO flag in mtk_cldma_submit_tx()
- Squash variable declarations in mtk_cldma_check_intr_status()
- Remove unlikely() from validation paths in mtk_cldma_check_ch_cfg()
- Clamp data_recv_len with min_t to prevent skb_over_panic in mtk_cldma_rx_skb_adjust() [sashiko]
- Use READ_ONCE() for HWO flag polling in mtk_cldma_check_rx_req() [sashiko]
- Fix mtk_cldma_rx_done_work() to always unmask interrupt on error path [sashiko]
- Add DMA address guard in mtk_cldma_txq_free() teardown loop [sashiko]
- Add IS_ERR() check for kthread_run() in mtk_ctrl_trb_srv_init() [sashiko]
- Fix queue_info memory leak on validation failure in mtk_pcie_hif_init() [sashiko]
- Handle non-EAGAIN errors in mtk_ctrl_trb_handler() TX path [sashiko]
- Fix 'err' typo to 'ret' in mtk_cldma_txbuf_set() error message
- Remove unused variable mdev in mtk_cldma_rx_check_again() [sashiko]
- Remove unused variables trans and ctrl_blk in mtk_cldma_txq_free() and mtk_cldma_rxq_free() [sashiko]
- Patch 4 (Add control port):
- Add @cfg kernel-doc parameter for mtk_ctrl_init()
- Update mtk_ctrl_init() return description to cover additional error codes
- Fix double list_del in mtk_port_stale_list_grp_cleanup() [sashiko]
- Fix direct mtk_port_trb_free() call to use kref_put() in mtk_port_ch_enable() error path [sashiko]
- Fix direct mtk_port_trb_free() call to use kref_put() in mtk_port_ch_disable() error path [sashiko]
- Add mtk_port_tbl_destroy() in mtk_port_mngr_init() error path to prevent port memory leak [sashiko]
- Change port_ops exit/reset/enable/disable callbacks from int to void
- Move -EIO dispatch comment to where the code was introduced
- Patch 5 (Add FSM thread):
- Add bounds check for rtft_entry in mtk_fsm_parse_hs2_msg() [sashiko]
- Add skb length validation before accessing ctrl_msg_header in mtk_fsm_sap_ctrl_msg_handler() [sashiko]
- Fix skb leak on CTRL_MSG_HS2 mismatch return in mtk_fsm_sap_ctrl_msg_handler() [sashiko]
- Add skb length validation before accessing ctrl_msg_header in mtk_fsm_md_ctrl_msg_handler() [sashiko]
- Replace devm_kzalloc/devm_kfree with kzalloc/kfree for FSM events [sashiko]
- Fix mtk_fsm_evt_submit() to return -ETIMEDOUT on blocking event timeout [sashiko]
- Change FSM kthread from TASK_INTERRUPTIBLE to TASK_UNINTERRUPTIBLE [sashiko]
- Remove unused variable hw_id in mtk_cldma_dev_exit() [sashiko]
- Patch 6 (Add AT & MBIM WWAN ports):
- Use imperative mode in commit message
- Remove unnecessary zero-initialization of ret in mtk_port_copy_data_from()
- Change copy_from_user() error code from -EFAULT to -EINVAL in mtk_port_copy_data_from()
- Return -EINVAL for zero-length write in mtk_port_common_write()
- Change mtk_port_wwan_exit/enable/disable() return type from int to void
- Fix packet_size to account for CCCI header reservation in mtk_port_common_write() [sashiko]
- Fix WWAN tx callbacks to consume skb and return 0 per wwan_port_ops contract [sashiko]
- Fix wwan_create_port() error path: clear ERR_PTR to NULL and call mtk_port_ch_disable() [sashiko]
- Patch 7 (Add maintainers entry): new patch
- Link to v1: https://patch.msgid.link/20260529-t9xx_driver_v1-v1-0-bdbfe2c01e57@compal.com
---
Jack Wu (7):
net: wwan: t9xx: Add PCIe core
net: wwan: t9xx: Add control plane transaction layer
net: wwan: t9xx: Add control DMA interface
net: wwan: t9xx: Add control port
net: wwan: t9xx: Add FSM thread
net: wwan: t9xx: Add AT & MBIM WWAN ports
net: wwan: t9xx: Add maintainers entry
MAINTAINERS | 9 +
drivers/net/wwan/Kconfig | 17 +
drivers/net/wwan/Makefile | 1 +
drivers/net/wwan/t9xx/Makefile | 14 +
drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 111 ++
drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 88 ++
drivers/net/wwan/t9xx/mtk_dev.c | 55 +
drivers/net/wwan/t9xx/mtk_dev.h | 114 ++
drivers/net/wwan/t9xx/mtk_fsm.c | 948 +++++++++++++++
drivers/net/wwan/t9xx/mtk_fsm.h | 140 +++
drivers/net/wwan/t9xx/mtk_port.c | 968 +++++++++++++++
drivers/net/wwan/t9xx/mtk_port.h | 174 +++
drivers/net/wwan/t9xx/mtk_port_io.c | 573 +++++++++
drivers/net/wwan/t9xx/mtk_port_io.h | 41 +
drivers/net/wwan/t9xx/mtk_utility.h | 33 +
drivers/net/wwan/t9xx/pcie/Makefile | 15 +
drivers/net/wwan/t9xx/pcie/mtk_cldma.c | 1420 +++++++++++++++++++++++
drivers/net/wwan/t9xx/pcie/mtk_cldma.h | 173 +++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.c | 371 ++++++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h | 174 +++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c | 178 +++
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h | 101 ++
drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c | 55 +
drivers/net/wwan/t9xx/pcie/mtk_pci.c | 1102 ++++++++++++++++++
drivers/net/wwan/t9xx/pcie/mtk_pci.h | 234 ++++
drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c | 69 ++
drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h | 71 ++
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c | 603 ++++++++++
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 107 ++
29 files changed, 7959 insertions(+)
---
base-commit: eb3f4b7426cfd2b79d65b7d37155480b32259a11
change-id: 20260529-t9xx_driver_v1-1744f8af7739
Best regards,
--
Jack Wu <jackbb_wu@compal.com>
^ permalink raw reply
* Re: chipidea: usbmisc_imx: i.MX93 support
From: Stefan Wahren @ 2026-06-24 10:05 UTC (permalink / raw)
To: Xu Yang
Cc: Xu Yang, Frank Li, Jun Li, Alexander Stein, Greg Kroah-Hartman,
Linux ARM, linux-usb@vger.kernel.org
In-Reply-To: <lwo7rsdsfuibceyz5qoi46bbrhmofjpb2e3ukzdawieou3uo7m@5o2ral3q4oaf>
Hi Xu,
Am 24.06.26 um 11:11 schrieb Xu Yang:
> On Wed, Jun 24, 2026 at 08:30:00AM +0200, Stefan Wahren wrote:
> What's your issue on i.MX93?
we have an i.MX93 board using both USB interfaces:
USB1:
- external available via USB-C connector
- OTG interface should be able to switch between host and peripheral
- as USB-C interface control we use the TI TUSB321AI [1] (while its DIR,
OUT1, OUT2, CURRENT_MODE, PORT is unconnected or tied to GND)
USB2:
- used as internal host interface
We have the problem that 5 seconds after disconnecting a USB device
from USB-C (USB1) interface, the USB driver complains that the VBUS is
still to high:
[ 144.078347] usb 2-1: USB disconnect, device number 2
[ 144.083425] ci_hdrc ci_hdrc.0: remove, state 1
[ 144.083521] usb 2-1.1: USB disconnect, device number 3
[ 144.090310] usb usb2: USB disconnect, device number 1
[ 144.139325] usb 2-1.5: USB disconnect, device number 4
[ 144.158700] ci_hdrc ci_hdrc.0: USB bus 2 deregistered
[ 149.190274] ci_hdrc ci_hdrc.0: timeout waiting for 00000800 in OTGSC
[ 149.196741] usbmisc_imx 4c100200.usbmisc: vbus is error
[ 149.202078] usbmisc_imx 4c100200.usbmisc: Error occurs during
detection: -22
But except of this, both roles works as expected.
Here are the relevant device tree parts:
reg_usb_otg1_vbus: regulator-usb-otg1-vbus {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usbotg1grp>;
compatible = "regulator-fixed";
regulator-name = "usb_otg1_vbus";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
gpio = <&gpio4 13 GPIO_ACTIVE_HIGH>;
enable-active-high;
regulator-boot-on;
};
&usbotg1 {
vbus-supply = <®_usb_otg1_vbus>;
over-current-active-low;
status = "okay";
};
Thanks
Stefan
[1] - https://www.ti.com/lit/ds/symlink/tusb321ai.pdf
>
> Thanks,
> Xu Yang
^ permalink raw reply
* Re: [RFC PATCH 0/2] kasan: hw_tags: Add option to tag only at allocation time
From: Catalin Marinas @ 2026-06-24 10:13 UTC (permalink / raw)
To: Dev Jain
Cc: Harry Yoo, ryabinin.a.a, akpm, corbet, glider, andreyknvl,
dvyukov, vincenzo.frascino, kasan-dev, linux-mm, linux-kernel,
skhan, workflows, linux-doc, linux-arm-kernel, ryan.roberts,
anshuman.khandual, kaleshsingh, 21cnbao, david, will
In-Reply-To: <f5927785-d5d3-4e64-bbac-220d40718a1f@arm.com>
On Wed, Jun 24, 2026 at 09:44:49AM +0530, Dev Jain wrote:
> Anyhow someone needs to first test the current patchset to get some
> numbers, we would be wasting time on this if no one gets an improvement.
I agree. Something like iperf3 would be interesting, lots of skb
allocations.
--
Catalin
^ permalink raw reply
* [PATCH 0/2] arm64: Add Axiado AX3005 SoC and EVK support
From: Swark Yang @ 2026-06-24 10:21 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah
Cc: devicetree, linux-arm-kernel, linux-kernel, Swark Yang
This series adds initial device tree support for the Axiado AX3005 SoC
and its evaluation board (EVK).
The AX3005 uses Cadence-derived UART/I2C/I3C/GPIO and Synopsys
DesignWare SPI IP blocks. These are already described by
existing bindings, so the device tree reuses the "axiado,ax3000-*",
"cdns,*" and "snps,*" compatible strings; only a new SoC/board
level compatible is added.
Patch 1 adds the AX3005 board/SoC compatible strings to the Axiado
platform binding.
Patch 2 adds the AX3005 SoC dtsi, the EVK board dts, and the Makefile
entry. The EVK enables the CPUs, timer, GPIO, UART, I2C, I3C, SPI and
USB controllers.
Validated with:
- make CHECK_DTBS=y axiado/ax3005-evk.dtb
- make dt_binding_check DT_SCHEMA_FILES=axiado.yaml
- boot-tested on the AX3005 EVK (to init CLI via ramfs)
Signed-off-by: Swark Yang <syang@axiado.com>
---
Swark Yang (2):
dt-bindings: arm: axiado: add AX3005 EVK
arm64: dts: axiado: Add initial support for AX3005 SoC and eval board
Documentation/devicetree/bindings/arm/axiado.yaml | 6 +
arch/arm64/boot/dts/axiado/Makefile | 1 +
arch/arm64/boot/dts/axiado/ax3005-evk.dts | 327 +++++++++
arch/arm64/boot/dts/axiado/ax3005.dtsi | 843 ++++++++++++++++++++++
4 files changed, 1177 insertions(+)
---
base-commit: 2b414a95b8f7307d42173ba9e580d6d3e2bcbfce
change-id: 20260617-upstream-axiado-ax3005-upstream-bb09780a2fdf
Best regards,
--
Swark Yang <syang@axiado.com>
^ permalink raw reply
* [PATCH 1/2] dt-bindings: arm: axiado: add AX3005 EVK
From: Swark Yang @ 2026-06-24 10:21 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah
Cc: devicetree, linux-arm-kernel, linux-kernel, Swark Yang
In-Reply-To: <20260624-upstream-axiado-ax3005-upstream-v1-0-c05bd0bc9124@axiado.com>
Add device tree binding schema for the Axiado AX3005 SoC and its
associated evaluation board. This binding will be used for the
board-level DTS files that support the AX3005 platforms.
Signed-off-by: Swark Yang <syang@axiado.com>
---
Documentation/devicetree/bindings/arm/axiado.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/axiado.yaml b/Documentation/devicetree/bindings/arm/axiado.yaml
index bfabe7b32e65..008d2b1d4e62 100644
--- a/Documentation/devicetree/bindings/arm/axiado.yaml
+++ b/Documentation/devicetree/bindings/arm/axiado.yaml
@@ -20,4 +20,10 @@ properties:
- axiado,ax3000-evk # Axiado AX3000 Evaluation Board
- const: axiado,ax3000 # Axiado AX3000 SoC
+ - description: AX3005 based boards
+ items:
+ - enum:
+ - axiado,ax3005-evk # Axiado AX3005 Evaluation Board
+ - const: axiado,ax3005 # Axiado AX3005 SoC
+
additionalProperties: true
--
2.34.1
^ permalink raw reply related
* [PATCH 2/2] arm64: dts: axiado: Add initial support for AX3005 SoC and eval board
From: Swark Yang @ 2026-06-24 10:21 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah
Cc: devicetree, linux-arm-kernel, linux-kernel, Swark Yang
In-Reply-To: <20260624-upstream-axiado-ax3005-upstream-v1-0-c05bd0bc9124@axiado.com>
Add initial device tree support for the AX3005 SoC and its evaluation
board. The AX3005 is a multi-core SoC featuring 4 Cortex-A53 cores, and
this adds the CPUs, timer, GPIO, UART, I2C, I3C, SPI and USB
controllers.
The AX3005 groups its low-speed controllers into four Slow Peripheral
(SP) blocks (SP0-SP3), each exposing its own I2C/I3C, SPI and UART
instances. The controllers are numbered by their per-SP hardware
instance ID, so the i2cN/spiN/uartN labels and their aliases are
intentionally non-contiguous and do not increase monotonically with
the register address.
Signed-off-by: Swark Yang <syang@axiado.com>
---
arch/arm64/boot/dts/axiado/Makefile | 1 +
arch/arm64/boot/dts/axiado/ax3005-evk.dts | 327 ++++++++++++
arch/arm64/boot/dts/axiado/ax3005.dtsi | 843 ++++++++++++++++++++++++++++++
3 files changed, 1171 insertions(+)
diff --git a/arch/arm64/boot/dts/axiado/Makefile b/arch/arm64/boot/dts/axiado/Makefile
index 6676ad07db61..e71a0850a451 100644
--- a/arch/arm64/boot/dts/axiado/Makefile
+++ b/arch/arm64/boot/dts/axiado/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
dtb-$(CONFIG_ARCH_AXIADO) += ax3000-evk.dtb
+dtb-$(CONFIG_ARCH_AXIADO) += ax3005-evk.dtb
diff --git a/arch/arm64/boot/dts/axiado/ax3005-evk.dts b/arch/arm64/boot/dts/axiado/ax3005-evk.dts
new file mode 100644
index 000000000000..9187057eb7a5
--- /dev/null
+++ b/arch/arm64/boot/dts/axiado/ax3005-evk.dts
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DTS file for Axiado AX3005 SoC based EVK
+ * Copyright (c) 2026 Axiado Corporation.
+ */
+
+/dts-v1/;
+
+#include "ax3005.dtsi"
+
+/ {
+ model = "Axiado AX3005 EVK";
+ compatible = "axiado,ax3005-evk", "axiado,ax3005";
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ aliases {
+ serial0 = &uart0;
+ serial1 = &uart1;
+ serial2 = &uart2;
+ serial3 = &uart3;
+ serial4 = &uart4;
+ serial5 = &uart5;
+ serial6 = &uart6;
+ serial7 = &uart7;
+ serial8 = &uart8;
+ i2c0 = &i2c0;
+ i2c1 = &i2c1;
+ i2c2 = &i2c2;
+ i2c3 = &i2c3;
+ i2c4 = &i2c4;
+ i2c5 = &i2c5;
+ i2c8 = &i2c8;
+ i2c9 = &i2c9;
+ i2c10 = &i2c10;
+ i2c11 = &i2c11;
+ i2c12 = &i3c12;
+ i2c13 = &i3c13;
+ i2c14 = &i2c14;
+ i2c15 = &i2c15;
+ i2c16 = &i2c16;
+ i2c17 = &i2c17;
+ i2c18 = &i2c18;
+ i2c19 = &i2c19;
+ i2c20 = &i2c20;
+ i2c21 = &i2c21;
+ i2c22 = &i2c22;
+ i2c23 = &i2c23;
+ i2c24 = &i2c24;
+ i2c25 = &i2c25;
+ i2c26 = &i2c26;
+ i2c27 = &i2c27;
+ i2c28 = &i2c28;
+ i2c29 = &i2c29;
+ i2c30 = &i2c30;
+ i2c31 = &i2c31;
+ i2c32 = &i2c32;
+ i2c33 = &i2c33;
+ i2c34 = &i2c34;
+ i2c35 = &i2c35;
+ i2c36 = &i2c36;
+ spi0 = &spi0;
+ spi1 = &spi1;
+ spi2 = &spi2;
+ spi3 = &spi3;
+ spi5 = &spi5;
+ spi6 = &spi6;
+ };
+
+ chosen {
+ stdout-path = "serial3:115200";
+ };
+
+ memory@0 {
+ device_type = "memory";
+ /* Cortex-A53 will use following memory map */
+ reg = <0x0 0x81000000 0x0 0x7f000000>;
+ };
+};
+
+&gpio0 {
+ status = "okay";
+};
+
+&gpio1 {
+ status = "okay";
+};
+
+&gpio2 {
+ status = "okay";
+};
+
+&gpio3 {
+ status = "okay";
+};
+
+&gpio4 {
+ status = "okay";
+};
+
+&gpio5 {
+ status = "okay";
+};
+
+&gpio6 {
+ status = "okay";
+};
+
+&gpio7 {
+ status = "okay";
+};
+
+&i2c0 {
+ status = "okay";
+};
+
+&i2c1 {
+ status = "okay";
+};
+
+&i2c2 {
+ status = "okay";
+};
+
+&i2c3 {
+ status = "okay";
+};
+
+&i2c4 {
+ status = "okay";
+};
+
+&i2c5 {
+ status = "okay";
+};
+
+&i2c8 {
+ status = "okay";
+};
+
+&i2c9 {
+ status = "okay";
+};
+
+&i2c10 {
+ status = "okay";
+};
+
+&i2c11 {
+ status = "okay";
+};
+
+&i3c12 {
+ status = "okay";
+};
+
+&i3c13 {
+ status = "okay";
+};
+
+&i2c14 {
+ status = "okay";
+};
+
+&i2c15 {
+ status = "okay";
+};
+
+&i2c16 {
+ status = "okay";
+};
+
+&i2c17 {
+ status = "okay";
+};
+
+&i2c18 {
+ status = "okay";
+};
+
+&i2c19 {
+ status = "okay";
+};
+
+&i2c20 {
+ status = "okay";
+};
+
+&i2c21 {
+ status = "okay";
+};
+
+&i2c22 {
+ status = "okay";
+};
+
+&i2c23 {
+ status = "okay";
+};
+
+&i2c24 {
+ status = "okay";
+};
+
+&i2c25 {
+ status = "okay";
+};
+
+&i2c26 {
+ status = "okay";
+};
+
+&i2c27 {
+ status = "okay";
+};
+
+&i2c28 {
+ status = "okay";
+};
+
+&i2c29 {
+ status = "okay";
+};
+
+&i2c30 {
+ status = "okay";
+};
+
+&i2c31 {
+ status = "okay";
+};
+
+&i2c32 {
+ status = "okay";
+};
+
+&i2c33 {
+ status = "okay";
+};
+
+&i2c34 {
+ status = "okay";
+};
+
+&i2c35 {
+ status = "okay";
+};
+
+&i2c36 {
+ status = "okay";
+};
+
+&spi0 {
+ status = "okay";
+};
+
+&spi1 {
+ status = "okay";
+};
+
+&spi2 {
+ status = "okay";
+};
+
+&spi3 {
+ status = "okay";
+};
+
+&spi5 {
+ status = "okay";
+};
+
+&spi6 {
+ status = "okay";
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&uart1 {
+ status = "okay";
+};
+
+&uart2 {
+ status = "okay";
+};
+
+&uart3 {
+ status = "okay";
+};
+
+&uart4 {
+ status = "okay";
+};
+
+&uart5 {
+ status = "okay";
+};
+
+&uart6 {
+ status = "okay";
+};
+
+&uart7 {
+ status = "okay";
+};
+
+&uart8 {
+ status = "okay";
+};
+
+&usb2_0 {
+ status = "okay";
+};
+
+&usb2_1 {
+ status = "okay";
+};
+
+&usb3_0 {
+ status = "okay";
+};
+
+&usb3_1 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/axiado/ax3005.dtsi b/arch/arm64/boot/dts/axiado/ax3005.dtsi
new file mode 100644
index 000000000000..3c2ce18082a9
--- /dev/null
+++ b/arch/arm64/boot/dts/axiado/ax3005.dtsi
@@ -0,0 +1,843 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2026 Axiado Corporation.
+ */
+
+/dts-v1/;
+
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+/memreserve/ 0x80002fa0 0x00000008;
+/ {
+ model = "Axiado AX3005";
+ interrupt-parent = <&gic500>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ cpus {
+ #address-cells = <2>;
+ #size-cells = <0>;
+
+ cpu0: cpu@0 {
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ reg = <0x0 0x0>;
+ enable-method = "spin-table";
+ cpu-release-addr = <0x0 0x80002fa0>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <128>;
+ i-cache-size = <0x8000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ next-level-cache = <&l2>;
+ };
+
+ cpu1: cpu@1 {
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ reg = <0x0 0x1>;
+ enable-method = "spin-table";
+ cpu-release-addr = <0x0 0x80002fa0>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <128>;
+ i-cache-size = <0x8000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ next-level-cache = <&l2>;
+ };
+
+ cpu2: cpu@2 {
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ reg = <0x0 0x2>;
+ enable-method = "spin-table";
+ cpu-release-addr = <0x0 0x80002fa0>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <128>;
+ i-cache-size = <0x8000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ next-level-cache = <&l2>;
+ };
+
+ cpu3: cpu@3 {
+ compatible = "arm,cortex-a53";
+ device_type = "cpu";
+ reg = <0x0 0x3>;
+ enable-method = "spin-table";
+ cpu-release-addr = <0x0 0x80002fa0>;
+ d-cache-size = <0x8000>;
+ d-cache-line-size = <64>;
+ d-cache-sets = <128>;
+ i-cache-size = <0x8000>;
+ i-cache-line-size = <64>;
+ i-cache-sets = <256>;
+ next-level-cache = <&l2>;
+ };
+
+ l2: l2-cache0 {
+ compatible = "cache";
+ cache-unified;
+ cache-size = <0x100000>;
+ cache-line-size = <64>;
+ cache-sets = <1024>;
+ cache-level = <2>;
+ };
+ };
+
+ timer {
+ compatible = "arm,armv8-timer";
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_PPI 13 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_PPI 14 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_PPI 11 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_PPI 10 IRQ_TYPE_LEVEL_HIGH>;
+ arm,cpu-registers-not-fw-configured;
+ };
+
+ clocks {
+ refclk: clock-125000000 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <125000000>;
+ };
+
+ pclk: clock-100000000 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <100000000>;
+ };
+
+ sysclk: clock-200000000 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <200000000>;
+ };
+
+ spiclk: clock-400000000 {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <400000000>;
+ };
+ };
+
+ soc {
+ compatible = "simple-bus";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ interrupt-parent = <&gic500>;
+ ranges;
+
+ gic500: interrupt-controller@40400000 {
+ compatible = "arm,gic-v3";
+ reg = <0x0 0x40400000 0x0 0x10000>,
+ <0x0 0x40500000 0x0 0xc0000>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ #interrupt-cells = <3>;
+ interrupt-controller;
+ #redistributor-regions = <1>;
+ interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ /* GPIO Controller banks 0 - 7 */
+ gpio0: gpio-controller@33000000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33000000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 183 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio1: gpio-controller@33080000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33080000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio2: gpio-controller@33100000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33100000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 185 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio3: gpio-controller@33180000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33180000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio4: gpio-controller@33200000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33200000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio5: gpio-controller@33280000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33280000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 188 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio6: gpio-controller@33300000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33300000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 189 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ gpio7: gpio-controller@33380000 {
+ compatible = "axiado,ax3000-gpio", "cdns,gpio-r1p02";
+ reg = <0x0 0x33380000 0x0 0x100>;
+ clocks = <&pclk>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ status = "disabled";
+ };
+
+ i2c0: i2c@33000400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33000400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c1: i2c@33000800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33000800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c2: i2c@33080400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33080400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c3: i2c@33080800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33080800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 79 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c4: i2c@33100400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33100400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c5: i2c@33100800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33100800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c8: i2c@33200400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33200400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c9: i2c@33200800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33200800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c10: i2c@33280400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33280400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c11: i2c@33280800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33280800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i3c12: i3c@33300400 {
+ compatible = "axiado,ax3000-i3c", "cdns,i3c-master";
+ reg = <0x0 0x33300400 0x0 0x400>;
+ clock-names = "pclk", "sysclk";
+ clocks = <&pclk &sysclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-scl-hz = <100000>;
+ i3c-scl-hz = <400000>;
+ #address-cells = <3>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i3c13: i3c@33300800 {
+ compatible = "axiado,ax3000-i3c", "cdns,i3c-master";
+ reg = <0x0 0x33300800 0x0 0x400>;
+ clock-names = "pclk", "sysclk";
+ clocks = <&pclk &sysclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
+ i2c-scl-hz = <100000>;
+ i3c-scl-hz = <400000>;
+ #address-cells = <3>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c14: i2c@33380400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33380400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c15: i2c@33380800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33380800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c16: i2c@33120400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33120400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 92 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c17: i2c@33021400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33021400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 213 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c18: i2c@33021800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33021800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 214 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c19: i2c@330c0400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x330c0400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 215 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c20: i2c@330c0800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x330c0800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 216 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c21: i2c@330c0c00 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x330c0c00 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 217 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c22: i2c@331a0800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x331a0800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 218 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c23: i2c@33302800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33302800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 219 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c24: i2c@33302c00 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33302c00 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 220 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c25: i2c@33303000 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33303000 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 221 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c26: i2c@33382000 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33382000 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 222 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c27: i2c@33382400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33382400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 223 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c28: i2c@33382800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33382800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 224 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c29: i2c@33382c00 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33382c00 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 225 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c30: i2c@33383000 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33383000 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 226 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c31: i2c@33383400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33383400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 227 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c32: i2c@33383800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33383800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 228 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c33: i2c@33282400 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33282400 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 238 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c34: i2c@33282800 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33282800 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 239 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c35: i2c@33282c00 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33282c00 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 240 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ i2c36: i2c@33283000 {
+ compatible = "cdns,i2c-r1p14";
+ reg = <0x0 0x33283000 0x0 0x400>;
+ clocks = <&pclk>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 241 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
+ spi0: spi@33010000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x33010000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <2>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi1: spi@33090000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x33090000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <1>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi2: spi@333c0000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x333c0000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <1>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi3: spi@330e0000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x330e0000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <1>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi5: spi@33390000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x33390000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <2>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ spi6: spi@333a0000 {
+ compatible = "snps,dwc-ssi-1.01a";
+ reg = <0x0 0x333a0000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&spiclk>;
+ num-cs = <1>;
+ reg-io-width = <4>;
+ status = "disabled";
+ };
+
+ uart0: serial@33020000 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33020000 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart1: serial@330a0000 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x330a0000 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart2: serial@33120000 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33120000 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart3: serial@33020800 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33020800 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart4: serial@331a0400 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x331a0400 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 208 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart5: serial@33381d00 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33381d00 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 209 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart6: serial@33381e00 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33381e00 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 210 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart7: serial@33381f00 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x33381f00 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 211 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ uart8: serial@330c0000 {
+ compatible = "axiado,ax3000-uart", "cdns,uart-r1p12";
+ reg = <0x0 0x330c0000 0x0 0x100>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 212 IRQ_TYPE_LEVEL_HIGH>;
+ clock-names = "uart_clk", "pclk";
+ clocks = <&refclk &pclk>;
+ status = "disabled";
+ };
+
+ usb2_0: usb@41000000 {
+ compatible = "generic-xhci";
+ reg = <0x0 0x41000000 0x0 0x100000>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+
+ usb2_1: usb@41100000 {
+ compatible = "generic-xhci";
+ reg = <0x0 0x41100000 0x0 0x100000>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+
+ usb3_0: usb@41400000 {
+ compatible = "generic-xhci";
+ reg = <0x0 0x41400000 0x0 0x100000>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+
+ usb3_1: usb@41500000 {
+ compatible = "generic-xhci";
+ reg = <0x0 0x41500000 0x0 0x100000>;
+ interrupt-parent = <&gic500>;
+ interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
+ };
+};
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v3 06/15] drm/tidss: Remove extra pm_runtime_mark_last_busy
From: Swamil Jain @ 2026-06-24 10:39 UTC (permalink / raw)
To: Tomi Valkeinen, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Aradhya Bhatia,
Nishanth Menon, Vignesh Raghavendra, Devarsh Thakkar,
Louis Chauvet
Cc: devicetree, dri-devel, linux-kernel, linux-arm-kernel
In-Reply-To: <20260529-beagley-ai-display-v3-6-7fefdc5d1adf@ideasonboard.com>
On 29-05-2026 14:15, Tomi Valkeinen wrote:
> pm_runtime_put_autosuspend() calls pm_runtime_mark_last_busy(), so no
> need to call pm_runtime_mark_last_busy() explicitly in the driver.
>
> Tested-by: Swamil Jain <s-jain1@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
Reviewed-by: Swamil Jain <s-jain1@ti.com>
> drivers/gpu/drm/tidss/tidss_drv.c | 2 --
> 1 file changed, 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/tidss/tidss_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c
> index 1c8cc18bc53c..5cb3e746aeb3 100644
> --- a/drivers/gpu/drm/tidss/tidss_drv.c
> +++ b/drivers/gpu/drm/tidss/tidss_drv.c
> @@ -42,8 +42,6 @@ void tidss_runtime_put(struct tidss_device *tidss)
> {
> int r;
>
> - pm_runtime_mark_last_busy(tidss->dev);
> -
> r = pm_runtime_put_autosuspend(tidss->dev);
> WARN_ON(r < 0);
> }
>
^ permalink raw reply
* Re: [RFC PATCH 1/6] media: mc: Implement shared media graph
From: Kieran Bingham @ 2026-06-24 10:39 UTC (permalink / raw)
To: Paul Elder, laurent.pinchart
Cc: Paul Elder, michael.riesch, xuhf, stefan.klug, dan.scally,
jacopo.mondi, linux-media, linux-arm-kernel, linux-rockchip,
linux-kernel, hverkuil+cisco, nicolas.dufresne, ribalda,
sakari.ailus
In-Reply-To: <20260619052637.1110672-2-paul.elder@ideasonboard.com>
Hi Paul,
I'm taking a first read through, some comments inline, but not
necessarily a full review:
Quoting Paul Elder (2026-06-19 06:26:28)
> Currently, a media graph contains a main device whose driver is
> responsible for creating the media device. We have however recently run
> into devices that have multiple devices that can quality as a main
s/quality/qualify/
> device. Examples are the RK3588 which has a VICAP and two ISP
> instances, and another example is the i.MX8MP which has an ISI and two
> ISP instances. As there is currently no way to reconcile who the main
> device is in the media device, these setups simple cannot be used
> simultaneously.
I'm very excited to see how we could apply this to the NXP i.MX8MP to
resolve the ISI+ISP issue!
> This patch extends the media controller API with a "shared media graph"
> framework. This allows drivers to share a media device, thus enabling
> the setups mentioned above. Instead of owning and creating a media
> device, drivers can join-or-create a shared media device via the shared
> media graph API. The matching is done automatically based on the
> detected endpoints in the device tree.
Sounds great! I'll read on...
>
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> ---
> drivers/media/mc/Makefile | 2 +-
> drivers/media/mc/mc-shared-graph.c | 335 +++++++++++++++++++++++++++++
> include/media/mc-shared-graph.h | 92 ++++++++
> 3 files changed, 428 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/mc/mc-shared-graph.c
> create mode 100644 include/media/mc-shared-graph.h
>
> diff --git a/drivers/media/mc/Makefile b/drivers/media/mc/Makefile
> index 2b7af42ba59c..1d502fdc52ad 100644
> --- a/drivers/media/mc/Makefile
> +++ b/drivers/media/mc/Makefile
> @@ -1,7 +1,7 @@
> # SPDX-License-Identifier: GPL-2.0
>
> mc-objs := mc-device.o mc-devnode.o mc-entity.o \
> - mc-request.o
> + mc-request.o mc-shared-graph.o
>
> ifneq ($(CONFIG_USB),)
> mc-objs += mc-dev-allocator.o
> diff --git a/drivers/media/mc/mc-shared-graph.c b/drivers/media/mc/mc-shared-graph.c
> new file mode 100644
> index 000000000000..c4067e5b861d
> --- /dev/null
> +++ b/drivers/media/mc/mc-shared-graph.c
> @@ -0,0 +1,335 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * mc-shared-graph.c - Media Controller Shared Graph API
> + *
> + * Copyright (c) 2026 Paul Elder <paul.elder@ideasonboard.com>
> + */
> +
> +/*
> + * This file adds the Media Controller Shared Graph API. This allows drivers
> + * to create shared media graphs or join existing media graphs from other
> + * drivers, so that they can all be in the same media graph. This allows us to
> + * have more complex media graphs chaining more complex hardware together,
> + * instead of simple async subdevs.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/fwnode.h>
> +#include <linux/kref.h>
> +#include <linux/property.h>
> +
> +#include <media/media-device.h>
> +
> +#include <media/mc-shared-graph.h>
> +
> +static LIST_HEAD(media_device_shared_list);
> +static DEFINE_MUTEX(media_device_shared_lock);
> +
> +struct media_device_shared_member {
> + struct device *dev;
> + struct fwnode_handle *fwnode;
> + struct list_head list;
> +};
> +
> +struct media_device_shared_link {
> + struct media_entity *source;
> + u16 source_pad;
> + struct media_entity *sink;
> + u16 sink_pad;
> + u32 flags;
> + struct list_head list;
> +};
> +
> +// TODO figure out locking for when multiple drivers touch the media graph;
> +// maybe macros for shared versions?
Do you mean for when drivers are trying to change link state directly?
> +struct media_device_shared {
> + struct media_device mdev;
> + struct list_head members;
> + struct list_head links;
> +
> + struct list_head list;
> + struct kref refcount;
> +
> + struct device *removed_device;
> +};
> +
> +static inline struct media_device_shared *
> +to_media_device_shared(struct media_device *mdev)
> +{
> + return container_of(mdev, struct media_device_shared, mdev);
> +}
> +
> +static void media_device_shared_release(struct kref *kref)
> +{
> + struct media_device_shared *mds =
> + container_of(kref, struct media_device_shared, refcount);
> +
> + dev_dbg(mds->removed_device, "%s: releasing Media Device\n", __func__);
> +
> + mutex_lock(&media_device_shared_lock);
> +
> + media_device_unregister(&mds->mdev);
> + media_device_cleanup(&mds->mdev);
> +
> + list_del(&mds->list);
> + mutex_unlock(&media_device_shared_lock);
> +
> + kfree(mds);
> +}
> +
> +/* Callers should hold media_device_shared_lock when calling this function */
Lets add a lockdep_assert then?
> +static bool __media_device_shared_find_match(struct media_device_shared *mds,
> + struct fwnode_handle *fwnode)
> +{
> + struct media_device_shared_member *member;
> + struct fwnode_handle *ep;
> + struct fwnode_handle *remote_ep;
> + bool match = false;
> +
is it just as easy as:
lockdep_assert_held(&media_device_shared_lock);
?
> + // TODO: parse the device tree endpoints graph instead of finding just the
> + // first-level neighbours
> + fwnode_graph_for_each_endpoint(fwnode, ep) {
> + list_for_each_entry(member, &mds->members, list) {
> + remote_ep = fwnode_graph_get_remote_port_parent(ep);
> + match = (member->fwnode == remote_ep);
> + fwnode_handle_put(remote_ep);
> +
> + if (!match)
> + continue;
> +
> + goto match_complete;
> + }
> + }
> +
> +match_complete:
> + fwnode_handle_put(ep);
> + return match;
> +}
> +
> +/* Callers should hold media_device_shared_lock when calling this function */
Lets add a lockdep check too then :D (same everywhere/anywhere it's
needed)
> +static struct media_device *__media_device_shared_get(struct device *dev)
> +{
> + struct media_device_shared *mds;
> + struct media_device_shared_member *member;
> + struct fwnode_handle *fwnode = dev_fwnode(dev);
> + bool ret;
> +
> + dev_dbg(dev, "%s: searching for media device for %pfwf", __func__, fwnode);
> +
> + list_for_each_entry(mds, &media_device_shared_list, list) {
> + ret = __media_device_shared_find_match(mds, fwnode);
> + if (ret)
> + break;
> + }
> +
> + if (!ret)
> + return NULL;
> +
> + member = kzalloc_obj(*member);
> + if (!member)
> + return NULL;
> +
> + member->dev = dev;
> + member->fwnode = fwnode;
> + list_add_tail(&member->list, &mds->members);
> + kref_get(&mds->refcount);
> +
> + dev_dbg(dev, "%s: %pfwf joined media device of %pfwf",
> + __func__, fwnode,
> + list_first_entry(&mds->members, struct media_device_shared_member, list)->fwnode);
> +
> + return &mds->mdev;
> +}
> +
> +/* Callers should hold media_device_shared_lock when calling this function */
> +static struct media_device *__media_device_shared_create(struct device *dev)
> +{
> + struct media_device_shared *mds;
> + struct media_device_shared_member *member;
> + struct fwnode_handle *fwnode = dev_fwnode(dev);
> + int ret;
> +
> + mds = kzalloc_obj(*mds);
> + if (!mds)
> + return NULL;
> +
> + member = kzalloc_obj(*member);
> + if (!member)
> + goto err_free_mds;
> +
> + media_device_init(&mds->mdev);
> +
> + ret = media_device_register(&mds->mdev);
> + if (ret)
> + goto err_free_member;
> +
> + INIT_LIST_HEAD(&mds->members);
> + member->dev = dev;
> + member->fwnode = fwnode;
> + list_add_tail(&member->list, &mds->members);
> +
> + INIT_LIST_HEAD(&mds->links);
> +
> + kref_init(&mds->refcount);
> + list_add_tail(&mds->list, &media_device_shared_list);
> +
> + // TODO figure out how to reconcile this with multiple members
Aha, right - becuse the 'media_device dev' becomes whoever registers first.
I wonder where it's actually used, and maybe that's ok ?
> + mds->mdev.dev = dev;
> +
> + devv_dbg(dev, "%s: Allocated media device with %pfwf at %p\n",
> + __func__, fwnode, &mds->mdev);
> + return &mds->mdev;
> +
> +err_free_member:
> + kfree(member);
> +err_free_mds:
> + kfree(mds);
> + return NULL;
> +}
> +
> +// TODO figure out how to resolve the identifiers (model, driver name, etc);
> +// atm it's racy and whoever gets it last wins
Maybe that's something we need to pass or set up explicitly then, so
it's clear it's a 'specific graph'
Would this give us a way to represent the hardware in a new form - i.e.
thinking about IMX8MP - the existing graph wouldn't be available - so it
wouldn't be any worry from a UABI perspective because it's just a whole
new interface/media-device name....
> +struct media_device *media_device_shared_join(struct device *dev)
> +{
> + struct media_device *mdev;
> +
> + mutex_lock(&media_device_shared_lock);
> +
> + mdev = __media_device_shared_get(dev);
> + if (!!mdev) {
> + dev_dbg(dev, "%s: found media device for %pfwf", __func__, dev_fwnode(dev));
> + mutex_unlock(&media_device_shared_lock);
> + return mdev;
> + }
> +
> + mdev = __media_device_shared_create(dev);
> + if (!mdev) {
> + dev_warn(dev, "%s: failed to create media device for %pfwf", __func__, dev_fwnode(dev));
> + mutex_unlock(&media_device_shared_lock);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + dev_dbg(dev, "%s: created media device for %pfwf", __func__, dev_fwnode(dev));
> + mutex_unlock(&media_device_shared_lock);
> + return mdev;
> +}
> +EXPORT_SYMBOL_GPL(media_device_shared_join);
> +
> +void media_device_shared_leave(struct media_device *mdev, struct device *dev)
> +{
> + struct media_device_shared *mds = to_media_device_shared(mdev);
> + struct media_device_shared_member *member;
> + struct media_device_shared_member *member_tmp;
> + bool removed = false;
> +
> + mutex_lock(&media_device_shared_lock);
> +
> + list_for_each_entry_safe(member, member_tmp, &mds->members, list) {
> + if (member->dev == dev) {
> + list_del(&member->list);
> + kfree(member);
> + removed = true;
> + }
> + }
> +
> + if (!removed)
> + dev_err(dev, "%s: %pfwf trying to leave from graph in which not a member",
> + __func__, dev_fwnode(dev));
> +
> + mds->removed_device = dev;
> + mutex_unlock(&media_device_shared_lock);
> + kref_put(&mds->refcount, media_device_shared_release);
> +}
> +EXPORT_SYMBOL_GPL(media_device_shared_leave);
> +
> +int media_device_shared_join_link_source(struct media_device *mdev,
> + struct device *dev,
> + struct media_entity *source,
> + u16 source_pad, u32 flags)
> +{
> + struct media_device_shared *mds = to_media_device_shared(mdev);
> + struct media_device_shared_link *link;
> + struct media_device_shared_link *link_tmp;
> + int ret = 0;
> +
> + mutex_lock(&media_device_shared_lock);
> +
> + /*
> + * TODO Figure out flags. Should we use greatest common denominator? Or
> + * prioritize sink? Or whoever wins the race? For now we just take the flags
> + * from the sink.
> + *
> + * TODO Figure out how to actually do the matching. For now we just match
> + * whoever comes in first. This works with the simple example we're running
> + * with now (rkcif + one rkisp2) but with setups with multiple copies of
> + * hardware this will cause problems, like with rkcif + two rkisp2 and
> + * imx8-isi + two rkisp1.
> + */
> + list_for_each_entry_safe(link, link_tmp, &mds->links, list) {
> + if (link->sink) {
> + ret = media_create_pad_link(source, source_pad,
> + link->sink, link->sink_pad,
> + link->flags);
> + list_del(&link->list);
> + kfree(link);
> + goto exit_join_link_source;
> + }
> + }
> +
> + link = kzalloc_obj(*link);
> + if (!link) {
> + ret = -ENOMEM;
> + goto exit_join_link_source;
> + }
> +
> + link->source = source;
> + link->source_pad = source_pad;
> + link->flags = flags;
> + list_add_tail(&link->list, &mds->links);
> +
> +exit_join_link_source:
> + mutex_unlock(&media_device_shared_lock);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(media_device_shared_join_link_source);
> +
> +// TODO deduplicate from above
Indeed, it does look like an opportunity.
> +int media_device_shared_join_link_sink(struct media_device *mdev,
> + struct device *dev,
> + struct media_entity *sink,
> + u16 sink_pad, u32 flags)
> +{
> + struct media_device_shared *mds = to_media_device_shared(mdev);
> + struct media_device_shared_link *link;
> + struct media_device_shared_link *link_tmp;
> + int ret = 0;
> +
> + mutex_lock(&media_device_shared_lock);
> +
> + list_for_each_entry_safe(link, link_tmp, &mds->links, list) {
> + if (link->source) {
> + ret = media_create_pad_link(link->source, link->source_pad,
> + sink, sink_pad,
> + flags);
> + list_del(&link->list);
> + kfree(link);
> + goto exit_join_link_sink;
> + }
> + }
> +
> + link = kzalloc_obj(*link);
> + if (!link) {
> + ret = -ENOMEM;
> + goto exit_join_link_sink;
> + }
> +
> + link->sink = sink;
> + link->sink_pad = sink_pad;
> + link->flags = flags;
> + list_add_tail(&link->list, &mds->links);
> +
> +exit_join_link_sink:
> + mutex_unlock(&media_device_shared_lock);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(media_device_shared_join_link_sink);
> diff --git a/include/media/mc-shared-graph.h b/include/media/mc-shared-graph.h
> new file mode 100644
> index 000000000000..487325163f84
> --- /dev/null
> +++ b/include/media/mc-shared-graph.h
> @@ -0,0 +1,92 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * mc-shared-graph.h - Media Controller Shared Graph API
> + *
> + * Copyright (c) 2026 Paul Elder <paul.elder@ideasonboard.com>
> + */
> +
> +/*
> + * This file adds the Media Controller Shared Graph API. This allows drivers
> + * to create shared media graphs or join existing media graphs from other
> + * drivers, so that they can all be in the same media graph. This allows us to
> + * have more complex media graphs chaining more complex hardware together,
> + * instead of simple async subdevs.
> + */
> +
> +#include <linux/types.h>
> +
> +#ifndef _MEDIA_SHARED_GRAPH_H
> +#define _MEDIA_SHARED_GRAPH_H
> +
> +struct device;
> +struct media_device;
> +struct media_entity;
> +
> +#if defined(CONFIG_MEDIA_CONTROLLER)
> +/**
> + * media_device_shared_join() - Join or create a new shared media device
> + *
> + * @dev: struct &device pointer
> + *
> + * This is the entrance function for a device to join or create a new shared
> + * media device. It searches for an existing shared media device based on the
> + * neighbours in the device's device tree ports node. If found, then this
> + * functions returns the existing shared media device and joins it. If one is
> + * not found then one is created and initialized and returned.
> + */
> +struct media_device *media_device_shared_join(struct device *dev);
> +
> +/**
> + * media_device_shared_leave() - Leave the shared media device.
> + *
> + * @mdev: struct &media_device pointer
> + * @dev: struct &device pointer
> + *
> + * This function makes the device leave the shared media device. When all
> + * members have left the media device it will be freed.
> + */
> +void media_device_shared_leave(struct media_device *mdev, struct device *dev);
> +
> +/**
> + * media_device_shared_join_link_source() - Register a link source in the shared media device
> + *
> + * @mdev: The struct &media_device pointer that is part of a shared media device
> + * @dev: struct &device pointer
> + * @source: The link source
> + * @source_pad: The pad
> + * @flags: The flags
> + *
> + * This function registers with the shared media device the source part of a
> + * link. When the shared media device receives the matching sink part of a link
> + * via media_device_shared_join_link_sink() then the link will be fully created.
> + */
> +int media_device_shared_join_link_source(struct media_device *mdev,
> + struct device *dev,
> + struct media_entity *source,
> + u16 source_pad, u32 flags);
> +
> +/**
> + * media_device_shared_join_link_sink() - Register a link sink in the shared media device
> + *
> + * Same as media_device_shared_join_link_source() but for sink instead of
> + * source.
> + */
> +int media_device_shared_join_link_sink(struct media_device *mdev,
> + struct device *dev,
> + struct media_entity *sink,
> + u16 sink_pad, u32 flags);
> +#else
> +static inline struct media_device *media_device_shared_join(struct device *dev)
> +{ return NULL; }
> +static inline void media_device_shared_leave(struct media_device *mdev,
> + struct device *dev) { }
> +static inline int media_device_shared_join_link_source(struct media_device *mdev,
> + struct device *dev,
> + struct media_entity *source,
> + u16 source_pad, u32 flags) { }
> +static inline int media_device_shared_join_link_sink(struct media_device *mdev,
> + struct device *dev,
> + struct media_entity *sink,
> + u16 sink_pad, u32 flags) { }
Should these two stubs which return an int return an error code? -ENODEV or such ?
> +#endif /* CONFIG_MEDIA_CONTROLLER */
> +#endif /* _MEDIA_DEV_SHARED_GRAPH_H */
> --
> 2.47.2
>
^ permalink raw reply
* Re: [PATCH v3 07/15] drm/tidss: oldi: Remove define for unused register OLDI_LB_CTRL
From: Swamil Jain @ 2026-06-24 10:43 UTC (permalink / raw)
To: Tomi Valkeinen, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Aradhya Bhatia,
Nishanth Menon, Vignesh Raghavendra, Devarsh Thakkar,
Louis Chauvet
Cc: devicetree, dri-devel, linux-kernel, linux-arm-kernel
In-Reply-To: <20260529-beagley-ai-display-v3-7-7fefdc5d1adf@ideasonboard.com>
On 29-05-2026 14:15, Tomi Valkeinen wrote:
> OLDI_LB_CTRL define is not used, and doesn't seem to exist at least on
> some SoCs. Let's remove the define.
>
> Tested-by: Swamil Jain <s-jain1@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
Reviewed-by: Swamil Jain <s-jain1@ti.com>
> drivers/gpu/drm/tidss/tidss_oldi.h | 1 -
> 1 file changed, 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/tidss/tidss_oldi.h b/drivers/gpu/drm/tidss/tidss_oldi.h
> index 8cd535c5ee65..a361e6dbfce3 100644
> --- a/drivers/gpu/drm/tidss/tidss_oldi.h
> +++ b/drivers/gpu/drm/tidss/tidss_oldi.h
> @@ -20,7 +20,6 @@ struct tidss_oldi;
>
> /* Register offsets */
> #define OLDI_PD_CTRL 0x100
> -#define OLDI_LB_CTRL 0x104
>
> /* Power control bits */
> #define OLDI_PWRDOWN_TX(n) BIT(n)
>
^ permalink raw reply
* Re: [PATCH v3 08/15] drm/tidss: Add mechanism to detect DPI output
From: Swamil Jain @ 2026-06-24 10:44 UTC (permalink / raw)
To: Tomi Valkeinen, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Aradhya Bhatia,
Nishanth Menon, Vignesh Raghavendra, Devarsh Thakkar,
Louis Chauvet
Cc: devicetree, dri-devel, linux-kernel, linux-arm-kernel
In-Reply-To: <20260529-beagley-ai-display-v3-8-7fefdc5d1adf@ideasonboard.com>
On 29-05-2026 14:15, Tomi Valkeinen wrote:
> There are situations where the driver needs to know if the output is
> going to the DPI output or not. There is no trivial way to get this
> information, as there is no "DPI bridge". We can only find this out in
> reverse: check if the output is NOT DPI, and if that is negative, then
> it must be DPI.
>
> At the moment we have two non-DPI outputs: DSI and OLDI. DSI always has
> "ti,j721e-dsi" DSI bridge connected to the DSI, so we can use that for
> checking. OLDI doesn't have a compatible property, but we can check if
> the DT node has "oldi-transmitters" node as a parent, and the dss node
> itself as a grand-parent.
>
> If the output is not connected to either of the above, it must be DPI.
>
> Tested-by: Swamil Jain <s-jain1@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
Reviewed-by: Swamil Jain <s-jain1@ti.com>
> drivers/gpu/drm/tidss/tidss_crtc.c | 10 +++++--
> drivers/gpu/drm/tidss/tidss_crtc.h | 4 ++-
> drivers/gpu/drm/tidss/tidss_dispc.c | 5 +++-
> drivers/gpu/drm/tidss/tidss_dispc.h | 3 +-
> drivers/gpu/drm/tidss/tidss_kms.c | 55 ++++++++++++++++++++++++++++++++++++-
> 5 files changed, 70 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c
> index a31c21c5f855..dfdf61b01dcd 100644
> --- a/drivers/gpu/drm/tidss/tidss_crtc.c
> +++ b/drivers/gpu/drm/tidss/tidss_crtc.c
> @@ -192,7 +192,8 @@ static void tidss_crtc_atomic_flush(struct drm_crtc *crtc,
> return;
>
> /* Write vp properties to HW if needed. */
> - dispc_vp_setup(tidss->dispc, tcrtc->hw_videoport, crtc->state, false);
> + dispc_vp_setup(tidss->dispc, tcrtc->hw_videoport, crtc->state, false,
> + tcrtc->dpi_output);
>
> /* Update plane positions if needed. */
> tidss_crtc_position_planes(tidss, crtc, old_crtc_state, false);
> @@ -235,7 +236,8 @@ static void tidss_crtc_atomic_enable(struct drm_crtc *crtc,
> if (r != 0)
> return;
>
> - dispc_vp_setup(tidss->dispc, tcrtc->hw_videoport, crtc->state, true);
> + dispc_vp_setup(tidss->dispc, tcrtc->hw_videoport, crtc->state, true,
> + tcrtc->dpi_output);
> tidss_crtc_position_planes(tidss, crtc, old_state, true);
>
> /* Turn vertical blanking interrupt reporting on. */
> @@ -417,7 +419,8 @@ static const struct drm_crtc_funcs tidss_crtc_funcs = {
>
> struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss,
> u32 hw_videoport,
> - struct drm_plane *primary)
> + struct drm_plane *primary,
> + bool dpi_output)
> {
> struct tidss_crtc *tcrtc;
> struct drm_crtc *crtc;
> @@ -430,6 +433,7 @@ struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss,
> return ERR_PTR(-ENOMEM);
>
> tcrtc->hw_videoport = hw_videoport;
> + tcrtc->dpi_output = dpi_output;
> init_completion(&tcrtc->framedone_completion);
>
> crtc = &tcrtc->crtc;
> diff --git a/drivers/gpu/drm/tidss/tidss_crtc.h b/drivers/gpu/drm/tidss/tidss_crtc.h
> index 040d1205496b..65df220698f6 100644
> --- a/drivers/gpu/drm/tidss/tidss_crtc.h
> +++ b/drivers/gpu/drm/tidss/tidss_crtc.h
> @@ -20,6 +20,7 @@ struct tidss_crtc {
> struct drm_crtc crtc;
>
> u32 hw_videoport;
> + bool dpi_output;
>
> struct drm_pending_vblank_event *event;
>
> @@ -44,5 +45,6 @@ void tidss_crtc_error_irq(struct drm_crtc *crtc, u64 irqstatus);
>
> struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss,
> u32 hw_videoport,
> - struct drm_plane *primary);
> + struct drm_plane *primary,
> + bool dpi_output);
> #endif
> diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
> index 58d5eb033bdb..c21ac3f51720 100644
> --- a/drivers/gpu/drm/tidss/tidss_dispc.c
> +++ b/drivers/gpu/drm/tidss/tidss_dispc.c
> @@ -448,6 +448,7 @@ static const u16 *dispc_common_regmap;
>
> struct dss_vp_data {
> u32 *gamma_table;
> + bool dpi_output;
> };
>
> struct dispc_device {
> @@ -2770,8 +2771,10 @@ static void dispc_vp_set_color_mgmt(struct dispc_device *dispc,
> }
>
> void dispc_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
> - const struct drm_crtc_state *state, bool newmodeset)
> + const struct drm_crtc_state *state, bool newmodeset,
> + bool dpi_output)
> {
> + dispc->vp_data[hw_videoport].dpi_output = dpi_output;
> dispc_vp_set_default_color(dispc, hw_videoport, 0);
> dispc_vp_set_color_mgmt(dispc, hw_videoport, state, newmodeset);
> }
> diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
> index 739d211d0018..6f53d554259c 100644
> --- a/drivers/gpu/drm/tidss/tidss_dispc.h
> +++ b/drivers/gpu/drm/tidss/tidss_dispc.h
> @@ -131,7 +131,8 @@ void dispc_vp_disable_clk(struct dispc_device *dispc, u32 hw_videoport);
> int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport,
> unsigned long rate);
> void dispc_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
> - const struct drm_crtc_state *state, bool newmodeset);
> + const struct drm_crtc_state *state, bool newmodeset,
> + bool dpi_output);
>
> int dispc_runtime_suspend(struct dispc_device *dispc);
> int dispc_runtime_resume(struct dispc_device *dispc);
> diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c
> index 8bb93194e5ac..bc8b10af9a48 100644
> --- a/drivers/gpu/drm/tidss/tidss_kms.c
> +++ b/drivers/gpu/drm/tidss/tidss_kms.c
> @@ -122,6 +122,50 @@ static const struct drm_mode_config_funcs mode_config_funcs = {
> .atomic_commit = drm_atomic_helper_commit,
> };
>
> +static const char * const tidss_internal_bridge_compatibles[] = {
> + "ti,j721e-dsi",
> +};
> +
> +/*
> + * Detect whether the bridge is internal to the SoC or not. This is needed
> + * to find out whether we are using DPI output (thus no internal bridge).
> + * We detect this via two means:
> + * - If the bridge's of_node has a compatible, compare to known internal values.
> + * - If the bridge is a grand-child of DSS, and has "oldi-transmitters" parent.
> + */
> +static bool tidss_is_bridge_internal(struct tidss_device *tidss,
> + struct drm_bridge *bridge)
> +{
> + struct device_node *parent, *grand_parent;
> + struct property *prop;
> + bool is_internal;
> +
> + if (WARN_ON(!bridge->of_node))
> + return false;
> +
> + prop = of_find_property(bridge->of_node, "compatible", NULL);
> + for (const char *cp = of_prop_next_string(prop, NULL); cp;
> + cp = of_prop_next_string(prop, cp)) {
> + for (unsigned int i = 0;
> + i < ARRAY_SIZE(tidss_internal_bridge_compatibles); ++i) {
> + if (strcmp(cp, tidss_internal_bridge_compatibles[i]) == 0)
> + return true;
> + }
> + }
> +
> + parent = of_get_parent(bridge->of_node);
> + grand_parent = of_get_parent(parent);
> +
> + is_internal = parent && grand_parent &&
> + tidss->dev->of_node == grand_parent &&
> + of_node_name_eq(parent, "oldi-transmitters");
> +
> + of_node_put(grand_parent);
> + of_node_put(parent);
> +
> + return is_internal;
> +}
> +
> static int tidss_dispc_modeset_init(struct tidss_device *tidss)
> {
> struct device *dev = tidss->dev;
> @@ -133,6 +177,7 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss)
> u32 hw_videoport;
> struct drm_bridge *bridge;
> u32 enc_type;
> + bool dpi_output;
> };
>
> const struct dispc_features *feat = tidss->feat;
> @@ -149,6 +194,7 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss)
> struct drm_panel *panel;
> struct drm_bridge *bridge;
> u32 enc_type = DRM_MODE_ENCODER_NONE;
> + bool dpi_output;
> int ret;
>
> ret = drm_of_find_panel_or_bridge(dev->of_node, i, 0,
> @@ -160,6 +206,11 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss)
> return dev_err_probe(dev, ret, "port %d probe failed\n", i);
> }
>
> + if (bridge)
> + dpi_output = !tidss_is_bridge_internal(tidss, bridge);
> + else
> + dpi_output = true;
> +
> if (panel) {
> u32 conn_type;
>
> @@ -199,6 +250,7 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss)
> pipes[num_pipes].hw_videoport = i;
> pipes[num_pipes].bridge = bridge;
> pipes[num_pipes].enc_type = enc_type;
> + pipes[num_pipes].dpi_output = dpi_output;
> num_pipes++;
> }
>
> @@ -224,7 +276,8 @@ static int tidss_dispc_modeset_init(struct tidss_device *tidss)
> tidss->planes[tidss->num_planes++] = &tplane->plane;
>
> tcrtc = tidss_crtc_create(tidss, pipes[i].hw_videoport,
> - &tplane->plane);
> + &tplane->plane,
> + pipes[i].dpi_output);
> if (IS_ERR(tcrtc)) {
> dev_err(tidss->dev, "crtc create failed\n");
> return PTR_ERR(tcrtc);
>
^ permalink raw reply
* Re: [PATCH RFC 3/3] arm64: Add HOTPLUG_PARALLEL support for secondary CPUs
From: Jinjie Ruan @ 2026-06-24 10:00 UTC (permalink / raw)
To: Will Deacon
Cc: Michael Kelley, catalin.marinas@arm.com,
tsbogend@alpha.franken.de, pjw@kernel.org, palmer@dabbelt.com,
aou@eecs.berkeley.edu, alex@ghiti.fr, tglx@kernel.org,
mingo@redhat.com, bp@alien8.de, dave.hansen@linux.intel.com,
hpa@zytor.com, peterz@infradead.org, kees@kernel.org,
nathan@kernel.org, linusw@kernel.org, ojeda@kernel.org,
david.kaplan@amd.com, lukas.bulwahn@redhat.com,
ryan.roberts@arm.com, maz@kernel.org, timothy.hayes@arm.com,
lpieralisi@kernel.org, thuth@redhat.com, oupton@kernel.org,
yeoreum.yun@arm.com, miko.lenczewski@arm.com, broonie@kernel.org,
kevin.brodsky@arm.com, james.clark@linaro.org, tabba@google.com,
mrigendra.chaubey@gmail.com, arnd@arndb.de,
anshuman.khandual@arm.com, x86@kernel.org,
linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, linux-mips@vger.kernel.org,
linux-riscv@lists.infradead.org
In-Reply-To: <ajqYaklhIyvaNLlk@willie-the-truck>
On 6/23/2026 10:30 PM, Will Deacon wrote:
> On Mon, Jun 22, 2026 at 04:06:38PM +0800, Jinjie Ruan wrote:
>> On 6/18/2026 8:21 PM, Will Deacon wrote:
>>> On Mon, Jun 15, 2026 at 04:51:48PM +0800, Jinjie Ruan wrote:
>>>> On 6/12/2026 11:45 PM, Michael Kelley wrote:
>>>>> From: Jinjie Ruan <ruanjinjie@huawei.com> Sent: Thursday, June 11, 2026 6:38 AM
>>>>>>
>>>>>> Support for parallel secondary CPU bringup is already utilized by x86,
>>>>>> MIPS, and RISC-V. This patch brings this capability to the arm64
>>>>>> architecture.
>>>>>>
>>>>>> Rework the global `secondary_data` accessed during early boot into
>>>>>> a per-CPU array. This array maps logical CPU IDs to MPIDR_EL1 values,
>>>>>> enabling the early boot code in head.S to resolve each secondary CPU's
>>>>>> logical ID concurrently.
>>>>>>
>>>>>> To fully enable HOTPLUG_PARALLEL, this patch implements:
>>>>>> 1) An arm64-specific arch_cpuhp_kick_ap_alive() handler.
>>>>>> 2) Callbacks to cpuhp_ap_sync_alive() inside secondary_start_kernel().
>>>>>>
>>>>>> Successfully tested on QEMU ARM64 virt machine (KVM on, 128 vCPUs).
>>>>>>
>>>>>> | test kernel | secondary CPUs boot time |
>>>>>> | --------------------- | -------------------- |
>>>>>> | Without this patch | 155.672 |
>>>>>> | cpuhp.parallel=0 | 62.897 |
>>>>>> | cpuhp.parallel=1 | 166.703 |
>>>>>
>>>>> The last two rows seem mixed up. I would expect parallel=0 to
>>>>> result in a longer boot time.
>>>>
>>>> The results are correct and not mixed up.
>>>>
>>>> Compared to the original non‑HOTPLUG_PARALLEL approach, the advantage of
>>>> cpuhp.parallel=0 lies in its use of cpu_relax(`yield` on arm64) instead
>>>> of the wait_for_completion_timeout() mechanism (which may cause sleep
>>>> and context switching). This significantly reduces the overhead of VM
>>>> exits and context switches in a KVM guest, thereby cutting the secondary
>>>> CPU boot time by more than half.
>>>
>>> I don't think that's a particularly compelling reason to enable this for
>>> arm64, in all honesty. The yield instruction typically doesn't do
>>> anything on actual arm64 silicon, so this probably means that you're
>>> introducing busy-loops which tend to be bad for power and scalability.
>>
>> After updating the implementation in v2, the performance gains are
>> primarily observed on actual hardware.
>
> ... but that's presumably because the secondary cores are busy-looping.
> That's not something we should do during boot. It might be "fast" on
> your machine but it will probably be "hot" as well.
Hi, Will,
I see your point regarding the 'hot boot' issue, which is indeed a valid
concern for power-constrained devices,
My optimization is tailored for servers and continuously powered single
boards, where boot-up speed is much more critical than temporary power
usage during the early boot phase.
Perhaps we could replace the "yield" instruction with "WFE / SEV"
instructions to coordinate the parallel boot of the primary and
secondary cores. This approach would allow the secondary cores to enter
a low-power standby state rather than busy-looping, effectively
preventing the thermal and power issues on battery-constrained machines.
>
>>> I implemented this a while ago [1] but didn't manage to see much in terms
>>> of performance improvement and so I didn't bother to send the patches out
>>
>> As shown in v2 below, on actual hardware, this results in a 40%–60%
>> reduction in boot time.
>>
>> Bringup Time Comparison (ms, lower is better):
>>
>> | Platform | Baseline| P=0 | P=1 | Delta(%)|
>> | --------------------- | ------- | ------- | ------ | ------- |
>> | 64-core ATF QEMU | 2075.8 | 2080.7 | 1653.4 | 20.34% |
>> | 192-core server(HIP12)| 14619.2 | 14619.1 | 8589.4 | 41.21% |
>> | 32-core board | 2776.5 | 2881.0 | 1045.0 | 62.36% |
>>
>> Link:
>> https://lore.kernel.org/all/20260618092444.1316336-5-ruanjinjie@huawei.com/
>
> To be honest, I'm pretty confused with all these numbers. Your first
> table above suggests that parallel boot is *slower* but then this table
> suggests the opposite. However, it also has a QEMU entry despite being
> "on actual hardware". Is that in a VM?
Sorry, there is a little confused. 192-core server(HIP12) and 32-core
board are tested on real hardware, which has 40%–60% reduction in boot time.
>
>>> after talking about it at KVM forum [2]. However, as mentioned at the end
>>> of that talk, it _is_ still useful for confidential VMs using PSCI so
>>> let me dust off my old series and send it out to see what you think.
>>>
>>> It relies on PSCI v0.2, which means we don't need the NR_CPUS size array
>>> for secondary_data and I also have some support for error handling (it
>>> doesn't look like you handle __early_cpu_boot_status properly).
>>
>> I need some time to look closely at your patch. Alternatively, I will
>> integrate your changes, re-test everything on actual hardware, and then
>> send out a revised version.
>
> Please just give me a week or so to rebase my changes and send them out
> for discussion. It'll be interesting to see what numbers you get.
Sounds good! Take your time, and I'm looking forward to your series.
In the meantime, I have just sent out v3 of this patch. While working
closely with your previous code, I identified a few bugs (including the
multi-CPU status trampling issue we discussed) and addressed them in
this new version.I wanted to share v3 with you now so you can easily
review the fixes and potentially integrate them when you rebase your
changes next week. It also includes the updated performance numbers on
my setup for your reference.
Link:
https://lore.kernel.org/all/20260624092537.2916971-1-ruanjinjie@huawei.com/
Looking forward to the discussion!
Best regards,
Jinjie
>
>> It seems that the following patch removing
>> `rcutree_report_cpu_starting()` will reintroduce the original issue as
>> commit ce3d31ad3cac ("arm64/smp: Move
>> rcu_cpu_starting() earlier") soloved.
>>
>> Link:
>> https://web.git.kernel.org/pub/scm/linux/kernel/git/will/linux.git/commit/?h=cpu-hotplug&id=bba4b62f45f2614bf6085e6cd3f233528f85bf26
>>
>> Indeed, I also noticed that the invocation order of
>> rcutree_report_cpu_starting() on arm64 is somewhat suboptimal. It
>> hinders the implementation of parallel bringup on arm64 and could
>> potentially lead to RCU stalls.
>>
>> Link:
>> https://lore.kernel.org/all/20260618092444.1316336-4-ruanjinjie@huawei.com/
>>
>> [ 0.329017] smp: Bringing up secondary CPUs ...
>> [ 0.343628] Detected VIPT I-cache on CPU1
>> [ 0.343788]
>> [ 0.343806] =============================
>> [ 0.343816] WARNING: suspicious RCU usage
>> [ 0.343966] 7.1.0-rc1-g27c1871848a2 #109 Not tainted
>> [ 0.344087] -----------------------------
>> [ 0.344098] kernel/locking/lockdep.c:3801 RCU-list traversed in
>> non-reader section!!
>
> Thanks, I'll look into this.
>
> Will
>
^ permalink raw reply
* Re: [PATCH v3 10/15] drm/tidss: Add support for DPIENABLE bit
From: Swamil Jain @ 2026-06-24 10:46 UTC (permalink / raw)
To: Tomi Valkeinen, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Aradhya Bhatia,
Nishanth Menon, Vignesh Raghavendra, Devarsh Thakkar,
Louis Chauvet
Cc: devicetree, dri-devel, linux-kernel, linux-arm-kernel
In-Reply-To: <20260529-beagley-ai-display-v3-10-7fefdc5d1adf@ideasonboard.com>
On 29-05-2026 14:15, Tomi Valkeinen wrote:
> Many (or even all?) K3 SoCs have DSS VP_CONTROL.DPIENABLE bit described
> in their documentation. This bit controls whether the DPI block is
> enabled, and is set to 1 by default (i.e. DPI is enabled at HW reset).
>
> However, in almost all SoCs the setting does not actually do anything,
> and at the moment the bit is not managed by the driver.
>
> The exception is AM62L, which does have DPIENABLE connected, and
> disabling the DPI block when it is not in use provides power savings.
>
> Let's add a new feature flag for this, 'has_vp_control_dpienable', and
> implement the support. Disable DPIENABLE for all videoports at resume
> time, so that it is 0 by default. Specifically enable and disable it in
> dispc_vp_enable() and dispc_vp_disable() for DPI output.
>
> Tested-by: Swamil Jain <s-jain1@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
Reviewed-by: Swamil Jain <s-jain1@ti.com>
> drivers/gpu/drm/tidss/tidss_dispc.c | 23 +++++++++++++++++++++--
> drivers/gpu/drm/tidss/tidss_dispc.h | 2 ++
> drivers/gpu/drm/tidss/tidss_dispc_regs.h | 1 +
> 3 files changed, 24 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c
> index 08342a9a5e8c..1b8d52f10673 100644
> --- a/drivers/gpu/drm/tidss/tidss_dispc.c
> +++ b/drivers/gpu/drm/tidss/tidss_dispc.c
> @@ -442,6 +442,8 @@ const struct dispc_features dispc_am62l_feats = {
> },
>
> .vid_order = {0},
> +
> + .has_vp_control_dpienable = true,
> };
>
> static const u16 *dispc_common_regmap;
> @@ -1210,6 +1212,11 @@ void dispc_vp_prepare(struct dispc_device *dispc, u32 hw_videoport,
> (!ipc ? DPI0_CLK_CTRL_DATA_CLK_INVDIS : 0) |
> (rf ? DPI0_CLK_CTRL_SYNC_CLK_INVDIS : 0));
> }
> +
> + if (dispc->feat->has_vp_control_dpienable &&
> + dispc->vp_data[hw_videoport].dpi_output)
> + VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1,
> + DISPC_VP_CONTROL_DPIENABLE_MASK);
> }
>
> void dispc_vp_enable(struct dispc_device *dispc, u32 hw_videoport)
> @@ -1226,6 +1233,11 @@ void dispc_vp_disable(struct dispc_device *dispc, u32 hw_videoport)
>
> void dispc_vp_unprepare(struct dispc_device *dispc, u32 hw_videoport)
> {
> + if (dispc->feat->has_vp_control_dpienable &&
> + dispc->vp_data[hw_videoport].dpi_output)
> + VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 0,
> + DISPC_VP_CONTROL_DPIENABLE_MASK);
> +
> if (dispc->feat->vp_bus_type[hw_videoport] == DISPC_VP_OLDI_AM65X) {
> dispc_vp_write(dispc, hw_videoport, DISPC_VP_DSS_OLDI_CFG, 0);
>
> @@ -2445,10 +2457,17 @@ static void dispc_vp_init(struct dispc_device *dispc)
>
> dev_dbg(dispc->dev, "%s()\n", __func__);
>
> - /* Enable the gamma Shadow bit-field for all VPs*/
> - for (i = 0; i < dispc->feat->num_vps; i++)
> + for (i = 0; i < dispc->feat->num_vps; i++) {
> + /* Enable the gamma Shadow bit-field for all VPs*/
> VP_REG_FLD_MOD(dispc, i, DISPC_VP_CONFIG, 1,
> DISPC_VP_CONFIG_GAMMAENABLE_MASK);
> +
> + if (dispc->feat->has_vp_control_dpienable) {
> + /* Disable DPIENABLE for all VPs */
> + VP_REG_FLD_MOD(dispc, i, DISPC_VP_CONTROL, 0,
> + DISPC_VP_CONTROL_DPIENABLE_MASK);
> + }
> + }
> }
>
> static void dispc_initial_config(struct dispc_device *dispc)
> diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h
> index 6f53d554259c..0fbfb86adfbf 100644
> --- a/drivers/gpu/drm/tidss/tidss_dispc.h
> +++ b/drivers/gpu/drm/tidss/tidss_dispc.h
> @@ -92,6 +92,8 @@ struct dispc_features {
> u32 num_vids;
> struct dispc_vid_info vid_info[TIDSS_MAX_PLANES];
> u32 vid_order[TIDSS_MAX_PLANES];
> + /* The DSS has VP_CONTROL.DPIENABLE bit */
> + bool has_vp_control_dpienable;
> };
>
> extern const struct dispc_features dispc_k2g_feats;
> diff --git a/drivers/gpu/drm/tidss/tidss_dispc_regs.h b/drivers/gpu/drm/tidss/tidss_dispc_regs.h
> index 4cdde24d8372..4246c72efdd5 100644
> --- a/drivers/gpu/drm/tidss/tidss_dispc_regs.h
> +++ b/drivers/gpu/drm/tidss/tidss_dispc_regs.h
> @@ -230,6 +230,7 @@ enum dispc_common_regs {
>
> #define DISPC_VP_CONTROL 0x4
> #define DISPC_VP_CONTROL_DATALINES_MASK GENMASK(10, 8)
> +#define DISPC_VP_CONTROL_DPIENABLE_MASK GENMASK(6, 6)
> #define DISPC_VP_CONTROL_GOBIT_MASK GENMASK(5, 5)
> #define DISPC_VP_CONTROL_ENABLE_MASK GENMASK(0, 0)
>
>
^ permalink raw reply
* Re: [PATCH v3 11/15] drm/tidss: oldi: Fix OLDI signal polarities
From: Swamil Jain @ 2026-06-24 10:47 UTC (permalink / raw)
To: Tomi Valkeinen, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Aradhya Bhatia,
Nishanth Menon, Vignesh Raghavendra, Devarsh Thakkar,
Louis Chauvet
Cc: devicetree, dri-devel, linux-kernel, linux-arm-kernel
In-Reply-To: <20260529-beagley-ai-display-v3-11-7fefdc5d1adf@ideasonboard.com>
On 29-05-2026 14:15, Tomi Valkeinen wrote:
> OLDI has a few issues with how it handles the signal polarities:
>
> - It always sets OLDI_DEPOL, which means DE active low
> - It sets DRM_BUS_FLAG_DE_HIGH in struct drm_bridge_timings, i.e.
> reverse to the OLDI_DEPOL
> - It sets DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE in struct drm_bridge_timings,
> but the TRM says "The DSS interface is clocked on the rising edge of
> OLDI_FWD_P_CLK pixel clock", which I read as "OLDI samples on rising
> edge".
> - But the defined drm_bridge_timings is not actually used anywhere, even
> if it is set to bridge->timings, so the bus flags are just ignored.
>
> However, based on my testing, OLDI_DEPOL bit or the edge on which data
> and syncs are driven doesn't seem to affect the OLDI output. Possibly
> it's not as robust, but I did not see any effect with an oscilloscope.
>
> However, the code is still quite broken, so let's fix it:
> - Remove drm_bridge_timings
> - Set the correct input_bus_cfg.flags in tidss_oldi_atomic_check()
> - Set OLDI_DEPOL based on the DE bus flag
>
> Tested-by: Swamil Jain <s-jain1@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
Reviewed-by: Swamil Jain <s-jain1@ti.com>
> drivers/gpu/drm/tidss/tidss_oldi.c | 38 ++++++++++++++++++++++++++++----------
> 1 file changed, 28 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/gpu/drm/tidss/tidss_oldi.c b/drivers/gpu/drm/tidss/tidss_oldi.c
> index 17c535bfa057..e925ddaa4fd6 100644
> --- a/drivers/gpu/drm/tidss/tidss_oldi.c
> +++ b/drivers/gpu/drm/tidss/tidss_oldi.c
> @@ -164,7 +164,8 @@ static void tidss_oldi_tx_power(struct tidss_oldi *oldi, bool enable)
> regmap_update_bits(oldi->io_ctrl, OLDI_PD_CTRL, mask, enable ? 0 : mask);
> }
>
> -static int tidss_oldi_config(struct tidss_oldi *oldi)
> +static int tidss_oldi_config(struct tidss_oldi *oldi,
> + struct drm_bridge_state *bridge_state)
> {
> const struct oldi_bus_format *bus_fmt = NULL;
> u32 oldi_cfg = 0;
> @@ -183,7 +184,8 @@ static int tidss_oldi_config(struct tidss_oldi *oldi)
> "OLDI%u: DSS port width %d not supported\n",
> oldi->oldi_instance, bus_fmt->data_width);
>
> - oldi_cfg |= OLDI_DEPOL;
> + if (bridge_state->input_bus_cfg.flags & DRM_BUS_FLAG_DE_LOW)
> + oldi_cfg |= OLDI_DEPOL; /* 1 = active low */
>
> oldi_cfg = (oldi_cfg & (~OLDI_MAP)) | (bus_fmt->oldi_mode_reg_val << 1);
>
> @@ -220,6 +222,22 @@ static int tidss_oldi_config(struct tidss_oldi *oldi)
> return ret;
> }
>
> +static int tidss_oldi_atomic_check(struct drm_bridge *bridge,
> + struct drm_bridge_state *bridge_state,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state)
> +{
> + bridge_state->input_bus_cfg.flags &=
> + ~(DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
> + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE);
> +
> + bridge_state->input_bus_cfg.flags |=
> + DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE |
> + DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE;
> +
> + return 0;
> +}
> +
> static void tidss_oldi_atomic_pre_enable(struct drm_bridge *bridge,
> struct drm_atomic_state *state)
> {
> @@ -228,6 +246,7 @@ static void tidss_oldi_atomic_pre_enable(struct drm_bridge *bridge,
> struct drm_connector_state *conn_state;
> struct drm_crtc_state *crtc_state;
> struct drm_display_mode *mode;
> + struct drm_bridge_state *bridge_state;
>
> if (oldi->link_type == OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK)
> return;
> @@ -245,10 +264,14 @@ static void tidss_oldi_atomic_pre_enable(struct drm_bridge *bridge,
> if (WARN_ON(!crtc_state))
> return;
>
> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> + if (WARN_ON(!bridge_state))
> + return;
> +
> mode = &crtc_state->adjusted_mode;
>
> /* Configure the OLDI params*/
> - tidss_oldi_config(oldi);
> + tidss_oldi_config(oldi, bridge_state);
>
> /* Set the OLDI serial clock (7 times the pixel clock) */
> tidss_oldi_set_serial_clk(oldi, mode->clock * 7 * 1000);
> @@ -329,7 +352,8 @@ tidss_oldi_mode_valid(struct drm_bridge *bridge,
> }
>
> static const struct drm_bridge_funcs tidss_oldi_bridge_funcs = {
> - .attach = tidss_oldi_bridge_attach,
> + .attach = tidss_oldi_bridge_attach,
> + .atomic_check = tidss_oldi_atomic_check,
> .atomic_pre_enable = tidss_oldi_atomic_pre_enable,
> .atomic_post_disable = tidss_oldi_atomic_post_disable,
> .atomic_get_input_bus_fmts = tidss_oldi_atomic_get_input_bus_fmts,
> @@ -440,11 +464,6 @@ static int get_parent_dss_vp(struct device_node *oldi_tx, u32 *parent_vp)
> return -ENODEV;
> }
>
> -static const struct drm_bridge_timings default_tidss_oldi_timings = {
> - .input_bus_flags = DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
> - | DRM_BUS_FLAG_DE_HIGH,
> -};
> -
> void tidss_oldi_deinit(struct tidss_device *tidss)
> {
> for (int i = 0; i < tidss->num_oldis; i++) {
> @@ -598,7 +617,6 @@ int tidss_oldi_init(struct tidss_device *tidss)
> /* Register the bridge. */
> oldi->bridge.of_node = child;
> oldi->bridge.driver_private = oldi;
> - oldi->bridge.timings = &default_tidss_oldi_timings;
>
> tidss->oldis[tidss->num_oldis++] = oldi;
> tidss->is_ext_vp_clk[oldi->parent_vp] = true;
>
^ permalink raw reply
* Re: [PATCH v3 12/15] drm/tidss: oldi: Convert OLDI to an aux driver
From: Swamil Jain @ 2026-06-24 10:49 UTC (permalink / raw)
To: Tomi Valkeinen, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Lee Jones, Aradhya Bhatia,
Nishanth Menon, Vignesh Raghavendra, Devarsh Thakkar,
Louis Chauvet
Cc: devicetree, dri-devel, linux-kernel, linux-arm-kernel
In-Reply-To: <20260529-beagley-ai-display-v3-12-7fefdc5d1adf@ideasonboard.com>
On 29-05-2026 14:15, Tomi Valkeinen wrote:
> Currently in the DT, OLDI is defined in child nodes under the DSS node.
> The tidss driver will parse the DT, and create DRM bridges for the
> OLDIs, and there are no Linux devices for the OLDIs.
>
> On new SoCs the OLDIs have their own power-domains which we need to
> control. The cleanest way to do this in DT is to add the PDs to the OLDI
> nodes. But this means the OLDI bridge code would somehow have to
> manually manage the PDs, the PDs not being under a Linux device, and
> there isn't much support for that kind of setup in the PD framework.
>
> A solution to this is to convert the OLDI to an auxiliary device/driver,
> created by tidss:
>
> - At module load time the tidss module will, in addition to registering
> the tidss DRM driver, register an oldi auxiliary driver.
> - At probe time tidss will parse the DT, and create an auxiliary device
> for each OLDI.
>
> The aux driver will probe, and as its of_node points to the OLDI node
> containing the PD, the driver framework will take care of enabling and
> disabling the PD when OLDI is used ("used" as in pm_runtime context).
>
> Tested-by: Swamil Jain <s-jain1@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
Reviewed-by: Swamil Jain <s-jain1@ti.com>
> drivers/gpu/drm/tidss/tidss_drv.c | 51 +++-
> drivers/gpu/drm/tidss/tidss_drv.h | 5 +-
> drivers/gpu/drm/tidss/tidss_oldi.c | 502 ++++++++++++++++++++++++++-----------
> drivers/gpu/drm/tidss/tidss_oldi.h | 7 +-
> 4 files changed, 405 insertions(+), 160 deletions(-)
>
> diff --git a/drivers/gpu/drm/tidss/tidss_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c
> index 5cb3e746aeb3..aef945101be4 100644
> --- a/drivers/gpu/drm/tidss/tidss_drv.c
> +++ b/drivers/gpu/drm/tidss/tidss_drv.c
> @@ -133,10 +133,6 @@ static int tidss_probe(struct platform_device *pdev)
> return ret;
> }
>
> - ret = tidss_oldi_init(tidss);
> - if (ret)
> - return dev_err_probe(dev, ret, "failed to init OLDI\n");
> -
> pm_runtime_enable(dev);
>
> pm_runtime_set_autosuspend_delay(dev, 1000);
> @@ -147,24 +143,30 @@ static int tidss_probe(struct platform_device *pdev)
> dispc_runtime_resume(tidss->dispc);
> #endif
>
> + ret = tidss_oldi_create_devices(tidss);
> + if (ret) {
> + dev_err_probe(dev, ret, "failed to create OLDI devices\n");
> + goto err_runtime_suspend;
> + }
> +
> ret = tidss_modeset_init(tidss);
> if (ret < 0) {
> if (ret != -EPROBE_DEFER)
> dev_err(dev, "failed to init DRM/KMS (%d)\n", ret);
> - goto err_runtime_suspend;
> + goto err_destroy_oldis;
> }
>
> irq = platform_get_irq(pdev, 0);
> if (irq < 0) {
> ret = irq;
> - goto err_runtime_suspend;
> + goto err_destroy_oldis;
> }
> tidss->irq = irq;
>
> ret = tidss_irq_install(ddev, irq);
> if (ret) {
> dev_err(dev, "tidss_irq_install failed: %d\n", ret);
> - goto err_runtime_suspend;
> + goto err_destroy_oldis;
> }
>
> drm_kms_helper_poll_init(ddev);
> @@ -194,6 +196,9 @@ static int tidss_probe(struct platform_device *pdev)
> err_irq_uninstall:
> tidss_irq_uninstall(ddev);
>
> +err_destroy_oldis:
> + tidss_oldi_destroy_devices(tidss);
> +
> err_runtime_suspend:
> #ifndef CONFIG_PM
> dispc_runtime_suspend(tidss->dispc);
> @@ -201,8 +206,6 @@ static int tidss_probe(struct platform_device *pdev)
> pm_runtime_dont_use_autosuspend(dev);
> pm_runtime_disable(dev);
>
> - tidss_oldi_deinit(tidss);
> -
> return ret;
> }
>
> @@ -218,6 +221,8 @@ static void tidss_remove(struct platform_device *pdev)
>
> tidss_irq_uninstall(ddev);
>
> + tidss_oldi_destroy_devices(tidss);
> +
> #ifndef CONFIG_PM
> /* If we don't have PM, we need to call suspend manually */
> dispc_runtime_suspend(tidss->dispc);
> @@ -225,8 +230,6 @@ static void tidss_remove(struct platform_device *pdev)
> pm_runtime_dont_use_autosuspend(dev);
> pm_runtime_disable(dev);
>
> - tidss_oldi_deinit(tidss);
> -
> /* devm allocated dispc goes away with the dev so mark it NULL */
> dispc_remove(tidss);
>
> @@ -262,7 +265,31 @@ static struct platform_driver tidss_platform_driver = {
> },
> };
>
> -drm_module_platform_driver(tidss_platform_driver);
> +static int __init tidss_platform_driver_init(void)
> +{
> + int ret;
> +
> + ret = tidss_oldi_register_driver();
> + if (ret)
> + return ret;
> +
> + ret = drm_platform_driver_register(&tidss_platform_driver);
> + if (ret) {
> + tidss_oldi_unregister_driver();
> + return ret;
> + }
> +
> + return 0;
> +}
> +module_init(tidss_platform_driver_init);
> +
> +static void __exit tidss_platform_driver_exit(void)
> +{
> + platform_driver_unregister(&tidss_platform_driver);
> + tidss_oldi_unregister_driver();
> +}
> +module_exit(tidss_platform_driver_exit);
> +
>
> MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
> MODULE_DESCRIPTION("TI Keystone DSS Driver");
> diff --git a/drivers/gpu/drm/tidss/tidss_drv.h b/drivers/gpu/drm/tidss/tidss_drv.h
> index e1c1f41d8b4b..d3dba639b278 100644
> --- a/drivers/gpu/drm/tidss/tidss_drv.h
> +++ b/drivers/gpu/drm/tidss/tidss_drv.h
> @@ -16,7 +16,8 @@
> #define TIDSS_MAX_OLDI_TXES 2
>
> typedef u32 dispc_irq_t;
> -struct tidss_oldi;
> +
> +struct auxiliary_device;
>
> struct tidss_device {
> struct drm_device ddev; /* DRM device for DSS */
> @@ -34,7 +35,7 @@ struct tidss_device {
> struct drm_plane *planes[TIDSS_MAX_PLANES];
>
> unsigned int num_oldis;
> - struct tidss_oldi *oldis[TIDSS_MAX_OLDI_TXES];
> + struct auxiliary_device *oldis[TIDSS_MAX_OLDI_TXES];
>
> unsigned int irq;
>
> diff --git a/drivers/gpu/drm/tidss/tidss_oldi.c b/drivers/gpu/drm/tidss/tidss_oldi.c
> index e925ddaa4fd6..188f31b57c6a 100644
> --- a/drivers/gpu/drm/tidss/tidss_oldi.c
> +++ b/drivers/gpu/drm/tidss/tidss_oldi.c
> @@ -5,11 +5,13 @@
> * Aradhya Bhatia <a-bhatia1@ti.com>
> */
>
> +#include <linux/auxiliary_bus.h>
> #include <linux/clk.h>
> #include <linux/of.h>
> #include <linux/of_graph.h>
> #include <linux/mfd/syscon.h>
> #include <linux/media-bus-format.h>
> +#include <linux/pm_runtime.h>
> #include <linux/regmap.h>
>
> #include <drm/drm_atomic_helper.h>
> @@ -20,6 +22,12 @@
> #include "tidss_dispc_regs.h"
> #include "tidss_oldi.h"
>
> +static DEFINE_IDA(tidss_oldi_ida);
> +
> +struct tidss_oldi_platform_data {
> + struct tidss_device *tidss;
> +};
> +
> struct tidss_oldi {
> struct tidss_device *tidss;
> struct device *dev;
> @@ -251,6 +259,8 @@ static void tidss_oldi_atomic_pre_enable(struct drm_bridge *bridge,
> if (oldi->link_type == OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK)
> return;
>
> + WARN_ON(pm_runtime_get_sync(oldi->dev) < 0);
> +
> connector = drm_atomic_get_new_connector_for_encoder(state,
> bridge->encoder);
> if (WARN_ON(!connector))
> @@ -296,6 +306,8 @@ static void tidss_oldi_atomic_post_disable(struct drm_bridge *bridge,
>
> /* Clear OLDI Config */
> tidss_disable_oldi(oldi->tidss, oldi->parent_vp);
> +
> + pm_runtime_put_autosuspend(oldi->dev);
> }
>
> #define MAX_INPUT_SEL_FORMATS 1
> @@ -383,12 +395,16 @@ static int get_oldi_mode(struct device_node *oldi_tx, int *companion_instance)
> */
> return OLDI_MODE_SINGLE_LINK;
>
> - if (of_property_read_u32(companion, "reg", &companion_reg))
> + if (of_property_read_u32(companion, "reg", &companion_reg)) {
> + of_node_put(companion);
> return OLDI_MODE_UNSUPPORTED;
> + }
>
> - if (companion_reg > (TIDSS_MAX_OLDI_TXES - 1))
> + if (companion_reg > (TIDSS_MAX_OLDI_TXES - 1)) {
> /* Invalid companion OLDI reg value. */
> + of_node_put(companion);
> return OLDI_MODE_UNSUPPORTED;
> + }
>
> *companion_instance = (int)companion_reg;
>
> @@ -464,174 +480,372 @@ static int get_parent_dss_vp(struct device_node *oldi_tx, u32 *parent_vp)
> return -ENODEV;
> }
>
> -void tidss_oldi_deinit(struct tidss_device *tidss)
> +static int tidss_oldi_probe(struct auxiliary_device *auxdev,
> + const struct auxiliary_device_id *id)
> {
> - for (int i = 0; i < tidss->num_oldis; i++) {
> - if (tidss->oldis[i]) {
> - drm_bridge_remove(&tidss->oldis[i]->bridge);
> - tidss->is_ext_vp_clk[tidss->oldis[i]->parent_vp] = false;
> - tidss->oldis[i] = NULL;
> + struct device *dev = &auxdev->dev;
> + struct tidss_oldi_platform_data *oldi_pdata = dev_get_platdata(dev);
> + struct tidss_device *tidss = oldi_pdata->tidss;
> + struct device_node *node = auxdev->dev.of_node;
> + enum tidss_oldi_link_type link_type = OLDI_MODE_UNSUPPORTED;
> + int companion_instance = -1;
> + struct drm_bridge *bridge;
> + struct device_link *link;
> + struct tidss_oldi *oldi;
> + u32 oldi_instance;
> + u32 parent_vp;
> + int ret;
> +
> + ret = of_property_read_u32(node, "reg", &oldi_instance);
> + if (ret)
> + return ret;
> +
> + ret = get_parent_dss_vp(node, &parent_vp);
> + if (ret)
> + return ret;
> +
> + /*
> + * Now that it's confirmed that OLDI is connected with DSS,
> + * let's continue getting the OLDI sinks ahead and other OLDI
> + * properties.
> + */
> + bridge = devm_drm_of_get_bridge(dev, node, OLDI_OUTPUT_PORT, 0);
> + if (IS_ERR(bridge)) {
> + /*
> + * Either there was no OLDI sink in the devicetree, or the OLDI
> + * sink has not been added yet. In any case, return.
> + */
> + return dev_err_probe(dev, PTR_ERR(bridge),
> + "no panel/bridge for OLDI%u.\n",
> + oldi_instance);
> + }
> +
> + link_type = get_oldi_mode(node, &companion_instance);
> + if (link_type == OLDI_MODE_UNSUPPORTED) {
> + return dev_err_probe(dev, -EINVAL,
> + "OLDI%u: Unsupported OLDI connection.\n",
> + oldi_instance);
> + } else if ((link_type == OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) ||
> + (link_type == OLDI_MODE_CLONE_SINGLE_LINK)) {
> + /*
> + * The OLDI driver cannot support OLDI clone mode properly at
> + * present. The clone mode requires 2 working encoder-bridge
> + * pipelines, generating from the same crtc. The DRM framework
> + * does not support this at present. If there were to be, say, 2
> + * OLDI sink bridges each connected to an OLDI TXes, they
> + * couldn't both be supported simultaneously. This driver still
> + * has some code pertaining to OLDI clone mode configuration in
> + * DSS hardware for future, when there is a better
> + * infrastructure in the DRM framework to support 2
> + * encoder-bridge pipelines simultaneously. Till that time, this
> + * driver shall error out if it detects a clone mode
> + * configuration.
> + */
> + return dev_err_probe(dev, -EOPNOTSUPP,
> + "The OLDI driver does not support Clone Mode at present.\n");
> + }
> +
> + oldi = devm_drm_bridge_alloc(dev, struct tidss_oldi, bridge,
> + &tidss_oldi_bridge_funcs);
> + if (IS_ERR(oldi))
> + return PTR_ERR(oldi);
> +
> + oldi->parent_vp = parent_vp;
> + oldi->oldi_instance = oldi_instance;
> + oldi->companion_instance = companion_instance;
> + oldi->link_type = link_type;
> + oldi->dev = dev;
> + oldi->next_bridge = bridge;
> + oldi->tidss = tidss;
> +
> + auxiliary_set_drvdata(auxdev, oldi);
> +
> + /*
> + * Only the primary OLDI needs to reference the io-ctrl system
> + * registers, and the serial clock.
> + * We don't require a check for secondary OLDI in dual-link mode
> + * because the driver will not create a drm_bridge instance.
> + * But the driver will need to create a drm_bridge instance,
> + * for secondary OLDI in clone mode (once it is supported).
> + */
> + if (link_type != OLDI_MODE_SECONDARY_DUAL_LINK &&
> + link_type != OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) {
> + oldi->io_ctrl = syscon_regmap_lookup_by_phandle(node,
> + "ti,oldi-io-ctrl");
> + if (IS_ERR(oldi->io_ctrl)) {
> + return dev_err_probe(oldi->dev, PTR_ERR(oldi->io_ctrl),
> + "OLDI%u: syscon_regmap_lookup_by_phandle failed.\n",
> + oldi_instance);
> + }
> +
> + oldi->serial = of_clk_get_by_name(node, "serial");
> + if (IS_ERR(oldi->serial)) {
> + return dev_err_probe(oldi->dev, PTR_ERR(oldi->serial),
> + "OLDI%u: Failed to get serial clock.\n",
> + oldi_instance);
> }
> }
> +
> + if (link_type != OLDI_MODE_SECONDARY_DUAL_LINK) {
> + /* Register the bridge. */
> + oldi->bridge.of_node = node;
> + oldi->bridge.driver_private = oldi;
> +
> + tidss->is_ext_vp_clk[oldi->parent_vp] = true;
> +
> + drm_bridge_add(&oldi->bridge);
> + }
> +
> + link = device_link_add(&auxdev->dev, tidss->dev,
> + DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER);
> + if (!link) {
> + ret = -EINVAL;
> + goto err_bridge_remove;
> + }
> +
> + pm_runtime_enable(dev);
> + pm_runtime_set_autosuspend_delay(dev, 500);
> + pm_runtime_use_autosuspend(dev);
> +
> + return 0;
> +
> +err_bridge_remove:
> + if (link_type != OLDI_MODE_SECONDARY_DUAL_LINK) {
> + drm_bridge_remove(&oldi->bridge);
> + tidss->is_ext_vp_clk[oldi->parent_vp] = false;
> + }
> +
> + clk_put(oldi->serial);
> +
> + return ret;
> }
>
> -int tidss_oldi_init(struct tidss_device *tidss)
> +static void tidss_oldi_remove(struct auxiliary_device *auxdev)
> {
> - struct tidss_oldi *oldi;
> - struct device_node *child;
> - struct drm_bridge *bridge;
> - u32 parent_vp, oldi_instance;
> - int companion_instance = -1;
> - enum tidss_oldi_link_type link_type = OLDI_MODE_UNSUPPORTED;
> - struct device_node *oldi_parent;
> - int ret = 0;
> + struct tidss_oldi *oldi = auxiliary_get_drvdata(auxdev);
> + struct tidss_device *tidss = oldi->tidss;
> + struct device *dev = &auxdev->dev;
>
> - tidss->num_oldis = 0;
> + pm_runtime_dont_use_autosuspend(dev);
> + pm_runtime_disable(dev);
>
> - oldi_parent = of_get_child_by_name(tidss->dev->of_node, "oldi-transmitters");
> - if (!oldi_parent)
> - /* Return gracefully */
> - return 0;
> + if (oldi->link_type != OLDI_MODE_SECONDARY_DUAL_LINK) {
> + drm_bridge_remove(&oldi->bridge);
> +
> + tidss->is_ext_vp_clk[oldi->parent_vp] = false;
> + }
> +
> + clk_put(oldi->serial);
> +}
> +
> +static const struct auxiliary_device_id tidss_oldi_aux_id_table[] = {
> + { .name = "tidss.oldi" },
> + {}
> +};
> +
> +static struct auxiliary_driver oldi_aux_driver = {
> + .name = "oldi",
> + .probe = tidss_oldi_probe,
> + .remove = tidss_oldi_remove,
> + .id_table = tidss_oldi_aux_id_table,
> +};
> +
> +static void tidss_oldi_aux_device_release(struct device *dev)
> +{
> + struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
> +
> + ida_free(&tidss_oldi_ida, auxdev->id);
> +
> + of_node_put(auxdev->dev.of_node);
>
> - for_each_available_child_of_node(oldi_parent, child) {
> - ret = get_parent_dss_vp(child, &parent_vp);
> - if (ret) {
> - if (ret == -ENODEV) {
> - /*
> - * ENODEV means that this particular OLDI node
> - * is not connected with the DSS, which is not
> - * a harmful case. There could be another OLDI
> - * which may still be connected.
> - * Continue to search for that.
> - */
> - continue;
> + kfree(auxdev->dev.platform_data);
> + kfree(auxdev);
> +}
> +
> +static struct auxiliary_device *
> +tidss_oldi_create_device(struct tidss_device *tidss,
> + struct device_node *oldi_tx)
> +{
> + struct tidss_oldi_platform_data *oldi_pdata;
> + struct auxiliary_device *companion_auxdev;
> + struct auxiliary_device *auxdev;
> + struct device_link *link = NULL;
> + u32 oldi_aux_id;
> + int ret;
> +
> + /*
> + * Allocate the ID first, so that we get a lower ID for the primary
> + * OLDI, instead of the companion grabbing it in the call below. Note
> + * that the ID allocated here often matches the OLDI hardware index,
> + * but not always.
> + * The OLDI hardware index cannot be used as an ID, as, say, OLDI 1 on
> + * two DSS instances would produce the exact same device name
> + * ("tidss.oldi.1").
> + */
> + ret = ida_alloc(&tidss_oldi_ida, GFP_KERNEL);
> + if (ret < 0)
> + return ERR_PTR(ret);
> +
> + oldi_aux_id = ret;
> +
> + companion_auxdev = NULL;
> +
> + /*
> + * If this is the primary OLDI and there is a companion, create the
> + * auxdev for the secondary OLDI first, as the secondary will act as
> + * a supplier for the primary OLDI.
> + */
> + if (!of_property_read_bool(oldi_tx, "ti,secondary-oldi")) {
> + struct device_node *companion_node;
> +
> + companion_node = of_parse_phandle(oldi_tx, "ti,companion-oldi", 0);
> + if (companion_node) {
> + companion_auxdev =
> + tidss_oldi_create_device(tidss, companion_node);
> +
> + of_node_put(companion_node);
> +
> + if (IS_ERR(companion_auxdev)) {
> + dev_err(tidss->dev,
> + "Failed to create secondary oldi device\n");
> + ret = PTR_ERR(companion_auxdev);
> + goto err_free_ida;
> }
> - goto err_put_node;
> }
> + }
>
> - ret = of_property_read_u32(child, "reg", &oldi_instance);
> - if (ret)
> - goto err_put_node;
> + oldi_pdata = kzalloc_obj(*oldi_pdata);
> + if (!oldi_pdata) {
> + ret = -ENOMEM;
> + goto err_free_ida;
> + }
>
> - /*
> - * Now that it's confirmed that OLDI is connected with DSS,
> - * let's continue getting the OLDI sinks ahead and other OLDI
> - * properties.
> - */
> - bridge = devm_drm_of_get_bridge(tidss->dev, child,
> - OLDI_OUTPUT_PORT, 0);
> - if (IS_ERR(bridge)) {
> - /*
> - * Either there was no OLDI sink in the devicetree, or
> - * the OLDI sink has not been added yet. In any case,
> - * return.
> - * We don't want to have an OLDI node connected to DSS
> - * but not to any sink.
> - */
> - ret = dev_err_probe(tidss->dev, PTR_ERR(bridge),
> - "no panel/bridge for OLDI%u.\n",
> - oldi_instance);
> - goto err_put_node;
> - }
> + oldi_pdata->tidss = tidss;
>
> - link_type = get_oldi_mode(child, &companion_instance);
> - if (link_type == OLDI_MODE_UNSUPPORTED) {
> - ret = dev_err_probe(tidss->dev, -EINVAL,
> - "OLDI%u: Unsupported OLDI connection.\n",
> - oldi_instance);
> - goto err_put_node;
> - } else if ((link_type == OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) ||
> - (link_type == OLDI_MODE_CLONE_SINGLE_LINK)) {
> - /*
> - * The OLDI driver cannot support OLDI clone mode
> - * properly at present.
> - * The clone mode requires 2 working encoder-bridge
> - * pipelines, generating from the same crtc. The DRM
> - * framework does not support this at present. If
> - * there were to be, say, 2 OLDI sink bridges each
> - * connected to an OLDI TXes, they couldn't both be
> - * supported simultaneously.
> - * This driver still has some code pertaining to OLDI
> - * clone mode configuration in DSS hardware for future,
> - * when there is a better infrastructure in the DRM
> - * framework to support 2 encoder-bridge pipelines
> - * simultaneously.
> - * Till that time, this driver shall error out if it
> - * detects a clone mode configuration.
> - */
> - ret = dev_err_probe(tidss->dev, -EOPNOTSUPP,
> - "The OLDI driver does not support Clone Mode at present.\n");
> - goto err_put_node;
> - } else if (link_type == OLDI_MODE_SECONDARY_DUAL_LINK) {
> - /*
> - * This is the secondary OLDI node, which serves as a
> - * companion to the primary OLDI, when it is configured
> - * for the dual-link mode. Since the primary OLDI will
> - * be a part of bridge chain, no need to put this one
> - * too. Continue onto the next OLDI node.
> - */
> - continue;
> - }
> + auxdev = kzalloc_obj(*auxdev);
> + if (!auxdev) {
> + ret = -ENOMEM;
> + goto err_free_pdata;
> + }
> +
> + *auxdev = (struct auxiliary_device) {
> + .name = "oldi",
> + .id = oldi_aux_id,
> + .dev = {
> + .parent = tidss->dev,
> + .of_node = of_node_get(oldi_tx),
> + .release = tidss_oldi_aux_device_release,
> + .platform_data = oldi_pdata,
> + },
> + };
> +
> + ret = auxiliary_device_init(auxdev);
> + if (ret) {
> + dev_err(tidss->dev, "OLDI auxiliary_device_init failed: %d\n",
> + ret);
> + goto err_free_auxdev;
> + }
>
> - oldi = devm_drm_bridge_alloc(tidss->dev, struct tidss_oldi, bridge,
> - &tidss_oldi_bridge_funcs);
> - if (IS_ERR(oldi)) {
> - ret = PTR_ERR(oldi);
> - goto err_put_node;
> + /*
> + * Create a device-link between the primary and the secondary, so that
> + * the secondary will be powered on when the primary is used.
> + */
> + if (companion_auxdev) {
> + link = device_link_add(&auxdev->dev, &companion_auxdev->dev,
> + DL_FLAG_PM_RUNTIME |
> + DL_FLAG_AUTOREMOVE_CONSUMER |
> + DL_FLAG_AUTOREMOVE_SUPPLIER);
> + if (!link) {
> + dev_err(tidss->dev,
> + "device_link_add failed between primary and secondary OLDI\n");
> + ret = -EINVAL;
> + goto err_uninit_auxdev;
> }
> + }
>
> - oldi->parent_vp = parent_vp;
> - oldi->oldi_instance = oldi_instance;
> - oldi->companion_instance = companion_instance;
> - oldi->link_type = link_type;
> - oldi->dev = tidss->dev;
> - oldi->next_bridge = bridge;
> + ret = auxiliary_device_add(auxdev);
> + if (ret) {
> + dev_err(tidss->dev, "OLDI auxiliary_device_add failed: %d\n",
> + ret);
> + goto err_link_del;
> + }
>
> - /*
> - * Only the primary OLDI needs to reference the io-ctrl system
> - * registers, and the serial clock.
> - * We don't require a check for secondary OLDI in dual-link mode
> - * because the driver will not create a drm_bridge instance.
> - * But the driver will need to create a drm_bridge instance,
> - * for secondary OLDI in clone mode (once it is supported).
> - */
> - if (link_type != OLDI_MODE_SECONDARY_CLONE_SINGLE_LINK) {
> - oldi->io_ctrl = syscon_regmap_lookup_by_phandle(child,
> - "ti,oldi-io-ctrl");
> - if (IS_ERR(oldi->io_ctrl)) {
> - ret = dev_err_probe(oldi->dev, PTR_ERR(oldi->io_ctrl),
> - "OLDI%u: syscon_regmap_lookup_by_phandle failed.\n",
> - oldi_instance);
> - goto err_put_node;
> - }
> + tidss->oldis[tidss->num_oldis++] = auxdev;
>
> - oldi->serial = of_clk_get_by_name(child, "serial");
> - if (IS_ERR(oldi->serial)) {
> - ret = dev_err_probe(oldi->dev, PTR_ERR(oldi->serial),
> - "OLDI%u: Failed to get serial clock.\n",
> - oldi_instance);
> - goto err_put_node;
> - }
> - }
> + return auxdev;
>
> - /* Register the bridge. */
> - oldi->bridge.of_node = child;
> - oldi->bridge.driver_private = oldi;
> +err_link_del:
> + if (link)
> + device_link_del(link);
> +err_uninit_auxdev:
> + auxiliary_device_uninit(auxdev);
> + /* return here, as the rest are done in auxdev's release */
> + return ERR_PTR(ret);
>
> - tidss->oldis[tidss->num_oldis++] = oldi;
> - tidss->is_ext_vp_clk[oldi->parent_vp] = true;
> - oldi->tidss = tidss;
> +err_free_auxdev:
> + kfree(auxdev);
> +err_free_pdata:
> + kfree(oldi_pdata);
> +err_free_ida:
> + ida_free(&tidss_oldi_ida, oldi_aux_id);
>
> - drm_bridge_add(&oldi->bridge);
> + return ERR_PTR(ret);
> +}
> +
> +int tidss_oldi_create_devices(struct tidss_device *tidss)
> +{
> + struct device_node *oldi_txes;
> + struct device_node *oldi_tx;
> + int ret;
> +
> + oldi_txes = of_get_child_by_name(tidss->dev->of_node,
> + "oldi-transmitters");
> + if (!oldi_txes)
> + return 0;
> +
> + /*
> + * Look for primary OLDIs and create devices for them. For dual-link
> + * cases, the primary's create_device call will also create the
> + * secondary device.
> + */
> + for_each_available_child_of_node(oldi_txes, oldi_tx) {
> + struct auxiliary_device *auxdev;
> +
> + if (of_property_read_bool(oldi_tx, "ti,secondary-oldi"))
> + continue;
> +
> + auxdev = tidss_oldi_create_device(tidss, oldi_tx);
> + if (IS_ERR(auxdev)) {
> + ret = PTR_ERR(auxdev);
> + goto err_destroy_oldis;
> + }
> }
>
> - of_node_put(child);
> - of_node_put(oldi_parent);
> + of_node_put(oldi_txes);
>
> return 0;
>
> -err_put_node:
> - of_node_put(child);
> - of_node_put(oldi_parent);
> +err_destroy_oldis:
> + tidss_oldi_destroy_devices(tidss);
> +
> + of_node_put(oldi_tx);
> + of_node_put(oldi_txes);
> +
> return ret;
> }
> +
> +void tidss_oldi_destroy_devices(struct tidss_device *tidss)
> +{
> + for (unsigned int i = 0; i < tidss->num_oldis; ++i)
> + auxiliary_device_destroy(tidss->oldis[i]);
> +}
> +
> +int tidss_oldi_register_driver(void)
> +{
> + return auxiliary_driver_register(&oldi_aux_driver);
> +}
> +
> +void tidss_oldi_unregister_driver(void)
> +{
> + auxiliary_driver_unregister(&oldi_aux_driver);
> +}
> diff --git a/drivers/gpu/drm/tidss/tidss_oldi.h b/drivers/gpu/drm/tidss/tidss_oldi.h
> index a361e6dbfce3..2069bd46aaae 100644
> --- a/drivers/gpu/drm/tidss/tidss_oldi.h
> +++ b/drivers/gpu/drm/tidss/tidss_oldi.h
> @@ -36,7 +36,10 @@ enum tidss_oldi_link_type {
> OLDI_MODE_SECONDARY_DUAL_LINK,
> };
>
> -int tidss_oldi_init(struct tidss_device *tidss);
> -void tidss_oldi_deinit(struct tidss_device *tidss);
> +int tidss_oldi_create_devices(struct tidss_device *tidss);
> +void tidss_oldi_destroy_devices(struct tidss_device *tidss);
> +
> +int tidss_oldi_register_driver(void);
> +void tidss_oldi_unregister_driver(void);
>
> #endif /* __TIDSS_OLDI_H__ */
>
^ permalink raw reply
* Re: [PATCH 0/2] CPPC: reduce FFH feedback-counter sampling skew on arm64
From: Beata Michalska @ 2026-06-24 10:51 UTC (permalink / raw)
To: Pengjie Zhang
Cc: Will Deacon, catalin.marinas, rafael, lenb, robert.moore,
zhenglifeng1, zhanjie9, sumitg, cuiyunhui, linux-arm-kernel,
linux-kernel, linux-acpi, acpica-devel, linuxarm,
jonathan.cameron, prime.zeng, wanghuiqiang, xuwei5, lihuisong,
yubowen8, wangzhi12, ionela.voinescu, jeremy.linton
In-Reply-To: <443104e2-ba6e-454e-8469-909f35817a99@huawei.com>
Apologies but this one completely skipped my radar.
Will have a look in the next few days.
---
BR
Beata
On Wed, May 20, 2026 at 10:55:54AM +0800, Pengjie Zhang wrote:
>
> On 5/19/2026 6:47 PM, Will Deacon wrote:
> > On Thu, Apr 30, 2026 at 06:00:44PM +0800, zhangpengjie (A) wrote:
> > > Hi all,
> > >
> > > Gentle ping on this thread. It has been a while since I posted it.
> > >
> > > Could someone please take a look when you have time? If there is anything
> > > I should revise or any additional information needed, I'd be happy to
> > > update it.
> > It's hard to find active folks who have contributed meaningfully to the
> > cppc_acpi driver... I've added Ionella and Jeremy, in case they can take
> > a look.
> >
> > Will
> Thanks Will, and thanks for adding Ionela and Jeremy.
>
> While waiting for further comments, I would like to add some
> test data to make the effect of this series clearer.
>
> On the test platform, the maximum frequency reported by the platform
> is 2300000. I sampled cpuinfo_cur_freq before and after applying this
> series.
>
> Before applying the series, the samples showed visible transient outliers.
> The minimum value was 2154583 and the maximum value was 2491071.
> There were 8 samples above 2400000 and 8 samples below 2200000.
> The largest value exceeded the platform maximum by about 8.3%.
>
> After applying the series, the samples became much more stable.
> The minimum value was 2290243 and the maximum value was 2306310.
> There were no samples above 2400000 and no samples below 2200000.
> The largest value exceeded the platform maximum by only about 0.27%.
>
> A summary of the 96 samples is:
>
> before after
> min 2154583 2290243
> max 2491071 2306310
> range 336488 16067
> average 2298436.4 2298479.4
> stddev 55184.1 2868.2
> samples > 2300000 26 / 96 16 / 96
> samples > 2400000 8 / 96 0 / 96
> samples < 2200000 8 / 96 0 / 96
>
> So this series does not try to clamp the value to the platform maximum.
> Instead, it reduces the sampling skew between the delivered and reference
> feedback counters. The remaining small deviation around 2300000 is
> much smaller than the previous transient spikes.
>
> One concern that may come up is that an FFH read may cause an idle
> target CPU to be woken, depending on the platform/vendor implementation.
> However, that behavior is not introduced by this series. It is already part
> of how FFH counter reads are implemented on such platforms. This series
> only changes the sampling form for the FFH feedback counters: when both
> delivered and reference counters are FFH counters, read them together
> instead of issuing two separate FFH reads.
>
> If the target CPU has to be involved for an FFH read, doing one paired
> read should be no worse than doing two separate reads, and it also
> narrows the sampling window between the two counters.
>
> If there is any concern about the generic hook or the arm64 implementation,
> I would be happy to revise it.
>
> The raw data is as follows:
> before:
> 2303809 2294827 2300000 2293643 2290740 2300000 2297228 2296082
> 2301707 2295354 2296601 2303163 2296766 2296543 2295412 2298394
> 2297387 2300000 2308274 2301882 2297752 2418568 2491071 2300000
> 2183264 2296238 2434731 2296721 2439777 2302159 2301773 2298226
> 2300000 2305936 2301133 2297511 2300000 2300000 2294408 2298494
> 2295011 2302721 2295955 2301505 2298064 2297419 2298933 2189595
> 2298058 2296046 2300000 2301449 2414908 2296559 2305251 2166666
> 2296626 2173303 2300000 2298806 2411389 2301822 2297291 2300000
> 2423831 2297902 2300000 2435730 2302433 2295353 2298898 2296043
> 2321868 2294907 2300000 2157841 2296052 2206530 2300000 2297811
> 2297920 2294382 2297767 2157230 2302564 2298504 2296822 2300000
> 2296868 2294866 2154583 2290888 2302542 2292549 2300000 2184259
>
> after:
> 2303738 2296153 2298087 2295607 2301373 2298076 2300000 2295081
> 2297788 2300000 2300000 2295238 2301449 2300000 2298488 2297911
> 2301477 2298507 2294976 2296852 2293689 2294077 2293887 2292619
> 2300000 2300000 2298072 2300000 2291943 2300000 2295370 2300000
> 2301873 2304645 2300000 2296766 2300000 2300000 2290243 2297954
> 2297183 2306310 2300000 2296889 2300000 2303800 2301970 2296888
> 2300000 2301354 2300000 2298405 2298202 2296767 2298663 2302522
> 2297821 2302471 2300000 2303233 2298226 2298698 2300000 2297291
> 2296470 2300000 2298398 2300000 2295681 2300000 2300000 2296344
> 2300000 2296008 2302375 2297977 2298447 2296519 2295565 2294866
> 2297945 2300000 2294978 2303595 2300000 2300000 2294930 2301096
> 2296271 2296086 2294482 2300000 2294843 2300000 2296803 2295708
> > > On 4/10/2026 5:41 PM, Pengjie Zhang wrote:
> > > > The legacy CPPC feedback-counter path reads the delivered and reference
> > > > performance counters separately.
> > > >
> > > > On arm64 systems using AMU-backed CPPC FFH counters, each FFH read is
> > > > served through a cross-CPU counter read helper. Reading the counters
> > > > separately therefore widens the sampling window between them and can
> > > > skew the delivered/reference ratio used by cpuinfo_cur_freq. Under heavy
> > > > load, the skew is observable as transient values that may exceed the
> > > > platform maximum, as discussed in [1] and [2].
> > > >
> > > > This series adds a small generic hook for architectures that can obtain
> > > > both FFH feedback counters in one operation, while preserving the
> > > > existing per-register read path as the fallback.
> > > >
> > > > Patch 1 adds the generic CPPC hook and uses it from cppc_get_perf_ctrs().
> > > > Patch 2 implements the hook on arm64 by sampling both AMU counters in a
> > > > single operation on the target CPU.
> > > >
> > > > [1] https://lore.kernel.org/all/20231025093847.3740104-4-zengheng4@huawei.com/
> > > > [2] https://lore.kernel.org/all/20231212072617.14756-1-lihuisong@huawei.com/
> > > >
> > > > Signed-off-by: Pengjie Zhang <zhangpengjie2@huawei.com>
> > > >
> > > > Pengjie Zhang (2):
> > > > ACPI: CPPC: add paired FFH feedback-counter read hook
> > > > arm64: topology: read CPPC FFH feedback counters in one operation
> > > >
> > > > arch/arm64/kernel/topology.c | 75 ++++++++++++++++++++++++++++++++----
> > > > drivers/acpi/cppc_acpi.c | 58 +++++++++++++++++++++++++---
> > > > include/acpi/cppc_acpi.h | 7 ++++
> > > > 3 files changed, 127 insertions(+), 13 deletions(-)
> > > >
^ permalink raw reply
* [PATCH v3 5/7] net: wwan: t9xx: Add FSM thread
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
The FSM (Finite-state Machine) thread is responsible for
synchronizing the actions of different modules. The
asynchronous events from the device or the OS will trigger
a state transition.
The FSM thread will append it to the event queue when an
event arrives. It handles the events sequentially. After
processing the event, the FSM thread notifies other modules
before and after the state transition.
Seven FSM states are defined. They can transition from one
state to another, self-transition in some states, and
transition in some sub-states.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
drivers/net/wwan/t9xx/Makefile | 3 +-
drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 46 ++
drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 2 +
drivers/net/wwan/t9xx/mtk_dev.h | 1 +
drivers/net/wwan/t9xx/mtk_fsm.c | 948 ++++++++++++++++++++++++
drivers/net/wwan/t9xx/mtk_fsm.h | 140 ++++
drivers/net/wwan/t9xx/mtk_port.c | 65 ++
drivers/net/wwan/t9xx/mtk_port.h | 2 +
drivers/net/wwan/t9xx/mtk_utility.h | 33 +
drivers/net/wwan/t9xx/pcie/mtk_cldma.c | 222 +++++-
drivers/net/wwan/t9xx/pcie/mtk_cldma.h | 3 +
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h | 3 -
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c | 7 +-
drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h | 2 -
drivers/net/wwan/t9xx/pcie/mtk_pci.c | 16 +-
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c | 10 +
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 1 -
17 files changed, 1488 insertions(+), 16 deletions(-)
diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
index db3b1aa1928b..75760b2039dc 100644
--- a/drivers/net/wwan/t9xx/Makefile
+++ b/drivers/net/wwan/t9xx/Makefile
@@ -10,4 +10,5 @@ mtk_t9xx-y := \
mtk_dev.o \
mtk_ctrl_plane.o \
mtk_port.o \
- mtk_port_io.o
+ mtk_port_io.o \
+ mtk_fsm.o
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
index b9a0443ce8ec..dc6a0670fe2b 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
@@ -5,10 +5,46 @@
*/
#include <linux/device.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/pm_runtime.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
#include "mtk_ctrl_plane.h"
#include "mtk_port.h"
+#define TAG "CTRL"
+
+static void mtk_ctrl_trans_fsm_state_handler(struct mtk_fsm_param *param,
+ struct mtk_ctrl_blk *ctrl_blk)
+{
+ struct mtk_md_dev *mdev = ctrl_blk->mdev;
+
+ switch (param->to) {
+ case FSM_STATE_OFF:
+ ctrl_blk->ops->fsm_indication(mdev, param);
+ ctrl_blk->ops->exit(mdev);
+ break;
+ case FSM_STATE_ON:
+ ctrl_blk->ops->init(mdev);
+ fallthrough;
+ default:
+ ctrl_blk->ops->fsm_indication(mdev, param);
+ break;
+ }
+}
+
+static void mtk_ctrl_fsm_state_listener(struct mtk_fsm_param *param, void *data)
+{
+ struct mtk_ctrl_blk *ctrl_blk = data;
+
+ mtk_port_mngr_fsm_state_handler(param, ctrl_blk->port_mngr);
+ mtk_ctrl_trans_fsm_state_handler(param, ctrl_blk);
+ mtk_port_mngr_fsm_state_handler_late(param, ctrl_blk->port_mngr);
+}
+
/**
* mtk_ctrl_init() - Initialize the control plane block.
* @mdev: Pointer to the MTK modem device.
@@ -39,8 +75,17 @@ int mtk_ctrl_init(struct mtk_md_dev *mdev, struct mtk_ctrl_hif_ops *ops, struct
if (err)
goto err_free_mem;
+ err = mtk_fsm_notifier_register(mdev, MTK_USER_CTRL, mtk_ctrl_fsm_state_listener,
+ ctrl_blk, FSM_PRIO_1, false);
+ if (err) {
+ dev_err((mdev)->dev, "Fail to register fsm notification(ret = %d)\n", err);
+ goto err_port_exit;
+ }
+
return 0;
+err_port_exit:
+ mtk_port_mngr_exit(ctrl_blk);
err_free_mem:
devm_kfree(mdev->dev, ctrl_blk);
@@ -58,6 +103,7 @@ void mtk_ctrl_exit(struct mtk_md_dev *mdev)
{
struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+ mtk_fsm_notifier_unregister(mdev, MTK_USER_CTRL);
mtk_port_mngr_exit(ctrl_blk);
devm_kfree(mdev->dev, ctrl_blk);
mdev->ctrl_blk = NULL;
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
index d7fcccde8a1b..92817e92a2e4 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
@@ -10,6 +10,7 @@
#include <linux/skbuff.h>
#include "mtk_dev.h"
+#include "mtk_fsm.h"
#define Q_MTU_2K (0x800)
#define Q_MTU_3_5K (0xE00)
@@ -62,6 +63,7 @@ struct mtk_ctrl_hif_ops {
int (*init)(struct mtk_md_dev *mdev);
int (*exit)(struct mtk_md_dev *mdev);
int (*submit_skb)(struct mtk_md_dev *mdev, struct sk_buff *skb, bool force_send);
+ void (*fsm_indication)(struct mtk_md_dev *mdev, struct mtk_fsm_param *param);
int (*send_cmd)(struct mtk_md_dev *mdev, int cmd, void *data);
};
diff --git a/drivers/net/wwan/t9xx/mtk_dev.h b/drivers/net/wwan/t9xx/mtk_dev.h
index bb3ea68890ea..2388ada2c6a6 100644
--- a/drivers/net/wwan/t9xx/mtk_dev.h
+++ b/drivers/net/wwan/t9xx/mtk_dev.h
@@ -59,6 +59,7 @@ struct mtk_md_dev {
u32 hw_ver;
char dev_str[MTK_DEV_STR_LEN];
struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_md_fsm *fsm;
};
static inline u32 mtk_dev_get_dev_state(struct mtk_md_dev *mdev)
diff --git a/drivers/net/wwan/t9xx/mtk_fsm.c b/drivers/net/wwan/t9xx/mtk_fsm.c
new file mode 100644
index 000000000000..a9943c63986c
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_fsm.c
@@ -0,0 +1,948 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/kref.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/skbuff.h>
+#include <linux/wait.h>
+
+#include "mtk_fsm.h"
+#include "mtk_port.h"
+#include "mtk_port_io.h"
+#include "mtk_utility.h"
+
+#define EVT_TF_GATECLOSED (1)
+#define MTK_FSM_INFO_LEN (64)
+
+#define FSM_HS_START_MASK (FSM_F_SAP_HS_START | FSM_F_MD_HS_START)
+#define FSM_HS2_DONE_MASK (FSM_F_SAP_HS2_DONE | FSM_F_MD_HS2_DONE)
+
+#define RTFT_DATA_SIZE (3 * 1024)
+#define EVT_HANDLER_TIMEOUT (HZ * 30)
+#define BLOCKING_EVT_TIMEOUT (2 * EVT_HANDLER_TIMEOUT)
+
+#define REGION_BITMASK 0xF
+#define DEVICE_CFG_SHIFT 24
+#define DEVICE_CFG_REGION_MASK 0x3
+
+enum device_stage {
+ DEV_STAGE_IDLE = 4,
+ DEV_STAGE_MAX
+};
+
+enum device_cfg {
+ DEV_CFG_NORMAL = 0,
+ DEV_CFG_MD_ONLY,
+};
+
+enum runtime_feature_support_type {
+ RTFT_TYPE_NOT_EXIST = 0,
+ RTFT_TYPE_NOT_SUPPORT = 1,
+ RTFT_TYPE_MUST_SUPPORT = 2,
+ RTFT_TYPE_OPTIONAL_SUPPORT = 3,
+ RTFT_TYPE_SUPPORT_BACKWARD_COMPAT = 4,
+};
+
+enum query_runtime_feature_id {
+ QUERY_RTFT_ID_MD_PORT_ENUM = 0,
+ QUERY_RTFT_ID_SAP_PORT_ENUM = 1,
+ QUERY_RTFT_ID_MD_PORT_CFG = 2,
+ QUERY_RTFT_ID_MAX
+};
+
+enum ctrl_msg_id {
+ CTRL_MSG_HS1 = 0,
+ CTRL_MSG_HS2 = 1,
+ CTRL_MSG_HS3 = 2,
+};
+
+struct ctrl_msg_header {
+ __le32 id;
+ __le32 ex_msg;
+ __le32 data_len;
+ u8 reserved[];
+} __packed;
+
+struct runtime_feature_entry {
+ u8 feature_id;
+ struct runtime_feature_info support_info;
+ u8 reserved[2];
+ __le32 data_len;
+ u8 data[];
+};
+
+struct feature_query {
+ __le32 head_pattern;
+ struct runtime_feature_info ft_set[FEATURE_CNT];
+ __le32 tail_pattern;
+};
+
+static int mtk_fsm_send_hs1_msg(struct fsm_hs_info *hs_info)
+{
+ struct ctrl_msg_header *ctrl_msg_h;
+ struct feature_query *ft_query;
+ struct sk_buff *skb;
+ int ret, msg_size;
+
+ msg_size = sizeof(*ctrl_msg_h) + sizeof(*ft_query);
+ skb = __dev_alloc_skb(msg_size, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_put(skb, msg_size);
+ ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
+ ctrl_msg_h->id = cpu_to_le32(CTRL_MSG_HS1);
+ ctrl_msg_h->ex_msg = 0;
+ ctrl_msg_h->data_len = cpu_to_le32(sizeof(*ft_query));
+
+ ft_query = (struct feature_query *)(skb->data + sizeof(*ctrl_msg_h));
+ ft_query->head_pattern = cpu_to_le32(FEATURE_QUERY_PATTERN);
+ memcpy(ft_query->ft_set, hs_info->query_ft_set, sizeof(hs_info->query_ft_set));
+ ft_query->tail_pattern = cpu_to_le32(FEATURE_QUERY_PATTERN);
+
+ /* send handshake1 message to device */
+ ret = mtk_port_internal_write(hs_info->ctrl_port, skb);
+ if (ret <= 0)
+ return ret;
+
+ return 0;
+}
+
+static int mtk_fsm_feature_set_match(enum runtime_feature_support_type *cur_ft_spt,
+ struct runtime_feature_info rtft_info_st,
+ struct runtime_feature_info rtft_info_cfg)
+{
+ int ret = 0;
+
+ switch (FIELD_GET(FEATURE_TYPE, rtft_info_st.feature)) {
+ case RTFT_TYPE_NOT_EXIST:
+ fallthrough;
+ case RTFT_TYPE_NOT_SUPPORT:
+ *cur_ft_spt = RTFT_TYPE_NOT_EXIST;
+ break;
+ case RTFT_TYPE_MUST_SUPPORT:
+ if (FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_EXIST ||
+ FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_SUPPORT)
+ ret = -EPROTO;
+ else
+ *cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;
+ break;
+ case RTFT_TYPE_OPTIONAL_SUPPORT:
+ if (FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_EXIST ||
+ FIELD_GET(FEATURE_TYPE, rtft_info_cfg.feature) == RTFT_TYPE_NOT_SUPPORT) {
+ *cur_ft_spt = RTFT_TYPE_NOT_SUPPORT;
+ } else {
+ if (FIELD_GET(FEATURE_VER, rtft_info_st.feature) ==
+ FIELD_GET(FEATURE_VER, rtft_info_cfg.feature))
+ *cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;
+ else
+ *cur_ft_spt = RTFT_TYPE_NOT_SUPPORT;
+ }
+ break;
+ case RTFT_TYPE_SUPPORT_BACKWARD_COMPAT:
+ if (FIELD_GET(FEATURE_VER, rtft_info_st.feature) >=
+ FIELD_GET(FEATURE_VER, rtft_info_cfg.feature))
+ *cur_ft_spt = RTFT_TYPE_MUST_SUPPORT;
+ else
+ *cur_ft_spt = RTFT_TYPE_NOT_EXIST;
+ break;
+ default:
+ ret = -EPROTO;
+ }
+
+ return ret;
+}
+
+static int (*query_rtft_action[FEATURE_CNT])(struct mtk_md_dev *mdev, void *rt_data) = {
+ [QUERY_RTFT_ID_MD_PORT_ENUM] = mtk_port_status_update,
+ [QUERY_RTFT_ID_SAP_PORT_ENUM] = mtk_port_status_update,
+};
+
+static int mtk_fsm_parse_hs2_msg(struct fsm_hs_info *hs_info)
+{
+ struct mtk_md_fsm *fsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info->id]);
+ char *rt_data = ((struct sk_buff *)hs_info->rt_data)->data;
+ enum runtime_feature_support_type cur_ft_spt;
+ struct runtime_feature_entry *rtft_entry;
+ unsigned int ft_id, offset, data_len;
+ int ret = 0;
+
+ offset = sizeof(struct feature_query);
+ for (ft_id = 0; ft_id < FEATURE_CNT; ft_id++) {
+ if (offset + sizeof(*rtft_entry) > hs_info->rt_data_len)
+ break;
+
+ rtft_entry = (struct runtime_feature_entry *)(rt_data + offset);
+ ret = mtk_fsm_feature_set_match(&cur_ft_spt,
+ rtft_entry->support_info,
+ hs_info->query_ft_set[ft_id]);
+ if (ret < 0)
+ break;
+
+ if (cur_ft_spt == RTFT_TYPE_MUST_SUPPORT)
+ if (query_rtft_action[ft_id])
+ ret = query_rtft_action[ft_id](fsm->mdev, rtft_entry->data);
+ if (ret < 0)
+ break;
+
+ data_len = le32_to_cpu(rtft_entry->data_len);
+ if (data_len > hs_info->rt_data_len - offset - sizeof(*rtft_entry))
+ break;
+
+ offset += sizeof(*rtft_entry) + data_len;
+ }
+
+ if (ft_id != FEATURE_CNT) {
+ dev_err((fsm->mdev)->dev, "Unable to handle mistake hs2 msg, ft_id=%d\n", ft_id);
+ ret = -EPROTO;
+ }
+
+ return ret;
+}
+
+static int mtk_fsm_append_rtft_entries(struct mtk_md_dev *mdev, void *feature_data,
+ unsigned int *len, struct fsm_hs_info *hs_info)
+{
+ char *rt_data = ((struct sk_buff *)hs_info->rt_data)->data;
+ struct runtime_feature_entry *rtft_entry;
+ int ft_id, ret = 0, rtdata_len = 0;
+ struct feature_query *ft_query;
+
+ ft_query = (struct feature_query *)rt_data;
+ if (le32_to_cpu(ft_query->head_pattern) != FEATURE_QUERY_PATTERN ||
+ le32_to_cpu(ft_query->tail_pattern) != FEATURE_QUERY_PATTERN) {
+ ret = -EPROTO;
+ goto hs_err;
+ }
+
+ /* parse runtime feature query and fill runtime feature entry */
+ rtft_entry = feature_data;
+ for (ft_id = 0; ft_id < FEATURE_CNT && rtdata_len < RTFT_DATA_SIZE; ft_id++) {
+ rtft_entry->feature_id = ft_id;
+ rtft_entry->data_len = 0;
+
+ switch (FIELD_GET(FEATURE_TYPE, ft_query->ft_set[ft_id].feature)) {
+ case RTFT_TYPE_NOT_EXIST:
+ fallthrough;
+ case RTFT_TYPE_NOT_SUPPORT:
+ fallthrough;
+ case RTFT_TYPE_MUST_SUPPORT:
+ rtft_entry->support_info = ft_query->ft_set[ft_id];
+ break;
+ case RTFT_TYPE_OPTIONAL_SUPPORT:
+ fallthrough;
+ case RTFT_TYPE_SUPPORT_BACKWARD_COMPAT:
+ rtft_entry->support_info.feature = FEATURE_TYPE_NOT;
+ rtft_entry->support_info.feature |= FEATURE_VER_0;
+ break;
+ }
+
+ rtdata_len += sizeof(*rtft_entry) + le32_to_cpu(rtft_entry->data_len);
+ rtft_entry = (struct runtime_feature_entry *)(feature_data + rtdata_len);
+ }
+ *len = rtdata_len;
+ return 0;
+
+hs_err:
+ *len = 0;
+ return ret;
+}
+
+static int mtk_fsm_send_hs3_msg(struct fsm_hs_info *hs_info)
+{
+ struct mtk_md_fsm *fsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info->id]);
+ unsigned int data_len, msg_size = 0;
+ struct ctrl_msg_header *ctrl_msg_h;
+ struct sk_buff *skb;
+ int ret;
+
+ skb = __dev_alloc_skb(RTFT_DATA_SIZE, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ msg_size += sizeof(*ctrl_msg_h);
+ ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
+ ctrl_msg_h->id = cpu_to_le32(CTRL_MSG_HS3);
+ ctrl_msg_h->ex_msg = 0;
+ ret = mtk_fsm_append_rtft_entries(fsm->mdev,
+ skb->data + sizeof(*ctrl_msg_h),
+ &data_len, hs_info);
+ if (ret) {
+ dev_kfree_skb(skb);
+ return ret;
+ }
+
+ ctrl_msg_h->data_len = cpu_to_le32(data_len);
+ msg_size += data_len;
+ skb_put(skb, msg_size);
+ ret = mtk_port_internal_write(hs_info->ctrl_port, skb);
+ if (ret <= 0)
+ return ret;
+
+ return 0;
+}
+
+static int mtk_fsm_sap_ctrl_msg_handler(void *__fsm, struct sk_buff *skb)
+{
+ struct ctrl_msg_header *ctrl_msg_h;
+ struct mtk_md_fsm *fsm = __fsm;
+ struct fsm_hs_info *hs_info;
+ int ret;
+
+ if (skb->len < sizeof(*ctrl_msg_h)) {
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
+ skb_pull(skb, sizeof(*ctrl_msg_h));
+
+ hs_info = &fsm->hs_info[HS_ID_SAP];
+ if (le32_to_cpu(ctrl_msg_h->id) != CTRL_MSG_HS2) {
+ dev_kfree_skb(skb);
+ return -EPROTO;
+ }
+
+ hs_info->rt_data = skb;
+ hs_info->rt_data_len = skb->len;
+ ret = mtk_fsm_evt_submit(fsm->mdev, FSM_EVT_STARTUP,
+ hs_info->fsm_flag_hs2, hs_info, sizeof(*hs_info), 0);
+ if (ret == FSM_EVT_RET_FAIL)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+static int mtk_fsm_md_ctrl_msg_handler(void *__fsm, struct sk_buff *skb)
+{
+ struct ctrl_msg_header *ctrl_msg_h;
+ struct mtk_md_fsm *fsm = __fsm;
+ struct fsm_hs_info *hs_info;
+ bool consumed_skb = false;
+ int ret;
+
+ if (skb->len < sizeof(*ctrl_msg_h)) {
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ ctrl_msg_h = (struct ctrl_msg_header *)skb->data;
+ hs_info = &fsm->hs_info[HS_ID_MD];
+ switch (le32_to_cpu(ctrl_msg_h->id)) {
+ case CTRL_MSG_HS2:
+ skb_pull(skb, sizeof(*ctrl_msg_h));
+ hs_info->rt_data = skb;
+ hs_info->rt_data_len = skb->len;
+ ret = mtk_fsm_evt_submit(fsm->mdev, FSM_EVT_STARTUP,
+ hs_info->fsm_flag_hs2, hs_info, sizeof(*hs_info), 0);
+ if (ret != FSM_EVT_RET_FAIL)
+ consumed_skb = true;
+ break;
+ default:
+ dev_err(fsm->mdev->dev, "Invalid ctrl msg id\n");
+ }
+
+ if (!consumed_skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+static int (*ctrl_msg_handler[HS_ID_MAX])(void *__fsm, struct sk_buff *skb) = {
+ [HS_ID_MD] = mtk_fsm_md_ctrl_msg_handler,
+ [HS_ID_SAP] = mtk_fsm_sap_ctrl_msg_handler,
+};
+
+static void mtk_fsm_idle_evt_handler(struct mtk_md_dev *mdev,
+ u32 dev_state, struct mtk_md_fsm *fsm)
+{
+ u32 dev_cfg = dev_state >> DEVICE_CFG_SHIFT & DEVICE_CFG_REGION_MASK;
+ int hs_id;
+
+ if (dev_cfg == DEV_CFG_MD_ONLY)
+ fsm->hs_done_flag = FSM_F_MD_HS_START | FSM_F_MD_HS2_DONE;
+ else
+ fsm->hs_done_flag = FSM_HS_START_MASK | FSM_HS2_DONE_MASK;
+
+ mtk_fsm_evt_submit(mdev, FSM_EVT_STARTUP, FSM_F_DFLT, NULL, 0, 0);
+
+ for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++)
+ mtk_dev_unmask_dev_evt(mdev, fsm->hs_info[hs_id].mhccif_ch);
+}
+
+static int mtk_fsm_early_bootup_handler(u32 status, void *__fsm)
+{
+ struct mtk_md_fsm *fsm = __fsm;
+ struct mtk_md_dev *mdev;
+ u32 dev_state, dev_stage;
+
+ mdev = fsm->mdev;
+ mtk_dev_mask_dev_evt(mdev, status);
+ mtk_dev_clear_dev_evt(mdev, status);
+
+ dev_state = mtk_dev_get_dev_state(mdev);
+ dev_stage = dev_state & REGION_BITMASK;
+ if (dev_stage >= DEV_STAGE_MAX) {
+ dev_err(mdev->dev, "Invalid dev state 0x%x\n", dev_state);
+ return -ENXIO;
+ }
+
+ if (dev_state == fsm->last_dev_state)
+ goto exit;
+ fsm->last_dev_state = dev_state;
+
+ if (dev_stage == DEV_STAGE_IDLE)
+ mtk_fsm_idle_evt_handler(mdev, dev_state, fsm);
+
+exit:
+ return 0;
+}
+
+static int mtk_fsm_ctrl_ch_start(struct mtk_md_fsm *fsm, struct fsm_hs_info *hs_info, int flag)
+{
+ if (!hs_info->ctrl_port) {
+ hs_info->ctrl_port = mtk_port_internal_open(fsm->mdev, hs_info->port_name, flag);
+ if (!hs_info->ctrl_port) {
+ dev_err(fsm->mdev->dev, "Failed to open ctrl port(%s)\n",
+ hs_info->port_name);
+ return -ENODEV;
+ }
+
+ mtk_port_internal_recv_register(hs_info->ctrl_port,
+ ctrl_msg_handler[hs_info->id], fsm);
+ }
+
+ return 0;
+}
+
+static void mtk_fsm_ctrl_ch_stop(struct mtk_md_fsm *fsm)
+{
+ struct fsm_hs_info *hs_info;
+ int hs_id;
+
+ for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++) {
+ hs_info = &fsm->hs_info[hs_id];
+ if (hs_info->ctrl_port) {
+ mtk_port_internal_close(hs_info->ctrl_port);
+ hs_info->ctrl_port = NULL;
+ }
+ }
+}
+
+static void mtk_fsm_switch_state(struct mtk_md_fsm *fsm,
+ enum mtk_fsm_state to_state, struct mtk_fsm_evt *event)
+{
+ char fsm_info[MTK_FSM_INFO_LEN];
+ struct mtk_fsm_notifier *nt;
+ struct mtk_fsm_param param;
+
+ param.from = fsm->state;
+ param.to = to_state;
+ param.evt_id = event ? event->id : FSM_EVT_MAX;
+ param.fsm_flag = event ? event->fsm_flag : FSM_F_DFLT;
+
+ list_for_each_entry(nt, &fsm->pre_notifiers, entry)
+ nt->cb(¶m, nt->data);
+
+ fsm->state = to_state;
+ fsm->fsm_flag |= event ? event->fsm_flag : FSM_F_DFLT;
+
+ snprintf(fsm_info, MTK_FSM_INFO_LEN,
+ "state=%d, fsm_flag=0x%x", to_state, fsm->fsm_flag);
+ mtk_uevent_notify(fsm->mdev->dev, MTK_UEVENT_FSM, fsm_info);
+
+ list_for_each_entry(nt, &fsm->post_notifiers, entry)
+ nt->cb(¶m, nt->data);
+}
+
+static int mtk_fsm_startup_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)
+{
+ enum mtk_fsm_state to_state = FSM_STATE_BOOTUP;
+ struct fsm_hs_info *hs_info = event->data;
+ struct mtk_md_dev *mdev = fsm->mdev;
+ int ret = 0;
+
+ if (fsm->state != FSM_STATE_ON && fsm->state != FSM_STATE_BOOTUP) {
+ ret = -EPROTO;
+ goto free_rt_data;
+ }
+
+ if (fsm->state != FSM_STATE_BOOTUP) {
+ mtk_fsm_switch_state(fsm, to_state, event);
+ return 0;
+ }
+
+ if (event->fsm_flag & FSM_HS_START_MASK) {
+ mtk_fsm_switch_state(fsm, to_state, event);
+
+ ret = mtk_fsm_ctrl_ch_start(fsm, hs_info, O_NONBLOCK);
+ if (!ret)
+ ret = mtk_fsm_send_hs1_msg(hs_info);
+ if (ret)
+ goto hs_err;
+ } else if (event->fsm_flag & FSM_HS2_DONE_MASK) {
+ ret = mtk_fsm_parse_hs2_msg(hs_info);
+ if (!ret) {
+ mtk_fsm_switch_state(fsm, to_state, event);
+ ret = mtk_fsm_send_hs3_msg(hs_info);
+ }
+ dev_kfree_skb(hs_info->rt_data);
+ hs_info->rt_data = NULL;
+ if (ret)
+ goto hs_err;
+ }
+
+ if (((fsm->fsm_flag | event->fsm_flag) & fsm->hs_done_flag) == fsm->hs_done_flag) {
+ to_state = FSM_STATE_READY;
+ mtk_fsm_switch_state(fsm, to_state, NULL);
+ }
+
+ return 0;
+
+free_rt_data:
+ if (hs_info && hs_info->rt_data) {
+ dev_kfree_skb(hs_info->rt_data);
+ hs_info->rt_data = NULL;
+ }
+hs_err:
+ dev_err((mdev)->dev, "Failed to hs with device %d:0x%x, ret=%d",
+ fsm->state, fsm->fsm_flag, ret);
+ return ret;
+}
+
+static void mtk_fsm_evt_release(struct kref *kref)
+{
+ struct mtk_fsm_evt *event = container_of(kref, struct mtk_fsm_evt, kref);
+
+ kfree(event);
+}
+
+static void mtk_fsm_evt_put(struct mtk_fsm_evt *event)
+{
+ kref_put(&event->kref, mtk_fsm_evt_release);
+}
+
+static void mtk_fsm_evt_finish(struct mtk_md_fsm *fsm,
+ struct mtk_fsm_evt *event, int retval)
+{
+ if (event->mode & EVT_MODE_BLOCKING) {
+ event->status = retval;
+ wake_up(&fsm->evt_waitq);
+ }
+ mtk_fsm_evt_put(event);
+}
+
+static void mtk_fsm_evt_cleanup(struct mtk_md_fsm *fsm, struct list_head *evtq)
+{
+ struct mtk_fsm_evt *event, *tmp;
+
+ list_for_each_entry_safe(event, tmp, evtq, entry) {
+ list_del(&event->entry);
+ mtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_FAIL);
+ }
+}
+
+static int mtk_fsm_enter_off_state(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)
+{
+ struct mtk_md_dev *mdev = fsm->mdev;
+ int hs_id;
+
+ if (fsm->state == FSM_STATE_OFF || fsm->state == FSM_STATE_INVALID)
+ return -EPROTO;
+
+ mtk_dev_mask_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);
+ for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++)
+ mtk_dev_mask_dev_evt(mdev, fsm->hs_info[hs_id].mhccif_ch);
+
+ mtk_fsm_ctrl_ch_stop(fsm);
+ mtk_fsm_switch_state(fsm, FSM_STATE_OFF, event);
+
+ return 0;
+}
+
+static int mtk_fsm_dev_rm_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&fsm->evtq_lock, flags);
+ set_bit(EVT_TF_GATECLOSED, &fsm->t_flag);
+ mtk_fsm_evt_cleanup(fsm, &fsm->evtq);
+ spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+
+ return mtk_fsm_enter_off_state(fsm, event);
+}
+
+static int mtk_fsm_hs1_handler(u32 status, void *__hs_info)
+{
+ struct fsm_hs_info *hs_info = __hs_info;
+ struct mtk_md_dev *mdev;
+ struct mtk_md_fsm *fsm;
+
+ fsm = container_of(hs_info, struct mtk_md_fsm, hs_info[hs_info->id]);
+ mdev = fsm->mdev;
+ mtk_fsm_evt_submit(mdev, FSM_EVT_STARTUP,
+ hs_info->fsm_flag_hs1, hs_info, sizeof(*hs_info), 0);
+ mtk_dev_mask_dev_evt(mdev, hs_info->mhccif_ch);
+ mtk_dev_clear_dev_evt(mdev, hs_info->mhccif_ch);
+
+ return 0;
+}
+
+static void mtk_fsm_hs_info_init_by_hsid(struct mtk_md_fsm *fsm, int hs_id)
+{
+ struct fsm_hs_info *hs_info;
+
+ if (hs_id < 0 || hs_id >= HS_ID_MAX) {
+ dev_warn((fsm->mdev)->dev, "hs_id = %d, invalid.\n", hs_id);
+ return;
+ }
+
+ hs_info = &fsm->hs_info[hs_id];
+ hs_info->id = hs_id;
+ hs_info->ctrl_port = NULL;
+ hs_info->rt_data = NULL;
+ switch (hs_id) {
+ case HS_ID_MD:
+ snprintf(hs_info->port_name, PORT_NAME_LEN, "MDCTRL");
+ hs_info->mhccif_ch = DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD;
+ hs_info->fsm_flag_hs1 = FSM_F_MD_HS_START;
+ hs_info->fsm_flag_hs2 = FSM_F_MD_HS2_DONE;
+ hs_info->query_ft_set[QUERY_RTFT_ID_MD_PORT_ENUM].feature =
+ FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT);
+ hs_info->query_ft_set[QUERY_RTFT_ID_MD_PORT_ENUM].feature |=
+ FIELD_PREP(FEATURE_VER, 0);
+ hs_info->query_ft_set[QUERY_RTFT_ID_MD_PORT_CFG].feature =
+ FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_NOT_SUPPORT);
+ break;
+ case HS_ID_SAP:
+ snprintf(hs_info->port_name, PORT_NAME_LEN, "SAPCTRL");
+ hs_info->mhccif_ch = DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP;
+ hs_info->fsm_flag_hs1 = FSM_F_SAP_HS_START;
+ hs_info->fsm_flag_hs2 = FSM_F_SAP_HS2_DONE;
+ hs_info->query_ft_set[QUERY_RTFT_ID_SAP_PORT_ENUM].feature =
+ FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT);
+ hs_info->query_ft_set[QUERY_RTFT_ID_SAP_PORT_ENUM].feature |=
+ FIELD_PREP(FEATURE_VER, 0);
+ break;
+ }
+}
+
+static void mtk_fsm_hs_info_init(struct mtk_md_fsm *fsm)
+{
+ struct mtk_md_dev *mdev = fsm->mdev;
+ struct fsm_hs_info *hs_info;
+ int hs_id;
+
+ for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++) {
+ mtk_fsm_hs_info_init_by_hsid(fsm, hs_id);
+ hs_info = &fsm->hs_info[hs_id];
+ mtk_dev_register_dev_evt(mdev, hs_info->mhccif_ch,
+ mtk_fsm_hs1_handler, hs_info);
+ }
+}
+
+static void mtk_fsm_hs_info_exit(struct mtk_md_fsm *fsm)
+{
+ struct mtk_md_dev *mdev = fsm->mdev;
+ struct fsm_hs_info *hs_info;
+ int hs_id;
+
+ for (hs_id = 0; hs_id < HS_ID_MAX; hs_id++) {
+ hs_info = &fsm->hs_info[hs_id];
+ mtk_dev_unregister_dev_evt(mdev, hs_info->mhccif_ch);
+ }
+}
+
+static int mtk_fsm_dev_add_act(struct mtk_md_fsm *fsm, struct mtk_fsm_evt *event)
+{
+ if (fsm->state != FSM_STATE_OFF && fsm->state != FSM_STATE_INVALID)
+ return -EPROTO;
+
+ mtk_fsm_switch_state(fsm, FSM_STATE_ON, event);
+ mtk_dev_unmask_dev_evt(fsm->mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);
+
+ return 0;
+}
+
+static int (*evts_act_tbl[FSM_EVT_MAX])(struct mtk_md_fsm *__fsm, struct mtk_fsm_evt *event) = {
+ [FSM_EVT_STARTUP] = mtk_fsm_startup_act,
+ [FSM_EVT_DEV_RM] = mtk_fsm_dev_rm_act,
+ [FSM_EVT_DEV_ADD] = mtk_fsm_dev_add_act,
+};
+
+int mtk_fsm_start(struct mtk_md_dev *mdev)
+{
+ struct mtk_md_fsm *fsm = mdev->fsm;
+
+ if (!fsm)
+ return -EINVAL;
+
+ if (!fsm->fsm_handler)
+ return -EFAULT;
+
+ wake_up_process(fsm->fsm_handler);
+ return 0;
+}
+EXPORT_SYMBOL(mtk_fsm_start);
+
+static void mkt_fsm_notifier_cleanup(struct mtk_md_dev *mdev, struct list_head *ntq)
+{
+ struct mtk_fsm_notifier *nt, *tmp;
+
+ list_for_each_entry_safe(nt, tmp, ntq, entry) {
+ list_del(&nt->entry);
+ dev_warn((mdev)->dev, "Having to free notifier(%d) by FSM!\n", nt->id);
+ devm_kfree(mdev->dev, nt);
+ }
+}
+
+static void mtk_fsm_notifier_insert(struct mtk_fsm_notifier *notifier, struct list_head *head)
+{
+ struct mtk_fsm_notifier *nt;
+
+ list_for_each_entry(nt, head, entry) {
+ if (notifier->prio > nt->prio) {
+ list_add(¬ifier->entry, nt->entry.prev);
+ return;
+ }
+ }
+ list_add_tail(¬ifier->entry, head);
+}
+
+int mtk_fsm_notifier_register(struct mtk_md_dev *mdev, enum mtk_user_id id,
+ void (*cb)(struct mtk_fsm_param *, void *data),
+ void *data, enum mtk_fsm_prio prio, bool is_pre)
+{
+ struct mtk_md_fsm *fsm = mdev->fsm;
+ struct mtk_fsm_notifier *notifier;
+
+ if (!fsm)
+ return -EINVAL;
+
+ if (id >= MTK_USER_MAX || !cb || prio >= FSM_PRIO_MAX)
+ return -EINVAL;
+
+ notifier = devm_kzalloc(mdev->dev, sizeof(*notifier), GFP_KERNEL);
+ if (!notifier)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(¬ifier->entry);
+ notifier->id = id;
+ notifier->cb = cb;
+ notifier->data = data;
+ notifier->prio = prio;
+
+ if (is_pre)
+ mtk_fsm_notifier_insert(notifier, &fsm->pre_notifiers);
+ else
+ mtk_fsm_notifier_insert(notifier, &fsm->post_notifiers);
+
+ return 0;
+}
+EXPORT_SYMBOL(mtk_fsm_notifier_register);
+
+int mtk_fsm_notifier_unregister(struct mtk_md_dev *mdev, enum mtk_user_id id)
+{
+ struct mtk_md_fsm *fsm = mdev->fsm;
+ struct mtk_fsm_notifier *nt, *tmp;
+
+ if (!fsm)
+ return -EINVAL;
+
+ list_for_each_entry_safe(nt, tmp, &fsm->pre_notifiers, entry) {
+ if (nt->id == id) {
+ list_del(&nt->entry);
+ devm_kfree(mdev->dev, nt);
+ break;
+ }
+ }
+ list_for_each_entry_safe(nt, tmp, &fsm->post_notifiers, entry) {
+ if (nt->id == id) {
+ list_del(&nt->entry);
+ devm_kfree(mdev->dev, nt);
+ break;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL(mtk_fsm_notifier_unregister);
+
+int mtk_fsm_evt_submit(struct mtk_md_dev *mdev,
+ enum mtk_fsm_evt_id id, enum mtk_fsm_flag flag,
+ void *data, unsigned int len, unsigned char mode)
+{
+ struct mtk_md_fsm *fsm = mdev->fsm;
+ struct mtk_fsm_evt *event;
+ unsigned long flags;
+ int ret = 0;
+
+ if (!fsm || id >= FSM_EVT_MAX) {
+ dev_err((mdev)->dev, "Invalid param!\n");
+ return FSM_EVT_RET_FAIL;
+ }
+
+ if (test_bit(EVT_TF_GATECLOSED, &fsm->t_flag)) {
+ dev_err((mdev)->dev, "Failed to submit evt, fsm has been removed!\n");
+ return FSM_EVT_RET_FAIL;
+ }
+
+ event = kzalloc(sizeof(*event),
+ (in_hardirq() || in_softirq() || irqs_disabled()) ?
+ GFP_ATOMIC : GFP_KERNEL);
+ if (!event)
+ return FSM_EVT_RET_FAIL;
+
+ kref_init(&event->kref);
+ event->mdev = mdev;
+ event->id = id;
+ event->fsm_flag = flag;
+ event->status = FSM_EVT_RET_ONGOING;
+ event->data = data;
+ event->len = len;
+ event->mode = mode;
+
+ spin_lock_irqsave(&fsm->evtq_lock, flags);
+ if (test_bit(EVT_TF_GATECLOSED, &fsm->t_flag)) {
+ spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+ mtk_fsm_evt_put(event);
+ dev_err(mdev->dev, "Failed to add event, fsm dev has been removed!\n");
+ return FSM_EVT_RET_FAIL;
+ }
+
+ kref_get(&event->kref);
+ if (mode & EVT_MODE_TOHEAD)
+ list_add(&event->entry, &fsm->evtq);
+ else
+ list_add_tail(&event->entry, &fsm->evtq);
+ spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+
+ wake_up_process(fsm->fsm_handler);
+ if (mode & EVT_MODE_BLOCKING) {
+ ret = wait_event_timeout(fsm->evt_waitq,
+ (event->status != 0), BLOCKING_EVT_TIMEOUT);
+ if (!ret && event->status != FSM_EVT_RET_DONE) {
+ dev_err((mdev)->dev, "Handling fsm blocking event timeout!\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = event->status;
+ }
+ }
+ mtk_fsm_evt_put(event);
+
+ return ret;
+}
+EXPORT_SYMBOL(mtk_fsm_evt_submit);
+
+static int mtk_fsm_evt_handler(void *__fsm)
+{
+ struct mtk_md_fsm *fsm = __fsm;
+ struct mtk_fsm_evt *event;
+ unsigned long flags;
+ int ret;
+
+wake_up:
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ while (!kthread_should_stop() && !list_empty(&fsm->evtq)) {
+ set_current_state(TASK_RUNNING);
+ spin_lock_irqsave(&fsm->evtq_lock, flags);
+ event = list_first_entry(&fsm->evtq, struct mtk_fsm_evt, entry);
+ list_del(&event->entry);
+ spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+
+ if (event->id < FSM_EVT_MAX) {
+ ret = evts_act_tbl[event->id](fsm, event);
+ if (ret) {
+ dev_err((fsm->mdev)->dev,
+ "Failed to handle evt, fsm state = %d, ret = %d\n",
+ fsm->state, ret);
+ mtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_FAIL);
+ } else {
+ mtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_DONE);
+ }
+ } else {
+ mtk_fsm_evt_finish(fsm, event, FSM_EVT_RET_DONE);
+ }
+ }
+
+ if (kthread_should_stop()) {
+ set_current_state(TASK_RUNNING);
+ return 0;
+ }
+
+ schedule();
+ goto wake_up;
+}
+
+int mtk_fsm_init(struct mtk_md_dev *mdev)
+{
+ struct mtk_md_fsm *fsm;
+ int ret;
+
+ fsm = devm_kzalloc(mdev->dev, sizeof(*fsm), GFP_KERNEL);
+ if (!fsm)
+ return -ENOMEM;
+
+ fsm->fsm_handler = kthread_create(mtk_fsm_evt_handler, fsm, "fsm_evt_thread%d_%s",
+ mdev->hw_ver, mdev->dev_str);
+ if (IS_ERR(fsm->fsm_handler)) {
+ ret = PTR_ERR(fsm->fsm_handler);
+ goto exit;
+ }
+
+ fsm->mdev = mdev;
+ fsm->state = FSM_STATE_INVALID;
+ fsm->fsm_flag = FSM_F_DFLT;
+
+ INIT_LIST_HEAD(&fsm->evtq);
+ spin_lock_init(&fsm->evtq_lock);
+ init_waitqueue_head(&fsm->evt_waitq);
+
+ INIT_LIST_HEAD(&fsm->pre_notifiers);
+ INIT_LIST_HEAD(&fsm->post_notifiers);
+
+ mtk_fsm_hs_info_init(fsm);
+ mtk_dev_register_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC,
+ mtk_fsm_early_bootup_handler, fsm);
+ mdev->fsm = fsm;
+ return 0;
+exit:
+ devm_kfree(mdev->dev, fsm);
+ return ret;
+}
+EXPORT_SYMBOL(mtk_fsm_init);
+
+int mtk_fsm_exit(struct mtk_md_dev *mdev)
+{
+ struct mtk_md_fsm *fsm = mdev->fsm;
+ unsigned long flags;
+
+ if (!fsm)
+ return -EINVAL;
+
+ if (fsm->fsm_handler) {
+ kthread_stop(fsm->fsm_handler);
+ fsm->fsm_handler = NULL;
+ }
+
+ spin_lock_irqsave(&fsm->evtq_lock, flags);
+ if (WARN_ON(!list_empty(&fsm->evtq)))
+ mtk_fsm_evt_cleanup(fsm, &fsm->evtq);
+ spin_unlock_irqrestore(&fsm->evtq_lock, flags);
+
+ mkt_fsm_notifier_cleanup(mdev, &fsm->pre_notifiers);
+ mkt_fsm_notifier_cleanup(mdev, &fsm->post_notifiers);
+
+ mtk_dev_unregister_dev_evt(mdev, DEV_EVT_D2H_BOOT_FLOW_SYNC);
+ mtk_fsm_hs_info_exit(fsm);
+
+ devm_kfree(mdev->dev, fsm);
+ return 0;
+}
+EXPORT_SYMBOL(mtk_fsm_exit);
diff --git a/drivers/net/wwan/t9xx/mtk_fsm.h b/drivers/net/wwan/t9xx/mtk_fsm.h
new file mode 100644
index 000000000000..f2fc66bcef61
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_fsm.h
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_FSM_H__
+#define __MTK_FSM_H__
+
+#include "mtk_dev.h"
+
+#define FEATURE_CNT (64)
+#define FEATURE_QUERY_PATTERN (0x49434343)
+
+#define FEATURE_TYPE GENMASK(3, 0)
+#define FEATURE_VER GENMASK(7, 4)
+
+#define FEATURE_TYPE_NOT FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_NOT_SUPPORT)
+#define FEATURE_TYPE_MUST FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_MUST_SUPPORT)
+#define FEATURE_TYPE_OPTIONAL FIELD_PREP(FEATURE_TYPE, RTFT_TYPE_OPTIONAL_SUPPORT)
+#define FEATURE_VER_0 FIELD_PREP(FEATURE_VER, 0)
+
+#define EVT_MODE_BLOCKING (0x01)
+#define EVT_MODE_TOHEAD (0x02)
+
+#define FSM_EVT_RET_FAIL (-1)
+#define FSM_EVT_RET_ONGOING (0)
+#define FSM_EVT_RET_DONE (1)
+
+enum mtk_fsm_flag {
+ FSM_F_DFLT = 0,
+ FSM_F_SAP_HS_START = BIT(0),
+ FSM_F_SAP_HS2_DONE = BIT(1),
+ FSM_F_MD_HS_START = BIT(2),
+ FSM_F_MD_HS2_DONE = BIT(3),
+};
+
+enum mtk_fsm_state {
+ FSM_STATE_INVALID = 0,
+ FSM_STATE_OFF,
+ FSM_STATE_ON,
+ FSM_STATE_BOOTUP,
+ FSM_STATE_READY,
+};
+
+enum mtk_fsm_evt_id {
+ FSM_EVT_STARTUP = 0,
+ FSM_EVT_DEV_RM,
+ FSM_EVT_DEV_ADD,
+ FSM_EVT_MAX
+};
+
+enum mtk_fsm_prio {
+ FSM_PRIO_0 = 0,
+ FSM_PRIO_1 = 1,
+ FSM_PRIO_MAX
+};
+
+struct mtk_fsm_param {
+ enum mtk_fsm_state from;
+ enum mtk_fsm_state to;
+ enum mtk_fsm_evt_id evt_id;
+ enum mtk_fsm_flag fsm_flag;
+};
+
+#define PORT_NAME_LEN 20
+
+enum handshake_info_id {
+ HS_ID_MD = 0,
+ HS_ID_SAP,
+ HS_ID_MAX
+};
+
+struct runtime_feature_info {
+ u8 feature;
+};
+
+struct fsm_hs_info {
+ unsigned char id;
+ void *ctrl_port;
+ char port_name[PORT_NAME_LEN];
+ unsigned int mhccif_ch;
+ unsigned int fsm_flag_hs1;
+ unsigned int fsm_flag_hs2;
+ /* the feature that the device should support */
+ struct runtime_feature_info query_ft_set[FEATURE_CNT];
+ /* runtime data from device need to be parsed by host */
+ void *rt_data;
+ unsigned int rt_data_len;
+};
+
+struct mtk_md_fsm {
+ struct mtk_md_dev *mdev;
+ struct task_struct *fsm_handler;
+ struct fsm_hs_info hs_info[HS_ID_MAX];
+ unsigned int hs_done_flag;
+ unsigned long t_flag;
+ u32 last_dev_state;
+ enum mtk_fsm_state state;
+ unsigned int fsm_flag;
+ struct list_head evtq;
+ /* protect evtq */
+ spinlock_t evtq_lock;
+ /* waitq for fsm blocking submit */
+ wait_queue_head_t evt_waitq;
+ struct list_head pre_notifiers;
+ struct list_head post_notifiers;
+};
+
+struct mtk_fsm_evt {
+ struct list_head entry;
+ struct kref kref;
+ struct mtk_md_dev *mdev;
+ enum mtk_fsm_evt_id id;
+ unsigned int fsm_flag;
+ int status;
+ unsigned char mode;
+ unsigned int len;
+ void *data;
+};
+
+struct mtk_fsm_notifier {
+ struct list_head entry;
+ enum mtk_user_id id;
+ void (*cb)(struct mtk_fsm_param *param, void *data);
+ void *data;
+ enum mtk_fsm_prio prio;
+};
+
+int mtk_fsm_init(struct mtk_md_dev *mdev);
+int mtk_fsm_exit(struct mtk_md_dev *mdev);
+int mtk_fsm_start(struct mtk_md_dev *mdev);
+int mtk_fsm_notifier_register(struct mtk_md_dev *mdev, enum mtk_user_id id,
+ void (*cb)(struct mtk_fsm_param *, void *data),
+ void *data, enum mtk_fsm_prio prio, bool is_pre);
+int mtk_fsm_notifier_unregister(struct mtk_md_dev *mdev, enum mtk_user_id id);
+int mtk_fsm_evt_submit(struct mtk_md_dev *mdev,
+ enum mtk_fsm_evt_id id, enum mtk_fsm_flag flag,
+ void *data, unsigned int len, unsigned char mode);
+
+#endif /* __MTK_FSM_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c
index 034c9ad0f892..fbc2fb7bdc2d 100644
--- a/drivers/net/wwan/t9xx/mtk_port.c
+++ b/drivers/net/wwan/t9xx/mtk_port.c
@@ -819,6 +819,71 @@ int mtk_port_ch_disable(struct mtk_port *port)
return ret;
}
+static void mtk_port_disable(struct mtk_port_mngr *port_mngr)
+{
+ struct mtk_port **ports;
+ int tbl_type;
+ int ret, idx;
+
+ ports = kcalloc(port_mngr->port_cnt, sizeof(struct mtk_port *), GFP_KERNEL);
+ if (!ports)
+ return;
+
+ tbl_type = PORT_TBL_SAP;
+ do {
+ ret = radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
+ (void **)ports, 0, port_mngr->port_cnt);
+ for (idx = 0; idx < ret; idx++)
+ ports_ops[ports[idx]->info.type]->disable(ports[idx]);
+ } while (++tbl_type < PORT_TBL_MAX);
+ kfree(ports);
+}
+
+void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg)
+{
+ struct mtk_port_mngr *port_mngr;
+
+ if (!fsm_param || !arg)
+ return;
+
+ port_mngr = arg;
+
+ switch (fsm_param->to) {
+ case FSM_STATE_OFF:
+ mtk_port_disable(port_mngr);
+ break;
+ default:
+ break;
+ }
+}
+
+void mtk_port_mngr_fsm_state_handler_late(struct mtk_fsm_param *fsm_param, void *arg)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct mtk_port *port;
+
+ if (!fsm_param || !arg)
+ return;
+
+ port_mngr = arg;
+
+ switch (fsm_param->to) {
+ case FSM_STATE_BOOTUP:
+ if (fsm_param->fsm_flag & FSM_F_MD_HS_START) {
+ port = mtk_port_search_by_id(port_mngr, CCCI_CONTROL_RX);
+ if (port)
+ ports_ops[port->info.type]->enable(port);
+ } else if (fsm_param->fsm_flag & FSM_F_SAP_HS_START) {
+ port = mtk_port_search_by_id(port_mngr, CCCI_SAP_CONTROL_RX);
+ if (port)
+ ports_ops[port->info.type]->enable(port);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt)
{
struct mtk_port_mngr *port_mngr;
diff --git a/drivers/net/wwan/t9xx/mtk_port.h b/drivers/net/wwan/t9xx/mtk_port.h
index bd4291408bc2..a201c0007878 100644
--- a/drivers/net/wwan/t9xx/mtk_port.h
+++ b/drivers/net/wwan/t9xx/mtk_port.h
@@ -152,6 +152,8 @@ int mtk_port_send_data(struct mtk_port *port, void *data);
int mtk_port_status_update(struct mtk_md_dev *mdev, void *data);
int mtk_port_ch_enable(struct mtk_port *port);
int mtk_port_ch_disable(struct mtk_port *port);
+void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg);
+void mtk_port_mngr_fsm_state_handler_late(struct mtk_fsm_param *fsm_param, void *arg);
int mtk_port_mngr_init(struct mtk_ctrl_blk *ctrl_blk, struct mtk_port_cfg *port_cfg, int port_cnt);
void mtk_port_mngr_exit(struct mtk_ctrl_blk *ctrl_blk);
void mtk_port_trb_init(struct mtk_port *port, struct trb *trb, enum mtk_trb_cmd_type cmd,
diff --git a/drivers/net/wwan/t9xx/mtk_utility.h b/drivers/net/wwan/t9xx/mtk_utility.h
new file mode 100644
index 000000000000..b72db3842d2d
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_utility.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_UTILITY_H__
+#define __MTK_UTILITY_H__
+
+#include <linux/device.h>
+#include "mtk_dev.h"
+
+#define MTK_UEVENT_INFO_LEN 128
+
+/* MTK uevent */
+enum mtk_uevent_id {
+ MTK_UEVENT_UNDEF = 0,
+ MTK_UEVENT_FSM = 1,
+ MTK_UEVENT_MINIDUMP = 2,
+ MTK_UEVENT_LOWPOWER = 3,
+ MTK_UEVENT_MAX
+};
+
+static inline void mtk_uevent_notify(struct device *dev, enum mtk_uevent_id id, const char *info)
+{
+ char buf[MTK_UEVENT_INFO_LEN];
+ char *ext[2] = {NULL, NULL};
+
+ snprintf(buf, MTK_UEVENT_INFO_LEN, "%s:event_id=%d, info=%s",
+ dev->kobj.name, id, info);
+ ext[0] = buf;
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, ext);
+}
+#endif /* __MTK_UTILITY_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
index 7a0815aa2fc8..0ad475d31c34 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.c
@@ -34,12 +34,172 @@
#define CLDMA_RETRY_DELAY_MS (100)
#define NO_BUDGET (0)
+static struct cldma_drv_info_desc cldma_drv_info_tbl[] = {
+ {0x01CA, &drv_ops_name(m9xx), &cldma_regs_name(m9xx)},
+ {0, NULL},
+};
+
+static void mtk_cldma_get_drv_info(struct cldma_drv_info *drv_info, u32 hw_ver)
+{
+ struct cldma_drv_info_desc *p_drv_info;
+ u8 i;
+
+ for (i = 0; (p_drv_info = &cldma_drv_info_tbl[i]) && p_drv_info &&
+ p_drv_info->drv_ops && p_drv_info->hw_regs; i++)
+ if (p_drv_info->hw_ver == hw_ver) {
+ drv_info->drv_ops = p_drv_info->drv_ops;
+ drv_info->hw_regs = p_drv_info->hw_regs;
+ }
+}
+
+static int mtk_cldma_isr(int irq_id, void *param)
+{
+ struct cldma_drv_info *drv_info = param;
+ struct mtk_md_dev *mdev;
+ u32 tx_done, rx_done;
+ u32 tx_sta, rx_sta;
+ struct txq *txq;
+ struct rxq *rxq;
+ int i;
+
+ mdev = drv_info->mdev;
+ drv_info->drv_ops->cldma_get_intr_status(drv_info, &tx_sta, &rx_sta);
+ tx_done = (tx_sta >> QUEUE_XFER_DONE) & 0xFF;
+ rx_done = (rx_sta >> QUEUE_XFER_DONE) & 0xFF;
+
+ if (tx_done) {
+ for (i = 0; i < HW_QUEUE_NUM; i++) {
+ txq = drv_info->txq[i];
+ if (!(tx_done & BIT(i)) || !txq)
+ continue;
+ queue_work(drv_info->wq, &txq->tx_done_work);
+ }
+ }
+ if (rx_done) {
+ for (i = 0; i < HW_QUEUE_NUM; i++) {
+ rxq = drv_info->rxq[i];
+ if (!(rx_done & BIT(i)) || !rxq)
+ continue;
+ queue_work(drv_info->wq, &rxq->rx_done_work);
+ }
+ }
+
+ mtk_pci_clear_irq(mdev, drv_info->pci_ext_irq_id);
+ mtk_pci_unmask_irq(mdev, drv_info->pci_ext_irq_id);
+
+ return IRQ_HANDLED;
+}
+
static const int mtk_cldma_hw_id_tbl[NR_CLDMA] = {
[CLDMA0] = CLDMA0_HW_ID,
[CLDMA1] = CLDMA1_HW_ID,
- [CLDMA4] = CLDMA4_HW_ID,
};
+static int mtk_cldma_dev_init(struct cldma_dev *cd, int hif_id)
+{
+ char gpd_pool_name[DMA_POOL_NAME_LEN];
+ char bd_pool_name[DMA_POOL_NAME_LEN];
+ struct cldma_drv_info *drv_info;
+ struct cldma_hw_regs *hw_regs;
+ struct mtk_md_dev *mdev;
+ unsigned int flag;
+ int hw_id, ret;
+
+ if (!cd || hif_id >= NR_CLDMA)
+ return -EINVAL;
+
+ if (cd->cldma_drv_info[hif_id])
+ return 0;
+
+ hw_id = mtk_cldma_hw_id_tbl[hif_id];
+ mdev = cd->trans->mdev;
+ drv_info = devm_kzalloc(mdev->dev, sizeof(*drv_info), GFP_KERNEL);
+ if (!drv_info)
+ return -ENOMEM;
+
+ drv_info->cd = cd;
+ drv_info->mdev = mdev;
+ drv_info->hif_id = hif_id;
+ drv_info->hw_id = hw_id;
+ mtk_cldma_get_drv_info(drv_info, mdev->hw_ver);
+
+ if (!drv_info->drv_ops || !drv_info->hw_regs) {
+ dev_err((mdev)->dev, "Failed to find CLDMA Driver for PCI %x\n", mdev->hw_ver);
+ ret = -EIO;
+ goto err_free_drv_info;
+ }
+
+ hw_regs = drv_info->hw_regs;
+ snprintf(gpd_pool_name, DMA_POOL_NAME_LEN, "cldma%d_gpd_pool_%s",
+ hw_id, mdev->dev_str);
+ snprintf(bd_pool_name, DMA_POOL_NAME_LEN, "cldma%d_bd_pool_%s",
+ hw_id, mdev->dev_str);
+ drv_info->gpd_dma_pool = dma_pool_create(gpd_pool_name, mdev->dev,
+ sizeof(union gpd), 4, 0);
+ if (!drv_info->gpd_dma_pool) {
+ dev_err((mdev)->dev, "Failed to alloc gpd dma pool for cldma%d\n", hw_id);
+ ret = -ENOMEM;
+ goto err_free_drv_info;
+ }
+ drv_info->bd_dma_pool = dma_pool_create(bd_pool_name, mdev->dev,
+ sizeof(union bd), 4, 0);
+ if (!drv_info->bd_dma_pool) {
+ dev_err((mdev)->dev, "Failed to alloc bd dma pool for cldma%d\n", hw_id);
+ ret = -ENOMEM;
+ goto err_destroy_gpd_pool;
+ }
+
+ switch (hif_id) {
+ case CLDMA0:
+ drv_info->pci_ext_irq_id = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_CLDMA0);
+ drv_info->base_addr = hw_regs->cldma0_base_addr;
+ break;
+ case CLDMA1:
+ drv_info->pci_ext_irq_id = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_CLDMA1);
+ drv_info->base_addr = hw_regs->cldma1_base_addr;
+ break;
+ default:
+ ret = -EINVAL;
+ goto err_destroy_dma_pool;
+ }
+
+ flag = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI;
+ drv_info->wq = alloc_workqueue("cldma%d_workq_%s", flag, 0, hw_id, mdev->dev_str);
+ if (!drv_info->wq) {
+ dev_err((mdev)->dev, "Failed to alloc work queue for cldma%d\n", hw_id);
+ ret = -ENOMEM;
+ goto err_destroy_dma_pool;
+ }
+
+ drv_info->drv_ops->cldma_drv_init(drv_info);
+
+ /* mask/clear PCI CLDMA L1 interrupt */
+ mtk_pci_mask_irq(mdev, drv_info->pci_ext_irq_id);
+ mtk_pci_clear_irq(mdev, drv_info->pci_ext_irq_id);
+
+ /* register CLDMA interrupt handler */
+ ret = mtk_pci_register_irq(mdev, drv_info->pci_ext_irq_id, mtk_cldma_isr, drv_info);
+ if (ret)
+ goto err_destroy_wq;
+
+ /* unmask PCI CLDMA L1 interrupt */
+ mtk_pci_unmask_irq(mdev, drv_info->pci_ext_irq_id);
+
+ cd->cldma_drv_info[hif_id] = drv_info;
+ return 0;
+
+err_destroy_wq:
+ destroy_workqueue(drv_info->wq);
+err_destroy_dma_pool:
+ dma_pool_destroy(drv_info->bd_dma_pool);
+err_destroy_gpd_pool:
+ dma_pool_destroy(drv_info->gpd_dma_pool);
+err_free_drv_info:
+ devm_kfree(mdev->dev, drv_info);
+
+ return ret;
+}
+
static inline void mtk_cldma_clr_bd_dsc(struct cldma_drv_info *drv_info,
struct bd_dsc *bd_dsc_pool, int nr_bds)
{
@@ -824,6 +984,7 @@ static void mtk_cldma_rxq_free(struct cldma_drv_info *drv_info, u32 rxqno)
if (req->skb) {
if (rxq->nr_bds) {
skb_shinfo(req->skb)->frag_list = NULL;
+ dev_kfree_skb_any(req->skb);
} else {
if (req->data_dma_addr)
dma_unmap_single(mdev->dev, req->data_dma_addr,
@@ -853,6 +1014,44 @@ static void mtk_cldma_rxq_free(struct cldma_drv_info *drv_info, u32 rxqno)
devm_kfree(mdev->dev, rxq);
}
+static int mtk_cldma_dev_exit(struct cldma_dev *cd, int hif_id)
+{
+ struct cldma_drv_info *drv_info;
+ struct mtk_md_dev *mdev;
+ int virq_id;
+ int i;
+
+ if (!cd || hif_id >= NR_CLDMA)
+ return -EINVAL;
+
+ if (!cd->cldma_drv_info[hif_id])
+ return 0;
+
+ /* free cldma descriptor */
+ drv_info = cd->cldma_drv_info[hif_id];
+ mdev = cd->trans->mdev;
+ virq_id = mtk_pci_get_virq_id(mdev, drv_info->pci_ext_irq_id);
+ mtk_pci_mask_irq(mdev, drv_info->pci_ext_irq_id);
+ synchronize_irq(virq_id);
+ for (i = 0; i < HW_QUEUE_NUM; i++) {
+ if (drv_info->txq[i])
+ mtk_cldma_txq_free(drv_info, drv_info->txq[i]->txqno);
+ if (drv_info->rxq[i])
+ mtk_cldma_rxq_free(drv_info, drv_info->rxq[i]->rxqno);
+ }
+
+ flush_workqueue(drv_info->wq);
+ destroy_workqueue(drv_info->wq);
+ dma_pool_destroy(drv_info->bd_dma_pool);
+ dma_pool_destroy(drv_info->gpd_dma_pool);
+ mtk_pci_unregister_irq(mdev, drv_info->pci_ext_irq_id);
+
+ devm_kfree(mdev->dev, drv_info);
+ cd->cldma_drv_info[hif_id] = NULL;
+
+ return 0;
+}
+
static int mtk_cldma_start_xfer(struct cldma_drv_info *drv_info, u32 qno)
{
struct cldma_drv_ops *drv_ops;
@@ -1163,6 +1362,27 @@ int mtk_cldma_trb_process(void *dev, struct sk_buff *skb)
return trb_act_tbl[trb->cmd](cd, skb);
}
+void mtk_cldma_fsm_state_listener(struct mtk_fsm_param *param, struct mtk_ctrl_trans *trans)
+{
+ struct cldma_dev *cd = trans->dev;
+ int i;
+
+ switch (param->to) {
+ case FSM_STATE_BOOTUP:
+ if (param->fsm_flag & FSM_F_SAP_HS_START)
+ mtk_cldma_dev_init(cd, CLDMA0);
+ else if (param->fsm_flag & FSM_F_MD_HS_START)
+ mtk_cldma_dev_init(cd, CLDMA1);
+ break;
+ case FSM_STATE_OFF:
+ for (i = 0; i < NR_CLDMA; i++)
+ mtk_cldma_dev_exit(cd, i);
+ break;
+ default:
+ break;
+ }
+}
+
int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que)
{
struct cldma_drv_info *drv_info;
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
index 74ce4f2f0b30..4686f7b178e5 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma.h
@@ -167,4 +167,7 @@ int mtk_cldma_check_ch_cfg(void *dev, struct queue_info *que);
#define drv_ops_name(NAME) cldma_drv_ops_##NAME
#define cldma_regs_name(NAME) mtk_cldma_regs_##NAME
+extern struct cldma_drv_ops cldma_drv_ops_m9xx;
+extern struct cldma_hw_regs mtk_cldma_regs_m9xx;
+
#endif
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
index 8763c23abf54..6de87b7ffd45 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv.h
@@ -11,7 +11,6 @@
#define LINK_ERROR_VAL (0xFFFFFFFF)
#define CLDMA0_HW_ID (0)
#define CLDMA1_HW_ID (1)
-#define CLDMA4_HW_ID (4)
struct cldma_hw_regs {
u8 cldma_rx_skb_pool_max_size;
@@ -36,7 +35,6 @@ struct cldma_hw_regs {
u16 reg_cldma_l2rimsr0;
u16 reg_cldma_l2rimsr1;
u16 reg_cldma_int_mask;
- u16 reg_cldma4_int_mask;
u16 reg_cldma_slp_mem_ctl;
u16 reg_cldma_busy_mask;
u16 reg_cldma_ip_busy_to_pcie_mask;
@@ -58,7 +56,6 @@ struct cldma_hw_regs {
u32 rq_err_int_bitmask;
u32 cldma0_base_addr;
u32 cldma1_base_addr;
- u32 cldma4_base_addr;
u32 rq_active_start_err_int_bitmask;
u32 reg_cldma_ul_start_addrl_0;
u32 reg_cldma_ul_start_addrh_0;
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
index d9145d146a5c..a59c35fc1577 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.c
@@ -34,7 +34,6 @@
struct cldma_hw_regs mtk_cldma_regs_m9xx = {
.cldma0_base_addr = CLDMA0_BASE_ADDR,
.cldma1_base_addr = CLDMA1_BASE_ADDR,
- .cldma4_base_addr = CLDMA4_BASE_ADDR,
.cldma_rx_skb_pool_max_size = CLDMA_RX_SKB_POOL_MAX_SIZE,
.cldma_rx_skb_reload_threshold = CLDMA_RX_SKB_RELOAD_THRESHOLD,
.tq_err_int_offset = TQ_ERR_INT_OFFSET,
@@ -93,7 +92,6 @@ struct cldma_hw_regs mtk_cldma_regs_m9xx = {
.reg_cldma_l3risar1 = REG_CLDMA_L3RISAR1,
.reg_cldma_ip_busy = REG_CLDMA_IP_BUSY,
.reg_cldma_int_mask = REG_CLDMA_INT_EAP_USIP_MASK,
- .reg_cldma4_int_mask = REG_CLDMA_INT_WF_MASK,
.reg_cldma_ip_busy_to_pcie_mask = REG_CLDMA_IP_BUSY_TO_PCIE_MASK,
.reg_cldma_ip_busy_to_pcie_mask_set = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_SET,
.reg_cldma_ip_busy_to_pcie_mask_clr = REG_CLDMA_IP_BUSY_TO_PCIE_MASK_CLR,
@@ -135,10 +133,7 @@ static void mtk_cldma_drv_init_m9xx(struct cldma_drv_info *drv_info)
ALLQ << 24);
/* enable interrupt to PCIe */
- if (drv_info->hw_id == CLDMA4_HW_ID)
- mtk_pci_write32(mdev, base + hw_regs->reg_cldma4_int_mask, 0);
- else
- mtk_pci_write32(mdev, base + hw_regs->reg_cldma_int_mask, 0);
+ mtk_pci_write32(mdev, base + hw_regs->reg_cldma_int_mask, 0);
/* disable illegal memory check */
mtk_pci_write32(mdev, base + hw_regs->reg_cldma_ul_dummy_0, 1);
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
index 2c63c43ff065..f113c4c1068a 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
+++ b/drivers/net/wwan/t9xx/pcie/mtk_cldma_drv_m9xx.h
@@ -8,7 +8,6 @@
#define CLDMA0_BASE_ADDR (0x1021C000)
#define CLDMA1_BASE_ADDR (0x1021E000)
-#define CLDMA4_BASE_ADDR (0x10224000)
#define CLDMA_RX_SKB_POOL_MAX_SIZE (64)
#define CLDMA_RX_SKB_RELOAD_THRESHOLD (16)
@@ -80,7 +79,6 @@
#define REG_CLDMA_L2RIMSR1 (0x0800 + 0x00FC)
#define REG_CLDMA_INT_EAP_USIP_MASK (0x0800 + 0x011C)
-#define REG_CLDMA_INT_WF_MASK (0x0800 + 0x0120)
#define REG_CLDMA_RQ1_GPD_DONE_CNT (0x0800 + 0x0174)
#define REG_CLDMA_TQ1_GPD_DONE_CNT (0x0800 + 0x0184)
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
index d3f862098a1d..4b93da5833db 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_pci.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
@@ -897,22 +897,34 @@ static int mtk_pci_dev_init(struct mtk_md_dev *mdev)
{
int ret;
- ret = mtk_trans_ctrl_init(mdev);
+ ret = mtk_fsm_init(mdev);
if (ret) {
- dev_err(mdev->dev, "Failed to initialize control plane: %d\n", ret);
+ dev_err(mdev->dev, "Failed to initialize FSM: %d\n", ret);
return ret;
}
+ ret = mtk_trans_ctrl_init(mdev);
+ if (ret)
+ goto free_fsm;
+
return 0;
+free_fsm:
+ mtk_fsm_exit(mdev);
+ return ret;
}
static void mtk_pci_dev_exit(struct mtk_md_dev *mdev)
{
+ mtk_fsm_evt_submit(mdev, FSM_EVT_DEV_RM, 0, NULL, 0,
+ EVT_MODE_BLOCKING | EVT_MODE_TOHEAD);
mtk_trans_ctrl_exit(mdev);
+ mtk_fsm_exit(mdev);
}
static int mtk_pci_dev_start(struct mtk_md_dev *mdev)
{
+ mtk_fsm_evt_submit(mdev, FSM_EVT_DEV_ADD, 0, NULL, 0, 0);
+ mtk_fsm_start(mdev);
return 0;
}
static const struct mtk_dev_ops pci_hw_ops = {
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
index 6eeed6935550..f905c9055b2b 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
@@ -481,6 +481,15 @@ static int mtk_pcie_hif_submit_skb(struct mtk_md_dev *mdev, struct sk_buff *skb,
return 0;
}
+static void mtk_pcie_hif_fsm_indication(struct mtk_md_dev *mdev, struct mtk_fsm_param *param)
+{
+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+ struct mtk_ctrl_trans *trans;
+
+ trans = ctrl_blk->ctrl_hw_priv;
+ mtk_cldma_fsm_state_listener(param, trans);
+}
+
static int mtk_pcie_hif_cmd_func(struct mtk_md_dev *mdev, int cmd, void *data)
{
struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
@@ -508,6 +517,7 @@ static struct mtk_ctrl_hif_ops pcie_ctrl_ops = {
.init = mtk_pcie_hif_init,
.exit = mtk_pcie_hif_exit,
.submit_skb = mtk_pcie_hif_submit_skb,
+ .fsm_indication = mtk_pcie_hif_fsm_indication,
.send_cmd = mtk_pcie_hif_cmd_func,
};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
index a3ff56ddf86f..4b9c9db6ad71 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
@@ -29,7 +29,6 @@
enum mtk_hif_id {
CLDMA0,
CLDMA1,
- CLDMA4,
NR_CLDMA
};
--
2.34.1
^ permalink raw reply related
* [PATCH v3 2/7] net: wwan: t9xx: Add control plane transaction layer
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
The control plane implements TX services that reside in the
transaction layer. The services receive the packets from the
port layer and call the corresponding DMA components to
transmit data to the device. Meanwhile, TX services receive
and manage the port control commands from the port layer.
The control plane implements RX services that reside in the
transaction layer. The services receive the downlink packets
from the modem and transfer the packets to the corresponding
port layer interfaces.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
drivers/net/wwan/Kconfig | 5 +++
drivers/net/wwan/t9xx/Makefile | 5 +--
drivers/net/wwan/t9xx/mtk_ctrl_plane.c | 48 +++++++++++++++++++++++++++++
drivers/net/wwan/t9xx/mtk_ctrl_plane.h | 22 +++++++++++++
drivers/net/wwan/t9xx/mtk_dev.c | 44 ++++++++++++++++++++++++++
drivers/net/wwan/t9xx/mtk_dev.h | 5 +++
drivers/net/wwan/t9xx/pcie/Makefile | 10 ++++++
drivers/net/wwan/t9xx/pcie/mtk_pci.c | 10 +++---
drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h | 21 +++++++++++++
9 files changed, 163 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
index 4cee537c739f..7019b44494f8 100644
--- a/drivers/net/wwan/Kconfig
+++ b/drivers/net/wwan/Kconfig
@@ -124,6 +124,7 @@ config MTK_T7XX
config MTK_T9XX
tristate "MediaTek PCIe 5G WWAN modem T9xx device"
depends on PCI
+ select MTK_T9XX_PCI
select NET_DEVLINK
help
Enables MediaTek PCIe based 5G WWAN modem (T9xx series) device.
@@ -133,6 +134,10 @@ config MTK_T9XX
If unsure, say N.
+config MTK_T9XX_PCI
+ tristate
+ depends on PCI
+
endif # WWAN
endmenu
diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
index 6f2dd3f91454..ae9d6f2344ab 100644
--- a/drivers/net/wwan/t9xx/Makefile
+++ b/drivers/net/wwan/t9xx/Makefile
@@ -4,7 +4,8 @@ ccflags-y += -I$(src)/pcie
ccflags-y += -I$(src)
obj-$(CONFIG_MTK_T9XX) += mtk_t9xx.o
+obj-$(CONFIG_MTK_T9XX_PCI) += pcie/
mtk_t9xx-y := \
- pcie/mtk_pci.o \
- pcie/mtk_pci_drv_m9xx.o
+ mtk_dev.o \
+ mtk_ctrl_plane.o
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.c b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
new file mode 100644
index 000000000000..07938f3e6fe2
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ * Copyright (c) 2022-2023, Intel Corporation.
+ */
+
+#include <linux/device.h>
+
+#include "mtk_ctrl_plane.h"
+
+/**
+ * mtk_ctrl_init() - Initialize the control plane block.
+ * @mdev: Pointer to the MTK modem device.
+ *
+ * Allocates and initializes the control plane block
+ * associated with @mdev.
+ *
+ * Return: 0 on success, -ENOMEM on allocation failure.
+ */
+int mtk_ctrl_init(struct mtk_md_dev *mdev)
+{
+ struct mtk_ctrl_blk *ctrl_blk;
+
+ ctrl_blk = devm_kzalloc(mdev->dev, sizeof(*ctrl_blk), GFP_KERNEL);
+ if (!ctrl_blk)
+ return -ENOMEM;
+
+ ctrl_blk->mdev = mdev;
+ mdev->ctrl_blk = ctrl_blk;
+
+ return 0;
+}
+EXPORT_SYMBOL(mtk_ctrl_init);
+
+/**
+ * mtk_ctrl_exit() - Clean up the control plane block.
+ * @mdev: Pointer to the MTK modem device.
+ *
+ * Frees the control plane block associated with @mdev.
+ */
+void mtk_ctrl_exit(struct mtk_md_dev *mdev)
+{
+ struct mtk_ctrl_blk *ctrl_blk = mdev->ctrl_blk;
+
+ devm_kfree(mdev->dev, ctrl_blk);
+ mdev->ctrl_blk = NULL;
+}
+EXPORT_SYMBOL(mtk_ctrl_exit);
diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
new file mode 100644
index 000000000000..c141876ef95d
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_CTRL_PLANE_H__
+#define __MTK_CTRL_PLANE_H__
+
+#include <linux/kref.h>
+#include <linux/skbuff.h>
+
+#include "mtk_dev.h"
+
+struct mtk_ctrl_blk {
+ struct mtk_md_dev *mdev;
+ struct mtk_ctrl_trans *trans;
+};
+
+int mtk_ctrl_init(struct mtk_md_dev *mdev);
+void mtk_ctrl_exit(struct mtk_md_dev *mdev);
+
+#endif /* __MTK_CTRL_PLANE_H__ */
diff --git a/drivers/net/wwan/t9xx/mtk_dev.c b/drivers/net/wwan/t9xx/mtk_dev.c
new file mode 100644
index 000000000000..f254ca7ed877
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_dev.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/module.h>
+
+#include "mtk_dev.h"
+
+struct mtk_md_dev *mtk_dev_alloc(struct device *pdev, const struct mtk_dev_ops *dev_ops)
+{
+ struct mtk_md_dev *mdev;
+
+ mdev = devm_kzalloc(pdev, sizeof(*mdev), GFP_KERNEL);
+ if (!mdev)
+ return NULL;
+
+ mdev->dev_ops = dev_ops;
+ mdev->dev = pdev;
+ return mdev;
+}
+EXPORT_SYMBOL(mtk_dev_alloc);
+
+void mtk_dev_free(struct mtk_md_dev *mdev)
+{
+ struct device *dev = mdev->dev;
+
+ devm_kfree(dev, mdev);
+}
+EXPORT_SYMBOL(mtk_dev_free);
+
+static int __init mtk_common_drv_init(void)
+{
+ return 0;
+}
+module_init(mtk_common_drv_init);
+
+static void __exit mtk_common_drv_exit(void)
+{
+}
+module_exit(mtk_common_drv_exit);
+
+MODULE_DESCRIPTION("MediaTek T9xx PCIe WWAN driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wwan/t9xx/mtk_dev.h b/drivers/net/wwan/t9xx/mtk_dev.h
index 8278a0e2875e..bb3ea68890ea 100644
--- a/drivers/net/wwan/t9xx/mtk_dev.h
+++ b/drivers/net/wwan/t9xx/mtk_dev.h
@@ -36,6 +36,7 @@ enum mtk_dev_evt_d2h {
};
struct mtk_md_dev;
+struct mtk_ctrl_blk;
struct mtk_dev_ops {
u32 (*get_dev_state)(struct mtk_md_dev *mdev);
@@ -57,6 +58,7 @@ struct mtk_md_dev {
void *hw_priv;
u32 hw_ver;
char dev_str[MTK_DEV_STR_LEN];
+ struct mtk_ctrl_blk *ctrl_blk;
};
static inline u32 mtk_dev_get_dev_state(struct mtk_md_dev *mdev)
@@ -105,4 +107,7 @@ static inline int mtk_dev_send_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
return mdev->dev_ops->send_dev_evt(mdev, dev_evt);
}
+struct mtk_md_dev *mtk_dev_alloc(struct device *pdev, const struct mtk_dev_ops *dev_ops);
+void mtk_dev_free(struct mtk_md_dev *mdev);
+
#endif /* __MTK_DEV_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/Makefile b/drivers/net/wwan/t9xx/pcie/Makefile
new file mode 100644
index 000000000000..7410d1796d27
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+ccflags-y += -I$(src)
+ccflags-y += -I$(src)/..
+
+obj-$(CONFIG_MTK_T9XX_PCI) += mtk_t9xx_pcie.o
+
+mtk_t9xx_pcie-y := \
+ mtk_pci_drv_m9xx.o \
+ mtk_pci.o
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
index c6a7196fcdd6..90b33dd6effd 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_pci.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
@@ -14,6 +14,7 @@
#include <linux/module.h>
#include "mtk_dev.h"
+#include "mtk_trans_ctrl.h"
#include "mtk_pci.h"
#include "mtk_pci_reg.h"
@@ -467,6 +468,7 @@ static u32 mtk_pci_ext_h2d_evt_hw_bits(u32 chs)
SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DEVICE_RESET,
DEV_EVT_H2D_DEVICE_RESET);
+
return LE32_TO_U32(cpu_to_le32(hw_bits));
}
@@ -908,13 +910,11 @@ static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
struct mtk_md_dev *mdev;
int ret;
- mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
+ mdev = mtk_dev_alloc(dev, &pci_hw_ops);
if (!mdev) {
ret = -ENOMEM;
goto log_err;
}
- mdev->dev_ops = &pci_hw_ops;
- mdev->dev = dev;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
@@ -991,7 +991,7 @@ static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
free_priv_data:
devm_kfree(dev, priv);
free_cntx_data:
- devm_kfree(dev, mdev);
+ mtk_dev_free(mdev);
log_err:
dev_err(dev, "Failed to probe device, ret=%d\n", ret);
@@ -1017,7 +1017,7 @@ static void mtk_pci_remove(struct pci_dev *pdev)
pci_load_and_free_saved_state(pdev, &priv->saved_state);
devm_kfree(dev, priv);
- devm_kfree(dev, mdev);
+ mtk_dev_free(mdev);
}
static pci_ers_result_t mtk_pci_error_detected(struct pci_dev *pdev,
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
new file mode 100644
index 000000000000..d6de4c43b529
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_TRANS_CTRL_H__
+#define __MTK_TRANS_CTRL_H__
+
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+#include "mtk_dev.h"
+
+struct mtk_ctrl_trans {
+ struct mtk_ctrl_blk *ctrl_blk;
+ struct mtk_md_dev *mdev;
+};
+
+#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v3 1/7] net: wwan: t9xx: Add PCIe core
From: Jack Wu via B4 Relay @ 2026-06-24 10:04 UTC (permalink / raw)
To: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Jack Wu, Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng,
Matthias Brugger, AngeloGioacchino Del Regno, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: linux-kernel, netdev, linux-arm-kernel, linux-mediatek, linux-doc
In-Reply-To: <20260624-t9xx_driver_v1-v3-0-73ff03f60c48@compal.com>
From: Jack Wu <jackbb_wu@compal.com>
Registers the T900 device driver with the kernel. Set up all
the fundamental configurations for the device: PCIe layer,
Modem Host Cross Core Interface (MHCCIF), Reset Generation
Unit (RGU), modem common control operations and build
infrastructure.
* PCIe layer code implements driver probe and removal, MSI-X
interrupt initialization and de-initialization, and the way
of resetting the device.
* MHCCIF provides interrupt channels to communicate events
such as handshake, PM and port enumeration.
* RGU provides interrupt channels to generate notifications
from the device so that the T900 driver could get the
device reset.
* Modem common control operations provide the basic read/write
functions of the device's hardware registers,
mask/unmask/get/clear functions of the device's interrupt
registers and inquiry functions of the device's status.
Signed-off-by: Jack Wu <jackbb_wu@compal.com>
---
drivers/net/wwan/Kconfig | 12 +
drivers/net/wwan/Makefile | 1 +
drivers/net/wwan/t9xx/Makefile | 10 +
drivers/net/wwan/t9xx/mtk_dev.h | 108 +++
drivers/net/wwan/t9xx/pcie/mtk_pci.c | 1049 +++++++++++++++++++++++++
drivers/net/wwan/t9xx/pcie/mtk_pci.h | 234 ++++++
drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c | 69 ++
drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h | 70 ++
8 files changed, 1553 insertions(+)
diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig
index 88df55d78d90..4cee537c739f 100644
--- a/drivers/net/wwan/Kconfig
+++ b/drivers/net/wwan/Kconfig
@@ -121,6 +121,18 @@ config MTK_T7XX
If unsure, say N.
+config MTK_T9XX
+ tristate "MediaTek PCIe 5G WWAN modem T9xx device"
+ depends on PCI
+ select NET_DEVLINK
+ help
+ Enables MediaTek PCIe based 5G WWAN modem (T9xx series) device.
+
+ To compile this driver as a module, choose M here: the module will be
+ called mtk_t9xx.
+
+ If unsure, say N.
+
endif # WWAN
endmenu
diff --git a/drivers/net/wwan/Makefile b/drivers/net/wwan/Makefile
index 3960c0ae2445..7361eef4c472 100644
--- a/drivers/net/wwan/Makefile
+++ b/drivers/net/wwan/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_QCOM_BAM_DMUX) += qcom_bam_dmux.o
obj-$(CONFIG_RPMSG_WWAN_CTRL) += rpmsg_wwan_ctrl.o
obj-$(CONFIG_IOSM) += iosm/
obj-$(CONFIG_MTK_T7XX) += t7xx/
+obj-$(CONFIG_MTK_T9XX) += t9xx/
diff --git a/drivers/net/wwan/t9xx/Makefile b/drivers/net/wwan/t9xx/Makefile
new file mode 100644
index 000000000000..6f2dd3f91454
--- /dev/null
+++ b/drivers/net/wwan/t9xx/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+ccflags-y += -I$(src)/pcie
+ccflags-y += -I$(src)
+
+obj-$(CONFIG_MTK_T9XX) += mtk_t9xx.o
+
+mtk_t9xx-y := \
+ pcie/mtk_pci.o \
+ pcie/mtk_pci_drv_m9xx.o
diff --git a/drivers/net/wwan/t9xx/mtk_dev.h b/drivers/net/wwan/t9xx/mtk_dev.h
new file mode 100644
index 000000000000..8278a0e2875e
--- /dev/null
+++ b/drivers/net/wwan/t9xx/mtk_dev.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_DEV_H__
+#define __MTK_DEV_H__
+
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define MTK_DEV_STR_LEN 16
+
+enum mtk_user_id {
+ MTK_USER_MIN,
+ MTK_USER_CTRL,
+ MTK_USER_DATA,
+ MTK_USER_MAX
+};
+
+enum mtk_dev_evt_h2d {
+ DEV_EVT_H2D_DEVICE_RESET = BIT(2),
+ DEV_EVT_H2D_MAX = BIT(5)
+};
+
+enum mtk_dev_evt_d2h {
+ DEV_EVT_D2H_BOOT_FLOW_SYNC = BIT(4),
+ DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP = BIT(5),
+ DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD = BIT(6),
+ DEV_EVT_D2H_MAX = BIT(11)
+};
+
+struct mtk_md_dev;
+
+struct mtk_dev_ops {
+ u32 (*get_dev_state)(struct mtk_md_dev *mdev);
+ void (*ack_dev_state)(struct mtk_md_dev *mdev, u32 state);
+ u32 (*get_dev_cfg)(struct mtk_md_dev *mdev);
+ int (*register_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt,
+ int (*evt_cb)(u32 status, void *data), void *data);
+ void (*unregister_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+ void (*mask_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+ void (*unmask_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+ void (*clear_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+ int (*send_dev_evt)(struct mtk_md_dev *mdev, u32 dev_evt);
+};
+
+/* mtk_md_dev defines the structure of MTK modem device */
+struct mtk_md_dev {
+ struct device *dev;
+ const struct mtk_dev_ops *dev_ops;
+ void *hw_priv;
+ u32 hw_ver;
+ char dev_str[MTK_DEV_STR_LEN];
+};
+
+static inline u32 mtk_dev_get_dev_state(struct mtk_md_dev *mdev)
+{
+ return mdev->dev_ops->get_dev_state(mdev);
+}
+
+static inline void mtk_dev_ack_dev_state(struct mtk_md_dev *mdev, u32 state)
+{
+ return mdev->dev_ops->ack_dev_state(mdev, state);
+}
+
+static inline u32 mtk_dev_get_dev_cfg(struct mtk_md_dev *mdev)
+{
+ return mdev->dev_ops->get_dev_cfg(mdev);
+}
+
+static inline int mtk_dev_register_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt,
+ int (*evt_cb)(u32 status, void *data), void *data)
+{
+ return mdev->dev_ops->register_dev_evt(mdev, dev_evt, evt_cb, data);
+}
+
+static inline void mtk_dev_unregister_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+ mdev->dev_ops->unregister_dev_evt(mdev, dev_evt);
+}
+
+static inline void mtk_dev_mask_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+ mdev->dev_ops->mask_dev_evt(mdev, dev_evt);
+}
+
+static inline void mtk_dev_unmask_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+ mdev->dev_ops->unmask_dev_evt(mdev, dev_evt);
+}
+
+static inline void mtk_dev_clear_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+ mdev->dev_ops->clear_dev_evt(mdev, dev_evt);
+}
+
+static inline int mtk_dev_send_dev_evt(struct mtk_md_dev *mdev, u32 dev_evt)
+{
+ return mdev->dev_ops->send_dev_evt(mdev, dev_evt);
+}
+
+#endif /* __MTK_DEV_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.c b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
new file mode 100644
index 000000000000..c6a7196fcdd6
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.c
@@ -0,0 +1,1049 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#include <linux/acpi.h>
+#include <linux/aer.h>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "mtk_dev.h"
+#include "mtk_pci.h"
+#include "mtk_pci_reg.h"
+
+#define MTK_PCI_BAR_NUM 6
+#define MTK_PCI_TRANSPARENT_ATR_SIZE (0x3F)
+#define MTK_PCI_MINIMUM_ATR_SIZE (0x1000)
+#define ATR_SIZE_LO32_MASK GENMASK_ULL(31, 0)
+#define ATR_SIZE_HI32_MASK GENMASK_ULL(63, 32)
+#define ATR_SIZE_BIAS_FROM_LO32 2
+#define ATR_ADDR_ALIGN_MASK 0xFFFFF000
+#define ATR_EN BIT(0)
+#define ATR_PARAM_OFFSET 16
+/* Delay between ACPI PXP._OFF and _ON for modem power cycle stabilization */
+#define MTK_PLDR_POWER_OFF_DELAY_MS 500
+#define LE32_TO_U32(x) ((__force u32)(__le32)(x))
+#define SET_HW_BITS(dest, chs, mhccif, dev) \
+ ({ \
+ if ((chs) & (dev)) \
+ (dest) |= FIELD_PREP(mhccif, 1); \
+ })
+
+struct mtk_mhccif_cb {
+ struct list_head entry;
+ int (*evt_cb)(u32 status, void *data);
+ void *data;
+ u32 chs;
+};
+
+/**
+ * mtk_pci_setup_atr() - Configure a PCIe address translation rule
+ * @mdev: MTK MD device
+ * @cfg: ATR configuration parameters
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ u32 addr, val, size_h, size_l;
+ int atr_size, pos, offset;
+
+ if (cfg->transparent) {
+ /* No address conversion is performed */
+ atr_size = MTK_PCI_TRANSPARENT_ATR_SIZE;
+ } else {
+ if (cfg->size < MTK_PCI_MINIMUM_ATR_SIZE)
+ cfg->size = MTK_PCI_MINIMUM_ATR_SIZE;
+
+ if (cfg->src_addr & (cfg->size - 1)) {
+ dev_err((mdev)->dev, "Invalid atr src addr is not aligned to size\n");
+ return -EFAULT;
+ }
+
+ if (cfg->trsl_addr & (cfg->size - 1)) {
+ dev_err((mdev)->dev,
+ "Invalid atr trsl addr is not aligned to size, %llx, %llx\n",
+ cfg->trsl_addr, cfg->size - 1);
+ return -EFAULT;
+ }
+
+ size_l = FIELD_GET(ATR_SIZE_LO32_MASK, cfg->size);
+ size_h = FIELD_GET(ATR_SIZE_HI32_MASK, cfg->size);
+ pos = ffs(size_l);
+ if (pos) {
+ atr_size = pos - ATR_SIZE_BIAS_FROM_LO32;
+ } else {
+ pos = ffs(size_h);
+ atr_size = pos + 32 - ATR_SIZE_BIAS_FROM_LO32;
+ }
+ }
+
+ /* Calculate table offset */
+ offset = ATR_PORT_OFFSET * cfg->port + ATR_TABLE_OFFSET * cfg->table;
+ addr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB + offset;
+ val = (u32)(cfg->src_addr >> 32);
+ mtk_pci_mac_write32(priv, addr, val);
+
+ addr = REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset;
+ val = (u32)(cfg->src_addr & ATR_ADDR_ALIGN_MASK) | (atr_size << 1) | ATR_EN;
+ mtk_pci_mac_write32(priv, addr, val);
+
+ addr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB + offset;
+ val = (u32)(cfg->trsl_addr >> 32);
+ mtk_pci_mac_write32(priv, addr, val);
+
+ addr = REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB + offset;
+ val = (u32)(cfg->trsl_addr & ATR_ADDR_ALIGN_MASK);
+ mtk_pci_mac_write32(priv, addr, val);
+
+ /* TRSL_PARAM */
+ addr = REG_ATR_PCIE_WIN0_T0_TRSL_PARAM + offset;
+ val = (cfg->trsl_param << ATR_PARAM_OFFSET) | cfg->trsl_id;
+ mtk_pci_mac_write32(priv, addr, val);
+
+ return 0;
+}
+
+/**
+ * mtk_pci_atr_disable() - Disable all PCIe address translation rules
+ * @priv: MTK PCI private data
+ */
+void mtk_pci_atr_disable(struct mtk_pci_priv *priv)
+{
+ int port, tbl, offset;
+ u32 val;
+
+ /* Disable all ATR table for all ports */
+ for (port = ATR_SRC_PCI_WIN0; port <= ATR_SRC_AXIS_3; port++)
+ for (tbl = 0; tbl < ATR_TABLE_NUM_PER_ATR; tbl++) {
+ /* Calculate table offset */
+ offset = ATR_PORT_OFFSET * port + ATR_TABLE_OFFSET * tbl;
+ val = mtk_pci_mac_read32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset);
+ val = val & (~BIT(0));
+ /* Disable table by SRC_ADDR_L */
+ mtk_pci_mac_write32(priv, REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB + offset, val);
+ }
+}
+
+static void mtk_pci_set_msix_merged(struct mtk_pci_priv *priv, int irq_cnt)
+{
+ mtk_pci_mac_write32(priv, REG_PCIE_CFG_MSIX, ffs(irq_cnt) * 2 - 1);
+}
+
+/**
+ * mtk_pci_get_dev_state() - Read the device state from the modem
+ * @mdev: MTK MD device
+ *
+ * Return: Device state value.
+ */
+u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev)
+{
+ return mtk_pci_mac_read32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_7);
+}
+
+/**
+ * mtk_pci_ack_dev_state() - Acknowledge the device state to the modem
+ * @mdev: MTK MD device
+ * @state: State value to acknowledge
+ */
+void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state)
+{
+ mtk_pci_mac_write32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_7, state);
+}
+
+/**
+ * mtk_pci_get_irq_id() - Map an IRQ source to its hardware IRQ ID
+ * @mdev: MTK MD device
+ * @irq_src: IRQ source enum
+ *
+ * Return: IRQ ID on success, -EINVAL on failure.
+ */
+int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ const int *irq_tbl = priv->cfg->irq_tbl;
+ int irq_id = -EINVAL;
+
+ if (irq_src > MTK_IRQ_SRC_MIN && irq_src < MTK_IRQ_SRC_MAX) {
+ irq_id = irq_tbl[irq_src];
+ if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX)
+ irq_id = -EINVAL;
+ }
+
+ return irq_id;
+}
+
+/**
+ * mtk_pci_get_virq_id() - Get the Linux virtual IRQ for a hardware IRQ ID
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: Virtual IRQ number on success, negative error code on failure.
+ */
+int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id)
+{
+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+
+ if (!priv->irq_cnt || irq_id < 0)
+ return -EINVAL;
+
+ return pci_irq_vector(pdev, irq_id % priv->irq_cnt);
+}
+
+/**
+ * mtk_pci_register_irq() - Register a callback for a hardware IRQ
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ * @irq_cb: Callback function
+ * @data: Private data passed to callback
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,
+ int (*irq_cb)(int irq_id, void *data), void *data)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+
+ if ((irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX) || !irq_cb)
+ return -EINVAL;
+
+ if (priv->irq_cb_list[irq_id]) {
+ dev_err((mdev)->dev,
+ "Unable to register irq, irq_id=%d, it's already been register by %ps.\n",
+ irq_id, priv->irq_cb_list[irq_id]);
+ return -EFAULT;
+ }
+ priv->irq_cb_list[irq_id] = irq_cb;
+ priv->irq_cb_data[irq_id] = data;
+
+ return 0;
+}
+
+/**
+ * mtk_pci_unregister_irq() - Unregister a hardware IRQ callback
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+
+ if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX)
+ return -EINVAL;
+
+ if (!priv->irq_cb_list[irq_id]) {
+ dev_err((mdev)->dev, "irq_id=%d has not been registered\n", irq_id);
+ return -EFAULT;
+ }
+ priv->irq_cb_list[irq_id] = NULL;
+ priv->irq_cb_data[irq_id] = NULL;
+
+ return 0;
+}
+
+/**
+ * mtk_pci_mask_irq() - Mask (disable) a hardware IRQ
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+
+ if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX ||
+ priv->irq_type != PCI_IRQ_MSIX) {
+ dev_err(mdev->dev, "Failed to mask irq: input irq_id=%d\n", irq_id);
+ return -EINVAL;
+ }
+
+ mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, BIT(irq_id));
+
+ return 0;
+}
+
+/**
+ * mtk_pci_unmask_irq() - Unmask (enable) a hardware IRQ
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+
+ if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX ||
+ priv->irq_type != PCI_IRQ_MSIX) {
+ dev_err(mdev->dev, "Failed to unmask irq: input irq_id=%d\n", irq_id);
+ return -EINVAL;
+ }
+
+ mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_SET_GRP0_0, BIT(irq_id));
+
+ return 0;
+}
+
+/**
+ * mtk_pci_clear_irq() - Clear (acknowledge) a hardware IRQ
+ * @mdev: MTK MD device
+ * @irq_id: Hardware IRQ ID
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+
+ if (irq_id < 0 || irq_id >= MTK_IRQ_CNT_MAX ||
+ priv->irq_type != PCI_IRQ_MSIX) {
+ dev_err(mdev->dev, "Failed to clear irq: input irq_id=%d\n", irq_id);
+ return -EINVAL;
+ }
+
+ mtk_pci_mac_write32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0, BIT(irq_id));
+
+ return 0;
+}
+
+static u32 mtk_pci_ext_d2h_evt_hw_bits(u32 chs)
+{
+ u32 hw_bits = 0;
+
+ SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC,
+ DEV_EVT_D2H_BOOT_FLOW_SYNC);
+ SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP,
+ DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP);
+ SET_HW_BITS(hw_bits, chs, MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD,
+ DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD);
+
+ return LE32_TO_U32(cpu_to_le32(hw_bits));
+}
+
+static u32 mtk_pci_ext_d2h_evt_chs(u32 hw_bits)
+{
+ u32 chs = 0;
+
+ if (!hw_bits)
+ return chs;
+
+ chs = FIELD_PREP(DEV_EVT_D2H_BOOT_FLOW_SYNC,
+ FIELD_GET(MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC, hw_bits)) |
+ FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_SAP,
+ FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP, hw_bits)) |
+ FIELD_PREP(DEV_EVT_D2H_ASYNC_HS_NOTIFY_MD,
+ FIELD_GET(MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD, hw_bits));
+
+ return chs;
+}
+
+/**
+ * mtk_pci_register_ext_evt() - Register a callback for MHCCIF device events
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to register
+ * @evt_cb: Callback function
+ * @data: Private data passed to callback
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,
+ int (*evt_cb)(u32 status, void *data), void *data)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ struct mtk_mhccif_cb *cb;
+ int ret = 0;
+
+ if (!chs || !evt_cb)
+ return -EINVAL;
+
+ spin_lock_bh(&priv->mhccif_lock);
+ list_for_each_entry(cb, &priv->mhccif_cb_list, entry) {
+ if (cb->chs & chs) {
+ ret = -EFAULT;
+ dev_err((mdev)->dev,
+ "Unable to register evt, intersection: chs=0x%08x&0x%08x cb=%ps\n",
+ chs, cb->chs, cb->evt_cb);
+ goto err_spin_unlock;
+ }
+ }
+ cb = devm_kzalloc(mdev->dev, sizeof(*cb), GFP_ATOMIC);
+ if (!cb) {
+ ret = -ENOMEM;
+ goto err_spin_unlock;
+ }
+ cb->evt_cb = evt_cb;
+ cb->data = data;
+ cb->chs = chs;
+ list_add_tail(&cb->entry, &priv->mhccif_cb_list);
+err_spin_unlock:
+ spin_unlock_bh(&priv->mhccif_lock);
+
+ return ret;
+}
+
+/**
+ * mtk_pci_unregister_ext_evt() - Unregister an MHCCIF device event callback
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to unregister
+ */
+void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ struct mtk_mhccif_cb *cb, *next;
+
+ if (!chs)
+ return;
+
+ spin_lock_bh(&priv->mhccif_lock);
+ list_for_each_entry_safe(cb, next, &priv->mhccif_cb_list, entry) {
+ if (cb->chs == chs) {
+ list_del(&cb->entry);
+ devm_kfree(mdev->dev, cb);
+ goto out;
+ }
+ }
+ dev_warn((mdev)->dev,
+ "Unable to unregister evt, no chs=0x%08x has been registered.\n", chs);
+out:
+ spin_unlock_bh(&priv->mhccif_lock);
+}
+
+/**
+ * mtk_pci_mask_ext_evt() - Mask (disable) MHCCIF device events
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to mask
+ */
+void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ u32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
+
+ mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
+ MHCCIF_EP2RC_SW_INT_EAP_MASK_SET, hw_bits);
+}
+
+/**
+ * mtk_pci_unmask_ext_evt() - Unmask (enable) MHCCIF device events
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to unmask
+ */
+void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ u32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
+
+ mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
+ MHCCIF_EP2RC_SW_INT_EAP_MASK_CLR, hw_bits);
+}
+
+/**
+ * mtk_pci_clear_ext_evt() - Clear (acknowledge) MHCCIF device events
+ * @mdev: MTK MD device
+ * @chs: Bitmask of event channels to clear
+ */
+void mtk_pci_clear_ext_evt(struct mtk_md_dev *mdev, u32 chs)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ u32 hw_bits = mtk_pci_ext_d2h_evt_hw_bits(chs);
+
+ mtk_pci_write32(mdev, priv->cfg->mhccif_rc_base_addr +
+ MHCCIF_EP2RC_SW_INT_ACK, hw_bits);
+}
+
+static u32 mtk_pci_ext_h2d_evt_hw_bits(u32 chs)
+{
+ u32 hw_bits = 0;
+
+ SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DEVICE_RESET,
+ DEV_EVT_H2D_DEVICE_RESET);
+ return LE32_TO_U32(cpu_to_le32(hw_bits));
+}
+
+/**
+ * mtk_pci_send_ext_evt() - Send an MHCCIF event to the modem
+ * @mdev: MTK MD device
+ * @ch: Event channel to trigger (must be a single bit)
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ u32 rc_base, hw_bits;
+
+ rc_base = priv->cfg->mhccif_rc_base_addr;
+
+ /* Only allow one ch to be triggered at a time */
+ if (!is_power_of_2(ch)) {
+ dev_err((mdev)->dev, "Unsupported ext evt ch=0x%08x\n", ch);
+ return -EINVAL;
+ }
+
+ hw_bits = mtk_pci_ext_h2d_evt_hw_bits(ch);
+ mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_BSY, hw_bits);
+ mtk_pci_write32(mdev, rc_base + MHCCIF_RC2EP_SW_TCHNUM, ffs(hw_bits) - 1);
+ return 0;
+}
+
+static u32 mtk_pci_get_ext_evt_hw_status(struct mtk_md_dev *mdev)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+
+ return mtk_pci_read32(mdev, priv->cfg->mhccif_rc_base_addr +
+ MHCCIF_EP2RC_SW_INT_STS);
+}
+
+/**
+ * mtk_pci_fldr() - Perform a Function Level Device Reset via ACPI _RST
+ * @mdev: MTK MD device
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_fldr(struct mtk_md_dev *mdev)
+{
+#ifdef CONFIG_ACPI
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status acpi_ret;
+ acpi_handle handle;
+
+ if (acpi_disabled) {
+ dev_err((mdev)->dev, "Unsupported, acpi function isn't enable\n");
+ return -ENODEV;
+ }
+
+ handle = ACPI_HANDLE(mdev->dev);
+
+ if (!handle) {
+ dev_err((mdev)->dev, "Unsupported, acpi handle isn't found\n");
+ return -ENODEV;
+ }
+
+ if (!acpi_has_method(handle, "_RST")) {
+ dev_err((mdev)->dev, "Unsupported, _RST method isn't found\n");
+ return -ENODEV;
+ }
+
+ acpi_ret = acpi_evaluate_object(handle, "_RST", NULL, &buffer);
+ if (ACPI_FAILURE(acpi_ret)) {
+ dev_err((mdev)->dev, "Failed to execute _RST method: %s\n",
+ acpi_format_exception(acpi_ret));
+ return -EFAULT;
+ }
+
+ acpi_os_free(buffer.pointer);
+
+ return 0;
+#else /* !CONFIG_ACPI */
+ dev_err((mdev)->dev, "Unsupported, CONFIG ACPI hasn't been set to 'y'\n");
+
+ return -ENODEV;
+#endif /* !CONFIG_ACPI */
+}
+
+/**
+ * mtk_pci_pldr() - Perform a PCIe Link Down Reset via ACPI PXP._OFF/_ON
+ * @mdev: MTK MD device
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_pldr(struct mtk_md_dev *mdev)
+{
+#ifdef CONFIG_ACPI
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ struct pci_dev *bridge;
+ acpi_status acpi_ret;
+ acpi_handle handle;
+
+ if (acpi_disabled) {
+ dev_err((mdev)->dev, "Unsupported, acpi function isn't enable\n");
+ return -ENODEV;
+ }
+
+ bridge = pci_upstream_bridge(to_pci_dev(mdev->dev));
+ if (!bridge) {
+ dev_err((mdev)->dev, "Unable to find bridge\n");
+ return -ENODEV;
+ }
+
+ handle = ACPI_HANDLE(&bridge->dev);
+ if (!handle) {
+ dev_err((mdev)->dev, "Unsupported, acpi handle isn't found\n");
+ return -ENODEV;
+ }
+ if (!acpi_has_method(handle, "PXP._OFF") ||
+ !acpi_has_method(handle, "PXP._ON")) {
+ dev_err((mdev)->dev, "Unsupported, pldr method isn't supported\n");
+ return -ENODEV;
+ }
+ acpi_ret = acpi_evaluate_object(handle, "PXP._OFF", NULL, &buffer);
+ if (ACPI_FAILURE(acpi_ret)) {
+ dev_err((mdev)->dev, "Failed to execute _OFF method: %s\n",
+ acpi_format_exception(acpi_ret));
+ return -EFAULT;
+ }
+ acpi_os_free(buffer.pointer);
+
+ msleep(MTK_PLDR_POWER_OFF_DELAY_MS);
+
+ buffer.length = ACPI_ALLOCATE_BUFFER;
+ buffer.pointer = NULL;
+ acpi_ret = acpi_evaluate_object(handle, "PXP._ON", NULL, &buffer);
+ if (ACPI_FAILURE(acpi_ret)) {
+ dev_err((mdev)->dev, "Failed to execute _ON method: %s\n",
+ acpi_format_exception(acpi_ret));
+ return -EFAULT;
+ }
+ acpi_os_free(buffer.pointer);
+
+ return 0;
+#else
+ dev_err((mdev)->dev, "Unsupported, CONFIG ACPI hasn't been set to 'y'\n");
+
+ return -ENODEV;
+#endif
+}
+
+/**
+ * mtk_pci_get_dev_cfg() - Read the device configuration from the modem
+ * @mdev: MTK MD device
+ *
+ * Return: Device configuration value.
+ */
+u32 mtk_pci_get_dev_cfg(struct mtk_md_dev *mdev)
+{
+ u32 val;
+
+ val = mtk_pci_mac_read32(mdev->hw_priv, REG_PCIE_DEBUG_DUMMY_4);
+ return (val >> MTK_CFG_INFO_BIT_SHIFT);
+}
+
+static int mtk_pci_dev_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type)
+{
+ switch (type) {
+ case RESET_MHCCIF:
+ return mtk_pci_send_ext_evt(mdev, DEV_EVT_H2D_DEVICE_RESET);
+ case RESET_FLDR:
+ return mtk_pci_fldr(mdev);
+ case RESET_PLDR:
+ return mtk_pci_pldr(mdev);
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * mtk_pci_reset() - Reset the modem device
+ * @mdev: MTK MD device
+ * @type: Reset type (MHCCIF, FLDR, or PLDR)
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int mtk_pci_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type)
+{
+ return mtk_pci_dev_reset(mdev, type);
+}
+
+/**
+ * mtk_pci_link_check() - Check if the PCIe link to the modem is active
+ * @mdev: MTK MD device
+ *
+ * Return: true if the device is present, false otherwise.
+ */
+bool mtk_pci_link_check(struct mtk_md_dev *mdev)
+{
+ return pci_device_is_present(to_pci_dev(mdev->dev));
+}
+
+static void mtk_mhccif_isr_work(struct work_struct *work)
+{
+ struct mtk_pci_priv *priv =
+ container_of(work, struct mtk_pci_priv, mhccif_work);
+ struct mtk_md_dev *mdev = priv->irq_desc->mdev;
+ struct mtk_mhccif_cb *cb;
+ u32 stat, mask, chs;
+
+ stat = mtk_pci_get_ext_evt_hw_status(mdev);
+ mask = mtk_pci_read32(mdev, priv->cfg->mhccif_rc_base_addr
+ + MHCCIF_EP2RC_SW_INT_EAP_MASK);
+ if (unlikely(stat == U32_MAX && !(mtk_pci_link_check(mdev)))) {
+ /* When link failed, we don't need to unmask/clear. */
+ dev_err((mdev)->dev, "Failed to check link in MHCCIF handler.\n");
+ return;
+ }
+
+ stat &= ~mask;
+ chs = mtk_pci_ext_d2h_evt_chs(stat);
+ spin_lock_bh(&priv->mhccif_lock);
+ list_for_each_entry(cb, &priv->mhccif_cb_list, entry) {
+ if (cb->chs & chs)
+ cb->evt_cb(cb->chs & chs, cb->data);
+ }
+ spin_unlock_bh(&priv->mhccif_lock);
+
+ mtk_pci_clear_irq(mdev, priv->mhccif_irq_id);
+ mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
+}
+
+static const struct pci_device_id t9xx_pci_table[] = {
+ MTK_PCI_DEV_CFG(0x0900, mtk_dev_cfg_0900),
+ CEI_PCI_DEV_CFG(0x01CA, mtk_dev_cfg_0900),
+ {/* end: all zeroes */}
+};
+
+MODULE_DEVICE_TABLE(pci, t9xx_pci_table);
+
+static int mtk_pci_bar_init(struct mtk_md_dev *mdev)
+{
+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ u32 bar[MTK_PCI_BAR_NUM];
+ int i, ret;
+
+ for (i = 0; i < MTK_PCI_BAR_NUM; i++)
+ pci_read_config_dword(to_pci_dev(mdev->dev),
+ PCI_BASE_ADDRESS_0 + (i << 2), bar + i);
+
+ ret = pcim_iomap_regions(pdev, MTK_REQUESTED_BARS, mdev->dev_str);
+ if (ret) {
+ dev_err((mdev)->dev, "Failed to init MMIO. ret=%d\n", ret);
+ return ret;
+ }
+
+ /* get ioremapped memory */
+ priv->mac_reg_base = pcim_iomap_table(pdev)[MTK_BAR_0_1_IDX];
+ priv->bar23_addr = pcim_iomap_table(pdev)[MTK_BAR_2_3_IDX];
+ if (!priv->mac_reg_base || !priv->bar23_addr) {
+ dev_err((mdev)->dev, "Failed to init BAR.\n");
+ return -EINVAL;
+ }
+ /* We use MD view base address "0" to observe registers */
+ priv->ext_reg_base = priv->bar23_addr - ATR_PCIE_REG_TRSL_ADDR;
+
+ return 0;
+}
+
+static int mtk_mhccif_irq_cb(int irq_id, void *data)
+{
+ struct mtk_md_dev *mdev = data;
+ struct mtk_pci_priv *priv;
+
+ priv = mdev->hw_priv;
+ queue_work(system_highpri_wq, &priv->mhccif_work);
+
+ return 0;
+}
+
+static int mtk_mhccif_init(struct mtk_md_dev *mdev)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ int ret;
+
+ INIT_LIST_HEAD(&priv->mhccif_cb_list);
+ spin_lock_init(&priv->mhccif_lock);
+ INIT_WORK(&priv->mhccif_work, mtk_mhccif_isr_work);
+
+ ret = mtk_pci_get_irq_id(mdev, MTK_IRQ_SRC_MHCCIF);
+ if (ret < 0) {
+ dev_err((mdev)->dev, "Failed to get mhccif_irq_id. ret=%d\n", ret);
+ return ret;
+ }
+ priv->mhccif_irq_id = ret;
+
+ ret = mtk_pci_register_irq(mdev, priv->mhccif_irq_id, mtk_mhccif_irq_cb, mdev);
+ if (ret) {
+ dev_err((mdev)->dev, "Failed to register mhccif_irq callback\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mtk_mhccif_exit(struct mtk_md_dev *mdev)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+
+ mtk_pci_unregister_irq(mdev, priv->mhccif_irq_id);
+ cancel_work_sync(&priv->mhccif_work);
+}
+
+static irqreturn_t mtk_pci_irq_handler(struct mtk_md_dev *mdev, u32 irq_state)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ int irq_id;
+
+ /* Check whether each set bit has a callback, if has, call it */
+ do {
+ irq_id = fls(irq_state) - 1;
+ irq_state &= ~BIT(irq_id);
+ if (likely(priv->irq_cb_list[irq_id]))
+ priv->irq_cb_list[irq_id](irq_id, priv->irq_cb_data[irq_id]);
+ else
+ dev_err((mdev)->dev, "Unhandled irq_id=%d, no callback for it.\n", irq_id);
+ } while (irq_state);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mtk_pci_irq_msix(int irq, void *data)
+{
+ struct mtk_pci_irq_desc *irq_desc = data;
+ struct mtk_md_dev *mdev = irq_desc->mdev;
+ struct mtk_pci_priv *priv;
+ u32 irq_state, irq_enable;
+
+ priv = mdev->hw_priv;
+ irq_state = mtk_pci_mac_read32(priv, REG_MSIX_ISTATUS_HOST_GRP0_0);
+ irq_enable = mtk_pci_mac_read32(priv, REG_IMASK_HOST_MSIX_GRP0_0);
+ irq_state &= irq_enable;
+
+ if (unlikely(!irq_state) ||
+ unlikely(!((irq_state & GENMASK(priv->irq_cnt - 1, 0)) &
+ irq_desc->msix_bits)))
+ return IRQ_NONE;
+
+ /* Mask the bit and user needs to unmask by itself */
+ mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0,
+ irq_state & ~BIT(30));
+
+ return mtk_pci_irq_handler(mdev, irq_state);
+}
+
+static int mtk_pci_request_irq_msix(struct mtk_md_dev *mdev,
+ int irq_cnt_allocated)
+{
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ struct mtk_pci_irq_desc *irq_desc;
+ struct pci_dev *pdev;
+ int irq_cnt;
+ int ret, i;
+
+ /* calculate the nearest 2's power number */
+ irq_cnt = BIT(fls(irq_cnt_allocated) - 1);
+ pdev = to_pci_dev(mdev->dev);
+ irq_desc = priv->irq_desc;
+ for (i = 0; i < irq_cnt; i++) {
+ irq_desc[i].mdev = mdev;
+ irq_desc[i].msix_bits = BIT(i);
+ snprintf(irq_desc[i].name, MTK_IRQ_NAME_LEN, "msix%d-%s", i, mdev->dev_str);
+ ret = pci_request_irq(pdev, i, mtk_pci_irq_msix, NULL,
+ &irq_desc[i], irq_desc[i].name);
+ if (ret) {
+ dev_err((mdev)->dev, "Failed to request %s: ret=%d\n",
+ irq_desc[i].name, ret);
+ for (i--; i >= 0; i--)
+ pci_free_irq(pdev, i, &irq_desc[i]);
+ return ret;
+ }
+ }
+ priv->irq_cnt = irq_cnt;
+ priv->irq_type = PCI_IRQ_MSIX;
+
+ if (irq_cnt != MTK_IRQ_CNT_MAX)
+ mtk_pci_set_msix_merged(priv, irq_cnt);
+
+ return 0;
+}
+
+static int mtk_pci_request_irq(struct mtk_md_dev *mdev)
+{
+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
+ int irq_cnt, ret;
+
+ irq_cnt = pci_alloc_irq_vectors(pdev, MTK_IRQ_CNT_MIN,
+ MTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);
+
+ if (irq_cnt < MTK_IRQ_CNT_MIN) {
+ dev_err(mdev->dev,
+ "Unable to alloc pci irq vectors. ret=%d maxirqcnt=%d irqtype=0x%x\n",
+ irq_cnt, MTK_IRQ_CNT_MAX, PCI_IRQ_MSIX);
+ return -EFAULT;
+ }
+
+ ret = mtk_pci_request_irq_msix(mdev, irq_cnt);
+ if (ret)
+ pci_free_irq_vectors(pdev);
+
+ return ret;
+}
+
+static void mtk_pci_free_irq(struct mtk_md_dev *mdev)
+{
+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ int i;
+
+ for (i = 0; i < priv->irq_cnt; i++)
+ pci_free_irq(pdev, i, &priv->irq_desc[i]);
+
+ pci_free_irq_vectors(pdev);
+}
+
+static const struct mtk_dev_ops pci_hw_ops = {
+ .get_dev_state = mtk_pci_get_dev_state,
+ .ack_dev_state = mtk_pci_ack_dev_state,
+ .get_dev_cfg = mtk_pci_get_dev_cfg,
+ .register_dev_evt = mtk_pci_register_ext_evt,
+ .unregister_dev_evt = mtk_pci_unregister_ext_evt,
+ .mask_dev_evt = mtk_pci_mask_ext_evt,
+ .unmask_dev_evt = mtk_pci_unmask_ext_evt,
+ .clear_dev_evt = mtk_pci_clear_ext_evt,
+ .send_dev_evt = mtk_pci_send_ext_evt,
+};
+
+static int mtk_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_pci_priv *priv;
+ struct mtk_md_dev *mdev;
+ int ret;
+
+ mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
+ if (!mdev) {
+ ret = -ENOMEM;
+ goto log_err;
+ }
+ mdev->dev_ops = &pci_hw_ops;
+ mdev->dev = dev;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto free_cntx_data;
+ }
+
+ pci_set_drvdata(pdev, mdev);
+ priv->cfg = (void *)id->driver_data;
+ priv->mdev = mdev;
+ mdev->hw_ver = pdev->device;
+ mdev->hw_priv = priv;
+ mdev->dev = dev;
+ snprintf(mdev->dev_str, MTK_DEV_STR_LEN, "%02x%02x%d",
+ pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
+ if (pdev->state_saved)
+ pci_restore_state(pdev);
+
+ ret = pcim_enable_device(pdev);
+ if (ret) {
+ dev_err((mdev)->dev, "Failed to enable pci device.\n");
+ goto free_priv_data;
+ }
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ if (ret) {
+ dev_err((mdev)->dev, "Failed to set DMA Mask and Coherent. (ret=%d)\n", ret);
+ goto free_priv_data;
+ }
+
+ ret = mtk_pci_bar_init(mdev);
+ if (ret)
+ goto free_priv_data;
+
+ ret = priv->cfg->atr_init(mdev);
+ if (ret)
+ goto free_priv_data;
+
+ ret = mtk_mhccif_init(mdev);
+ if (ret)
+ goto free_priv_data;
+
+ /* mask all irqs */
+ if (priv->cfg->flag & MTK_CFG_IRQ_DFLT_MASK)
+ mtk_pci_mac_write32(priv, REG_IMASK_HOST_MSIX_CLR_GRP0_0, U32_MAX);
+
+ ret = mtk_pci_request_irq(mdev);
+ if (ret)
+ goto free_mhccif;
+
+ pci_set_master(pdev);
+ mtk_pci_unmask_irq(mdev, priv->mhccif_irq_id);
+
+ if (mtk_pci_link_check(mdev)) {
+ pci_save_state(pdev);
+ } else {
+ ret = -ENOLINK;
+ goto clear_master;
+ }
+
+ priv->saved_state = pci_store_saved_state(pdev);
+ if (!priv->saved_state) {
+ ret = -EFAULT;
+ goto clear_master;
+ }
+
+ return 0;
+
+clear_master:
+ pci_clear_master(pdev);
+ mtk_pci_free_irq(mdev);
+free_mhccif:
+ mtk_mhccif_exit(mdev);
+free_priv_data:
+ devm_kfree(dev, priv);
+free_cntx_data:
+ devm_kfree(dev, mdev);
+log_err:
+ dev_err(dev, "Failed to probe device, ret=%d\n", ret);
+
+ return ret;
+}
+
+static void mtk_pci_remove(struct pci_dev *pdev)
+{
+ struct mtk_md_dev *mdev = pci_get_drvdata(pdev);
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ struct device *dev = &pdev->dev;
+
+ mtk_pci_mask_irq(mdev, priv->mhccif_irq_id);
+
+ if (mtk_pci_pldr(mdev)) {
+ dev_warn(dev, "Failed to execute PLDR, try external event\n");
+ mtk_pci_reset(mdev, RESET_MHCCIF);
+ }
+
+ pci_clear_master(pdev);
+ mtk_pci_free_irq(mdev);
+ mtk_mhccif_exit(mdev);
+ pci_load_and_free_saved_state(pdev, &priv->saved_state);
+
+ devm_kfree(dev, priv);
+ devm_kfree(dev, mdev);
+}
+
+static pci_ers_result_t mtk_pci_error_detected(struct pci_dev *pdev,
+ pci_channel_state_t state)
+{
+ struct mtk_md_dev *mdev = pci_get_drvdata(pdev);
+
+ dev_err((mdev)->dev, "AER detected: pci_channel_state_t=%d\n", state);
+
+ /* Request a slot reset. */
+ return PCI_ERS_RESULT_CAN_RECOVER;
+}
+
+static const struct pci_error_handlers mtk_pci_err_handler = {
+ .error_detected = mtk_pci_error_detected,
+};
+
+static struct pci_driver mtk_pci_drv = {
+ .name = "mtk_pci_drv",
+ .id_table = t9xx_pci_table,
+ .probe = mtk_pci_probe,
+ .remove = mtk_pci_remove,
+ .err_handler = &mtk_pci_err_handler
+};
+
+module_pci_driver(mtk_pci_drv);
+
+MODULE_DESCRIPTION("MediaTek T9xx PCIe WWAN driver pcie layer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci.h b/drivers/net/wwan/t9xx/pcie/mtk_pci.h
new file mode 100644
index 000000000000..9819a1b07c1b
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PCI_H__
+#define __MTK_PCI_H__
+
+#include <linux/pci.h>
+
+#include "../mtk_dev.h"
+
+enum mtk_irq_src {
+ MTK_IRQ_SRC_MIN,
+ MTK_IRQ_SRC_MHCCIF,
+ MTK_IRQ_SRC_DPMAIF,
+ MTK_IRQ_SRC_DPMAIF2,
+ MTK_IRQ_SRC_CLDMA0,
+ MTK_IRQ_SRC_CLDMA1,
+ MTK_IRQ_SRC_CLDMA2,
+ MTK_IRQ_SRC_CLDMA3,
+ MTK_IRQ_SRC_PM_LOCK,
+ MTK_IRQ_SRC_DPMAIF3,
+ MTK_IRQ_SRC_DPMAIF6,
+ MTK_IRQ_SRC_MAX
+};
+
+enum mtk_reset_type {
+ RESET_FLDR,
+ RESET_PLDR,
+ RESET_MHCCIF,
+};
+
+enum mtk_atr_type {
+ ATR_PCI2AXI = 0,
+ ATR_AXI2PCI,
+};
+
+enum mtk_atr_src_port {
+ ATR_SRC_PCI_WIN0 = 0,
+ ATR_SRC_PCI_WIN1,
+ ATR_SRC_AXIS_0,
+ ATR_SRC_AXIS_1,
+ ATR_SRC_AXIS_2,
+ ATR_SRC_AXIS_3,
+};
+
+enum mtk_atr_dst_port {
+ ATR_DST_PCI_TRX = 0,
+ ATR_DST_AXIM_0 = 4,
+ ATR_DST_AXIM_1,
+ ATR_DST_AXIM_2,
+ ATR_DST_AXIM_3,
+};
+
+enum mtk_pci_evt_h2d {
+ DEV_EVT_H2D_EXTEND_BASE = DEV_EVT_H2D_MAX,
+ EXT_EVT_H2D_RESERVED_FOR_CLDMA0 = DEV_EVT_H2D_EXTEND_BASE << 1,
+ EXT_EVT_H2D_RESERVED_FOR_CLDMA1 = DEV_EVT_H2D_EXTEND_BASE << 2,
+ EXT_EVT_H2D_RESERVED_FOR_CLDMA3 = DEV_EVT_H2D_EXTEND_BASE << 3,
+ EXT_EVT_H2D_RESERVED_FOR_CLDMA2 = DEV_EVT_H2D_EXTEND_BASE << 4,
+ EXT_EVT_H2D_RESERVED_FOR_DPMAIF = DEV_EVT_H2D_EXTEND_BASE << 5,
+ EXT_EVT_H2D_PCIE_PM_SUSPEND_REQ = DEV_EVT_H2D_EXTEND_BASE << 6,
+ EXT_EVT_H2D_PCIE_PM_RESUME_REQ = DEV_EVT_H2D_EXTEND_BASE << 7,
+ EXT_EVT_H2D_PCIE_PM_SUSPEND_REQ_AP = DEV_EVT_H2D_EXTEND_BASE << 8,
+ EXT_EVT_H2D_PCIE_PM_RESUME_REQ_AP = DEV_EVT_H2D_EXTEND_BASE << 9,
+ EXT_EVT_H2D_RESERVED_FOR_TEST = DEV_EVT_H2D_EXTEND_BASE << 11,
+};
+
+enum mtk_pci_evt_d2h {
+ DEV_EVT_D2H_EXTEND_BASE = DEV_EVT_D2H_MAX,
+ EXT_EVT_D2H_RESERVED_FOR_CLDMA0 = DEV_EVT_D2H_EXTEND_BASE << 1,
+ EXT_EVT_D2H_RESERVED_FOR_CLDMA1 = DEV_EVT_D2H_EXTEND_BASE << 2,
+ EXT_EVT_D2H_RESERVED_FOR_CLDMA3 = DEV_EVT_D2H_EXTEND_BASE << 3,
+ EXT_EVT_D2H_RESERVED_FOR_CLDMA2 = DEV_EVT_D2H_EXTEND_BASE << 4,
+ EXT_EVT_D2H_RESERVED_FOR_DPMAIF = DEV_EVT_D2H_EXTEND_BASE << 5,
+ EXT_EVT_D2H_PCIE_PM_SUSPEND_ACK = DEV_EVT_D2H_EXTEND_BASE << 6,
+ EXT_EVT_D2H_PCIE_PM_RESUME_ACK = DEV_EVT_D2H_EXTEND_BASE << 7,
+ EXT_EVT_D2H_PCIE_PM_SUSPEND_ACK_AP = DEV_EVT_D2H_EXTEND_BASE << 8,
+ EXT_EVT_D2H_PCIE_PM_RESUME_ACK_AP = DEV_EVT_D2H_EXTEND_BASE << 9,
+ EXT_EVT_D2H_SOFT_OFF_NOTIFY = DEV_EVT_D2H_EXTEND_BASE << 10,
+ EXT_EVT_D2H_FRC_DONE_NOTIFY = DEV_EVT_D2H_EXTEND_BASE << 11,
+ EXT_EVT_D2H_RESERVED_FOR_TEST1 = DEV_EVT_D2H_EXTEND_BASE << 12,
+ EXT_EVT_D2H_RESERVED_FOR_TEST2 = DEV_EVT_D2H_EXTEND_BASE << 13,
+};
+
+#define MTK_PCI_CLASS 0x0D4000
+#define MTK_PCI_VENDOR_ID 0x14C3
+#define CEI_PCI_VENDOR_ID 0x03F0
+
+#define MTK_CFG_INFO_BIT_SHIFT 4
+
+#define MTK_PCI_DEV_CFG(id, cfg) \
+{ \
+ PCI_DEVICE(MTK_PCI_VENDOR_ID, id), \
+ MTK_PCI_CLASS, PCI_ANY_ID, \
+ .driver_data = (kernel_ulong_t)&(cfg), \
+}
+
+#define CEI_PCI_DEV_CFG(id, cfg) \
+{ \
+ PCI_DEVICE(CEI_PCI_VENDOR_ID, id), \
+ MTK_PCI_CLASS, PCI_ANY_ID, \
+ .driver_data = (kernel_ulong_t)&(cfg), \
+}
+
+#define MTK_CFG_IRQ_DFLT_MASK BIT(0)
+#define MTK_CFG_DISABLE_AP_DRM BIT(2)
+#define MTK_CFG_PM_SW_IRQ BIT(6)
+
+#define MTK_BAR_0_1_IDX 0
+#define MTK_BAR_2_3_IDX 2
+
+#define MTK_REQUESTED_BARS \
+ ((1 << MTK_BAR_0_1_IDX) | \
+ (1 << MTK_BAR_2_3_IDX))
+
+#define MTK_IRQ_CNT_MIN 1
+#define MTK_IRQ_CNT_MAX 32
+#define MTK_IRQ_NAME_LEN 32
+
+#define ATR_PORT_OFFSET 0x100
+#define ATR_TABLE_OFFSET 0x20
+#define ATR_TABLE_NUM_PER_ATR 8
+#define ATR_PCIE_REG_TRSL_ADDR 0x10000000
+#define ATR_PCIE_REG_SIZE 0x00400000
+#define ATR_PCIE_REG_PORT ATR_SRC_PCI_WIN0
+#define ATR_PCIE_REG_TABLE_NUM 1
+#define ATR_PCIE_REG_TRSL_PORT ATR_DST_AXIM_0
+#define ATR_PCIE_DEV_DMA_SRC_ADDR 0x00000000
+#define ATR_PCIE_DEV_DMA_TRANSPARENT 1
+#define ATR_PCIE_DEV_DMA_SIZE 0
+#define ATR_PCIE_DEV_DMA_TABLE_NUM 0
+#define ATR_PCIE_DEV_DMA_TRSL_ADDR 0x00000000
+
+struct mtk_pci_irq_desc {
+ struct mtk_md_dev *mdev;
+ u32 msix_bits;
+ char name[MTK_IRQ_NAME_LEN];
+};
+
+struct mtk_pci_dev_cfg {
+ u32 flag;
+ u32 mhccif_rc_base_addr;
+ u32 istatus_host_ctrl_addr;
+ int irq_tbl[MTK_IRQ_SRC_MAX];
+ int (*atr_init)(struct mtk_md_dev *mdev);
+};
+
+extern const struct mtk_pci_dev_cfg mtk_dev_cfg_0900;
+
+struct mtk_pci_priv {
+ struct mtk_md_dev *mdev;
+ const struct mtk_pci_dev_cfg *cfg;
+ void __iomem *bar23_addr;
+ void __iomem *mac_reg_base;
+ void __iomem *ext_reg_base;
+ int irq_cnt;
+ int irq_type;
+ void *irq_cb_data[MTK_IRQ_CNT_MAX];
+
+ int (*irq_cb_list[MTK_IRQ_CNT_MAX])(int irq_id, void *data);
+ struct mtk_pci_irq_desc irq_desc[MTK_IRQ_CNT_MAX];
+ struct list_head mhccif_cb_list;
+ /* mhccif_lock: lock to protect mhccif_cb_list */
+ spinlock_t mhccif_lock;
+ struct work_struct mhccif_work;
+ int mhccif_irq_id;
+ struct pci_saved_state *saved_state;
+};
+
+struct mtk_atr_cfg {
+ u64 src_addr;
+ u64 trsl_addr;
+ u64 size;
+ u32 type; /* Port type */
+ u32 port; /* Port number */
+ u32 table; /* Table number (8 tables for each port) */
+ u32 trsl_id;
+ u32 trsl_param;
+ u32 transparent;
+};
+
+/* BAR 0/1 MMIO access */
+static inline u32 mtk_pci_mac_read32(struct mtk_pci_priv *priv, u64 addr)
+{
+ return ioread32(priv->mac_reg_base + addr);
+}
+
+static inline void mtk_pci_mac_write32(struct mtk_pci_priv *priv, u64 addr, u32 val)
+{
+ iowrite32(val, priv->mac_reg_base + addr);
+}
+
+/* BAR 2/3 MMIO access */
+static inline u32 mtk_pci_read32(struct mtk_md_dev *mdev, u64 addr)
+{
+ return ioread32(((struct mtk_pci_priv *)mdev->hw_priv)->ext_reg_base + addr);
+}
+
+static inline void mtk_pci_write32(struct mtk_md_dev *mdev, u64 addr, u32 val)
+{
+ iowrite32(val, ((struct mtk_pci_priv *)mdev->hw_priv)->ext_reg_base + addr);
+}
+
+/* Device operations */
+u32 mtk_pci_get_dev_state(struct mtk_md_dev *mdev);
+void mtk_pci_ack_dev_state(struct mtk_md_dev *mdev, u32 state);
+u32 mtk_pci_get_dev_cfg(struct mtk_md_dev *mdev);
+/* IRQ Related operations */
+int mtk_pci_get_irq_id(struct mtk_md_dev *mdev, enum mtk_irq_src irq_src);
+int mtk_pci_get_virq_id(struct mtk_md_dev *mdev, int irq_id);
+int mtk_pci_register_irq(struct mtk_md_dev *mdev, int irq_id,
+ int (*irq_cb)(int irq_id, void *data), void *data);
+int mtk_pci_unregister_irq(struct mtk_md_dev *mdev, int irq_id);
+int mtk_pci_mask_irq(struct mtk_md_dev *mdev, int irq_id);
+int mtk_pci_unmask_irq(struct mtk_md_dev *mdev, int irq_id);
+int mtk_pci_clear_irq(struct mtk_md_dev *mdev, int irq_id);
+/* External event related */
+int mtk_pci_register_ext_evt(struct mtk_md_dev *mdev, u32 chs,
+ int (*evt_cb)(u32 status, void *data), void *data);
+void mtk_pci_unregister_ext_evt(struct mtk_md_dev *mdev, u32 chs);
+void mtk_pci_mask_ext_evt(struct mtk_md_dev *mdev, u32 chs);
+void mtk_pci_unmask_ext_evt(struct mtk_md_dev *mdev, u32 chs);
+void mtk_pci_clear_ext_evt(struct mtk_md_dev *mdev, u32 chs);
+int mtk_pci_send_ext_evt(struct mtk_md_dev *mdev, u32 ch);
+int mtk_pci_fldr(struct mtk_md_dev *mdev);
+int mtk_pci_pldr(struct mtk_md_dev *mdev);
+int mtk_pci_reset(struct mtk_md_dev *mdev, enum mtk_reset_type type);
+bool mtk_pci_link_check(struct mtk_md_dev *mdev);
+int mtk_pci_setup_atr(struct mtk_md_dev *mdev, struct mtk_atr_cfg *cfg);
+void mtk_pci_atr_disable(struct mtk_pci_priv *priv);
+
+#endif /* __MTK_PCI_H__ */
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c
new file mode 100644
index 000000000000..88b44142afb7
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_drv_m9xx.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+#include <linux/types.h>
+#include "mtk_pci.h"
+#include "mtk_pci_reg.h"
+
+static int mtk_pci_atr_init_m9xx(struct mtk_md_dev *mdev)
+{
+ struct pci_dev *pdev = to_pci_dev(mdev->dev);
+ struct mtk_pci_priv *priv = mdev->hw_priv;
+ struct mtk_atr_cfg cfg;
+ int port, ret;
+
+ mtk_pci_atr_disable(priv);
+
+ /* Config ATR for RC to access device's register */
+ cfg.src_addr = pci_resource_start(pdev, MTK_BAR_2_3_IDX);
+ cfg.size = ATR_PCIE_REG_SIZE;
+ cfg.trsl_addr = ATR_PCIE_REG_TRSL_ADDR;
+ cfg.type = ATR_PCI2AXI;
+ cfg.port = ATR_PCIE_REG_PORT;
+ cfg.table = ATR_PCIE_REG_TABLE_NUM;
+ cfg.trsl_id = ATR_PCIE_REG_TRSL_PORT;
+ cfg.trsl_param = 0x0;
+ cfg.transparent = 0x0;
+ ret = mtk_pci_setup_atr(mdev, &cfg);
+ if (ret)
+ return ret;
+
+ /* Config ATR for EP to access RC's memory */
+ for (port = ATR_SRC_AXIS_0; port <= ATR_SRC_AXIS_3; port++) {
+ cfg.src_addr = ATR_PCIE_DEV_DMA_SRC_ADDR;
+ cfg.size = ATR_PCIE_DEV_DMA_SIZE;
+ cfg.trsl_addr = ATR_PCIE_DEV_DMA_TRSL_ADDR;
+ cfg.type = ATR_AXI2PCI;
+ cfg.port = port;
+ cfg.table = ATR_PCIE_DEV_DMA_TABLE_NUM;
+ cfg.trsl_id = ATR_DST_PCI_TRX;
+ cfg.trsl_param = 0x0;
+ /* Enable transparent translation */
+ cfg.transparent = ATR_PCIE_DEV_DMA_TRANSPARENT;
+ ret = mtk_pci_setup_atr(mdev, &cfg);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+const struct mtk_pci_dev_cfg mtk_dev_cfg_0900 = {
+ .flag = MTK_CFG_PM_SW_IRQ,
+ .mhccif_rc_base_addr = 0x1000A000,
+ .istatus_host_ctrl_addr = REG_ISTATUS_HOST_CTRL_NEW,
+ .irq_tbl = {
+ [MTK_IRQ_SRC_DPMAIF] = 24,
+ [MTK_IRQ_SRC_CLDMA0] = 27,
+ [MTK_IRQ_SRC_CLDMA1] = 26,
+ [MTK_IRQ_SRC_CLDMA2] = 25,
+ [MTK_IRQ_SRC_MHCCIF] = 28,
+ [MTK_IRQ_SRC_DPMAIF2] = 29,
+ [MTK_IRQ_SRC_CLDMA3] = 31,
+ [MTK_IRQ_SRC_PM_LOCK] = 0,
+ [MTK_IRQ_SRC_DPMAIF3] = 7,
+ [MTK_IRQ_SRC_DPMAIF6] = 10,
+ },
+ .atr_init = mtk_pci_atr_init_m9xx,
+};
diff --git a/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
new file mode 100644
index 000000000000..3f0667e8a846
--- /dev/null
+++ b/drivers/net/wwan/t9xx/pcie/mtk_pci_reg.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2022, MediaTek Inc.
+ */
+
+#ifndef __MTK_PCI_REG_H__
+#define __MTK_PCI_REG_H__
+
+#define REG_ISTATUS_HOST_CTRL_NEW 0x031C
+#define REG_PCIE_MISC_CTRL 0x0348
+#define REG_PCIE_CFG_MSIX 0x03EC
+#define REG_ATR_PCIE_WIN0_T0_SRC_ADDR_LSB 0x0600
+#define REG_ATR_PCIE_WIN0_T0_SRC_ADDR_MSB 0x0604
+#define REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_LSB 0x0608
+#define REG_ATR_PCIE_WIN0_T0_TRSL_ADDR_MSB 0x060C
+#define REG_ATR_PCIE_WIN0_T0_TRSL_PARAM 0x0610
+#define REG_PCIE_DEBUG_DUMMY_3 0x0D0C
+#define REG_PCIE_DEBUG_DUMMY_4 0x0D10
+#define REG_PCIE_DEBUG_DUMMY_7 0x0D1C
+#define REG_MSIX_ISTATUS_HOST_GRP0_0 0x0F00
+#define REG_IMASK_HOST_MSIX_SET_GRP0_0 0x3000
+#define REG_IMASK_HOST_MSIX_CLR_GRP0_0 0x3080
+#define REG_IMASK_HOST_MSIX_GRP0_0 0x3100
+
+/* mhccif registers */
+#define MHCCIF_RC2EP_SW_BSY 0x4
+#define MHCCIF_RC2EP_SW_TCHNUM 0xC
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA0 BIT(4)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA1 BIT(5)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA3 BIT(6)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_CLDMA2 BIT(7)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_DPMAIF BIT(8)
+#define MHCCIF_RC2EP_EVT_PCIE_PM_SUSPEND_REQ BIT(9)
+#define MHCCIF_RC2EP_EVT_PCIE_PM_RESUME_REQ BIT(10)
+#define MHCCIF_RC2EP_EVT_PCIE_PM_SUSPEND_REQ_AP BIT(11)
+#define MHCCIF_RC2EP_EVT_PCIE_PM_RESUME_REQ_AP BIT(12)
+#define MHCCIF_RC2EP_EVT_DEVICE_RESET BIT(13)
+#define MHCCIF_RC2EP_EVT_RESERVED_FOR_TEST BIT(31)
+
+#define MHCCIF_EP2RC_SW_INT_STS 0x10
+#define MHCCIF_EP2RC_SW_INT_ACK 0x14
+#define MHCCIF_EP2RC_SW_INT_EAP_MASK 0x20
+#define MHCCIF_EP2RC_SW_INT_EAP_MASK_SET 0x30
+#define MHCCIF_EP2RC_SW_INT_EAP_MASK_CLR 0x40
+#define MHCCIF_EP2RC_SPARE_REG_1 0x0104
+#define MHCCIF_EP2RC_SPARE_REG_5 0x0114
+#define MHCCIF_EP2RC_SPARE_REG_13 0x0134
+#define MHCCIF_EP2RC_SPARE_REG_14 0x0138
+#define MHCCIF_EP2RC_EVT_BOOT_FLOW_SYNC BIT(5)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA0 BIT(6)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA1 BIT(7)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA3 BIT(8)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_CLDMA2 BIT(9)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_DPMAIF BIT(10)
+#define MHCCIF_EP2RC_EVT_PCIE_PM_SUSPEND_ACK BIT(11)
+#define MHCCIF_EP2RC_EVT_PCIE_PM_RESUME_ACK BIT(12)
+#define MHCCIF_EP2RC_EVT_PCIE_PM_SUSPEND_ACK_AP BIT(13)
+#define MHCCIF_EP2RC_EVT_PCIE_PM_RESUME_ACK_AP BIT(14)
+#define MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_SAP BIT(15)
+#define MHCCIF_EP2RC_EVT_ASYNC_HS_NOTIFY_MD BIT(16)
+#define MHCCIF_EP2RC_EVT_SOFT_OFF_NOTIFY BIT(17)
+#define MHCCIF_EP2RC_EVT_MD_REBOOT BIT(19)
+#define MHCCIF_EP2RC_EVT_MD_POWEROFF BIT(20)
+#define MHCCIF_EP2RC_EVT_GNSS_ENABLE BIT(21)
+#define MHCCIF_EP2RC_EVT_GNSS_DISABLE BIT(22)
+#define MHCCIF_EP2RC_EVT_FRC_DONE_NOTIFY BIT(24)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_TEST1 BIT(30)
+#define MHCCIF_EP2RC_EVT_RESERVED_FOR_TEST2 BIT(31)
+
+#endif /* __MTK_PCI_REG_H__ */
--
2.34.1
^ permalink raw reply related
* [PATCH v9 04/12] reset: realtek: Add RTD1625-ISO reset controller driver
From: Yu-Chun Lin @ 2026-06-24 11:29 UTC (permalink / raw)
To: mturquette, sboyd, robh, krzk+dt, conor+dt, p.zabel, cylee12,
afaerber, jyanchou, bmasney
Cc: devicetree, linux-clk, linux-kernel, linux-arm-kernel,
linux-realtek-soc, james.tai, cy.huang, stanley_chang,
eleanor.lin
In-Reply-To: <20260624112940.3475605-1-eleanor.lin@realtek.com>
From: Cheng-Yu Lee <cylee12@realtek.com>
Add support for the ISO (Isolation) domain reset controller on the Realtek
RTD1625 SoC.
The reset controller shares the same register space with the ISO clock
controller. To handle this shared register space, the reset driver is
implemented as an auxiliary driver. It will be instantiated and probed via
the auxiliary bus by the RTD1625-ISO clock controller driver.
Signed-off-by: Cheng-Yu Lee <cylee12@realtek.com>
Co-developed-by: Yu-Chun Lin <eleanor.lin@realtek.com>
Signed-off-by: Yu-Chun Lin <eleanor.lin@realtek.com>
---
Changes in v9:
- Extract reset-related code from the previous clock driver patch
(formerly patch 9 in v8).
---
drivers/reset/realtek/Makefile | 2 +-
drivers/reset/realtek/reset-rtd1625-iso.c | 99 +++++++++++++++++++++++
2 files changed, 100 insertions(+), 1 deletion(-)
create mode 100644 drivers/reset/realtek/reset-rtd1625-iso.c
diff --git a/drivers/reset/realtek/Makefile b/drivers/reset/realtek/Makefile
index c3f605ffb11c..9007c9d5683b 100644
--- a/drivers/reset/realtek/Makefile
+++ b/drivers/reset/realtek/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_RESET_RTK_COMMON) += reset-rtk-common.o
-obj-$(CONFIG_RESET_RTD1625) += reset-rtd1625-crt.o
+obj-$(CONFIG_RESET_RTD1625) += reset-rtd1625-crt.o reset-rtd1625-iso.o
diff --git a/drivers/reset/realtek/reset-rtd1625-iso.c b/drivers/reset/realtek/reset-rtd1625-iso.c
new file mode 100644
index 000000000000..78eaabb408f0
--- /dev/null
+++ b/drivers/reset/realtek/reset-rtd1625-iso.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Realtek Semiconductor Corporation
+ */
+
+#include <dt-bindings/reset/realtek,rtd1625.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include "reset-rtk-common.h"
+
+#define RTD1625_ISO_RSTN_MAX 29
+#define RTD1625_ISO_S_RSTN_MAX 5
+
+static const struct rtk_reset_desc rtd1625_iso_reset_descs[] = {
+ [RTD1625_ISO_RSTN_VFD] = { .ofs = 0x88, .bit = 0 },
+ [RTD1625_ISO_RSTN_CEC0] = { .ofs = 0x88, .bit = 2 },
+ [RTD1625_ISO_RSTN_CEC1] = { .ofs = 0x88, .bit = 3 },
+ [RTD1625_ISO_RSTN_CBUSTX] = { .ofs = 0x88, .bit = 5 },
+ [RTD1625_ISO_RSTN_CBUSRX] = { .ofs = 0x88, .bit = 6 },
+ [RTD1625_ISO_RSTN_USB3_PHY2_XTAL_POW] = { .ofs = 0x88, .bit = 7 },
+ [RTD1625_ISO_RSTN_UR0] = { .ofs = 0x88, .bit = 8 },
+ [RTD1625_ISO_RSTN_GMAC] = { .ofs = 0x88, .bit = 9 },
+ [RTD1625_ISO_RSTN_GPHY] = { .ofs = 0x88, .bit = 10 },
+ [RTD1625_ISO_RSTN_I2C_0] = { .ofs = 0x88, .bit = 11 },
+ [RTD1625_ISO_RSTN_I2C_1] = { .ofs = 0x88, .bit = 12 },
+ [RTD1625_ISO_RSTN_CBUS] = { .ofs = 0x88, .bit = 13 },
+ [RTD1625_ISO_RSTN_USB_DRD] = { .ofs = 0x88, .bit = 14 },
+ [RTD1625_ISO_RSTN_USB_HOST] = { .ofs = 0x88, .bit = 15 },
+ [RTD1625_ISO_RSTN_USB_PHY_0] = { .ofs = 0x88, .bit = 16 },
+ [RTD1625_ISO_RSTN_USB_PHY_1] = { .ofs = 0x88, .bit = 17 },
+ [RTD1625_ISO_RSTN_USB_PHY_2] = { .ofs = 0x88, .bit = 18 },
+ [RTD1625_ISO_RSTN_USB] = { .ofs = 0x88, .bit = 19 },
+ [RTD1625_ISO_RSTN_TYPE_C] = { .ofs = 0x88, .bit = 20 },
+ [RTD1625_ISO_RSTN_USB_U3_HOST] = { .ofs = 0x88, .bit = 21 },
+ [RTD1625_ISO_RSTN_USB3_PHY0_POW] = { .ofs = 0x88, .bit = 22 },
+ [RTD1625_ISO_RSTN_USB3_P0_MDIO] = { .ofs = 0x88, .bit = 23 },
+ [RTD1625_ISO_RSTN_USB3_PHY1_POW] = { .ofs = 0x88, .bit = 24 },
+ [RTD1625_ISO_RSTN_USB3_P1_MDIO] = { .ofs = 0x88, .bit = 25 },
+ [RTD1625_ISO_RSTN_VTC] = { .ofs = 0x88, .bit = 26 },
+ [RTD1625_ISO_RSTN_USB3_PHY2_POW] = { .ofs = 0x88, .bit = 27 },
+ [RTD1625_ISO_RSTN_USB3_P2_MDIO] = { .ofs = 0x88, .bit = 28 },
+ [RTD1625_ISO_RSTN_USB_PHY_3] = { .ofs = 0x88, .bit = 29 },
+ [RTD1625_ISO_RSTN_USB_PHY_4] = { .ofs = 0x88, .bit = 30 },
+};
+
+static const struct rtk_reset_desc rtd1625_iso_s_reset_descs[] = {
+ [RTD1625_ISO_S_RSTN_ISOM_MIS] = { .ofs = 0x310, .bit = 0, .write_en = 1 },
+ [RTD1625_ISO_S_RSTN_GPIOM] = { .ofs = 0x310, .bit = 2, .write_en = 1 },
+ [RTD1625_ISO_S_RSTN_TIMER7] = { .ofs = 0x310, .bit = 4, .write_en = 1 },
+ [RTD1625_ISO_S_RSTN_IRDA] = { .ofs = 0x310, .bit = 6, .write_en = 1 },
+ [RTD1625_ISO_S_RSTN_UR10] = { .ofs = 0x310, .bit = 8, .write_en = 1 },
+};
+
+static int rtd1625_iso_reset_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct device *parent = dev->parent;
+ struct rtk_reset_data *data;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ if (of_device_is_compatible(parent->of_node, "realtek,rtd1625-iso-s-clk")) {
+ data->descs = rtd1625_iso_s_reset_descs;
+ data->rcdev.nr_resets = RTD1625_ISO_S_RSTN_MAX;
+ } else {
+ data->descs = rtd1625_iso_reset_descs;
+ data->rcdev.nr_resets = RTD1625_ISO_RSTN_MAX;
+ }
+
+ data->rcdev.owner = THIS_MODULE;
+
+ return rtk_reset_controller_add(dev, data);
+}
+
+static const struct auxiliary_device_id rtd1625_iso_reset_ids[] = {
+ { .name = "clk_rtk.iso_rst" },
+ { .name = "clk_rtk.iso_s_rst" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(auxiliary, rtd1625_iso_reset_ids);
+
+static struct auxiliary_driver rtd1625_iso_driver = {
+ .probe = rtd1625_iso_reset_probe,
+ .id_table = rtd1625_iso_reset_ids,
+ .driver = {
+ .name = "rtd1625-iso-reset",
+ },
+};
+module_auxiliary_driver(rtd1625_iso_driver);
+
+MODULE_DESCRIPTION("Realtek RTD1625 ISO Reset Controller Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("REALTEK_RESET");
--
2.43.0
^ permalink raw reply related
* [PATCH v9 08/12] clk: realtek: Add support for mux clock
From: Yu-Chun Lin @ 2026-06-24 11:29 UTC (permalink / raw)
To: mturquette, sboyd, robh, krzk+dt, conor+dt, p.zabel, cylee12,
afaerber, jyanchou, bmasney
Cc: devicetree, linux-clk, linux-kernel, linux-arm-kernel,
linux-realtek-soc, james.tai, cy.huang, stanley_chang,
eleanor.lin
In-Reply-To: <20260624112940.3475605-1-eleanor.lin@realtek.com>
From: Cheng-Yu Lee <cylee12@realtek.com>
Add a simple regmap-based clk_ops implementation for Realtek mux clocks.
The implementation supports parent selection and rate determination through
regmap-backed register access.
Signed-off-by: Cheng-Yu Lee <cylee12@realtek.com>
Co-developed-by: Yu-Chun Lin <eleanor.lin@realtek.com>
Signed-off-by: Yu-Chun Lin <eleanor.lin@realtek.com>
---
Changes in v9:
- Fix error handling in 'clk_regmap_mux_get_parent()'.
---
drivers/clk/realtek/Makefile | 1 +
drivers/clk/realtek/clk-regmap-mux.c | 41 ++++++++++++++++++++++++++
drivers/clk/realtek/clk-regmap-mux.h | 43 ++++++++++++++++++++++++++++
3 files changed, 85 insertions(+)
create mode 100644 drivers/clk/realtek/clk-regmap-mux.c
create mode 100644 drivers/clk/realtek/clk-regmap-mux.h
diff --git a/drivers/clk/realtek/Makefile b/drivers/clk/realtek/Makefile
index 90c0658a83bf..3b014240a211 100644
--- a/drivers/clk/realtek/Makefile
+++ b/drivers/clk/realtek/Makefile
@@ -5,3 +5,4 @@ clk-rtk-y += clk-rtk-common.o
clk-rtk-y += clk-pll.o
clk-rtk-y += freq_table.o
clk-rtk-y += clk-regmap-gate.o
+clk-rtk-y += clk-regmap-mux.o
diff --git a/drivers/clk/realtek/clk-regmap-mux.c b/drivers/clk/realtek/clk-regmap-mux.c
new file mode 100644
index 000000000000..bdd579e1165d
--- /dev/null
+++ b/drivers/clk/realtek/clk-regmap-mux.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2017-2026 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/export.h>
+#include <linux/regmap.h>
+#include "clk-regmap-mux.h"
+
+static u8 clk_regmap_mux_get_parent(struct clk_hw *hw)
+{
+ struct clk_regmap_mux *clkm = to_clk_regmap_mux(hw);
+ int num_parents = clk_hw_get_num_parents(hw);
+ u32 val;
+ int ret;
+
+ ret = regmap_read(clkm->clkr.regmap, clkm->mux_ofs, &val);
+ if (ret)
+ return 0xff;
+
+ val = (val >> clkm->shift) & clkm->mask;
+
+ return val >= num_parents ? 0xff : val;
+}
+
+static int clk_regmap_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_regmap_mux *clkm = to_clk_regmap_mux(hw);
+
+ return regmap_update_bits(clkm->clkr.regmap, clkm->mux_ofs,
+ clkm->mask << clkm->shift, (u32)index << clkm->shift);
+}
+
+const struct clk_ops rtk_clk_regmap_mux_ops = {
+ .set_parent = clk_regmap_mux_set_parent,
+ .get_parent = clk_regmap_mux_get_parent,
+ .determine_rate = __clk_mux_determine_rate,
+};
+EXPORT_SYMBOL_NS_GPL(rtk_clk_regmap_mux_ops, "REALTEK_CLK");
diff --git a/drivers/clk/realtek/clk-regmap-mux.h b/drivers/clk/realtek/clk-regmap-mux.h
new file mode 100644
index 000000000000..3ab8dc5032fc
--- /dev/null
+++ b/drivers/clk/realtek/clk-regmap-mux.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2017-2026 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#ifndef __CLK_REALTEK_CLK_REGMAP_MUX_H
+#define __CLK_REALTEK_CLK_REGMAP_MUX_H
+
+#include "clk-rtk-common.h"
+
+struct clk_regmap_mux {
+ struct clk_regmap clkr;
+ int mux_ofs;
+ unsigned int mask;
+ unsigned int shift;
+};
+
+#define __clk_regmap_mux_hw(_p) __clk_regmap_hw(&(_p)->clkr)
+
+#define __CLK_REGMAP_MUX(_name, _parents, _ops, _flags, _ofs, _sft, _mask) \
+ struct clk_regmap_mux _name = { \
+ .clkr.hw.init = \
+ CLK_HW_INIT_PARENTS(#_name, _parents, _ops, _flags), \
+ .mux_ofs = _ofs, \
+ .shift = _sft, \
+ .mask = _mask, \
+ }
+
+#define CLK_REGMAP_MUX(_name, _parents, _flags, _ofs, _sft, _mask) \
+ __CLK_REGMAP_MUX(_name, _parents, &rtk_clk_regmap_mux_ops, _flags, _ofs, \
+ _sft, _mask)
+
+static inline struct clk_regmap_mux *to_clk_regmap_mux(struct clk_hw *hw)
+{
+ struct clk_regmap *clkr = to_clk_regmap(hw);
+
+ return container_of(clkr, struct clk_regmap_mux, clkr);
+}
+
+extern const struct clk_ops rtk_clk_regmap_mux_ops;
+
+#endif /* __CLK_REALTEK_CLK_REGMAP_MUX_H */
--
2.43.0
^ permalink raw reply related
* [PATCH v9 00/12] clk / reset: realtek: Add RTD1625 clock and reset support
From: Yu-Chun Lin @ 2026-06-24 11:29 UTC (permalink / raw)
To: mturquette, sboyd, robh, krzk+dt, conor+dt, p.zabel, cylee12,
afaerber, jyanchou, bmasney
Cc: devicetree, linux-clk, linux-kernel, linux-arm-kernel,
linux-realtek-soc, james.tai, cy.huang, stanley_chang,
eleanor.lin
Hello,
This patch series adds the clock and reset controller support for Realtek's
RTD1625 SoC platform.
Because the reset controllers share the same register space with the
clock controllers on this platform, we utilize the Auxiliary Bus framework
to decouple them. The clock controllers act as the primary devices,
registering the reset controllers as auxiliary devices.
To make it easier for maintainers to review, the series has been organized
by subsystem:
1. Device Tree Bindings:
- Add bindings for the Realtek RTD1625 Clock & Reset Controllers.
2. Reset Subsystem:
- Introduce the basic Realtek reset infrastructure.
- Add the RTD1625-CRT and RTD1625-ISO platform reset drivers.
3. Clock Subsystem Infrastructure:
- Introduce a common probe, and add support for basic clocks including
PLLs, gate clocks, mux clocks, and MMC-tuned PLLs.
4. Clock Platform Drivers:
- Add the clock controller drivers for RTD1625-CRT and RTD1625-ISO.
- These drivers provide the clock sources and instantiate the
corresponding auxiliary reset devices.
Best regards,
Yu-Chun Lin
---
Changes in v9:
General:
- Split the combined clock and reset drivers into separate patches.
- Rename 'common.[ch]' to 'clk-rtk-common.[ch]' and 'reset-rtk-common.[ch]' to
avoid generic names.
- Adapt suggestions from AI review.
- Link: https://sashiko.dev/#/patchset/20260610080824.255063-1-eleanor.lin%40realtek.com.
Patch 6:
- Add 'ftbl_find_ceil_by_rate()' as a fallback in 'clk_pll_determine_rate()'.
Patch 8:
- Fix error handling in 'clk_regmap_mux_get_parent()'.
Patch 9:
- Fix potential integer overflow on 32-bit architectures in
'clk_pll_mmc_determine_rate()'.
- Add comments in 'clk_pll_mmc_set_rate()'.
v8: https://lore.kernel.org/lkml/20260610080824.255063-1-eleanor.lin@realtek.com/
v7: https://lore.kernel.org/lkml/20260508111641.3192177-1-eleanor.lin@realtek.com/
v6: https://lore.kernel.org/lkml/20260402073957.2742459-1-eleanor.lin@realtek.com/
v5: https://lore.kernel.org/lkml/20260324025332.3416977-1-eleanor.lin@realtek.com/
v4: https://lore.kernel.org/lkml/20260313081100.596224-1-eleanor.lin@realtek.com/
v3: https://lore.kernel.org/lkml/20260122110857.12995-1-eleanor.lin@realtek.com/
v2: https://lore.kernel.org/lkml/20260113112333.821-1-eleanor.lin@realtek.com/
v1: https://lore.kernel.org/lkml/20251229075313.27254-1-eleanor.lin@realtek.com/
Cheng-Yu Lee (10):
reset: Add Realtek basic reset support
reset: realtek: Add RTD1625-CRT reset driver
reset: realtek: Add RTD1625-ISO reset controller driver
clk: realtek: Introduce a common probe()
clk: realtek: Add support for phase locked loops (PLLs)
clk: realtek: Add support for gate clock
clk: realtek: Add support for mux clock
clk: realtek: Add support for MMC-tuned PLL clocks
clk: realtek: Add RTD1625-CRT clock controller driver
clk: realtek: Add RTD1625-ISO clock controller driver
Yu-Chun Lin (2):
dt-bindings: clock: Add Realtek RTD1625 Clock & Reset Controller
arm64: dts: realtek: Add clock support for RTD1625
.../bindings/clock/realtek,rtd1625-clk.yaml | 58 ++
MAINTAINERS | 20 +
arch/arm64/boot/dts/realtek/kent.dtsi | 33 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/realtek/Kconfig | 48 ++
drivers/clk/realtek/Makefile | 12 +
drivers/clk/realtek/clk-pll-mmc.c | 430 ++++++++++
drivers/clk/realtek/clk-pll.c | 217 +++++
drivers/clk/realtek/clk-pll.h | 60 ++
drivers/clk/realtek/clk-regmap-gate.c | 70 ++
drivers/clk/realtek/clk-regmap-gate.h | 65 ++
drivers/clk/realtek/clk-regmap-mux.c | 41 +
drivers/clk/realtek/clk-regmap-mux.h | 43 +
drivers/clk/realtek/clk-rtd1625-crt.c | 792 ++++++++++++++++++
drivers/clk/realtek/clk-rtd1625-iso.c | 151 ++++
drivers/clk/realtek/clk-rtk-common.c | 66 ++
drivers/clk/realtek/clk-rtk-common.h | 37 +
drivers/clk/realtek/freq_table.c | 57 ++
drivers/clk/realtek/freq_table.h | 18 +
drivers/reset/Kconfig | 1 +
drivers/reset/Makefile | 1 +
drivers/reset/realtek/Kconfig | 19 +
drivers/reset/realtek/Makefile | 3 +
drivers/reset/realtek/reset-rtd1625-crt.c | 187 +++++
drivers/reset/realtek/reset-rtd1625-iso.c | 99 +++
drivers/reset/realtek/reset-rtk-common.c | 90 ++
drivers/reset/realtek/reset-rtk-common.h | 29 +
.../dt-bindings/clock/realtek,rtd1625-clk.h | 164 ++++
include/dt-bindings/reset/realtek,rtd1625.h | 171 ++++
30 files changed, 2984 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/realtek,rtd1625-clk.yaml
create mode 100644 drivers/clk/realtek/Kconfig
create mode 100644 drivers/clk/realtek/Makefile
create mode 100644 drivers/clk/realtek/clk-pll-mmc.c
create mode 100644 drivers/clk/realtek/clk-pll.c
create mode 100644 drivers/clk/realtek/clk-pll.h
create mode 100644 drivers/clk/realtek/clk-regmap-gate.c
create mode 100644 drivers/clk/realtek/clk-regmap-gate.h
create mode 100644 drivers/clk/realtek/clk-regmap-mux.c
create mode 100644 drivers/clk/realtek/clk-regmap-mux.h
create mode 100644 drivers/clk/realtek/clk-rtd1625-crt.c
create mode 100644 drivers/clk/realtek/clk-rtd1625-iso.c
create mode 100644 drivers/clk/realtek/clk-rtk-common.c
create mode 100644 drivers/clk/realtek/clk-rtk-common.h
create mode 100644 drivers/clk/realtek/freq_table.c
create mode 100644 drivers/clk/realtek/freq_table.h
create mode 100644 drivers/reset/realtek/Kconfig
create mode 100644 drivers/reset/realtek/Makefile
create mode 100644 drivers/reset/realtek/reset-rtd1625-crt.c
create mode 100644 drivers/reset/realtek/reset-rtd1625-iso.c
create mode 100644 drivers/reset/realtek/reset-rtk-common.c
create mode 100644 drivers/reset/realtek/reset-rtk-common.h
create mode 100644 include/dt-bindings/clock/realtek,rtd1625-clk.h
create mode 100644 include/dt-bindings/reset/realtek,rtd1625.h
--
2.43.0
^ permalink raw reply
* [PATCH v9 05/12] clk: realtek: Introduce a common probe()
From: Yu-Chun Lin @ 2026-06-24 11:29 UTC (permalink / raw)
To: mturquette, sboyd, robh, krzk+dt, conor+dt, p.zabel, cylee12,
afaerber, jyanchou, bmasney
Cc: devicetree, linux-clk, linux-kernel, linux-arm-kernel,
linux-realtek-soc, james.tai, cy.huang, stanley_chang,
eleanor.lin
In-Reply-To: <20260624112940.3475605-1-eleanor.lin@realtek.com>
From: Cheng-Yu Lee <cylee12@realtek.com>
Add rtk_clk_probe() to set up the shared regmap, register clock hardware,
and add the clock provider.
Additionally, if the "#reset-cells" property is present in the device tree,
it creates and registers an auxiliary device using the provided aux_name.
This allows the dedicated reset driver to bind to this device, enabling
both clock and reset drivers to share the same regmap.
Signed-off-by: Cheng-Yu Lee <cylee12@realtek.com>
Co-developed-by: Yu-Chun Lin <eleanor.lin@realtek.com>
Signed-off-by: Yu-Chun Lin <eleanor.lin@realtek.com>
---
Changes in v9:
- Rename common.[ch] to clk-rtk-common.[ch].
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/realtek/Kconfig | 30 +++++++++++++
drivers/clk/realtek/Makefile | 4 ++
drivers/clk/realtek/clk-rtk-common.c | 66 ++++++++++++++++++++++++++++
drivers/clk/realtek/clk-rtk-common.h | 37 ++++++++++++++++
7 files changed, 140 insertions(+)
create mode 100644 drivers/clk/realtek/Kconfig
create mode 100644 drivers/clk/realtek/Makefile
create mode 100644 drivers/clk/realtek/clk-rtk-common.c
create mode 100644 drivers/clk/realtek/clk-rtk-common.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 3bc431a2a7d1..9cdcb333b68f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22675,6 +22675,7 @@ L: devicetree@vger.kernel.org
L: linux-clk@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/clock/realtek*
+F: drivers/clk/realtek/*
F: drivers/reset/realtek/*
F: include/dt-bindings/clock/realtek*
F: include/dt-bindings/reset/realtek*
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index b2efbe9f6acb..8bf262dd23a9 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -519,6 +519,7 @@ source "drivers/clk/nuvoton/Kconfig"
source "drivers/clk/pistachio/Kconfig"
source "drivers/clk/qcom/Kconfig"
source "drivers/clk/ralink/Kconfig"
+source "drivers/clk/realtek/Kconfig"
source "drivers/clk/renesas/Kconfig"
source "drivers/clk/rockchip/Kconfig"
source "drivers/clk/samsung/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index a3e2862ebd7e..e226bee2d039 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -140,6 +140,7 @@ obj-$(CONFIG_COMMON_CLK_PISTACHIO) += pistachio/
obj-$(CONFIG_COMMON_CLK_PXA) += pxa/
obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/
obj-y += ralink/
+obj-$(CONFIG_COMMON_CLK_REALTEK) += realtek/
obj-y += renesas/
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/
diff --git a/drivers/clk/realtek/Kconfig b/drivers/clk/realtek/Kconfig
new file mode 100644
index 000000000000..ed97531e321d
--- /dev/null
+++ b/drivers/clk/realtek/Kconfig
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config COMMON_CLK_REALTEK
+ tristate "Clock driver for Realtek SoCs"
+ depends on ARCH_REALTEK || COMPILE_TEST
+ default ARCH_REALTEK
+ help
+ Enable the common clock framework infrastructure for Realtek
+ system-on-chip platforms.
+
+ This provides the base support required by individual Realtek
+ clock controller drivers to expose clocks to peripheral devices.
+
+ If you have a Realtek-based platform, say Y.
+
+if COMMON_CLK_REALTEK
+
+config RTK_CLK_COMMON
+ tristate "Realtek Clock Common"
+ depends on RESET_CONTROLLER
+ select AUXILIARY_BUS
+ select MFD_SYSCON
+ select RESET_RTK_COMMON
+ help
+ Common helper code shared by Realtek clock controller drivers.
+
+ This provides utility functions and data structures used by
+ multiple Realtek clock implementations, and include integration
+ with reset controllers where required.
+
+endif
diff --git a/drivers/clk/realtek/Makefile b/drivers/clk/realtek/Makefile
new file mode 100644
index 000000000000..13000ed4ba11
--- /dev/null
+++ b/drivers/clk/realtek/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_RTK_CLK_COMMON) += clk-rtk.o
+
+clk-rtk-y += clk-rtk-common.o
diff --git a/drivers/clk/realtek/clk-rtk-common.c b/drivers/clk/realtek/clk-rtk-common.c
new file mode 100644
index 000000000000..4fd16585dbf4
--- /dev/null
+++ b/drivers/clk/realtek/clk-rtk-common.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2019-2026 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include "clk-rtk-common.h"
+
+static int rtk_reset_controller_register(struct device *dev, const char *aux_name,
+ struct regmap *map)
+{
+ struct auxiliary_device *adev;
+
+ if (!of_property_present(dev->of_node, "#reset-cells"))
+ return 0;
+
+ adev = devm_auxiliary_device_create(dev, aux_name, (void *)map);
+
+ if (!adev)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int rtk_clk_probe(struct platform_device *pdev, const struct rtk_clk_desc *desc,
+ const char *aux_name)
+{
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ int i, ret;
+
+ regmap = device_node_to_regmap(dev->of_node);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "failed to get regmap\n");
+
+ for (i = 0; i < desc->num_clks; i++)
+ desc->clks[i]->regmap = regmap;
+
+ for (i = 0; i < desc->clk_data->num; i++) {
+ struct clk_hw *hw = desc->clk_data->hws[i];
+
+ if (!hw)
+ continue;
+
+ ret = devm_clk_hw_register(dev, hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register hw of clk%d\n", i);
+ }
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
+ desc->clk_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add clock provider\n");
+
+ return rtk_reset_controller_register(dev, aux_name, regmap);
+}
+EXPORT_SYMBOL_NS_GPL(rtk_clk_probe, "REALTEK_CLK");
+
+MODULE_DESCRIPTION("Realtek clock infrastructure");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/realtek/clk-rtk-common.h b/drivers/clk/realtek/clk-rtk-common.h
new file mode 100644
index 000000000000..c52fcdbff5ee
--- /dev/null
+++ b/drivers/clk/realtek/clk-rtk-common.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2016-2026 Realtek Semiconductor Corporation
+ * Author: Cheng-Yu Lee <cylee12@realtek.com>
+ */
+
+#ifndef __CLK_REALTEK_COMMON_H
+#define __CLK_REALTEK_COMMON_H
+
+#include <linux/clk-provider.h>
+
+#define __clk_regmap_hw(_p) ((_p)->hw)
+
+struct device;
+struct platform_device;
+struct regmap;
+
+struct clk_regmap {
+ struct clk_hw hw;
+ struct regmap *regmap;
+};
+
+struct rtk_clk_desc {
+ struct clk_hw_onecell_data *clk_data;
+ struct clk_regmap * const *clks;
+ size_t num_clks;
+};
+
+static inline struct clk_regmap *to_clk_regmap(struct clk_hw *hw)
+{
+ return container_of(hw, struct clk_regmap, hw);
+}
+
+int rtk_clk_probe(struct platform_device *pdev, const struct rtk_clk_desc *desc,
+ const char *aux_name);
+
+#endif /* __CLK_REALTEK_COMMON_H */
--
2.43.0
^ permalink raw reply related
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