diff --git a/.github/update.log b/.github/update.log
index 7314121e95..17d3d0d1ef 100644
--- a/.github/update.log
+++ b/.github/update.log
@@ -1178,3 +1178,4 @@ Update On Fri Nov 7 19:41:20 CET 2025
Update On Sat Nov 8 19:37:24 CET 2025
Update On Sun Nov 9 19:36:09 CET 2025
Update On Mon Nov 10 19:41:15 CET 2025
+Update On Tue Nov 11 19:39:14 CET 2025
diff --git a/clash-meta/go.mod b/clash-meta/go.mod
index d203eb6407..90362be0c6 100644
--- a/clash-meta/go.mod
+++ b/clash-meta/go.mod
@@ -22,7 +22,7 @@ require (
github.com/metacubex/chacha v0.1.5
github.com/metacubex/fswatch v0.1.1
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
- github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be
+ github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128
github.com/metacubex/randv2 v0.2.0
github.com/metacubex/restls-client-go v0.1.7
@@ -35,7 +35,7 @@ require (
github.com/metacubex/sing-tun v0.4.9
github.com/metacubex/sing-vmess v0.2.4
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
- github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719
+ github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148
github.com/metacubex/utls v1.8.3
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
diff --git a/clash-meta/go.sum b/clash-meta/go.sum
index 4670178e98..0bec36121a 100644
--- a/clash-meta/go.sum
+++ b/clash-meta/go.sum
@@ -106,8 +106,8 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301 h1:N5GExQJqYAH3gOCshpp2u/J3CtNYzMctmlb0xK9wtbQ=
github.com/metacubex/gvisor v0.0.0-20250919004547-6122b699a301/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
-github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be h1:Y7SigZIqfv/+RIA/D7R6EbB9p+brPRoGOM6zobSmRIM=
-github.com/metacubex/kcp-go v0.0.0-20251105084629-8c93f4bf37be/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
+github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9 h1:7m3tRPrLpKOLOvZ/Lp4XCxz0t7rg9t9K35x6TahjR8o=
+github.com/metacubex/kcp-go v0.0.0-20251111012849-7455698490e9/go.mod h1:HIJZW4QMhbBqXuqC1ly6Hn0TEYT2SzRw58ns1yGhXTs=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.55.1-0.20251024060151-bd465f127128 h1:I1uvJl206/HbkzEAZpLgGkZgUveOZb+P+6oTUj7dN+o=
@@ -135,8 +135,8 @@ github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkA
github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
-github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719 h1:T6qCCfolRDAVJKeaPW/mXwNLjnlo65AYN7WS2jrBNaM=
-github.com/metacubex/smux v0.0.0-20250922175018-15c9a6a78719/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
+github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 h1:a6DF0ze9miXes+rdwl8a4Wkvfpe0lXYU82sPJfDzz6s=
+github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148 h1:Zd0QqciLIhv9MKbGKTPEgN8WUFsgQGA1WJBy6spEnVU=
github.com/metacubex/tfo-go v0.0.0-20251024101424-368b42b59148/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.3 h1:0m/yCxm3SK6kWve2lKiFb1pue1wHitJ8sQQD4Ikqde4=
diff --git a/clash-meta/hub/executor/executor.go b/clash-meta/hub/executor/executor.go
index e92a8de448..988d73e272 100644
--- a/clash-meta/hub/executor/executor.go
+++ b/clash-meta/hub/executor/executor.go
@@ -449,12 +449,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
return
}
- for name, proxy := range proxies {
- outbound, ok := proxy.(C.Proxy)
- if !ok {
- continue
- }
-
+ for name, outbound := range proxies {
selector, ok := outbound.Adapter().(outboundgroup.SelectAble)
if !ok {
continue
diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json
index 27fa0f398c..d462568789 100644
--- a/clash-nyanpasu/manifest/version.json
+++ b/clash-nyanpasu/manifest/version.json
@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.16",
- "mihomo_alpha": "alpha-054e63c",
+ "mihomo_alpha": "alpha-2f545ef",
"clash_rs": "v0.9.2",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.9.2-alpha+sha.87c7b2c"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
- "updated_at": "2025-11-09T22:21:09.350Z"
+ "updated_at": "2025-11-10T22:21:23.620Z"
}
diff --git a/filebrowser/.github/CODEOWNERS b/filebrowser/.github/CODEOWNERS
index 827fbfd1cf..4c1605e6fb 100644
--- a/filebrowser/.github/CODEOWNERS
+++ b/filebrowser/.github/CODEOWNERS
@@ -1,5 +1 @@
-# These owners will be the default owners for everything in the repo.
-# Unless a later match takes precedence, @o1egl will be requested for
-# review when someone opens a pull request.
-
-* @o1egl @hacdias
+* @filebrowser/maintainers
diff --git a/filebrowser/CHANGELOG.md b/filebrowser/CHANGELOG.md
index 7a130be046..f5c0c07043 100644
--- a/filebrowser/CHANGELOG.md
+++ b/filebrowser/CHANGELOG.md
@@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [2.45.1](https://github.com/filebrowser/filebrowser/compare/v2.45.0...v2.45.1) (2025-11-11)
+
+
+### Bug Fixes
+
+* share page preview items to contain baseUrl ([#5510](https://github.com/filebrowser/filebrowser/issues/5510)) ([6950c2e](https://github.com/filebrowser/filebrowser/commit/6950c2e4d2868f06235f93c0a18b303b4095ca0a))
+
## [2.45.0](https://github.com/filebrowser/filebrowser/compare/v2.44.2...v2.45.0) (2025-11-01)
diff --git a/filebrowser/README.md b/filebrowser/README.md
index 94ec1cefa9..607bca4673 100644
--- a/filebrowser/README.md
+++ b/filebrowser/README.md
@@ -1,12 +1,10 @@
-
+
[](https://github.com/filebrowser/filebrowser/actions/workflows/main.yaml)
-[](https://goreportcard.com/report/github.com/filebrowser/filebrowser)
-[](http://godoc.org/github.com/filebrowser/filebrowser)
+[](https://goreportcard.com/report/github.com/filebrowser/filebrowser/v2)
[](https://github.com/filebrowser/filebrowser/releases/latest)
-[](http://webchat.freenode.net/?channels=%23filebrowser)
File Browser provides a file managing interface within a specified directory and it can be used to upload, delete, preview and edit your files. It is a **create-your-own-cloud**-kind of software where you can just install it on your server, direct it to a path and access your files through a nice web interface.
diff --git a/filebrowser/branding/banner.png b/filebrowser/branding/banner.png
new file mode 100644
index 0000000000..9a533bfc85
Binary files /dev/null and b/filebrowser/branding/banner.png differ
diff --git a/filebrowser/branding/banner.svg b/filebrowser/branding/banner.svg
new file mode 100644
index 0000000000..85de752598
--- /dev/null
+++ b/filebrowser/branding/banner.svg
@@ -0,0 +1 @@
+File Browser
diff --git a/filebrowser/branding/icon.png b/filebrowser/branding/icon.png
new file mode 100644
index 0000000000..ba267bc0fb
Binary files /dev/null and b/filebrowser/branding/icon.png differ
diff --git a/filebrowser/branding/icon.svg b/filebrowser/branding/icon.svg
new file mode 100644
index 0000000000..df65444a82
--- /dev/null
+++ b/filebrowser/branding/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/filebrowser/branding/logo.png b/filebrowser/branding/logo.png
new file mode 100644
index 0000000000..22225604cd
Binary files /dev/null and b/filebrowser/branding/logo.png differ
diff --git a/filebrowser/branding/logo.svg b/filebrowser/branding/logo.svg
new file mode 100644
index 0000000000..dac88ae274
--- /dev/null
+++ b/filebrowser/branding/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/filebrowser/frontend/public/img/logo.svg b/filebrowser/frontend/public/img/logo.svg
index 5e78eccff1..dac88ae274 100644
--- a/filebrowser/frontend/public/img/logo.svg
+++ b/filebrowser/frontend/public/img/logo.svg
@@ -1,147 +1 @@
-
-image/svg+xml
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/filebrowser/frontend/src/components/settings/AceEditorTheme.vue b/filebrowser/frontend/src/components/settings/AceEditorTheme.vue
index 09efe48b5a..68585a3a1e 100644
--- a/filebrowser/frontend/src/components/settings/AceEditorTheme.vue
+++ b/filebrowser/frontend/src/components/settings/AceEditorTheme.vue
@@ -1,5 +1,9 @@
-
+
{{ theme.name }}
diff --git a/filebrowser/frontend/src/views/Share.vue b/filebrowser/frontend/src/views/Share.vue
index 40b940b509..f92b86331d 100644
--- a/filebrowser/frontend/src/views/Share.vue
+++ b/filebrowser/frontend/src/views/Share.vue
@@ -301,7 +301,7 @@ import { pub as api } from "@/api";
import { filesize } from "@/utils";
import dayjs from "dayjs";
import { Base64 } from "js-base64";
-
+import { createURL } from "@/api/utils";
import HeaderBar from "@/components/header/HeaderBar.vue";
import Action from "@/components/header/Action.vue";
import Breadcrumbs from "@/components/Breadcrumbs.vue";
@@ -354,14 +354,11 @@ const icon = computed(() => {
const link = computed(() => (req.value ? api.getDownloadURL(req.value) : ""));
const raw = computed(() => {
- return req.value
- ? req.value.items[fileStore.selected[0]].url.replace(
- /share/,
- "api/public/dl"
- ) +
- "?token=" +
- token.value
- : "";
+ if (!req.value || !req.value.items[fileStore.selected[0]]) return "";
+ return createURL(
+ `api/public/dl/${hash.value}${req.value.items[fileStore.selected[0]].path}`,
+ { token: token.value }
+ );
});
const inlineLink = computed(() =>
req.value ? api.getDownloadURL(req.value, true) : ""
diff --git a/filebrowser/http/share.go b/filebrowser/http/share.go
index e1036d2a1e..d968c05be2 100644
--- a/filebrowser/http/share.go
+++ b/filebrowser/http/share.go
@@ -77,7 +77,16 @@ var shareDeleteHandler = withPermShare(func(_ http.ResponseWriter, r *http.Reque
return http.StatusBadRequest, nil
}
- err := d.store.Share.Delete(hash)
+ link, err := d.store.Share.GetByHash(hash)
+ if err != nil {
+ return errToStatus(err), err
+ }
+
+ if link.UserID != d.user.ID && !d.user.Perm.Admin {
+ return http.StatusForbidden, nil
+ }
+
+ err = d.store.Share.Delete(hash)
return errToStatus(err), err
})
diff --git a/filebrowser/www/docs/index.md b/filebrowser/www/docs/index.md
index 84ecb603cf..8e3e265fcf 100644
--- a/filebrowser/www/docs/index.md
+++ b/filebrowser/www/docs/index.md
@@ -5,7 +5,7 @@
-
+
> [!WARNING]
diff --git a/filebrowser/www/docs/installation.md b/filebrowser/www/docs/installation.md
index eb609f066d..741e8c1ab6 100644
--- a/filebrowser/www/docs/installation.md
+++ b/filebrowser/www/docs/installation.md
@@ -4,7 +4,7 @@ File Browser is a single binary and can be used as standalone executable. Howeve
## Binary
-The quickest and easiest way to install File Browser is to use a package manager, or our download script, which automatically fetches the latest version of File Browser for your platform.
+The quickest and easiest way to install File Browser is to use a package manager, or our download script, which automatically fetches the latest version of File Browser for your platform. Alternatively, you can manually download the binary from the [releases page](https://github.com/filebrowser/filebrowser/releases).
=== "Brew"
diff --git a/lede/target/linux/generic/backport-6.12/203-v6.15-drivers-base-component-add-function-to-query-the-bound.patch b/lede/target/linux/generic/backport-6.12/203-v6.15-drivers-base-component-add-function-to-query-the-bound.patch
new file mode 100644
index 0000000000..201bd229aa
--- /dev/null
+++ b/lede/target/linux/generic/backport-6.12/203-v6.15-drivers-base-component-add-function-to-query-the-bound.patch
@@ -0,0 +1,69 @@
+From a6ba2dad0aa4f623ab0def8b6e6888ac00639055 Mon Sep 17 00:00:00 2001
+From: Heiko Stuebner
+Date: Fri, 21 Feb 2025 00:41:40 +0100
+Subject: [PATCH] drivers: base: component: add function to query the bound
+ status
+
+The component helpers already expose the bound status in debugfs, but at
+times it might be necessary to also check that state in the kernel and
+act differently depending on the result.
+
+For example the shutdown handler of a drm-driver might need to stop
+a whole output pipeline if the drm device is up and running, but may
+run into problems if that drm-device has never been set up before,
+for example because the binding deferred.
+
+So add a little helper that returns the bound status for a componet
+device.
+
+Acked-by: Greg Kroah-Hartman
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20250220234141.2788785-2-heiko@sntech.de
+---
+ drivers/base/component.c | 14 ++++++++++++++
+ include/linux/component.h | 4 +++-
+ 2 files changed, 17 insertions(+), 1 deletion(-)
+
+--- a/drivers/base/component.c
++++ b/drivers/base/component.c
+@@ -569,6 +569,20 @@ void component_master_del(struct device
+ }
+ EXPORT_SYMBOL_GPL(component_master_del);
+
++bool component_master_is_bound(struct device *parent,
++ const struct component_master_ops *ops)
++{
++ struct aggregate_device *adev;
++
++ guard(mutex)(&component_mutex);
++ adev = __aggregate_find(parent, ops);
++ if (!adev)
++ return 0;
++
++ return adev->bound;
++}
++EXPORT_SYMBOL_GPL(component_master_is_bound);
++
+ static void component_unbind(struct component *component,
+ struct aggregate_device *adev, void *data)
+ {
+--- a/include/linux/component.h
++++ b/include/linux/component.h
+@@ -3,7 +3,7 @@
+ #define COMPONENT_H
+
+ #include
+-
++#include
+
+ struct device;
+
+@@ -90,6 +90,8 @@ int component_compare_dev_name(struct de
+
+ void component_master_del(struct device *,
+ const struct component_master_ops *);
++bool component_master_is_bound(struct device *parent,
++ const struct component_master_ops *ops);
+
+ struct component_match;
+
diff --git a/lede/target/linux/rockchip/armv8/config-6.12 b/lede/target/linux/rockchip/armv8/config-6.12
index b2055e1a4f..3b4b33bb0e 100644
--- a/lede/target/linux/rockchip/armv8/config-6.12
+++ b/lede/target/linux/rockchip/armv8/config-6.12
@@ -404,6 +404,7 @@ CONFIG_MFD_CORE=y
CONFIG_MFD_RK8XX=y
CONFIG_MFD_RK8XX_I2C=y
CONFIG_MFD_RK8XX_SPI=y
+CONFIG_MFD_ROCKCHIP_MFPWM=y
CONFIG_MFD_SYSCON=y
CONFIG_MIGRATION=y
CONFIG_MMC=y
@@ -587,6 +588,7 @@ CONFIG_ROCKCHIP_MBOX=y
CONFIG_ROCKCHIP_MFPWM=y
CONFIG_ROCKCHIP_PHY=y
CONFIG_ROCKCHIP_PM_DOMAINS=y
+CONFIG_ROCKCHIP_PWM_CAPTURE=y
CONFIG_ROCKCHIP_THERMAL=y
CONFIG_ROCKCHIP_TIMER=y
CONFIG_RODATA_FULL_DEFAULT_ENABLED=y
diff --git a/lede/target/linux/rockchip/patches-6.12/040-01-v6.13-phy-phy-rockchip-samsung-hdptx-Don-t-request.patch b/lede/target/linux/rockchip/patches-6.12/040-01-v6.13-phy-phy-rockchip-samsung-hdptx-Don-t-request.patch
new file mode 100644
index 0000000000..d938011933
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-01-v6.13-phy-phy-rockchip-samsung-hdptx-Don-t-request.patch
@@ -0,0 +1,71 @@
+From f2dbca169790ea1e436ffdd9ef37d7c3a4401c46 Mon Sep 17 00:00:00 2001
+From: Andy Yan
+Date: Fri, 20 Sep 2024 17:36:28 +0800
+Subject: [PATCH] phy: phy-rockchip-samsung-hdptx: Don't request
+ RST_PHY/RST_ROPLL/RST_LCPLL
+
+RST_PHY/RST_ROPLL/RST_LCPLL are used for debug only on rk3588,
+and they are not exported on rk3576, no need to request it in
+driver.
+
+Signed-off-by: Andy Yan
+Link: https://lore.kernel.org/r/20240920093629.7410-1-andyshrk@163.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 17 +----------------
+ 1 file changed, 1 insertion(+), 16 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -257,13 +257,10 @@ struct ropll_config {
+ };
+
+ enum rk_hdptx_reset {
+- RST_PHY = 0,
+- RST_APB,
++ RST_APB = 0,
+ RST_INIT,
+ RST_CMN,
+ RST_LANE,
+- RST_ROPLL,
+- RST_LCPLL,
+ RST_MAX
+ };
+
+@@ -679,11 +676,6 @@ static void rk_hdptx_phy_disable(struct
+ {
+ u32 val;
+
+- /* reset phy and apb, or phy locked flag may keep 1 */
+- reset_control_assert(hdptx->rsts[RST_PHY].rstc);
+- usleep_range(20, 30);
+- reset_control_deassert(hdptx->rsts[RST_PHY].rstc);
+-
+ reset_control_assert(hdptx->rsts[RST_APB].rstc);
+ usleep_range(20, 30);
+ reset_control_deassert(hdptx->rsts[RST_APB].rstc);
+@@ -804,10 +796,6 @@ static int rk_hdptx_ropll_tmds_cmn_confi
+
+ rk_hdptx_pre_power_up(hdptx);
+
+- reset_control_assert(hdptx->rsts[RST_ROPLL].rstc);
+- usleep_range(20, 30);
+- reset_control_deassert(hdptx->rsts[RST_ROPLL].rstc);
+-
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_cmn_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_tmds_cmn_init_seq);
+
+@@ -1130,13 +1118,10 @@ static int rk_hdptx_phy_probe(struct pla
+ return dev_err_probe(dev, PTR_ERR(hdptx->regmap),
+ "Failed to init regmap\n");
+
+- hdptx->rsts[RST_PHY].id = "phy";
+ hdptx->rsts[RST_APB].id = "apb";
+ hdptx->rsts[RST_INIT].id = "init";
+ hdptx->rsts[RST_CMN].id = "cmn";
+ hdptx->rsts[RST_LANE].id = "lane";
+- hdptx->rsts[RST_ROPLL].id = "ropll";
+- hdptx->rsts[RST_LCPLL].id = "lcpll";
+
+ ret = devm_reset_control_bulk_get_exclusive(dev, RST_MAX, hdptx->rsts);
+ if (ret)
diff --git a/lede/target/linux/rockchip/patches-6.12/040-02-v6.15-phy-phy-rockchip-samsung-hdptx-annotate-regmap.patch b/lede/target/linux/rockchip/patches-6.12/040-02-v6.15-phy-phy-rockchip-samsung-hdptx-annotate-regmap.patch
new file mode 100644
index 0000000000..3af5f85f3f
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-02-v6.15-phy-phy-rockchip-samsung-hdptx-annotate-regmap.patch
@@ -0,0 +1,42 @@
+From c8f7d65cac565cacf0420acf8b54c855dd7b4484 Mon Sep 17 00:00:00 2001
+From: Heiko Stuebner
+Date: Fri, 6 Dec 2024 11:34:00 +0100
+Subject: [PATCH] phy: phy-rockchip-samsung-hdptx: annotate regmap
+ register-callback
+
+The variant of the driver in the vendor-tree contained those handy
+comments in the regmap register callback. Having the different ranges
+describe what they are looks helpful.
+
+Signed-off-by: Heiko Stuebner
+Reviewed-by: Cristian Ciocaltea
+Reviewed-by: Sebastian Reichel
+Link: https://lore.kernel.org/r/20241206103401.1780416-2-heiko@sntech.de
+Signed-off-by: Vinod Koul
+---
+ drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -574,13 +574,13 @@ static const struct reg_sequence rk_hdtp
+ static bool rk_hdptx_phy_is_rw_reg(struct device *dev, unsigned int reg)
+ {
+ switch (reg) {
+- case 0x0000 ... 0x029c:
+- case 0x0400 ... 0x04a4:
+- case 0x0800 ... 0x08a4:
+- case 0x0c00 ... 0x0cb4:
+- case 0x1000 ... 0x10b4:
+- case 0x1400 ... 0x14b4:
+- case 0x1800 ... 0x18b4:
++ case 0x0000 ... 0x029c: /* CMN Register */
++ case 0x0400 ... 0x04a4: /* Sideband Register */
++ case 0x0800 ... 0x08a4: /* Lane Top Register */
++ case 0x0c00 ... 0x0cb4: /* Lane 0 Register */
++ case 0x1000 ... 0x10b4: /* Lane 1 Register */
++ case 0x1400 ... 0x14b4: /* Lane 2 Register */
++ case 0x1800 ... 0x18b4: /* Lane 3 Register */
+ return true;
+ }
+
diff --git a/lede/target/linux/rockchip/patches-6.12/040-03-v6.15-phy-phy-rockchip-samsung-hdptx-Supplement-some-register.patch b/lede/target/linux/rockchip/patches-6.12/040-03-v6.15-phy-phy-rockchip-samsung-hdptx-Supplement-some-register.patch
new file mode 100644
index 0000000000..7ba10067fe
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-03-v6.15-phy-phy-rockchip-samsung-hdptx-Supplement-some-register.patch
@@ -0,0 +1,37 @@
+From f706024107204cb0b640bac35ea47e7b91b8c71f Mon Sep 17 00:00:00 2001
+From: Damon Ding
+Date: Wed, 5 Feb 2025 18:51:55 +0800
+Subject: [PATCH] phy: phy-rockchip-samsung-hdptx: Supplement some register
+ names with their full version
+
+Complete the register names of CMN_REG(0081) and CMN_REG(0087) to their
+full version, and it can help to better match the datasheet.
+
+Signed-off-by: Damon Ding
+Reviewed-by: Dmitry Baryshkov
+Link: https://lore.kernel.org/r/20250205105157.580060-3-damon.ding@rock-chips.com
+Signed-off-by: Vinod Koul
+---
+ drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -82,14 +82,14 @@
+ #define ROPLL_SSC_EN BIT(0)
+ /* CMN_REG(0081) */
+ #define OVRD_PLL_CD_CLK_EN BIT(8)
+-#define PLL_CD_HSCLK_EAST_EN BIT(0)
++#define ANA_PLL_CD_HSCLK_EAST_EN BIT(0)
+ /* CMN_REG(0086) */
+ #define PLL_PCG_POSTDIV_SEL_MASK GENMASK(7, 4)
+ #define PLL_PCG_CLK_SEL_MASK GENMASK(3, 1)
+ #define PLL_PCG_CLK_EN BIT(0)
+ /* CMN_REG(0087) */
+-#define PLL_FRL_MODE_EN BIT(3)
+-#define PLL_TX_HS_CLK_EN BIT(2)
++#define ANA_PLL_FRL_MODE_EN BIT(3)
++#define ANA_PLL_TX_HS_CLK_EN BIT(2)
+ /* CMN_REG(0089) */
+ #define LCPLL_ALONE_MODE BIT(1)
+ /* CMN_REG(0097) */
diff --git a/lede/target/linux/rockchip/patches-6.12/040-04-v6.15-phy-phy-rockchip-samsung-hdptx-Add-the-_MASK-suffix-to.patch b/lede/target/linux/rockchip/patches-6.12/040-04-v6.15-phy-phy-rockchip-samsung-hdptx-Add-the-_MASK-suffix-to.patch
new file mode 100644
index 0000000000..9cc71f8241
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-04-v6.15-phy-phy-rockchip-samsung-hdptx-Add-the-_MASK-suffix-to.patch
@@ -0,0 +1,170 @@
+From 2dc8224e3758c5d6387786ea1d74d2d510149b1a Mon Sep 17 00:00:00 2001
+From: Damon Ding
+Date: Wed, 5 Feb 2025 18:51:56 +0800
+Subject: [PATCH] phy: phy-rockchip-samsung-hdptx: Add the '_MASK' suffix to
+ all registers
+
+Adding the '_MASK' suffix to all registers in order to ensures consistency
+in the naming convention for register macros throughout the file.
+
+Signed-off-by: Damon Ding
+Reviewed-by: Dmitry Baryshkov
+Link: https://lore.kernel.org/r/20250205105157.580060-4-damon.ding@rock-chips.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 88 +++++++++----------
+ 1 file changed, 44 insertions(+), 44 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -50,60 +50,60 @@
+ #define LCPLL_PI_EN_MASK BIT(5)
+ #define LCPLL_100M_CLK_EN_MASK BIT(0)
+ /* CMN_REG(0025) */
+-#define LCPLL_PMS_IQDIV_RSTN BIT(4)
++#define LCPLL_PMS_IQDIV_RSTN_MASK BIT(4)
+ /* CMN_REG(0028) */
+-#define LCPLL_SDC_FRAC_EN BIT(2)
+-#define LCPLL_SDC_FRAC_RSTN BIT(0)
++#define LCPLL_SDC_FRAC_EN_MASK BIT(2)
++#define LCPLL_SDC_FRAC_RSTN_MASK BIT(0)
+ /* CMN_REG(002d) */
+ #define LCPLL_SDC_N_MASK GENMASK(3, 1)
+ /* CMN_REG(002e) */
+ #define LCPLL_SDC_NUMBERATOR_MASK GENMASK(5, 0)
+ /* CMN_REG(002f) */
+ #define LCPLL_SDC_DENOMINATOR_MASK GENMASK(7, 2)
+-#define LCPLL_SDC_NDIV_RSTN BIT(0)
++#define LCPLL_SDC_NDIV_RSTN_MASK BIT(0)
+ /* CMN_REG(003d) */
+-#define ROPLL_LCVCO_EN BIT(4)
++#define ROPLL_LCVCO_EN_MASK BIT(4)
+ /* CMN_REG(004e) */
+-#define ROPLL_PI_EN BIT(5)
++#define ROPLL_PI_EN_MASK BIT(5)
+ /* CMN_REG(005c) */
+-#define ROPLL_PMS_IQDIV_RSTN BIT(5)
++#define ROPLL_PMS_IQDIV_RSTN_MASK BIT(5)
+ /* CMN_REG(005e) */
+ #define ROPLL_SDM_EN_MASK BIT(6)
+-#define ROPLL_SDM_FRAC_EN_RBR BIT(3)
+-#define ROPLL_SDM_FRAC_EN_HBR BIT(2)
+-#define ROPLL_SDM_FRAC_EN_HBR2 BIT(1)
+-#define ROPLL_SDM_FRAC_EN_HBR3 BIT(0)
++#define ROPLL_SDC_FRAC_EN_RBR_MASK BIT(3)
++#define ROPLL_SDC_FRAC_EN_HBR_MASK BIT(2)
++#define ROPLL_SDC_FRAC_EN_HBR2_MASK BIT(1)
++#define ROPLL_SDM_FRAC_EN_HBR3_MASK BIT(0)
+ /* CMN_REG(0064) */
+ #define ROPLL_SDM_NUM_SIGN_RBR_MASK BIT(3)
+ /* CMN_REG(0069) */
+ #define ROPLL_SDC_N_RBR_MASK GENMASK(2, 0)
+ /* CMN_REG(0074) */
+-#define ROPLL_SDC_NDIV_RSTN BIT(2)
+-#define ROPLL_SSC_EN BIT(0)
++#define ROPLL_SDC_NDIV_RSTN_MASK BIT(2)
++#define ROPLL_SSC_EN_MASK BIT(0)
+ /* CMN_REG(0081) */
+-#define OVRD_PLL_CD_CLK_EN BIT(8)
+-#define ANA_PLL_CD_HSCLK_EAST_EN BIT(0)
++#define OVRD_PLL_CD_CLK_EN_MASK BIT(8)
++#define ANA_PLL_CD_HSCLK_EAST_EN_MASK BIT(0)
+ /* CMN_REG(0086) */
+ #define PLL_PCG_POSTDIV_SEL_MASK GENMASK(7, 4)
+ #define PLL_PCG_CLK_SEL_MASK GENMASK(3, 1)
+-#define PLL_PCG_CLK_EN BIT(0)
++#define PLL_PCG_CLK_EN_MASK BIT(0)
+ /* CMN_REG(0087) */
+-#define ANA_PLL_FRL_MODE_EN BIT(3)
+-#define ANA_PLL_TX_HS_CLK_EN BIT(2)
++#define ANA_PLL_FRL_MODE_EN_MASK BIT(3)
++#define ANA_PLL_TX_HS_CLK_EN_MASK BIT(2)
+ /* CMN_REG(0089) */
+-#define LCPLL_ALONE_MODE BIT(1)
++#define LCPLL_ALONE_MODE_MASK BIT(1)
+ /* CMN_REG(0097) */
+-#define DIG_CLK_SEL BIT(1)
++#define DIG_CLK_SEL_MASK BIT(1)
+ #define LCPLL_REF BIT(1)
+ #define ROPLL_REF 0
+ /* CMN_REG(0099) */
+-#define CMN_ROPLL_ALONE_MODE BIT(2)
++#define CMN_ROPLL_ALONE_MODE_MASK BIT(2)
+ #define ROPLL_ALONE_MODE BIT(2)
+ /* CMN_REG(009a) */
+-#define HS_SPEED_SEL BIT(0)
++#define HS_SPEED_SEL_MASK BIT(0)
+ #define DIV_10_CLOCK BIT(0)
+ /* CMN_REG(009b) */
+-#define IS_SPEED_SEL BIT(4)
++#define LS_SPEED_SEL_MASK BIT(4)
+ #define LINK_SYMBOL_CLOCK BIT(4)
+ #define LINK_SYMBOL_CLOCK1_2 0
+
+@@ -161,36 +161,36 @@
+ #define SB_READY_MASK BIT(4)
+
+ /* LNTOP_REG(0200) */
+-#define PROTOCOL_SEL BIT(2)
++#define PROTOCOL_SEL_MASK BIT(2)
+ #define HDMI_MODE BIT(2)
+ #define HDMI_TMDS_FRL_SEL BIT(1)
+ /* LNTOP_REG(0206) */
+-#define DATA_BUS_SEL BIT(0)
++#define DATA_BUS_WIDTH_SEL_MASK BIT(0)
+ #define DATA_BUS_36_40 BIT(0)
+ /* LNTOP_REG(0207) */
+-#define LANE_EN 0xf
++#define LANE_EN_MASK 0xf
+ #define ALL_LANE_EN 0xf
+
+ /* LANE_REG(0312) */
+-#define LN0_TX_SER_RATE_SEL_RBR BIT(5)
+-#define LN0_TX_SER_RATE_SEL_HBR BIT(4)
+-#define LN0_TX_SER_RATE_SEL_HBR2 BIT(3)
+-#define LN0_TX_SER_RATE_SEL_HBR3 BIT(2)
++#define LN0_TX_SER_RATE_SEL_RBR_MASK BIT(5)
++#define LN0_TX_SER_RATE_SEL_HBR_MASK BIT(4)
++#define LN0_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
++#define LN0_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
+ /* LANE_REG(0412) */
+-#define LN1_TX_SER_RATE_SEL_RBR BIT(5)
+-#define LN1_TX_SER_RATE_SEL_HBR BIT(4)
+-#define LN1_TX_SER_RATE_SEL_HBR2 BIT(3)
+-#define LN1_TX_SER_RATE_SEL_HBR3 BIT(2)
++#define LN1_TX_SER_RATE_SEL_RBR_MASK BIT(5)
++#define LN1_TX_SER_RATE_SEL_HBR_MASK BIT(4)
++#define LN1_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
++#define LN1_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
+ /* LANE_REG(0512) */
+-#define LN2_TX_SER_RATE_SEL_RBR BIT(5)
+-#define LN2_TX_SER_RATE_SEL_HBR BIT(4)
+-#define LN2_TX_SER_RATE_SEL_HBR2 BIT(3)
+-#define LN2_TX_SER_RATE_SEL_HBR3 BIT(2)
++#define LN2_TX_SER_RATE_SEL_RBR_MASK BIT(5)
++#define LN2_TX_SER_RATE_SEL_HBR_MASK BIT(4)
++#define LN2_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
++#define LN2_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
+ /* LANE_REG(0612) */
+-#define LN3_TX_SER_RATE_SEL_RBR BIT(5)
+-#define LN3_TX_SER_RATE_SEL_HBR BIT(4)
+-#define LN3_TX_SER_RATE_SEL_HBR2 BIT(3)
+-#define LN3_TX_SER_RATE_SEL_HBR3 BIT(2)
++#define LN3_TX_SER_RATE_SEL_RBR_MASK BIT(5)
++#define LN3_TX_SER_RATE_SEL_HBR_MASK BIT(4)
++#define LN3_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
++#define LN3_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
+
+ #define HDMI14_MAX_RATE 340000000
+ #define HDMI20_MAX_RATE 600000000
+@@ -825,8 +825,8 @@ static int rk_hdptx_ropll_tmds_cmn_confi
+ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_POSTDIV_SEL_MASK,
+ FIELD_PREP(PLL_PCG_POSTDIV_SEL_MASK, cfg->pms_sdiv));
+
+- regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_CLK_EN,
+- PLL_PCG_CLK_EN);
++ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_CLK_EN_MASK,
++ FIELD_PREP(PLL_PCG_CLK_EN_MASK, 0x1));
+
+ ret = rk_hdptx_post_enable_pll(hdptx);
+ if (!ret)
diff --git a/lede/target/linux/rockchip/patches-6.12/040-05-v6.15-phy-phy-rockchip-samsung-hdptx-Add-eDP-mode-support-for.patch b/lede/target/linux/rockchip/patches-6.12/040-05-v6.15-phy-phy-rockchip-samsung-hdptx-Add-eDP-mode-support-for.patch
new file mode 100644
index 0000000000..26d2fc2660
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-05-v6.15-phy-phy-rockchip-samsung-hdptx-Add-eDP-mode-support-for.patch
@@ -0,0 +1,1114 @@
+From 8f831f272b4c89aa13b45bd010c2c18ad97a3f1b Mon Sep 17 00:00:00 2001
+From: Damon Ding
+Date: Wed, 5 Feb 2025 18:51:57 +0800
+Subject: [PATCH] phy: phy-rockchip-samsung-hdptx: Add eDP mode support for
+ RK3588
+
+The PHY is based on a Samsung IP block that supports HDMI 2.1, and eDP
+1.4b. RK3588 integrates the Analogix eDP 1.3 TX controller IP and the
+HDMI/eDP TX Combo PHY to support eDP display.
+
+Add basic support for RBR/HBR/HBR2 link rates, and the voltage swing and
+pre-emphasis configurations of each link rate are set according to the
+eDP 1.3 requirements.
+
+Signed-off-by: Damon Ding
+Reviewed-by: Dmitry Baryshkov
+Link: https://lore.kernel.org/r/20250205105157.580060-5-damon.ding@rock-chips.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 879 +++++++++++++++++-
+ 1 file changed, 869 insertions(+), 10 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -25,6 +25,7 @@
+ #define HDPTX_I_PLL_EN BIT(7)
+ #define HDPTX_I_BIAS_EN BIT(6)
+ #define HDPTX_I_BGR_EN BIT(5)
++#define HDPTX_MODE_SEL BIT(0)
+ #define GRF_HDPTX_STATUS 0x80
+ #define HDPTX_O_PLL_LOCK_DONE BIT(3)
+ #define HDPTX_O_PHY_CLK_RDY BIT(2)
+@@ -44,6 +45,7 @@
+ #define LANE_REG(n) HDTPX_REG(n, 0300, 062d)
+
+ /* CMN_REG(0008) */
++#define OVRD_LCPLL_EN_MASK BIT(7)
+ #define LCPLL_EN_MASK BIT(6)
+ #define LCPLL_LCVCO_MODE_EN_MASK BIT(4)
+ /* CMN_REG(001e) */
+@@ -61,28 +63,88 @@
+ /* CMN_REG(002f) */
+ #define LCPLL_SDC_DENOMINATOR_MASK GENMASK(7, 2)
+ #define LCPLL_SDC_NDIV_RSTN_MASK BIT(0)
++/* CMN_REG(003c) */
++#define ANA_LCPLL_RESERVED7_MASK BIT(7)
+ /* CMN_REG(003d) */
++#define OVRD_ROPLL_EN_MASK BIT(7)
++#define ROPLL_EN_MASK BIT(6)
+ #define ROPLL_LCVCO_EN_MASK BIT(4)
++/* CMN_REG(0046) */
++#define ROPLL_ANA_CPP_CTRL_COARSE_MASK GENMASK(7, 4)
++#define ROPLL_ANA_CPP_CTRL_FINE_MASK GENMASK(3, 0)
++/* CMN_REG(0047) */
++#define ROPLL_ANA_LPF_C_SEL_COARSE_MASK GENMASK(5, 3)
++#define ROPLL_ANA_LPF_C_SEL_FINE_MASK GENMASK(2, 0)
+ /* CMN_REG(004e) */
+ #define ROPLL_PI_EN_MASK BIT(5)
++/* CMN_REG(0051) */
++#define ROPLL_PMS_MDIV_MASK GENMASK(7, 0)
++/* CMN_REG(0055) */
++#define ROPLL_PMS_MDIV_AFC_MASK GENMASK(7, 0)
++/* CMN_REG(0059) */
++#define ANA_ROPLL_PMS_PDIV_MASK GENMASK(7, 4)
++#define ANA_ROPLL_PMS_REFDIV_MASK GENMASK(3, 0)
++/* CMN_REG(005a) */
++#define ROPLL_PMS_SDIV_RBR_MASK GENMASK(7, 4)
++#define ROPLL_PMS_SDIV_HBR_MASK GENMASK(3, 0)
++/* CMN_REG(005b) */
++#define ROPLL_PMS_SDIV_HBR2_MASK GENMASK(7, 4)
+ /* CMN_REG(005c) */
+ #define ROPLL_PMS_IQDIV_RSTN_MASK BIT(5)
+ /* CMN_REG(005e) */
+ #define ROPLL_SDM_EN_MASK BIT(6)
++#define OVRD_ROPLL_SDM_RSTN_MASK BIT(5)
++#define ROPLL_SDM_RSTN_MASK BIT(4)
+ #define ROPLL_SDC_FRAC_EN_RBR_MASK BIT(3)
+ #define ROPLL_SDC_FRAC_EN_HBR_MASK BIT(2)
+ #define ROPLL_SDC_FRAC_EN_HBR2_MASK BIT(1)
+ #define ROPLL_SDM_FRAC_EN_HBR3_MASK BIT(0)
++/* CMN_REG(005f) */
++#define OVRD_ROPLL_SDC_RSTN_MASK BIT(5)
++#define ROPLL_SDC_RSTN_MASK BIT(4)
++/* CMN_REG(0060) */
++#define ROPLL_SDM_DENOMINATOR_MASK GENMASK(7, 0)
+ /* CMN_REG(0064) */
+ #define ROPLL_SDM_NUM_SIGN_RBR_MASK BIT(3)
++#define ROPLL_SDM_NUM_SIGN_HBR_MASK BIT(2)
++#define ROPLL_SDM_NUM_SIGN_HBR2_MASK BIT(1)
++/* CMN_REG(0065) */
++#define ROPLL_SDM_NUM_MASK GENMASK(7, 0)
+ /* CMN_REG(0069) */
+ #define ROPLL_SDC_N_RBR_MASK GENMASK(2, 0)
++/* CMN_REG(006a) */
++#define ROPLL_SDC_N_HBR_MASK GENMASK(5, 3)
++#define ROPLL_SDC_N_HBR2_MASK GENMASK(2, 0)
++/* CMN_REG(006b) */
++#define ROPLL_SDC_N_HBR3_MASK GENMASK(3, 1)
++/* CMN_REG(006c) */
++#define ROPLL_SDC_NUM_MASK GENMASK(5, 0)
++/* cmn_reg0070 */
++#define ROPLL_SDC_DENO_MASK GENMASK(5, 0)
+ /* CMN_REG(0074) */
++#define OVRD_ROPLL_SDC_NDIV_RSTN_MASK BIT(3)
+ #define ROPLL_SDC_NDIV_RSTN_MASK BIT(2)
++#define OVRD_ROPLL_SSC_EN_MASK BIT(1)
+ #define ROPLL_SSC_EN_MASK BIT(0)
++/* CMN_REG(0075) */
++#define ANA_ROPLL_SSC_FM_DEVIATION_MASK GENMASK(5, 0)
++/* CMN_REG(0076) */
++#define ANA_ROPLL_SSC_FM_FREQ_MASK GENMASK(6, 2)
++/* CMN_REG(0077) */
++#define ANA_ROPLL_SSC_CLK_DIV_SEL_MASK GENMASK(6, 3)
+ /* CMN_REG(0081) */
+ #define OVRD_PLL_CD_CLK_EN_MASK BIT(8)
++#define ANA_PLL_CD_TX_SER_RATE_SEL_MASK BIT(3)
++#define ANA_PLL_CD_HSCLK_WEST_EN_MASK BIT(1)
+ #define ANA_PLL_CD_HSCLK_EAST_EN_MASK BIT(0)
++/* CMN_REG(0082) */
++#define ANA_PLL_CD_VREG_GAIN_CTRL_MASK GENMASK(3, 0)
++/* CMN_REG(0083) */
++#define ANA_PLL_CD_VREG_ICTRL_MASK GENMASK(6, 5)
++/* CMN_REG(0084) */
++#define PLL_LCRO_CLK_SEL_MASK BIT(5)
++/* CMN_REG(0085) */
++#define ANA_PLL_SYNC_LOSS_DET_MODE_MASK GENMASK(1, 0)
+ /* CMN_REG(0086) */
+ #define PLL_PCG_POSTDIV_SEL_MASK GENMASK(7, 4)
+ #define PLL_PCG_CLK_SEL_MASK GENMASK(3, 1)
+@@ -92,11 +154,14 @@
+ #define ANA_PLL_TX_HS_CLK_EN_MASK BIT(2)
+ /* CMN_REG(0089) */
+ #define LCPLL_ALONE_MODE_MASK BIT(1)
++/* CMN_REG(0095) */
++#define DP_TX_LINK_BW_MASK GENMASK(1, 0)
+ /* CMN_REG(0097) */
+ #define DIG_CLK_SEL_MASK BIT(1)
+ #define LCPLL_REF BIT(1)
+ #define ROPLL_REF 0
+ /* CMN_REG(0099) */
++#define SSC_EN_MASK GENMASK(7, 6)
+ #define CMN_ROPLL_ALONE_MODE_MASK BIT(2)
+ #define ROPLL_ALONE_MODE BIT(2)
+ /* CMN_REG(009a) */
+@@ -118,6 +183,8 @@
+ /* SB_REG(0104) */
+ #define OVRD_SB_EN_MASK BIT(5)
+ #define SB_EN_MASK BIT(4)
++#define OVRD_SB_AUX_EN_MASK BIT(1)
++#define SB_AUX_EN_MASK BIT(0)
+ /* SB_REG(0105) */
+ #define OVRD_SB_EARC_CMDC_EN_MASK BIT(6)
+ #define SB_EARC_CMDC_EN_MASK BIT(5)
+@@ -126,6 +193,8 @@
+ #define ANA_SB_TX_LLVL_PROG_MASK GENMASK(6, 4)
+ /* SB_REG(0109) */
+ #define ANA_SB_DMRX_AFC_DIV_RATIO_MASK GENMASK(2, 0)
++/* SB_REG(010d) */
++#define ANA_SB_DMRX_LPBK_DATA_MASK BIT(4)
+ /* SB_REG(010f) */
+ #define OVRD_SB_VREG_EN_MASK BIT(7)
+ #define SB_VREG_EN_MASK BIT(6)
+@@ -133,6 +202,7 @@
+ #define SB_VREG_LPF_BYPASS_MASK BIT(4)
+ #define ANA_SB_VREG_GAIN_CTRL_MASK GENMASK(3, 0)
+ /* SB_REG(0110) */
++#define ANA_SB_VREG_OUT_SEL_MASK BIT(1)
+ #define ANA_SB_VREG_REF_SEL_MASK BIT(0)
+ /* SB_REG(0113) */
+ #define SB_RX_RCAL_OPT_CODE_MASK GENMASK(5, 4)
+@@ -147,13 +217,24 @@
+ #define AFC_RSTN_DELAY_TIME_MASK GENMASK(6, 4)
+ /* SB_REG(0117) */
+ #define FAST_PULSE_TIME_MASK GENMASK(3, 0)
++/* SB_REG(0118) */
++#define SB_TG_EARC_DMRX_RECVRD_CLK_CNT_MASK GENMASK(7, 0)
++/* SB_REG(011a) */
++#define SB_TG_CNT_RUN_NO_7_0_MASK GENMASK(7, 0)
+ /* SB_REG(011b) */
+ #define SB_EARC_SIG_DET_BYPASS_MASK BIT(4)
+ #define SB_AFC_TOL_MASK GENMASK(3, 0)
++/* SB_REG(011c) */
++#define SB_AFC_STB_NUM_MASK GENMASK(3, 0)
++/* SB_REG(011d) */
++#define SB_TG_OSC_CNT_MIN_MASK GENMASK(7, 0)
++/* SB_REG(011e) */
++#define SB_TG_OSC_CNT_MAX_MASK GENMASK(7, 0)
+ /* SB_REG(011f) */
+ #define SB_PWM_AFC_CTRL_MASK GENMASK(7, 2)
+ #define SB_RCAL_RSTN_MASK BIT(1)
+ /* SB_REG(0120) */
++#define SB_AUX_EN_IN_MASK BIT(7)
+ #define SB_EARC_EN_MASK BIT(1)
+ #define SB_EARC_AFC_EN_MASK BIT(2)
+ /* SB_REG(0123) */
+@@ -165,27 +246,74 @@
+ #define HDMI_MODE BIT(2)
+ #define HDMI_TMDS_FRL_SEL BIT(1)
+ /* LNTOP_REG(0206) */
++#define DATA_BUS_WIDTH_MASK GENMASK(2, 1)
+ #define DATA_BUS_WIDTH_SEL_MASK BIT(0)
+ #define DATA_BUS_36_40 BIT(0)
+ /* LNTOP_REG(0207) */
+ #define LANE_EN_MASK 0xf
+ #define ALL_LANE_EN 0xf
+
++/* LANE_REG(0301) */
++#define OVRD_LN_TX_DRV_EI_EN_MASK BIT(7)
++#define LN_TX_DRV_EI_EN_MASK BIT(6)
++/* LANE_REG(0303) */
++#define OVRD_LN_TX_DRV_LVL_CTRL_MASK BIT(5)
++#define LN_TX_DRV_LVL_CTRL_MASK GENMASK(4, 0)
++/* LANE_REG(0304) */
++#define OVRD_LN_TX_DRV_POST_LVL_CTRL_MASK BIT(4)
++#define LN_TX_DRV_POST_LVL_CTRL_MASK GENMASK(3, 0)
++/* LANE_REG(0305) */
++#define OVRD_LN_TX_DRV_PRE_LVL_CTRL_MASK BIT(6)
++#define LN_TX_DRV_PRE_LVL_CTRL_MASK GENMASK(5, 2)
++/* LANE_REG(0306) */
++#define LN_ANA_TX_DRV_IDRV_IDN_CTRL_MASK GENMASK(7, 5)
++#define LN_ANA_TX_DRV_IDRV_IUP_CTRL_MASK GENMASK(4, 2)
++#define LN_ANA_TX_DRV_ACCDRV_EN_MASK BIT(0)
++/* LANE_REG(0307) */
++#define LN_ANA_TX_DRV_ACCDRV_POL_SEL_MASK BIT(6)
++#define LN_ANA_TX_DRV_ACCDRV_CTRL_MASK GENMASK(5, 3)
++/* LANE_REG(030a) */
++#define LN_ANA_TX_JEQ_EN_MASK BIT(4)
++#define LN_TX_JEQ_EVEN_CTRL_RBR_MASK GENMASK(3, 0)
++/* LANE_REG(030b) */
++#define LN_TX_JEQ_EVEN_CTRL_HBR_MASK GENMASK(7, 4)
++#define LN_TX_JEQ_EVEN_CTRL_HBR2_MASK GENMASK(3, 0)
++/* LANE_REG(030c) */
++#define LN_TX_JEQ_ODD_CTRL_RBR_MASK GENMASK(3, 0)
++/* LANE_REG(030d) */
++#define LN_TX_JEQ_ODD_CTRL_HBR_MASK GENMASK(7, 4)
++#define LN_TX_JEQ_ODD_CTRL_HBR2_MASK GENMASK(3, 0)
++/* LANE_REG(0310) */
++#define LN_ANA_TX_SYNC_LOSS_DET_MODE_MASK GENMASK(1, 0)
++/* LANE_REG(0311) */
++#define LN_TX_SER_40BIT_EN_RBR_MASK BIT(3)
++#define LN_TX_SER_40BIT_EN_HBR_MASK BIT(2)
++#define LN_TX_SER_40BIT_EN_HBR2_MASK BIT(1)
+ /* LANE_REG(0312) */
+ #define LN0_TX_SER_RATE_SEL_RBR_MASK BIT(5)
+ #define LN0_TX_SER_RATE_SEL_HBR_MASK BIT(4)
+ #define LN0_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
+ #define LN0_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
++/* LANE_REG(0316) */
++#define LN_ANA_TX_SER_VREG_GAIN_CTRL_MASK GENMASK(3, 0)
++/* LANE_REG(031B) */
++#define LN_ANA_TX_RESERVED_MASK GENMASK(7, 0)
++/* LANE_REG(031e) */
++#define LN_POLARITY_INV_MASK BIT(2)
++#define LN_LANE_MODE_MASK BIT(1)
++
+ /* LANE_REG(0412) */
+ #define LN1_TX_SER_RATE_SEL_RBR_MASK BIT(5)
+ #define LN1_TX_SER_RATE_SEL_HBR_MASK BIT(4)
+ #define LN1_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
+ #define LN1_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
++
+ /* LANE_REG(0512) */
+ #define LN2_TX_SER_RATE_SEL_RBR_MASK BIT(5)
+ #define LN2_TX_SER_RATE_SEL_HBR_MASK BIT(4)
+ #define LN2_TX_SER_RATE_SEL_HBR2_MASK BIT(3)
+ #define LN2_TX_SER_RATE_SEL_HBR3_MASK BIT(2)
++
+ /* LANE_REG(0612) */
+ #define LN3_TX_SER_RATE_SEL_RBR_MASK BIT(5)
+ #define LN3_TX_SER_RATE_SEL_HBR_MASK BIT(4)
+@@ -195,6 +323,12 @@
+ #define HDMI14_MAX_RATE 340000000
+ #define HDMI20_MAX_RATE 600000000
+
++enum dp_link_rate {
++ DP_BW_RBR,
++ DP_BW_HBR,
++ DP_BW_HBR2,
++};
++
+ struct lcpll_config {
+ u32 bit_rate;
+ u8 lcvco_mode_en;
+@@ -256,6 +390,19 @@ struct ropll_config {
+ u8 cd_tx_ser_rate_sel;
+ };
+
++struct tx_drv_ctrl {
++ u8 tx_drv_lvl_ctrl;
++ u8 tx_drv_post_lvl_ctrl;
++ u8 ana_tx_drv_idrv_idn_ctrl;
++ u8 ana_tx_drv_idrv_iup_ctrl;
++ u8 ana_tx_drv_accdrv_en;
++ u8 ana_tx_drv_accdrv_ctrl;
++ u8 tx_drv_pre_lvl_ctrl;
++ u8 ana_tx_jeq_en;
++ u8 tx_jeq_even_ctrl;
++ u8 tx_jeq_odd_ctrl;
++};
++
+ enum rk_hdptx_reset {
+ RST_APB = 0,
+ RST_INIT,
+@@ -291,6 +438,10 @@ struct rk_hdptx_phy {
+ unsigned long rate;
+
+ atomic_t usage_count;
++
++ /* used for dp mode */
++ unsigned int link_rate;
++ unsigned int lanes;
+ };
+
+ static const struct ropll_config ropll_tmds_cfg[] = {
+@@ -571,6 +722,90 @@ static const struct reg_sequence rk_hdtp
+ REG_SEQ0(LANE_REG(0606), 0x1c),
+ };
+
++static struct tx_drv_ctrl tx_drv_ctrl_rbr[4][4] = {
++ /* voltage swing 0, pre-emphasis 0->3 */
++ {
++ { 0x2, 0x0, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ { 0x4, 0x3, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0x7, 0x6, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0xd, 0xc, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 1, pre-emphasis 0->2 */
++ {
++ { 0x4, 0x0, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ { 0x9, 0x5, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0xc, 0x8, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 2, pre-emphasis 0->1 */
++ {
++ { 0x8, 0x0, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ { 0xc, 0x5, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 3, pre-emphasis 0 */
++ {
++ { 0xb, 0x0, 0x7, 0x7, 0x1, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ }
++};
++
++static struct tx_drv_ctrl tx_drv_ctrl_hbr[4][4] = {
++ /* voltage swing 0, pre-emphasis 0->3 */
++ {
++ { 0x2, 0x0, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ { 0x5, 0x4, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0x9, 0x8, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0xd, 0xc, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 1, pre-emphasis 0->2 */
++ {
++ { 0x6, 0x1, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ { 0xa, 0x6, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0xc, 0x8, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 2, pre-emphasis 0->1 */
++ {
++ { 0x9, 0x1, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ { 0xd, 0x6, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 3, pre-emphasis 0 */
++ {
++ { 0xc, 0x1, 0x7, 0x7, 0x1, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ }
++};
++
++static struct tx_drv_ctrl tx_drv_ctrl_hbr2[4][4] = {
++ /* voltage swing 0, pre-emphasis 0->3 */
++ {
++ { 0x2, 0x1, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0x5, 0x4, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0x9, 0x8, 0x4, 0x6, 0x1, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0xd, 0xc, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 1, pre-emphasis 0->2 */
++ {
++ { 0x6, 0x1, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ { 0xb, 0x7, 0x4, 0x6, 0x0, 0x4, 0x0, 0x1, 0x7, 0x7 },
++ { 0xd, 0x9, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 2, pre-emphasis 0->1 */
++ {
++ { 0x8, 0x1, 0x4, 0x6, 0x0, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ { 0xc, 0x6, 0x7, 0x7, 0x1, 0x7, 0x0, 0x1, 0x7, 0x7 },
++ },
++
++ /* voltage swing 3, pre-emphasis 0 */
++ {
++ { 0xb, 0x0, 0x7, 0x7, 0x1, 0x4, 0x1, 0x1, 0x7, 0x7 },
++ }
++};
++
+ static bool rk_hdptx_phy_is_rw_reg(struct device *dev, unsigned int reg)
+ {
+ switch (reg) {
+@@ -859,9 +1094,45 @@ static int rk_hdptx_ropll_tmds_mode_conf
+ return rk_hdptx_post_enable_lane(hdptx);
+ }
+
++static void rk_hdptx_dp_reset(struct rk_hdptx_phy *hdptx)
++{
++ reset_control_assert(hdptx->rsts[RST_LANE].rstc);
++ reset_control_assert(hdptx->rsts[RST_CMN].rstc);
++ reset_control_assert(hdptx->rsts[RST_INIT].rstc);
++
++ reset_control_assert(hdptx->rsts[RST_APB].rstc);
++ udelay(10);
++ reset_control_deassert(hdptx->rsts[RST_APB].rstc);
++
++ regmap_update_bits(hdptx->regmap, LANE_REG(0301),
++ OVRD_LN_TX_DRV_EI_EN_MASK | LN_TX_DRV_EI_EN_MASK,
++ FIELD_PREP(OVRD_LN_TX_DRV_EI_EN_MASK, 1) |
++ FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0401),
++ OVRD_LN_TX_DRV_EI_EN_MASK | LN_TX_DRV_EI_EN_MASK,
++ FIELD_PREP(OVRD_LN_TX_DRV_EI_EN_MASK, 1) |
++ FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0501),
++ OVRD_LN_TX_DRV_EI_EN_MASK | LN_TX_DRV_EI_EN_MASK,
++ FIELD_PREP(OVRD_LN_TX_DRV_EI_EN_MASK, 1) |
++ FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0601),
++ OVRD_LN_TX_DRV_EI_EN_MASK | LN_TX_DRV_EI_EN_MASK,
++ FIELD_PREP(OVRD_LN_TX_DRV_EI_EN_MASK, 1) |
++ FIELD_PREP(LN_TX_DRV_EI_EN_MASK, 0));
++
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0));
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x0));
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x0));
++}
++
+ static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx,
+ unsigned int rate)
+ {
++ enum phy_mode mode = phy_get_mode(hdptx->phy);
+ u32 status;
+ int ret;
+
+@@ -875,10 +1146,14 @@ static int rk_hdptx_phy_consumer_get(str
+ if (status & HDPTX_O_PLL_LOCK_DONE)
+ dev_warn(hdptx->dev, "PLL locked by unknown consumer!\n");
+
+- if (rate) {
+- ret = rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
+- if (ret)
+- goto dec_usage;
++ if (mode == PHY_MODE_DP) {
++ rk_hdptx_dp_reset(hdptx);
++ } else {
++ if (rate) {
++ ret = rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
++ if (ret)
++ goto dec_usage;
++ }
+ }
+
+ return 0;
+@@ -890,6 +1165,7 @@ dec_usage:
+
+ static int rk_hdptx_phy_consumer_put(struct rk_hdptx_phy *hdptx, bool force)
+ {
++ enum phy_mode mode = phy_get_mode(hdptx->phy);
+ u32 status;
+ int ret;
+
+@@ -903,8 +1179,12 @@ static int rk_hdptx_phy_consumer_put(str
+ } else {
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &status);
+ if (!ret) {
+- if (status & HDPTX_O_PLL_LOCK_DONE)
+- rk_hdptx_phy_disable(hdptx);
++ if (status & HDPTX_O_PLL_LOCK_DONE) {
++ if (mode == PHY_MODE_DP)
++ rk_hdptx_dp_reset(hdptx);
++ else
++ rk_hdptx_phy_disable(hdptx);
++ }
+ return 0;
+ } else if (force) {
+ return 0;
+@@ -915,11 +1195,262 @@ static int rk_hdptx_phy_consumer_put(str
+ return ret;
+ }
+
++static void rk_hdptx_dp_pll_init(struct rk_hdptx_phy *hdptx)
++{
++ regmap_update_bits(hdptx->regmap, CMN_REG(003c), ANA_LCPLL_RESERVED7_MASK,
++ FIELD_PREP(ANA_LCPLL_RESERVED7_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0046),
++ ROPLL_ANA_CPP_CTRL_COARSE_MASK | ROPLL_ANA_CPP_CTRL_FINE_MASK,
++ FIELD_PREP(ROPLL_ANA_CPP_CTRL_COARSE_MASK, 0xe) |
++ FIELD_PREP(ROPLL_ANA_CPP_CTRL_FINE_MASK, 0xe));
++ regmap_update_bits(hdptx->regmap, CMN_REG(0047),
++ ROPLL_ANA_LPF_C_SEL_COARSE_MASK |
++ ROPLL_ANA_LPF_C_SEL_FINE_MASK,
++ FIELD_PREP(ROPLL_ANA_LPF_C_SEL_COARSE_MASK, 0x4) |
++ FIELD_PREP(ROPLL_ANA_LPF_C_SEL_FINE_MASK, 0x4));
++
++ regmap_write(hdptx->regmap, CMN_REG(0051), FIELD_PREP(ROPLL_PMS_MDIV_MASK, 0x87));
++ regmap_write(hdptx->regmap, CMN_REG(0052), FIELD_PREP(ROPLL_PMS_MDIV_MASK, 0x71));
++ regmap_write(hdptx->regmap, CMN_REG(0053), FIELD_PREP(ROPLL_PMS_MDIV_MASK, 0x71));
++
++ regmap_write(hdptx->regmap, CMN_REG(0055),
++ FIELD_PREP(ROPLL_PMS_MDIV_AFC_MASK, 0x87));
++ regmap_write(hdptx->regmap, CMN_REG(0056),
++ FIELD_PREP(ROPLL_PMS_MDIV_AFC_MASK, 0x71));
++ regmap_write(hdptx->regmap, CMN_REG(0057),
++ FIELD_PREP(ROPLL_PMS_MDIV_AFC_MASK, 0x71));
++
++ regmap_write(hdptx->regmap, CMN_REG(0059),
++ FIELD_PREP(ANA_ROPLL_PMS_PDIV_MASK, 0x1) |
++ FIELD_PREP(ANA_ROPLL_PMS_REFDIV_MASK, 0x1));
++ regmap_write(hdptx->regmap, CMN_REG(005a),
++ FIELD_PREP(ROPLL_PMS_SDIV_RBR_MASK, 0x3) |
++ FIELD_PREP(ROPLL_PMS_SDIV_HBR_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(005b), ROPLL_PMS_SDIV_HBR2_MASK,
++ FIELD_PREP(ROPLL_PMS_SDIV_HBR2_MASK, 0x0));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDM_EN_MASK,
++ FIELD_PREP(ROPLL_SDM_EN_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(005e),
++ OVRD_ROPLL_SDM_RSTN_MASK | ROPLL_SDM_RSTN_MASK,
++ FIELD_PREP(OVRD_ROPLL_SDM_RSTN_MASK, 0x1) |
++ FIELD_PREP(ROPLL_SDM_RSTN_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDC_FRAC_EN_RBR_MASK,
++ FIELD_PREP(ROPLL_SDC_FRAC_EN_RBR_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDC_FRAC_EN_HBR_MASK,
++ FIELD_PREP(ROPLL_SDC_FRAC_EN_HBR_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDC_FRAC_EN_HBR2_MASK,
++ FIELD_PREP(ROPLL_SDC_FRAC_EN_HBR2_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(005f),
++ OVRD_ROPLL_SDC_RSTN_MASK | ROPLL_SDC_RSTN_MASK,
++ FIELD_PREP(OVRD_ROPLL_SDC_RSTN_MASK, 0x1) |
++ FIELD_PREP(ROPLL_SDC_RSTN_MASK, 0x1));
++ regmap_write(hdptx->regmap, CMN_REG(0060),
++ FIELD_PREP(ROPLL_SDM_DENOMINATOR_MASK, 0x21));
++ regmap_write(hdptx->regmap, CMN_REG(0061),
++ FIELD_PREP(ROPLL_SDM_DENOMINATOR_MASK, 0x27));
++ regmap_write(hdptx->regmap, CMN_REG(0062),
++ FIELD_PREP(ROPLL_SDM_DENOMINATOR_MASK, 0x27));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0064),
++ ROPLL_SDM_NUM_SIGN_RBR_MASK |
++ ROPLL_SDM_NUM_SIGN_HBR_MASK |
++ ROPLL_SDM_NUM_SIGN_HBR2_MASK,
++ FIELD_PREP(ROPLL_SDM_NUM_SIGN_RBR_MASK, 0x0) |
++ FIELD_PREP(ROPLL_SDM_NUM_SIGN_HBR_MASK, 0x1) |
++ FIELD_PREP(ROPLL_SDM_NUM_SIGN_HBR2_MASK, 0x1));
++ regmap_write(hdptx->regmap, CMN_REG(0065),
++ FIELD_PREP(ROPLL_SDM_NUM_MASK, 0x0));
++ regmap_write(hdptx->regmap, CMN_REG(0066),
++ FIELD_PREP(ROPLL_SDM_NUM_MASK, 0xd));
++ regmap_write(hdptx->regmap, CMN_REG(0067),
++ FIELD_PREP(ROPLL_SDM_NUM_MASK, 0xd));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0069), ROPLL_SDC_N_RBR_MASK,
++ FIELD_PREP(ROPLL_SDC_N_RBR_MASK, 0x2));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(006a),
++ ROPLL_SDC_N_HBR_MASK | ROPLL_SDC_N_HBR2_MASK,
++ FIELD_PREP(ROPLL_SDC_N_HBR_MASK, 0x1) |
++ FIELD_PREP(ROPLL_SDC_N_HBR2_MASK, 0x1));
++
++ regmap_write(hdptx->regmap, CMN_REG(006c),
++ FIELD_PREP(ROPLL_SDC_NUM_MASK, 0x3));
++ regmap_write(hdptx->regmap, CMN_REG(006d),
++ FIELD_PREP(ROPLL_SDC_NUM_MASK, 0x7));
++ regmap_write(hdptx->regmap, CMN_REG(006e),
++ FIELD_PREP(ROPLL_SDC_NUM_MASK, 0x7));
++
++ regmap_write(hdptx->regmap, CMN_REG(0070),
++ FIELD_PREP(ROPLL_SDC_DENO_MASK, 0x8));
++ regmap_write(hdptx->regmap, CMN_REG(0071),
++ FIELD_PREP(ROPLL_SDC_DENO_MASK, 0x18));
++ regmap_write(hdptx->regmap, CMN_REG(0072),
++ FIELD_PREP(ROPLL_SDC_DENO_MASK, 0x18));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0074),
++ OVRD_ROPLL_SDC_NDIV_RSTN_MASK | ROPLL_SDC_NDIV_RSTN_MASK,
++ FIELD_PREP(OVRD_ROPLL_SDC_NDIV_RSTN_MASK, 0x1) |
++ FIELD_PREP(ROPLL_SDC_NDIV_RSTN_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0077), ANA_ROPLL_SSC_CLK_DIV_SEL_MASK,
++ FIELD_PREP(ANA_ROPLL_SSC_CLK_DIV_SEL_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0081), ANA_PLL_CD_TX_SER_RATE_SEL_MASK,
++ FIELD_PREP(ANA_PLL_CD_TX_SER_RATE_SEL_MASK, 0x0));
++ regmap_update_bits(hdptx->regmap, CMN_REG(0081),
++ ANA_PLL_CD_HSCLK_EAST_EN_MASK | ANA_PLL_CD_HSCLK_WEST_EN_MASK,
++ FIELD_PREP(ANA_PLL_CD_HSCLK_EAST_EN_MASK, 0x1) |
++ FIELD_PREP(ANA_PLL_CD_HSCLK_WEST_EN_MASK, 0x0));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0082), ANA_PLL_CD_VREG_GAIN_CTRL_MASK,
++ FIELD_PREP(ANA_PLL_CD_VREG_GAIN_CTRL_MASK, 0x4));
++ regmap_update_bits(hdptx->regmap, CMN_REG(0083), ANA_PLL_CD_VREG_ICTRL_MASK,
++ FIELD_PREP(ANA_PLL_CD_VREG_ICTRL_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(0084), PLL_LCRO_CLK_SEL_MASK,
++ FIELD_PREP(PLL_LCRO_CLK_SEL_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(0085), ANA_PLL_SYNC_LOSS_DET_MODE_MASK,
++ FIELD_PREP(ANA_PLL_SYNC_LOSS_DET_MODE_MASK, 0x3));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0087), ANA_PLL_TX_HS_CLK_EN_MASK,
++ FIELD_PREP(ANA_PLL_TX_HS_CLK_EN_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0097), DIG_CLK_SEL_MASK,
++ FIELD_PREP(DIG_CLK_SEL_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0099), CMN_ROPLL_ALONE_MODE_MASK,
++ FIELD_PREP(CMN_ROPLL_ALONE_MODE_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(009a), HS_SPEED_SEL_MASK,
++ FIELD_PREP(HS_SPEED_SEL_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, CMN_REG(009b), LS_SPEED_SEL_MASK,
++ FIELD_PREP(LS_SPEED_SEL_MASK, 0x1));
++}
++
++static int rk_hdptx_dp_aux_init(struct rk_hdptx_phy *hdptx)
++{
++ u32 status;
++ int ret;
++
++ regmap_update_bits(hdptx->regmap, SB_REG(0102), ANA_SB_RXTERM_OFFSP_MASK,
++ FIELD_PREP(ANA_SB_RXTERM_OFFSP_MASK, 0x3));
++ regmap_update_bits(hdptx->regmap, SB_REG(0103), ANA_SB_RXTERM_OFFSN_MASK,
++ FIELD_PREP(ANA_SB_RXTERM_OFFSN_MASK, 0x3));
++ regmap_update_bits(hdptx->regmap, SB_REG(0104), SB_AUX_EN_MASK,
++ FIELD_PREP(SB_AUX_EN_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, SB_REG(0105), ANA_SB_TX_HLVL_PROG_MASK,
++ FIELD_PREP(ANA_SB_TX_HLVL_PROG_MASK, 0x7));
++ regmap_update_bits(hdptx->regmap, SB_REG(0106), ANA_SB_TX_LLVL_PROG_MASK,
++ FIELD_PREP(ANA_SB_TX_LLVL_PROG_MASK, 0x7));
++
++ regmap_update_bits(hdptx->regmap, SB_REG(010d), ANA_SB_DMRX_LPBK_DATA_MASK,
++ FIELD_PREP(ANA_SB_DMRX_LPBK_DATA_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, SB_REG(010f), ANA_SB_VREG_GAIN_CTRL_MASK,
++ FIELD_PREP(ANA_SB_VREG_GAIN_CTRL_MASK, 0x0));
++ regmap_update_bits(hdptx->regmap, SB_REG(0110),
++ ANA_SB_VREG_OUT_SEL_MASK | ANA_SB_VREG_REF_SEL_MASK,
++ FIELD_PREP(ANA_SB_VREG_OUT_SEL_MASK, 0x1) |
++ FIELD_PREP(ANA_SB_VREG_REF_SEL_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, SB_REG(0113),
++ SB_RX_RCAL_OPT_CODE_MASK | SB_RX_RTERM_CTRL_MASK,
++ FIELD_PREP(SB_RX_RCAL_OPT_CODE_MASK, 0x1) |
++ FIELD_PREP(SB_RX_RTERM_CTRL_MASK, 0x3));
++ regmap_update_bits(hdptx->regmap, SB_REG(0114),
++ SB_TG_SB_EN_DELAY_TIME_MASK | SB_TG_RXTERM_EN_DELAY_TIME_MASK,
++ FIELD_PREP(SB_TG_SB_EN_DELAY_TIME_MASK, 0x2) |
++ FIELD_PREP(SB_TG_RXTERM_EN_DELAY_TIME_MASK, 0x2));
++ regmap_update_bits(hdptx->regmap, SB_REG(0115),
++ SB_READY_DELAY_TIME_MASK | SB_TG_OSC_EN_DELAY_TIME_MASK,
++ FIELD_PREP(SB_READY_DELAY_TIME_MASK, 0x2) |
++ FIELD_PREP(SB_TG_OSC_EN_DELAY_TIME_MASK, 0x2));
++ regmap_update_bits(hdptx->regmap, SB_REG(0116),
++ AFC_RSTN_DELAY_TIME_MASK,
++ FIELD_PREP(AFC_RSTN_DELAY_TIME_MASK, 0x2));
++ regmap_update_bits(hdptx->regmap, SB_REG(0117),
++ FAST_PULSE_TIME_MASK,
++ FIELD_PREP(FAST_PULSE_TIME_MASK, 0x4));
++ regmap_update_bits(hdptx->regmap, SB_REG(0118),
++ SB_TG_EARC_DMRX_RECVRD_CLK_CNT_MASK,
++ FIELD_PREP(SB_TG_EARC_DMRX_RECVRD_CLK_CNT_MASK, 0xa));
++
++ regmap_update_bits(hdptx->regmap, SB_REG(011a), SB_TG_CNT_RUN_NO_7_0_MASK,
++ FIELD_PREP(SB_TG_CNT_RUN_NO_7_0_MASK, 0x3));
++ regmap_update_bits(hdptx->regmap, SB_REG(011b),
++ SB_EARC_SIG_DET_BYPASS_MASK | SB_AFC_TOL_MASK,
++ FIELD_PREP(SB_EARC_SIG_DET_BYPASS_MASK, 0x1) |
++ FIELD_PREP(SB_AFC_TOL_MASK, 0x3));
++ regmap_update_bits(hdptx->regmap, SB_REG(011c), SB_AFC_STB_NUM_MASK,
++ FIELD_PREP(SB_AFC_STB_NUM_MASK, 0x4));
++ regmap_update_bits(hdptx->regmap, SB_REG(011d), SB_TG_OSC_CNT_MIN_MASK,
++ FIELD_PREP(SB_TG_OSC_CNT_MIN_MASK, 0x67));
++ regmap_update_bits(hdptx->regmap, SB_REG(011e), SB_TG_OSC_CNT_MAX_MASK,
++ FIELD_PREP(SB_TG_OSC_CNT_MAX_MASK, 0x6a));
++ regmap_update_bits(hdptx->regmap, SB_REG(011f), SB_PWM_AFC_CTRL_MASK,
++ FIELD_PREP(SB_PWM_AFC_CTRL_MASK, 0x5));
++ regmap_update_bits(hdptx->regmap, SB_REG(011f), SB_RCAL_RSTN_MASK,
++ FIELD_PREP(SB_RCAL_RSTN_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, SB_REG(0120), SB_AUX_EN_IN_MASK,
++ FIELD_PREP(SB_AUX_EN_IN_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, SB_REG(0102), OVRD_SB_RXTERM_EN_MASK,
++ FIELD_PREP(OVRD_SB_RXTERM_EN_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, SB_REG(0103), OVRD_SB_RX_RESCAL_DONE_MASK,
++ FIELD_PREP(OVRD_SB_RX_RESCAL_DONE_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, SB_REG(0104), OVRD_SB_EN_MASK,
++ FIELD_PREP(OVRD_SB_EN_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, SB_REG(0104), OVRD_SB_AUX_EN_MASK,
++ FIELD_PREP(OVRD_SB_AUX_EN_MASK, 0x1));
++
++ regmap_update_bits(hdptx->regmap, SB_REG(010f), OVRD_SB_VREG_EN_MASK,
++ FIELD_PREP(OVRD_SB_VREG_EN_MASK, 0x1));
++
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x1));
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_I_BIAS_EN << 16 | FIELD_PREP(HDPTX_I_BIAS_EN, 0x1));
++ usleep_range(20, 25);
++
++ reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
++ usleep_range(20, 25);
++ reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
++ usleep_range(20, 25);
++
++ regmap_update_bits(hdptx->regmap, SB_REG(0103), OVRD_SB_RX_RESCAL_DONE_MASK,
++ FIELD_PREP(OVRD_SB_RX_RESCAL_DONE_MASK, 0x1));
++ usleep_range(100, 110);
++ regmap_update_bits(hdptx->regmap, SB_REG(0104), SB_EN_MASK,
++ FIELD_PREP(SB_EN_MASK, 0x1));
++ usleep_range(100, 110);
++ regmap_update_bits(hdptx->regmap, SB_REG(0102), SB_RXTERM_EN_MASK,
++ FIELD_PREP(SB_RXTERM_EN_MASK, 0x1));
++ usleep_range(20, 25);
++ regmap_update_bits(hdptx->regmap, SB_REG(010f), SB_VREG_EN_MASK,
++ FIELD_PREP(SB_VREG_EN_MASK, 0x1));
++ usleep_range(20, 25);
++ regmap_update_bits(hdptx->regmap, SB_REG(0104), SB_AUX_EN_MASK,
++ FIELD_PREP(SB_AUX_EN_MASK, 0x1));
++ usleep_range(100, 110);
++
++ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS,
++ status, FIELD_GET(HDPTX_O_SB_RDY, status),
++ 50, 1000);
++ if (ret) {
++ dev_err(hdptx->dev, "Failed to get phy sb ready: %d\n", ret);
++ return ret;
++ }
++
++ return 0;
++}
++
+ static int rk_hdptx_phy_power_on(struct phy *phy)
+ {
+ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
+ int bus_width = phy_get_bus_width(hdptx->phy);
+- int ret;
++ enum phy_mode mode = phy_get_mode(phy);
++ int ret, lane;
+
+ /*
+ * FIXME: Temporary workaround to pass pixel_clk_rate
+@@ -935,9 +1466,37 @@ static int rk_hdptx_phy_power_on(struct
+ if (ret)
+ return ret;
+
+- ret = rk_hdptx_ropll_tmds_mode_config(hdptx, rate);
+- if (ret)
+- rk_hdptx_phy_consumer_put(hdptx, true);
++ if (mode == PHY_MODE_DP) {
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x1));
++
++ for (lane = 0; lane < 4; lane++) {
++ regmap_update_bits(hdptx->regmap, LANE_REG(031e) + 0x400 * lane,
++ LN_POLARITY_INV_MASK | LN_LANE_MODE_MASK,
++ FIELD_PREP(LN_POLARITY_INV_MASK, 0) |
++ FIELD_PREP(LN_LANE_MODE_MASK, 1));
++ }
++
++ regmap_update_bits(hdptx->regmap, LNTOP_REG(0200), PROTOCOL_SEL_MASK,
++ FIELD_PREP(PROTOCOL_SEL_MASK, 0x0));
++ regmap_update_bits(hdptx->regmap, LNTOP_REG(0206), DATA_BUS_WIDTH_MASK,
++ FIELD_PREP(DATA_BUS_WIDTH_MASK, 0x1));
++ regmap_update_bits(hdptx->regmap, LNTOP_REG(0206), DATA_BUS_WIDTH_SEL_MASK,
++ FIELD_PREP(DATA_BUS_WIDTH_SEL_MASK, 0x0));
++
++ rk_hdptx_dp_pll_init(hdptx);
++
++ ret = rk_hdptx_dp_aux_init(hdptx);
++ if (ret)
++ rk_hdptx_phy_consumer_put(hdptx, true);
++ } else {
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
++
++ ret = rk_hdptx_ropll_tmds_mode_config(hdptx, rate);
++ if (ret)
++ rk_hdptx_phy_consumer_put(hdptx, true);
++ }
+
+ return ret;
+ }
+@@ -949,9 +1508,308 @@ static int rk_hdptx_phy_power_off(struct
+ return rk_hdptx_phy_consumer_put(hdptx, false);
+ }
+
++static int rk_hdptx_phy_verify_config(struct rk_hdptx_phy *hdptx,
++ struct phy_configure_opts_dp *dp)
++{
++ int i;
++
++ if (dp->set_rate) {
++ switch (dp->link_rate) {
++ case 1620:
++ case 2700:
++ case 5400:
++ break;
++ default:
++ return -EINVAL;
++ }
++ }
++
++ if (dp->set_lanes) {
++ switch (dp->lanes) {
++ case 1:
++ case 2:
++ case 4:
++ break;
++ default:
++ return -EINVAL;
++ }
++ }
++
++ if (dp->set_voltages) {
++ for (i = 0; i < hdptx->lanes; i++) {
++ if (dp->voltage[i] > 3 || dp->pre[i] > 3)
++ return -EINVAL;
++
++ if (dp->voltage[i] + dp->pre[i] > 3)
++ return -EINVAL;
++ }
++ }
++
++ return 0;
++}
++
++static int rk_hdptx_phy_set_rate(struct rk_hdptx_phy *hdptx,
++ struct phy_configure_opts_dp *dp)
++{
++ u32 bw, status;
++ int ret;
++
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x0));
++
++ switch (dp->link_rate) {
++ case 1620:
++ bw = DP_BW_RBR;
++ break;
++ case 2700:
++ bw = DP_BW_HBR;
++ break;
++ case 5400:
++ bw = DP_BW_HBR2;
++ break;
++ default:
++ return -EINVAL;
++ }
++ hdptx->link_rate = dp->link_rate;
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0008), OVRD_LCPLL_EN_MASK | LCPLL_EN_MASK,
++ FIELD_PREP(OVRD_LCPLL_EN_MASK, 0x1) |
++ FIELD_PREP(LCPLL_EN_MASK, 0x0));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(003d), OVRD_ROPLL_EN_MASK | ROPLL_EN_MASK,
++ FIELD_PREP(OVRD_ROPLL_EN_MASK, 0x1) |
++ FIELD_PREP(ROPLL_EN_MASK, 0x1));
++
++ if (dp->ssc) {
++ regmap_update_bits(hdptx->regmap, CMN_REG(0074),
++ OVRD_ROPLL_SSC_EN_MASK | ROPLL_SSC_EN_MASK,
++ FIELD_PREP(OVRD_ROPLL_SSC_EN_MASK, 0x1) |
++ FIELD_PREP(ROPLL_SSC_EN_MASK, 0x1));
++ regmap_write(hdptx->regmap, CMN_REG(0075),
++ FIELD_PREP(ANA_ROPLL_SSC_FM_DEVIATION_MASK, 0xc));
++ regmap_update_bits(hdptx->regmap, CMN_REG(0076),
++ ANA_ROPLL_SSC_FM_FREQ_MASK,
++ FIELD_PREP(ANA_ROPLL_SSC_FM_FREQ_MASK, 0x1f));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0099), SSC_EN_MASK,
++ FIELD_PREP(SSC_EN_MASK, 0x2));
++ } else {
++ regmap_update_bits(hdptx->regmap, CMN_REG(0074),
++ OVRD_ROPLL_SSC_EN_MASK | ROPLL_SSC_EN_MASK,
++ FIELD_PREP(OVRD_ROPLL_SSC_EN_MASK, 0x1) |
++ FIELD_PREP(ROPLL_SSC_EN_MASK, 0x0));
++ regmap_write(hdptx->regmap, CMN_REG(0075),
++ FIELD_PREP(ANA_ROPLL_SSC_FM_DEVIATION_MASK, 0x20));
++ regmap_update_bits(hdptx->regmap, CMN_REG(0076),
++ ANA_ROPLL_SSC_FM_FREQ_MASK,
++ FIELD_PREP(ANA_ROPLL_SSC_FM_FREQ_MASK, 0xc));
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0099), SSC_EN_MASK,
++ FIELD_PREP(SSC_EN_MASK, 0x0));
++ }
++
++ regmap_update_bits(hdptx->regmap, CMN_REG(0095), DP_TX_LINK_BW_MASK,
++ FIELD_PREP(DP_TX_LINK_BW_MASK, bw));
++
++ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
++ HDPTX_I_PLL_EN << 16 | FIELD_PREP(HDPTX_I_PLL_EN, 0x1));
++
++ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS,
++ status, FIELD_GET(HDPTX_O_PLL_LOCK_DONE, status),
++ 50, 1000);
++ if (ret) {
++ dev_err(hdptx->dev, "Failed to get phy pll lock: %d\n", ret);
++ return ret;
++ }
++
++ return 0;
++}
++
++static int rk_hdptx_phy_set_lanes(struct rk_hdptx_phy *hdptx,
++ struct phy_configure_opts_dp *dp)
++{
++ hdptx->lanes = dp->lanes;
++
++ regmap_update_bits(hdptx->regmap, LNTOP_REG(0207), LANE_EN_MASK,
++ FIELD_PREP(LANE_EN_MASK, GENMASK(hdptx->lanes - 1, 0)));
++
++ return 0;
++}
++
++static void rk_hdptx_phy_set_voltage(struct rk_hdptx_phy *hdptx,
++ struct phy_configure_opts_dp *dp,
++ u8 lane)
++{
++ const struct tx_drv_ctrl *ctrl;
++ u32 offset = lane * 0x400;
++
++ switch (hdptx->link_rate) {
++ case 1620:
++ ctrl = &tx_drv_ctrl_rbr[dp->voltage[lane]][dp->pre[lane]];
++ regmap_update_bits(hdptx->regmap, LANE_REG(030a) + offset,
++ LN_TX_JEQ_EVEN_CTRL_RBR_MASK,
++ FIELD_PREP(LN_TX_JEQ_EVEN_CTRL_RBR_MASK,
++ ctrl->tx_jeq_even_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(030c) + offset,
++ LN_TX_JEQ_ODD_CTRL_RBR_MASK,
++ FIELD_PREP(LN_TX_JEQ_ODD_CTRL_RBR_MASK,
++ ctrl->tx_jeq_odd_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0311) + offset,
++ LN_TX_SER_40BIT_EN_RBR_MASK,
++ FIELD_PREP(LN_TX_SER_40BIT_EN_RBR_MASK, 0x1));
++ break;
++ case 2700:
++ ctrl = &tx_drv_ctrl_hbr[dp->voltage[lane]][dp->pre[lane]];
++ regmap_update_bits(hdptx->regmap, LANE_REG(030b) + offset,
++ LN_TX_JEQ_EVEN_CTRL_HBR_MASK,
++ FIELD_PREP(LN_TX_JEQ_EVEN_CTRL_HBR_MASK,
++ ctrl->tx_jeq_even_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(030d) + offset,
++ LN_TX_JEQ_ODD_CTRL_HBR_MASK,
++ FIELD_PREP(LN_TX_JEQ_ODD_CTRL_HBR_MASK,
++ ctrl->tx_jeq_odd_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0311) + offset,
++ LN_TX_SER_40BIT_EN_HBR_MASK,
++ FIELD_PREP(LN_TX_SER_40BIT_EN_HBR_MASK, 0x1));
++ break;
++ case 5400:
++ default:
++ ctrl = &tx_drv_ctrl_hbr2[dp->voltage[lane]][dp->pre[lane]];
++ regmap_update_bits(hdptx->regmap, LANE_REG(030b) + offset,
++ LN_TX_JEQ_EVEN_CTRL_HBR2_MASK,
++ FIELD_PREP(LN_TX_JEQ_EVEN_CTRL_HBR2_MASK,
++ ctrl->tx_jeq_even_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(030d) + offset,
++ LN_TX_JEQ_ODD_CTRL_HBR2_MASK,
++ FIELD_PREP(LN_TX_JEQ_ODD_CTRL_HBR2_MASK,
++ ctrl->tx_jeq_odd_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0311) + offset,
++ LN_TX_SER_40BIT_EN_HBR2_MASK,
++ FIELD_PREP(LN_TX_SER_40BIT_EN_HBR2_MASK, 0x1));
++ break;
++ }
++
++ regmap_update_bits(hdptx->regmap, LANE_REG(0303) + offset,
++ OVRD_LN_TX_DRV_LVL_CTRL_MASK | LN_TX_DRV_LVL_CTRL_MASK,
++ FIELD_PREP(OVRD_LN_TX_DRV_LVL_CTRL_MASK, 0x1) |
++ FIELD_PREP(LN_TX_DRV_LVL_CTRL_MASK,
++ ctrl->tx_drv_lvl_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0304) + offset,
++ OVRD_LN_TX_DRV_POST_LVL_CTRL_MASK |
++ LN_TX_DRV_POST_LVL_CTRL_MASK,
++ FIELD_PREP(OVRD_LN_TX_DRV_POST_LVL_CTRL_MASK, 0x1) |
++ FIELD_PREP(LN_TX_DRV_POST_LVL_CTRL_MASK,
++ ctrl->tx_drv_post_lvl_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0305) + offset,
++ OVRD_LN_TX_DRV_PRE_LVL_CTRL_MASK |
++ LN_TX_DRV_PRE_LVL_CTRL_MASK,
++ FIELD_PREP(OVRD_LN_TX_DRV_PRE_LVL_CTRL_MASK, 0x1) |
++ FIELD_PREP(LN_TX_DRV_PRE_LVL_CTRL_MASK,
++ ctrl->tx_drv_pre_lvl_ctrl));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0306) + offset,
++ LN_ANA_TX_DRV_IDRV_IDN_CTRL_MASK |
++ LN_ANA_TX_DRV_IDRV_IUP_CTRL_MASK |
++ LN_ANA_TX_DRV_ACCDRV_EN_MASK,
++ FIELD_PREP(LN_ANA_TX_DRV_IDRV_IDN_CTRL_MASK,
++ ctrl->ana_tx_drv_idrv_idn_ctrl) |
++ FIELD_PREP(LN_ANA_TX_DRV_IDRV_IUP_CTRL_MASK,
++ ctrl->ana_tx_drv_idrv_iup_ctrl) |
++ FIELD_PREP(LN_ANA_TX_DRV_ACCDRV_EN_MASK,
++ ctrl->ana_tx_drv_accdrv_en));
++ regmap_update_bits(hdptx->regmap, LANE_REG(0307) + offset,
++ LN_ANA_TX_DRV_ACCDRV_POL_SEL_MASK |
++ LN_ANA_TX_DRV_ACCDRV_CTRL_MASK,
++ FIELD_PREP(LN_ANA_TX_DRV_ACCDRV_POL_SEL_MASK, 0x1) |
++ FIELD_PREP(LN_ANA_TX_DRV_ACCDRV_CTRL_MASK,
++ ctrl->ana_tx_drv_accdrv_ctrl));
++
++ regmap_update_bits(hdptx->regmap, LANE_REG(030a) + offset,
++ LN_ANA_TX_JEQ_EN_MASK,
++ FIELD_PREP(LN_ANA_TX_JEQ_EN_MASK, ctrl->ana_tx_jeq_en));
++
++ regmap_update_bits(hdptx->regmap, LANE_REG(0310) + offset,
++ LN_ANA_TX_SYNC_LOSS_DET_MODE_MASK,
++ FIELD_PREP(LN_ANA_TX_SYNC_LOSS_DET_MODE_MASK, 0x3));
++
++ regmap_update_bits(hdptx->regmap, LANE_REG(0316) + offset,
++ LN_ANA_TX_SER_VREG_GAIN_CTRL_MASK,
++ FIELD_PREP(LN_ANA_TX_SER_VREG_GAIN_CTRL_MASK, 0x2));
++
++ regmap_update_bits(hdptx->regmap, LANE_REG(031b) + offset,
++ LN_ANA_TX_RESERVED_MASK,
++ FIELD_PREP(LN_ANA_TX_RESERVED_MASK, 0x1));
++}
++
++static int rk_hdptx_phy_set_voltages(struct rk_hdptx_phy *hdptx,
++ struct phy_configure_opts_dp *dp)
++{
++ u8 lane;
++ u32 status;
++ int ret;
++
++ for (lane = 0; lane < hdptx->lanes; lane++)
++ rk_hdptx_phy_set_voltage(hdptx, dp, lane);
++
++ reset_control_deassert(hdptx->rsts[RST_LANE].rstc);
++
++ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS,
++ status, FIELD_GET(HDPTX_O_PHY_RDY, status),
++ 50, 5000);
++ if (ret) {
++ dev_err(hdptx->dev, "Failed to get phy ready: %d\n", ret);
++ return ret;
++ }
++
++ return 0;
++}
++
++static int rk_hdptx_phy_configure(struct phy *phy, union phy_configure_opts *opts)
++{
++ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
++ enum phy_mode mode = phy_get_mode(phy);
++ int ret;
++
++ if (mode != PHY_MODE_DP)
++ return 0;
++
++ ret = rk_hdptx_phy_verify_config(hdptx, &opts->dp);
++ if (ret) {
++ dev_err(hdptx->dev, "invalid params for phy configure\n");
++ return ret;
++ }
++
++ if (opts->dp.set_rate) {
++ ret = rk_hdptx_phy_set_rate(hdptx, &opts->dp);
++ if (ret) {
++ dev_err(hdptx->dev, "failed to set rate: %d\n", ret);
++ return ret;
++ }
++ }
++
++ if (opts->dp.set_lanes) {
++ ret = rk_hdptx_phy_set_lanes(hdptx, &opts->dp);
++ if (ret) {
++ dev_err(hdptx->dev, "failed to set lanes: %d\n", ret);
++ return ret;
++ }
++ }
++
++ if (opts->dp.set_voltages) {
++ ret = rk_hdptx_phy_set_voltages(hdptx, &opts->dp);
++ if (ret) {
++ dev_err(hdptx->dev, "failed to set voltages: %d\n",
++ ret);
++ return ret;
++ }
++ }
++
++ return 0;
++}
++
+ static const struct phy_ops rk_hdptx_phy_ops = {
+ .power_on = rk_hdptx_phy_power_on,
+ .power_off = rk_hdptx_phy_power_off,
++ .configure = rk_hdptx_phy_configure,
+ .owner = THIS_MODULE,
+ };
+
+@@ -1193,5 +2051,6 @@ module_platform_driver(rk_hdptx_phy_driv
+
+ MODULE_AUTHOR("Algea Cao ");
+ MODULE_AUTHOR("Cristian Ciocaltea ");
++MODULE_AUTHOR("Damon Ding ");
+ MODULE_DESCRIPTION("Samsung HDMI/eDP Transmitter Combo PHY Driver");
+ MODULE_LICENSE("GPL");
diff --git a/lede/target/linux/rockchip/patches-6.12/040-06-v6.15-phy-phy-rockchip-samsung-hdptx-Add-support-for-RK3576.patch b/lede/target/linux/rockchip/patches-6.12/040-06-v6.15-phy-phy-rockchip-samsung-hdptx-Add-support-for-RK3576.patch
new file mode 100644
index 0000000000..f8590c0cbb
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-06-v6.15-phy-phy-rockchip-samsung-hdptx-Add-support-for-RK3576.patch
@@ -0,0 +1,49 @@
+From 4a8463ae8d871ccd491d48a371a6789eb7378243 Mon Sep 17 00:00:00 2001
+From: Nicolas Frattaroli
+Date: Thu, 6 Mar 2025 20:29:23 +0100
+Subject: [PATCH] phy: phy-rockchip-samsung-hdptx: Add support for RK3576
+
+Despite the compatible already being listed in the bindings, the PHY
+driver never gained explicit support for it. This is especially a
+problem because the explicitly listed PHY addresses need to be specified
+for each SoC.
+
+To solve this, add the compatible, and a PHY config, with the address
+gleaned from rk3576.dtsi.
+
+Signed-off-by: Nicolas Frattaroli
+Reviewed-by: Cristian Ciocaltea
+Reviewed-by: Heiko Stuebner
+Link: https://lore.kernel.org/r/20250306-rk3576-hdptx-phy-v1-1-288cc4b0611a@collabora.com
+Signed-off-by: Vinod Koul
+---
+ drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -2022,6 +2022,13 @@ static const struct dev_pm_ops rk_hdptx_
+ rk_hdptx_phy_runtime_resume, NULL)
+ };
+
++static const struct rk_hdptx_phy_cfg rk3576_hdptx_phy_cfgs = {
++ .num_phys = 1,
++ .phy_ids = {
++ 0x2b000000,
++ },
++};
++
+ static const struct rk_hdptx_phy_cfg rk3588_hdptx_phy_cfgs = {
+ .num_phys = 2,
+ .phy_ids = {
+@@ -2032,6 +2039,10 @@ static const struct rk_hdptx_phy_cfg rk3
+
+ static const struct of_device_id rk_hdptx_phy_of_match[] = {
+ {
++ .compatible = "rockchip,rk3576-hdptx-phy",
++ .data = &rk3576_hdptx_phy_cfgs
++ },
++ {
+ .compatible = "rockchip,rk3588-hdptx-phy",
+ .data = &rk3588_hdptx_phy_cfgs
+ },
diff --git a/lede/target/linux/rockchip/patches-6.12/040-07-v6.16-phy-Add-HDMI-configuration-options.patch b/lede/target/linux/rockchip/patches-6.12/040-07-v6.16-phy-Add-HDMI-configuration-options.patch
new file mode 100644
index 0000000000..47ce0c13db
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-07-v6.16-phy-Add-HDMI-configuration-options.patch
@@ -0,0 +1,83 @@
+From 10ed34d6eaaf86e301a8f2dd190d26dfbc9799bd Mon Sep 17 00:00:00 2001
+From: Sandor Yu
+Date: Tue, 18 Mar 2025 14:35:35 +0200
+Subject: [PATCH] phy: Add HDMI configuration options
+
+Allow HDMI PHYs to be configured through the generic
+functions through a custom structure added to the generic union.
+
+The parameters added here are based on HDMI PHY
+implementation practices. The current set of parameters
+should cover the potential users.
+
+Signed-off-by: Sandor Yu
+Reviewed-by: Dmitry Baryshkov
+Reviewed-by: Maxime Ripard
+Acked-by: Vinod Koul
+Link: https://lore.kernel.org/r/d1cff6c03ec3732d2244022029245ab2d954d997.1734340233.git.Sandor.yu@nxp.com
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-1-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ include/linux/phy/phy-hdmi.h | 19 +++++++++++++++++++
+ include/linux/phy/phy.h | 7 ++++++-
+ 2 files changed, 25 insertions(+), 1 deletion(-)
+ create mode 100644 include/linux/phy/phy-hdmi.h
+
+--- /dev/null
++++ b/include/linux/phy/phy-hdmi.h
+@@ -0,0 +1,19 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * Copyright 2022,2024 NXP
++ */
++
++#ifndef __PHY_HDMI_H_
++#define __PHY_HDMI_H_
++
++/**
++ * struct phy_configure_opts_hdmi - HDMI configuration set
++ * @tmds_char_rate: HDMI TMDS Character Rate in Hertz.
++ *
++ * This structure is used to represent the configuration state of a HDMI phy.
++ */
++struct phy_configure_opts_hdmi {
++ unsigned long long tmds_char_rate;
++};
++
++#endif /* __PHY_HDMI_H_ */
+--- a/include/linux/phy/phy.h
++++ b/include/linux/phy/phy.h
+@@ -17,6 +17,7 @@
+ #include
+
+ #include
++#include
+ #include
+ #include
+
+@@ -42,7 +43,8 @@ enum phy_mode {
+ PHY_MODE_MIPI_DPHY,
+ PHY_MODE_SATA,
+ PHY_MODE_LVDS,
+- PHY_MODE_DP
++ PHY_MODE_DP,
++ PHY_MODE_HDMI,
+ };
+
+ enum phy_media {
+@@ -60,11 +62,14 @@ enum phy_media {
+ * the DisplayPort protocol.
+ * @lvds: Configuration set applicable for phys supporting
+ * the LVDS phy mode.
++ * @hdmi: Configuration set applicable for phys supporting
++ * the HDMI phy mode.
+ */
+ union phy_configure_opts {
+ struct phy_configure_opts_mipi_dphy mipi_dphy;
+ struct phy_configure_opts_dp dp;
+ struct phy_configure_opts_lvds lvds;
++ struct phy_configure_opts_hdmi hdmi;
+ };
+
+ /**
diff --git a/lede/target/linux/rockchip/patches-6.12/040-08-v6.16-phy-hdmi-Add-color-depth-configuration.patch b/lede/target/linux/rockchip/patches-6.12/040-08-v6.16-phy-hdmi-Add-color-depth-configuration.patch
new file mode 100644
index 0000000000..cc00c6c034
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-08-v6.16-phy-hdmi-Add-color-depth-configuration.patch
@@ -0,0 +1,33 @@
+From 3bb9286f4ece6acbc1fbaa9f192a82645d30efbf Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:36 +0200
+Subject: [PATCH] phy: hdmi: Add color depth configuration
+
+Extend the HDMI configuration options to allow managing bits per color
+channel. This is required by some PHY drivers such as
+rockchip-samsung-hdptx.
+
+Reviewed-by: Dmitry Baryshkov
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-2-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ include/linux/phy/phy-hdmi.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/include/linux/phy/phy-hdmi.h
++++ b/include/linux/phy/phy-hdmi.h
+@@ -9,11 +9,13 @@
+ /**
+ * struct phy_configure_opts_hdmi - HDMI configuration set
+ * @tmds_char_rate: HDMI TMDS Character Rate in Hertz.
++ * @bpc: Bits per color channel.
+ *
+ * This structure is used to represent the configuration state of a HDMI phy.
+ */
+ struct phy_configure_opts_hdmi {
+ unsigned long long tmds_char_rate;
++ unsigned int bpc;
+ };
+
+ #endif /* __PHY_HDMI_H_ */
diff --git a/lede/target/linux/rockchip/patches-6.12/040-09-v6.16-phy-rockchip-samsung-hdptx-Drop-unused-struct-lcpll_confi.patch b/lede/target/linux/rockchip/patches-6.12/040-09-v6.16-phy-rockchip-samsung-hdptx-Drop-unused-struct-lcpll_confi.patch
new file mode 100644
index 0000000000..8d6da312fc
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-09-v6.16-phy-rockchip-samsung-hdptx-Drop-unused-struct-lcpll_confi.patch
@@ -0,0 +1,56 @@
+From 6218c3fd6702a5bc4ab323fed25714cde127684c Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:39 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Drop unused struct lcpll_config
+
+This is just a leftover from downstream support for HDMI 2.1.
+Remove the unused struct for now.
+
+Reviewed-by: Dmitry Baryshkov
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-5-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 31 -------------------
+ 1 file changed, 31 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -329,37 +329,6 @@ enum dp_link_rate {
+ DP_BW_HBR2,
+ };
+
+-struct lcpll_config {
+- u32 bit_rate;
+- u8 lcvco_mode_en;
+- u8 pi_en;
+- u8 clk_en_100m;
+- u8 pms_mdiv;
+- u8 pms_mdiv_afc;
+- u8 pms_pdiv;
+- u8 pms_refdiv;
+- u8 pms_sdiv;
+- u8 pi_cdiv_rstn;
+- u8 pi_cdiv_sel;
+- u8 sdm_en;
+- u8 sdm_rstn;
+- u8 sdc_frac_en;
+- u8 sdc_rstn;
+- u8 sdm_deno;
+- u8 sdm_num_sign;
+- u8 sdm_num;
+- u8 sdc_n;
+- u8 sdc_n2;
+- u8 sdc_num;
+- u8 sdc_deno;
+- u8 sdc_ndiv_rstn;
+- u8 ssc_en;
+- u8 ssc_fm_dev;
+- u8 ssc_fm_freq;
+- u8 ssc_clk_div_sel;
+- u8 cd_tx_ser_rate_sel;
+-};
+-
+ struct ropll_config {
+ u32 bit_rate;
+ u8 pms_mdiv;
diff --git a/lede/target/linux/rockchip/patches-6.12/040-10-v6.16-phy-rockchip-samsung-hdptx-Drop-unused-phy_cfg-driver-dat.patch b/lede/target/linux/rockchip/patches-6.12/040-10-v6.16-phy-rockchip-samsung-hdptx-Drop-unused-phy_cfg-driver-dat.patch
new file mode 100644
index 0000000000..bbb38090a4
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-10-v6.16-phy-rockchip-samsung-hdptx-Drop-unused-phy_cfg-driver-dat.patch
@@ -0,0 +1,26 @@
+From bcd61d182618c6a77d0841fcdc3333e125725360 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:40 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Drop unused phy_cfg driver data
+
+There is no usage of phy_cfg in the upstream driver data, nor in the
+downstream one, hence remove it.
+
+Reviewed-by: Dmitry Baryshkov
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-6-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -397,7 +397,6 @@ struct rk_hdptx_phy {
+ int phy_id;
+
+ struct phy *phy;
+- struct phy_config *phy_cfg;
+ struct clk_bulk_data *clks;
+ int nr_clks;
+ struct reset_control_bulk_data rsts[RST_MAX];
diff --git a/lede/target/linux/rockchip/patches-6.12/040-11-v6.16-phy-rockchip-samsung-hdptx-Drop-superfluous-cfgs-driver.patch b/lede/target/linux/rockchip/patches-6.12/040-11-v6.16-phy-rockchip-samsung-hdptx-Drop-superfluous-cfgs-driver.patch
new file mode 100644
index 0000000000..ac86e83be7
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-11-v6.16-phy-rockchip-samsung-hdptx-Drop-superfluous-cfgs-driver.patch
@@ -0,0 +1,61 @@
+From bacf2fe750dab6bc7ed50556aaadd3ab107fc643 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:41 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Drop superfluous cfgs driver
+ data
+
+The ->cfgs member has been introduced via commit f08d1c085638 ("phy:
+phy-rockchip-samsung-hdptx: Don't use dt aliases to determine phy-id"),
+but it is only used during probe() in order to setup ->phy_id.
+
+Use a probe() local variable to store device match data and remove the
+now unnecessary member from struct rk_hdptx_phy.
+
+Reviewed-by: Dmitry Baryshkov
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-7-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 12 +++++-------
+ 1 file changed, 5 insertions(+), 7 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -392,10 +392,7 @@ struct rk_hdptx_phy {
+ struct regmap *regmap;
+ struct regmap *grf;
+
+- /* PHY const config */
+- const struct rk_hdptx_phy_cfg *cfgs;
+ int phy_id;
+-
+ struct phy *phy;
+ struct clk_bulk_data *clks;
+ int nr_clks;
+@@ -1896,6 +1893,7 @@ static int rk_hdptx_phy_runtime_resume(s
+
+ static int rk_hdptx_phy_probe(struct platform_device *pdev)
+ {
++ const struct rk_hdptx_phy_cfg *cfgs;
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct rk_hdptx_phy *hdptx;
+@@ -1914,14 +1912,14 @@ static int rk_hdptx_phy_probe(struct pla
+ return dev_err_probe(dev, PTR_ERR(regs),
+ "Failed to ioremap resource\n");
+
+- hdptx->cfgs = device_get_match_data(dev);
+- if (!hdptx->cfgs)
++ cfgs = device_get_match_data(dev);
++ if (!cfgs)
+ return dev_err_probe(dev, -EINVAL, "missing match data\n");
+
+ /* find the phy-id from the io address */
+ hdptx->phy_id = -ENODEV;
+- for (id = 0; id < hdptx->cfgs->num_phys; id++) {
+- if (res->start == hdptx->cfgs->phy_ids[id]) {
++ for (id = 0; id < cfgs->num_phys; id++) {
++ if (res->start == cfgs->phy_ids[id]) {
+ hdptx->phy_id = id;
+ break;
+ }
diff --git a/lede/target/linux/rockchip/patches-6.12/040-12-v6.16-phy-rockchip-samsung-hdptx-Avoid-Hz-hHz-unit-conversion.patch b/lede/target/linux/rockchip/patches-6.12/040-12-v6.16-phy-rockchip-samsung-hdptx-Avoid-Hz-hHz-unit-conversion.patch
new file mode 100644
index 0000000000..9d90a332fb
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-12-v6.16-phy-rockchip-samsung-hdptx-Avoid-Hz-hHz-unit-conversion.patch
@@ -0,0 +1,240 @@
+From 0edf9d2bb9b4ba7566dfdc7605883e04575129d9 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:42 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Avoid Hz<->hHz unit conversion
+ overhead
+
+The ropll_tmds_cfg table used to identify the configuration params for
+the supported rates expects the search key, i.e. bit_rate member of
+struct ropll_config, to be provided in hHz rather than Hz (1 hHz = 100
+Hz). This requires multiple conversions between these units being
+performed at runtime.
+
+Improve implementation clarity and efficiency by consistently using the
+Hz unit throughout driver's internal data structures and functions.
+Also rename the rather misleading struct member.
+
+Signed-off-by: Cristian Ciocaltea
+Reviewed-by: Dmitry Baryshkov
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-8-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 79 +++++++++----------
+ 1 file changed, 39 insertions(+), 40 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -330,7 +330,7 @@ enum dp_link_rate {
+ };
+
+ struct ropll_config {
+- u32 bit_rate;
++ unsigned long long rate;
+ u8 pms_mdiv;
+ u8 pms_mdiv_afc;
+ u8 pms_pdiv;
+@@ -410,47 +410,47 @@ struct rk_hdptx_phy {
+ };
+
+ static const struct ropll_config ropll_tmds_cfg[] = {
+- { 5940000, 124, 124, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
++ { 594000000ULL, 124, 124, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 3712500, 155, 155, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
++ { 371250000ULL, 155, 155, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 2970000, 124, 124, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
++ { 297000000ULL, 124, 124, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 1620000, 135, 135, 1, 1, 3, 1, 1, 0, 1, 1, 1, 1, 4, 0, 3, 5, 5, 0x10,
++ { 162000000ULL, 135, 135, 1, 1, 3, 1, 1, 0, 1, 1, 1, 1, 4, 0, 3, 5, 5, 0x10,
+ 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 1856250, 155, 155, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
++ { 185625000ULL, 155, 155, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 1540000, 193, 193, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 193, 1, 32, 2, 1,
++ { 154000000ULL, 193, 193, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 193, 1, 32, 2, 1,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 1485000, 0x7b, 0x7b, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 4, 0, 3, 5, 5,
++ { 148500000ULL, 0x7b, 0x7b, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 4, 0, 3, 5, 5,
+ 0x10, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 1462500, 122, 122, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 244, 1, 16, 2, 1, 1,
++ { 146250000ULL, 122, 122, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 244, 1, 16, 2, 1, 1,
+ 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 1190000, 149, 149, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 149, 1, 16, 2, 1, 1,
++ { 119000000ULL, 149, 149, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 149, 1, 16, 2, 1, 1,
+ 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 1065000, 89, 89, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 89, 1, 16, 1, 0, 1,
++ { 106500000ULL, 89, 89, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 89, 1, 16, 1, 0, 1,
+ 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 1080000, 135, 135, 1, 1, 5, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0,
++ { 108000000ULL, 135, 135, 1, 1, 5, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0,
+ 0x14, 0x18, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 855000, 214, 214, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 214, 1, 16, 2, 1,
++ { 85500000ULL, 214, 214, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 214, 1, 16, 2, 1,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 835000, 105, 105, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 42, 1, 16, 1, 0,
++ { 83500000ULL, 105, 105, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 42, 1, 16, 1, 0,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 928125, 155, 155, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
++ { 92812500ULL, 155, 155, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 742500, 124, 124, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
++ { 74250000ULL, 124, 124, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 62, 1, 16, 5, 0,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 650000, 162, 162, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 54, 0, 16, 4, 1,
++ { 65000000ULL, 162, 162, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 54, 0, 16, 4, 1,
+ 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 502500, 84, 84, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 11, 1, 4, 5,
++ { 50250000ULL, 84, 84, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 11, 1, 4, 5,
+ 4, 11, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 337500, 0x70, 0x70, 1, 1, 0xf, 1, 1, 1, 1, 1, 1, 1, 0x2, 0, 0x01, 5,
++ { 33750000ULL, 0x70, 0x70, 1, 1, 0xf, 1, 1, 1, 1, 1, 1, 1, 0x2, 0, 0x01, 5,
+ 1, 1, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 400000, 100, 100, 1, 1, 11, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0,
++ { 40000000ULL, 100, 100, 1, 1, 11, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0,
+ 0x14, 0x18, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 270000, 0x5a, 0x5a, 1, 1, 0xf, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0,
++ { 27000000ULL, 0x5a, 0x5a, 1, 1, 0xf, 1, 1, 0, 1, 0, 1, 1, 0x9, 0, 0x05, 0,
+ 0x14, 0x18, 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+- { 251750, 84, 84, 1, 1, 0xf, 1, 1, 1, 1, 1, 1, 1, 168, 1, 16, 4, 1, 1,
++ { 25175000ULL, 84, 84, 1, 1, 0xf, 1, 1, 1, 1, 1, 1, 1, 168, 1, 16, 4, 1, 1,
+ 1, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+ };
+
+@@ -896,10 +896,10 @@ static void rk_hdptx_phy_disable(struct
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+ }
+
+-static bool rk_hdptx_phy_clk_pll_calc(unsigned int data_rate,
++static bool rk_hdptx_phy_clk_pll_calc(unsigned long long rate,
+ struct ropll_config *cfg)
+ {
+- const unsigned int fout = data_rate / 2, fref = 24000;
++ const unsigned int fout = div_u64(rate, 200), fref = 24000;
+ unsigned long k = 0, lc, k_sub, lc_sub;
+ unsigned int fvco, sdc;
+ u32 mdiv, sdiv, n = 8;
+@@ -969,14 +969,14 @@ static bool rk_hdptx_phy_clk_pll_calc(un
+ }
+
+ static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx,
+- unsigned int rate)
++ unsigned long long rate)
+ {
+ const struct ropll_config *cfg = NULL;
+ struct ropll_config rc = {0};
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
+- if (rate == ropll_tmds_cfg[i].bit_rate) {
++ if (rate == ropll_tmds_cfg[i].rate) {
+ cfg = &ropll_tmds_cfg[i];
+ break;
+ }
+@@ -990,8 +990,8 @@ static int rk_hdptx_ropll_tmds_cmn_confi
+ }
+ }
+
+- dev_dbg(hdptx->dev, "mdiv=%u, sdiv=%u, sdm_en=%u, k_sign=%u, k=%u, lc=%u\n",
+- cfg->pms_mdiv, cfg->pms_sdiv + 1, cfg->sdm_en,
++ dev_dbg(hdptx->dev, "%s rate=%llu mdiv=%u sdiv=%u sdm_en=%u k_sign=%u k=%u lc=%u\n",
++ __func__, rate, cfg->pms_mdiv, cfg->pms_sdiv + 1, cfg->sdm_en,
+ cfg->sdm_num_sign, cfg->sdm_num, cfg->sdm_deno);
+
+ rk_hdptx_pre_power_up(hdptx);
+@@ -1030,19 +1030,19 @@ static int rk_hdptx_ropll_tmds_cmn_confi
+
+ ret = rk_hdptx_post_enable_pll(hdptx);
+ if (!ret)
+- hdptx->rate = rate * 100;
++ hdptx->rate = rate;
+
+ return ret;
+ }
+
+ static int rk_hdptx_ropll_tmds_mode_config(struct rk_hdptx_phy *hdptx,
+- unsigned int rate)
++ unsigned long long rate)
+ {
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_sb_init_seq);
+
+ regmap_write(hdptx->regmap, LNTOP_REG(0200), 0x06);
+
+- if (rate > HDMI14_MAX_RATE / 100) {
++ if (rate > HDMI14_MAX_RATE) {
+ /* For 1/40 bitrate clk */
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_tmds_lntop_highbr_seq);
+ } else {
+@@ -1095,7 +1095,7 @@ static void rk_hdptx_dp_reset(struct rk_
+ }
+
+ static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx,
+- unsigned int rate)
++ unsigned long long rate)
+ {
+ enum phy_mode mode = phy_get_mode(hdptx->phy);
+ u32 status;
+@@ -1413,8 +1413,8 @@ static int rk_hdptx_dp_aux_init(struct r
+ static int rk_hdptx_phy_power_on(struct phy *phy)
+ {
+ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
+- int bus_width = phy_get_bus_width(hdptx->phy);
+ enum phy_mode mode = phy_get_mode(phy);
++ unsigned long long rate;
+ int ret, lane;
+
+ /*
+@@ -1422,10 +1422,10 @@ static int rk_hdptx_phy_power_on(struct
+ * from the HDMI bridge driver until phy_configure_opts_hdmi
+ * becomes available in the PHY API.
+ */
+- unsigned int rate = bus_width & 0xfffffff;
++ rate = phy_get_bus_width(hdptx->phy) & 0xfffffff;
++ rate *= 100;
+
+- dev_dbg(hdptx->dev, "%s bus_width=%x rate=%u\n",
+- __func__, bus_width, rate);
++ dev_dbg(hdptx->dev, "%s rate=%llu\n", __func__, rate);
+
+ ret = rk_hdptx_phy_consumer_get(hdptx, rate);
+ if (ret)
+@@ -1787,7 +1787,7 @@ static int rk_hdptx_phy_clk_prepare(stru
+ {
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+- return rk_hdptx_phy_consumer_get(hdptx, hdptx->rate / 100);
++ return rk_hdptx_phy_consumer_get(hdptx, hdptx->rate);
+ }
+
+ static void rk_hdptx_phy_clk_unprepare(struct clk_hw *hw)
+@@ -1808,18 +1808,17 @@ static unsigned long rk_hdptx_phy_clk_re
+ static long rk_hdptx_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+ {
+- u32 bit_rate = rate / 100;
+ int i;
+
+ if (rate > HDMI20_MAX_RATE)
+ return rate;
+
+ for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
+- if (bit_rate == ropll_tmds_cfg[i].bit_rate)
++ if (rate == ropll_tmds_cfg[i].rate)
+ break;
+
+ if (i == ARRAY_SIZE(ropll_tmds_cfg) &&
+- !rk_hdptx_phy_clk_pll_calc(bit_rate, NULL))
++ !rk_hdptx_phy_clk_pll_calc(rate, NULL))
+ return -EINVAL;
+
+ return rate;
+@@ -1830,7 +1829,7 @@ static int rk_hdptx_phy_clk_set_rate(str
+ {
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+- return rk_hdptx_ropll_tmds_cmn_config(hdptx, rate / 100);
++ return rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
+ }
+
+ static const struct clk_ops hdptx_phy_clk_ops = {
diff --git a/lede/target/linux/rockchip/patches-6.12/040-13-v6.16-phy-rockchip-samsung-hdptx-Setup-TMDS-char-rate-via.patch b/lede/target/linux/rockchip/patches-6.12/040-13-v6.16-phy-rockchip-samsung-hdptx-Setup-TMDS-char-rate-via.patch
new file mode 100644
index 0000000000..2b22600e85
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-13-v6.16-phy-rockchip-samsung-hdptx-Setup-TMDS-char-rate-via.patch
@@ -0,0 +1,131 @@
+From c871a311edf0ebb1b934946a84a6c532cac0c035 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:43 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Setup TMDS char rate via
+ phy_configure_opts_hdmi
+
+The current workaround to setup the TMDS character rate relies on the
+unconventional usage of phy_set_bus_width().
+
+Make use of the recently introduced HDMI PHY configuration API to
+properly handle the setup. The workaround will be dropped as soon as
+the switch has been completed on both ends.
+
+Rename rk_hdptx_phy_verify_config() to rk_hdptx_phy_verify_dp_config()
+and introduce the rk_hdptx_phy_verify_hdmi_config() helper to check the
+HDMI parameters during phy_configure().
+
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-9-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 64 ++++++++++++++-----
+ 1 file changed, 47 insertions(+), 17 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -394,6 +394,7 @@ struct rk_hdptx_phy {
+
+ int phy_id;
+ struct phy *phy;
++ struct phy_configure_opts_hdmi hdmi_cfg;
+ struct clk_bulk_data *clks;
+ int nr_clks;
+ struct reset_control_bulk_data rsts[RST_MAX];
+@@ -1414,20 +1415,24 @@ static int rk_hdptx_phy_power_on(struct
+ {
+ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
+ enum phy_mode mode = phy_get_mode(phy);
+- unsigned long long rate;
+ int ret, lane;
+
+- /*
+- * FIXME: Temporary workaround to pass pixel_clk_rate
+- * from the HDMI bridge driver until phy_configure_opts_hdmi
+- * becomes available in the PHY API.
+- */
+- rate = phy_get_bus_width(hdptx->phy) & 0xfffffff;
+- rate *= 100;
++ if (mode != PHY_MODE_DP) {
++ if (!hdptx->hdmi_cfg.tmds_char_rate) {
++ /*
++ * FIXME: Temporary workaround to setup TMDS char rate
++ * from the RK DW HDMI QP bridge driver.
++ * Will be removed as soon the switch to the HDMI PHY
++ * configuration API has been completed on both ends.
++ */
++ hdptx->hdmi_cfg.tmds_char_rate = phy_get_bus_width(hdptx->phy) & 0xfffffff;
++ hdptx->hdmi_cfg.tmds_char_rate *= 100;
++ }
+
+- dev_dbg(hdptx->dev, "%s rate=%llu\n", __func__, rate);
++ dev_dbg(hdptx->dev, "%s rate=%llu\n", __func__, hdptx->hdmi_cfg.tmds_char_rate);
++ }
+
+- ret = rk_hdptx_phy_consumer_get(hdptx, rate);
++ ret = rk_hdptx_phy_consumer_get(hdptx, hdptx->hdmi_cfg.tmds_char_rate);
+ if (ret)
+ return ret;
+
+@@ -1458,7 +1463,7 @@ static int rk_hdptx_phy_power_on(struct
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
+ HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
+
+- ret = rk_hdptx_ropll_tmds_mode_config(hdptx, rate);
++ ret = rk_hdptx_ropll_tmds_mode_config(hdptx, hdptx->hdmi_cfg.tmds_char_rate);
+ if (ret)
+ rk_hdptx_phy_consumer_put(hdptx, true);
+ }
+@@ -1473,8 +1478,27 @@ static int rk_hdptx_phy_power_off(struct
+ return rk_hdptx_phy_consumer_put(hdptx, false);
+ }
+
+-static int rk_hdptx_phy_verify_config(struct rk_hdptx_phy *hdptx,
+- struct phy_configure_opts_dp *dp)
++static int rk_hdptx_phy_verify_hdmi_config(struct rk_hdptx_phy *hdptx,
++ struct phy_configure_opts_hdmi *hdmi)
++{
++ int i;
++
++ if (!hdmi->tmds_char_rate || hdmi->tmds_char_rate > HDMI20_MAX_RATE)
++ return -EINVAL;
++
++ for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
++ if (hdmi->tmds_char_rate == ropll_tmds_cfg[i].rate)
++ break;
++
++ if (i == ARRAY_SIZE(ropll_tmds_cfg) &&
++ !rk_hdptx_phy_clk_pll_calc(hdmi->tmds_char_rate, NULL))
++ return -EINVAL;
++
++ return 0;
++}
++
++static int rk_hdptx_phy_verify_dp_config(struct rk_hdptx_phy *hdptx,
++ struct phy_configure_opts_dp *dp)
+ {
+ int i;
+
+@@ -1734,12 +1758,18 @@ static int rk_hdptx_phy_configure(struct
+ enum phy_mode mode = phy_get_mode(phy);
+ int ret;
+
+- if (mode != PHY_MODE_DP)
+- return 0;
++ if (mode != PHY_MODE_DP) {
++ ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &opts->hdmi);
++ if (ret)
++ dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
++ else
++ hdptx->hdmi_cfg = opts->hdmi;
++ return ret;
++ }
+
+- ret = rk_hdptx_phy_verify_config(hdptx, &opts->dp);
++ ret = rk_hdptx_phy_verify_dp_config(hdptx, &opts->dp);
+ if (ret) {
+- dev_err(hdptx->dev, "invalid params for phy configure\n");
++ dev_err(hdptx->dev, "invalid dp params for phy configure\n");
+ return ret;
+ }
+
diff --git a/lede/target/linux/rockchip/patches-6.12/040-14-v6.16-phy-rockchip-samsung-hdptx-Provide-config-params.patch b/lede/target/linux/rockchip/patches-6.12/040-14-v6.16-phy-rockchip-samsung-hdptx-Provide-config-params.patch
new file mode 100644
index 0000000000..22eb311ca7
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-14-v6.16-phy-rockchip-samsung-hdptx-Provide-config-params.patch
@@ -0,0 +1,42 @@
+From 2392050a2cb94ff3397949e109e4b9f0285ee085 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:44 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Provide config params
+ validation support
+
+Implement the phy_ops.validate() callback to allow checking the PHY
+configuration parameters without actually applying them.
+
+Reviewed-by: Dmitry Baryshkov
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-10-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -1801,10 +1801,22 @@ static int rk_hdptx_phy_configure(struct
+ return 0;
+ }
+
++static int rk_hdptx_phy_validate(struct phy *phy, enum phy_mode mode,
++ int submode, union phy_configure_opts *opts)
++{
++ struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
++
++ if (mode != PHY_MODE_DP)
++ return rk_hdptx_phy_verify_hdmi_config(hdptx, &opts->hdmi);
++
++ return rk_hdptx_phy_verify_dp_config(hdptx, &opts->dp);
++}
++
+ static const struct phy_ops rk_hdptx_phy_ops = {
+ .power_on = rk_hdptx_phy_power_on,
+ .power_off = rk_hdptx_phy_power_off,
+ .configure = rk_hdptx_phy_configure,
++ .validate = rk_hdptx_phy_validate,
+ .owner = THIS_MODULE,
+ };
+
diff --git a/lede/target/linux/rockchip/patches-6.12/040-15-v6.16-phy-rockchip-samsung-hdptx-Restrict-altering-TMDS-char.patch b/lede/target/linux/rockchip/patches-6.12/040-15-v6.16-phy-rockchip-samsung-hdptx-Restrict-altering-TMDS-char.patch
new file mode 100644
index 0000000000..ef64539ace
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-15-v6.16-phy-rockchip-samsung-hdptx-Restrict-altering-TMDS-char.patch
@@ -0,0 +1,129 @@
+From 6efbd0f46dd8ae1d2b91b41d98c2800c60ab1f5e Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:45 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Restrict altering TMDS char
+ rate via CCF
+
+Although, in theory, the clock provider functionality could be enabled
+as a standalone driver feature, in practice it is unlikely that it would
+be ever needed separately from the common PHY related features, i.e.
+making use of the PHY PLL as an alternative and more accurate clock
+source for display modes handling. Which means the PLL will be always
+programmed according to the TMDS char rate set via the HDMI PHY
+configuration API.
+
+Currently it's possible to freely adjust the rate via the clock API as
+well, that is through clk_set_rate(). Making the clock read-only is not
+feasible since we need to ensure any rate update done via the PHY
+configuration API has been actually programmed into the hardware before
+CCF accesses it. This would be normally done during phy_ops.power_on()
+or clk_ops.prepare() callbacks, but it might happen that the former gets
+fired too late and the latter only once, hence we need to keep handle it
+via clk_ops.set_rate() as a fallback approach.
+
+Prevent changing the TMDS character rate via CCF by letting
+rk_hdptx_phy_clk_round_rate() always return the value set via
+phy_configure(). To avoid breaking existing users, i.e. RK DW HDMI QP
+bridge driver, until the switch to the HDMI PHY config based approach is
+completed, introduce a temporary exception to the rule, toggled via the
+new ->restrict_rate_change flag, which indicates whether phy_configure()
+has been called or not.
+
+Additionally, revert any unlikely rate change that might have occurred
+between the calls to ->round_rate() and ->set_rate().
+
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-11-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 52 ++++++++++++++-----
+ 1 file changed, 40 insertions(+), 12 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -402,6 +402,7 @@ struct rk_hdptx_phy {
+ /* clk provider */
+ struct clk_hw hw;
+ unsigned long rate;
++ bool restrict_rate_change;
+
+ atomic_t usage_count;
+
+@@ -1760,10 +1761,12 @@ static int rk_hdptx_phy_configure(struct
+
+ if (mode != PHY_MODE_DP) {
+ ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &opts->hdmi);
+- if (ret)
++ if (ret) {
+ dev_err(hdptx->dev, "invalid hdmi params for phy configure\n");
+- else
++ } else {
+ hdptx->hdmi_cfg = opts->hdmi;
++ hdptx->restrict_rate_change = true;
++ }
+ return ret;
+ }
+
+@@ -1850,20 +1853,31 @@ static unsigned long rk_hdptx_phy_clk_re
+ static long rk_hdptx_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+ {
+- int i;
++ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+- if (rate > HDMI20_MAX_RATE)
+- return rate;
++ /*
++ * FIXME: Temporarily allow altering TMDS char rate via CCF.
++ * To be dropped as soon as the RK DW HDMI QP bridge driver
++ * switches to make use of phy_configure().
++ */
++ if (!hdptx->restrict_rate_change && rate != hdptx->hdmi_cfg.tmds_char_rate) {
++ struct phy_configure_opts_hdmi hdmi = {
++ .tmds_char_rate = rate,
++ };
++ int ret = rk_hdptx_phy_verify_hdmi_config(hdptx, &hdmi);
+
+- for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
+- if (rate == ropll_tmds_cfg[i].rate)
+- break;
++ if (ret)
++ return ret;
+
+- if (i == ARRAY_SIZE(ropll_tmds_cfg) &&
+- !rk_hdptx_phy_clk_pll_calc(rate, NULL))
+- return -EINVAL;
++ hdptx->hdmi_cfg = hdmi;
++ }
+
+- return rate;
++ /*
++ * The TMDS char rate shall be adjusted via phy_configure() only,
++ * hence ensure rk_hdptx_phy_clk_set_rate() won't be invoked with
++ * a different rate argument.
++ */
++ return hdptx->hdmi_cfg.tmds_char_rate;
+ }
+
+ static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+@@ -1871,6 +1885,20 @@ static int rk_hdptx_phy_clk_set_rate(str
+ {
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
++ /* Revert any unlikely TMDS char rate change since round_rate() */
++ if (hdptx->hdmi_cfg.tmds_char_rate != rate) {
++ dev_warn(hdptx->dev, "Reverting unexpected rate change from %lu to %llu\n",
++ rate, hdptx->hdmi_cfg.tmds_char_rate);
++ hdptx->hdmi_cfg.tmds_char_rate = rate;
++ }
++
++ /*
++ * The TMDS char rate would be normally programmed in HW during
++ * phy_ops.power_on() or clk_ops.prepare() callbacks, but it might
++ * happen that the former gets fired too late, i.e. after this call,
++ * while the latter being executed only once, i.e. when clock remains
++ * in the prepared state during rate changes.
++ */
+ return rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
+ }
+
diff --git a/lede/target/linux/rockchip/patches-6.12/040-16-v6.16-phy-rockchip-samsung-hdptx-Rename-ambiguous.patch b/lede/target/linux/rockchip/patches-6.12/040-16-v6.16-phy-rockchip-samsung-hdptx-Rename-ambiguous.patch
new file mode 100644
index 0000000000..df71625d18
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-16-v6.16-phy-rockchip-samsung-hdptx-Rename-ambiguous.patch
@@ -0,0 +1,58 @@
+From 37f335dbfd028c008d0a7940ca5a270d1e2f6b81 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:46 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Rename ambiguous
+ rk_hdptx_phy->rate
+
+The main purpose of the ->rate member of struct rk_hdptx_phy is to
+implement rk_hdptx_phy_clk_recalc_rate() by providing the actual rate
+programmed in hardware. Hence the current naming is too generic and
+rather ambiguous.
+
+Improve clarity by renaming ->rate to ->hw_rate.
+
+Signed-off-by: Cristian Ciocaltea
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-12-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -401,7 +401,7 @@ struct rk_hdptx_phy {
+
+ /* clk provider */
+ struct clk_hw hw;
+- unsigned long rate;
++ unsigned long hw_rate;
+ bool restrict_rate_change;
+
+ atomic_t usage_count;
+@@ -1032,7 +1032,7 @@ static int rk_hdptx_ropll_tmds_cmn_confi
+
+ ret = rk_hdptx_post_enable_pll(hdptx);
+ if (!ret)
+- hdptx->rate = rate;
++ hdptx->hw_rate = rate;
+
+ return ret;
+ }
+@@ -1832,7 +1832,7 @@ static int rk_hdptx_phy_clk_prepare(stru
+ {
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+- return rk_hdptx_phy_consumer_get(hdptx, hdptx->rate);
++ return rk_hdptx_phy_consumer_get(hdptx, hdptx->hw_rate);
+ }
+
+ static void rk_hdptx_phy_clk_unprepare(struct clk_hw *hw)
+@@ -1847,7 +1847,7 @@ static unsigned long rk_hdptx_phy_clk_re
+ {
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+- return hdptx->rate;
++ return hdptx->hw_rate;
+ }
+
+ static long rk_hdptx_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/lede/target/linux/rockchip/patches-6.12/040-17-v6.16-phy-rockchip-samsung-hdptx-Optimize-internal-rate-handlin.patch b/lede/target/linux/rockchip/patches-6.12/040-17-v6.16-phy-rockchip-samsung-hdptx-Optimize-internal-rate-handlin.patch
new file mode 100644
index 0000000000..d737f228aa
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-17-v6.16-phy-rockchip-samsung-hdptx-Optimize-internal-rate-handlin.patch
@@ -0,0 +1,147 @@
+From 45b14bdcf4acfd483d9890396197c35c23821124 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:47 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Optimize internal rate handling
+
+Drop the rate parameter from a bunch of internal helpers and, instead,
+make better use of the newly introduced ->hdmi_cfg.tmds_char_rate driver
+data.
+
+Signed-off-by: Cristian Ciocaltea
+Acked-by: Dmitry Baryshkov
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-13-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 47 +++++++++----------
+ 1 file changed, 23 insertions(+), 24 deletions(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -970,31 +970,34 @@ static bool rk_hdptx_phy_clk_pll_calc(un
+ return true;
+ }
+
+-static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx,
+- unsigned long long rate)
++static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx)
+ {
+ const struct ropll_config *cfg = NULL;
+ struct ropll_config rc = {0};
+ int ret, i;
+
++ if (!hdptx->hdmi_cfg.tmds_char_rate)
++ return 0;
++
+ for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
+- if (rate == ropll_tmds_cfg[i].rate) {
++ if (hdptx->hdmi_cfg.tmds_char_rate == ropll_tmds_cfg[i].rate) {
+ cfg = &ropll_tmds_cfg[i];
+ break;
+ }
+
+ if (!cfg) {
+- if (rk_hdptx_phy_clk_pll_calc(rate, &rc)) {
+- cfg = &rc;
+- } else {
+- dev_err(hdptx->dev, "%s cannot find pll cfg\n", __func__);
++ if (!rk_hdptx_phy_clk_pll_calc(hdptx->hdmi_cfg.tmds_char_rate, &rc)) {
++ dev_err(hdptx->dev, "%s cannot find pll cfg for rate=%llu\n",
++ __func__, hdptx->hdmi_cfg.tmds_char_rate);
+ return -EINVAL;
+ }
++
++ cfg = &rc;
+ }
+
+ dev_dbg(hdptx->dev, "%s rate=%llu mdiv=%u sdiv=%u sdm_en=%u k_sign=%u k=%u lc=%u\n",
+- __func__, rate, cfg->pms_mdiv, cfg->pms_sdiv + 1, cfg->sdm_en,
+- cfg->sdm_num_sign, cfg->sdm_num, cfg->sdm_deno);
++ __func__, hdptx->hdmi_cfg.tmds_char_rate, cfg->pms_mdiv, cfg->pms_sdiv + 1,
++ cfg->sdm_en, cfg->sdm_num_sign, cfg->sdm_num, cfg->sdm_deno);
+
+ rk_hdptx_pre_power_up(hdptx);
+
+@@ -1032,19 +1035,18 @@ static int rk_hdptx_ropll_tmds_cmn_confi
+
+ ret = rk_hdptx_post_enable_pll(hdptx);
+ if (!ret)
+- hdptx->hw_rate = rate;
++ hdptx->hw_rate = hdptx->hdmi_cfg.tmds_char_rate;
+
+ return ret;
+ }
+
+-static int rk_hdptx_ropll_tmds_mode_config(struct rk_hdptx_phy *hdptx,
+- unsigned long long rate)
++static int rk_hdptx_ropll_tmds_mode_config(struct rk_hdptx_phy *hdptx)
+ {
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_sb_init_seq);
+
+ regmap_write(hdptx->regmap, LNTOP_REG(0200), 0x06);
+
+- if (rate > HDMI14_MAX_RATE) {
++ if (hdptx->hdmi_cfg.tmds_char_rate > HDMI14_MAX_RATE) {
+ /* For 1/40 bitrate clk */
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_tmds_lntop_highbr_seq);
+ } else {
+@@ -1096,8 +1098,7 @@ static void rk_hdptx_dp_reset(struct rk_
+ HDPTX_I_BGR_EN << 16 | FIELD_PREP(HDPTX_I_BGR_EN, 0x0));
+ }
+
+-static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx,
+- unsigned long long rate)
++static int rk_hdptx_phy_consumer_get(struct rk_hdptx_phy *hdptx)
+ {
+ enum phy_mode mode = phy_get_mode(hdptx->phy);
+ u32 status;
+@@ -1116,11 +1117,9 @@ static int rk_hdptx_phy_consumer_get(str
+ if (mode == PHY_MODE_DP) {
+ rk_hdptx_dp_reset(hdptx);
+ } else {
+- if (rate) {
+- ret = rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
+- if (ret)
+- goto dec_usage;
+- }
++ ret = rk_hdptx_ropll_tmds_cmn_config(hdptx);
++ if (ret)
++ goto dec_usage;
+ }
+
+ return 0;
+@@ -1433,7 +1432,7 @@ static int rk_hdptx_phy_power_on(struct
+ dev_dbg(hdptx->dev, "%s rate=%llu\n", __func__, hdptx->hdmi_cfg.tmds_char_rate);
+ }
+
+- ret = rk_hdptx_phy_consumer_get(hdptx, hdptx->hdmi_cfg.tmds_char_rate);
++ ret = rk_hdptx_phy_consumer_get(hdptx);
+ if (ret)
+ return ret;
+
+@@ -1464,7 +1463,7 @@ static int rk_hdptx_phy_power_on(struct
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0,
+ HDPTX_MODE_SEL << 16 | FIELD_PREP(HDPTX_MODE_SEL, 0x0));
+
+- ret = rk_hdptx_ropll_tmds_mode_config(hdptx, hdptx->hdmi_cfg.tmds_char_rate);
++ ret = rk_hdptx_ropll_tmds_mode_config(hdptx);
+ if (ret)
+ rk_hdptx_phy_consumer_put(hdptx, true);
+ }
+@@ -1832,7 +1831,7 @@ static int rk_hdptx_phy_clk_prepare(stru
+ {
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+- return rk_hdptx_phy_consumer_get(hdptx, hdptx->hw_rate);
++ return rk_hdptx_phy_consumer_get(hdptx);
+ }
+
+ static void rk_hdptx_phy_clk_unprepare(struct clk_hw *hw)
+@@ -1899,7 +1898,7 @@ static int rk_hdptx_phy_clk_set_rate(str
+ * while the latter being executed only once, i.e. when clock remains
+ * in the prepared state during rate changes.
+ */
+- return rk_hdptx_ropll_tmds_cmn_config(hdptx, rate);
++ return rk_hdptx_ropll_tmds_cmn_config(hdptx);
+ }
+
+ static const struct clk_ops hdptx_phy_clk_ops = {
diff --git a/lede/target/linux/rockchip/patches-6.12/040-18-v6.16-phy-rockchip-samsung-hdptx-Add-high-color-depth-managemen.patch b/lede/target/linux/rockchip/patches-6.12/040-18-v6.16-phy-rockchip-samsung-hdptx-Add-high-color-depth-managemen.patch
new file mode 100644
index 0000000000..568419bba5
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-18-v6.16-phy-rockchip-samsung-hdptx-Add-high-color-depth-managemen.patch
@@ -0,0 +1,76 @@
+From 9d0ec51d7c227c3ae837e22832eaed219e25f126 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Tue, 18 Mar 2025 14:35:48 +0200
+Subject: [PATCH] phy: rockchip: samsung-hdptx: Add high color depth management
+
+Add support for 8-bit, 10-bit, 12-bit and 16-bit color depth setup.
+
+Reviewed-by: Dmitry Baryshkov
+Signed-off-by: Cristian Ciocaltea
+Reviewed-by: Dmitry Baryshkov
+Link: https://lore.kernel.org/r/20250318-phy-sam-hdptx-bpc-v6-14-8cb1678e7663@collabora.com
+Signed-off-by: Vinod Koul
+---
+ .../phy/rockchip/phy-rockchip-samsung-hdptx.c | 23 ++++++++++++++++++-
+ 1 file changed, 22 insertions(+), 1 deletion(-)
+
+--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+@@ -1030,6 +1030,9 @@ static int rk_hdptx_ropll_tmds_cmn_confi
+ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_POSTDIV_SEL_MASK,
+ FIELD_PREP(PLL_PCG_POSTDIV_SEL_MASK, cfg->pms_sdiv));
+
++ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_CLK_SEL_MASK,
++ FIELD_PREP(PLL_PCG_CLK_SEL_MASK, (hdptx->hdmi_cfg.bpc - 8) >> 1));
++
+ regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_CLK_EN_MASK,
+ FIELD_PREP(PLL_PCG_CLK_EN_MASK, 0x1));
+
+@@ -1429,7 +1432,8 @@ static int rk_hdptx_phy_power_on(struct
+ hdptx->hdmi_cfg.tmds_char_rate *= 100;
+ }
+
+- dev_dbg(hdptx->dev, "%s rate=%llu\n", __func__, hdptx->hdmi_cfg.tmds_char_rate);
++ dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
++ hdptx->hdmi_cfg.tmds_char_rate, hdptx->hdmi_cfg.bpc);
+ }
+
+ ret = rk_hdptx_phy_consumer_get(hdptx);
+@@ -1494,6 +1498,19 @@ static int rk_hdptx_phy_verify_hdmi_conf
+ !rk_hdptx_phy_clk_pll_calc(hdmi->tmds_char_rate, NULL))
+ return -EINVAL;
+
++ if (!hdmi->bpc)
++ hdmi->bpc = 8;
++
++ switch (hdmi->bpc) {
++ case 8:
++ case 10:
++ case 12:
++ case 16:
++ break;
++ default:
++ return -EINVAL;
++ };
++
+ return 0;
+ }
+
+@@ -1766,6 +1783,9 @@ static int rk_hdptx_phy_configure(struct
+ hdptx->hdmi_cfg = opts->hdmi;
+ hdptx->restrict_rate_change = true;
+ }
++
++ dev_dbg(hdptx->dev, "%s rate=%llu bpc=%u\n", __func__,
++ hdptx->hdmi_cfg.tmds_char_rate, hdptx->hdmi_cfg.bpc);
+ return ret;
+ }
+
+@@ -1974,6 +1994,7 @@ static int rk_hdptx_phy_probe(struct pla
+ return -ENOMEM;
+
+ hdptx->dev = dev;
++ hdptx->hdmi_cfg.bpc = 8;
+
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
diff --git a/lede/target/linux/rockchip/patches-6.12/040-55-v6.16-dt-bindings-display-rockchip-analogix-dp-Add-support-for.patch b/lede/target/linux/rockchip/patches-6.12/040-55-v6.16-dt-bindings-display-rockchip-analogix-dp-Add-support-for.patch
new file mode 100644
index 0000000000..096680e892
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/040-55-v6.16-dt-bindings-display-rockchip-analogix-dp-Add-support-for.patch
@@ -0,0 +1,68 @@
+From f855146263b14abadd8d5bd0e280e54fbab3bd18 Mon Sep 17 00:00:00 2001
+From: Damon Ding
+Date: Mon, 10 Mar 2025 18:41:10 +0800
+Subject: [PATCH] dt-bindings: display: rockchip: analogix-dp: Add support for
+ RK3588
+
+Compared with RK3288/RK3399, the HBR2 link rate support is the main
+improvement of RK3588 eDP TX controller, and there are also two
+independent eDP display interfaces on RK3588 Soc.
+
+The newly added 'apb' reset is to ensure the APB bus of eDP controller
+works well on the RK3588 SoC.
+
+Reviewed-by: Rob Herring (Arm)
+Signed-off-by: Damon Ding
+Link: https://lore.kernel.org/r/20250310104114.2608063-10-damon.ding@rock-chips.com
+Signed-off-by: Dmitry Baryshkov
+---
+ .../rockchip/rockchip,analogix-dp.yaml | 22 +++++++++++++++++--
+ 1 file changed, 20 insertions(+), 2 deletions(-)
+
+--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml
++++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,analogix-dp.yaml
+@@ -15,6 +15,7 @@ properties:
+ enum:
+ - rockchip,rk3288-dp
+ - rockchip,rk3399-edp
++ - rockchip,rk3588-edp
+
+ clocks:
+ minItems: 2
+@@ -31,10 +32,14 @@ properties:
+ maxItems: 1
+
+ resets:
+- maxItems: 1
++ minItems: 1
++ maxItems: 2
+
+ reset-names:
+- const: dp
++ minItems: 1
++ items:
++ - const: dp
++ - const: apb
+
+ rockchip,grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+@@ -52,6 +57,19 @@ required:
+ allOf:
+ - $ref: /schemas/display/bridge/analogix,dp.yaml#
+
++ - if:
++ properties:
++ compatible:
++ contains:
++ enum:
++ - rockchip,rk3588-edp
++ then:
++ properties:
++ resets:
++ minItems: 2
++ reset-names:
++ minItems: 2
++
+ unevaluatedProperties: false
+
+ examples:
diff --git a/lede/target/linux/rockchip/patches-6.12/041-01-v6.13-drm-rockchip-dw_hdmi-Filter-modes-based-on-hdmiphy_clk.patch b/lede/target/linux/rockchip/patches-6.12/041-01-v6.13-drm-rockchip-dw_hdmi-Filter-modes-based-on-hdmiphy_clk.patch
new file mode 100644
index 0000000000..f1c24eceba
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-01-v6.13-drm-rockchip-dw_hdmi-Filter-modes-based-on-hdmiphy_clk.patch
@@ -0,0 +1,92 @@
+From 3303a206ae7474b2f8a5d17d8df9de08bac16ca5 Mon Sep 17 00:00:00 2001
+From: Jonas Karlman
+Date: Sun, 8 Sep 2024 14:54:58 +0000
+Subject: [PATCH] drm/rockchip: dw_hdmi: Filter modes based on hdmiphy_clk
+
+RK3228 and RK3328 clock rate is being validated against a mpll config
+table intended for a Synopsys phy, and not the used inno-hdmi-phy.
+
+Instead get a reference to the hdmiphy clk and validate rates against
+it to enable use of HDMI2.0 modes, e.g. 4K@60Hz, on RK3228 and RK3328.
+
+For Synopsis phy the max_tmds_clock validation is sufficient.
+
+Signed-off-by: Jonas Karlman
+Tested-by: Diederik de Haas # Rock64
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240908145511.3331451-2-jonas@kwiboo.se
+---
+ drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 35 ++++++++++-----------
+ 1 file changed, 17 insertions(+), 18 deletions(-)
+
+--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
++++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+@@ -76,6 +76,7 @@ struct rockchip_hdmi {
+ struct rockchip_encoder encoder;
+ const struct rockchip_hdmi_chip_data *chip_data;
+ const struct dw_hdmi_plat_data *plat_data;
++ struct clk *hdmiphy_clk;
+ struct clk *ref_clk;
+ struct clk *grf_clk;
+ struct dw_hdmi *hdmi;
+@@ -251,10 +252,7 @@ dw_hdmi_rockchip_mode_valid(struct dw_hd
+ const struct drm_display_mode *mode)
+ {
+ struct rockchip_hdmi *hdmi = data;
+- const struct dw_hdmi_mpll_config *mpll_cfg = rockchip_mpll_cfg;
+ int pclk = mode->clock * 1000;
+- bool exact_match = hdmi->plat_data->phy_force_vendor;
+- int i;
+
+ if (hdmi->chip_data->max_tmds_clock &&
+ mode->clock > hdmi->chip_data->max_tmds_clock)
+@@ -263,26 +261,18 @@ dw_hdmi_rockchip_mode_valid(struct dw_hd
+ if (hdmi->ref_clk) {
+ int rpclk = clk_round_rate(hdmi->ref_clk, pclk);
+
+- if (abs(rpclk - pclk) > pclk / 1000)
++ if (rpclk < 0 || abs(rpclk - pclk) > pclk / 1000)
+ return MODE_NOCLOCK;
+ }
+
+- for (i = 0; mpll_cfg[i].mpixelclock != (~0UL); i++) {
+- /*
+- * For vendor specific phys force an exact match of the pixelclock
+- * to preserve the original behaviour of the driver.
+- */
+- if (exact_match && pclk == mpll_cfg[i].mpixelclock)
+- return MODE_OK;
+- /*
+- * The Synopsys phy can work with pixelclocks up to the value given
+- * in the corresponding mpll_cfg entry.
+- */
+- if (!exact_match && pclk <= mpll_cfg[i].mpixelclock)
+- return MODE_OK;
++ if (hdmi->hdmiphy_clk) {
++ int rpclk = clk_round_rate(hdmi->hdmiphy_clk, pclk);
++
++ if (rpclk < 0 || abs(rpclk - pclk) > pclk / 1000)
++ return MODE_NOCLOCK;
+ }
+
+- return MODE_BAD;
++ return MODE_OK;
+ }
+
+ static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
+@@ -607,6 +597,15 @@ static int dw_hdmi_rockchip_bind(struct
+ return ret;
+ }
+
++ if (hdmi->phy) {
++ struct of_phandle_args clkspec;
++
++ clkspec.np = hdmi->phy->dev.of_node;
++ hdmi->hdmiphy_clk = of_clk_get_from_provider(&clkspec);
++ if (IS_ERR(hdmi->hdmiphy_clk))
++ hdmi->hdmiphy_clk = NULL;
++ }
++
+ if (hdmi->chip_data == &rk3568_chip_data) {
+ regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1,
+ HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK |
diff --git a/lede/target/linux/rockchip/patches-6.12/041-02-v6.13-drm-rockchip-dw_hdmi-Adjust-cklvl-txlvl-for-RF-EMI.patch b/lede/target/linux/rockchip/patches-6.12/041-02-v6.13-drm-rockchip-dw_hdmi-Adjust-cklvl-txlvl-for-RF-EMI.patch
new file mode 100644
index 0000000000..8eed371cc2
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-02-v6.13-drm-rockchip-dw_hdmi-Adjust-cklvl-txlvl-for-RF-EMI.patch
@@ -0,0 +1,53 @@
+From 6e94e2871eb706a17692acf7ef85ecf2789f6433 Mon Sep 17 00:00:00 2001
+From: Yakir Yang
+Date: Sun, 8 Sep 2024 14:54:59 +0000
+Subject: [PATCH] drm/rockchip: dw_hdmi: Adjust cklvl & txlvl for RF/EMI
+
+Dut to the high HDMI signal voltage driver, Mickey have meet
+a serious RF/EMI problem, so we decided to reduce HDMI signal
+voltage to a proper value.
+
+The default params for phy is cklvl = 20 & txlvl = 13 (RF/EMI failed)
+ ck: lvl = 13, term=100, vlo = 2.71, vhi=3.14, vswing = 0.43
+ tx: lvl = 20, term=100, vlo = 2.81, vhi=3.16, vswing = 0.35
+
+1. We decided to reduce voltage value to lower, but VSwing still
+keep high, RF/EMI have been improved but still failed.
+ ck: lvl = 6, term=100, vlo = 2.61, vhi=3.11, vswing = 0.50
+ tx: lvl = 6, term=100, vlo = 2.61, vhi=3.11, vswing = 0.50
+
+2. We try to keep voltage value and vswing both lower, then RF/EMI
+test all passed ;)
+ ck: lvl = 11, term= 66, vlo = 2.68, vhi=3.09, vswing = 0.40
+ tx: lvl = 11, term= 66, vlo = 2.68, vhi=3.09, vswing = 0.40
+When we back to run HDMI different test and single-end test, we see
+different test passed, but signle-end test failed. The oscilloscope
+show that simgle-end clock's VL value is 1.78v (which remind LowLimit
+should not lower then 2.6v).
+
+3. That's to say there are some different between PHY document and
+measure value. And according to experiment 2 results, we need to
+higher clock voltage and lower data voltage, then we can keep RF/EMI
+satisfied and single-end & differen test passed.
+ ck: lvl = 9, term=100, vlo = 2.65, vhi=3.12, vswing = 0.47
+ tx: lvl = 16, term=100, vlo = 2.75, vhi=3.15, vswing = 0.39
+
+Signed-off-by: Yakir Yang
+Signed-off-by: Jonas Karlman
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240908145511.3331451-3-jonas@kwiboo.se
+---
+ drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
++++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+@@ -202,7 +202,7 @@ static const struct dw_hdmi_curr_ctrl ro
+ static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
+ /*pixelclk symbol term vlev*/
+ { 74250000, 0x8009, 0x0004, 0x0272},
+- { 148500000, 0x802b, 0x0004, 0x028d},
++ { 165000000, 0x802b, 0x0004, 0x0209},
+ { 297000000, 0x8039, 0x0005, 0x028d},
+ { ~0UL, 0x0000, 0x0000, 0x0000}
+ };
diff --git a/lede/target/linux/rockchip/patches-6.12/041-03-v6.13-drm-rockchip-dw_hdmi-Add-phy_config-for-594Mhz-pixel-cloc.patch b/lede/target/linux/rockchip/patches-6.12/041-03-v6.13-drm-rockchip-dw_hdmi-Add-phy_config-for-594Mhz-pixel-cloc.patch
new file mode 100644
index 0000000000..5929c58c61
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-03-v6.13-drm-rockchip-dw_hdmi-Add-phy_config-for-594Mhz-pixel-cloc.patch
@@ -0,0 +1,25 @@
+From b60c86d305f46483d3ed0743e9ec97a76addcabc Mon Sep 17 00:00:00 2001
+From: Nickey Yang
+Date: Sun, 8 Sep 2024 14:55:00 +0000
+Subject: [PATCH] drm/rockchip: dw_hdmi: Add phy_config for 594Mhz pixel clock
+
+Add phy_config for 594Mhz pixel clock used for HDMI2.0 display modes.
+
+Signed-off-by: Nickey Yang
+Signed-off-by: Jonas Karlman
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240908145511.3331451-4-jonas@kwiboo.se
+---
+ drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
++++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+@@ -204,6 +204,7 @@ static const struct dw_hdmi_phy_config r
+ { 74250000, 0x8009, 0x0004, 0x0272},
+ { 165000000, 0x802b, 0x0004, 0x0209},
+ { 297000000, 0x8039, 0x0005, 0x028d},
++ { 594000000, 0x8039, 0x0000, 0x019d},
+ { ~0UL, 0x0000, 0x0000, 0x0000}
+ };
+
diff --git a/lede/target/linux/rockchip/patches-6.12/041-04-v6.13-drm-rockchip-dw_hdmi-Set-cur_ctr-to-0-always.patch b/lede/target/linux/rockchip/patches-6.12/041-04-v6.13-drm-rockchip-dw_hdmi-Set-cur_ctr-to-0-always.patch
new file mode 100644
index 0000000000..a7e60e111c
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-04-v6.13-drm-rockchip-dw_hdmi-Set-cur_ctr-to-0-always.patch
@@ -0,0 +1,46 @@
+From 7d324630f3515bd6e11cadeb1d748bd74ecc9664 Mon Sep 17 00:00:00 2001
+From: Douglas Anderson
+Date: Sun, 8 Sep 2024 14:55:01 +0000
+Subject: [PATCH] drm/rockchip: dw_hdmi: Set cur_ctr to 0 always
+
+Jitter was improved by lowering the MPLL bandwidth to account for high
+frequency noise in the rk3288 PLL. In each case MPLL bandwidth was
+lowered only enough to get us a comfortable margin. We believe that
+lowering the bandwidth like this is safe given sufficient testing.
+
+Signed-off-by: Douglas Anderson
+Signed-off-by: Yakir Yang
+Signed-off-by: Jonas Karlman
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240908145511.3331451-5-jonas@kwiboo.se
+---
+ drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 16 +---------------
+ 1 file changed, 1 insertion(+), 15 deletions(-)
+
+--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
++++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+@@ -179,23 +179,9 @@ static const struct dw_hdmi_mpll_config
+ static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
+ /* pixelclk bpp8 bpp10 bpp12 */
+ {
+- 40000000, { 0x0018, 0x0018, 0x0018 },
+- }, {
+- 65000000, { 0x0028, 0x0028, 0x0028 },
+- }, {
+- 66000000, { 0x0038, 0x0038, 0x0038 },
+- }, {
+- 74250000, { 0x0028, 0x0038, 0x0038 },
+- }, {
+- 83500000, { 0x0028, 0x0038, 0x0038 },
+- }, {
+- 146250000, { 0x0038, 0x0038, 0x0038 },
+- }, {
+- 148500000, { 0x0000, 0x0038, 0x0038 },
+- }, {
+ 600000000, { 0x0000, 0x0000, 0x0000 },
+ }, {
+- ~0UL, { 0x0000, 0x0000, 0x0000},
++ ~0UL, { 0x0000, 0x0000, 0x0000 },
+ }
+ };
+
diff --git a/lede/target/linux/rockchip/patches-6.12/041-05-v6.13-drm-rockchip-dw_hdmi-Use-auto-generated-tables.patch b/lede/target/linux/rockchip/patches-6.12/041-05-v6.13-drm-rockchip-dw_hdmi-Use-auto-generated-tables.patch
new file mode 100644
index 0000000000..fd5ed90d22
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-05-v6.13-drm-rockchip-dw_hdmi-Use-auto-generated-tables.patch
@@ -0,0 +1,173 @@
+From 7595c7ef17ffe70d0f4fdda01f87f105a12de66b Mon Sep 17 00:00:00 2001
+From: Douglas Anderson
+Date: Sun, 8 Sep 2024 14:55:02 +0000
+Subject: [PATCH] drm/rockchip: dw_hdmi: Use auto-generated tables
+
+The previous tables for mpll_cfg and curr_ctrl were created using the
+20-pages of example settings provided by the PHY vendor. Those
+example settings weren't particularly dense, so there were places
+where we were guessing what the settings would be for 10-bit and
+12-bit (not that we use those anyway). It was also always a lot of
+extra work every time we wanted to add a new clock rate since we had
+to cross-reference several tables.
+
+In I've gone through the work to figure
+out how to generate this table automatically. Let's now use the
+automatically generated table and then we'll never need to look at it
+again.
+
+We only support 8-bit mode right now and only support a small number
+of clock rates and I've verified that the only 8-bit rate that was
+affected was 148.5. That mode appears to have been wrong in the old
+table.
+
+Signed-off-by: Douglas Anderson
+Signed-off-by: Yakir Yang
+Signed-off-by: Jonas Karlman
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240908145511.3331451-6-jonas@kwiboo.se
+---
+ drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 104 ++++++++++----------
+ 1 file changed, 53 insertions(+), 51 deletions(-)
+
+--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
++++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+@@ -92,74 +92,70 @@ static struct rockchip_hdmi *to_rockchip
+
+ static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = {
+ {
+- 27000000, {
+- { 0x00b3, 0x0000},
+- { 0x2153, 0x0000},
+- { 0x40f3, 0x0000}
++ 30666000, {
++ { 0x00b3, 0x0000 },
++ { 0x2153, 0x0000 },
++ { 0x40f3, 0x0000 },
+ },
+ }, {
+- 36000000, {
+- { 0x00b3, 0x0000},
+- { 0x2153, 0x0000},
+- { 0x40f3, 0x0000}
++ 36800000, {
++ { 0x00b3, 0x0000 },
++ { 0x2153, 0x0000 },
++ { 0x40a2, 0x0001 },
+ },
+ }, {
+- 40000000, {
+- { 0x00b3, 0x0000},
+- { 0x2153, 0x0000},
+- { 0x40f3, 0x0000}
++ 46000000, {
++ { 0x00b3, 0x0000 },
++ { 0x2142, 0x0001 },
++ { 0x40a2, 0x0001 },
+ },
+ }, {
+- 54000000, {
+- { 0x0072, 0x0001},
+- { 0x2142, 0x0001},
+- { 0x40a2, 0x0001},
++ 61333000, {
++ { 0x0072, 0x0001 },
++ { 0x2142, 0x0001 },
++ { 0x40a2, 0x0001 },
+ },
+ }, {
+- 65000000, {
+- { 0x0072, 0x0001},
+- { 0x2142, 0x0001},
+- { 0x40a2, 0x0001},
++ 73600000, {
++ { 0x0072, 0x0001 },
++ { 0x2142, 0x0001 },
++ { 0x4061, 0x0002 },
+ },
+ }, {
+- 66000000, {
+- { 0x013e, 0x0003},
+- { 0x217e, 0x0002},
+- { 0x4061, 0x0002}
++ 92000000, {
++ { 0x0072, 0x0001 },
++ { 0x2145, 0x0002 },
++ { 0x4061, 0x0002 },
+ },
+ }, {
+- 74250000, {
+- { 0x0072, 0x0001},
+- { 0x2145, 0x0002},
+- { 0x4061, 0x0002}
++ 122666000, {
++ { 0x0051, 0x0002 },
++ { 0x2145, 0x0002 },
++ { 0x4061, 0x0002 },
+ },
+ }, {
+- 83500000, {
+- { 0x0072, 0x0001},
++ 147200000, {
++ { 0x0051, 0x0002 },
++ { 0x2145, 0x0002 },
++ { 0x4064, 0x0003 },
+ },
+ }, {
+- 108000000, {
+- { 0x0051, 0x0002},
+- { 0x2145, 0x0002},
+- { 0x4061, 0x0002}
++ 184000000, {
++ { 0x0051, 0x0002 },
++ { 0x214c, 0x0003 },
++ { 0x4064, 0x0003 },
+ },
+ }, {
+- 106500000, {
+- { 0x0051, 0x0002},
+- { 0x2145, 0x0002},
+- { 0x4061, 0x0002}
+- },
+- }, {
+- 146250000, {
+- { 0x0051, 0x0002},
+- { 0x2145, 0x0002},
+- { 0x4061, 0x0002}
++ 226666000, {
++ { 0x0040, 0x0003 },
++ { 0x214c, 0x0003 },
++ { 0x4064, 0x0003 },
+ },
+ }, {
+- 148500000, {
+- { 0x0051, 0x0003},
+- { 0x214c, 0x0003},
+- { 0x4064, 0x0003}
++ 272000000, {
++ { 0x0040, 0x0003 },
++ { 0x214c, 0x0003 },
++ { 0x5a64, 0x0003 },
+ },
+ }, {
+ 340000000, {
+@@ -168,10 +164,16 @@ static const struct dw_hdmi_mpll_config
+ { 0x5a64, 0x0003 },
+ },
+ }, {
++ 600000000, {
++ { 0x1a40, 0x0003 },
++ { 0x3b4c, 0x0003 },
++ { 0x5a64, 0x0003 },
++ },
++ }, {
+ ~0UL, {
+- { 0x00a0, 0x000a },
+- { 0x2001, 0x000f },
+- { 0x4002, 0x000f },
++ { 0x0000, 0x0000 },
++ { 0x0000, 0x0000 },
++ { 0x0000, 0x0000 },
+ },
+ }
+ };
diff --git a/lede/target/linux/rockchip/patches-6.12/041-06-v6.13-drm-rockchip-dw_hdmi-Enable-4K-60Hz-mode-on-RK3399-and.patch b/lede/target/linux/rockchip/patches-6.12/041-06-v6.13-drm-rockchip-dw_hdmi-Enable-4K-60Hz-mode-on-RK3399-and.patch
new file mode 100644
index 0000000000..f3398ec66c
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-06-v6.13-drm-rockchip-dw_hdmi-Enable-4K-60Hz-mode-on-RK3399-and.patch
@@ -0,0 +1,37 @@
+From 28f0ae48e7fdbd6cdcf3972c8d8686a529ae1ede Mon Sep 17 00:00:00 2001
+From: Jonas Karlman
+Date: Sun, 8 Sep 2024 14:55:03 +0000
+Subject: [PATCH] drm/rockchip: dw_hdmi: Enable 4K@60Hz mode on RK3399 and
+ RK356x
+
+Use a maximum TMDS clock rate limit of 594MHz to enable use of HDMI2.0
+modes, e.g. 4K@60Hz, on RK3399 and RK3568.
+
+Signed-off-by: Jonas Karlman
+Tested-by: Diederik de Haas # Quartz64 Model B
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240908145511.3331451-7-jonas@kwiboo.se
+---
+ drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
++++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+@@ -481,7 +481,7 @@ static struct rockchip_hdmi_chip_data rk
+ .lcdsel_grf_reg = RK3399_GRF_SOC_CON20,
+ .lcdsel_big = HIWORD_UPDATE(0, RK3399_HDMI_LCDC_SEL),
+ .lcdsel_lit = HIWORD_UPDATE(RK3399_HDMI_LCDC_SEL, RK3399_HDMI_LCDC_SEL),
+- .max_tmds_clock = 340000,
++ .max_tmds_clock = 594000,
+ };
+
+ static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = {
+@@ -495,7 +495,7 @@ static const struct dw_hdmi_plat_data rk
+
+ static struct rockchip_hdmi_chip_data rk3568_chip_data = {
+ .lcdsel_grf_reg = -1,
+- .max_tmds_clock = 340000,
++ .max_tmds_clock = 594000,
+ };
+
+ static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = {
diff --git a/lede/target/linux/rockchip/patches-6.12/041-07-v6.13-drm-rockchip-Load-crtc-devices-in-preferred-order.patch b/lede/target/linux/rockchip/patches-6.12/041-07-v6.13-drm-rockchip-Load-crtc-devices-in-preferred-order.patch
new file mode 100644
index 0000000000..3eae81af5e
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-07-v6.13-drm-rockchip-Load-crtc-devices-in-preferred-order.patch
@@ -0,0 +1,59 @@
+From 0c4558a1bc2df9b6e6fb311de9cab192b0943426 Mon Sep 17 00:00:00 2001
+From: Jonas Karlman
+Date: Sun, 8 Sep 2024 14:55:04 +0000
+Subject: [PATCH] drm/rockchip: Load crtc devices in preferred order
+
+On RK3399 the VOPL is loaded before VOPB and get registered as crtc-0.
+However, on RK3288 and PX30 VOPB is gets registered as crtc-0 instead of
+VOPL.
+
+With VOPL registered as crtc-0 the kernel kms client is not able to
+enable 4K display modes for console use on RK3399.
+
+Load VOPB before VOPL to help kernel kms client make use of 4K display
+modes for console use on RK3399.
+
+Signed-off-by: Jonas Karlman
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240908145511.3331451-8-jonas@kwiboo.se
+---
+ drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 23 +++++++++++++++++++++
+ 1 file changed, 23 insertions(+)
+
+--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
++++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
+@@ -358,11 +358,34 @@ static void rockchip_drm_match_remove(st
+ device_link_del(link);
+ }
+
++/* list of preferred vop devices */
++static const char *const rockchip_drm_match_preferred[] = {
++ "rockchip,rk3399-vop-big",
++ NULL,
++};
++
+ static struct component_match *rockchip_drm_match_add(struct device *dev)
+ {
+ struct component_match *match = NULL;
++ struct device_node *port;
+ int i;
+
++ /* add preferred vop device match before adding driver device matches */
++ for (i = 0; ; i++) {
++ port = of_parse_phandle(dev->of_node, "ports", i);
++ if (!port)
++ break;
++
++ if (of_device_is_available(port->parent) &&
++ of_device_compatible_match(port->parent,
++ rockchip_drm_match_preferred))
++ drm_of_component_match_add(dev, &match,
++ component_compare_of,
++ port->parent);
++
++ of_node_put(port);
++ }
++
+ for (i = 0; i < num_rockchip_sub_drivers; i++) {
+ struct platform_driver *drv = rockchip_sub_drivers[i];
+ struct device *p = NULL, *d;
diff --git a/lede/target/linux/rockchip/patches-6.12/041-08-v6.13-drm-rockchip-Run-DRM-default-client-setup.patch b/lede/target/linux/rockchip/patches-6.12/041-08-v6.13-drm-rockchip-Run-DRM-default-client-setup.patch
new file mode 100644
index 0000000000..1a0cba19ff
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-08-v6.13-drm-rockchip-Run-DRM-default-client-setup.patch
@@ -0,0 +1,63 @@
+From 905df8f19e1a7042ddeae1e39d0920b60dfe8c0d Mon Sep 17 00:00:00 2001
+From: Thomas Zimmermann
+Date: Tue, 24 Sep 2024 09:12:35 +0200
+Subject: [PATCH] drm/rockchip: Run DRM default client setup
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Call drm_client_setup() to run the kernel's default client setup
+for DRM. Set fbdev_probe in struct drm_driver, so that the client
+setup can start the common fbdev client.
+
+v5:
+- select DRM_CLIENT_SELECTION
+
+Signed-off-by: Thomas Zimmermann
+Cc: Sandy Huang
+Cc: "Heiko St1bner"
+Cc: Andy Yan
+Acked-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240924071734.98201-38-tzimmermann@suse.de
+---
+ drivers/gpu/drm/rockchip/Kconfig | 1 +
+ drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 4 +++-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+--- a/drivers/gpu/drm/rockchip/Kconfig
++++ b/drivers/gpu/drm/rockchip/Kconfig
+@@ -2,6 +2,7 @@
+ config DRM_ROCKCHIP
+ tristate "DRM Support for Rockchip"
+ depends on DRM && ROCKCHIP_IOMMU
++ select DRM_CLIENT_SELECTION
+ select DRM_GEM_DMA_HELPER
+ select DRM_KMS_HELPER
+ select DRM_PANEL
+--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
++++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
+@@ -17,6 +17,7 @@
+ #include
+
+ #include
++#include
+ #include
+ #include
+ #include
+@@ -195,7 +196,7 @@ static int rockchip_drm_bind(struct devi
+ if (ret)
+ goto err_kms_helper_poll_fini;
+
+- drm_fbdev_dma_setup(drm_dev, 0);
++ drm_client_setup(drm_dev, NULL);
+
+ return 0;
+ err_kms_helper_poll_fini:
+@@ -230,6 +231,7 @@ static const struct drm_driver rockchip_
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+ .dumb_create = rockchip_gem_dumb_create,
+ .gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table,
++ DRM_FBDEV_DMA_DRIVER_OPS,
+ .fops = &rockchip_drm_driver_fops,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
diff --git a/lede/target/linux/rockchip/patches-6.12/041-09-v6.13-drm-bridge-synopsys-Add-DW-HDMI-QP-TX-Controller-support.patch b/lede/target/linux/rockchip/patches-6.12/041-09-v6.13-drm-bridge-synopsys-Add-DW-HDMI-QP-TX-Controller-support.patch
new file mode 100644
index 0000000000..0a7d24f90d
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-09-v6.13-drm-bridge-synopsys-Add-DW-HDMI-QP-TX-Controller-support.patch
@@ -0,0 +1,1592 @@
+From 5bd0d8e687bf04fdd3d4a733a6bb17e25d4a1de2 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Wed, 16 Oct 2024 23:06:51 +0300
+Subject: [PATCH] drm/bridge: synopsys: Add DW HDMI QP TX Controller support
+ library
+
+The Synopsys DesignWare HDMI 2.1 Quad-Pixel (QP) TX Controller IP
+supports the following features, among others:
+
+* Fixed Rate Link (FRL)
+* Display Stream Compression (DSC)
+* 4K@120Hz and 8K@60Hz video modes
+* Variable Refresh Rate (VRR) including Quick Media Switching (QMS), aka
+ Cinema VRR
+* Fast Vactive (FVA), aka Quick Frame Transport (QFT)
+* SCDC I2C DDC access
+* TMDS Scrambler enabling 2160p@60Hz with RGB/YCbCr4:4:4
+* YCbCr4:2:0 enabling 2160p@60Hz at lower HDMI link speeds
+* Multi-stream audio
+* Enhanced Audio Return Channel (EARC)
+
+Add library containing common helpers to enable basic support, i.e. RGB
+output up to 4K@30Hz, without audio, CEC or any HDMI 2.1 specific
+features.
+
+Co-developed-by: Algea Cao
+Signed-off-by: Algea Cao
+Tested-by: Heiko Stuebner
+Reviewed-by: Maxime Ripard
+Signed-off-by: Cristian Ciocaltea
+Acked-by: Maxime Ripard
+Link: https://patchwork.freedesktop.org/patch/msgid/20241016-b4-rk3588-bridge-upstream-v10-1-87ef92a6d14e@collabora.com
+Signed-off-by: Maxime Ripard
+---
+ drivers/gpu/drm/bridge/synopsys/Kconfig | 8 +
+ drivers/gpu/drm/bridge/synopsys/Makefile | 2 +
+ drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 647 ++++++++++++++
+ drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h | 834 +++++++++++++++++++
+ include/drm/bridge/dw_hdmi_qp.h | 32 +
+ 5 files changed, 1523 insertions(+)
+ create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
+ create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
+ create mode 100644 include/drm/bridge/dw_hdmi_qp.h
+
+--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
++++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
+@@ -46,6 +46,14 @@ config DRM_DW_HDMI_CEC
+ Support the CE interface which is part of the Synopsys
+ Designware HDMI block.
+
++config DRM_DW_HDMI_QP
++ tristate
++ select DRM_DISPLAY_HDMI_HELPER
++ select DRM_DISPLAY_HDMI_STATE_HELPER
++ select DRM_DISPLAY_HELPER
++ select DRM_KMS_HELPER
++ select REGMAP_MMIO
++
+ config DRM_DW_MIPI_DSI
+ tristate
+ select DRM_KMS_HELPER
+--- a/drivers/gpu/drm/bridge/synopsys/Makefile
++++ b/drivers/gpu/drm/bridge/synopsys/Makefile
+@@ -5,4 +5,6 @@ obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw
+ obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
+ obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o
+
++obj-$(CONFIG_DRM_DW_HDMI_QP) += dw-hdmi-qp.o
++
+ obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw-mipi-dsi.o
+--- /dev/null
++++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
+@@ -0,0 +1,647 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
++ * Copyright (c) 2024 Collabora Ltd.
++ *
++ * Author: Algea Cao
++ * Author: Cristian Ciocaltea
++ */
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#include
++
++#include "dw-hdmi-qp.h"
++
++#define DDC_CI_ADDR 0x37
++#define DDC_SEGMENT_ADDR 0x30
++
++#define HDMI14_MAX_TMDSCLK 340000000
++
++#define SCRAMB_POLL_DELAY_MS 3000
++
++struct dw_hdmi_qp_i2c {
++ struct i2c_adapter adap;
++
++ struct mutex lock; /* used to serialize data transfers */
++ struct completion cmp;
++ u8 stat;
++
++ u8 slave_reg;
++ bool is_regaddr;
++ bool is_segment;
++};
++
++struct dw_hdmi_qp {
++ struct drm_bridge bridge;
++
++ struct device *dev;
++ struct dw_hdmi_qp_i2c *i2c;
++
++ struct {
++ const struct dw_hdmi_qp_phy_ops *ops;
++ void *data;
++ } phy;
++
++ struct regmap *regm;
++};
++
++static void dw_hdmi_qp_write(struct dw_hdmi_qp *hdmi, unsigned int val,
++ int offset)
++{
++ regmap_write(hdmi->regm, offset, val);
++}
++
++static unsigned int dw_hdmi_qp_read(struct dw_hdmi_qp *hdmi, int offset)
++{
++ unsigned int val = 0;
++
++ regmap_read(hdmi->regm, offset, &val);
++
++ return val;
++}
++
++static void dw_hdmi_qp_mod(struct dw_hdmi_qp *hdmi, unsigned int data,
++ unsigned int mask, unsigned int reg)
++{
++ regmap_update_bits(hdmi->regm, reg, mask, data);
++}
++
++static int dw_hdmi_qp_i2c_read(struct dw_hdmi_qp *hdmi,
++ unsigned char *buf, unsigned int length)
++{
++ struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
++ int stat;
++
++ if (!i2c->is_regaddr) {
++ dev_dbg(hdmi->dev, "set read register address to 0\n");
++ i2c->slave_reg = 0x00;
++ i2c->is_regaddr = true;
++ }
++
++ while (length--) {
++ reinit_completion(&i2c->cmp);
++
++ dw_hdmi_qp_mod(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR,
++ I2CM_INTERFACE_CONTROL0);
++
++ if (i2c->is_segment)
++ dw_hdmi_qp_mod(hdmi, I2CM_EXT_READ, I2CM_WR_MASK,
++ I2CM_INTERFACE_CONTROL0);
++ else
++ dw_hdmi_qp_mod(hdmi, I2CM_FM_READ, I2CM_WR_MASK,
++ I2CM_INTERFACE_CONTROL0);
++
++ stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
++ if (!stat) {
++ dev_err(hdmi->dev, "i2c read timed out\n");
++ dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
++ return -EAGAIN;
++ }
++
++ /* Check for error condition on the bus */
++ if (i2c->stat & I2CM_NACK_RCVD_IRQ) {
++ dev_err(hdmi->dev, "i2c read error\n");
++ dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
++ return -EIO;
++ }
++
++ *buf++ = dw_hdmi_qp_read(hdmi, I2CM_INTERFACE_RDDATA_0_3) & 0xff;
++ dw_hdmi_qp_mod(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
++ }
++
++ i2c->is_segment = false;
++
++ return 0;
++}
++
++static int dw_hdmi_qp_i2c_write(struct dw_hdmi_qp *hdmi,
++ unsigned char *buf, unsigned int length)
++{
++ struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
++ int stat;
++
++ if (!i2c->is_regaddr) {
++ /* Use the first write byte as register address */
++ i2c->slave_reg = buf[0];
++ length--;
++ buf++;
++ i2c->is_regaddr = true;
++ }
++
++ while (length--) {
++ reinit_completion(&i2c->cmp);
++
++ dw_hdmi_qp_write(hdmi, *buf++, I2CM_INTERFACE_WRDATA_0_3);
++ dw_hdmi_qp_mod(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR,
++ I2CM_INTERFACE_CONTROL0);
++ dw_hdmi_qp_mod(hdmi, I2CM_FM_WRITE, I2CM_WR_MASK,
++ I2CM_INTERFACE_CONTROL0);
++
++ stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
++ if (!stat) {
++ dev_err(hdmi->dev, "i2c write time out!\n");
++ dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
++ return -EAGAIN;
++ }
++
++ /* Check for error condition on the bus */
++ if (i2c->stat & I2CM_NACK_RCVD_IRQ) {
++ dev_err(hdmi->dev, "i2c write nack!\n");
++ dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
++ return -EIO;
++ }
++
++ dw_hdmi_qp_mod(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
++ }
++
++ return 0;
++}
++
++static int dw_hdmi_qp_i2c_xfer(struct i2c_adapter *adap,
++ struct i2c_msg *msgs, int num)
++{
++ struct dw_hdmi_qp *hdmi = i2c_get_adapdata(adap);
++ struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
++ u8 addr = msgs[0].addr;
++ int i, ret = 0;
++
++ if (addr == DDC_CI_ADDR)
++ /*
++ * The internal I2C controller does not support the multi-byte
++ * read and write operations needed for DDC/CI.
++ * FIXME: Blacklist the DDC/CI address until we filter out
++ * unsupported I2C operations.
++ */
++ return -EOPNOTSUPP;
++
++ for (i = 0; i < num; i++) {
++ if (msgs[i].len == 0) {
++ dev_err(hdmi->dev,
++ "unsupported transfer %d/%d, no data\n",
++ i + 1, num);
++ return -EOPNOTSUPP;
++ }
++ }
++
++ guard(mutex)(&i2c->lock);
++
++ /* Unmute DONE and ERROR interrupts */
++ dw_hdmi_qp_mod(hdmi, I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N,
++ I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N,
++ MAINUNIT_1_INT_MASK_N);
++
++ /* Set slave device address taken from the first I2C message */
++ if (addr == DDC_SEGMENT_ADDR && msgs[0].len == 1)
++ addr = DDC_ADDR;
++
++ dw_hdmi_qp_mod(hdmi, addr << 5, I2CM_SLVADDR, I2CM_INTERFACE_CONTROL0);
++
++ /* Set slave device register address on transfer */
++ i2c->is_regaddr = false;
++
++ /* Set segment pointer for I2C extended read mode operation */
++ i2c->is_segment = false;
++
++ for (i = 0; i < num; i++) {
++ if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) {
++ i2c->is_segment = true;
++ dw_hdmi_qp_mod(hdmi, DDC_SEGMENT_ADDR, I2CM_SEG_ADDR,
++ I2CM_INTERFACE_CONTROL1);
++ dw_hdmi_qp_mod(hdmi, *msgs[i].buf << 7, I2CM_SEG_PTR,
++ I2CM_INTERFACE_CONTROL1);
++ } else {
++ if (msgs[i].flags & I2C_M_RD)
++ ret = dw_hdmi_qp_i2c_read(hdmi, msgs[i].buf,
++ msgs[i].len);
++ else
++ ret = dw_hdmi_qp_i2c_write(hdmi, msgs[i].buf,
++ msgs[i].len);
++ }
++ if (ret < 0)
++ break;
++ }
++
++ if (!ret)
++ ret = num;
++
++ /* Mute DONE and ERROR interrupts */
++ dw_hdmi_qp_mod(hdmi, 0, I2CM_OP_DONE_MASK_N | I2CM_NACK_RCVD_MASK_N,
++ MAINUNIT_1_INT_MASK_N);
++
++ return ret;
++}
++
++static u32 dw_hdmi_qp_i2c_func(struct i2c_adapter *adapter)
++{
++ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
++}
++
++static const struct i2c_algorithm dw_hdmi_qp_algorithm = {
++ .master_xfer = dw_hdmi_qp_i2c_xfer,
++ .functionality = dw_hdmi_qp_i2c_func,
++};
++
++static struct i2c_adapter *dw_hdmi_qp_i2c_adapter(struct dw_hdmi_qp *hdmi)
++{
++ struct dw_hdmi_qp_i2c *i2c;
++ struct i2c_adapter *adap;
++ int ret;
++
++ i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
++ if (!i2c)
++ return ERR_PTR(-ENOMEM);
++
++ mutex_init(&i2c->lock);
++ init_completion(&i2c->cmp);
++
++ adap = &i2c->adap;
++ adap->owner = THIS_MODULE;
++ adap->dev.parent = hdmi->dev;
++ adap->algo = &dw_hdmi_qp_algorithm;
++ strscpy(adap->name, "DesignWare HDMI QP", sizeof(adap->name));
++
++ i2c_set_adapdata(adap, hdmi);
++
++ ret = devm_i2c_add_adapter(hdmi->dev, adap);
++ if (ret) {
++ dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
++ devm_kfree(hdmi->dev, i2c);
++ return ERR_PTR(ret);
++ }
++
++ hdmi->i2c = i2c;
++ dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
++
++ return adap;
++}
++
++static int dw_hdmi_qp_config_avi_infoframe(struct dw_hdmi_qp *hdmi,
++ const u8 *buffer, size_t len)
++{
++ u32 val, i, j;
++
++ if (len != HDMI_INFOFRAME_SIZE(AVI)) {
++ dev_err(hdmi->dev, "failed to configure avi infoframe\n");
++ return -EINVAL;
++ }
++
++ /*
++ * DW HDMI QP IP uses a different byte format from standard AVI info
++ * frames, though generally the bits are in the correct bytes.
++ */
++ val = buffer[1] << 8 | buffer[2] << 16;
++ dw_hdmi_qp_write(hdmi, val, PKT_AVI_CONTENTS0);
++
++ for (i = 0; i < 4; i++) {
++ for (j = 0; j < 4; j++) {
++ if (i * 4 + j >= 14)
++ break;
++ if (!j)
++ val = buffer[i * 4 + j + 3];
++ val |= buffer[i * 4 + j + 3] << (8 * j);
++ }
++
++ dw_hdmi_qp_write(hdmi, val, PKT_AVI_CONTENTS1 + i * 4);
++ }
++
++ dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_FIELDRATE, PKTSCHED_PKT_CONFIG1);
++
++ dw_hdmi_qp_mod(hdmi, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN,
++ PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN);
++
++ return 0;
++}
++
++static int dw_hdmi_qp_config_drm_infoframe(struct dw_hdmi_qp *hdmi,
++ const u8 *buffer, size_t len)
++{
++ u32 val, i;
++
++ if (len != HDMI_INFOFRAME_SIZE(DRM)) {
++ dev_err(hdmi->dev, "failed to configure drm infoframe\n");
++ return -EINVAL;
++ }
++
++ dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN);
++
++ val = buffer[1] << 8 | buffer[2] << 16;
++ dw_hdmi_qp_write(hdmi, val, PKT_DRMI_CONTENTS0);
++
++ for (i = 0; i <= buffer[2]; i++) {
++ if (i % 4 == 0)
++ val = buffer[3 + i];
++ val |= buffer[3 + i] << ((i % 4) * 8);
++
++ if ((i % 4 == 3) || i == buffer[2])
++ dw_hdmi_qp_write(hdmi, val,
++ PKT_DRMI_CONTENTS1 + ((i / 4) * 4));
++ }
++
++ dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_FIELDRATE, PKTSCHED_PKT_CONFIG1);
++ dw_hdmi_qp_mod(hdmi, PKTSCHED_DRMI_TX_EN, PKTSCHED_DRMI_TX_EN,
++ PKTSCHED_PKT_EN);
++
++ return 0;
++}
++
++static int dw_hdmi_qp_bridge_atomic_check(struct drm_bridge *bridge,
++ struct drm_bridge_state *bridge_state,
++ struct drm_crtc_state *crtc_state,
++ struct drm_connector_state *conn_state)
++{
++ struct dw_hdmi_qp *hdmi = bridge->driver_private;
++ int ret;
++
++ ret = drm_atomic_helper_connector_hdmi_check(conn_state->connector,
++ conn_state->state);
++ if (ret)
++ dev_dbg(hdmi->dev, "%s failed: %d\n", __func__, ret);
++
++ return ret;
++}
++
++static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge,
++ struct drm_bridge_state *old_state)
++{
++ struct dw_hdmi_qp *hdmi = bridge->driver_private;
++ struct drm_atomic_state *state = old_state->base.state;
++ struct drm_connector_state *conn_state;
++ struct drm_connector *connector;
++ unsigned int op_mode;
++
++ connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
++ if (WARN_ON(!connector))
++ return;
++
++ conn_state = drm_atomic_get_new_connector_state(state, connector);
++ if (WARN_ON(!conn_state))
++ return;
++
++ if (connector->display_info.is_hdmi) {
++ dev_dbg(hdmi->dev, "%s mode=HDMI rate=%llu\n",
++ __func__, conn_state->hdmi.tmds_char_rate);
++ op_mode = 0;
++ } else {
++ dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__);
++ op_mode = OPMODE_DVI;
++ }
++
++ hdmi->phy.ops->init(hdmi, hdmi->phy.data);
++
++ dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
++ dw_hdmi_qp_mod(hdmi, op_mode, OPMODE_DVI, LINK_CONFIG0);
++
++ drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
++}
++
++static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge,
++ struct drm_bridge_state *old_state)
++{
++ struct dw_hdmi_qp *hdmi = bridge->driver_private;
++
++ hdmi->phy.ops->disable(hdmi, hdmi->phy.data);
++}
++
++static enum drm_connector_status
++dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge)
++{
++ struct dw_hdmi_qp *hdmi = bridge->driver_private;
++
++ return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
++}
++
++static const struct drm_edid *
++dw_hdmi_qp_bridge_edid_read(struct drm_bridge *bridge,
++ struct drm_connector *connector)
++{
++ struct dw_hdmi_qp *hdmi = bridge->driver_private;
++ const struct drm_edid *drm_edid;
++
++ drm_edid = drm_edid_read_ddc(connector, bridge->ddc);
++ if (!drm_edid)
++ dev_dbg(hdmi->dev, "failed to get edid\n");
++
++ return drm_edid;
++}
++
++static enum drm_mode_status
++dw_hdmi_qp_bridge_mode_valid(struct drm_bridge *bridge,
++ const struct drm_display_info *info,
++ const struct drm_display_mode *mode)
++{
++ struct dw_hdmi_qp *hdmi = bridge->driver_private;
++ unsigned long long rate;
++
++ rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB);
++ if (rate > HDMI14_MAX_TMDSCLK) {
++ dev_dbg(hdmi->dev, "Unsupported mode clock: %d\n", mode->clock);
++ return MODE_CLOCK_HIGH;
++ }
++
++ return MODE_OK;
++}
++
++static int dw_hdmi_qp_bridge_clear_infoframe(struct drm_bridge *bridge,
++ enum hdmi_infoframe_type type)
++{
++ struct dw_hdmi_qp *hdmi = bridge->driver_private;
++
++ switch (type) {
++ case HDMI_INFOFRAME_TYPE_AVI:
++ dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN,
++ PKTSCHED_PKT_EN);
++ break;
++
++ case HDMI_INFOFRAME_TYPE_DRM:
++ dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN);
++ break;
++
++ default:
++ dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type);
++ }
++
++ return 0;
++}
++
++static int dw_hdmi_qp_bridge_write_infoframe(struct drm_bridge *bridge,
++ enum hdmi_infoframe_type type,
++ const u8 *buffer, size_t len)
++{
++ struct dw_hdmi_qp *hdmi = bridge->driver_private;
++
++ dw_hdmi_qp_bridge_clear_infoframe(bridge, type);
++
++ switch (type) {
++ case HDMI_INFOFRAME_TYPE_AVI:
++ return dw_hdmi_qp_config_avi_infoframe(hdmi, buffer, len);
++
++ case HDMI_INFOFRAME_TYPE_DRM:
++ return dw_hdmi_qp_config_drm_infoframe(hdmi, buffer, len);
++
++ default:
++ dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type);
++ return 0;
++ }
++}
++
++static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
++ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
++ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
++ .atomic_reset = drm_atomic_helper_bridge_reset,
++ .atomic_check = dw_hdmi_qp_bridge_atomic_check,
++ .atomic_enable = dw_hdmi_qp_bridge_atomic_enable,
++ .atomic_disable = dw_hdmi_qp_bridge_atomic_disable,
++ .detect = dw_hdmi_qp_bridge_detect,
++ .edid_read = dw_hdmi_qp_bridge_edid_read,
++ .mode_valid = dw_hdmi_qp_bridge_mode_valid,
++ .hdmi_clear_infoframe = dw_hdmi_qp_bridge_clear_infoframe,
++ .hdmi_write_infoframe = dw_hdmi_qp_bridge_write_infoframe,
++};
++
++static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id)
++{
++ struct dw_hdmi_qp *hdmi = dev_id;
++ struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
++ u32 stat;
++
++ stat = dw_hdmi_qp_read(hdmi, MAINUNIT_1_INT_STATUS);
++
++ i2c->stat = stat & (I2CM_OP_DONE_IRQ | I2CM_READ_REQUEST_IRQ |
++ I2CM_NACK_RCVD_IRQ);
++
++ if (i2c->stat) {
++ dw_hdmi_qp_write(hdmi, i2c->stat, MAINUNIT_1_INT_CLEAR);
++ complete(&i2c->cmp);
++ }
++
++ if (stat)
++ return IRQ_HANDLED;
++
++ return IRQ_NONE;
++}
++
++static const struct regmap_config dw_hdmi_qp_regmap_config = {
++ .reg_bits = 32,
++ .val_bits = 32,
++ .reg_stride = 4,
++ .max_register = EARCRX_1_INT_FORCE,
++};
++
++static void dw_hdmi_qp_init_hw(struct dw_hdmi_qp *hdmi)
++{
++ dw_hdmi_qp_write(hdmi, 0, MAINUNIT_0_INT_MASK_N);
++ dw_hdmi_qp_write(hdmi, 0, MAINUNIT_1_INT_MASK_N);
++ dw_hdmi_qp_write(hdmi, 428571429, TIMER_BASE_CONFIG0);
++
++ /* Software reset */
++ dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
++
++ dw_hdmi_qp_write(hdmi, 0x085c085c, I2CM_FM_SCL_CONFIG0);
++
++ dw_hdmi_qp_mod(hdmi, 0, I2CM_FM_EN, I2CM_INTERFACE_CONTROL0);
++
++ /* Clear DONE and ERROR interrupts */
++ dw_hdmi_qp_write(hdmi, I2CM_OP_DONE_CLEAR | I2CM_NACK_RCVD_CLEAR,
++ MAINUNIT_1_INT_CLEAR);
++
++ if (hdmi->phy.ops->setup_hpd)
++ hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
++}
++
++struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
++ struct drm_encoder *encoder,
++ const struct dw_hdmi_qp_plat_data *plat_data)
++{
++ struct device *dev = &pdev->dev;
++ struct dw_hdmi_qp *hdmi;
++ void __iomem *regs;
++ int ret;
++
++ if (!plat_data->phy_ops || !plat_data->phy_ops->init ||
++ !plat_data->phy_ops->disable || !plat_data->phy_ops->read_hpd) {
++ dev_err(dev, "Missing platform PHY ops\n");
++ return ERR_PTR(-ENODEV);
++ }
++
++ hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
++ if (!hdmi)
++ return ERR_PTR(-ENOMEM);
++
++ hdmi->dev = dev;
++
++ regs = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(regs))
++ return ERR_CAST(regs);
++
++ hdmi->regm = devm_regmap_init_mmio(dev, regs, &dw_hdmi_qp_regmap_config);
++ if (IS_ERR(hdmi->regm)) {
++ dev_err(dev, "Failed to configure regmap\n");
++ return ERR_CAST(hdmi->regm);
++ }
++
++ hdmi->phy.ops = plat_data->phy_ops;
++ hdmi->phy.data = plat_data->phy_data;
++
++ dw_hdmi_qp_init_hw(hdmi);
++
++ ret = devm_request_threaded_irq(dev, plat_data->main_irq,
++ dw_hdmi_qp_main_hardirq, NULL,
++ IRQF_SHARED, dev_name(dev), hdmi);
++ if (ret)
++ return ERR_PTR(ret);
++
++ hdmi->bridge.driver_private = hdmi;
++ hdmi->bridge.funcs = &dw_hdmi_qp_bridge_funcs;
++ hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT |
++ DRM_BRIDGE_OP_EDID |
++ DRM_BRIDGE_OP_HDMI |
++ DRM_BRIDGE_OP_HPD;
++ hdmi->bridge.of_node = pdev->dev.of_node;
++ hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
++ hdmi->bridge.vendor = "Synopsys";
++ hdmi->bridge.product = "DW HDMI QP TX";
++
++ hdmi->bridge.ddc = dw_hdmi_qp_i2c_adapter(hdmi);
++ if (IS_ERR(hdmi->bridge.ddc))
++ return ERR_CAST(hdmi->bridge.ddc);
++
++ ret = devm_drm_bridge_add(dev, &hdmi->bridge);
++ if (ret)
++ return ERR_PTR(ret);
++
++ ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL,
++ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
++ if (ret)
++ return ERR_PTR(ret);
++
++ return hdmi;
++}
++EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind);
++
++void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi)
++{
++ dw_hdmi_qp_init_hw(hdmi);
++}
++EXPORT_SYMBOL_GPL(dw_hdmi_qp_resume);
++
++MODULE_AUTHOR("Algea Cao ");
++MODULE_AUTHOR("Cristian Ciocaltea ");
++MODULE_DESCRIPTION("DW HDMI QP transmitter library");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
+@@ -0,0 +1,834 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * Copyright (C) Rockchip Electronics Co.Ltd
++ * Author:
++ * Algea Cao
++ */
++#ifndef __DW_HDMI_QP_H__
++#define __DW_HDMI_QP_H__
++
++#include
++
++/* Main Unit Registers */
++#define CORE_ID 0x0
++#define VER_NUMBER 0x4
++#define VER_TYPE 0x8
++#define CONFIG_REG 0xc
++#define CONFIG_CEC BIT(28)
++#define CONFIG_AUD_UD BIT(23)
++#define CORE_TIMESTAMP_HHMM 0x14
++#define CORE_TIMESTAMP_MMDD 0x18
++#define CORE_TIMESTAMP_YYYY 0x1c
++/* Reset Manager Registers */
++#define GLOBAL_SWRESET_REQUEST 0x40
++#define EARCRX_CMDC_SWINIT_P BIT(27)
++#define AVP_DATAPATH_PACKET_AUDIO_SWINIT_P BIT(10)
++#define GLOBAL_SWDISABLE 0x44
++#define CEC_SWDISABLE BIT(17)
++#define AVP_DATAPATH_PACKET_AUDIO_SWDISABLE BIT(10)
++#define AVP_DATAPATH_VIDEO_SWDISABLE BIT(6)
++#define RESET_MANAGER_CONFIG0 0x48
++#define RESET_MANAGER_STATUS0 0x50
++#define RESET_MANAGER_STATUS1 0x54
++#define RESET_MANAGER_STATUS2 0x58
++/* Timer Base Registers */
++#define TIMER_BASE_CONFIG0 0x80
++#define TIMER_BASE_STATUS0 0x84
++/* CMU Registers */
++#define CMU_CONFIG0 0xa0
++#define CMU_CONFIG1 0xa4
++#define CMU_CONFIG2 0xa8
++#define CMU_CONFIG3 0xac
++#define CMU_STATUS 0xb0
++#define DISPLAY_CLK_MONITOR 0x3f
++#define DISPLAY_CLK_LOCKED 0X15
++#define EARC_BPCLK_OFF BIT(9)
++#define AUDCLK_OFF BIT(7)
++#define LINKQPCLK_OFF BIT(5)
++#define VIDQPCLK_OFF BIT(3)
++#define IPI_CLK_OFF BIT(1)
++#define CMU_IPI_CLK_FREQ 0xb4
++#define CMU_VIDQPCLK_FREQ 0xb8
++#define CMU_LINKQPCLK_FREQ 0xbc
++#define CMU_AUDQPCLK_FREQ 0xc0
++#define CMU_EARC_BPCLK_FREQ 0xc4
++/* I2CM Registers */
++#define I2CM_SM_SCL_CONFIG0 0xe0
++#define I2CM_FM_SCL_CONFIG0 0xe4
++#define I2CM_CONFIG0 0xe8
++#define I2CM_CONTROL0 0xec
++#define I2CM_STATUS0 0xf0
++#define I2CM_INTERFACE_CONTROL0 0xf4
++#define I2CM_ADDR 0xff000
++#define I2CM_SLVADDR 0xfe0
++#define I2CM_WR_MASK 0x1e
++#define I2CM_EXT_READ BIT(4)
++#define I2CM_SHORT_READ BIT(3)
++#define I2CM_FM_READ BIT(2)
++#define I2CM_FM_WRITE BIT(1)
++#define I2CM_FM_EN BIT(0)
++#define I2CM_INTERFACE_CONTROL1 0xf8
++#define I2CM_SEG_PTR 0x7f80
++#define I2CM_SEG_ADDR 0x7f
++#define I2CM_INTERFACE_WRDATA_0_3 0xfc
++#define I2CM_INTERFACE_WRDATA_4_7 0x100
++#define I2CM_INTERFACE_WRDATA_8_11 0x104
++#define I2CM_INTERFACE_WRDATA_12_15 0x108
++#define I2CM_INTERFACE_RDDATA_0_3 0x10c
++#define I2CM_INTERFACE_RDDATA_4_7 0x110
++#define I2CM_INTERFACE_RDDATA_8_11 0x114
++#define I2CM_INTERFACE_RDDATA_12_15 0x118
++/* SCDC Registers */
++#define SCDC_CONFIG0 0x140
++#define SCDC_I2C_FM_EN BIT(12)
++#define SCDC_UPD_FLAGS_AUTO_CLR BIT(6)
++#define SCDC_UPD_FLAGS_POLL_EN BIT(4)
++#define SCDC_CONTROL0 0x148
++#define SCDC_STATUS0 0x150
++#define STATUS_UPDATE BIT(0)
++#define FRL_START BIT(4)
++#define FLT_UPDATE BIT(5)
++/* FLT Registers */
++#define FLT_CONFIG0 0x160
++#define FLT_CONFIG1 0x164
++#define FLT_CONFIG2 0x168
++#define FLT_CONTROL0 0x170
++/* Main Unit 2 Registers */
++#define MAINUNIT_STATUS0 0x180
++/* Video Interface Registers */
++#define VIDEO_INTERFACE_CONFIG0 0x800
++#define VIDEO_INTERFACE_CONFIG1 0x804
++#define VIDEO_INTERFACE_CONFIG2 0x808
++#define VIDEO_INTERFACE_CONTROL0 0x80c
++#define VIDEO_INTERFACE_STATUS0 0x814
++/* Video Packing Registers */
++#define VIDEO_PACKING_CONFIG0 0x81c
++/* Audio Interface Registers */
++#define AUDIO_INTERFACE_CONFIG0 0x820
++#define AUD_IF_SEL_MSK 0x3
++#define AUD_IF_SPDIF 0x2
++#define AUD_IF_I2S 0x1
++#define AUD_IF_PAI 0x0
++#define AUD_FIFO_INIT_ON_OVF_MSK BIT(2)
++#define AUD_FIFO_INIT_ON_OVF_EN BIT(2)
++#define I2S_LINES_EN_MSK GENMASK(7, 4)
++#define I2S_LINES_EN(x) BIT((x) + 4)
++#define I2S_BPCUV_RCV_MSK BIT(12)
++#define I2S_BPCUV_RCV_EN BIT(12)
++#define I2S_BPCUV_RCV_DIS 0
++#define SPDIF_LINES_EN GENMASK(19, 16)
++#define AUD_FORMAT_MSK GENMASK(26, 24)
++#define AUD_3DOBA (0x7 << 24)
++#define AUD_3DASP (0x6 << 24)
++#define AUD_MSOBA (0x5 << 24)
++#define AUD_MSASP (0x4 << 24)
++#define AUD_HBR (0x3 << 24)
++#define AUD_DST (0x2 << 24)
++#define AUD_OBA (0x1 << 24)
++#define AUD_ASP (0x0 << 24)
++#define AUDIO_INTERFACE_CONFIG1 0x824
++#define AUDIO_INTERFACE_CONTROL0 0x82c
++#define AUDIO_FIFO_CLR_P BIT(0)
++#define AUDIO_INTERFACE_STATUS0 0x834
++/* Frame Composer Registers */
++#define FRAME_COMPOSER_CONFIG0 0x840
++#define FRAME_COMPOSER_CONFIG1 0x844
++#define FRAME_COMPOSER_CONFIG2 0x848
++#define FRAME_COMPOSER_CONFIG3 0x84c
++#define FRAME_COMPOSER_CONFIG4 0x850
++#define FRAME_COMPOSER_CONFIG5 0x854
++#define FRAME_COMPOSER_CONFIG6 0x858
++#define FRAME_COMPOSER_CONFIG7 0x85c
++#define FRAME_COMPOSER_CONFIG8 0x860
++#define FRAME_COMPOSER_CONFIG9 0x864
++#define FRAME_COMPOSER_CONTROL0 0x86c
++/* Video Monitor Registers */
++#define VIDEO_MONITOR_CONFIG0 0x880
++#define VIDEO_MONITOR_STATUS0 0x884
++#define VIDEO_MONITOR_STATUS1 0x888
++#define VIDEO_MONITOR_STATUS2 0x88c
++#define VIDEO_MONITOR_STATUS3 0x890
++#define VIDEO_MONITOR_STATUS4 0x894
++#define VIDEO_MONITOR_STATUS5 0x898
++#define VIDEO_MONITOR_STATUS6 0x89c
++/* HDCP2 Logic Registers */
++#define HDCP2LOGIC_CONFIG0 0x8e0
++#define HDCP2_BYPASS BIT(0)
++#define HDCP2LOGIC_ESM_GPIO_IN 0x8e4
++#define HDCP2LOGIC_ESM_GPIO_OUT 0x8e8
++/* HDCP14 Registers */
++#define HDCP14_CONFIG0 0x900
++#define HDCP14_CONFIG1 0x904
++#define HDCP14_CONFIG2 0x908
++#define HDCP14_CONFIG3 0x90c
++#define HDCP14_KEY_SEED 0x914
++#define HDCP14_KEY_H 0x918
++#define HDCP14_KEY_L 0x91c
++#define HDCP14_KEY_STATUS 0x920
++#define HDCP14_AKSV_H 0x924
++#define HDCP14_AKSV_L 0x928
++#define HDCP14_AN_H 0x92c
++#define HDCP14_AN_L 0x930
++#define HDCP14_STATUS0 0x934
++#define HDCP14_STATUS1 0x938
++/* Scrambler Registers */
++#define SCRAMB_CONFIG0 0x960
++/* Video Configuration Registers */
++#define LINK_CONFIG0 0x968
++#define OPMODE_FRL_4LANES BIT(8)
++#define OPMODE_DVI BIT(4)
++#define OPMODE_FRL BIT(0)
++/* TMDS FIFO Registers */
++#define TMDS_FIFO_CONFIG0 0x970
++#define TMDS_FIFO_CONTROL0 0x974
++/* FRL RSFEC Registers */
++#define FRL_RSFEC_CONFIG0 0xa20
++#define FRL_RSFEC_STATUS0 0xa30
++/* FRL Packetizer Registers */
++#define FRL_PKTZ_CONFIG0 0xa40
++#define FRL_PKTZ_CONTROL0 0xa44
++#define FRL_PKTZ_CONTROL1 0xa50
++#define FRL_PKTZ_STATUS1 0xa54
++/* Packet Scheduler Registers */
++#define PKTSCHED_CONFIG0 0xa80
++#define PKTSCHED_PRQUEUE0_CONFIG0 0xa84
++#define PKTSCHED_PRQUEUE1_CONFIG0 0xa88
++#define PKTSCHED_PRQUEUE2_CONFIG0 0xa8c
++#define PKTSCHED_PRQUEUE2_CONFIG1 0xa90
++#define PKTSCHED_PRQUEUE2_CONFIG2 0xa94
++#define PKTSCHED_PKT_CONFIG0 0xa98
++#define PKTSCHED_PKT_CONFIG1 0xa9c
++#define PKTSCHED_DRMI_FIELDRATE BIT(13)
++#define PKTSCHED_AVI_FIELDRATE BIT(12)
++#define PKTSCHED_PKT_CONFIG2 0xaa0
++#define PKTSCHED_PKT_CONFIG3 0xaa4
++#define PKTSCHED_PKT_EN 0xaa8
++#define PKTSCHED_DRMI_TX_EN BIT(17)
++#define PKTSCHED_AUDI_TX_EN BIT(15)
++#define PKTSCHED_AVI_TX_EN BIT(13)
++#define PKTSCHED_EMP_CVTEM_TX_EN BIT(10)
++#define PKTSCHED_AMD_TX_EN BIT(8)
++#define PKTSCHED_GCP_TX_EN BIT(3)
++#define PKTSCHED_AUDS_TX_EN BIT(2)
++#define PKTSCHED_ACR_TX_EN BIT(1)
++#define PKTSCHED_NULL_TX_EN BIT(0)
++#define PKTSCHED_PKT_CONTROL0 0xaac
++#define PKTSCHED_PKT_SEND 0xab0
++#define PKTSCHED_PKT_STATUS0 0xab4
++#define PKTSCHED_PKT_STATUS1 0xab8
++#define PKT_NULL_CONTENTS0 0xb00
++#define PKT_NULL_CONTENTS1 0xb04
++#define PKT_NULL_CONTENTS2 0xb08
++#define PKT_NULL_CONTENTS3 0xb0c
++#define PKT_NULL_CONTENTS4 0xb10
++#define PKT_NULL_CONTENTS5 0xb14
++#define PKT_NULL_CONTENTS6 0xb18
++#define PKT_NULL_CONTENTS7 0xb1c
++#define PKT_ACP_CONTENTS0 0xb20
++#define PKT_ACP_CONTENTS1 0xb24
++#define PKT_ACP_CONTENTS2 0xb28
++#define PKT_ACP_CONTENTS3 0xb2c
++#define PKT_ACP_CONTENTS4 0xb30
++#define PKT_ACP_CONTENTS5 0xb34
++#define PKT_ACP_CONTENTS6 0xb38
++#define PKT_ACP_CONTENTS7 0xb3c
++#define PKT_ISRC1_CONTENTS0 0xb40
++#define PKT_ISRC1_CONTENTS1 0xb44
++#define PKT_ISRC1_CONTENTS2 0xb48
++#define PKT_ISRC1_CONTENTS3 0xb4c
++#define PKT_ISRC1_CONTENTS4 0xb50
++#define PKT_ISRC1_CONTENTS5 0xb54
++#define PKT_ISRC1_CONTENTS6 0xb58
++#define PKT_ISRC1_CONTENTS7 0xb5c
++#define PKT_ISRC2_CONTENTS0 0xb60
++#define PKT_ISRC2_CONTENTS1 0xb64
++#define PKT_ISRC2_CONTENTS2 0xb68
++#define PKT_ISRC2_CONTENTS3 0xb6c
++#define PKT_ISRC2_CONTENTS4 0xb70
++#define PKT_ISRC2_CONTENTS5 0xb74
++#define PKT_ISRC2_CONTENTS6 0xb78
++#define PKT_ISRC2_CONTENTS7 0xb7c
++#define PKT_GMD_CONTENTS0 0xb80
++#define PKT_GMD_CONTENTS1 0xb84
++#define PKT_GMD_CONTENTS2 0xb88
++#define PKT_GMD_CONTENTS3 0xb8c
++#define PKT_GMD_CONTENTS4 0xb90
++#define PKT_GMD_CONTENTS5 0xb94
++#define PKT_GMD_CONTENTS6 0xb98
++#define PKT_GMD_CONTENTS7 0xb9c
++#define PKT_AMD_CONTENTS0 0xba0
++#define PKT_AMD_CONTENTS1 0xba4
++#define PKT_AMD_CONTENTS2 0xba8
++#define PKT_AMD_CONTENTS3 0xbac
++#define PKT_AMD_CONTENTS4 0xbb0
++#define PKT_AMD_CONTENTS5 0xbb4
++#define PKT_AMD_CONTENTS6 0xbb8
++#define PKT_AMD_CONTENTS7 0xbbc
++#define PKT_VSI_CONTENTS0 0xbc0
++#define PKT_VSI_CONTENTS1 0xbc4
++#define PKT_VSI_CONTENTS2 0xbc8
++#define PKT_VSI_CONTENTS3 0xbcc
++#define PKT_VSI_CONTENTS4 0xbd0
++#define PKT_VSI_CONTENTS5 0xbd4
++#define PKT_VSI_CONTENTS6 0xbd8
++#define PKT_VSI_CONTENTS7 0xbdc
++#define PKT_AVI_CONTENTS0 0xbe0
++#define HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT BIT(4)
++#define HDMI_FC_AVICONF0_BAR_DATA_VERT_BAR 0x04
++#define HDMI_FC_AVICONF0_BAR_DATA_HORIZ_BAR 0x08
++#define HDMI_FC_AVICONF2_IT_CONTENT_VALID 0x80
++#define PKT_AVI_CONTENTS1 0xbe4
++#define PKT_AVI_CONTENTS2 0xbe8
++#define PKT_AVI_CONTENTS3 0xbec
++#define PKT_AVI_CONTENTS4 0xbf0
++#define PKT_AVI_CONTENTS5 0xbf4
++#define PKT_AVI_CONTENTS6 0xbf8
++#define PKT_AVI_CONTENTS7 0xbfc
++#define PKT_SPDI_CONTENTS0 0xc00
++#define PKT_SPDI_CONTENTS1 0xc04
++#define PKT_SPDI_CONTENTS2 0xc08
++#define PKT_SPDI_CONTENTS3 0xc0c
++#define PKT_SPDI_CONTENTS4 0xc10
++#define PKT_SPDI_CONTENTS5 0xc14
++#define PKT_SPDI_CONTENTS6 0xc18
++#define PKT_SPDI_CONTENTS7 0xc1c
++#define PKT_AUDI_CONTENTS0 0xc20
++#define PKT_AUDI_CONTENTS1 0xc24
++#define PKT_AUDI_CONTENTS2 0xc28
++#define PKT_AUDI_CONTENTS3 0xc2c
++#define PKT_AUDI_CONTENTS4 0xc30
++#define PKT_AUDI_CONTENTS5 0xc34
++#define PKT_AUDI_CONTENTS6 0xc38
++#define PKT_AUDI_CONTENTS7 0xc3c
++#define PKT_NVI_CONTENTS0 0xc40
++#define PKT_NVI_CONTENTS1 0xc44
++#define PKT_NVI_CONTENTS2 0xc48
++#define PKT_NVI_CONTENTS3 0xc4c
++#define PKT_NVI_CONTENTS4 0xc50
++#define PKT_NVI_CONTENTS5 0xc54
++#define PKT_NVI_CONTENTS6 0xc58
++#define PKT_NVI_CONTENTS7 0xc5c
++#define PKT_DRMI_CONTENTS0 0xc60
++#define PKT_DRMI_CONTENTS1 0xc64
++#define PKT_DRMI_CONTENTS2 0xc68
++#define PKT_DRMI_CONTENTS3 0xc6c
++#define PKT_DRMI_CONTENTS4 0xc70
++#define PKT_DRMI_CONTENTS5 0xc74
++#define PKT_DRMI_CONTENTS6 0xc78
++#define PKT_DRMI_CONTENTS7 0xc7c
++#define PKT_GHDMI1_CONTENTS0 0xc80
++#define PKT_GHDMI1_CONTENTS1 0xc84
++#define PKT_GHDMI1_CONTENTS2 0xc88
++#define PKT_GHDMI1_CONTENTS3 0xc8c
++#define PKT_GHDMI1_CONTENTS4 0xc90
++#define PKT_GHDMI1_CONTENTS5 0xc94
++#define PKT_GHDMI1_CONTENTS6 0xc98
++#define PKT_GHDMI1_CONTENTS7 0xc9c
++#define PKT_GHDMI2_CONTENTS0 0xca0
++#define PKT_GHDMI2_CONTENTS1 0xca4
++#define PKT_GHDMI2_CONTENTS2 0xca8
++#define PKT_GHDMI2_CONTENTS3 0xcac
++#define PKT_GHDMI2_CONTENTS4 0xcb0
++#define PKT_GHDMI2_CONTENTS5 0xcb4
++#define PKT_GHDMI2_CONTENTS6 0xcb8
++#define PKT_GHDMI2_CONTENTS7 0xcbc
++/* EMP Packetizer Registers */
++#define PKT_EMP_CONFIG0 0xce0
++#define PKT_EMP_CONTROL0 0xcec
++#define PKT_EMP_CONTROL1 0xcf0
++#define PKT_EMP_CONTROL2 0xcf4
++#define PKT_EMP_VTEM_CONTENTS0 0xd00
++#define PKT_EMP_VTEM_CONTENTS1 0xd04
++#define PKT_EMP_VTEM_CONTENTS2 0xd08
++#define PKT_EMP_VTEM_CONTENTS3 0xd0c
++#define PKT_EMP_VTEM_CONTENTS4 0xd10
++#define PKT_EMP_VTEM_CONTENTS5 0xd14
++#define PKT_EMP_VTEM_CONTENTS6 0xd18
++#define PKT_EMP_VTEM_CONTENTS7 0xd1c
++#define PKT0_EMP_CVTEM_CONTENTS0 0xd20
++#define PKT0_EMP_CVTEM_CONTENTS1 0xd24
++#define PKT0_EMP_CVTEM_CONTENTS2 0xd28
++#define PKT0_EMP_CVTEM_CONTENTS3 0xd2c
++#define PKT0_EMP_CVTEM_CONTENTS4 0xd30
++#define PKT0_EMP_CVTEM_CONTENTS5 0xd34
++#define PKT0_EMP_CVTEM_CONTENTS6 0xd38
++#define PKT0_EMP_CVTEM_CONTENTS7 0xd3c
++#define PKT1_EMP_CVTEM_CONTENTS0 0xd40
++#define PKT1_EMP_CVTEM_CONTENTS1 0xd44
++#define PKT1_EMP_CVTEM_CONTENTS2 0xd48
++#define PKT1_EMP_CVTEM_CONTENTS3 0xd4c
++#define PKT1_EMP_CVTEM_CONTENTS4 0xd50
++#define PKT1_EMP_CVTEM_CONTENTS5 0xd54
++#define PKT1_EMP_CVTEM_CONTENTS6 0xd58
++#define PKT1_EMP_CVTEM_CONTENTS7 0xd5c
++#define PKT2_EMP_CVTEM_CONTENTS0 0xd60
++#define PKT2_EMP_CVTEM_CONTENTS1 0xd64
++#define PKT2_EMP_CVTEM_CONTENTS2 0xd68
++#define PKT2_EMP_CVTEM_CONTENTS3 0xd6c
++#define PKT2_EMP_CVTEM_CONTENTS4 0xd70
++#define PKT2_EMP_CVTEM_CONTENTS5 0xd74
++#define PKT2_EMP_CVTEM_CONTENTS6 0xd78
++#define PKT2_EMP_CVTEM_CONTENTS7 0xd7c
++#define PKT3_EMP_CVTEM_CONTENTS0 0xd80
++#define PKT3_EMP_CVTEM_CONTENTS1 0xd84
++#define PKT3_EMP_CVTEM_CONTENTS2 0xd88
++#define PKT3_EMP_CVTEM_CONTENTS3 0xd8c
++#define PKT3_EMP_CVTEM_CONTENTS4 0xd90
++#define PKT3_EMP_CVTEM_CONTENTS5 0xd94
++#define PKT3_EMP_CVTEM_CONTENTS6 0xd98
++#define PKT3_EMP_CVTEM_CONTENTS7 0xd9c
++#define PKT4_EMP_CVTEM_CONTENTS0 0xda0
++#define PKT4_EMP_CVTEM_CONTENTS1 0xda4
++#define PKT4_EMP_CVTEM_CONTENTS2 0xda8
++#define PKT4_EMP_CVTEM_CONTENTS3 0xdac
++#define PKT4_EMP_CVTEM_CONTENTS4 0xdb0
++#define PKT4_EMP_CVTEM_CONTENTS5 0xdb4
++#define PKT4_EMP_CVTEM_CONTENTS6 0xdb8
++#define PKT4_EMP_CVTEM_CONTENTS7 0xdbc
++#define PKT5_EMP_CVTEM_CONTENTS0 0xdc0
++#define PKT5_EMP_CVTEM_CONTENTS1 0xdc4
++#define PKT5_EMP_CVTEM_CONTENTS2 0xdc8
++#define PKT5_EMP_CVTEM_CONTENTS3 0xdcc
++#define PKT5_EMP_CVTEM_CONTENTS4 0xdd0
++#define PKT5_EMP_CVTEM_CONTENTS5 0xdd4
++#define PKT5_EMP_CVTEM_CONTENTS6 0xdd8
++#define PKT5_EMP_CVTEM_CONTENTS7 0xddc
++/* Audio Packetizer Registers */
++#define AUDPKT_CONTROL0 0xe20
++#define AUDPKT_PBIT_FORCE_EN_MASK BIT(12)
++#define AUDPKT_PBIT_FORCE_EN BIT(12)
++#define AUDPKT_CHSTATUS_OVR_EN_MASK BIT(0)
++#define AUDPKT_CHSTATUS_OVR_EN BIT(0)
++#define AUDPKT_CONTROL1 0xe24
++#define AUDPKT_ACR_CONTROL0 0xe40
++#define AUDPKT_ACR_N_VALUE 0xfffff
++#define AUDPKT_ACR_CONTROL1 0xe44
++#define AUDPKT_ACR_CTS_OVR_VAL_MSK GENMASK(23, 4)
++#define AUDPKT_ACR_CTS_OVR_VAL(x) ((x) << 4)
++#define AUDPKT_ACR_CTS_OVR_EN_MSK BIT(1)
++#define AUDPKT_ACR_CTS_OVR_EN BIT(1)
++#define AUDPKT_ACR_STATUS0 0xe4c
++#define AUDPKT_CHSTATUS_OVR0 0xe60
++#define AUDPKT_CHSTATUS_OVR1 0xe64
++/* IEC60958 Byte 3: Sampleing frenuency Bits 24 to 27 */
++#define AUDPKT_CHSTATUS_SR_MASK GENMASK(3, 0)
++#define AUDPKT_CHSTATUS_SR_22050 0x4
++#define AUDPKT_CHSTATUS_SR_24000 0x6
++#define AUDPKT_CHSTATUS_SR_32000 0x3
++#define AUDPKT_CHSTATUS_SR_44100 0x0
++#define AUDPKT_CHSTATUS_SR_48000 0x2
++#define AUDPKT_CHSTATUS_SR_88200 0x8
++#define AUDPKT_CHSTATUS_SR_96000 0xa
++#define AUDPKT_CHSTATUS_SR_176400 0xc
++#define AUDPKT_CHSTATUS_SR_192000 0xe
++#define AUDPKT_CHSTATUS_SR_768000 0x9
++#define AUDPKT_CHSTATUS_SR_NOT_INDICATED 0x1
++/* IEC60958 Byte 4: Original Sampleing frenuency Bits 36 to 39 */
++#define AUDPKT_CHSTATUS_0SR_MASK GENMASK(15, 12)
++#define AUDPKT_CHSTATUS_OSR_8000 0x6
++#define AUDPKT_CHSTATUS_OSR_11025 0xa
++#define AUDPKT_CHSTATUS_OSR_12000 0x2
++#define AUDPKT_CHSTATUS_OSR_16000 0x8
++#define AUDPKT_CHSTATUS_OSR_22050 0xb
++#define AUDPKT_CHSTATUS_OSR_24000 0x9
++#define AUDPKT_CHSTATUS_OSR_32000 0xc
++#define AUDPKT_CHSTATUS_OSR_44100 0xf
++#define AUDPKT_CHSTATUS_OSR_48000 0xd
++#define AUDPKT_CHSTATUS_OSR_88200 0x7
++#define AUDPKT_CHSTATUS_OSR_96000 0x5
++#define AUDPKT_CHSTATUS_OSR_176400 0x3
++#define AUDPKT_CHSTATUS_OSR_192000 0x1
++#define AUDPKT_CHSTATUS_OSR_NOT_INDICATED 0x0
++#define AUDPKT_CHSTATUS_OVR2 0xe68
++#define AUDPKT_CHSTATUS_OVR3 0xe6c
++#define AUDPKT_CHSTATUS_OVR4 0xe70
++#define AUDPKT_CHSTATUS_OVR5 0xe74
++#define AUDPKT_CHSTATUS_OVR6 0xe78
++#define AUDPKT_CHSTATUS_OVR7 0xe7c
++#define AUDPKT_CHSTATUS_OVR8 0xe80
++#define AUDPKT_CHSTATUS_OVR9 0xe84
++#define AUDPKT_CHSTATUS_OVR10 0xe88
++#define AUDPKT_CHSTATUS_OVR11 0xe8c
++#define AUDPKT_CHSTATUS_OVR12 0xe90
++#define AUDPKT_CHSTATUS_OVR13 0xe94
++#define AUDPKT_CHSTATUS_OVR14 0xe98
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC0 0xea0
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC1 0xea4
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC2 0xea8
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC3 0xeac
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC4 0xeb0
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC5 0xeb4
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC6 0xeb8
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC7 0xebc
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC8 0xec0
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC9 0xec4
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC10 0xec8
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC11 0xecc
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC12 0xed0
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC13 0xed4
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC14 0xed8
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC15 0xedc
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC16 0xee0
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC17 0xee4
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC18 0xee8
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC19 0xeec
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC20 0xef0
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC21 0xef4
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC22 0xef8
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC23 0xefc
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC24 0xf00
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC25 0xf04
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC26 0xf08
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC27 0xf0c
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC28 0xf10
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC29 0xf14
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC30 0xf18
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC31 0xf1c
++#define AUDPKT_USRDATA_OVR_MSG_GENERIC32 0xf20
++#define AUDPKT_VBIT_OVR0 0xf24
++/* CEC Registers */
++#define CEC_TX_CONTROL 0x1000
++#define CEC_STATUS 0x1004
++#define CEC_CONFIG 0x1008
++#define CEC_ADDR 0x100c
++#define CEC_TX_COUNT 0x1020
++#define CEC_TX_DATA3_0 0x1024
++#define CEC_TX_DATA7_4 0x1028
++#define CEC_TX_DATA11_8 0x102c
++#define CEC_TX_DATA15_12 0x1030
++#define CEC_RX_COUNT_STATUS 0x1040
++#define CEC_RX_DATA3_0 0x1044
++#define CEC_RX_DATA7_4 0x1048
++#define CEC_RX_DATA11_8 0x104c
++#define CEC_RX_DATA15_12 0x1050
++#define CEC_LOCK_CONTROL 0x1054
++#define CEC_RXQUAL_BITTIME_CONFIG 0x1060
++#define CEC_RX_BITTIME_CONFIG 0x1064
++#define CEC_TX_BITTIME_CONFIG 0x1068
++/* eARC RX CMDC Registers */
++#define EARCRX_CMDC_CONFIG0 0x1800
++#define EARCRX_XACTREAD_STOP_CFG BIT(26)
++#define EARCRX_XACTREAD_RETRY_CFG BIT(25)
++#define EARCRX_CMDC_DSCVR_EARCVALID0_TO_DISC1 BIT(24)
++#define EARCRX_CMDC_XACT_RESTART_EN BIT(18)
++#define EARCRX_CMDC_CONFIG1 0x1804
++#define EARCRX_CMDC_CONTROL 0x1808
++#define EARCRX_CMDC_HEARTBEAT_LOSS_EN BIT(4)
++#define EARCRX_CMDC_DISCOVERY_EN BIT(3)
++#define EARCRX_CONNECTOR_HPD BIT(1)
++#define EARCRX_CMDC_WHITELIST0_CONFIG 0x180c
++#define EARCRX_CMDC_WHITELIST1_CONFIG 0x1810
++#define EARCRX_CMDC_WHITELIST2_CONFIG 0x1814
++#define EARCRX_CMDC_WHITELIST3_CONFIG 0x1818
++#define EARCRX_CMDC_STATUS 0x181c
++#define EARCRX_CMDC_XACT_INFO 0x1820
++#define EARCRX_CMDC_XACT_ACTION 0x1824
++#define EARCRX_CMDC_HEARTBEAT_RXSTAT_SE 0x1828
++#define EARCRX_CMDC_HEARTBEAT_STATUS 0x182c
++#define EARCRX_CMDC_XACT_WR0 0x1840
++#define EARCRX_CMDC_XACT_WR1 0x1844
++#define EARCRX_CMDC_XACT_WR2 0x1848
++#define EARCRX_CMDC_XACT_WR3 0x184c
++#define EARCRX_CMDC_XACT_WR4 0x1850
++#define EARCRX_CMDC_XACT_WR5 0x1854
++#define EARCRX_CMDC_XACT_WR6 0x1858
++#define EARCRX_CMDC_XACT_WR7 0x185c
++#define EARCRX_CMDC_XACT_WR8 0x1860
++#define EARCRX_CMDC_XACT_WR9 0x1864
++#define EARCRX_CMDC_XACT_WR10 0x1868
++#define EARCRX_CMDC_XACT_WR11 0x186c
++#define EARCRX_CMDC_XACT_WR12 0x1870
++#define EARCRX_CMDC_XACT_WR13 0x1874
++#define EARCRX_CMDC_XACT_WR14 0x1878
++#define EARCRX_CMDC_XACT_WR15 0x187c
++#define EARCRX_CMDC_XACT_WR16 0x1880
++#define EARCRX_CMDC_XACT_WR17 0x1884
++#define EARCRX_CMDC_XACT_WR18 0x1888
++#define EARCRX_CMDC_XACT_WR19 0x188c
++#define EARCRX_CMDC_XACT_WR20 0x1890
++#define EARCRX_CMDC_XACT_WR21 0x1894
++#define EARCRX_CMDC_XACT_WR22 0x1898
++#define EARCRX_CMDC_XACT_WR23 0x189c
++#define EARCRX_CMDC_XACT_WR24 0x18a0
++#define EARCRX_CMDC_XACT_WR25 0x18a4
++#define EARCRX_CMDC_XACT_WR26 0x18a8
++#define EARCRX_CMDC_XACT_WR27 0x18ac
++#define EARCRX_CMDC_XACT_WR28 0x18b0
++#define EARCRX_CMDC_XACT_WR29 0x18b4
++#define EARCRX_CMDC_XACT_WR30 0x18b8
++#define EARCRX_CMDC_XACT_WR31 0x18bc
++#define EARCRX_CMDC_XACT_WR32 0x18c0
++#define EARCRX_CMDC_XACT_WR33 0x18c4
++#define EARCRX_CMDC_XACT_WR34 0x18c8
++#define EARCRX_CMDC_XACT_WR35 0x18cc
++#define EARCRX_CMDC_XACT_WR36 0x18d0
++#define EARCRX_CMDC_XACT_WR37 0x18d4
++#define EARCRX_CMDC_XACT_WR38 0x18d8
++#define EARCRX_CMDC_XACT_WR39 0x18dc
++#define EARCRX_CMDC_XACT_WR40 0x18e0
++#define EARCRX_CMDC_XACT_WR41 0x18e4
++#define EARCRX_CMDC_XACT_WR42 0x18e8
++#define EARCRX_CMDC_XACT_WR43 0x18ec
++#define EARCRX_CMDC_XACT_WR44 0x18f0
++#define EARCRX_CMDC_XACT_WR45 0x18f4
++#define EARCRX_CMDC_XACT_WR46 0x18f8
++#define EARCRX_CMDC_XACT_WR47 0x18fc
++#define EARCRX_CMDC_XACT_WR48 0x1900
++#define EARCRX_CMDC_XACT_WR49 0x1904
++#define EARCRX_CMDC_XACT_WR50 0x1908
++#define EARCRX_CMDC_XACT_WR51 0x190c
++#define EARCRX_CMDC_XACT_WR52 0x1910
++#define EARCRX_CMDC_XACT_WR53 0x1914
++#define EARCRX_CMDC_XACT_WR54 0x1918
++#define EARCRX_CMDC_XACT_WR55 0x191c
++#define EARCRX_CMDC_XACT_WR56 0x1920
++#define EARCRX_CMDC_XACT_WR57 0x1924
++#define EARCRX_CMDC_XACT_WR58 0x1928
++#define EARCRX_CMDC_XACT_WR59 0x192c
++#define EARCRX_CMDC_XACT_WR60 0x1930
++#define EARCRX_CMDC_XACT_WR61 0x1934
++#define EARCRX_CMDC_XACT_WR62 0x1938
++#define EARCRX_CMDC_XACT_WR63 0x193c
++#define EARCRX_CMDC_XACT_WR64 0x1940
++#define EARCRX_CMDC_XACT_RD0 0x1960
++#define EARCRX_CMDC_XACT_RD1 0x1964
++#define EARCRX_CMDC_XACT_RD2 0x1968
++#define EARCRX_CMDC_XACT_RD3 0x196c
++#define EARCRX_CMDC_XACT_RD4 0x1970
++#define EARCRX_CMDC_XACT_RD5 0x1974
++#define EARCRX_CMDC_XACT_RD6 0x1978
++#define EARCRX_CMDC_XACT_RD7 0x197c
++#define EARCRX_CMDC_XACT_RD8 0x1980
++#define EARCRX_CMDC_XACT_RD9 0x1984
++#define EARCRX_CMDC_XACT_RD10 0x1988
++#define EARCRX_CMDC_XACT_RD11 0x198c
++#define EARCRX_CMDC_XACT_RD12 0x1990
++#define EARCRX_CMDC_XACT_RD13 0x1994
++#define EARCRX_CMDC_XACT_RD14 0x1998
++#define EARCRX_CMDC_XACT_RD15 0x199c
++#define EARCRX_CMDC_XACT_RD16 0x19a0
++#define EARCRX_CMDC_XACT_RD17 0x19a4
++#define EARCRX_CMDC_XACT_RD18 0x19a8
++#define EARCRX_CMDC_XACT_RD19 0x19ac
++#define EARCRX_CMDC_XACT_RD20 0x19b0
++#define EARCRX_CMDC_XACT_RD21 0x19b4
++#define EARCRX_CMDC_XACT_RD22 0x19b8
++#define EARCRX_CMDC_XACT_RD23 0x19bc
++#define EARCRX_CMDC_XACT_RD24 0x19c0
++#define EARCRX_CMDC_XACT_RD25 0x19c4
++#define EARCRX_CMDC_XACT_RD26 0x19c8
++#define EARCRX_CMDC_XACT_RD27 0x19cc
++#define EARCRX_CMDC_XACT_RD28 0x19d0
++#define EARCRX_CMDC_XACT_RD29 0x19d4
++#define EARCRX_CMDC_XACT_RD30 0x19d8
++#define EARCRX_CMDC_XACT_RD31 0x19dc
++#define EARCRX_CMDC_XACT_RD32 0x19e0
++#define EARCRX_CMDC_XACT_RD33 0x19e4
++#define EARCRX_CMDC_XACT_RD34 0x19e8
++#define EARCRX_CMDC_XACT_RD35 0x19ec
++#define EARCRX_CMDC_XACT_RD36 0x19f0
++#define EARCRX_CMDC_XACT_RD37 0x19f4
++#define EARCRX_CMDC_XACT_RD38 0x19f8
++#define EARCRX_CMDC_XACT_RD39 0x19fc
++#define EARCRX_CMDC_XACT_RD40 0x1a00
++#define EARCRX_CMDC_XACT_RD41 0x1a04
++#define EARCRX_CMDC_XACT_RD42 0x1a08
++#define EARCRX_CMDC_XACT_RD43 0x1a0c
++#define EARCRX_CMDC_XACT_RD44 0x1a10
++#define EARCRX_CMDC_XACT_RD45 0x1a14
++#define EARCRX_CMDC_XACT_RD46 0x1a18
++#define EARCRX_CMDC_XACT_RD47 0x1a1c
++#define EARCRX_CMDC_XACT_RD48 0x1a20
++#define EARCRX_CMDC_XACT_RD49 0x1a24
++#define EARCRX_CMDC_XACT_RD50 0x1a28
++#define EARCRX_CMDC_XACT_RD51 0x1a2c
++#define EARCRX_CMDC_XACT_RD52 0x1a30
++#define EARCRX_CMDC_XACT_RD53 0x1a34
++#define EARCRX_CMDC_XACT_RD54 0x1a38
++#define EARCRX_CMDC_XACT_RD55 0x1a3c
++#define EARCRX_CMDC_XACT_RD56 0x1a40
++#define EARCRX_CMDC_XACT_RD57 0x1a44
++#define EARCRX_CMDC_XACT_RD58 0x1a48
++#define EARCRX_CMDC_XACT_RD59 0x1a4c
++#define EARCRX_CMDC_XACT_RD60 0x1a50
++#define EARCRX_CMDC_XACT_RD61 0x1a54
++#define EARCRX_CMDC_XACT_RD62 0x1a58
++#define EARCRX_CMDC_XACT_RD63 0x1a5c
++#define EARCRX_CMDC_XACT_RD64 0x1a60
++#define EARCRX_CMDC_SYNC_CONFIG 0x1b00
++/* eARC RX DMAC Registers */
++#define EARCRX_DMAC_PHY_CONTROL 0x1c00
++#define EARCRX_DMAC_CONFIG 0x1c08
++#define EARCRX_DMAC_CONTROL0 0x1c0c
++#define EARCRX_DMAC_AUDIO_EN BIT(1)
++#define EARCRX_DMAC_EN BIT(0)
++#define EARCRX_DMAC_CONTROL1 0x1c10
++#define EARCRX_DMAC_STATUS 0x1c14
++#define EARCRX_DMAC_CHSTATUS0 0x1c18
++#define EARCRX_DMAC_CHSTATUS1 0x1c1c
++#define EARCRX_DMAC_CHSTATUS2 0x1c20
++#define EARCRX_DMAC_CHSTATUS3 0x1c24
++#define EARCRX_DMAC_CHSTATUS4 0x1c28
++#define EARCRX_DMAC_CHSTATUS5 0x1c2c
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC0 0x1c30
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC1 0x1c34
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC2 0x1c38
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC3 0x1c3c
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC4 0x1c40
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC5 0x1c44
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC6 0x1c48
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC7 0x1c4c
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC8 0x1c50
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC9 0x1c54
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC10 0x1c58
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_AC11 0x1c5c
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT0 0x1c60
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT1 0x1c64
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT2 0x1c68
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT3 0x1c6c
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT4 0x1c70
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT5 0x1c74
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT6 0x1c78
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT7 0x1c7c
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT8 0x1c80
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT9 0x1c84
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT10 0x1c88
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC1_PKT11 0x1c8c
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT0 0x1c90
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT1 0x1c94
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT2 0x1c98
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT3 0x1c9c
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT4 0x1ca0
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT5 0x1ca4
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT6 0x1ca8
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT7 0x1cac
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT8 0x1cb0
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT9 0x1cb4
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT10 0x1cb8
++#define EARCRX_DMAC_USRDATA_MSG_HDMI_ISRC2_PKT11 0x1cbc
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC0 0x1cc0
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC1 0x1cc4
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC2 0x1cc8
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC3 0x1ccc
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC4 0x1cd0
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC5 0x1cd4
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC6 0x1cd8
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC7 0x1cdc
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC8 0x1ce0
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC9 0x1ce4
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC10 0x1ce8
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC11 0x1cec
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC12 0x1cf0
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC13 0x1cf4
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC14 0x1cf8
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC15 0x1cfc
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC16 0x1d00
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC17 0x1d04
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC18 0x1d08
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC19 0x1d0c
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC20 0x1d10
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC21 0x1d14
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC22 0x1d18
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC23 0x1d1c
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC24 0x1d20
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC25 0x1d24
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC26 0x1d28
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC27 0x1d2c
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC28 0x1d30
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC29 0x1d34
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC30 0x1d38
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC31 0x1d3c
++#define EARCRX_DMAC_USRDATA_MSG_GENERIC32 0x1d40
++#define EARCRX_DMAC_CHSTATUS_STREAMER0 0x1d44
++#define EARCRX_DMAC_CHSTATUS_STREAMER1 0x1d48
++#define EARCRX_DMAC_CHSTATUS_STREAMER2 0x1d4c
++#define EARCRX_DMAC_CHSTATUS_STREAMER3 0x1d50
++#define EARCRX_DMAC_CHSTATUS_STREAMER4 0x1d54
++#define EARCRX_DMAC_CHSTATUS_STREAMER5 0x1d58
++#define EARCRX_DMAC_CHSTATUS_STREAMER6 0x1d5c
++#define EARCRX_DMAC_CHSTATUS_STREAMER7 0x1d60
++#define EARCRX_DMAC_CHSTATUS_STREAMER8 0x1d64
++#define EARCRX_DMAC_CHSTATUS_STREAMER9 0x1d68
++#define EARCRX_DMAC_CHSTATUS_STREAMER10 0x1d6c
++#define EARCRX_DMAC_CHSTATUS_STREAMER11 0x1d70
++#define EARCRX_DMAC_CHSTATUS_STREAMER12 0x1d74
++#define EARCRX_DMAC_CHSTATUS_STREAMER13 0x1d78
++#define EARCRX_DMAC_CHSTATUS_STREAMER14 0x1d7c
++#define EARCRX_DMAC_USRDATA_STREAMER0 0x1d80
++/* Main Unit Interrupt Registers */
++#define MAIN_INTVEC_INDEX 0x3000
++#define MAINUNIT_0_INT_STATUS 0x3010
++#define MAINUNIT_0_INT_MASK_N 0x3014
++#define MAINUNIT_0_INT_CLEAR 0x3018
++#define MAINUNIT_0_INT_FORCE 0x301c
++#define MAINUNIT_1_INT_STATUS 0x3020
++#define FLT_EXIT_TO_LTSL_IRQ BIT(22)
++#define FLT_EXIT_TO_LTS4_IRQ BIT(21)
++#define FLT_EXIT_TO_LTSP_IRQ BIT(20)
++#define SCDC_NACK_RCVD_IRQ BIT(12)
++#define SCDC_RR_REPLY_STOP_IRQ BIT(11)
++#define SCDC_UPD_FLAGS_CLR_IRQ BIT(10)
++#define SCDC_UPD_FLAGS_CHG_IRQ BIT(9)
++#define SCDC_UPD_FLAGS_RD_IRQ BIT(8)
++#define I2CM_NACK_RCVD_IRQ BIT(2)
++#define I2CM_READ_REQUEST_IRQ BIT(1)
++#define I2CM_OP_DONE_IRQ BIT(0)
++#define MAINUNIT_1_INT_MASK_N 0x3024
++#define I2CM_NACK_RCVD_MASK_N BIT(2)
++#define I2CM_READ_REQUEST_MASK_N BIT(1)
++#define I2CM_OP_DONE_MASK_N BIT(0)
++#define MAINUNIT_1_INT_CLEAR 0x3028
++#define I2CM_NACK_RCVD_CLEAR BIT(2)
++#define I2CM_READ_REQUEST_CLEAR BIT(1)
++#define I2CM_OP_DONE_CLEAR BIT(0)
++#define MAINUNIT_1_INT_FORCE 0x302c
++/* AVPUNIT Interrupt Registers */
++#define AVP_INTVEC_INDEX 0x3800
++#define AVP_0_INT_STATUS 0x3810
++#define AVP_0_INT_MASK_N 0x3814
++#define AVP_0_INT_CLEAR 0x3818
++#define AVP_0_INT_FORCE 0x381c
++#define AVP_1_INT_STATUS 0x3820
++#define AVP_1_INT_MASK_N 0x3824
++#define HDCP14_AUTH_CHG_MASK_N BIT(6)
++#define AVP_1_INT_CLEAR 0x3828
++#define AVP_1_INT_FORCE 0x382c
++#define AVP_2_INT_STATUS 0x3830
++#define AVP_2_INT_MASK_N 0x3834
++#define AVP_2_INT_CLEAR 0x3838
++#define AVP_2_INT_FORCE 0x383c
++#define AVP_3_INT_STATUS 0x3840
++#define AVP_3_INT_MASK_N 0x3844
++#define AVP_3_INT_CLEAR 0x3848
++#define AVP_3_INT_FORCE 0x384c
++#define AVP_4_INT_STATUS 0x3850
++#define AVP_4_INT_MASK_N 0x3854
++#define AVP_4_INT_CLEAR 0x3858
++#define AVP_4_INT_FORCE 0x385c
++#define AVP_5_INT_STATUS 0x3860
++#define AVP_5_INT_MASK_N 0x3864
++#define AVP_5_INT_CLEAR 0x3868
++#define AVP_5_INT_FORCE 0x386c
++#define AVP_6_INT_STATUS 0x3870
++#define AVP_6_INT_MASK_N 0x3874
++#define AVP_6_INT_CLEAR 0x3878
++#define AVP_6_INT_FORCE 0x387c
++/* CEC Interrupt Registers */
++#define CEC_INT_STATUS 0x4000
++#define CEC_INT_MASK_N 0x4004
++#define CEC_INT_CLEAR 0x4008
++#define CEC_INT_FORCE 0x400c
++/* eARC RX Interrupt Registers */
++#define EARCRX_INTVEC_INDEX 0x4800
++#define EARCRX_0_INT_STATUS 0x4810
++#define EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ BIT(9)
++#define EARCRX_CMDC_DISCOVERY_DONE_IRQ BIT(8)
++#define EARCRX_0_INT_MASK_N 0x4814
++#define EARCRX_0_INT_CLEAR 0x4818
++#define EARCRX_0_INT_FORCE 0x481c
++#define EARCRX_1_INT_STATUS 0x4820
++#define EARCRX_1_INT_MASK_N 0x4824
++#define EARCRX_1_INT_CLEAR 0x4828
++#define EARCRX_1_INT_FORCE 0x482c
++
++#endif /* __DW_HDMI_QP_H__ */
+--- /dev/null
++++ b/include/drm/bridge/dw_hdmi_qp.h
+@@ -0,0 +1,32 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
++ * Copyright (c) 2024 Collabora Ltd.
++ */
++
++#ifndef __DW_HDMI_QP__
++#define __DW_HDMI_QP__
++
++struct device;
++struct drm_encoder;
++struct dw_hdmi_qp;
++struct platform_device;
++
++struct dw_hdmi_qp_phy_ops {
++ int (*init)(struct dw_hdmi_qp *hdmi, void *data);
++ void (*disable)(struct dw_hdmi_qp *hdmi, void *data);
++ enum drm_connector_status (*read_hpd)(struct dw_hdmi_qp *hdmi, void *data);
++ void (*setup_hpd)(struct dw_hdmi_qp *hdmi, void *data);
++};
++
++struct dw_hdmi_qp_plat_data {
++ const struct dw_hdmi_qp_phy_ops *phy_ops;
++ void *phy_data;
++ int main_irq;
++};
++
++struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
++ struct drm_encoder *encoder,
++ const struct dw_hdmi_qp_plat_data *plat_data);
++void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi);
++#endif /* __DW_HDMI_QP__ */
diff --git a/lede/target/linux/rockchip/patches-6.12/041-10-v6.13-drm-rockchip-Add-basic-RK3588-HDMI-output-support.patch b/lede/target/linux/rockchip/patches-6.12/041-10-v6.13-drm-rockchip-Add-basic-RK3588-HDMI-output-support.patch
new file mode 100644
index 0000000000..6ea142c424
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-10-v6.13-drm-rockchip-Add-basic-RK3588-HDMI-output-support.patch
@@ -0,0 +1,511 @@
+From 128a9bf8ace290d86d2805c06f3b0e4cfab75de0 Mon Sep 17 00:00:00 2001
+From: Cristian Ciocaltea
+Date: Wed, 16 Oct 2024 23:06:53 +0300
+Subject: [PATCH] drm/rockchip: Add basic RK3588 HDMI output support
+
+The RK3588 SoC family integrates the newer Synopsys DesignWare HDMI 2.1
+Quad-Pixel (QP) TX controller IP and a HDMI/eDP TX Combo PHY based on a
+Samsung IP block.
+
+Add just the basic support for now, i.e. RGB output up to 4K@60Hz,
+without audio, CEC or any of the HDMI 2.1 specific features.
+
+Co-developed-by: Algea Cao
+Signed-off-by: Algea Cao
+Tested-by: Heiko Stuebner
+Reviewed-by: Maxime Ripard
+Signed-off-by: Cristian Ciocaltea
+Link: https://patchwork.freedesktop.org/patch/msgid/20241016-b4-rk3588-bridge-upstream-v10-3-87ef92a6d14e@collabora.com
+Signed-off-by: Maxime Ripard
+---
+ drivers/gpu/drm/rockchip/Kconfig | 9 +
+ drivers/gpu/drm/rockchip/Makefile | 1 +
+ .../gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 424 ++++++++++++++++++
+ drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 2 +
+ drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 +
+ 5 files changed, 437 insertions(+)
+ create mode 100644 drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+
+--- a/drivers/gpu/drm/rockchip/Kconfig
++++ b/drivers/gpu/drm/rockchip/Kconfig
+@@ -9,6 +9,7 @@ config DRM_ROCKCHIP
+ select VIDEOMODE_HELPERS
+ select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP
+ select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
++ select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP
+ select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
+ select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI
+ select GENERIC_PHY_MIPI_DPHY if ROCKCHIP_DW_MIPI_DSI
+@@ -64,6 +65,14 @@ config ROCKCHIP_DW_HDMI
+ enable HDMI on RK3288 or RK3399 based SoC, you should select
+ this option.
+
++config ROCKCHIP_DW_HDMI_QP
++ bool "Rockchip specific extensions for Synopsys DW HDMI QP"
++ select DRM_BRIDGE_CONNECTOR
++ help
++ This selects support for Rockchip SoC specific extensions
++ for the Synopsys DesignWare HDMI QP driver. If you want to
++ enable HDMI on RK3588 based SoC, you should select this option.
++
+ config ROCKCHIP_DW_MIPI_DSI
+ bool "Rockchip specific extensions for Synopsys DW MIPI DSI"
+ select GENERIC_PHY_MIPI_DPHY
+--- a/drivers/gpu/drm/rockchip/Makefile
++++ b/drivers/gpu/drm/rockchip/Makefile
+@@ -11,6 +11,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_VOP) += ro
+ rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
++rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o
+--- /dev/null
++++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+@@ -0,0 +1,424 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
++ * Copyright (c) 2024 Collabora Ltd.
++ *
++ * Author: Algea Cao
++ * Author: Cristian Ciocaltea
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#include
++#include
++#include
++#include
++#include
++#include
++
++#include "rockchip_drm_drv.h"
++
++#define RK3588_GRF_SOC_CON2 0x0308
++#define RK3588_HDMI0_HPD_INT_MSK BIT(13)
++#define RK3588_HDMI0_HPD_INT_CLR BIT(12)
++#define RK3588_GRF_SOC_CON7 0x031c
++#define RK3588_SET_HPD_PATH_MASK GENMASK(13, 12)
++#define RK3588_GRF_SOC_STATUS1 0x0384
++#define RK3588_HDMI0_LEVEL_INT BIT(16)
++#define RK3588_GRF_VO1_CON3 0x000c
++#define RK3588_SCLIN_MASK BIT(9)
++#define RK3588_SDAIN_MASK BIT(10)
++#define RK3588_MODE_MASK BIT(11)
++#define RK3588_I2S_SEL_MASK BIT(13)
++#define RK3588_GRF_VO1_CON9 0x0024
++#define RK3588_HDMI0_GRANT_SEL BIT(10)
++
++#define HIWORD_UPDATE(val, mask) ((val) | (mask) << 16)
++#define HOTPLUG_DEBOUNCE_MS 150
++
++struct rockchip_hdmi_qp {
++ struct device *dev;
++ struct regmap *regmap;
++ struct regmap *vo_regmap;
++ struct rockchip_encoder encoder;
++ struct clk *ref_clk;
++ struct dw_hdmi_qp *hdmi;
++ struct phy *phy;
++ struct gpio_desc *enable_gpio;
++ struct delayed_work hpd_work;
++};
++
++static struct rockchip_hdmi_qp *to_rockchip_hdmi_qp(struct drm_encoder *encoder)
++{
++ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
++
++ return container_of(rkencoder, struct rockchip_hdmi_qp, encoder);
++}
++
++static void dw_hdmi_qp_rockchip_encoder_enable(struct drm_encoder *encoder)
++{
++ struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
++ struct drm_crtc *crtc = encoder->crtc;
++ unsigned long long rate;
++
++ /* Unconditionally switch to TMDS as FRL is not yet supported */
++ gpiod_set_value(hdmi->enable_gpio, 1);
++
++ if (crtc && crtc->state) {
++ rate = drm_hdmi_compute_mode_clock(&crtc->state->adjusted_mode,
++ 8, HDMI_COLORSPACE_RGB);
++ clk_set_rate(hdmi->ref_clk, rate);
++ /*
++ * FIXME: Temporary workaround to pass pixel clock rate
++ * to the PHY driver until phy_configure_opts_hdmi
++ * becomes available in the PHY API. See also the related
++ * comment in rk_hdptx_phy_power_on() from
++ * drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
++ */
++ phy_set_bus_width(hdmi->phy, rate / 100);
++ }
++}
++
++static int
++dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
++ struct drm_crtc_state *crtc_state,
++ struct drm_connector_state *conn_state)
++{
++ struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
++
++ s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
++ s->output_type = DRM_MODE_CONNECTOR_HDMIA;
++
++ return 0;
++}
++
++static const struct
++drm_encoder_helper_funcs dw_hdmi_qp_rockchip_encoder_helper_funcs = {
++ .enable = dw_hdmi_qp_rockchip_encoder_enable,
++ .atomic_check = dw_hdmi_qp_rockchip_encoder_atomic_check,
++};
++
++static int dw_hdmi_qp_rk3588_phy_init(struct dw_hdmi_qp *dw_hdmi, void *data)
++{
++ struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
++
++ return phy_power_on(hdmi->phy);
++}
++
++static void dw_hdmi_qp_rk3588_phy_disable(struct dw_hdmi_qp *dw_hdmi,
++ void *data)
++{
++ struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
++
++ phy_power_off(hdmi->phy);
++}
++
++static enum drm_connector_status
++dw_hdmi_qp_rk3588_read_hpd(struct dw_hdmi_qp *dw_hdmi, void *data)
++{
++ struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
++ u32 val;
++
++ regmap_read(hdmi->regmap, RK3588_GRF_SOC_STATUS1, &val);
++
++ return val & RK3588_HDMI0_LEVEL_INT ?
++ connector_status_connected : connector_status_disconnected;
++}
++
++static void dw_hdmi_qp_rk3588_setup_hpd(struct dw_hdmi_qp *dw_hdmi, void *data)
++{
++ struct rockchip_hdmi_qp *hdmi = (struct rockchip_hdmi_qp *)data;
++
++ regmap_write(hdmi->regmap,
++ RK3588_GRF_SOC_CON2,
++ HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_CLR,
++ RK3588_HDMI0_HPD_INT_CLR |
++ RK3588_HDMI0_HPD_INT_MSK));
++}
++
++static const struct dw_hdmi_qp_phy_ops rk3588_hdmi_phy_ops = {
++ .init = dw_hdmi_qp_rk3588_phy_init,
++ .disable = dw_hdmi_qp_rk3588_phy_disable,
++ .read_hpd = dw_hdmi_qp_rk3588_read_hpd,
++ .setup_hpd = dw_hdmi_qp_rk3588_setup_hpd,
++};
++
++static void dw_hdmi_qp_rk3588_hpd_work(struct work_struct *work)
++{
++ struct rockchip_hdmi_qp *hdmi = container_of(work,
++ struct rockchip_hdmi_qp,
++ hpd_work.work);
++ struct drm_device *drm = hdmi->encoder.encoder.dev;
++ bool changed;
++
++ if (drm) {
++ changed = drm_helper_hpd_irq_event(drm);
++ if (changed)
++ drm_dbg(hdmi, "connector status changed\n");
++ }
++}
++
++static irqreturn_t dw_hdmi_qp_rk3588_hardirq(int irq, void *dev_id)
++{
++ struct rockchip_hdmi_qp *hdmi = dev_id;
++ u32 intr_stat, val;
++
++ regmap_read(hdmi->regmap, RK3588_GRF_SOC_STATUS1, &intr_stat);
++
++ if (intr_stat) {
++ val = HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_MSK,
++ RK3588_HDMI0_HPD_INT_MSK);
++ regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
++ return IRQ_WAKE_THREAD;
++ }
++
++ return IRQ_NONE;
++}
++
++static irqreturn_t dw_hdmi_qp_rk3588_irq(int irq, void *dev_id)
++{
++ struct rockchip_hdmi_qp *hdmi = dev_id;
++ u32 intr_stat, val;
++
++ regmap_read(hdmi->regmap, RK3588_GRF_SOC_STATUS1, &intr_stat);
++ if (!intr_stat)
++ return IRQ_NONE;
++
++ val = HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_CLR,
++ RK3588_HDMI0_HPD_INT_CLR);
++ regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
++
++ mod_delayed_work(system_wq, &hdmi->hpd_work,
++ msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
++
++ val |= HIWORD_UPDATE(0, RK3588_HDMI0_HPD_INT_MSK);
++ regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
++
++ return IRQ_HANDLED;
++}
++
++static const struct of_device_id dw_hdmi_qp_rockchip_dt_ids[] = {
++ { .compatible = "rockchip,rk3588-dw-hdmi-qp",
++ .data = &rk3588_hdmi_phy_ops },
++ {},
++};
++MODULE_DEVICE_TABLE(of, dw_hdmi_qp_rockchip_dt_ids);
++
++static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
++ void *data)
++{
++ static const char * const clk_names[] = {
++ "pclk", "earc", "aud", "hdp", "hclk_vo1",
++ "ref" /* keep "ref" last */
++ };
++ struct platform_device *pdev = to_platform_device(dev);
++ struct dw_hdmi_qp_plat_data plat_data;
++ struct drm_device *drm = data;
++ struct drm_connector *connector;
++ struct drm_encoder *encoder;
++ struct rockchip_hdmi_qp *hdmi;
++ struct clk *clk;
++ int ret, irq, i;
++ u32 val;
++
++ if (!pdev->dev.of_node)
++ return -ENODEV;
++
++ hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
++ if (!hdmi)
++ return -ENOMEM;
++
++ plat_data.phy_ops = of_device_get_match_data(dev);
++ if (!plat_data.phy_ops)
++ return -ENODEV;
++
++ plat_data.phy_data = hdmi;
++ hdmi->dev = &pdev->dev;
++
++ encoder = &hdmi->encoder.encoder;
++ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
++
++ rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder,
++ dev->of_node, 0, 0);
++ /*
++ * If we failed to find the CRTC(s) which this encoder is
++ * supposed to be connected to, it's because the CRTC has
++ * not been registered yet. Defer probing, and hope that
++ * the required CRTC is added later.
++ */
++ if (encoder->possible_crtcs == 0)
++ return -EPROBE_DEFER;
++
++ hdmi->regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
++ "rockchip,grf");
++ if (IS_ERR(hdmi->regmap)) {
++ drm_err(hdmi, "Unable to get rockchip,grf\n");
++ return PTR_ERR(hdmi->regmap);
++ }
++
++ hdmi->vo_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
++ "rockchip,vo-grf");
++ if (IS_ERR(hdmi->vo_regmap)) {
++ drm_err(hdmi, "Unable to get rockchip,vo-grf\n");
++ return PTR_ERR(hdmi->vo_regmap);
++ }
++
++ for (i = 0; i < ARRAY_SIZE(clk_names); i++) {
++ clk = devm_clk_get_enabled(hdmi->dev, clk_names[i]);
++
++ if (IS_ERR(clk)) {
++ ret = PTR_ERR(clk);
++ if (ret != -EPROBE_DEFER)
++ drm_err(hdmi, "Failed to get %s clock: %d\n",
++ clk_names[i], ret);
++ return ret;
++ }
++ }
++ hdmi->ref_clk = clk;
++
++ hdmi->enable_gpio = devm_gpiod_get_optional(hdmi->dev, "enable",
++ GPIOD_OUT_HIGH);
++ if (IS_ERR(hdmi->enable_gpio)) {
++ ret = PTR_ERR(hdmi->enable_gpio);
++ drm_err(hdmi, "Failed to request enable GPIO: %d\n", ret);
++ return ret;
++ }
++
++ hdmi->phy = devm_of_phy_get_by_index(dev, dev->of_node, 0);
++ if (IS_ERR(hdmi->phy)) {
++ ret = PTR_ERR(hdmi->phy);
++ if (ret != -EPROBE_DEFER)
++ drm_err(hdmi, "failed to get phy: %d\n", ret);
++ return ret;
++ }
++
++ val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) |
++ HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) |
++ HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) |
++ HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK);
++ regmap_write(hdmi->vo_regmap, RK3588_GRF_VO1_CON3, val);
++
++ val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK,
++ RK3588_SET_HPD_PATH_MASK);
++ regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON7, val);
++
++ val = HIWORD_UPDATE(RK3588_HDMI0_GRANT_SEL,
++ RK3588_HDMI0_GRANT_SEL);
++ regmap_write(hdmi->vo_regmap, RK3588_GRF_VO1_CON9, val);
++
++ val = HIWORD_UPDATE(RK3588_HDMI0_HPD_INT_MSK, RK3588_HDMI0_HPD_INT_MSK);
++ regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON2, val);
++
++ INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_qp_rk3588_hpd_work);
++
++ plat_data.main_irq = platform_get_irq_byname(pdev, "main");
++ if (plat_data.main_irq < 0)
++ return plat_data.main_irq;
++
++ irq = platform_get_irq_byname(pdev, "hpd");
++ if (irq < 0)
++ return irq;
++
++ ret = devm_request_threaded_irq(hdmi->dev, irq,
++ dw_hdmi_qp_rk3588_hardirq,
++ dw_hdmi_qp_rk3588_irq,
++ IRQF_SHARED, "dw-hdmi-qp-hpd",
++ hdmi);
++ if (ret)
++ return ret;
++
++ drm_encoder_helper_add(encoder, &dw_hdmi_qp_rockchip_encoder_helper_funcs);
++ drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
++
++ platform_set_drvdata(pdev, hdmi);
++
++ hdmi->hdmi = dw_hdmi_qp_bind(pdev, encoder, &plat_data);
++ if (IS_ERR(hdmi->hdmi)) {
++ ret = PTR_ERR(hdmi->hdmi);
++ drm_encoder_cleanup(encoder);
++ return ret;
++ }
++
++ connector = drm_bridge_connector_init(drm, encoder);
++ if (IS_ERR(connector)) {
++ ret = PTR_ERR(connector);
++ drm_err(hdmi, "failed to init bridge connector: %d\n", ret);
++ return ret;
++ }
++
++ return drm_connector_attach_encoder(connector, encoder);
++}
++
++static void dw_hdmi_qp_rockchip_unbind(struct device *dev,
++ struct device *master,
++ void *data)
++{
++ struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev);
++
++ cancel_delayed_work_sync(&hdmi->hpd_work);
++
++ drm_encoder_cleanup(&hdmi->encoder.encoder);
++}
++
++static const struct component_ops dw_hdmi_qp_rockchip_ops = {
++ .bind = dw_hdmi_qp_rockchip_bind,
++ .unbind = dw_hdmi_qp_rockchip_unbind,
++};
++
++static int dw_hdmi_qp_rockchip_probe(struct platform_device *pdev)
++{
++ return component_add(&pdev->dev, &dw_hdmi_qp_rockchip_ops);
++}
++
++static void dw_hdmi_qp_rockchip_remove(struct platform_device *pdev)
++{
++ component_del(&pdev->dev, &dw_hdmi_qp_rockchip_ops);
++}
++
++static int __maybe_unused dw_hdmi_qp_rockchip_resume(struct device *dev)
++{
++ struct rockchip_hdmi_qp *hdmi = dev_get_drvdata(dev);
++ u32 val;
++
++ val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) |
++ HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) |
++ HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) |
++ HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK);
++ regmap_write(hdmi->vo_regmap, RK3588_GRF_VO1_CON3, val);
++
++ val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK,
++ RK3588_SET_HPD_PATH_MASK);
++ regmap_write(hdmi->regmap, RK3588_GRF_SOC_CON7, val);
++
++ val = HIWORD_UPDATE(RK3588_HDMI0_GRANT_SEL,
++ RK3588_HDMI0_GRANT_SEL);
++ regmap_write(hdmi->vo_regmap, RK3588_GRF_VO1_CON9, val);
++
++ dw_hdmi_qp_resume(dev, hdmi->hdmi);
++
++ if (hdmi->encoder.encoder.dev)
++ drm_helper_hpd_irq_event(hdmi->encoder.encoder.dev);
++
++ return 0;
++}
++
++static const struct dev_pm_ops dw_hdmi_qp_rockchip_pm = {
++ SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_qp_rockchip_resume)
++};
++
++struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver = {
++ .probe = dw_hdmi_qp_rockchip_probe,
++ .remove = dw_hdmi_qp_rockchip_remove,
++ .driver = {
++ .name = "dwhdmiqp-rockchip",
++ .pm = &dw_hdmi_qp_rockchip_pm,
++ .of_match_table = dw_hdmi_qp_rockchip_dt_ids,
++ },
++};
+--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
++++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
+@@ -532,6 +532,8 @@ static int __init rockchip_drm_init(void
+ ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
+ ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
+ CONFIG_ROCKCHIP_DW_HDMI);
++ ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver,
++ CONFIG_ROCKCHIP_DW_HDMI_QP);
+ ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver,
+ CONFIG_ROCKCHIP_DW_MIPI_DSI);
+ ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);
+--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
++++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
+@@ -88,6 +88,7 @@ int rockchip_drm_encoder_set_crtc_endpoi
+ int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
+ extern struct platform_driver cdn_dp_driver;
+ extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
++extern struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver;
+ extern struct platform_driver dw_mipi_dsi_rockchip_driver;
+ extern struct platform_driver inno_hdmi_driver;
+ extern struct platform_driver rockchip_dp_driver;
diff --git a/lede/target/linux/rockchip/patches-6.12/041-11-v6.14-rockchip-drm-vop2-add-support-for-gamma-LUT.patch b/lede/target/linux/rockchip/patches-6.12/041-11-v6.14-rockchip-drm-vop2-add-support-for-gamma-LUT.patch
new file mode 100644
index 0000000000..270f693fab
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-11-v6.14-rockchip-drm-vop2-add-support-for-gamma-LUT.patch
@@ -0,0 +1,334 @@
+From 4f537776340dab2b680a4d8554567f6884240d0b Mon Sep 17 00:00:00 2001
+From: Piotr Zalewski
+Date: Fri, 1 Nov 2024 19:01:17 +0000
+Subject: [PATCH] rockchip/drm: vop2: add support for gamma LUT
+
+Add support for gamma LUT in VOP2 driver. The implementation was inspired
+by one found in VOP1 driver. Blue and red channels in gamma LUT register
+write were swapped with respect to how gamma LUT values are written in
+VOP1. Gamma LUT port selection was added before the write of new gamma LUT
+table.
+
+If the current SoC is rk356x, check if no other CRTC has gamma LUT enabled
+in atomic_check (only one video port can use gamma LUT at a time) and
+disable gamma LUT before the LUT table write.
+
+If the current SoC isn't rk356x, "seamless" gamma lut update is performed
+similarly to how it was done in the case of RK3399 in VOP1[1]. In seamless
+update gamma LUT disable before the write isn't necessary, check if no
+other CRTC has gamma LUT enabled is also not necessary, different register
+is being used to select gamma LUT port[2] and after setting DSP_LUT_EN bit,
+GAMMA_UPDATE_EN bit is set[3].
+
+Gamma size is set and drm color management is enabled for each video port's
+CRTC except ones which have no associated device.
+
+Patch was tested on RK3566 (Pinetab2). When using userspace tools
+which set eg. constant color temperature no issues were noticed. When
+using userspace tools which adjust eg. color temperature the slight screen
+flicker is visible probably because of gamma LUT disable needed in the
+case of RK356x before gamma LUT write.
+
+Compare behaviour of eg.:
+```
+gammastep -O 3000
+```
+
+To eg.:
+```
+gammastep -l 53:23 -t 6000:3000
+```
+
+In latter case color temperature is slowly adjusted at the beginning which
+causes screen to slighly flicker. Then it adjusts every few seconds which
+also causes slight screen flicker.
+
+[1] https://lists.infradead.org/pipermail/linux-rockchip/2021-October/028132.html
+[2] https://lore.kernel.org/linux-rockchip/48249708-8c05-40d2-a5d8-23de960c5a77@rock-chips.com/
+[3] https://github.com/radxa/kernel/blob/linux-6.1-stan-rkr1/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c#L3437
+
+Helped-by: Daniel Stone
+Helped-by: Dragan Simic
+Helped-by: Diederik de Haas
+Helped-by: Andy Yan
+Signed-off-by: Piotr Zalewski
+Reviewed-by: Andy Yan
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20241101185545.559090-3-pZ010001011111@proton.me
+---
+ drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 186 +++++++++++++++++++
+ drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 5 +
+ 2 files changed, 191 insertions(+)
+
+--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
++++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+@@ -281,6 +281,15 @@ static u32 vop2_readl(struct vop2 *vop2,
+ return val;
+ }
+
++static u32 vop2_vp_read(struct vop2_video_port *vp, u32 offset)
++{
++ u32 val;
++
++ regmap_read(vp->vop2->map, vp->data->offset + offset, &val);
++
++ return val;
++}
++
+ static void vop2_win_write(const struct vop2_win *win, unsigned int reg, u32 v)
+ {
+ regmap_field_write(win->reg[reg], v);
+@@ -1020,6 +1029,67 @@ static void vop2_disable(struct vop2 *vo
+ clk_disable_unprepare(vop2->hclk);
+ }
+
++static bool vop2_vp_dsp_lut_is_enabled(struct vop2_video_port *vp)
++{
++ u32 dsp_ctrl = vop2_vp_read(vp, RK3568_VP_DSP_CTRL);
++
++ return dsp_ctrl & RK3568_VP_DSP_CTRL__DSP_LUT_EN;
++}
++
++static void vop2_vp_dsp_lut_disable(struct vop2_video_port *vp)
++{
++ u32 dsp_ctrl = vop2_vp_read(vp, RK3568_VP_DSP_CTRL);
++
++ dsp_ctrl &= ~RK3568_VP_DSP_CTRL__DSP_LUT_EN;
++ vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl);
++}
++
++static bool vop2_vp_dsp_lut_poll_disabled(struct vop2_video_port *vp)
++{
++ u32 dsp_ctrl;
++ int ret = readx_poll_timeout(vop2_vp_dsp_lut_is_enabled, vp, dsp_ctrl,
++ !dsp_ctrl, 5, 30 * 1000);
++ if (ret) {
++ drm_err(vp->vop2->drm, "display LUT RAM enable timeout!\n");
++ return false;
++ }
++
++ return true;
++}
++
++static void vop2_vp_dsp_lut_enable(struct vop2_video_port *vp)
++{
++ u32 dsp_ctrl = vop2_vp_read(vp, RK3568_VP_DSP_CTRL);
++
++ dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_LUT_EN;
++ vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl);
++}
++
++static void vop2_vp_dsp_lut_update_enable(struct vop2_video_port *vp)
++{
++ u32 dsp_ctrl = vop2_vp_read(vp, RK3568_VP_DSP_CTRL);
++
++ dsp_ctrl |= RK3588_VP_DSP_CTRL__GAMMA_UPDATE_EN;
++ vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl);
++}
++
++static inline bool vop2_supports_seamless_gamma_lut_update(struct vop2 *vop2)
++{
++ return (vop2->data->soc_id != 3566 && vop2->data->soc_id != 3568);
++}
++
++static bool vop2_gamma_lut_in_use(struct vop2 *vop2, struct vop2_video_port *vp)
++{
++ const int nr_vps = vop2->data->nr_vps;
++ int gamma_en_vp_id;
++
++ for (gamma_en_vp_id = 0; gamma_en_vp_id < nr_vps; gamma_en_vp_id++)
++ if (vop2_vp_dsp_lut_is_enabled(&vop2->vps[gamma_en_vp_id]))
++ break;
++
++ return gamma_en_vp_id != nr_vps && gamma_en_vp_id != vp->id;
++}
++
+ static void vop2_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+ {
+@@ -1511,6 +1581,77 @@ static bool vop2_crtc_mode_fixup(struct
+ return true;
+ }
+
++static void vop2_crtc_write_gamma_lut(struct vop2 *vop2, struct drm_crtc *crtc)
++{
++ const struct vop2_video_port *vp = to_vop2_video_port(crtc);
++ const struct vop2_video_port_data *vp_data = &vop2->data->vp[vp->id];
++ struct drm_color_lut *lut = crtc->state->gamma_lut->data;
++ unsigned int i, bpc = ilog2(vp_data->gamma_lut_len);
++ u32 word;
++
++ for (i = 0; i < crtc->gamma_size; i++) {
++ word = (drm_color_lut_extract(lut[i].blue, bpc) << (2 * bpc)) |
++ (drm_color_lut_extract(lut[i].green, bpc) << bpc) |
++ drm_color_lut_extract(lut[i].red, bpc);
++
++ writel(word, vop2->lut_regs + i * 4);
++ }
++}
++
++static void vop2_crtc_atomic_set_gamma_seamless(struct vop2 *vop2,
++ struct vop2_video_port *vp,
++ struct drm_crtc *crtc)
++{
++ vop2_writel(vop2, RK3568_LUT_PORT_SEL,
++ FIELD_PREP(RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL, vp->id));
++ vop2_vp_dsp_lut_enable(vp);
++ vop2_crtc_write_gamma_lut(vop2, crtc);
++ vop2_vp_dsp_lut_update_enable(vp);
++}
++
++static void vop2_crtc_atomic_set_gamma_rk356x(struct vop2 *vop2,
++ struct vop2_video_port *vp,
++ struct drm_crtc *crtc)
++{
++ vop2_vp_dsp_lut_disable(vp);
++ vop2_cfg_done(vp);
++ if (!vop2_vp_dsp_lut_poll_disabled(vp))
++ return;
++
++ vop2_writel(vop2, RK3568_LUT_PORT_SEL, vp->id);
++ vop2_crtc_write_gamma_lut(vop2, crtc);
++ vop2_vp_dsp_lut_enable(vp);
++}
++
++static void vop2_crtc_atomic_try_set_gamma(struct vop2 *vop2,
++ struct vop2_video_port *vp,
++ struct drm_crtc *crtc,
++ struct drm_crtc_state *crtc_state)
++{
++ if (!vop2->lut_regs || !crtc_state->color_mgmt_changed)
++ return;
++
++ if (!crtc_state->gamma_lut) {
++ vop2_vp_dsp_lut_disable(vp);
++ return;
++ }
++
++ if (vop2_supports_seamless_gamma_lut_update(vop2))
++ vop2_crtc_atomic_set_gamma_seamless(vop2, vp, crtc);
++ else
++ vop2_crtc_atomic_set_gamma_rk356x(vop2, vp, crtc);
++}
++
++static inline void vop2_crtc_atomic_try_set_gamma_locked(struct vop2 *vop2,
++ struct vop2_video_port *vp,
++ struct drm_crtc *crtc,
++ struct drm_crtc_state *crtc_state)
++{
++ vop2_lock(vop2);
++ vop2_crtc_atomic_try_set_gamma(vop2, vp, crtc, crtc_state);
++ vop2_unlock(vop2);
++}
++
+ static void vop2_dither_setup(struct drm_crtc *crtc, u32 *dsp_ctrl)
+ {
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
+@@ -2107,11 +2248,40 @@ static void vop2_crtc_atomic_enable(stru
+
+ vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl);
+
++ vop2_crtc_atomic_try_set_gamma(vop2, vp, crtc, crtc_state);
++
+ drm_crtc_vblank_on(crtc);
+
+ vop2_unlock(vop2);
+ }
+
++static int vop2_crtc_atomic_check_gamma(struct vop2_video_port *vp,
++ struct drm_crtc *crtc,
++ struct drm_atomic_state *state,
++ struct drm_crtc_state *crtc_state)
++{
++ struct vop2 *vop2 = vp->vop2;
++ unsigned int len;
++
++ if (!vp->vop2->lut_regs || !crtc_state->color_mgmt_changed ||
++ !crtc_state->gamma_lut)
++ return 0;
++
++ len = drm_color_lut_size(crtc_state->gamma_lut);
++ if (len != crtc->gamma_size) {
++ drm_dbg(vop2->drm, "Invalid LUT size; got %d, expected %d\n",
++ len, crtc->gamma_size);
++ return -EINVAL;
++ }
++
++ if (!vop2_supports_seamless_gamma_lut_update(vop2) && vop2_gamma_lut_in_use(vop2, vp)) {
++ drm_info(vop2->drm, "Gamma LUT can be enabled for only one CRTC at a time\n");
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
+ static int vop2_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+ {
+@@ -2119,6 +2289,11 @@ static int vop2_crtc_atomic_check(struct
+ struct drm_plane *plane;
+ int nplanes = 0;
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
++ int ret;
++
++ ret = vop2_crtc_atomic_check_gamma(vp, crtc, state, crtc_state);
++ if (ret)
++ return ret;
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state)
+ nplanes++;
+@@ -2582,7 +2757,13 @@ static void vop2_crtc_atomic_begin(struc
+ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+ {
++ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
++ struct vop2 *vop2 = vp->vop2;
++
++ /* In case of modeset, gamma lut update already happened in atomic enable */
++ if (!drm_atomic_crtc_needs_modeset(crtc_state))
++ vop2_crtc_atomic_try_set_gamma_locked(vop2, vp, crtc, crtc_state);
+
+ vop2_post_config(crtc);
+
+@@ -2885,7 +3066,12 @@ static int vop2_create_crtcs(struct vop2
+ }
+
+ drm_crtc_helper_add(&vp->crtc, &vop2_crtc_helper_funcs);
++ if (vop2->lut_regs) {
++ const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id];
+
++ drm_mode_crtc_set_gamma_size(&vp->crtc, vp_data->gamma_lut_len);
++ drm_crtc_enable_color_mgmt(&vp->crtc, 0, false, vp_data->gamma_lut_len);
++ }
+ init_completion(&vp->dsp_hold_completion);
+ }
+
+--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
++++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
+@@ -404,6 +404,7 @@ enum dst_factor_mode {
+ #define RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN BIT(15)
+
+ #define RK3568_VP_DSP_CTRL__STANDBY BIT(31)
++#define RK3568_VP_DSP_CTRL__DSP_LUT_EN BIT(28)
+ #define RK3568_VP_DSP_CTRL__DITHER_DOWN_MODE BIT(20)
+ #define RK3568_VP_DSP_CTRL__DITHER_DOWN_SEL GENMASK(19, 18)
+ #define RK3568_VP_DSP_CTRL__DITHER_DOWN_EN BIT(17)
+@@ -418,6 +419,8 @@ enum dst_factor_mode {
+ #define RK3568_VP_DSP_CTRL__CORE_DCLK_DIV BIT(4)
+ #define RK3568_VP_DSP_CTRL__OUT_MODE GENMASK(3, 0)
+
++#define RK3588_VP_DSP_CTRL__GAMMA_UPDATE_EN BIT(22)
++
+ #define RK3588_VP_CLK_CTRL__DCLK_OUT_DIV GENMASK(3, 2)
+ #define RK3588_VP_CLK_CTRL__DCLK_CORE_DIV GENMASK(1, 0)
+
+@@ -470,6 +473,8 @@ enum dst_factor_mode {
+ #define RK3588_DSP_IF_POL__DP1_PIN_POL GENMASK(14, 12)
+ #define RK3588_DSP_IF_POL__DP0_PIN_POL GENMASK(10, 8)
+
++#define RK3588_LUT_PORT_SEL__GAMMA_AHB_WRITE_SEL GENMASK(13, 12)
++
+ #define RK3568_VP0_MIPI_CTRL__DCLK_DIV2_PHASE_LOCK BIT(5)
+ #define RK3568_VP0_MIPI_CTRL__DCLK_DIV2 BIT(4)
+
diff --git a/lede/target/linux/rockchip/patches-6.12/041-12-v6.14-drm-rockchip-analogix_dp-allow-to-work-without-panel.patch b/lede/target/linux/rockchip/patches-6.12/041-12-v6.14-drm-rockchip-analogix_dp-allow-to-work-without-panel.patch
new file mode 100644
index 0000000000..81ba60625a
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-12-v6.14-drm-rockchip-analogix_dp-allow-to-work-without-panel.patch
@@ -0,0 +1,28 @@
+From 86caee745e4506528801d9542db54e7b4c4d834b Mon Sep 17 00:00:00 2001
+From: Lucas Stach
+Date: Fri, 21 Jun 2024 22:17:55 +0200
+Subject: [PATCH] drm/rockchip: analogix_dp: allow to work without panel
+
+When the DP output is routed to a external connector there is no
+need for a fixed panel, as the panel may be detected via EDID on
+the AUX channel. Allow to continue probing if no panel reference
+is present.
+
+Signed-off-by: Lucas Stach
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20240621201755.500271-1-l.stach@pengutronix.de
+---
+ drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
++++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
+@@ -386,7 +386,7 @@ static int rockchip_dp_probe(struct plat
+ return -ENODEV;
+
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
+- if (ret < 0)
++ if (ret < 0 && ret != -ENODEV)
+ return ret;
+
+ dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
diff --git a/lede/target/linux/rockchip/patches-6.12/041-13-v6.14-drm-rockchip-avoid-64-bit-division.patch b/lede/target/linux/rockchip/patches-6.12/041-13-v6.14-drm-rockchip-avoid-64-bit-division.patch
new file mode 100644
index 0000000000..cdb64e617a
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-13-v6.14-drm-rockchip-avoid-64-bit-division.patch
@@ -0,0 +1,32 @@
+From 4b64b4a81fcd51f570c046cf904aef19ec756d45 Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann
+Date: Fri, 18 Oct 2024 15:10:10 +0000
+Subject: [PATCH] drm/rockchip: avoid 64-bit division
+
+Dividing a 64-bit integer prevents building this for 32-bit targets:
+
+ERROR: modpost: "__aeabi_uldivmod" [drivers/gpu/drm/rockchip/rockchipdrm.ko] undefined!
+
+As this function is not performance criticial, just Use the div_u64() helper.
+
+Fixes: 128a9bf8ace2 ("drm/rockchip: Add basic RK3588 HDMI output support")
+Signed-off-by: Arnd Bergmann
+Reviewed-by: Dmitry Baryshkov
+Reviewed-by: Nathan Chancellor
+Link: https://lore.kernel.org/r/20241018151016.3496613-1-arnd@kernel.org
+Signed-off-by: Liviu Dudau
+---
+ drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
++++ b/drivers/gpu/drm/rockchip/dw_hdmi_qp-rockchip.c
+@@ -82,7 +82,7 @@ static void dw_hdmi_qp_rockchip_encoder_
+ * comment in rk_hdptx_phy_power_on() from
+ * drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+ */
+- phy_set_bus_width(hdmi->phy, rate / 100);
++ phy_set_bus_width(hdmi->phy, div_u64(rate, 100));
+ }
+ }
+
diff --git a/lede/target/linux/rockchip/patches-6.12/041-14-v6.14-drm-bridge-synopsys-Add-MIPI-DSI2-host-controller-bridge.patch b/lede/target/linux/rockchip/patches-6.12/041-14-v6.14-drm-bridge-synopsys-Add-MIPI-DSI2-host-controller-bridge.patch
new file mode 100644
index 0000000000..6a8ef79e94
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-14-v6.14-drm-bridge-synopsys-Add-MIPI-DSI2-host-controller-bridge.patch
@@ -0,0 +1,1179 @@
+From 0d6d86253fef1e6b1e38a54db14bcbea9d0d9ca4 Mon Sep 17 00:00:00 2001
+From: Heiko Stuebner
+Date: Tue, 10 Dec 2024 00:10:19 +0100
+Subject: [PATCH] drm/bridge/synopsys: Add MIPI DSI2 host controller bridge
+
+Add a Synopsys Designware MIPI DSI host DRM bridge driver for their
+DSI2 host controller, based on the Rockchip version from the driver
+rockchip/dw-mipi-dsi2.c in their vendor-kernel with phy & bridge APIs.
+
+While the driver is heavily modelled after the previous IP, the register
+set of this DSI2 controller is completely different and there are also
+additional properties like the variable-width phy interface.
+
+Tested-by: Daniel Semkowicz
+Tested-by: Dmitry Yashin
+Reviewed-by: Neil Armstrong
+Signed-off-by: Heiko Stuebner
+Reviewed-by: Andy Yan
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20241209231021.2180582-2-heiko@sntech.de
+---
+ drivers/gpu/drm/bridge/synopsys/Kconfig | 6 +
+ drivers/gpu/drm/bridge/synopsys/Makefile | 1 +
+ .../gpu/drm/bridge/synopsys/dw-mipi-dsi2.c | 1030 +++++++++++++++++
+ include/drm/bridge/dw_mipi_dsi2.h | 95 ++
+ 4 files changed, 1132 insertions(+)
+ create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c
+ create mode 100644 include/drm/bridge/dw_mipi_dsi2.h
+
+--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
++++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
+@@ -59,3 +59,9 @@ config DRM_DW_MIPI_DSI
+ select DRM_KMS_HELPER
+ select DRM_MIPI_DSI
+ select DRM_PANEL_BRIDGE
++
++config DRM_DW_MIPI_DSI2
++ tristate
++ select DRM_KMS_HELPER
++ select DRM_MIPI_DSI
++ select DRM_PANEL_BRIDGE
+--- a/drivers/gpu/drm/bridge/synopsys/Makefile
++++ b/drivers/gpu/drm/bridge/synopsys/Makefile
+@@ -8,3 +8,4 @@ obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi
+ obj-$(CONFIG_DRM_DW_HDMI_QP) += dw-hdmi-qp.o
+
+ obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw-mipi-dsi.o
++obj-$(CONFIG_DRM_DW_MIPI_DSI2) += dw-mipi-dsi2.o
+--- /dev/null
++++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c
+@@ -0,0 +1,1030 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Copyright (c) 2024, Fuzhou Rockchip Electronics Co., Ltd
++ *
++ * Modified by Heiko Stuebner
++ * This generic Synopsys DesignWare MIPI DSI2 host driver is based on the
++ * Rockchip version from rockchip/dw-mipi-dsi2.c converted to use bridge APIs.
++ */
++
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++#include
++
++#include
++
++#include
++#include
++#include
++#include
++#include
++#include
++
++#define DSI2_PWR_UP 0x000c
++#define RESET 0
++#define POWER_UP BIT(0)
++#define CMD_TX_MODE(x) FIELD_PREP(BIT(24), x)
++#define DSI2_SOFT_RESET 0x0010
++#define SYS_RSTN BIT(2)
++#define PHY_RSTN BIT(1)
++#define IPI_RSTN BIT(0)
++#define INT_ST_MAIN 0x0014
++#define DSI2_MODE_CTRL 0x0018
++#define DSI2_MODE_STATUS 0x001c
++#define DSI2_CORE_STATUS 0x0020
++#define PRI_RD_DATA_AVAIL BIT(26)
++#define PRI_FIFOS_NOT_EMPTY BIT(25)
++#define PRI_BUSY BIT(24)
++#define CRI_RD_DATA_AVAIL BIT(18)
++#define CRT_FIFOS_NOT_EMPTY BIT(17)
++#define CRI_BUSY BIT(16)
++#define IPI_FIFOS_NOT_EMPTY BIT(9)
++#define IPI_BUSY BIT(8)
++#define CORE_FIFOS_NOT_EMPTY BIT(1)
++#define CORE_BUSY BIT(0)
++#define MANUAL_MODE_CFG 0x0024
++#define MANUAL_MODE_EN BIT(0)
++#define DSI2_TIMEOUT_HSTX_CFG 0x0048
++#define TO_HSTX(x) FIELD_PREP(GENMASK(15, 0), x)
++#define DSI2_TIMEOUT_HSTXRDY_CFG 0x004c
++#define TO_HSTXRDY(x) FIELD_PREP(GENMASK(15, 0), x)
++#define DSI2_TIMEOUT_LPRX_CFG 0x0050
++#define TO_LPRXRDY(x) FIELD_PREP(GENMASK(15, 0), x)
++#define DSI2_TIMEOUT_LPTXRDY_CFG 0x0054
++#define TO_LPTXRDY(x) FIELD_PREP(GENMASK(15, 0), x)
++#define DSI2_TIMEOUT_LPTXTRIG_CFG 0x0058
++#define TO_LPTXTRIG(x) FIELD_PREP(GENMASK(15, 0), x)
++#define DSI2_TIMEOUT_LPTXULPS_CFG 0x005c
++#define TO_LPTXULPS(x) FIELD_PREP(GENMASK(15, 0), x)
++#define DSI2_TIMEOUT_BTA_CFG 0x60
++#define TO_BTA(x) FIELD_PREP(GENMASK(15, 0), x)
++
++#define DSI2_PHY_MODE_CFG 0x0100
++#define PPI_WIDTH(x) FIELD_PREP(GENMASK(9, 8), x)
++#define PHY_LANES(x) FIELD_PREP(GENMASK(5, 4), (x) - 1)
++#define PHY_TYPE(x) FIELD_PREP(BIT(0), x)
++#define DSI2_PHY_CLK_CFG 0X0104
++#define PHY_LPTX_CLK_DIV(x) FIELD_PREP(GENMASK(12, 8), x)
++#define CLK_TYPE_MASK BIT(0)
++#define NON_CONTINUOUS_CLK BIT(0)
++#define CONTINUOUS_CLK 0
++#define DSI2_PHY_LP2HS_MAN_CFG 0x010c
++#define PHY_LP2HS_TIME(x) FIELD_PREP(GENMASK(28, 0), x)
++#define DSI2_PHY_HS2LP_MAN_CFG 0x0114
++#define PHY_HS2LP_TIME(x) FIELD_PREP(GENMASK(28, 0), x)
++#define DSI2_PHY_MAX_RD_T_MAN_CFG 0x011c
++#define PHY_MAX_RD_TIME(x) FIELD_PREP(GENMASK(26, 0), x)
++#define DSI2_PHY_ESC_CMD_T_MAN_CFG 0x0124
++#define PHY_ESC_CMD_TIME(x) FIELD_PREP(GENMASK(28, 0), x)
++#define DSI2_PHY_ESC_BYTE_T_MAN_CFG 0x012c
++#define PHY_ESC_BYTE_TIME(x) FIELD_PREP(GENMASK(28, 0), x)
++
++#define DSI2_PHY_IPI_RATIO_MAN_CFG 0x0134
++#define PHY_IPI_RATIO(x) FIELD_PREP(GENMASK(21, 0), x)
++#define DSI2_PHY_SYS_RATIO_MAN_CFG 0x013C
++#define PHY_SYS_RATIO(x) FIELD_PREP(GENMASK(16, 0), x)
++
++#define DSI2_DSI_GENERAL_CFG 0x0200
++#define BTA_EN BIT(1)
++#define EOTP_TX_EN BIT(0)
++#define DSI2_DSI_VCID_CFG 0x0204
++#define TX_VCID(x) FIELD_PREP(GENMASK(1, 0), x)
++#define DSI2_DSI_SCRAMBLING_CFG 0x0208
++#define SCRAMBLING_SEED(x) FIELD_PREP(GENMASK(31, 16), x)
++#define SCRAMBLING_EN BIT(0)
++#define DSI2_DSI_VID_TX_CFG 0x020c
++#define LPDT_DISPLAY_CMD_EN BIT(20)
++#define BLK_VFP_HS_EN BIT(14)
++#define BLK_VBP_HS_EN BIT(13)
++#define BLK_VSA_HS_EN BIT(12)
++#define BLK_HFP_HS_EN BIT(6)
++#define BLK_HBP_HS_EN BIT(5)
++#define BLK_HSA_HS_EN BIT(4)
++#define VID_MODE_TYPE(x) FIELD_PREP(GENMASK(1, 0), x)
++#define DSI2_CRI_TX_HDR 0x02c0
++#define CMD_TX_MODE(x) FIELD_PREP(BIT(24), x)
++#define DSI2_CRI_TX_PLD 0x02c4
++#define DSI2_CRI_RX_HDR 0x02c8
++#define DSI2_CRI_RX_PLD 0x02cc
++
++#define DSI2_IPI_COLOR_MAN_CFG 0x0300
++#define IPI_DEPTH(x) FIELD_PREP(GENMASK(7, 4), x)
++#define IPI_DEPTH_5_6_5_BITS 0x02
++#define IPI_DEPTH_6_BITS 0x03
++#define IPI_DEPTH_8_BITS 0x05
++#define IPI_DEPTH_10_BITS 0x06
++#define IPI_FORMAT(x) FIELD_PREP(GENMASK(3, 0), x)
++#define IPI_FORMAT_RGB 0x0
++#define IPI_FORMAT_DSC 0x0b
++#define DSI2_IPI_VID_HSA_MAN_CFG 0x0304
++#define VID_HSA_TIME(x) FIELD_PREP(GENMASK(29, 0), x)
++#define DSI2_IPI_VID_HBP_MAN_CFG 0x030c
++#define VID_HBP_TIME(x) FIELD_PREP(GENMASK(29, 0), x)
++#define DSI2_IPI_VID_HACT_MAN_CFG 0x0314
++#define VID_HACT_TIME(x) FIELD_PREP(GENMASK(29, 0), x)
++#define DSI2_IPI_VID_HLINE_MAN_CFG 0x031c
++#define VID_HLINE_TIME(x) FIELD_PREP(GENMASK(29, 0), x)
++#define DSI2_IPI_VID_VSA_MAN_CFG 0x0324
++#define VID_VSA_LINES(x) FIELD_PREP(GENMASK(9, 0), x)
++#define DSI2_IPI_VID_VBP_MAN_CFG 0X032C
++#define VID_VBP_LINES(x) FIELD_PREP(GENMASK(9, 0), x)
++#define DSI2_IPI_VID_VACT_MAN_CFG 0X0334
++#define VID_VACT_LINES(x) FIELD_PREP(GENMASK(13, 0), x)
++#define DSI2_IPI_VID_VFP_MAN_CFG 0X033C
++#define VID_VFP_LINES(x) FIELD_PREP(GENMASK(9, 0), x)
++#define DSI2_IPI_PIX_PKT_CFG 0x0344
++#define MAX_PIX_PKT(x) FIELD_PREP(GENMASK(15, 0), x)
++
++#define DSI2_INT_ST_PHY 0x0400
++#define DSI2_INT_MASK_PHY 0x0404
++#define DSI2_INT_ST_TO 0x0410
++#define DSI2_INT_MASK_TO 0x0414
++#define DSI2_INT_ST_ACK 0x0420
++#define DSI2_INT_MASK_ACK 0x0424
++#define DSI2_INT_ST_IPI 0x0430
++#define DSI2_INT_MASK_IPI 0x0434
++#define DSI2_INT_ST_FIFO 0x0440
++#define DSI2_INT_MASK_FIFO 0x0444
++#define DSI2_INT_ST_PRI 0x0450
++#define DSI2_INT_MASK_PRI 0x0454
++#define DSI2_INT_ST_CRI 0x0460
++#define DSI2_INT_MASK_CRI 0x0464
++#define DSI2_INT_FORCE_CRI 0x0468
++#define DSI2_MAX_REGISGER DSI2_INT_FORCE_CRI
++
++#define MODE_STATUS_TIMEOUT_US 10000
++#define CMD_PKT_STATUS_TIMEOUT_US 20000
++
++enum vid_mode_type {
++ VID_MODE_TYPE_NON_BURST_SYNC_PULSES,
++ VID_MODE_TYPE_NON_BURST_SYNC_EVENTS,
++ VID_MODE_TYPE_BURST,
++};
++
++enum mode_ctrl {
++ IDLE_MODE,
++ AUTOCALC_MODE,
++ COMMAND_MODE,
++ VIDEO_MODE,
++ DATA_STREAM_MODE,
++ VIDEO_TEST_MODE,
++ DATA_STREAM_TEST_MODE,
++};
++
++enum ppi_width {
++ PPI_WIDTH_8_BITS,
++ PPI_WIDTH_16_BITS,
++ PPI_WIDTH_32_BITS,
++};
++
++struct cmd_header {
++ u8 cmd_type;
++ u8 delay;
++ u8 payload_length;
++};
++
++struct dw_mipi_dsi2 {
++ struct drm_bridge bridge;
++ struct mipi_dsi_host dsi_host;
++ struct drm_bridge *panel_bridge;
++ struct device *dev;
++ struct regmap *regmap;
++ struct clk *pclk;
++ struct clk *sys_clk;
++
++ unsigned int lane_mbps; /* per lane */
++ u32 channel;
++ u32 lanes;
++ u32 format;
++ unsigned long mode_flags;
++
++ struct drm_display_mode mode;
++ const struct dw_mipi_dsi2_plat_data *plat_data;
++};
++
++static inline struct dw_mipi_dsi2 *host_to_dsi2(struct mipi_dsi_host *host)
++{
++ return container_of(host, struct dw_mipi_dsi2, dsi_host);
++}
++
++static inline struct dw_mipi_dsi2 *bridge_to_dsi2(struct drm_bridge *bridge)
++{
++ return container_of(bridge, struct dw_mipi_dsi2, bridge);
++}
++
++static int cri_fifos_wait_avail(struct dw_mipi_dsi2 *dsi2)
++{
++ u32 sts, mask;
++ int ret;
++
++ mask = CRI_BUSY | CRT_FIFOS_NOT_EMPTY;
++ ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_CORE_STATUS, sts,
++ !(sts & mask), 0, CMD_PKT_STATUS_TIMEOUT_US);
++ if (ret < 0) {
++ dev_err(dsi2->dev, "command interface is busy\n");
++ return ret;
++ }
++
++ return 0;
++}
++
++static void dw_mipi_dsi2_set_vid_mode(struct dw_mipi_dsi2 *dsi2)
++{
++ u32 val = 0, mode;
++ int ret;
++
++ if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HFP)
++ val |= BLK_HFP_HS_EN;
++
++ if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HBP)
++ val |= BLK_HBP_HS_EN;
++
++ if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HSA)
++ val |= BLK_HSA_HS_EN;
++
++ if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
++ val |= VID_MODE_TYPE_BURST;
++ else if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
++ val |= VID_MODE_TYPE_NON_BURST_SYNC_PULSES;
++ else
++ val |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS;
++
++ regmap_write(dsi2->regmap, DSI2_DSI_VID_TX_CFG, val);
++
++ regmap_write(dsi2->regmap, DSI2_MODE_CTRL, VIDEO_MODE);
++ ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_MODE_STATUS,
++ mode, mode & VIDEO_MODE,
++ 1000, MODE_STATUS_TIMEOUT_US);
++ if (ret < 0)
++ dev_err(dsi2->dev, "failed to enter video mode\n");
++}
++
++static void dw_mipi_dsi2_set_data_stream_mode(struct dw_mipi_dsi2 *dsi2)
++{
++ u32 mode;
++ int ret;
++
++ regmap_write(dsi2->regmap, DSI2_MODE_CTRL, DATA_STREAM_MODE);
++ ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_MODE_STATUS,
++ mode, mode & DATA_STREAM_MODE,
++ 1000, MODE_STATUS_TIMEOUT_US);
++ if (ret < 0)
++ dev_err(dsi2->dev, "failed to enter data stream mode\n");
++}
++
++static void dw_mipi_dsi2_set_cmd_mode(struct dw_mipi_dsi2 *dsi2)
++{
++ u32 mode;
++ int ret;
++
++ regmap_write(dsi2->regmap, DSI2_MODE_CTRL, COMMAND_MODE);
++ ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_MODE_STATUS,
++ mode, mode & COMMAND_MODE,
++ 1000, MODE_STATUS_TIMEOUT_US);
++ if (ret < 0)
++ dev_err(dsi2->dev, "failed to enter data stream mode\n");
++}
++
++static void dw_mipi_dsi2_host_softrst(struct dw_mipi_dsi2 *dsi2)
++{
++ regmap_write(dsi2->regmap, DSI2_SOFT_RESET, 0x0);
++ usleep_range(50, 100);
++ regmap_write(dsi2->regmap, DSI2_SOFT_RESET,
++ SYS_RSTN | PHY_RSTN | IPI_RSTN);
++}
++
++static void dw_mipi_dsi2_phy_clk_mode_cfg(struct dw_mipi_dsi2 *dsi2)
++{
++ u32 sys_clk, esc_clk_div;
++ u32 val = 0;
++
++ /*
++ * clk_type should be NON_CONTINUOUS_CLK before
++ * initial deskew calibration be sent.
++ */
++ val |= NON_CONTINUOUS_CLK;
++
++ /* The maximum value of the escape clock frequency is 20MHz */
++ sys_clk = clk_get_rate(dsi2->sys_clk) / USEC_PER_SEC;
++ esc_clk_div = DIV_ROUND_UP(sys_clk, 20 * 2);
++ val |= PHY_LPTX_CLK_DIV(esc_clk_div);
++
++ regmap_write(dsi2->regmap, DSI2_PHY_CLK_CFG, val);
++}
++
++static void dw_mipi_dsi2_phy_ratio_cfg(struct dw_mipi_dsi2 *dsi2)
++{
++ struct drm_display_mode *mode = &dsi2->mode;
++ u64 sys_clk = clk_get_rate(dsi2->sys_clk);
++ u64 pixel_clk, ipi_clk, phy_hsclk;
++ u64 tmp;
++
++ /*
++ * in DPHY mode, the phy_hstx_clk is exactly 1/16 the Lane high-speed
++ * data rate; In CPHY mode, the phy_hstx_clk is exactly 1/7 the trio
++ * high speed symbol rate.
++ */
++ phy_hsclk = DIV_ROUND_CLOSEST_ULL(dsi2->lane_mbps * USEC_PER_SEC, 16);
++
++ /* IPI_RATIO_MAN_CFG = PHY_HSTX_CLK / IPI_CLK */
++ pixel_clk = mode->crtc_clock * MSEC_PER_SEC;
++ ipi_clk = pixel_clk / 4;
++
++ tmp = DIV_ROUND_CLOSEST_ULL(phy_hsclk << 16, ipi_clk);
++ regmap_write(dsi2->regmap, DSI2_PHY_IPI_RATIO_MAN_CFG,
++ PHY_IPI_RATIO(tmp));
++
++ /*
++ * SYS_RATIO_MAN_CFG = MIPI_DCPHY_HSCLK_Freq / MIPI_DCPHY_HSCLK_Freq
++ */
++ tmp = DIV_ROUND_CLOSEST_ULL(phy_hsclk << 16, sys_clk);
++ regmap_write(dsi2->regmap, DSI2_PHY_SYS_RATIO_MAN_CFG,
++ PHY_SYS_RATIO(tmp));
++}
++
++static void dw_mipi_dsi2_lp2hs_or_hs2lp_cfg(struct dw_mipi_dsi2 *dsi2)
++{
++ const struct dw_mipi_dsi2_phy_ops *phy_ops = dsi2->plat_data->phy_ops;
++ struct dw_mipi_dsi2_phy_timing timing;
++ int ret;
++
++ ret = phy_ops->get_timing(dsi2->plat_data->priv_data,
++ dsi2->lane_mbps, &timing);
++ if (ret)
++ dev_err(dsi2->dev, "Retrieving phy timings failed\n");
++
++ regmap_write(dsi2->regmap, DSI2_PHY_LP2HS_MAN_CFG, PHY_LP2HS_TIME(timing.data_lp2hs));
++ regmap_write(dsi2->regmap, DSI2_PHY_HS2LP_MAN_CFG, PHY_HS2LP_TIME(timing.data_hs2lp));
++}
++
++static void dw_mipi_dsi2_phy_init(struct dw_mipi_dsi2 *dsi2)
++{
++ const struct dw_mipi_dsi2_phy_ops *phy_ops = dsi2->plat_data->phy_ops;
++ struct dw_mipi_dsi2_phy_iface iface;
++ u32 val = 0;
++
++ phy_ops->get_interface(dsi2->plat_data->priv_data, &iface);
++
++ switch (iface.ppi_width) {
++ case 8:
++ val |= PPI_WIDTH(PPI_WIDTH_8_BITS);
++ break;
++ case 16:
++ val |= PPI_WIDTH(PPI_WIDTH_16_BITS);
++ break;
++ case 32:
++ val |= PPI_WIDTH(PPI_WIDTH_32_BITS);
++ break;
++ default:
++ /* Caught in probe */
++ break;
++ }
++
++ val |= PHY_LANES(dsi2->lanes);
++ val |= PHY_TYPE(DW_MIPI_DSI2_DPHY);
++ regmap_write(dsi2->regmap, DSI2_PHY_MODE_CFG, val);
++
++ dw_mipi_dsi2_phy_clk_mode_cfg(dsi2);
++ dw_mipi_dsi2_phy_ratio_cfg(dsi2);
++ dw_mipi_dsi2_lp2hs_or_hs2lp_cfg(dsi2);
++
++ /* phy configuration 8 - 10 */
++}
++
++static void dw_mipi_dsi2_tx_option_set(struct dw_mipi_dsi2 *dsi2)
++{
++ u32 val;
++
++ val = BTA_EN | EOTP_TX_EN;
++
++ if (dsi2->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
++ val &= ~EOTP_TX_EN;
++
++ regmap_write(dsi2->regmap, DSI2_DSI_GENERAL_CFG, val);
++ regmap_write(dsi2->regmap, DSI2_DSI_VCID_CFG, TX_VCID(dsi2->channel));
++}
++
++static void dw_mipi_dsi2_ipi_color_coding_cfg(struct dw_mipi_dsi2 *dsi2)
++{
++ u32 val, color_depth;
++
++ switch (dsi2->format) {
++ case MIPI_DSI_FMT_RGB666:
++ case MIPI_DSI_FMT_RGB666_PACKED:
++ color_depth = IPI_DEPTH_6_BITS;
++ break;
++ case MIPI_DSI_FMT_RGB565:
++ color_depth = IPI_DEPTH_5_6_5_BITS;
++ break;
++ case MIPI_DSI_FMT_RGB888:
++ default:
++ color_depth = IPI_DEPTH_8_BITS;
++ break;
++ }
++
++ val = IPI_DEPTH(color_depth) |
++ IPI_FORMAT(IPI_FORMAT_RGB);
++ regmap_write(dsi2->regmap, DSI2_IPI_COLOR_MAN_CFG, val);
++}
++
++static void dw_mipi_dsi2_vertical_timing_config(struct dw_mipi_dsi2 *dsi2,
++ const struct drm_display_mode *mode)
++{
++ u32 vactive, vsa, vfp, vbp;
++
++ vactive = mode->vdisplay;
++ vsa = mode->vsync_end - mode->vsync_start;
++ vfp = mode->vsync_start - mode->vdisplay;
++ vbp = mode->vtotal - mode->vsync_end;
++
++ regmap_write(dsi2->regmap, DSI2_IPI_VID_VSA_MAN_CFG, VID_VSA_LINES(vsa));
++ regmap_write(dsi2->regmap, DSI2_IPI_VID_VBP_MAN_CFG, VID_VBP_LINES(vbp));
++ regmap_write(dsi2->regmap, DSI2_IPI_VID_VACT_MAN_CFG, VID_VACT_LINES(vactive));
++ regmap_write(dsi2->regmap, DSI2_IPI_VID_VFP_MAN_CFG, VID_VFP_LINES(vfp));
++}
++
++static void dw_mipi_dsi2_ipi_set(struct dw_mipi_dsi2 *dsi2)
++{
++ struct drm_display_mode *mode = &dsi2->mode;
++ u32 hline, hsa, hbp, hact;
++ u64 hline_time, hsa_time, hbp_time, hact_time, tmp;
++ u64 pixel_clk, phy_hs_clk;
++ u16 val;
++
++ val = mode->hdisplay;
++
++ regmap_write(dsi2->regmap, DSI2_IPI_PIX_PKT_CFG, MAX_PIX_PKT(val));
++
++ dw_mipi_dsi2_ipi_color_coding_cfg(dsi2);
++
++ /*
++ * if the controller is intended to operate in data stream mode,
++ * no more steps are required.
++ */
++ if (!(dsi2->mode_flags & MIPI_DSI_MODE_VIDEO))
++ return;
++
++ hact = mode->hdisplay;
++ hsa = mode->hsync_end - mode->hsync_start;
++ hbp = mode->htotal - mode->hsync_end;
++ hline = mode->htotal;
++
++ pixel_clk = mode->crtc_clock * MSEC_PER_SEC;
++
++ phy_hs_clk = DIV_ROUND_CLOSEST_ULL(dsi2->lane_mbps * USEC_PER_SEC, 16);
++
++ tmp = hsa * phy_hs_clk;
++ hsa_time = DIV_ROUND_CLOSEST_ULL(tmp << 16, pixel_clk);
++ regmap_write(dsi2->regmap, DSI2_IPI_VID_HSA_MAN_CFG, VID_HSA_TIME(hsa_time));
++
++ tmp = hbp * phy_hs_clk;
++ hbp_time = DIV_ROUND_CLOSEST_ULL(tmp << 16, pixel_clk);
++ regmap_write(dsi2->regmap, DSI2_IPI_VID_HBP_MAN_CFG, VID_HBP_TIME(hbp_time));
++
++ tmp = hact * phy_hs_clk;
++ hact_time = DIV_ROUND_CLOSEST_ULL(tmp << 16, pixel_clk);
++ regmap_write(dsi2->regmap, DSI2_IPI_VID_HACT_MAN_CFG, VID_HACT_TIME(hact_time));
++
++ tmp = hline * phy_hs_clk;
++ hline_time = DIV_ROUND_CLOSEST_ULL(tmp << 16, pixel_clk);
++ regmap_write(dsi2->regmap, DSI2_IPI_VID_HLINE_MAN_CFG, VID_HLINE_TIME(hline_time));
++
++ dw_mipi_dsi2_vertical_timing_config(dsi2, mode);
++}
++
++static void
++dw_mipi_dsi2_work_mode(struct dw_mipi_dsi2 *dsi2, u32 mode)
++{
++ /*
++ * select controller work in Manual mode
++ * Manual: MANUAL_MODE_EN
++ * Automatic: 0
++ */
++ regmap_write(dsi2->regmap, MANUAL_MODE_CFG, mode);
++}
++
++static int dw_mipi_dsi2_host_attach(struct mipi_dsi_host *host,
++ struct mipi_dsi_device *device)
++{
++ struct dw_mipi_dsi2 *dsi2 = host_to_dsi2(host);
++ const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
++ struct drm_bridge *bridge;
++ int ret;
++
++ if (device->lanes > dsi2->plat_data->max_data_lanes) {
++ dev_err(dsi2->dev, "the number of data lanes(%u) is too many\n",
++ device->lanes);
++ return -EINVAL;
++ }
++
++ dsi2->lanes = device->lanes;
++ dsi2->channel = device->channel;
++ dsi2->format = device->format;
++ dsi2->mode_flags = device->mode_flags;
++
++ bridge = devm_drm_of_get_bridge(dsi2->dev, dsi2->dev->of_node, 1, 0);
++ if (IS_ERR(bridge))
++ return PTR_ERR(bridge);
++
++ bridge->pre_enable_prev_first = true;
++ dsi2->panel_bridge = bridge;
++
++ drm_bridge_add(&dsi2->bridge);
++
++ if (pdata->host_ops && pdata->host_ops->attach) {
++ ret = pdata->host_ops->attach(pdata->priv_data, device);
++ if (ret < 0)
++ return ret;
++ }
++
++ return 0;
++}
++
++static int dw_mipi_dsi2_host_detach(struct mipi_dsi_host *host,
++ struct mipi_dsi_device *device)
++{
++ struct dw_mipi_dsi2 *dsi2 = host_to_dsi2(host);
++ const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
++ int ret;
++
++ if (pdata->host_ops && pdata->host_ops->detach) {
++ ret = pdata->host_ops->detach(pdata->priv_data, device);
++ if (ret < 0)
++ return ret;
++ }
++
++ drm_bridge_remove(&dsi2->bridge);
++
++ drm_of_panel_bridge_remove(host->dev->of_node, 1, 0);
++
++ return 0;
++}
++
++static int dw_mipi_dsi2_gen_pkt_hdr_write(struct dw_mipi_dsi2 *dsi2,
++ u32 hdr_val, bool lpm)
++{
++ int ret;
++
++ regmap_write(dsi2->regmap, DSI2_CRI_TX_HDR, hdr_val | CMD_TX_MODE(lpm));
++
++ ret = cri_fifos_wait_avail(dsi2);
++ if (ret) {
++ dev_err(dsi2->dev, "failed to write command header\n");
++ return ret;
++ }
++
++ return 0;
++}
++
++static int dw_mipi_dsi2_write(struct dw_mipi_dsi2 *dsi2,
++ const struct mipi_dsi_packet *packet, bool lpm)
++{
++ const u8 *tx_buf = packet->payload;
++ int len = packet->payload_length, pld_data_bytes = sizeof(u32);
++ __le32 word;
++
++ /* Send payload */
++ while (len) {
++ if (len < pld_data_bytes) {
++ word = 0;
++ memcpy(&word, tx_buf, len);
++ regmap_write(dsi2->regmap, DSI2_CRI_TX_PLD, le32_to_cpu(word));
++ len = 0;
++ } else {
++ memcpy(&word, tx_buf, pld_data_bytes);
++ regmap_write(dsi2->regmap, DSI2_CRI_TX_PLD, le32_to_cpu(word));
++ tx_buf += pld_data_bytes;
++ len -= pld_data_bytes;
++ }
++ }
++
++ word = 0;
++ memcpy(&word, packet->header, sizeof(packet->header));
++ return dw_mipi_dsi2_gen_pkt_hdr_write(dsi2, le32_to_cpu(word), lpm);
++}
++
++static int dw_mipi_dsi2_read(struct dw_mipi_dsi2 *dsi2,
++ const struct mipi_dsi_msg *msg)
++{
++ u8 *payload = msg->rx_buf;
++ int i, j, ret, len = msg->rx_len;
++ u8 data_type;
++ u16 wc;
++ u32 val;
++
++ ret = regmap_read_poll_timeout(dsi2->regmap, DSI2_CORE_STATUS,
++ val, val & CRI_RD_DATA_AVAIL,
++ 100, CMD_PKT_STATUS_TIMEOUT_US);
++ if (ret) {
++ dev_err(dsi2->dev, "CRI has no available read data\n");
++ return ret;
++ }
++
++ regmap_read(dsi2->regmap, DSI2_CRI_RX_HDR, &val);
++ data_type = val & 0x3f;
++
++ if (mipi_dsi_packet_format_is_short(data_type)) {
++ for (i = 0; i < len && i < 2; i++)
++ payload[i] = (val >> (8 * (i + 1))) & 0xff;
++
++ return 0;
++ }
++
++ wc = (val >> 8) & 0xffff;
++ /* Receive payload */
++ for (i = 0; i < len && i < wc; i += 4) {
++ regmap_read(dsi2->regmap, DSI2_CRI_RX_PLD, &val);
++ for (j = 0; j < 4 && j + i < len && j + i < wc; j++)
++ payload[i + j] = val >> (8 * j);
++ }
++
++ return 0;
++}
++
++static ssize_t dw_mipi_dsi2_host_transfer(struct mipi_dsi_host *host,
++ const struct mipi_dsi_msg *msg)
++{
++ struct dw_mipi_dsi2 *dsi2 = host_to_dsi2(host);
++ bool lpm = msg->flags & MIPI_DSI_MSG_USE_LPM;
++ struct mipi_dsi_packet packet;
++ int ret, nb_bytes;
++
++ regmap_update_bits(dsi2->regmap, DSI2_DSI_VID_TX_CFG,
++ LPDT_DISPLAY_CMD_EN,
++ lpm ? LPDT_DISPLAY_CMD_EN : 0);
++
++ /* create a packet to the DSI protocol */
++ ret = mipi_dsi_create_packet(&packet, msg);
++ if (ret) {
++ dev_err(dsi2->dev, "failed to create packet: %d\n", ret);
++ return ret;
++ }
++
++ ret = cri_fifos_wait_avail(dsi2);
++ if (ret)
++ return ret;
++
++ ret = dw_mipi_dsi2_write(dsi2, &packet, lpm);
++ if (ret)
++ return ret;
++
++ if (msg->rx_buf && msg->rx_len) {
++ ret = dw_mipi_dsi2_read(dsi2, msg);
++ if (ret < 0)
++ return ret;
++ nb_bytes = msg->rx_len;
++ } else {
++ nb_bytes = packet.size;
++ }
++
++ return nb_bytes;
++}
++
++static const struct mipi_dsi_host_ops dw_mipi_dsi2_host_ops = {
++ .attach = dw_mipi_dsi2_host_attach,
++ .detach = dw_mipi_dsi2_host_detach,
++ .transfer = dw_mipi_dsi2_host_transfer,
++};
++
++static u32 *
++dw_mipi_dsi2_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
++ struct drm_bridge_state *bridge_state,
++ struct drm_crtc_state *crtc_state,
++ struct drm_connector_state *conn_state,
++ u32 output_fmt,
++ unsigned int *num_input_fmts)
++{
++ struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
++ const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
++ u32 *input_fmts;
++
++ if (pdata->get_input_bus_fmts)
++ return pdata->get_input_bus_fmts(pdata->priv_data,
++ bridge, bridge_state,
++ crtc_state, conn_state,
++ output_fmt, num_input_fmts);
++
++ /* Fall back to MEDIA_BUS_FMT_FIXED as the only input format. */
++ input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
++ if (!input_fmts)
++ return NULL;
++ input_fmts[0] = MEDIA_BUS_FMT_FIXED;
++ *num_input_fmts = 1;
++
++ return input_fmts;
++}
++
++static int dw_mipi_dsi2_bridge_atomic_check(struct drm_bridge *bridge,
++ struct drm_bridge_state *bridge_state,
++ struct drm_crtc_state *crtc_state,
++ struct drm_connector_state *conn_state)
++{
++ struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
++ const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
++ bool ret;
++
++ bridge_state->input_bus_cfg.flags =
++ DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
++
++ if (pdata->mode_fixup) {
++ ret = pdata->mode_fixup(pdata->priv_data, &crtc_state->mode,
++ &crtc_state->adjusted_mode);
++ if (!ret) {
++ DRM_DEBUG_DRIVER("failed to fixup mode " DRM_MODE_FMT "\n",
++ DRM_MODE_ARG(&crtc_state->mode));
++ return -EINVAL;
++ }
++ }
++
++ return 0;
++}
++
++static void dw_mipi_dsi2_bridge_post_atomic_disable(struct drm_bridge *bridge,
++ struct drm_bridge_state *old_bridge_state)
++{
++ struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
++ const struct dw_mipi_dsi2_phy_ops *phy_ops = dsi2->plat_data->phy_ops;
++
++ regmap_write(dsi2->regmap, DSI2_IPI_PIX_PKT_CFG, 0);
++
++ /*
++ * Switch to command mode before panel-bridge post_disable &
++ * panel unprepare.
++ * Note: panel-bridge disable & panel disable has been called
++ * before by the drm framework.
++ */
++ dw_mipi_dsi2_set_cmd_mode(dsi2);
++
++ regmap_write(dsi2->regmap, DSI2_PWR_UP, RESET);
++
++ if (phy_ops->power_off)
++ phy_ops->power_off(dsi2->plat_data->priv_data);
++
++ clk_disable_unprepare(dsi2->sys_clk);
++ clk_disable_unprepare(dsi2->pclk);
++ pm_runtime_put(dsi2->dev);
++}
++
++static unsigned int dw_mipi_dsi2_get_lanes(struct dw_mipi_dsi2 *dsi2)
++{
++ /* single-dsi, so no other instance to consider */
++ return dsi2->lanes;
++}
++
++static void dw_mipi_dsi2_mode_set(struct dw_mipi_dsi2 *dsi2,
++ const struct drm_display_mode *adjusted_mode)
++{
++ const struct dw_mipi_dsi2_phy_ops *phy_ops = dsi2->plat_data->phy_ops;
++ void *priv_data = dsi2->plat_data->priv_data;
++ u32 lanes = dw_mipi_dsi2_get_lanes(dsi2);
++ int ret;
++
++ clk_prepare_enable(dsi2->pclk);
++ clk_prepare_enable(dsi2->sys_clk);
++
++ ret = phy_ops->get_lane_mbps(priv_data, adjusted_mode, dsi2->mode_flags,
++ lanes, dsi2->format, &dsi2->lane_mbps);
++ if (ret)
++ DRM_DEBUG_DRIVER("Phy get_lane_mbps() failed\n");
++
++ pm_runtime_get_sync(dsi2->dev);
++
++ dw_mipi_dsi2_host_softrst(dsi2);
++ regmap_write(dsi2->regmap, DSI2_PWR_UP, RESET);
++
++ dw_mipi_dsi2_work_mode(dsi2, MANUAL_MODE_EN);
++ dw_mipi_dsi2_phy_init(dsi2);
++
++ if (phy_ops->power_on)
++ phy_ops->power_on(dsi2->plat_data->priv_data);
++
++ dw_mipi_dsi2_tx_option_set(dsi2);
++
++ /*
++ * initial deskew calibration is send after phy_power_on,
++ * then we can configure clk_type.
++ */
++
++ regmap_update_bits(dsi2->regmap, DSI2_PHY_CLK_CFG, CLK_TYPE_MASK,
++ dsi2->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS ? NON_CONTINUOUS_CLK :
++ CONTINUOUS_CLK);
++
++ regmap_write(dsi2->regmap, DSI2_PWR_UP, POWER_UP);
++ dw_mipi_dsi2_set_cmd_mode(dsi2);
++
++ dw_mipi_dsi2_ipi_set(dsi2);
++}
++
++static void dw_mipi_dsi2_bridge_atomic_pre_enable(struct drm_bridge *bridge,
++ struct drm_bridge_state *old_bridge_state)
++{
++ struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
++
++ /* Power up the dsi ctl into a command mode */
++ dw_mipi_dsi2_mode_set(dsi2, &dsi2->mode);
++}
++
++static void dw_mipi_dsi2_bridge_mode_set(struct drm_bridge *bridge,
++ const struct drm_display_mode *mode,
++ const struct drm_display_mode *adjusted_mode)
++{
++ struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
++
++ /* Store the display mode for later use in pre_enable callback */
++ drm_mode_copy(&dsi2->mode, adjusted_mode);
++}
++
++static void dw_mipi_dsi2_bridge_atomic_enable(struct drm_bridge *bridge,
++ struct drm_bridge_state *old_bridge_state)
++{
++ struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
++
++ /* Switch to video mode for panel-bridge enable & panel enable */
++ if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO)
++ dw_mipi_dsi2_set_vid_mode(dsi2);
++ else
++ dw_mipi_dsi2_set_data_stream_mode(dsi2);
++}
++
++static enum drm_mode_status
++dw_mipi_dsi2_bridge_mode_valid(struct drm_bridge *bridge,
++ const struct drm_display_info *info,
++ const struct drm_display_mode *mode)
++{
++ struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
++ const struct dw_mipi_dsi2_plat_data *pdata = dsi2->plat_data;
++ enum drm_mode_status mode_status = MODE_OK;
++
++ if (pdata->mode_valid)
++ mode_status = pdata->mode_valid(pdata->priv_data, mode,
++ dsi2->mode_flags,
++ dw_mipi_dsi2_get_lanes(dsi2),
++ dsi2->format);
++
++ return mode_status;
++}
++
++static int dw_mipi_dsi2_bridge_attach(struct drm_bridge *bridge,
++ enum drm_bridge_attach_flags flags)
++{
++ struct dw_mipi_dsi2 *dsi2 = bridge_to_dsi2(bridge);
++
++ /* Set the encoder type as caller does not know it */
++ bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
++
++ /* Attach the panel-bridge to the dsi bridge */
++ return drm_bridge_attach(bridge->encoder, dsi2->panel_bridge, bridge,
++ flags);
++}
++
++static const struct drm_bridge_funcs dw_mipi_dsi2_bridge_funcs = {
++ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
++ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
++ .atomic_get_input_bus_fmts = dw_mipi_dsi2_bridge_atomic_get_input_bus_fmts,
++ .atomic_check = dw_mipi_dsi2_bridge_atomic_check,
++ .atomic_reset = drm_atomic_helper_bridge_reset,
++ .atomic_pre_enable = dw_mipi_dsi2_bridge_atomic_pre_enable,
++ .atomic_enable = dw_mipi_dsi2_bridge_atomic_enable,
++ .atomic_post_disable = dw_mipi_dsi2_bridge_post_atomic_disable,
++ .mode_set = dw_mipi_dsi2_bridge_mode_set,
++ .mode_valid = dw_mipi_dsi2_bridge_mode_valid,
++ .attach = dw_mipi_dsi2_bridge_attach,
++};
++
++static const struct regmap_config dw_mipi_dsi2_regmap_config = {
++ .name = "dsi2-host",
++ .reg_bits = 32,
++ .val_bits = 32,
++ .reg_stride = 4,
++ .fast_io = true,
++};
++
++static struct dw_mipi_dsi2 *
++__dw_mipi_dsi2_probe(struct platform_device *pdev,
++ const struct dw_mipi_dsi2_plat_data *plat_data)
++{
++ struct device *dev = &pdev->dev;
++ struct reset_control *apb_rst;
++ struct dw_mipi_dsi2 *dsi2;
++ int ret;
++
++ dsi2 = devm_kzalloc(dev, sizeof(*dsi2), GFP_KERNEL);
++ if (!dsi2)
++ return ERR_PTR(-ENOMEM);
++
++ dsi2->dev = dev;
++ dsi2->plat_data = plat_data;
++
++ if (!plat_data->phy_ops->init || !plat_data->phy_ops->get_lane_mbps ||
++ !plat_data->phy_ops->get_timing)
++ return dev_err_ptr_probe(dev, -ENODEV, "Phy not properly configured\n");
++
++ if (!plat_data->regmap) {
++ void __iomem *base = devm_platform_ioremap_resource(pdev, 0);
++
++ if (IS_ERR(base))
++ return dev_err_cast_probe(dev, base, "failed to registers\n");
++
++ dsi2->regmap = devm_regmap_init_mmio(dev, base,
++ &dw_mipi_dsi2_regmap_config);
++ if (IS_ERR(dsi2->regmap))
++ return dev_err_cast_probe(dev, dsi2->regmap, "failed to init regmap\n");
++ } else {
++ dsi2->regmap = plat_data->regmap;
++ }
++
++ dsi2->pclk = devm_clk_get(dev, "pclk");
++ if (IS_ERR(dsi2->pclk))
++ return dev_err_cast_probe(dev, dsi2->pclk, "Unable to get pclk\n");
++
++ dsi2->sys_clk = devm_clk_get(dev, "sys");
++ if (IS_ERR(dsi2->sys_clk))
++ return dev_err_cast_probe(dev, dsi2->sys_clk, "Unable to get sys_clk\n");
++
++ /*
++ * Note that the reset was not defined in the initial device tree, so
++ * we have to be prepared for it not being found.
++ */
++ apb_rst = devm_reset_control_get_optional_exclusive(dev, "apb");
++ if (IS_ERR(apb_rst))
++ return dev_err_cast_probe(dev, apb_rst, "Unable to get reset control\n");
++
++ if (apb_rst) {
++ ret = clk_prepare_enable(dsi2->pclk);
++ if (ret) {
++ dev_err(dev, "%s: Failed to enable pclk\n", __func__);
++ return ERR_PTR(ret);
++ }
++
++ reset_control_assert(apb_rst);
++ usleep_range(10, 20);
++ reset_control_deassert(apb_rst);
++
++ clk_disable_unprepare(dsi2->pclk);
++ }
++
++ devm_pm_runtime_enable(dev);
++
++ dsi2->dsi_host.ops = &dw_mipi_dsi2_host_ops;
++ dsi2->dsi_host.dev = dev;
++ ret = mipi_dsi_host_register(&dsi2->dsi_host);
++ if (ret) {
++ dev_err(dev, "Failed to register MIPI host: %d\n", ret);
++ pm_runtime_disable(dev);
++ return ERR_PTR(ret);
++ }
++
++ dsi2->bridge.driver_private = dsi2;
++ dsi2->bridge.funcs = &dw_mipi_dsi2_bridge_funcs;
++ dsi2->bridge.of_node = pdev->dev.of_node;
++
++ return dsi2;
++}
++
++static void __dw_mipi_dsi2_remove(struct dw_mipi_dsi2 *dsi2)
++{
++ mipi_dsi_host_unregister(&dsi2->dsi_host);
++}
++
++/*
++ * Probe/remove API, used to create the bridge instance.
++ */
++struct dw_mipi_dsi2 *
++dw_mipi_dsi2_probe(struct platform_device *pdev,
++ const struct dw_mipi_dsi2_plat_data *plat_data)
++{
++ return __dw_mipi_dsi2_probe(pdev, plat_data);
++}
++EXPORT_SYMBOL_GPL(dw_mipi_dsi2_probe);
++
++void dw_mipi_dsi2_remove(struct dw_mipi_dsi2 *dsi2)
++{
++ __dw_mipi_dsi2_remove(dsi2);
++}
++EXPORT_SYMBOL_GPL(dw_mipi_dsi2_remove);
++
++/*
++ * Bind/unbind API, used from platforms based on the component framework
++ * to attach the bridge to an encoder.
++ */
++int dw_mipi_dsi2_bind(struct dw_mipi_dsi2 *dsi2, struct drm_encoder *encoder)
++{
++ return drm_bridge_attach(encoder, &dsi2->bridge, NULL, 0);
++}
++EXPORT_SYMBOL_GPL(dw_mipi_dsi2_bind);
++
++void dw_mipi_dsi2_unbind(struct dw_mipi_dsi2 *dsi2)
++{
++}
++EXPORT_SYMBOL_GPL(dw_mipi_dsi2_unbind);
++
++MODULE_AUTHOR("Guochun Huang ");
++MODULE_AUTHOR("Heiko Stuebner ");
++MODULE_DESCRIPTION("DW MIPI DSI2 host controller driver");
++MODULE_LICENSE("GPL");
++MODULE_ALIAS("platform:dw-mipi-dsi2");
+--- /dev/null
++++ b/include/drm/bridge/dw_mipi_dsi2.h
+@@ -0,0 +1,95 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (c) 2024, Fuzhou Rockchip Electronics Co., Ltd
++ *
++ * Authors: Guochun Huang
++ * Heiko Stuebner
++ */
++
++#ifndef __DW_MIPI_DSI2__
++#define __DW_MIPI_DSI2__
++
++#include
++#include
++
++#include
++#include
++#include
++#include
++#include
++
++struct drm_display_mode;
++struct drm_encoder;
++struct dw_mipi_dsi2;
++struct mipi_dsi_device;
++struct platform_device;
++
++enum dw_mipi_dsi2_phy_type {
++ DW_MIPI_DSI2_DPHY,
++ DW_MIPI_DSI2_CPHY,
++};
++
++struct dw_mipi_dsi2_phy_iface {
++ int ppi_width;
++ enum dw_mipi_dsi2_phy_type phy_type;
++};
++
++struct dw_mipi_dsi2_phy_timing {
++ u32 data_hs2lp;
++ u32 data_lp2hs;
++};
++
++struct dw_mipi_dsi2_phy_ops {
++ int (*init)(void *priv_data);
++ void (*power_on)(void *priv_data);
++ void (*power_off)(void *priv_data);
++ void (*get_interface)(void *priv_data, struct dw_mipi_dsi2_phy_iface *iface);
++ int (*get_lane_mbps)(void *priv_data,
++ const struct drm_display_mode *mode,
++ unsigned long mode_flags, u32 lanes, u32 format,
++ unsigned int *lane_mbps);
++ int (*get_timing)(void *priv_data, unsigned int lane_mbps,
++ struct dw_mipi_dsi2_phy_timing *timing);
++ int (*get_esc_clk_rate)(void *priv_data, unsigned int *esc_clk_rate);
++};
++
++struct dw_mipi_dsi2_host_ops {
++ int (*attach)(void *priv_data,
++ struct mipi_dsi_device *dsi);
++ int (*detach)(void *priv_data,
++ struct mipi_dsi_device *dsi);
++};
++
++struct dw_mipi_dsi2_plat_data {
++ struct regmap *regmap;
++ unsigned int max_data_lanes;
++
++ enum drm_mode_status (*mode_valid)(void *priv_data,
++ const struct drm_display_mode *mode,
++ unsigned long mode_flags,
++ u32 lanes, u32 format);
++
++ bool (*mode_fixup)(void *priv_data, const struct drm_display_mode *mode,
++ struct drm_display_mode *adjusted_mode);
++
++ u32 *(*get_input_bus_fmts)(void *priv_data,
++ struct drm_bridge *bridge,
++ struct drm_bridge_state *bridge_state,
++ struct drm_crtc_state *crtc_state,
++ struct drm_connector_state *conn_state,
++ u32 output_fmt,
++ unsigned int *num_input_fmts);
++
++ const struct dw_mipi_dsi2_phy_ops *phy_ops;
++ const struct dw_mipi_dsi2_host_ops *host_ops;
++
++ void *priv_data;
++};
++
++struct dw_mipi_dsi2 *dw_mipi_dsi2_probe(struct platform_device *pdev,
++ const struct dw_mipi_dsi2_plat_data *plat_data);
++void dw_mipi_dsi2_remove(struct dw_mipi_dsi2 *dsi2);
++int dw_mipi_dsi2_bind(struct dw_mipi_dsi2 *dsi2, struct drm_encoder *encoder);
++void dw_mipi_dsi2_unbind(struct dw_mipi_dsi2 *dsi2);
++
++#endif /* __DW_MIPI_DSI2__ */
diff --git a/lede/target/linux/rockchip/patches-6.12/041-16-v6.14-drm-rockchip-Add-MIPI-DSI2-glue-driver-for-RK3588.patch b/lede/target/linux/rockchip/patches-6.12/041-16-v6.14-drm-rockchip-Add-MIPI-DSI2-glue-driver-for-RK3588.patch
new file mode 100644
index 0000000000..aab7e94a87
--- /dev/null
+++ b/lede/target/linux/rockchip/patches-6.12/041-16-v6.14-drm-rockchip-Add-MIPI-DSI2-glue-driver-for-RK3588.patch
@@ -0,0 +1,574 @@
+From 9f1e1e14f59de8e5a62226840abecbcdbd50221a Mon Sep 17 00:00:00 2001
+From: Heiko Stuebner
+Date: Tue, 10 Dec 2024 00:10:21 +0100
+Subject: [PATCH] drm/rockchip: Add MIPI DSI2 glue driver for RK3588
+
+This adds the glue code for the MIPI DSI2 bridge on Rockchip SoCs and
+enables its use on the RK3588.
+
+Right now the DSI2 controller is always paired with a DC-phy based on a
+Samsung IP, so the interface values are set statically for now.
+This stays true for the upcoming RK3576 as well.
+
+Tested-by: Daniel Semkowicz
+Tested-by: Dmitry Yashin
+Signed-off-by: Heiko Stuebner
+Reviewed-by: Andy Yan
+Signed-off-by: Heiko Stuebner
+Link: https://patchwork.freedesktop.org/patch/msgid/20241209231021.2180582-4-heiko@sntech.de
+---
+ drivers/gpu/drm/rockchip/Kconfig | 10 +
+ drivers/gpu/drm/rockchip/Makefile | 1 +
+ .../gpu/drm/rockchip/dw-mipi-dsi2-rockchip.c | 487 ++++++++++++++++++
+ drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 2 +
+ drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 +
+ 5 files changed, 501 insertions(+)
+ create mode 100644 drivers/gpu/drm/rockchip/dw-mipi-dsi2-rockchip.c
+
+--- a/drivers/gpu/drm/rockchip/Kconfig
++++ b/drivers/gpu/drm/rockchip/Kconfig
+@@ -11,6 +11,7 @@ config DRM_ROCKCHIP
+ select DRM_DW_HDMI if ROCKCHIP_DW_HDMI
+ select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP
+ select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
++ select DRM_DW_MIPI_DSI2 if ROCKCHIP_DW_MIPI_DSI2
+ select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI
+ select GENERIC_PHY_MIPI_DPHY if ROCKCHIP_DW_MIPI_DSI
+ select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC
+@@ -82,6 +83,15 @@ config ROCKCHIP_DW_MIPI_DSI
+ enable MIPI DSI on RK3288 or RK3399 based SoC, you should
+ select this option.
+
++config ROCKCHIP_DW_MIPI_DSI2
++ bool "Rockchip specific extensions for Synopsys DW MIPI DSI2"
++ select GENERIC_PHY_MIPI_DPHY
++ help
++ This selects support for Rockchip SoC specific extensions
++ for the Synopsys DesignWare DSI2 driver. If you want to
++ enable MIPI DSI on RK3576 or RK3588 based SoC, you should
++ select this option.
++
+ config ROCKCHIP_INNO_HDMI
+ bool "Rockchip specific extensions for Innosilicon HDMI"
+ select DRM_DISPLAY_HDMI_HELPER
+--- a/drivers/gpu/drm/rockchip/Makefile
++++ b/drivers/gpu/drm/rockchip/Makefile
+@@ -13,6 +13,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) +=
+ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o
++rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o
+ rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
+--- /dev/null
++++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi2-rockchip.c
+@@ -0,0 +1,487 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Copyright (C) 2024 Rockchip Electronics Co., Ltd.
++ * Author:
++ * Guochun Huang
++ * Heiko Stuebner
++ */
++
++#include
++#include
++#include
++#include
++#include