config/shared/platform/orangepi5plus/rk3588-v6.10-rc1.patch

12674 lines
400 KiB
Diff
Raw Normal View History

2024-06-11 20:53:31 +03:00
From 6b7104d36138a01859f23a81a565f1bbba89ecac Mon Sep 17 00:00:00 2001
From: Christopher Obbard <chris.obbard@collabora.com>
Date: Mon, 20 Feb 2023 16:59:04 +0000
Subject: [PATCH 01/54] [NOUPSTREAM] Add GitLab CI support
Add CI support. This will do the following:
1. Run dt_binding_check to make sure no major flaws were introduced in
the DT bindings
2. Run dtbs_check, for Rock 5A, Rock 5B and EVB1. If warnings are
generated the CI will report that as warning
3. Build a Kernel .deb package
4. Generate a test job for LAVA and run it
Co-developed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Co-developed-by: Sjoerd Simons <sjoerd@collabora.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
Signed-off-by: Christopher Obbard <chris.obbard@collabora.com>
---
.gitignore | 1 +
.gitlab-ci.yml | 142 ++++++++++++++++++++++++++++++++++++++++++++++
lava/testjob.yaml | 73 ++++++++++++++++++++++++
3 files changed, 216 insertions(+)
create mode 100644 .gitlab-ci.yml
create mode 100644 lava/testjob.yaml
diff --git a/.gitignore b/.gitignore
index c59dc60ba62e..59aa4da11b89 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,6 +104,7 @@ modules.order
!.kunitconfig
!.mailmap
!.rustfmt.toml
+!.gitlab-ci.yml
#
# Generated include files
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 000000000000..5582b71f0c67
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,142 @@
+default:
+ image: debian:testing
+ tags:
+ - bookworm
+
+stages:
+ - build
+ - test
+ - generate
+ - lava
+
+check devicetrees:
+ stage: build
+ tags:
+ - ultra-heavyweight
+ variables:
+ DEBIAN_FRONTEND: noninteractive
+ GIT_SUBMODULE_STRATEGY: normal
+ ARCH: arm64
+ DEFCONFIG: defconfig
+ CROSS_COMPILE: aarch64-linux-gnu-
+ DUT: rockchip/rk3588s-rock-5a.dtb rockchip/rk3588-rock-5b.dtb rockchip/rk3588-evb1-v10.dtb
+ before_script:
+ - apt update
+ - apt install -y devscripts
+ build-essential
+ crossbuild-essential-arm64
+ bc
+ bison
+ flex
+ swig
+ python3-dev
+ python3-pip
+ yamllint
+ - pip3 install --break-system-packages dtschema
+ script:
+ - make $DEFCONFIG
+ - make -j$(nproc) dt_binding_check
+ - make -j$(nproc) CHECK_DTBS=y $DUT 2> dt-warnings.txt
+ - if [[ $(cat dt-warnings.txt | wc -l) > 0 ]]; then cat dt-warnings.txt; exit 42; fi
+ allow_failure:
+ exit_codes:
+ - 42
+
+.build debian package:
+ stage: build
+ tags:
+ - ultra-heavyweight
+ cache:
+ when: on_success
+ key: $CI_COMMIT_REF_SLUG
+ paths:
+ - ccache
+ variables:
+ DEBIAN_FRONTEND: noninteractive
+ GIT_SUBMODULE_STRATEGY: normal
+ ARCH: amd64
+ DEFCONFIG: defconfig
+ CCACHE_BASEDIR: $CI_PROJECT_DIR
+ CCACHE_DIR: $CI_PROJECT_DIR/ccache
+ before_script:
+ - apt update
+ - apt install -y devscripts
+ build-essential
+ crossbuild-essential-arm64
+ bc
+ bison
+ ccache
+ flex
+ rsync
+ kmod
+ cpio
+ libelf-dev
+ libssl-dev
+ # Setup ccache
+ - export PATH="/usr/lib/ccache:$PATH"
+ - ccache -s
+ script:
+ - make $DEFCONFIG
+ - ./scripts/config -e WLAN -e WLAN_VENDOR_BROADCOM -m BRCMUTIL -m BRCMFMAC
+ -e BRCMFMAC_PROTO_BCDC -e BRCMFMAC_PROTO_MSGBUF
+ -e BRCMFMAC_USB
+ -e WLAN_VENDOR_REALTEK -m RTW89 -m RTW89_CORE
+ -m RTW89_PCI -m RTW89_8825B -m RTW89_8852BE
+ -m BINFMT_MISC
+ - make -j$(nproc) $ADDITIONAL_BUILD_CMD bindeb-pkg
+ - mkdir artifacts && dcmd mv ../*.changes artifacts/
+ artifacts:
+ paths:
+ - artifacts
+
+build arm64 debian package:
+ extends: .build debian package
+ variables:
+ ARCH: arm64
+ CROSS_COMPILE: aarch64-linux-gnu-
+ ADDITIONAL_BUILD_CMD: KBUILD_IMAGE=arch/arm64/boot/Image
+
+generate tests:
+ image: debian:bookworm-slim
+ stage: generate
+ tags:
+ - lightweight
+ variables:
+ GIT_STRATEGY: fetch
+ GIT_DEPTH: "1"
+ needs:
+ - "build arm64 debian package"
+ before_script:
+ - apt update
+ - apt install -y wget
+ script:
+ - mkdir deb
+ - "for x in artifacts/linux-image*.deb ; do dpkg -x ${x} deb ; done"
+ - cp deb/boot/vmlinuz* vmlinuz
+ - mkdir -p deb/lib/firmware/arm/mali/arch10.8
+ - wget -O deb/lib/firmware/arm/mali/arch10.8/mali_csffw.bin "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/arm/mali/arch10.8/mali_csffw.bin"
+ - tar -f modules.tar.gz -C deb -c -z -v lib/modules lib/firmware
+ - mkdir dtbs
+ - cp -r deb/usr/lib/linux-image*/* dtbs
+ - sed -i s,%%KERNEL_BUILD_JOB%%,${CI_JOB_ID},g lava/testjob.yaml
+ artifacts:
+ paths:
+ - vmlinuz*
+ - modules.tar.gz
+ - dtbs
+ - lava/testjob.yaml
+
+lava test:
+ stage: lava
+ tags:
+ - lava-runner
+ script:
+ - submit lava/testjob.yaml
+ needs:
+ - "generate tests"
+ artifacts:
+ when: always
+ paths:
+ - "*"
+ reports:
+ junit: "*.xml"
diff --git a/lava/testjob.yaml b/lava/testjob.yaml
new file mode 100644
index 000000000000..8dfaf772296c
--- /dev/null
+++ b/lava/testjob.yaml
@@ -0,0 +1,73 @@
+device_type: rk3588-rock-5b
+
+job_name: Hardware enablement tests {{job.CI_JOB_ID}}
+timeouts:
+ job:
+ minutes: 15
+ action:
+ minutes: 5
+priority: high
+visibility: public
+
+context:
+ extra_kernel_args: rootwait
+
+actions:
+ - deploy:
+ timeout:
+ minutes: 2
+ to: tftp
+ kernel:
+ url: "{{job.CI_PROJECT_URL}}/-/jobs/%%KERNEL_BUILD_JOB%%/artifacts/raw/vmlinuz"
+ type: image
+ modules:
+ url: "{{job.CI_PROJECT_URL}}/-/jobs/%%KERNEL_BUILD_JOB%%/artifacts/raw/modules.tar.gz"
+ compression: gz
+ dtb:
+ url: "{{job.CI_PROJECT_URL}}/-/jobs/%%KERNEL_BUILD_JOB%%/artifacts/raw/dtbs/rockchip/rk3588-rock-5b.dtb"
+ ramdisk:
+ url: https://gitlab.collabora.com/lava/health-check-images/-/jobs/artifacts/bookworm/raw/bookworm/bookworm-rootfs-arm64-initramfs.gz?job=build+bookworm+image:+%5Barm64,+rootfs%5D
+ compression: gz
+ nfsrootfs:
+ url: https://gitlab.collabora.com/lava/health-check-images/-/jobs/artifacts/bookworm/raw/bookworm/bookworm-rootfs-arm64.tar.gz?job=build+bookworm+image:+%5Barm64,+rootfs%5D
+ compression: gz
+
+ - boot:
+ method: u-boot
+ commands: nfs
+ timeout:
+ minutes: 10
+ auto_login:
+ login_prompt: 'login:'
+ username: user
+ password_prompt: 'Password:'
+ password: user
+ login_commands:
+ - sudo su
+ - env
+ - systemctl --failed
+ prompts:
+ - 'user@health(.*)$'
+ - 'root@health(.*)#'
+
+ - test:
+ timeout:
+ minutes: 1
+ definitions:
+ - repository:
+ metadata:
+ format: Lava-Test Test Definition 1.0
+ name: health
+ description: "health check"
+ os:
+ - apertis
+ scope:
+ - functional
+ environment:
+ - lava-test-shell
+ run:
+ steps:
+ - ip a s
+ from: inline
+ name: network
+ path: inline/health.yaml
--
2.44.1
From f851992e8c9758ccd3720b2b0a18a5f6a81bd820 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Tue, 25 Jul 2023 18:35:56 +0200
Subject: [PATCH 02/54] arm64: dts: rockchip: rk3588-rock5b: add USB-C support
Add hardware description for the USB-C port in the Radxa Rock 5 Model B.
This describes the OHCI, EHCI and XHCI USB parts, but not yet the
DisplayPort AltMode (bindings are not yet upstream).
For now the fusb302 node is marked with status "fail", since the board
is usually powered through the USB-C port. Handling of errors can result
in hard resets, which removed the bus power for some time resulting in
a board reset.
The main problem right now is that devices are supposed to interact with
the power-supply within 5 seconds after the plug event according to the
USB PD specification. This is more or less impossible to achieve when
the kernel is the first software communicating with the power-supply.
Currently the most likely workaround will be USB-PD handling added to
U-Boot. In that case U-Boot can update the status to "okay". That way
booting a kernel with the updated DT on an old U-Boot avoids a reset
loop.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
.../boot/dts/rockchip/rk3588-rock-5b.dts | 121 ++++++++++++++++++
1 file changed, 121 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
index b8e15b76a8a6..0c4359f1b97e 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
@@ -4,6 +4,7 @@
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
+#include <dt-bindings/usb/pd.h>
#include "rk3588.dtsi"
/ {
@@ -65,6 +66,15 @@ rfkill {
shutdown-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>;
};
+ vcc12v_dcin: vcc12v-dcin-regulator {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc12v_dcin";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <12000000>;
+ regulator-max-microvolt = <12000000>;
+ };
+
vcc3v3_pcie2x1l0: vcc3v3-pcie2x1l0-regulator {
compatible = "regulator-fixed";
enable-active-high;
@@ -123,6 +133,7 @@ vcc5v0_sys: vcc5v0-sys-regulator {
regulator-boot-on;
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
+ vin-supply = <&vcc12v_dcin>;
};
vcc_1v1_nldo_s3: vcc-1v1-nldo-s3-regulator {
@@ -225,6 +236,67 @@ regulator-state-mem {
};
};
+&i2c4 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2c4m1_xfer>;
+ status = "okay";
+
+ usbc0: usb-typec@22 {
+ compatible = "fcs,fusb302";
+ reg = <0x22>;
+ interrupt-parent = <&gpio3>;
+ interrupts = <RK_PB4 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&usbc0_int>;
+ vbus-supply = <&vcc12v_dcin>;
+ /*
+ * When the board is starting to send power-delivery messages
+ * too late (5 seconds according to the specification), the
+ * power-supply reacts with a hard-reset. That removes the
+ * power from VBUS for some time, which resets te whole board.
+ */
+ status = "fail";
+
+ usb_con: connector {
+ compatible = "usb-c-connector";
+ label = "USB-C";
+ data-role = "dual";
+ power-role = "sink";
+ try-power-role = "sink";
+ op-sink-microwatt = <1000000>;
+ sink-pdos =
+ <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>,
+ <PDO_VAR(5000, 20000, 5000)>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ usbc0_orien_sw: endpoint {
+ remote-endpoint = <&usbdp_phy0_orientation_switch>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ usbc0_role_sw: endpoint {
+ remote-endpoint = <&dwc3_0_role_switch>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ dp_altmode_mux: endpoint {
+ remote-endpoint = <&usbdp_phy0_dp_altmode_mux>;
+ };
+ };
+ };
+ };
+ };
+};
+
&i2c6 {
status = "okay";
@@ -354,6 +426,10 @@ usb {
vcc5v0_host_en: vcc5v0-host-en {
rockchip,pins = <4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
};
+
+ usbc0_int: usbc0-int {
+ rockchip,pins = <3 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
};
};
@@ -747,6 +823,14 @@ &uart2 {
status = "okay";
};
+&u2phy0 {
+ status = "okay";
+};
+
+&u2phy0_otg {
+ status = "okay";
+};
+
&u2phy1 {
status = "okay";
};
@@ -778,6 +862,29 @@ &usbdp_phy1 {
status = "okay";
};
+&usbdp_phy0 {
+ mode-switch;
+ orientation-switch;
+ sbu1-dc-gpios = <&gpio4 RK_PA6 GPIO_ACTIVE_HIGH>;
+ sbu2-dc-gpios = <&gpio4 RK_PA7 GPIO_ACTIVE_HIGH>;
+ status = "okay";
+
+ port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ usbdp_phy0_orientation_switch: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&usbc0_orien_sw>;
+ };
+
+ usbdp_phy0_dp_altmode_mux: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&dp_altmode_mux>;
+ };
+ };
+};
+
&usb_host0_ehci {
status = "okay";
};
@@ -786,6 +893,20 @@ &usb_host0_ohci {
status = "okay";
};
+&usb_host0_xhci {
+ usb-role-switch;
+ status = "okay";
+
+ port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ dwc3_0_role_switch: endpoint {
+ remote-endpoint = <&usbc0_role_sw>;
+ };
+ };
+};
+
&usb_host1_ehci {
status = "okay";
};
--
2.44.1
From eb006b9c23a873d2d7dbeb1af0c0d9418d35fd31 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Tue, 24 Oct 2023 16:09:35 +0200
Subject: [PATCH 03/54] math.h: add DIV_ROUND_UP_NO_OVERFLOW
Add a new DIV_ROUND_UP helper, which cannot overflow when
big numbers are being used.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
include/linux/math.h | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/include/linux/math.h b/include/linux/math.h
index dd4152711de7..f80bfb375ab9 100644
--- a/include/linux/math.h
+++ b/include/linux/math.h
@@ -36,6 +36,17 @@
#define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP
+/**
+ * DIV_ROUND_UP_NO_OVERFLOW - divide two numbers and always round up
+ * @n: numerator / dividend
+ * @d: denominator / divisor
+ *
+ * This functions does the same as DIV_ROUND_UP, but internally uses a
+ * division and a modulo operation instead of math tricks. This way it
+ * avoids overflowing when handling big numbers.
+ */
+#define DIV_ROUND_UP_NO_OVERFLOW(n, d) (((n) / (d)) + !!((n) % (d)))
+
#define DIV_ROUND_DOWN_ULL(ll, d) \
({ unsigned long long _tmp = (ll); do_div(_tmp, d); _tmp; })
--
2.44.1
From bba6ab440970c2b9a69d3b32a16f2680e38ea9e1 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Tue, 24 Oct 2023 16:13:50 +0200
Subject: [PATCH 04/54] clk: divider: Fix divisor masking on 64 bit platforms
The clock framework handles clock rates as "unsigned long", so u32 on
32-bit architectures and u64 on 64-bit architectures.
The current code casts the dividend to u64 on 32-bit to avoid a
potential overflow. For example DIV_ROUND_UP(3000000000, 1500000000)
= (3.0G + 1.5G - 1) / 1.5G = = OVERFLOW / 1.5G, which has been
introduced in commit 9556f9dad8f5 ("clk: divider: handle integer overflow
when dividing large clock rates").
On 64 bit platforms this masks the divisor, so that only the lower
32 bit are used. Thus requesting a frequency >= 4.3GHz results
in incorrect values. For example requesting 4300000000 (4.3 GHz) will
effectively request ca. 5 MHz. Requesting clk_round_rate(clk, ULONG_MAX)
is a bit of a special case, since that still returns correct values as
long as the parent clock is below 8.5 GHz.
Fix this by switching to DIV_ROUND_UP_NO_OVERFLOW, which cannot
overflow. This avoids any requirements on the arguments (except
that divisor should not be 0 obviously).
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/clk-divider.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index a2c2b5203b0a..94b4fb66a60f 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -220,7 +220,7 @@ static int _div_round_up(const struct clk_div_table *table,
unsigned long parent_rate, unsigned long rate,
unsigned long flags)
{
- int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
+ int div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate);
if (flags & CLK_DIVIDER_POWER_OF_TWO)
div = __roundup_pow_of_two(div);
@@ -237,7 +237,7 @@ static int _div_round_closest(const struct clk_div_table *table,
int up, down;
unsigned long up_rate, down_rate;
- up = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
+ up = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate);
down = parent_rate / rate;
if (flags & CLK_DIVIDER_POWER_OF_TWO) {
@@ -473,7 +473,7 @@ int divider_get_val(unsigned long rate, unsigned long parent_rate,
{
unsigned int div, value;
- div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
+ div = DIV_ROUND_UP_NO_OVERFLOW(parent_rate, rate);
if (!_is_valid_div(table, div, flags))
return -EINVAL;
--
2.44.1
From 37865cfc273efe64b16651ff167566564ca8cd2e Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Tue, 24 Oct 2023 18:09:57 +0200
Subject: [PATCH 05/54] clk: composite: replace open-coded abs_diff()
Replace the open coded abs_diff() with the existing helper function.
Suggested-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/clk-composite.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c
index 66759fe28fad..478a4e594336 100644
--- a/drivers/clk/clk-composite.c
+++ b/drivers/clk/clk-composite.c
@@ -6,6 +6,7 @@
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/math.h>
#include <linux/slab.h>
static u8 clk_composite_get_parent(struct clk_hw *hw)
@@ -119,10 +120,7 @@ static int clk_composite_determine_rate(struct clk_hw *hw,
if (ret)
continue;
- if (req->rate >= tmp_req.rate)
- rate_diff = req->rate - tmp_req.rate;
- else
- rate_diff = tmp_req.rate - req->rate;
+ rate_diff = abs_diff(req->rate, tmp_req.rate);
if (!rate_diff || !req->best_parent_hw
|| best_rate_diff > rate_diff) {
--
2.44.1
From 346137db0818fa1d55da82180f40c5986daec9a5 Mon Sep 17 00:00:00 2001
From: Alexey Charkov <alchark@gmail.com>
Date: Thu, 29 Feb 2024 23:26:32 +0400
Subject: [PATCH 06/54] arm64: dts: rockchip: enable built-in thermal
monitoring on RK3588
Include thermal zones information in device tree for RK3588 variants.
This also enables the TSADC controller unconditionally on all boards
to ensure that thermal protections are in place via throttling and
emergency reset, once OPPs are added to enable CPU DVFS.
The default settings (using CRU as the emergency reset mechanism)
should work on all boards regardless of their wiring, as CRU resets
do not depend on any external components. Boards that have the TSHUT
signal wired to the reset line of the PMIC may opt to switch to GPIO
tshut mode instead (rockchip,hw-tshut-mode = <1>;)
It seems though that downstream kernels don't use that, even for
those boards where the wiring allows for GPIO based tshut, such as
Radxa Rock 5B [1], [2], [3]
[1] https://github.com/radxa/kernel/blob/stable-5.10-rock5/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts#L540
[2] https://github.com/radxa/kernel/blob/stable-5.10-rock5/arch/arm64/boot/dts/rockchip/rk3588s.dtsi#L5433
[3] https://dl.radxa.com/rock5/5b/docs/hw/radxa_rock_5b_v1423_sch.pdf page 11 (TSADC_SHUT_H)
Signed-off-by: Alexey Charkov <alchark@gmail.com>
Link: https://lore.kernel.org/r/20240229-rk-dts-additions-v3-1-6afe8473a631@gmail.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588s.dtsi | 176 +++++++++++++++++++++-
1 file changed, 175 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
index 6ac5ac8b48ab..e2b24d4bfc2e 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
@@ -10,6 +10,7 @@
#include <dt-bindings/reset/rockchip,rk3588-cru.h>
#include <dt-bindings/phy/phy.h>
#include <dt-bindings/ata/ahci.h>
+#include <dt-bindings/thermal/thermal.h>
/ {
compatible = "rockchip,rk3588";
@@ -2385,7 +2386,180 @@ tsadc: tsadc@fec00000 {
pinctrl-1 = <&tsadc_shut>;
pinctrl-names = "gpio", "otpout";
#thermal-sensor-cells = <1>;
- status = "disabled";
+ status = "okay";
+ };
+
+ thermal_zones: thermal-zones {
+ /* sensor near the center of the SoC */
+ package_thermal: package-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tsadc 0>;
+
+ trips {
+ package_crit: package-crit {
+ temperature = <115000>;
+ hysteresis = <0>;
+ type = "critical";
+ };
+ };
+ };
+
+ /* sensor between A76 cores 0 and 1 */
+ bigcore0_thermal: bigcore0-thermal {
+ polling-delay-passive = <100>;
+ polling-delay = <0>;
+ thermal-sensors = <&tsadc 1>;
+
+ trips {
+ /* threshold to start collecting temperature
+ * statistics e.g. with the IPA governor
+ */
+ bigcore0_alert0: bigcore0-alert0 {
+ temperature = <75000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+ /* actual control temperature */
+ bigcore0_alert1: bigcore0-alert1 {
+ temperature = <85000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+ bigcore0_crit: bigcore0-crit {
+ temperature = <115000>;
+ hysteresis = <0>;
+ type = "critical";
+ };
+ };
+ cooling-maps {
+ map0 {
+ trip = <&bigcore0_alert1>;
+ cooling-device =
+ <&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu_b1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ /* sensor between A76 cores 2 and 3 */
+ bigcore2_thermal: bigcore2-thermal {
+ polling-delay-passive = <100>;
+ polling-delay = <0>;
+ thermal-sensors = <&tsadc 2>;
+
+ trips {
+ /* threshold to start collecting temperature
+ * statistics e.g. with the IPA governor
+ */
+ bigcore2_alert0: bigcore2-alert0 {
+ temperature = <75000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+ /* actual control temperature */
+ bigcore2_alert1: bigcore2-alert1 {
+ temperature = <85000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+ bigcore2_crit: bigcore2-crit {
+ temperature = <115000>;
+ hysteresis = <0>;
+ type = "critical";
+ };
+ };
+ cooling-maps {
+ map0 {
+ trip = <&bigcore2_alert1>;
+ cooling-device =
+ <&cpu_b2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu_b3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ /* sensor between the four A55 cores */
+ little_core_thermal: littlecore-thermal {
+ polling-delay-passive = <100>;
+ polling-delay = <0>;
+ thermal-sensors = <&tsadc 3>;
+
+ trips {
+ /* threshold to start collecting temperature
+ * statistics e.g. with the IPA governor
+ */
+ littlecore_alert0: littlecore-alert0 {
+ temperature = <75000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+ /* actual control temperature */
+ littlecore_alert1: littlecore-alert1 {
+ temperature = <85000>;
+ hysteresis = <2000>;
+ type = "passive";
+ };
+ littlecore_crit: littlecore-crit {
+ temperature = <115000>;
+ hysteresis = <0>;
+ type = "critical";
+ };
+ };
+ cooling-maps {
+ map0 {
+ trip = <&littlecore_alert1>;
+ cooling-device =
+ <&cpu_l0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu_l1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu_l2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
+ <&cpu_l3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
+ };
+ };
+ };
+
+ /* sensor near the PD_CENTER power domain */
+ center_thermal: center-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tsadc 4>;
+
+ trips {
+ center_crit: center-crit {
+ temperature = <115000>;
+ hysteresis = <0>;
+ type = "critical";
+ };
+ };
+ };
+
+ gpu_thermal: gpu-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tsadc 5>;
+
+ trips {
+ gpu_crit: gpu-crit {
+ temperature = <115000>;
+ hysteresis = <0>;
+ type = "critical";
+ };
+ };
+ };
+
+ npu_thermal: npu-thermal {
+ polling-delay-passive = <0>;
+ polling-delay = <0>;
+ thermal-sensors = <&tsadc 6>;
+
+ trips {
+ npu_crit: npu-crit {
+ temperature = <115000>;
+ hysteresis = <0>;
+ type = "critical";
+ };
+ };
+ };
};
saradc: adc@fec10000 {
--
2.44.1
From c81266f4e265da4e7a56222b6ea03e1936d11287 Mon Sep 17 00:00:00 2001
From: Alexey Charkov <alchark@gmail.com>
Date: Thu, 29 Feb 2024 23:26:33 +0400
Subject: [PATCH 07/54] arm64: dts: rockchip: enable automatic active cooling
on Rock 5B
This links the PWM fan on Radxa Rock 5B as an active cooling device
managed automatically by the thermal subsystem, with a target SoC
temperature of 65C and a minimum-spin interval from 55C to 65C to
ensure airflow when the system gets warm
Signed-off-by: Alexey Charkov <alchark@gmail.com>
Helped-by: Dragan Simic <dsimic@manjaro.org>
Reviewed-by: Dragan Simic <dsimic@manjaro.org>
Link: https://lore.kernel.org/r/20240229-rk-dts-additions-v3-2-6afe8473a631@gmail.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
.../boot/dts/rockchip/rk3588-rock-5b.dts | 30 ++++++++++++++++++-
1 file changed, 29 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
index 0c4359f1b97e..e18e970393a6 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
@@ -53,7 +53,7 @@ led_rgb_b {
fan: pwm-fan {
compatible = "pwm-fan";
- cooling-levels = <0 95 145 195 255>;
+ cooling-levels = <0 120 150 180 210 240 255>;
fan-supply = <&vcc5v0_sys>;
pwms = <&pwm1 0 50000 0>;
#cooling-cells = <2>;
@@ -351,6 +351,34 @@ i2s0_8ch_p0_0: endpoint {
};
};
+&package_thermal {
+ polling-delay = <1000>;
+
+ trips {
+ package_fan0: package-fan0 {
+ hysteresis = <2000>;
+ temperature = <55000>;
+ type = "active";
+ };
+ package_fan1: package-fan1 {
+ hysteresis = <2000>;
+ temperature = <65000>;
+ type = "active";
+ };
+ };
+
+ cooling-maps {
+ map1 {
+ cooling-device = <&fan THERMAL_NO_LIMIT 1>;
+ trip = <&package_fan0>;
+ };
+ map2 {
+ cooling-device = <&fan 2 THERMAL_NO_LIMIT>;
+ trip = <&package_fan1>;
+ };
+ };
+};
+
&pcie2x1l0 {
pinctrl-names = "default";
pinctrl-0 = <&pcie2_0_rst>;
--
2.44.1
From e6fac8dde2ec69f02435b3c5ccd68ea1355bac05 Mon Sep 17 00:00:00 2001
From: Alexey Charkov <alchark@gmail.com>
Date: Thu, 29 Feb 2024 23:26:34 +0400
Subject: [PATCH 08/54] arm64: dts: rockchip: Add CPU/memory regulator coupling
for RK3588
RK3588 chips allow for their CPU cores to be powered by a different
supply vs. their corresponding memory interfaces, and two of the
boards currently upstream do that (EVB1 and QuartzPro64).
The voltage of the memory interface though has to match that of the
CPU cores that use it, which downstream kernels achieve by the means
of a custom cpufreq driver which adjusts both at the same time.
It seems that regulator coupling is a more appropriate generic
interface for it, so this patch introduces coupling to affected
device trees to ensure that memory interface voltage is also updated
whenever cpufreq switches between CPU OPPs.
Note that other boards, such as Radxa Rock 5B, define both the CPU
and memory interface regulators as aliases to the same DT node, so
this doesn't apply there.
Signed-off-by: Alexey Charkov <alchark@gmail.com>
Link: https://lore.kernel.org/r/20240229-rk-dts-additions-v3-3-6afe8473a631@gmail.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts | 12 ++++++++++++
arch/arm64/boot/dts/rockchip/rk3588-quartzpro64.dts | 12 ++++++++++++
2 files changed, 24 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
index 7be2190244ba..7c5826da452e 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
@@ -878,6 +878,8 @@ regulators {
vdd_cpu_big1_s0: dcdc-reg1 {
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_big1_mem_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <550000>;
regulator-max-microvolt = <1050000>;
regulator-ramp-delay = <12500>;
@@ -890,6 +892,8 @@ regulator-state-mem {
vdd_cpu_big0_s0: dcdc-reg2 {
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_big0_mem_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <550000>;
regulator-max-microvolt = <1050000>;
regulator-ramp-delay = <12500>;
@@ -902,6 +906,8 @@ regulator-state-mem {
vdd_cpu_lit_s0: dcdc-reg3 {
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_lit_mem_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <550000>;
regulator-max-microvolt = <950000>;
regulator-ramp-delay = <12500>;
@@ -926,6 +932,8 @@ regulator-state-mem {
vdd_cpu_big1_mem_s0: dcdc-reg5 {
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_big1_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <675000>;
regulator-max-microvolt = <1050000>;
regulator-ramp-delay = <12500>;
@@ -939,6 +947,8 @@ regulator-state-mem {
vdd_cpu_big0_mem_s0: dcdc-reg6 {
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_big0_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <675000>;
regulator-max-microvolt = <1050000>;
regulator-ramp-delay = <12500>;
@@ -963,6 +973,8 @@ regulator-state-mem {
vdd_cpu_lit_mem_s0: dcdc-reg8 {
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_lit_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <675000>;
regulator-max-microvolt = <950000>;
regulator-ramp-delay = <12500>;
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-quartzpro64.dts b/arch/arm64/boot/dts/rockchip/rk3588-quartzpro64.dts
index b4f22d95ac0e..baeb08d665c7 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-quartzpro64.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-quartzpro64.dts
@@ -832,6 +832,8 @@ vdd_cpu_big1_s0: dcdc-reg1 {
regulator-name = "vdd_cpu_big1_s0";
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_big1_mem_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <550000>;
regulator-max-microvolt = <1050000>;
regulator-ramp-delay = <12500>;
@@ -845,6 +847,8 @@ vdd_cpu_big0_s0: dcdc-reg2 {
regulator-name = "vdd_cpu_big0_s0";
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_big0_mem_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <550000>;
regulator-max-microvolt = <1050000>;
regulator-ramp-delay = <12500>;
@@ -858,6 +862,8 @@ vdd_cpu_lit_s0: dcdc-reg3 {
regulator-name = "vdd_cpu_lit_s0";
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_lit_mem_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <550000>;
regulator-max-microvolt = <950000>;
regulator-ramp-delay = <12500>;
@@ -884,6 +890,8 @@ vdd_cpu_big1_mem_s0: dcdc-reg5 {
regulator-name = "vdd_cpu_big1_mem_s0";
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_big1_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <675000>;
regulator-max-microvolt = <1050000>;
regulator-ramp-delay = <12500>;
@@ -898,6 +906,8 @@ vdd_cpu_big0_mem_s0: dcdc-reg6 {
regulator-name = "vdd_cpu_big0_mem_s0";
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_big0_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <675000>;
regulator-max-microvolt = <1050000>;
regulator-ramp-delay = <12500>;
@@ -924,6 +934,8 @@ vdd_cpu_lit_mem_s0: dcdc-reg8 {
regulator-name = "vdd_cpu_lit_mem_s0";
regulator-always-on;
regulator-boot-on;
+ regulator-coupled-with = <&vdd_cpu_lit_s0>;
+ regulator-coupled-max-spread = <10000>;
regulator-min-microvolt = <675000>;
regulator-max-microvolt = <950000>;
regulator-ramp-delay = <12500>;
--
2.44.1
From bfc28c478d96020a987e19b393762b92c5a62896 Mon Sep 17 00:00:00 2001
From: Alexey Charkov <alchark@gmail.com>
Date: Thu, 29 Feb 2024 23:26:35 +0400
Subject: [PATCH 09/54] arm64: dts: rockchip: Add OPP data for CPU cores on
RK3588
By default the CPUs on RK3588 start up in a conservative performance
mode. Add frequency and voltage mappings to the device tree to enable
dynamic scaling via cpufreq.
OPP values are adapted from Radxa's downstream kernel for Rock 5B [1],
stripping them down to the minimum frequency and voltage combinations
as expected by the generic upstream cpufreq-dt driver, and also dropping
those OPPs that don't differ in voltage but only in frequency (keeping
the top frequency OPP in each case).
Note that this patch ignores voltage scaling for the CPU memory
interface which the downstream kernel does through a custom cpufreq
driver, and which is why the downstream version has two sets of voltage
values for each OPP (the second one being meant for the memory
interface supply regulator). This is done instead via regulator
coupling between CPU and memory interface supplies on affected boards.
This has been tested on Rock 5B with u-boot 2023.11 compiled from
Collabora's integration tree [2] with binary bl31 and appears to be
stable both under active cooling and passive cooling (with throttling)
[1] https://github.com/radxa/kernel/blob/stable-5.10-rock5/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
[2] https://gitlab.collabora.com/hardware-enablement/rockchip-3588/u-boot
Signed-off-by: Alexey Charkov <alchark@gmail.com>
Link: https://lore.kernel.org/r/20240229-rk-dts-additions-v3-4-6afe8473a631@gmail.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588s.dtsi | 122 ++++++++++++++++++++++
1 file changed, 122 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
index e2b24d4bfc2e..5104eebd563a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
@@ -97,6 +97,7 @@ cpu_l0: cpu@0 {
clocks = <&scmi_clk SCMI_CLK_CPUL>;
assigned-clocks = <&scmi_clk SCMI_CLK_CPUL>;
assigned-clock-rates = <816000000>;
+ operating-points-v2 = <&cluster0_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
i-cache-size = <32768>;
i-cache-line-size = <64>;
@@ -116,6 +117,7 @@ cpu_l1: cpu@100 {
enable-method = "psci";
capacity-dmips-mhz = <530>;
clocks = <&scmi_clk SCMI_CLK_CPUL>;
+ operating-points-v2 = <&cluster0_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
i-cache-size = <32768>;
i-cache-line-size = <64>;
@@ -135,6 +137,7 @@ cpu_l2: cpu@200 {
enable-method = "psci";
capacity-dmips-mhz = <530>;
clocks = <&scmi_clk SCMI_CLK_CPUL>;
+ operating-points-v2 = <&cluster0_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
i-cache-size = <32768>;
i-cache-line-size = <64>;
@@ -154,6 +157,7 @@ cpu_l3: cpu@300 {
enable-method = "psci";
capacity-dmips-mhz = <530>;
clocks = <&scmi_clk SCMI_CLK_CPUL>;
+ operating-points-v2 = <&cluster0_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
i-cache-size = <32768>;
i-cache-line-size = <64>;
@@ -175,6 +179,7 @@ cpu_b0: cpu@400 {
clocks = <&scmi_clk SCMI_CLK_CPUB01>;
assigned-clocks = <&scmi_clk SCMI_CLK_CPUB01>;
assigned-clock-rates = <816000000>;
+ operating-points-v2 = <&cluster1_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -194,6 +199,7 @@ cpu_b1: cpu@500 {
enable-method = "psci";
capacity-dmips-mhz = <1024>;
clocks = <&scmi_clk SCMI_CLK_CPUB01>;
+ operating-points-v2 = <&cluster1_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -215,6 +221,7 @@ cpu_b2: cpu@600 {
clocks = <&scmi_clk SCMI_CLK_CPUB23>;
assigned-clocks = <&scmi_clk SCMI_CLK_CPUB23>;
assigned-clock-rates = <816000000>;
+ operating-points-v2 = <&cluster2_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -234,6 +241,7 @@ cpu_b3: cpu@700 {
enable-method = "psci";
capacity-dmips-mhz = <1024>;
clocks = <&scmi_clk SCMI_CLK_CPUB23>;
+ operating-points-v2 = <&cluster2_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
i-cache-size = <65536>;
i-cache-line-size = <64>;
@@ -348,6 +356,120 @@ l3_cache: l3-cache {
};
};
+ cluster0_opp_table: opp-table-cluster0 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp-1008000000 {
+ opp-hz = /bits/ 64 <1008000000>;
+ opp-microvolt = <675000 675000 950000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1200000000 {
+ opp-hz = /bits/ 64 <1200000000>;
+ opp-microvolt = <712500 712500 950000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1416000000 {
+ opp-hz = /bits/ 64 <1416000000>;
+ opp-microvolt = <762500 762500 950000>;
+ clock-latency-ns = <40000>;
+ opp-suspend;
+ };
+ opp-1608000000 {
+ opp-hz = /bits/ 64 <1608000000>;
+ opp-microvolt = <850000 850000 950000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1800000000 {
+ opp-hz = /bits/ 64 <1800000000>;
+ opp-microvolt = <950000 950000 950000>;
+ clock-latency-ns = <40000>;
+ };
+ };
+
+ cluster1_opp_table: opp-table-cluster1 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp-1200000000 {
+ opp-hz = /bits/ 64 <1200000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1416000000 {
+ opp-hz = /bits/ 64 <1416000000>;
+ opp-microvolt = <725000 725000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1608000000 {
+ opp-hz = /bits/ 64 <1608000000>;
+ opp-microvolt = <762500 762500 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1800000000 {
+ opp-hz = /bits/ 64 <1800000000>;
+ opp-microvolt = <850000 850000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2016000000 {
+ opp-hz = /bits/ 64 <2016000000>;
+ opp-microvolt = <925000 925000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2208000000 {
+ opp-hz = /bits/ 64 <2208000000>;
+ opp-microvolt = <987500 987500 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2400000000 {
+ opp-hz = /bits/ 64 <2400000000>;
+ opp-microvolt = <1000000 1000000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ };
+
+ cluster2_opp_table: opp-table-cluster2 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp-1200000000 {
+ opp-hz = /bits/ 64 <1200000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1416000000 {
+ opp-hz = /bits/ 64 <1416000000>;
+ opp-microvolt = <725000 725000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1608000000 {
+ opp-hz = /bits/ 64 <1608000000>;
+ opp-microvolt = <762500 762500 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1800000000 {
+ opp-hz = /bits/ 64 <1800000000>;
+ opp-microvolt = <850000 850000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2016000000 {
+ opp-hz = /bits/ 64 <2016000000>;
+ opp-microvolt = <925000 925000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2208000000 {
+ opp-hz = /bits/ 64 <2208000000>;
+ opp-microvolt = <987500 987500 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2400000000 {
+ opp-hz = /bits/ 64 <2400000000>;
+ opp-microvolt = <1000000 1000000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ };
+
display_subsystem: display-subsystem {
compatible = "rockchip,display-subsystem";
ports = <&vop_out>;
--
2.44.1
From d4c81e098ca05e31b6b6e3faae8bd278faa41b6c Mon Sep 17 00:00:00 2001
From: Alexey Charkov <alchark@gmail.com>
Date: Thu, 29 Feb 2024 23:26:36 +0400
Subject: [PATCH 10/54] arm64: dts: rockchip: Add further granularity in RK3588
CPU OPPs
This introduces additional OPPs that share the same voltage as
another OPP already present in the .dtsi but with lower frequency.
The idea is to try and limit system throughput more gradually upon
reaching the throttling condition for workloads that are close to
sustainable power already, thus avoiding needless performance loss.
My limited synthetic benchmarking [1] showed around 3.8% performance
benefit when these are in place, other things equal (not meant to
be comprehensive). Though dmesg complains about these OPPs being
'inefficient':
[ 9.009561] cpu cpu0: EM: OPP:816000 is inefficient
[ 9.009580] cpu cpu0: EM: OPP:600000 is inefficient
[ 9.009591] cpu cpu0: EM: OPP:408000 is inefficient
[ 9.011370] cpu cpu4: EM: OPP:2352000 is inefficient
[ 9.011379] cpu cpu4: EM: OPP:2304000 is inefficient
[ 9.011384] cpu cpu4: EM: OPP:2256000 is inefficient
[ 9.011389] cpu cpu4: EM: OPP:600000 is inefficient
[ 9.011393] cpu cpu4: EM: OPP:408000 is inefficient
[ 9.012978] cpu cpu6: EM: OPP:2352000 is inefficient
[ 9.012987] cpu cpu6: EM: OPP:2304000 is inefficient
[ 9.012992] cpu cpu6: EM: OPP:2256000 is inefficient
[ 9.012996] cpu cpu6: EM: OPP:600000 is inefficient
[ 9.013000] cpu cpu6: EM: OPP:408000 is inefficient
[1] https://lore.kernel.org/linux-rockchip/CABjd4YxqarUCbZ-a2XLe3TWJ-qjphGkyq=wDnctnEhdoSdPPpw@mail.gmail.com/T/#me92aa0ee25e6eeb1d1501ce85f5af4e58b3b13c5
Signed-off-by: Alexey Charkov <alchark@gmail.com>
Link: https://lore.kernel.org/r/20240229-rk-dts-additions-v3-5-6afe8473a631@gmail.com
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588s.dtsi | 87 +++++++++++++++++++++++
1 file changed, 87 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
index 5104eebd563a..ae3ccafe6871 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
@@ -360,6 +360,21 @@ cluster0_opp_table: opp-table-cluster0 {
compatible = "operating-points-v2";
opp-shared;
+ opp-408000000 {
+ opp-hz = /bits/ 64 <408000000>;
+ opp-microvolt = <675000 675000 950000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-600000000 {
+ opp-hz = /bits/ 64 <600000000>;
+ opp-microvolt = <675000 675000 950000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-816000000 {
+ opp-hz = /bits/ 64 <816000000>;
+ opp-microvolt = <675000 675000 950000>;
+ clock-latency-ns = <40000>;
+ };
opp-1008000000 {
opp-hz = /bits/ 64 <1008000000>;
opp-microvolt = <675000 675000 950000>;
@@ -392,6 +407,27 @@ cluster1_opp_table: opp-table-cluster1 {
compatible = "operating-points-v2";
opp-shared;
+ opp-408000000 {
+ opp-hz = /bits/ 64 <408000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ opp-suspend;
+ };
+ opp-600000000 {
+ opp-hz = /bits/ 64 <600000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-816000000 {
+ opp-hz = /bits/ 64 <816000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1008000000 {
+ opp-hz = /bits/ 64 <1008000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ };
opp-1200000000 {
opp-hz = /bits/ 64 <1200000000>;
opp-microvolt = <675000 675000 1000000>;
@@ -422,6 +458,21 @@ opp-2208000000 {
opp-microvolt = <987500 987500 1000000>;
clock-latency-ns = <40000>;
};
+ opp-2256000000 {
+ opp-hz = /bits/ 64 <2256000000>;
+ opp-microvolt = <1000000 1000000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2304000000 {
+ opp-hz = /bits/ 64 <2304000000>;
+ opp-microvolt = <1000000 1000000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2352000000 {
+ opp-hz = /bits/ 64 <2352000000>;
+ opp-microvolt = <1000000 1000000 1000000>;
+ clock-latency-ns = <40000>;
+ };
opp-2400000000 {
opp-hz = /bits/ 64 <2400000000>;
opp-microvolt = <1000000 1000000 1000000>;
@@ -433,6 +484,27 @@ cluster2_opp_table: opp-table-cluster2 {
compatible = "operating-points-v2";
opp-shared;
+ opp-408000000 {
+ opp-hz = /bits/ 64 <408000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ opp-suspend;
+ };
+ opp-600000000 {
+ opp-hz = /bits/ 64 <600000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-816000000 {
+ opp-hz = /bits/ 64 <816000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-1008000000 {
+ opp-hz = /bits/ 64 <1008000000>;
+ opp-microvolt = <675000 675000 1000000>;
+ clock-latency-ns = <40000>;
+ };
opp-1200000000 {
opp-hz = /bits/ 64 <1200000000>;
opp-microvolt = <675000 675000 1000000>;
@@ -463,6 +535,21 @@ opp-2208000000 {
opp-microvolt = <987500 987500 1000000>;
clock-latency-ns = <40000>;
};
+ opp-2256000000 {
+ opp-hz = /bits/ 64 <2256000000>;
+ opp-microvolt = <1000000 1000000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2304000000 {
+ opp-hz = /bits/ 64 <2304000000>;
+ opp-microvolt = <1000000 1000000 1000000>;
+ clock-latency-ns = <40000>;
+ };
+ opp-2352000000 {
+ opp-hz = /bits/ 64 <2352000000>;
+ opp-microvolt = <1000000 1000000 1000000>;
+ clock-latency-ns = <40000>;
+ };
opp-2400000000 {
opp-hz = /bits/ 64 <2400000000>;
opp-microvolt = <1000000 1000000 1000000>;
--
2.44.1
From b81afac501f04f1137009cec98a1447321b84467 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Fri, 14 Jul 2023 17:38:24 +0200
Subject: [PATCH 11/54] [BROKEN] arm64: dts: rockchip: rk3588-evb1: add PCIe2
WLAN controller
Enable PCIe bus used by on-board PCIe Broadcom WLAN controller.
The WLAN controller is not detected. There are two reasons for
that.
First of all it is necessary to keep the HYM8563 clock enabled, but it
is disabled because the kernel does not know about the dependency and
disables any clocks it deems unused.
Apart from that it looks like the controller needs a long time to be
properly initialized. So detection only works when rescanning the bus
some time after boot. This still needs to be investigated.
Both of these issues should be fixable by implementing a pwrseq driver
once the following series has landed:
https://lore.kernel.org/all/20240104130123.37115-1-brgl@bgdev.pl/
In addition to the above issues, the mainline brcmfmac driver does
not yet support the chip version used by AP6275P.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
.../boot/dts/rockchip/rk3588-evb1-v10.dts | 61 +++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
index 7c5826da452e..65e16410084a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
@@ -120,6 +120,15 @@ backlight: backlight {
pwms = <&pwm2 0 25000 0>;
};
+ wlan-rfkill {
+ compatible = "rfkill-gpio";
+ label = "rfkill-pcie-wlan";
+ radio-type = "wlan";
+ shutdown-gpios = <&gpio3 RK_PB1 GPIO_ACTIVE_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&wifi_pwren>;
+ };
+
pcie20_avdd0v85: pcie20-avdd0v85-regulator {
compatible = "regulator-fixed";
regulator-name = "pcie20_avdd0v85";
@@ -237,12 +246,36 @@ vcc5v0_usb: vcc5v0-usb-regulator {
regulator-max-microvolt = <5000000>;
vin-supply = <&vcc5v0_usbdcin>;
};
+
+ vccio_wl: vccio-wl {
+ compatible = "regulator-fixed";
+ regulator-name = "wlan-vddio";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-always-on;
+ regulator-boot-on;
+ vin-supply = <&vcc_1v8_s0>;
+ };
+
+ vcc3v3_pciewl_vbat: vcc3v3-pciewl-vbat {
+ compatible = "regulator-fixed";
+ regulator-name = "wlan-vbat";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-always-on;
+ regulator-boot-on;
+ vin-supply = <&vcc_3v3_s0>;
+ };
};
&combphy0_ps {
status = "okay";
};
+&combphy1_ps {
+ status = "okay";
+};
+
&combphy2_psu {
status = "okay";
};
@@ -408,6 +441,12 @@ rgmii_phy: ethernet-phy@1 {
};
};
+&pcie2x1l0 {
+ reset-gpios = <&gpio4 RK_PA5 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&pcie2_0_rst>, <&pcie2_0_wake>, <&pcie2_0_clkreq>, <&wifi_host_wake_irq>;
+ status = "okay";
+};
+
&pcie2x1l1 {
reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
@@ -462,6 +501,18 @@ hym8563_int: hym8563-int {
};
pcie2 {
+ pcie2_0_rst: pcie2-0-rst {
+ rockchip,pins = <4 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ pcie2_0_wake: pcie2-0-wake {
+ rockchip,pins = <4 RK_PA4 4 &pcfg_pull_none>;
+ };
+
+ pcie2_0_clkreq: pcie2-0-clkreq {
+ rockchip,pins = <4 RK_PA3 4 &pcfg_pull_none>;
+ };
+
pcie2_1_rst: pcie2-1-rst {
rockchip,pins = <4 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>;
};
@@ -492,6 +543,16 @@ usbc0_int: usbc0-int {
rockchip,pins = <3 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
+
+ wlan {
+ wifi_host_wake_irq: wifi-host-wake-irq {
+ rockchip,pins = <3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_down>;
+ };
+
+ wifi_pwren: wifi-pwren {
+ rockchip,pins = <3 RK_PB1 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
};
&pwm2 {
--
2.44.1
From 9a4e298de029f488c6857b23eb98370dd861b6c7 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Mon, 11 Mar 2024 18:05:34 +0100
Subject: [PATCH 12/54] clk: rockchip: rk3588: drop unused code
All clocks are registered early using CLK_OF_DECLARE(), which marks
the DT node as processed. For the processed DT node the probe routine
is never called. Thus this whole code is never executed. This could
be "fixed" by using CLK_OF_DECLARE_DRIVER, which avoids marking the
DT node as processed. But then the probe routine would re-register
all the clocks by calling rk3588_clk_init() again.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/rockchip/clk-rk3588.c | 40 -------------------------------
1 file changed, 40 deletions(-)
diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c
index b30279a96dc8..74051277ecea 100644
--- a/drivers/clk/rockchip/clk-rk3588.c
+++ b/drivers/clk/rockchip/clk-rk3588.c
@@ -2502,43 +2502,3 @@ static void __init rk3588_clk_init(struct device_node *np)
}
CLK_OF_DECLARE(rk3588_cru, "rockchip,rk3588-cru", rk3588_clk_init);
-
-struct clk_rk3588_inits {
- void (*inits)(struct device_node *np);
-};
-
-static const struct clk_rk3588_inits clk_3588_cru_init = {
- .inits = rk3588_clk_init,
-};
-
-static const struct of_device_id clk_rk3588_match_table[] = {
- {
- .compatible = "rockchip,rk3588-cru",
- .data = &clk_3588_cru_init,
- },
- { }
-};
-
-static int __init clk_rk3588_probe(struct platform_device *pdev)
-{
- const struct clk_rk3588_inits *init_data;
- struct device *dev = &pdev->dev;
-
- init_data = device_get_match_data(dev);
- if (!init_data)
- return -EINVAL;
-
- if (init_data->inits)
- init_data->inits(dev->of_node);
-
- return 0;
-}
-
-static struct platform_driver clk_rk3588_driver = {
- .driver = {
- .name = "clk-rk3588",
- .of_match_table = clk_rk3588_match_table,
- .suppress_bind_attrs = true,
- },
-};
-builtin_platform_driver_probe(clk_rk3588_driver, clk_rk3588_probe);
--
2.44.1
From 5b5631ef482995c737a805d126344e23b8d30c3e Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Fri, 8 Mar 2024 23:19:55 +0100
Subject: [PATCH 13/54] clk: rockchip: handle missing clocks with -EPROBE_DEFER
In the future some clocks will be registered using CLK_OF_DECLARE
and some are registered later from the driver probe routine. Any
clock handled by the probe routine should return -EPROBE_DEFER
until that routine has been called.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/rockchip/clk.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index 73d2cbdc716b..31b7cc243d82 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -376,7 +376,7 @@ struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np,
goto err_free;
for (i = 0; i < nr_clks; ++i)
- clk_table[i] = ERR_PTR(-ENOENT);
+ clk_table[i] = ERR_PTR(-EPROBE_DEFER);
ctx->reg_base = base;
ctx->clk_data.clks = clk_table;
--
2.44.1
From b7d54daa05c7ff7777eb6ed11de732e681ba3786 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Tue, 12 Mar 2024 16:12:18 +0100
Subject: [PATCH 14/54] clk: rockchip: rk3588: register GATE_LINK later
The proper GATE_LINK implementation will use runtime PM to handle the
linked gate clocks, which requires device context. Currently all clocks
are registered early via CLK_OF_DECLARE, which is before the kernel
knows about devices.
Moving the full clocks registration to the probe routine does not work,
since the clocks needed for timers must be registered early.
To work around this issue, most of the clock tree is registered early,
but GATE_LINK clocks are handled in the probe routine. Since the resets
are not needed early either, they have also been moved to the probe
routine.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/rockchip/clk-rk3588.c | 64 +++++++++++++++++++++++++++----
1 file changed, 56 insertions(+), 8 deletions(-)
diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c
index 74051277ecea..29f31a5526fa 100644
--- a/drivers/clk/rockchip/clk-rk3588.c
+++ b/drivers/clk/rockchip/clk-rk3588.c
@@ -266,6 +266,8 @@ static struct rockchip_pll_rate_table rk3588_pll_rates[] = {
}, \
}
+static struct rockchip_clk_provider *early_ctx;
+
static struct rockchip_cpuclk_rate_table rk3588_cpub0clk_rates[] __initdata = {
RK3588_CPUB01CLK_RATE(2496000000, 1),
RK3588_CPUB01CLK_RATE(2400000000, 1),
@@ -694,7 +696,7 @@ static struct rockchip_pll_clock rk3588_pll_clks[] __initdata = {
RK3588_MODE_CON0, 10, 15, 0, rk3588_pll_rates),
};
-static struct rockchip_clk_branch rk3588_clk_branches[] __initdata = {
+static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
/*
* CRU Clock-Architecture
*/
@@ -2428,7 +2430,9 @@ static struct rockchip_clk_branch rk3588_clk_branches[] __initdata = {
RK3588_CLKGATE_CON(68), 5, GFLAGS),
GATE(ACLK_AV1, "aclk_av1", "aclk_av1_pre", 0,
RK3588_CLKGATE_CON(68), 2, GFLAGS),
+};
+static struct rockchip_clk_branch rk3588_clk_branches[] = {
GATE_LINK(ACLK_ISP1_PRE, "aclk_isp1_pre", "aclk_isp1_root", ACLK_VI_ROOT, 0, RK3588_CLKGATE_CON(26), 6, GFLAGS),
GATE_LINK(HCLK_ISP1_PRE, "hclk_isp1_pre", "hclk_isp1_root", HCLK_VI_ROOT, 0, RK3588_CLKGATE_CON(26), 8, GFLAGS),
GATE_LINK(HCLK_NVM, "hclk_nvm", "hclk_nvm_root", ACLK_NVM_ROOT, RK3588_LINKED_CLK, RK3588_CLKGATE_CON(31), 2, GFLAGS),
@@ -2453,14 +2457,18 @@ static struct rockchip_clk_branch rk3588_clk_branches[] __initdata = {
GATE_LINK(PCLK_VO1GRF, "pclk_vo1grf", "pclk_vo1_root", HCLK_VO1, CLK_IGNORE_UNUSED, RK3588_CLKGATE_CON(59), 12, GFLAGS),
};
-static void __init rk3588_clk_init(struct device_node *np)
+static void __init rk3588_clk_early_init(struct device_node *np)
{
struct rockchip_clk_provider *ctx;
- unsigned long clk_nr_clks;
+ unsigned long clk_nr_clks, max_clk_id1, max_clk_id2;
void __iomem *reg_base;
- clk_nr_clks = rockchip_clk_find_max_clk_id(rk3588_clk_branches,
- ARRAY_SIZE(rk3588_clk_branches)) + 1;
+ max_clk_id1 = rockchip_clk_find_max_clk_id(rk3588_clk_branches,
+ ARRAY_SIZE(rk3588_clk_branches));
+ max_clk_id2 = rockchip_clk_find_max_clk_id(rk3588_early_clk_branches,
+ ARRAY_SIZE(rk3588_early_clk_branches));
+ clk_nr_clks = max(max_clk_id1, max_clk_id2) + 1;
+
reg_base = of_iomap(np, 0);
if (!reg_base) {
pr_err("%s: could not map cru region\n", __func__);
@@ -2473,6 +2481,7 @@ static void __init rk3588_clk_init(struct device_node *np)
iounmap(reg_base);
return;
}
+ early_ctx = ctx;
rockchip_clk_register_plls(ctx, rk3588_pll_clks,
ARRAY_SIZE(rk3588_pll_clks),
@@ -2491,14 +2500,53 @@ static void __init rk3588_clk_init(struct device_node *np)
&rk3588_cpub1clk_data, rk3588_cpub1clk_rates,
ARRAY_SIZE(rk3588_cpub1clk_rates));
+ rockchip_clk_register_branches(ctx, rk3588_early_clk_branches,
+ ARRAY_SIZE(rk3588_early_clk_branches));
+
+ rockchip_clk_of_add_provider(np, ctx);
+}
+CLK_OF_DECLARE_DRIVER(rk3588_cru, "rockchip,rk3588-cru", rk3588_clk_early_init);
+
+static int clk_rk3588_probe(struct platform_device *pdev)
+{
+ struct rockchip_clk_provider *ctx = early_ctx;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
rockchip_clk_register_branches(ctx, rk3588_clk_branches,
ARRAY_SIZE(rk3588_clk_branches));
- rk3588_rst_init(np, reg_base);
-
+ rk3588_rst_init(np, ctx->reg_base);
rockchip_register_restart_notifier(ctx, RK3588_GLB_SRST_FST, NULL);
+ /*
+ * Re-add clock provider, so that the newly added clocks are also
+ * re-parented and get their defaults configured.
+ */
+ of_clk_del_provider(np);
rockchip_clk_of_add_provider(np, ctx);
+
+ return 0;
}
-CLK_OF_DECLARE(rk3588_cru, "rockchip,rk3588-cru", rk3588_clk_init);
+static const struct of_device_id clk_rk3588_match_table[] = {
+ {
+ .compatible = "rockchip,rk3588-cru",
+ },
+ { }
+};
+
+static struct platform_driver clk_rk3588_driver = {
+ .probe = clk_rk3588_probe,
+ .driver = {
+ .name = "clk-rk3588",
+ .of_match_table = clk_rk3588_match_table,
+ .suppress_bind_attrs = true,
+ },
+};
+
+static int __init rockchip_clk_rk3588_drv_register(void)
+{
+ return platform_driver_register(&clk_rk3588_driver);
+}
+core_initcall(rockchip_clk_rk3588_drv_register);
--
2.44.1
From 94082226ef32d0e97dc188cd9a065de6c55e8c77 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Mon, 25 Mar 2024 19:29:22 +0100
Subject: [PATCH 15/54] clk: rockchip: expose rockchip_clk_set_lookup
Move rockchip_clk_add_lookup to clk.h, so that it can be used
by sub-devices with their own driver. These might also have to
do a lookup, so rename the function to rockchip_clk_set_lookup
and add a matching rockchip_clk_add_lookup.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/rockchip/clk.c | 14 ++++----------
drivers/clk/rockchip/clk.h | 12 ++++++++++++
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index 31b7cc243d82..ef2408f10f39 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -197,12 +197,6 @@ static void rockchip_fractional_approximation(struct clk_hw *hw,
clk_fractional_divider_general_approximation(hw, rate, parent_rate, m, n);
}
-static void rockchip_clk_add_lookup(struct rockchip_clk_provider *ctx,
- struct clk *clk, unsigned int id)
-{
- ctx->clk_data.clks[id] = clk;
-}
-
static struct clk *rockchip_clk_register_frac_branch(
struct rockchip_clk_provider *ctx, const char *name,
const char *const *parent_names, u8 num_parents,
@@ -292,7 +286,7 @@ static struct clk *rockchip_clk_register_frac_branch(
return mux_clk;
}
- rockchip_clk_add_lookup(ctx, mux_clk, child->id);
+ rockchip_clk_set_lookup(ctx, mux_clk, child->id);
/* notifier on the fraction divider to catch rate changes */
if (frac->mux_frac_idx >= 0) {
@@ -424,7 +418,7 @@ void rockchip_clk_register_plls(struct rockchip_clk_provider *ctx,
continue;
}
- rockchip_clk_add_lookup(ctx, clk, list->id);
+ rockchip_clk_set_lookup(ctx, clk, list->id);
}
}
EXPORT_SYMBOL_GPL(rockchip_clk_register_plls);
@@ -585,7 +579,7 @@ void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
continue;
}
- rockchip_clk_add_lookup(ctx, clk, list->id);
+ rockchip_clk_set_lookup(ctx, clk, list->id);
}
}
EXPORT_SYMBOL_GPL(rockchip_clk_register_branches);
@@ -609,7 +603,7 @@ void rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx,
return;
}
- rockchip_clk_add_lookup(ctx, clk, lookup_id);
+ rockchip_clk_set_lookup(ctx, clk, lookup_id);
}
EXPORT_SYMBOL_GPL(rockchip_clk_register_armclk);
diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h
index fd3b476dedda..e39392e1c2a2 100644
--- a/drivers/clk/rockchip/clk.h
+++ b/drivers/clk/rockchip/clk.h
@@ -969,6 +969,18 @@ struct rockchip_clk_branch {
#define SGRF_GATE(_id, cname, pname) \
FACTOR(_id, cname, pname, 0, 1, 1)
+static inline struct clk *rockchip_clk_get_lookup(struct rockchip_clk_provider *ctx,
+ unsigned int id)
+{
+ return ctx->clk_data.clks[id];
+}
+
+static inline void rockchip_clk_set_lookup(struct rockchip_clk_provider *ctx,
+ struct clk *clk, unsigned int id)
+{
+ ctx->clk_data.clks[id] = clk;
+}
+
struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np,
void __iomem *base, unsigned long nr_clks);
void rockchip_clk_of_add_provider(struct device_node *np,
--
2.44.1
From 8af78be9475200087bbedcc6565ae5e50490901b Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Mon, 25 Mar 2024 19:31:59 +0100
Subject: [PATCH 16/54] clk: rockchip: fix error for unknown clocks
There is a clk == NULL check after the switch to check for
unsupported clk types. Since clk is re-assigned in a loop,
this check is useless right now for anything but the first
round. Let's fix this up by assigning clk = NULL in the
loop before the switch statement.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/rockchip/clk.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index ef2408f10f39..e150bc1fc319 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -444,12 +444,13 @@ void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
struct rockchip_clk_branch *list,
unsigned int nr_clk)
{
- struct clk *clk = NULL;
+ struct clk *clk;
unsigned int idx;
unsigned long flags;
for (idx = 0; idx < nr_clk; idx++, list++) {
flags = list->flags;
+ clk = NULL;
/* catch simple muxes */
switch (list->branch_type) {
--
2.44.1
From 75751a4bf52c74c6c742f0ed404307d1a894c623 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Mon, 25 Mar 2024 19:42:07 +0100
Subject: [PATCH 17/54] clk: rockchip: implement linked gate clock support
Recent Rockchip SoCs have a new hardware block called Native Interface
Unit (NIU), which gates clocks to devices behind them. These clock
gates will only have a running output clock when all of the following
conditions are met:
1. the parent clock is enabled
2. the enable bit is set correctly
3. the linked clock is enabled
To handle them this code registers them as a normal gate type clock,
which takes care of condition 1 + 2. The linked clock is handled by
using runtime PM clocks. Handling it via runtime PM requires setting
up a struct device for each of these clocks with a driver attached
to use the correct runtime PM operations. Thus the complete handling
of these clocks has been moved into its own driver.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/rockchip/Makefile | 1 +
drivers/clk/rockchip/clk-rk3588.c | 23 +------
drivers/clk/rockchip/clk.c | 52 ++++++++++++++++
drivers/clk/rockchip/clk.h | 25 ++++++++
drivers/clk/rockchip/gate-link.c | 99 +++++++++++++++++++++++++++++++
5 files changed, 179 insertions(+), 21 deletions(-)
create mode 100644 drivers/clk/rockchip/gate-link.c
diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile
index 36894f6a7022..179be95c6ffb 100644
--- a/drivers/clk/rockchip/Makefile
+++ b/drivers/clk/rockchip/Makefile
@@ -13,6 +13,7 @@ clk-rockchip-y += clk-inverter.o
clk-rockchip-y += clk-mmc-phase.o
clk-rockchip-y += clk-muxgrf.o
clk-rockchip-y += clk-ddr.o
+clk-rockchip-y += gate-link.o
clk-rockchip-$(CONFIG_RESET_CONTROLLER) += softrst.o
obj-$(CONFIG_CLK_PX30) += clk-px30.o
diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c
index 29f31a5526fa..1bf84ac44e85 100644
--- a/drivers/clk/rockchip/clk-rk3588.c
+++ b/drivers/clk/rockchip/clk-rk3588.c
@@ -12,25 +12,6 @@
#include <dt-bindings/clock/rockchip,rk3588-cru.h>
#include "clk.h"
-/*
- * Recent Rockchip SoCs have a new hardware block called Native Interface
- * Unit (NIU), which gates clocks to devices behind them. These effectively
- * need two parent clocks.
- *
- * Downstream enables the linked clock via runtime PM whenever the gate is
- * enabled. This implementation uses separate clock nodes for each of the
- * linked gate clocks, which leaks parts of the clock tree into DT.
- *
- * The GATE_LINK macro instead takes the second parent via 'linkname', but
- * ignores the information. Once the clock framework is ready to handle it, the
- * information should be passed on here. But since these clocks are required to
- * access multiple relevant IP blocks, such as PCIe or USB, we mark all linked
- * clocks critical until a better solution is available. This will waste some
- * power, but avoids leaking implementation details into DT or hanging the
- * system.
- */
-#define GATE_LINK(_id, cname, pname, linkedclk, f, o, b, gf) \
- GATE(_id, cname, pname, f, o, b, gf)
#define RK3588_LINKED_CLK CLK_IS_CRITICAL
@@ -2513,8 +2494,8 @@ static int clk_rk3588_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
- rockchip_clk_register_branches(ctx, rk3588_clk_branches,
- ARRAY_SIZE(rk3588_clk_branches));
+ rockchip_clk_register_late_branches(dev, ctx, rk3588_clk_branches,
+ ARRAY_SIZE(rk3588_clk_branches));
rk3588_rst_init(np, ctx->reg_base);
rockchip_register_restart_notifier(ctx, RK3588_GLB_SRST_FST, NULL);
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index e150bc1fc319..f5f11cc60046 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -19,6 +19,7 @@
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reboot.h>
@@ -440,6 +441,29 @@ unsigned long rockchip_clk_find_max_clk_id(struct rockchip_clk_branch *list,
}
EXPORT_SYMBOL_GPL(rockchip_clk_find_max_clk_id);
+static struct platform_device *rockchip_clk_register_gate_link(
+ struct device *parent_dev,
+ struct rockchip_clk_provider *ctx,
+ struct rockchip_clk_branch *clkbr)
+{
+ struct rockchip_gate_link_platdata gate_link_pdata = {
+ .ctx = ctx,
+ .clkbr = clkbr,
+ };
+
+ struct platform_device_info pdevinfo = {
+ .parent = parent_dev,
+ .name = "rockchip-gate-link-clk",
+ .id = clkbr->id,
+ .fwnode = dev_fwnode(parent_dev),
+ .of_node_reused = true,
+ .data = &gate_link_pdata,
+ .size_data = sizeof(gate_link_pdata),
+ };
+
+ return platform_device_register_full(&pdevinfo);
+}
+
void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
struct rockchip_clk_branch *list,
unsigned int nr_clk)
@@ -565,6 +589,9 @@ void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
list->div_width, list->div_flags,
ctx->reg_base, &ctx->lock);
break;
+ case branch_linked_gate:
+ /* must be registered late, fall-through for error message */
+ break;
}
/* none of the cases above matched */
@@ -585,6 +612,31 @@ void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
}
EXPORT_SYMBOL_GPL(rockchip_clk_register_branches);
+void rockchip_clk_register_late_branches(struct device *dev,
+ struct rockchip_clk_provider *ctx,
+ struct rockchip_clk_branch *list,
+ unsigned int nr_clk)
+{
+ unsigned int idx;
+
+ for (idx = 0; idx < nr_clk; idx++, list++) {
+ struct platform_device *pdev = NULL;
+
+ switch (list->branch_type) {
+ case branch_linked_gate:
+ pdev = rockchip_clk_register_gate_link(dev, ctx, list);
+ break;
+ default:
+ dev_err(dev, "unknown clock type %d\n", list->branch_type);
+ break;
+ }
+
+ if (!pdev)
+ dev_err(dev, "failed to register device for clock %s\n", list->name);
+ }
+}
+EXPORT_SYMBOL_GPL(rockchip_clk_register_late_branches);
+
void rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx,
unsigned int lookup_id,
const char *name, const char *const *parent_names,
diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h
index e39392e1c2a2..15aa2fd5265b 100644
--- a/drivers/clk/rockchip/clk.h
+++ b/drivers/clk/rockchip/clk.h
@@ -517,6 +517,7 @@ enum rockchip_clk_branch_type {
branch_divider,
branch_fraction_divider,
branch_gate,
+ branch_linked_gate,
branch_mmc,
branch_inverter,
branch_factor,
@@ -544,6 +545,7 @@ struct rockchip_clk_branch {
int gate_offset;
u8 gate_shift;
u8 gate_flags;
+ unsigned int linked_clk_id;
struct rockchip_clk_branch *child;
};
@@ -842,6 +844,20 @@ struct rockchip_clk_branch {
.gate_flags = gf, \
}
+#define GATE_LINK(_id, cname, pname, linkedclk, f, o, b, gf) \
+ { \
+ .id = _id, \
+ .branch_type = branch_linked_gate, \
+ .name = cname, \
+ .parent_names = (const char *[]){ pname }, \
+ .linked_clk_id = linkedclk, \
+ .num_parents = 1, \
+ .flags = f, \
+ .gate_offset = o, \
+ .gate_shift = b, \
+ .gate_flags = gf, \
+ }
+
#define MMC(_id, cname, pname, offset, shift) \
{ \
.id = _id, \
@@ -981,6 +997,11 @@ static inline void rockchip_clk_set_lookup(struct rockchip_clk_provider *ctx,
ctx->clk_data.clks[id] = clk;
}
+struct rockchip_gate_link_platdata {
+ struct rockchip_clk_provider *ctx;
+ struct rockchip_clk_branch *clkbr;
+};
+
struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np,
void __iomem *base, unsigned long nr_clks);
void rockchip_clk_of_add_provider(struct device_node *np,
@@ -990,6 +1011,10 @@ unsigned long rockchip_clk_find_max_clk_id(struct rockchip_clk_branch *list,
void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx,
struct rockchip_clk_branch *list,
unsigned int nr_clk);
+void rockchip_clk_register_late_branches(struct device *dev,
+ struct rockchip_clk_provider *ctx,
+ struct rockchip_clk_branch *list,
+ unsigned int nr_clk);
void rockchip_clk_register_plls(struct rockchip_clk_provider *ctx,
struct rockchip_pll_clock *pll_list,
unsigned int nr_pll, int grf_lock_offset);
diff --git a/drivers/clk/rockchip/gate-link.c b/drivers/clk/rockchip/gate-link.c
new file mode 100644
index 000000000000..47b6f3e7a6a2
--- /dev/null
+++ b/drivers/clk/rockchip/gate-link.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2024 Collabora Ltd.
+ * Author: Sebastian Reichel <sebastian.reichel@collabora.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/pm_clock.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include "clk.h"
+
+static int rk_clk_gate_link_register(struct device *dev,
+ struct rockchip_clk_provider *ctx,
+ struct rockchip_clk_branch *clkbr)
+{
+ unsigned long flags = clkbr->flags | CLK_SET_RATE_PARENT;
+ struct clk *clk;
+
+ clk = clk_register_gate(dev, clkbr->name, clkbr->parent_names[0],
+ flags, ctx->reg_base + clkbr->gate_offset,
+ clkbr->gate_shift, clkbr->gate_flags,
+ &ctx->lock);
+
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ rockchip_clk_set_lookup(ctx, clk, clkbr->id);
+ return 0;
+}
+
+static int rk_clk_gate_link_probe(struct platform_device *pdev)
+{
+ struct rockchip_gate_link_platdata *pdata;
+ struct device *dev = &pdev->dev;
+ struct clk *linked_clk;
+ int ret;
+
+ pdata = dev_get_platdata(dev);
+ if (!pdata)
+ return dev_err_probe(dev, -ENODEV, "missing platform data");
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = devm_pm_clk_create(dev);
+ if (ret)
+ return ret;
+
+ linked_clk = rockchip_clk_get_lookup(pdata->ctx, pdata->clkbr->linked_clk_id);
+ ret = pm_clk_add_clk(dev, linked_clk);
+ if (ret)
+ return ret;
+
+ ret = rk_clk_gate_link_register(dev, pdata->ctx, pdata->clkbr);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ pm_clk_remove_clk(dev, linked_clk);
+ return ret;
+}
+
+static void rk_clk_gate_link_remove(struct platform_device *pdev)
+{
+ struct rockchip_gate_link_platdata *pdata;
+ struct device *dev = &pdev->dev;
+ struct clk *clk, *linked_clk;
+
+ pdata = dev_get_platdata(dev);
+ clk = rockchip_clk_get_lookup(pdata->ctx, pdata->clkbr->id);
+ linked_clk = rockchip_clk_get_lookup(pdata->ctx, pdata->clkbr->linked_clk_id);
+ rockchip_clk_set_lookup(pdata->ctx, ERR_PTR(-ENODEV), pdata->clkbr->id);
+ clk_unregister_gate(clk);
+ pm_clk_remove_clk(dev, linked_clk);
+}
+
+static const struct dev_pm_ops rk_clk_gate_link_pm_ops = {
+ SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL)
+};
+
+struct platform_driver rk_clk_gate_link_driver = {
+ .probe = rk_clk_gate_link_probe,
+ .remove_new = rk_clk_gate_link_remove,
+ .driver = {
+ .name = "rockchip-gate-link-clk",
+ .pm = &rk_clk_gate_link_pm_ops,
+ },
+};
+
+static int __init rk_clk_gate_link_drv_register(void)
+{
+ return platform_driver_register(&rk_clk_gate_link_driver);
+}
+core_initcall(rk_clk_gate_link_drv_register);
--
2.44.1
From 97f9b5fa575c07a80527abdf17bf9153d258df23 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Fri, 8 Mar 2024 23:32:05 +0100
Subject: [PATCH 18/54] clk: rockchip: rk3588: drop RK3588_LINKED_CLK
With the proper GATE_LINK support, we no longer need to keep the
linked clocks always on. Thus it's time to drop the CLK_IS_CRITICAL
flag for them.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/clk/rockchip/clk-rk3588.c | 27 ++++++++++++---------------
1 file changed, 12 insertions(+), 15 deletions(-)
diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c
index 1bf84ac44e85..42579b6f74b4 100644
--- a/drivers/clk/rockchip/clk-rk3588.c
+++ b/drivers/clk/rockchip/clk-rk3588.c
@@ -12,9 +12,6 @@
#include <dt-bindings/clock/rockchip,rk3588-cru.h>
#include "clk.h"
-#define RK3588_LINKED_CLK CLK_IS_CRITICAL
-
-
#define RK3588_GRF_SOC_STATUS0 0x600
#define RK3588_PHYREF_ALT_GATE 0xc38
@@ -1439,7 +1436,7 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
COMPOSITE_NODIV(HCLK_NVM_ROOT, "hclk_nvm_root", mux_200m_100m_50m_24m_p, 0,
RK3588_CLKSEL_CON(77), 0, 2, MFLAGS,
RK3588_CLKGATE_CON(31), 0, GFLAGS),
- COMPOSITE(ACLK_NVM_ROOT, "aclk_nvm_root", gpll_cpll_p, RK3588_LINKED_CLK,
+ COMPOSITE(ACLK_NVM_ROOT, "aclk_nvm_root", gpll_cpll_p, 0,
RK3588_CLKSEL_CON(77), 7, 1, MFLAGS, 2, 5, DFLAGS,
RK3588_CLKGATE_CON(31), 1, GFLAGS),
GATE(ACLK_EMMC, "aclk_emmc", "aclk_nvm_root", 0,
@@ -1668,13 +1665,13 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
RK3588_CLKGATE_CON(42), 9, GFLAGS),
/* vdpu */
- COMPOSITE(ACLK_VDPU_ROOT, "aclk_vdpu_root", gpll_cpll_aupll_p, RK3588_LINKED_CLK,
+ COMPOSITE(ACLK_VDPU_ROOT, "aclk_vdpu_root", gpll_cpll_aupll_p, 0,
RK3588_CLKSEL_CON(98), 5, 2, MFLAGS, 0, 5, DFLAGS,
RK3588_CLKGATE_CON(44), 0, GFLAGS),
COMPOSITE_NODIV(ACLK_VDPU_LOW_ROOT, "aclk_vdpu_low_root", mux_400m_200m_100m_24m_p, 0,
RK3588_CLKSEL_CON(98), 7, 2, MFLAGS,
RK3588_CLKGATE_CON(44), 1, GFLAGS),
- COMPOSITE_NODIV(HCLK_VDPU_ROOT, "hclk_vdpu_root", mux_200m_100m_50m_24m_p, RK3588_LINKED_CLK,
+ COMPOSITE_NODIV(HCLK_VDPU_ROOT, "hclk_vdpu_root", mux_200m_100m_50m_24m_p, 0,
RK3588_CLKSEL_CON(98), 9, 2, MFLAGS,
RK3588_CLKGATE_CON(44), 2, GFLAGS),
COMPOSITE(ACLK_JPEG_DECODER_ROOT, "aclk_jpeg_decoder_root", gpll_cpll_aupll_spll_p, 0,
@@ -1725,9 +1722,9 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
COMPOSITE(ACLK_RKVENC0_ROOT, "aclk_rkvenc0_root", gpll_cpll_npll_p, 0,
RK3588_CLKSEL_CON(102), 7, 2, MFLAGS, 2, 5, DFLAGS,
RK3588_CLKGATE_CON(47), 1, GFLAGS),
- GATE(HCLK_RKVENC0, "hclk_rkvenc0", "hclk_rkvenc0_root", RK3588_LINKED_CLK,
+ GATE(HCLK_RKVENC0, "hclk_rkvenc0", "hclk_rkvenc0_root", 0,
RK3588_CLKGATE_CON(47), 4, GFLAGS),
- GATE(ACLK_RKVENC0, "aclk_rkvenc0", "aclk_rkvenc0_root", RK3588_LINKED_CLK,
+ GATE(ACLK_RKVENC0, "aclk_rkvenc0", "aclk_rkvenc0_root", 0,
RK3588_CLKGATE_CON(47), 5, GFLAGS),
COMPOSITE(CLK_RKVENC0_CORE, "clk_rkvenc0_core", gpll_cpll_aupll_npll_p, 0,
RK3588_CLKSEL_CON(102), 14, 2, MFLAGS, 9, 5, DFLAGS,
@@ -1737,10 +1734,10 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
RK3588_CLKGATE_CON(48), 6, GFLAGS),
/* vi */
- COMPOSITE(ACLK_VI_ROOT, "aclk_vi_root", gpll_cpll_npll_aupll_spll_p, RK3588_LINKED_CLK,
+ COMPOSITE(ACLK_VI_ROOT, "aclk_vi_root", gpll_cpll_npll_aupll_spll_p, 0,
RK3588_CLKSEL_CON(106), 5, 3, MFLAGS, 0, 5, DFLAGS,
RK3588_CLKGATE_CON(49), 0, GFLAGS),
- COMPOSITE_NODIV(HCLK_VI_ROOT, "hclk_vi_root", mux_200m_100m_50m_24m_p, RK3588_LINKED_CLK,
+ COMPOSITE_NODIV(HCLK_VI_ROOT, "hclk_vi_root", mux_200m_100m_50m_24m_p, 0,
RK3588_CLKSEL_CON(106), 8, 2, MFLAGS,
RK3588_CLKGATE_CON(49), 1, GFLAGS),
COMPOSITE_NODIV(PCLK_VI_ROOT, "pclk_vi_root", mux_100m_50m_24m_p, 0,
@@ -1910,10 +1907,10 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
COMPOSITE(ACLK_VOP_ROOT, "aclk_vop_root", gpll_cpll_dmyaupll_npll_spll_p, 0,
RK3588_CLKSEL_CON(110), 5, 3, MFLAGS, 0, 5, DFLAGS,
RK3588_CLKGATE_CON(52), 0, GFLAGS),
- COMPOSITE_NODIV(ACLK_VOP_LOW_ROOT, "aclk_vop_low_root", mux_400m_200m_100m_24m_p, RK3588_LINKED_CLK,
+ COMPOSITE_NODIV(ACLK_VOP_LOW_ROOT, "aclk_vop_low_root", mux_400m_200m_100m_24m_p, 0,
RK3588_CLKSEL_CON(110), 8, 2, MFLAGS,
RK3588_CLKGATE_CON(52), 1, GFLAGS),
- COMPOSITE_NODIV(HCLK_VOP_ROOT, "hclk_vop_root", mux_200m_100m_50m_24m_p, RK3588_LINKED_CLK,
+ COMPOSITE_NODIV(HCLK_VOP_ROOT, "hclk_vop_root", mux_200m_100m_50m_24m_p, 0,
RK3588_CLKSEL_CON(110), 10, 2, MFLAGS,
RK3588_CLKGATE_CON(52), 2, GFLAGS),
COMPOSITE_NODIV(PCLK_VOP_ROOT, "pclk_vop_root", mux_100m_50m_24m_p, 0,
@@ -2416,7 +2413,7 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
static struct rockchip_clk_branch rk3588_clk_branches[] = {
GATE_LINK(ACLK_ISP1_PRE, "aclk_isp1_pre", "aclk_isp1_root", ACLK_VI_ROOT, 0, RK3588_CLKGATE_CON(26), 6, GFLAGS),
GATE_LINK(HCLK_ISP1_PRE, "hclk_isp1_pre", "hclk_isp1_root", HCLK_VI_ROOT, 0, RK3588_CLKGATE_CON(26), 8, GFLAGS),
- GATE_LINK(HCLK_NVM, "hclk_nvm", "hclk_nvm_root", ACLK_NVM_ROOT, RK3588_LINKED_CLK, RK3588_CLKGATE_CON(31), 2, GFLAGS),
+ GATE_LINK(HCLK_NVM, "hclk_nvm", "hclk_nvm_root", ACLK_NVM_ROOT, 0, RK3588_CLKGATE_CON(31), 2, GFLAGS),
GATE_LINK(ACLK_USB, "aclk_usb", "aclk_usb_root", ACLK_VO1USB_TOP_ROOT, 0, RK3588_CLKGATE_CON(42), 2, GFLAGS),
GATE_LINK(HCLK_USB, "hclk_usb", "hclk_usb_root", HCLK_VO1USB_TOP_ROOT, 0, RK3588_CLKGATE_CON(42), 3, GFLAGS),
GATE_LINK(ACLK_JPEG_DECODER_PRE, "aclk_jpeg_decoder_pre", "aclk_jpeg_decoder_root", ACLK_VDPU_ROOT, 0, RK3588_CLKGATE_CON(44), 7, GFLAGS),
@@ -2428,9 +2425,9 @@ static struct rockchip_clk_branch rk3588_clk_branches[] = {
GATE_LINK(HCLK_RKVDEC1_PRE, "hclk_rkvdec1_pre", "hclk_rkvdec1_root", HCLK_VDPU_ROOT, 0, RK3588_CLKGATE_CON(41), 4, GFLAGS),
GATE_LINK(ACLK_RKVDEC1_PRE, "aclk_rkvdec1_pre", "aclk_rkvdec1_root", ACLK_VDPU_ROOT, 0, RK3588_CLKGATE_CON(41), 5, GFLAGS),
GATE_LINK(ACLK_HDCP0_PRE, "aclk_hdcp0_pre", "aclk_vo0_root", ACLK_VOP_LOW_ROOT, 0, RK3588_CLKGATE_CON(55), 9, GFLAGS),
- GATE_LINK(HCLK_VO0, "hclk_vo0", "hclk_vo0_root", HCLK_VOP_ROOT, RK3588_LINKED_CLK, RK3588_CLKGATE_CON(55), 5, GFLAGS),
+ GATE_LINK(HCLK_VO0, "hclk_vo0", "hclk_vo0_root", HCLK_VOP_ROOT, 0, RK3588_CLKGATE_CON(55), 5, GFLAGS),
GATE_LINK(ACLK_HDCP1_PRE, "aclk_hdcp1_pre", "aclk_hdcp1_root", ACLK_VO1USB_TOP_ROOT, 0, RK3588_CLKGATE_CON(59), 6, GFLAGS),
- GATE_LINK(HCLK_VO1, "hclk_vo1", "hclk_vo1_root", HCLK_VO1USB_TOP_ROOT, RK3588_LINKED_CLK, RK3588_CLKGATE_CON(59), 9, GFLAGS),
+ GATE_LINK(HCLK_VO1, "hclk_vo1", "hclk_vo1_root", HCLK_VO1USB_TOP_ROOT, 0, RK3588_CLKGATE_CON(59), 9, GFLAGS),
GATE_LINK(ACLK_AV1_PRE, "aclk_av1_pre", "aclk_av1_root", ACLK_VDPU_ROOT, 0, RK3588_CLKGATE_CON(68), 1, GFLAGS),
GATE_LINK(PCLK_AV1_PRE, "pclk_av1_pre", "pclk_av1_root", HCLK_VDPU_ROOT, 0, RK3588_CLKGATE_CON(68), 4, GFLAGS),
GATE_LINK(HCLK_SDIO_PRE, "hclk_sdio_pre", "hclk_sdio_root", HCLK_NVM, 0, RK3588_CLKGATE_CON(75), 1, GFLAGS),
--
2.44.1
From 70a3aa25bcb8fa0c5f63d613ee562d6b50e43843 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Tue, 2 Jan 2024 09:35:43 +0100
Subject: [PATCH 19/54] arm64: dts: rockchip: rk3588-evb1: add bluetooth rfkill
Add rfkill support for bluetooth. Bluetooth support itself is still
missing, but this ensures bluetooth can be powered off properly.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
index 65e16410084a..761e0108285d 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
@@ -120,6 +120,15 @@ backlight: backlight {
pwms = <&pwm2 0 25000 0>;
};
+ bluetooth-rfkill {
+ compatible = "rfkill-gpio";
+ label = "rfkill-bluetooth";
+ radio-type = "bluetooth";
+ shutdown-gpios = <&gpio3 RK_PA6 GPIO_ACTIVE_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&bluetooth_pwren>;
+ };
+
wlan-rfkill {
compatible = "rfkill-gpio";
label = "rfkill-pcie-wlan";
@@ -481,6 +490,12 @@ speaker_amplifier_en: speaker-amplifier-en {
};
};
+ bluetooth {
+ bluetooth_pwren: bluetooth-pwren {
+ rockchip,pins = <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
rtl8111 {
rtl8111_isolate: rtl8111-isolate {
rockchip,pins = <1 RK_PA4 RK_FUNC_GPIO &pcfg_pull_up>;
--
2.44.1
From 40e20352a122c1fe1e35c3140034fedd5129199d Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Tue, 2 Jan 2024 09:39:11 +0100
Subject: [PATCH 20/54] arm64: dts: rockchip: rk3588-evb1: improve PCIe
ethernet pin muxing
Also describe clkreq and wake signals in the PCIe pinmux used
by the onboard LAN card.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
index 761e0108285d..dd2fb2515900 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
@@ -459,7 +459,7 @@ &pcie2x1l0 {
&pcie2x1l1 {
reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
- pinctrl-0 = <&pcie2_1_rst>, <&rtl8111_isolate>;
+ pinctrl-0 = <&pcie2_1_rst>, <&pcie2_1_wake>, <&pcie2_1_clkreq>, <&rtl8111_isolate>;
status = "okay";
};
@@ -531,6 +531,14 @@ pcie2_0_clkreq: pcie2-0-clkreq {
pcie2_1_rst: pcie2-1-rst {
rockchip,pins = <4 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>;
};
+
+ pcie2_1_wake: pcie2-1-wake {
+ rockchip,pins = <4 RK_PA1 4 &pcfg_pull_none>;
+ };
+
+ pcie2_1_clkreq: pcie2-1-clkreq {
+ rockchip,pins = <4 RK_PA0 4 &pcfg_pull_none>;
+ };
};
pcie3 {
--
2.44.1
From 42bfdc60fdfa7d27754aaa9116def24f018ca4a9 Mon Sep 17 00:00:00 2001
From: "Carsten Haitzler (Rasterman)" <raster@rasterman.com>
Date: Tue, 6 Feb 2024 10:12:54 +0000
Subject: [PATCH 21/54] arm64: dts: rockchip: Slow down EMMC a bit to keep IO
stable
This drops to hs200 mode and 150Mhz as this is actually stable across
eMMC modules. There exist some that are incompatible at higher rates
with the rk3588 and to avoid your filesystem corrupting due to IO
errors, be more conservative and reduce the max. speed.
Signed-off-by: Carsten Haitzler <raster@rasterman.com>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
index e18e970393a6..f311c8f9d437 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
@@ -475,8 +475,8 @@ &sdhci {
no-sdio;
no-sd;
non-removable;
- mmc-hs400-1_8v;
- mmc-hs400-enhanced-strobe;
+ max-frequency = <150000000>;
+ mmc-hs200-1_8v;
status = "okay";
};
--
2.44.1
From bddd59567560d46a3d9363db6692f3232199bd20 Mon Sep 17 00:00:00 2001
From: Shreeya Patel <shreeya.patel@collabora.com>
Date: Wed, 20 Dec 2023 18:30:13 +0530
Subject: [PATCH 22/54] dt-bindings: media: Document bindings for HDMI RX
Controller
Document bindings for the Synopsys DesignWare HDMI RX Controller.
Reviewed-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: Shreeya Patel <shreeya.patel@collabora.com>
---
.../bindings/media/snps,dw-hdmi-rx.yaml | 130 ++++++++++++++++++
1 file changed, 130 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
new file mode 100644
index 000000000000..903aa62d33ef
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.yaml
@@ -0,0 +1,130 @@
+# SPDX-License-Identifier: (GPL-3.0 OR BSD-2-Clause)
+# Device Tree bindings for Synopsys DesignWare HDMI RX Controller
+
+---
+$id: http://devicetree.org/schemas/media/snps,dw-hdmi-rx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Synopsys DesignWare HDMI RX Controller
+
+maintainers:
+ - Shreeya Patel <shreeya.patel@collabora.com>
+
+properties:
+ compatible:
+ items:
+ - const: rockchip,rk3588-hdmirx-ctrler
+ - const: snps,dw-hdmi-rx
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 3
+
+ interrupt-names:
+ items:
+ - const: cec
+ - const: hdmi
+ - const: dma
+
+ clocks:
+ maxItems: 7
+
+ clock-names:
+ items:
+ - const: aclk
+ - const: audio
+ - const: cr_para
+ - const: pclk
+ - const: ref
+ - const: hclk_s_hdmirx
+ - const: hclk_vo1
+
+ power-domains:
+ maxItems: 1
+
+ resets:
+ maxItems: 4
+
+ reset-names:
+ items:
+ - const: rst_a
+ - const: rst_p
+ - const: rst_ref
+ - const: rst_biu
+
+ pinctrl-names:
+ const: default
+
+ memory-region:
+ maxItems: 1
+
+ hdmirx-5v-detection-gpios:
+ description: GPIO specifier for 5V detection.
+ maxItems: 1
+
+ rockchip,grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ The phandle of the syscon node for the GRF register.
+
+ rockchip,vo1_grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ The phandle of the syscon node for the VO1 GRF register.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - clocks
+ - clock-names
+ - power-domains
+ - resets
+ - pinctrl-0
+ - pinctrl-names
+ - hdmirx-5v-detection-gpios
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/rockchip,rk3588-cru.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/power/rk3588-power.h>
+ #include <dt-bindings/reset/rockchip,rk3588-cru.h>
+ #include <dt-bindings/pinctrl/rockchip.h>
+ #include <dt-bindings/gpio/gpio.h>
+ hdmirx_ctrler: hdmirx-controller@fdee0000 {
+ compatible = "rockchip,rk3588-hdmirx-ctrler", "snps,dw-hdmi-rx";
+ reg = <0xfdee0000 0x6000>;
+ interrupts = <GIC_SPI 177 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 436 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 179 IRQ_TYPE_LEVEL_HIGH 0>;
+ interrupt-names = "cec", "hdmi", "dma";
+ clocks = <&cru ACLK_HDMIRX>,
+ <&cru CLK_HDMIRX_AUD>,
+ <&cru CLK_CR_PARA>,
+ <&cru PCLK_HDMIRX>,
+ <&cru CLK_HDMIRX_REF>,
+ <&cru PCLK_S_HDMIRX>,
+ <&cru HCLK_VO1>;
+ clock-names = "aclk",
+ "audio",
+ "cr_para",
+ "pclk",
+ "ref",
+ "hclk_s_hdmirx",
+ "hclk_vo1";
+ power-domains = <&power RK3588_PD_VO1>;
+ resets = <&cru SRST_A_HDMIRX>, <&cru SRST_P_HDMIRX>,
+ <&cru SRST_HDMIRX_REF>, <&cru SRST_A_HDMIRX_BIU>;
+ reset-names = "rst_a", "rst_p", "rst_ref", "rst_biu";
+ pinctrl-0 = <&hdmim1_rx_cec &hdmim1_rx_hpdin &hdmim1_rx_scl &hdmim1_rx_sda &hdmirx_5v_detection>;
+ pinctrl-names = "default";
+ memory-region = <&hdmirx_cma>;
+ hdmirx-5v-detection-gpios = <&gpio1 RK_PC6 GPIO_ACTIVE_LOW>;
+ };
--
2.44.1
From 8353d22573a2b462a538a4b8e826f9fd9b4cdbae Mon Sep 17 00:00:00 2001
From: Shreeya Patel <shreeya.patel@collabora.com>
Date: Wed, 20 Dec 2023 16:50:14 +0530
Subject: [PATCH 23/54] arm64: dts: rockchip: Add device tree support for HDMI
RX Controller
Add device tree support for Synopsys DesignWare HDMI RX
Controller.
Signed-off-by: Dingxian Wen <shawn.wen@rock-chips.com>
Co-developed-by: Shreeya Patel <shreeya.patel@collabora.com>
Reviewed-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Tested-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: Shreeya Patel <shreeya.patel@collabora.com>
---
.../boot/dts/rockchip/rk3588-pinctrl.dtsi | 41 +++++++++++++++
.../boot/dts/rockchip/rk3588-rock-5b.dts | 18 +++++++
arch/arm64/boot/dts/rockchip/rk3588.dtsi | 50 +++++++++++++++++++
3 files changed, 109 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-pinctrl.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-pinctrl.dtsi
index 244c66faa161..e5f3d0acbd55 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-pinctrl.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588-pinctrl.dtsi
@@ -169,6 +169,47 @@ hdmim0_tx1_sda: hdmim0-tx1-sda {
/* hdmim0_tx1_sda */
<2 RK_PB4 4 &pcfg_pull_none>;
};
+
+ /omit-if-no-ref/
+ hdmim1_rx: hdmim1-rx {
+ rockchip,pins =
+ /* hdmim1_rx_cec */
+ <3 RK_PD1 5 &pcfg_pull_none>,
+ /* hdmim1_rx_scl */
+ <3 RK_PD2 5 &pcfg_pull_none_smt>,
+ /* hdmim1_rx_sda */
+ <3 RK_PD3 5 &pcfg_pull_none_smt>,
+ /* hdmim1_rx_hpdin */
+ <3 RK_PD4 5 &pcfg_pull_none>;
+ };
+
+ /omit-if-no-ref/
+ hdmim1_rx_cec: hdmim1-rx-cec {
+ rockchip,pins =
+ /* hdmim1_rx_cec */
+ <3 RK_PD1 5 &pcfg_pull_none>;
+ };
+
+ /omit-if-no-ref/
+ hdmim1_rx_hpdin: hdmim1-rx-hpdin {
+ rockchip,pins =
+ /* hdmim1_rx_hpdin */
+ <3 RK_PD4 5 &pcfg_pull_none>;
+ };
+
+ /omit-if-no-ref/
+ hdmim1_rx_scl: hdmim1-rx-scl {
+ rockchip,pins =
+ /* hdmim1_rx_scl */
+ <3 RK_PD2 5 &pcfg_pull_none>;
+ };
+
+ /omit-if-no-ref/
+ hdmim1_rx_sda: hdmim1-rx-sda {
+ rockchip,pins =
+ /* hdmim1_rx_sda */
+ <3 RK_PD3 5 &pcfg_pull_none>;
+ };
};
i2c0 {
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
index f311c8f9d437..f013d7841e89 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
@@ -196,6 +196,18 @@ &gpu {
status = "okay";
};
+&hdmirx_cma {
+ status = "okay";
+};
+
+&hdmirx_ctrler {
+ status = "okay";
+ hdmirx-5v-detection-gpios = <&gpio1 RK_PC6 GPIO_ACTIVE_LOW>;
+ pinctrl-0 = <&hdmim1_rx_cec &hdmim1_rx_hpdin &hdmim1_rx_scl &hdmim1_rx_sda &hdmirx_5v_detection>;
+ pinctrl-names = "default";
+ memory-region = <&hdmirx_cma>;
+};
+
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0m2_xfer>;
@@ -408,6 +420,12 @@ &pcie3x4 {
};
&pinctrl {
+ hdmirx {
+ hdmirx_5v_detection: hdmirx-5v-detection {
+ rockchip,pins = <1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+
hym8563 {
hym8563_int: hym8563-int {
rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
diff --git a/arch/arm64/boot/dts/rockchip/rk3588.dtsi b/arch/arm64/boot/dts/rockchip/rk3588.dtsi
index 5984016b5f96..534c42262c73 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588.dtsi
@@ -7,6 +7,24 @@
#include "rk3588-pinctrl.dtsi"
/ {
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ /*
+ * The 4k HDMI capture controller works only with 32bit
+ * phys addresses and doesn't support IOMMU. HDMI RX CMA
+ * must be reserved below 4GB.
+ */
+ hdmirx_cma: hdmirx_cma {
+ compatible = "shared-dma-pool";
+ alloc-ranges = <0x0 0x0 0x0 0xffffffff>;
+ size = <0x0 (160 * 0x100000)>; /* 160MiB */
+ no-map;
+ status = "disabled";
+ };
+ };
+
usb_host1_xhci: usb@fc400000 {
compatible = "rockchip,rk3588-dwc3", "snps,dwc3";
reg = <0x0 0xfc400000 0x0 0x400000>;
@@ -135,6 +153,38 @@ i2s10_8ch: i2s@fde00000 {
status = "disabled";
};
+ hdmirx_ctrler: hdmirx-controller@fdee0000 {
+ compatible = "rockchip,rk3588-hdmirx-ctrler", "snps,dw-hdmi-rx";
+ reg = <0x0 0xfdee0000 0x0 0x6000>;
+ power-domains = <&power RK3588_PD_VO1>;
+ rockchip,grf = <&sys_grf>;
+ rockchip,vo1_grf = <&vo1_grf>;
+ interrupts = <GIC_SPI 177 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 436 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 179 IRQ_TYPE_LEVEL_HIGH 0>;
+ interrupt-names = "cec", "hdmi", "dma";
+ clocks = <&cru ACLK_HDMIRX>,
+ <&cru CLK_HDMIRX_AUD>,
+ <&cru CLK_CR_PARA>,
+ <&cru PCLK_HDMIRX>,
+ <&cru CLK_HDMIRX_REF>,
+ <&cru PCLK_S_HDMIRX>,
+ <&cru HCLK_VO1>;
+ clock-names = "aclk",
+ "audio",
+ "cr_para",
+ "pclk",
+ "ref",
+ "hclk_s_hdmirx",
+ "hclk_vo1";
+ resets = <&cru SRST_A_HDMIRX>, <&cru SRST_P_HDMIRX>,
+ <&cru SRST_HDMIRX_REF>, <&cru SRST_A_HDMIRX_BIU>;
+ reset-names = "rst_a", "rst_p", "rst_ref", "rst_biu";
+ pinctrl-0 = <&hdmim1_rx>;
+ pinctrl-names = "default";
+ status = "disabled";
+ };
+
pcie3x4: pcie@fe150000 {
compatible = "rockchip,rk3588-pcie", "rockchip,rk3568-pcie";
#address-cells = <3>;
--
2.44.1
From 820dbb912706cdfc378cd4e344a92aca94a6dc57 Mon Sep 17 00:00:00 2001
From: Shreeya Patel <shreeya.patel@collabora.com>
Date: Wed, 20 Dec 2023 16:52:01 +0530
Subject: [PATCH 24/54] media: platform: synopsys: Add support for hdmi input
driver
Add initial support for the Synopsys DesignWare HDMI RX
Controller Driver used by Rockchip RK3588. The driver
supports:
- HDMI 1.4b and 2.0 modes (HDMI 4k@60Hz)
- RGB888, YUV422, YUV444 and YCC420 pixel formats
- CEC
- EDID configuration
The hardware also has Audio and HDCP capabilities, but these are
not yet supported by the driver.
Signed-off-by: Dingxian Wen <shawn.wen@rock-chips.com>
Co-developed-by: Shreeya Patel <shreeya.patel@collabora.com>
Reviewed-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Tested-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: Shreeya Patel <shreeya.patel@collabora.com>
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/synopsys/Kconfig | 3 +
drivers/media/platform/synopsys/Makefile | 2 +
.../media/platform/synopsys/hdmirx/Kconfig | 18 +
.../media/platform/synopsys/hdmirx/Makefile | 4 +
.../platform/synopsys/hdmirx/snps_hdmirx.c | 2856 +++++++++++++++++
.../platform/synopsys/hdmirx/snps_hdmirx.h | 394 +++
.../synopsys/hdmirx/snps_hdmirx_cec.c | 289 ++
.../synopsys/hdmirx/snps_hdmirx_cec.h | 46 +
10 files changed, 3614 insertions(+)
create mode 100644 drivers/media/platform/synopsys/Kconfig
create mode 100644 drivers/media/platform/synopsys/Makefile
create mode 100644 drivers/media/platform/synopsys/hdmirx/Kconfig
create mode 100644 drivers/media/platform/synopsys/hdmirx/Makefile
create mode 100644 drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
create mode 100644 drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h
create mode 100644 drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.c
create mode 100644 drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 2d79bfc68c15..a7b84d45cb86 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -83,6 +83,7 @@ source "drivers/media/platform/rockchip/Kconfig"
source "drivers/media/platform/samsung/Kconfig"
source "drivers/media/platform/st/Kconfig"
source "drivers/media/platform/sunxi/Kconfig"
+source "drivers/media/platform/synopsys/Kconfig"
source "drivers/media/platform/ti/Kconfig"
source "drivers/media/platform/verisilicon/Kconfig"
source "drivers/media/platform/via/Kconfig"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index da17301f7439..3b7aff947f91 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -26,6 +26,7 @@ obj-y += rockchip/
obj-y += samsung/
obj-y += st/
obj-y += sunxi/
+obj-y += synopsys/
obj-y += ti/
obj-y += verisilicon/
obj-y += via/
diff --git a/drivers/media/platform/synopsys/Kconfig b/drivers/media/platform/synopsys/Kconfig
new file mode 100644
index 000000000000..4fd521f78425
--- /dev/null
+++ b/drivers/media/platform/synopsys/Kconfig
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+source "drivers/media/platform/synopsys/hdmirx/Kconfig"
diff --git a/drivers/media/platform/synopsys/Makefile b/drivers/media/platform/synopsys/Makefile
new file mode 100644
index 000000000000..3b12c574dd67
--- /dev/null
+++ b/drivers/media/platform/synopsys/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-y += hdmirx/
diff --git a/drivers/media/platform/synopsys/hdmirx/Kconfig b/drivers/media/platform/synopsys/hdmirx/Kconfig
new file mode 100644
index 000000000000..adcdb7c2ed79
--- /dev/null
+++ b/drivers/media/platform/synopsys/hdmirx/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config VIDEO_SYNOPSYS_HDMIRX
+ tristate "Synopsys DesignWare HDMI Receiver driver"
+ depends on VIDEO_DEV
+ depends on ARCH_ROCKCHIP
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select VIDEOBUF2_DMA_CONTIG
+ select CEC_CORE
+ select CEC_NOTIFIER
+ select HDMI
+ help
+ Support for Synopsys HDMI HDMI RX Controller.
+ This driver supports HDMI 2.0 version.
+
+ To compile this driver as a module, choose M here. The module
+ will be called synopsys_hdmirx.
diff --git a/drivers/media/platform/synopsys/hdmirx/Makefile b/drivers/media/platform/synopsys/hdmirx/Makefile
new file mode 100644
index 000000000000..2fa2d9e25300
--- /dev/null
+++ b/drivers/media/platform/synopsys/hdmirx/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+synopsys-hdmirx-objs := snps_hdmirx.o snps_hdmirx_cec.o
+
+obj-$(CONFIG_VIDEO_SYNOPSYS_HDMIRX) += synopsys-hdmirx.o
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
new file mode 100644
index 000000000000..63a38ee089ec
--- /dev/null
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c
@@ -0,0 +1,2856 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Collabora, Ltd.
+ * Author: Shreeya Patel <shreeya.patel@collabora.com>
+ *
+ * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
+ * Author: Dingxian Wen <shawn.wen@rock-chips.com>
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/v4l2-dv-timings.h>
+#include <linux/workqueue.h>
+
+#include <media/cec.h>
+#include <media/cec-notifier.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+
+#include <sound/hdmi-codec.h>
+
+#include "snps_hdmirx.h"
+#include "snps_hdmirx_cec.h"
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level (0-3)");
+
+#define EDID_NUM_BLOCKS_MAX 2
+#define EDID_BLOCK_SIZE 128
+#define HDMIRX_STORED_BIT_WIDTH 8
+#define IREF_CLK_FREQ_HZ 428571429
+#define MEMORY_ALIGN_ROUND_UP_BYTES 64
+#define HDMIRX_PLANE_Y 0
+#define HDMIRX_PLANE_CBCR 1
+#define RK_IRQ_HDMIRX_HDMI 210
+#define FILTER_FRAME_CNT 6
+#define RK_SIP_FIQ_CTRL 0x82000024
+#define SIP_WDT_CFG 0x82000026
+#define DETECTION_THRESHOLD 7
+
+/* fiq control sub func */
+enum {
+ RK_SIP_FIQ_CTRL_FIQ_EN = 1,
+ RK_SIP_FIQ_CTRL_FIQ_DIS,
+ RK_SIP_FIQ_CTRL_SET_AFF
+};
+
+/* SIP_WDT_CONFIG call types */
+enum {
+ WDT_START = 0,
+ WDT_STOP = 1,
+ WDT_PING = 2,
+};
+
+enum hdmirx_pix_fmt {
+ HDMIRX_RGB888 = 0,
+ HDMIRX_YUV422 = 1,
+ HDMIRX_YUV444 = 2,
+ HDMIRX_YUV420 = 3,
+};
+
+enum ddr_store_fmt {
+ STORE_RGB888 = 0,
+ STORE_RGBA_ARGB,
+ STORE_YUV420_8BIT,
+ STORE_YUV420_10BIT,
+ STORE_YUV422_8BIT,
+ STORE_YUV422_10BIT,
+ STORE_YUV444_8BIT,
+ STORE_YUV420_16BIT = 8,
+ STORE_YUV422_16BIT = 9,
+};
+
+enum hdmirx_reg_attr {
+ HDMIRX_ATTR_RW = 0,
+ HDMIRX_ATTR_RO = 1,
+ HDMIRX_ATTR_WO = 2,
+ HDMIRX_ATTR_RE = 3,
+};
+
+enum hdmirx_edid_version {
+ HDMIRX_EDID_USER = 0,
+ HDMIRX_EDID_340M = 1,
+ HDMIRX_EDID_600M = 2,
+};
+
+enum {
+ HDMIRX_RST_A,
+ HDMIRX_RST_P,
+ HDMIRX_RST_REF,
+ HDMIRX_RST_BIU,
+ HDMIRX_NUM_RST,
+};
+
+static const char * const pix_fmt_str[] = {
+ "RGB888",
+ "YUV422",
+ "YUV444",
+ "YUV420",
+};
+
+struct hdmirx_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head queue;
+ u32 buff_addr[VIDEO_MAX_PLANES];
+};
+
+struct hdmirx_output_fmt {
+ u32 fourcc;
+ u8 cplanes;
+ u8 mplanes;
+ u8 bpp[VIDEO_MAX_PLANES];
+};
+
+struct hdmirx_stream {
+ struct snps_hdmirx_dev *hdmirx_dev;
+ struct video_device vdev;
+ struct vb2_queue buf_queue;
+ struct list_head buf_head;
+ struct hdmirx_buffer *curr_buf;
+ struct hdmirx_buffer *next_buf;
+ struct v4l2_pix_format_mplane pixm;
+ const struct hdmirx_output_fmt *out_fmt;
+ struct mutex vlock;
+ spinlock_t vbq_lock;
+ bool stopping;
+ wait_queue_head_t wq_stopped;
+ u32 frame_idx;
+ u32 line_flag_int_cnt;
+ u32 irq_stat;
+};
+
+struct snps_hdmirx_dev {
+ struct device *dev;
+ struct device *codec_dev;
+ struct hdmirx_stream stream;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_ctrl *detect_tx_5v_ctrl;
+ struct v4l2_dv_timings timings;
+ struct gpio_desc *detect_5v_gpio;
+ struct work_struct work_wdt_config;
+ struct delayed_work delayed_work_hotplug;
+ struct delayed_work delayed_work_res_change;
+ struct delayed_work delayed_work_heartbeat;
+ struct cec_notifier *cec_notifier;
+ struct hdmirx_cec *cec;
+ struct mutex stream_lock;
+ struct mutex work_lock;
+ struct reset_control_bulk_data resets[HDMIRX_NUM_RST];
+ struct clk_bulk_data *clks;
+ struct regmap *grf;
+ struct regmap *vo1_grf;
+ struct completion cr_write_done;
+ struct completion timer_base_lock;
+ struct completion avi_pkt_rcv;
+ enum hdmirx_edid_version edid_version;
+ enum hdmirx_pix_fmt pix_fmt;
+ void __iomem *regs;
+ int hdmi_irq;
+ int dma_irq;
+ int det_irq;
+ bool hpd_trigger_level;
+ bool tmds_clk_ratio;
+ bool is_dvi_mode;
+ bool got_timing;
+ u32 num_clks;
+ u32 edid_blocks_written;
+ u32 cur_vic;
+ u32 cur_fmt_fourcc;
+ u32 color_depth;
+ u8 edid[EDID_BLOCK_SIZE * 2];
+ hdmi_codec_plugged_cb plugged_cb;
+ spinlock_t rst_lock;
+};
+
+static u8 edid_init_data_340M[] = {
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x49, 0x70, 0x88, 0x35, 0x01, 0x00, 0x00, 0x00,
+ 0x2D, 0x1F, 0x01, 0x03, 0x80, 0x78, 0x44, 0x78,
+ 0x0A, 0xCF, 0x74, 0xA3, 0x57, 0x4C, 0xB0, 0x23,
+ 0x09, 0x48, 0x4C, 0x21, 0x08, 0x00, 0x61, 0x40,
+ 0x01, 0x01, 0x81, 0x00, 0x95, 0x00, 0xA9, 0xC0,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3A,
+ 0x80, 0x18, 0x71, 0x38, 0x2D, 0x40, 0x58, 0x2C,
+ 0x45, 0x00, 0x20, 0xC2, 0x31, 0x00, 0x00, 0x1E,
+ 0x01, 0x1D, 0x00, 0x72, 0x51, 0xD0, 0x1E, 0x20,
+ 0x6E, 0x28, 0x55, 0x00, 0x20, 0xC2, 0x31, 0x00,
+ 0x00, 0x1E, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x52,
+ 0x4B, 0x2D, 0x55, 0x48, 0x44, 0x0A, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xFD,
+ 0x00, 0x3B, 0x46, 0x1F, 0x8C, 0x3C, 0x00, 0x0A,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0xA7,
+
+ 0x02, 0x03, 0x2F, 0xD1, 0x51, 0x07, 0x16, 0x14,
+ 0x05, 0x01, 0x03, 0x12, 0x13, 0x84, 0x22, 0x1F,
+ 0x90, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x23, 0x09,
+ 0x07, 0x07, 0x83, 0x01, 0x00, 0x00, 0x67, 0x03,
+ 0x0C, 0x00, 0x30, 0x00, 0x10, 0x44, 0xE3, 0x05,
+ 0x03, 0x01, 0xE4, 0x0F, 0x00, 0x80, 0x01, 0x02,
+ 0x3A, 0x80, 0x18, 0x71, 0x38, 0x2D, 0x40, 0x58,
+ 0x2C, 0x45, 0x00, 0x20, 0xC2, 0x31, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F,
+};
+
+static u8 edid_init_data_600M[] = {
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
+ 0x49, 0x70, 0x88, 0x35, 0x01, 0x00, 0x00, 0x00,
+ 0x2D, 0x1F, 0x01, 0x03, 0x80, 0x78, 0x44, 0x78,
+ 0x0A, 0xCF, 0x74, 0xA3, 0x57, 0x4C, 0xB0, 0x23,
+ 0x09, 0x48, 0x4C, 0x00, 0x00, 0x00, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x08, 0xE8,
+ 0x00, 0x30, 0xF2, 0x70, 0x5A, 0x80, 0xB0, 0x58,
+ 0x8A, 0x00, 0xC4, 0x8E, 0x21, 0x00, 0x00, 0x1E,
+ 0x08, 0xE8, 0x00, 0x30, 0xF2, 0x70, 0x5A, 0x80,
+ 0xB0, 0x58, 0x8A, 0x00, 0x20, 0xC2, 0x31, 0x00,
+ 0x00, 0x1E, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x52,
+ 0x4B, 0x2D, 0x55, 0x48, 0x44, 0x0A, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xFD,
+ 0x00, 0x3B, 0x46, 0x1F, 0x8C, 0x3C, 0x00, 0x0A,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0x39,
+
+ 0x02, 0x03, 0x21, 0xD2, 0x41, 0x61, 0x23, 0x09,
+ 0x07, 0x07, 0x83, 0x01, 0x00, 0x00, 0x66, 0x03,
+ 0x0C, 0x00, 0x30, 0x00, 0x10, 0x67, 0xD8, 0x5D,
+ 0xC4, 0x01, 0x78, 0xC0, 0x07, 0xE3, 0x05, 0x03,
+ 0x01, 0x08, 0xE8, 0x00, 0x30, 0xF2, 0x70, 0x5A,
+ 0x80, 0xB0, 0x58, 0x8A, 0x00, 0xC4, 0x8E, 0x21,
+ 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE8,
+};
+
+static const struct v4l2_dv_timings cea640x480 = V4L2_DV_BT_CEA_640X480P59_94;
+
+static const struct v4l2_dv_timings_cap hdmirx_timings_cap = {
+ .type = V4L2_DV_BT_656_1120,
+ .reserved = { 0 },
+ V4L2_INIT_BT_TIMINGS(640, 4096, /* min/max width */
+ 480, 2160, /* min/max height */
+ 20000000, 600000000, /* min/max pixelclock */
+ /* standards */
+ V4L2_DV_BT_STD_CEA861,
+ /* capabilities */
+ V4L2_DV_BT_CAP_PROGRESSIVE |
+ V4L2_DV_BT_CAP_INTERLACED)
+};
+
+static const struct hdmirx_output_fmt g_out_fmts[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_BGR24,
+ .cplanes = 1,
+ .mplanes = 1,
+ .bpp = { 24 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV24,
+ .cplanes = 2,
+ .mplanes = 1,
+ .bpp = { 8, 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV16,
+ .cplanes = 2,
+ .mplanes = 1,
+ .bpp = { 8, 16 },
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .cplanes = 2,
+ .mplanes = 1,
+ .bpp = { 8, 16 },
+ }
+};
+
+static void hdmirx_writel(struct snps_hdmirx_dev *hdmirx_dev, int reg, u32 val)
+{
+ unsigned long lock_flags = 0;
+
+ spin_lock_irqsave(&hdmirx_dev->rst_lock, lock_flags);
+ writel(val, hdmirx_dev->regs + reg);
+ spin_unlock_irqrestore(&hdmirx_dev->rst_lock, lock_flags);
+}
+
+static u32 hdmirx_readl(struct snps_hdmirx_dev *hdmirx_dev, int reg)
+{
+ unsigned long lock_flags = 0;
+ u32 val;
+
+ spin_lock_irqsave(&hdmirx_dev->rst_lock, lock_flags);
+ val = readl(hdmirx_dev->regs + reg);
+ spin_unlock_irqrestore(&hdmirx_dev->rst_lock, lock_flags);
+ return val;
+}
+
+static void hdmirx_reset_dma(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ unsigned long lock_flags = 0;
+
+ spin_lock_irqsave(&hdmirx_dev->rst_lock, lock_flags);
+ reset_control_reset(hdmirx_dev->resets[0].rstc);
+ spin_unlock_irqrestore(&hdmirx_dev->rst_lock, lock_flags);
+}
+
+static void hdmirx_update_bits(struct snps_hdmirx_dev *hdmirx_dev, int reg,
+ u32 mask, u32 data)
+{
+ unsigned long lock_flags = 0;
+ u32 val;
+
+ spin_lock_irqsave(&hdmirx_dev->rst_lock, lock_flags);
+ val = readl(hdmirx_dev->regs + reg) & ~mask;
+ val |= (data & mask);
+ writel(val, hdmirx_dev->regs + reg);
+ spin_unlock_irqrestore(&hdmirx_dev->rst_lock, lock_flags);
+}
+
+static int hdmirx_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_SOURCE_CHANGE:
+ if (fh->vdev->vfl_dir == VFL_DIR_RX)
+ return v4l2_src_change_event_subscribe(fh, sub);
+ break;
+ case V4L2_EVENT_CTRL:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ default:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ }
+
+ return -EINVAL;
+}
+
+static bool tx_5v_power_present(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ bool ret;
+ int val, i, cnt;
+
+ cnt = 0;
+ for (i = 0; i < 10; i++) {
+ usleep_range(1000, 1100);
+ val = gpiod_get_value(hdmirx_dev->detect_5v_gpio);
+ if (val > 0)
+ cnt++;
+ if (cnt >= DETECTION_THRESHOLD)
+ break;
+ }
+
+ ret = (cnt >= DETECTION_THRESHOLD) ? true : false;
+ v4l2_dbg(3, debug, &hdmirx_dev->v4l2_dev, "%s: %d\n", __func__, ret);
+
+ return ret;
+}
+
+static bool signal_not_lock(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ u32 mu_status, dma_st10, cmu_st;
+
+ mu_status = hdmirx_readl(hdmirx_dev, MAINUNIT_STATUS);
+ dma_st10 = hdmirx_readl(hdmirx_dev, DMA_STATUS10);
+ cmu_st = hdmirx_readl(hdmirx_dev, CMU_STATUS);
+
+ if ((mu_status & TMDSVALID_STABLE_ST) &&
+ (dma_st10 & HDMIRX_LOCK) &&
+ (cmu_st & TMDSQPCLK_LOCKED_ST))
+ return false;
+
+ return true;
+}
+
+static void hdmirx_get_colordepth(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ u32 val, color_depth_reg;
+
+ val = hdmirx_readl(hdmirx_dev, DMA_STATUS11);
+ color_depth_reg = (val & HDMIRX_COLOR_DEPTH_MASK) >> 3;
+
+ switch (color_depth_reg) {
+ case 0x4:
+ hdmirx_dev->color_depth = 24;
+ break;
+ case 0x5:
+ hdmirx_dev->color_depth = 30;
+ break;
+ case 0x6:
+ hdmirx_dev->color_depth = 36;
+ break;
+ case 0x7:
+ hdmirx_dev->color_depth = 48;
+ break;
+ default:
+ hdmirx_dev->color_depth = 24;
+ break;
+ }
+
+ v4l2_dbg(1, debug, v4l2_dev, "%s: color_depth: %d, reg_val:%d\n",
+ __func__, hdmirx_dev->color_depth, color_depth_reg);
+}
+
+static void hdmirx_get_pix_fmt(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ u32 val;
+
+ val = hdmirx_readl(hdmirx_dev, DMA_STATUS11);
+ hdmirx_dev->pix_fmt = val & HDMIRX_FORMAT_MASK;
+
+ switch (hdmirx_dev->pix_fmt) {
+ case HDMIRX_RGB888:
+ hdmirx_dev->cur_fmt_fourcc = V4L2_PIX_FMT_BGR24;
+ break;
+ case HDMIRX_YUV422:
+ hdmirx_dev->cur_fmt_fourcc = V4L2_PIX_FMT_NV16;
+ break;
+ case HDMIRX_YUV444:
+ hdmirx_dev->cur_fmt_fourcc = V4L2_PIX_FMT_NV24;
+ break;
+ case HDMIRX_YUV420:
+ hdmirx_dev->cur_fmt_fourcc = V4L2_PIX_FMT_NV12;
+ break;
+ default:
+ v4l2_err(v4l2_dev,
+ "%s: err pix_fmt: %d, set RGB888 as default\n",
+ __func__, hdmirx_dev->pix_fmt);
+ hdmirx_dev->pix_fmt = HDMIRX_RGB888;
+ hdmirx_dev->cur_fmt_fourcc = V4L2_PIX_FMT_BGR24;
+ break;
+ }
+
+ v4l2_dbg(1, debug, v4l2_dev, "%s: pix_fmt: %s\n", __func__,
+ pix_fmt_str[hdmirx_dev->pix_fmt]);
+}
+
+static void hdmirx_get_timings(struct snps_hdmirx_dev *hdmirx_dev,
+ struct v4l2_bt_timings *bt, bool from_dma)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ u32 hact, vact, htotal, vtotal, fps;
+ u32 hfp, hs, hbp, vfp, vs, vbp;
+ u32 val;
+
+ if (from_dma) {
+ val = hdmirx_readl(hdmirx_dev, DMA_STATUS2);
+ hact = (val >> 16) & 0xffff;
+ vact = val & 0xffff;
+ val = hdmirx_readl(hdmirx_dev, DMA_STATUS3);
+ htotal = (val >> 16) & 0xffff;
+ vtotal = val & 0xffff;
+ val = hdmirx_readl(hdmirx_dev, DMA_STATUS4);
+ hs = (val >> 16) & 0xffff;
+ vs = val & 0xffff;
+ val = hdmirx_readl(hdmirx_dev, DMA_STATUS5);
+ hbp = (val >> 16) & 0xffff;
+ vbp = val & 0xffff;
+ hfp = htotal - hact - hs - hbp;
+ vfp = vtotal - vact - vs - vbp;
+ } else {
+ val = hdmirx_readl(hdmirx_dev, VMON_STATUS1);
+ hs = (val >> 16) & 0xffff;
+ hfp = val & 0xffff;
+ val = hdmirx_readl(hdmirx_dev, VMON_STATUS2);
+ hbp = val & 0xffff;
+ val = hdmirx_readl(hdmirx_dev, VMON_STATUS3);
+ htotal = (val >> 16) & 0xffff;
+ hact = val & 0xffff;
+ val = hdmirx_readl(hdmirx_dev, VMON_STATUS4);
+ vs = (val >> 16) & 0xffff;
+ vfp = val & 0xffff;
+ val = hdmirx_readl(hdmirx_dev, VMON_STATUS5);
+ vbp = val & 0xffff;
+ val = hdmirx_readl(hdmirx_dev, VMON_STATUS6);
+ vtotal = (val >> 16) & 0xffff;
+ vact = val & 0xffff;
+ if (hdmirx_dev->pix_fmt == HDMIRX_YUV420)
+ hact *= 2;
+ }
+ if (hdmirx_dev->pix_fmt == HDMIRX_YUV420)
+ htotal *= 2;
+ fps = (bt->pixelclock + (htotal * vtotal) / 2) / (htotal * vtotal);
+ if (hdmirx_dev->pix_fmt == HDMIRX_YUV420)
+ fps *= 2;
+ bt->width = hact;
+ bt->height = vact;
+ bt->hfrontporch = hfp;
+ bt->hsync = hs;
+ bt->hbackporch = hbp;
+ bt->vfrontporch = vfp;
+ bt->vsync = vs;
+ bt->vbackporch = vbp;
+
+ v4l2_dbg(1, debug, v4l2_dev, "get timings from %s\n", from_dma ? "dma" : "ctrl");
+ v4l2_dbg(1, debug, v4l2_dev, "act:%ux%u, total:%ux%u, fps:%u, pixclk:%llu\n",
+ bt->width, bt->height, htotal, vtotal, fps, bt->pixelclock);
+
+ v4l2_dbg(2, debug, v4l2_dev, "hfp:%u, hs:%u, hbp:%u, vfp:%u, vs:%u, vbp:%u\n",
+ bt->hfrontporch, bt->hsync, bt->hbackporch,
+ bt->vfrontporch, bt->vsync, bt->vbackporch);
+}
+
+static bool hdmirx_check_timing_valid(struct v4l2_bt_timings *bt)
+{
+ if (bt->width < 100 || bt->width > 5000 ||
+ bt->height < 100 || bt->height > 5000)
+ return false;
+
+ if (!bt->hsync || bt->hsync > 200 ||
+ !bt->vsync || bt->vsync > 100)
+ return false;
+
+ if (!bt->hbackporch || bt->hbackporch > 2000 ||
+ !bt->vbackporch || bt->vbackporch > 2000)
+ return false;
+
+ if (!bt->hfrontporch || bt->hfrontporch > 2000 ||
+ !bt->vfrontporch || bt->vfrontporch > 2000)
+ return false;
+
+ return true;
+}
+
+static int hdmirx_get_detected_timings(struct snps_hdmirx_dev *hdmirx_dev,
+ struct v4l2_dv_timings *timings,
+ bool from_dma)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ struct v4l2_bt_timings *bt = &timings->bt;
+ u32 field_type, color_depth, deframer_st;
+ u32 val, tmdsqpclk_freq, pix_clk;
+ u64 tmp_data, tmds_clk;
+
+ memset(timings, 0, sizeof(struct v4l2_dv_timings));
+ timings->type = V4L2_DV_BT_656_1120;
+
+ val = hdmirx_readl(hdmirx_dev, DMA_STATUS11);
+ field_type = (val & HDMIRX_TYPE_MASK) >> 7;
+ hdmirx_get_pix_fmt(hdmirx_dev);
+ bt->interlaced = field_type & BIT(0) ? V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE;
+ val = hdmirx_readl(hdmirx_dev, PKTDEC_AVIIF_PB7_4);
+ hdmirx_dev->cur_vic = val | VIC_VAL_MASK;
+ hdmirx_get_colordepth(hdmirx_dev);
+ color_depth = hdmirx_dev->color_depth;
+ deframer_st = hdmirx_readl(hdmirx_dev, DEFRAMER_STATUS);
+ hdmirx_dev->is_dvi_mode = deframer_st & OPMODE_STS_MASK ? false : true;
+ tmdsqpclk_freq = hdmirx_readl(hdmirx_dev, CMU_TMDSQPCLK_FREQ);
+ tmds_clk = tmdsqpclk_freq * 4 * 1000;
+ tmp_data = tmds_clk * 24;
+ do_div(tmp_data, color_depth);
+ pix_clk = tmp_data;
+ bt->pixelclock = pix_clk;
+
+ hdmirx_get_timings(hdmirx_dev, bt, from_dma);
+ if (bt->interlaced == V4L2_DV_INTERLACED) {
+ bt->height *= 2;
+ bt->il_vsync = bt->vsync + 1;
+ }
+
+ v4l2_dbg(2, debug, v4l2_dev, "tmds_clk:%llu\n", tmds_clk);
+ v4l2_dbg(1, debug, v4l2_dev, "interlace:%d, fmt:%d, vic:%d, color:%d, mode:%s\n",
+ bt->interlaced, hdmirx_dev->pix_fmt,
+ hdmirx_dev->cur_vic, hdmirx_dev->color_depth,
+ hdmirx_dev->is_dvi_mode ? "dvi" : "hdmi");
+ v4l2_dbg(2, debug, v4l2_dev, "deframer_st:%#x\n", deframer_st);
+
+ if (!hdmirx_check_timing_valid(bt))
+ return -EINVAL;
+
+ return 0;
+}
+
+static bool port_no_link(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ return !tx_5v_power_present(hdmirx_dev);
+}
+
+static int hdmirx_query_dv_timings(struct file *file, void *_fh,
+ struct v4l2_dv_timings *timings)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ int ret;
+
+ if (port_no_link(hdmirx_dev)) {
+ v4l2_err(v4l2_dev, "%s: port has no link\n", __func__);
+ return -ENOLINK;
+ }
+
+ if (signal_not_lock(hdmirx_dev)) {
+ v4l2_err(v4l2_dev, "%s: signal is not locked\n", __func__);
+ return -ENOLCK;
+ }
+
+ /*
+ * query dv timings is during preview, dma's timing is stable,
+ * so we can get from DMA. If the current resolution is negative,
+ * get timing from CTRL need to change polarity of sync,
+ * maybe cause DMA errors.
+ */
+ ret = hdmirx_get_detected_timings(hdmirx_dev, timings, true);
+ if (ret)
+ return ret;
+
+ if (debug)
+ v4l2_print_dv_timings(hdmirx_dev->v4l2_dev.name,
+ "query_dv_timings: ", timings, false);
+
+ if (!v4l2_valid_dv_timings(timings, &hdmirx_timings_cap, NULL, NULL)) {
+ v4l2_dbg(1, debug, v4l2_dev, "%s: timings out of range\n", __func__);
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static void hdmirx_hpd_ctrl(struct snps_hdmirx_dev *hdmirx_dev, bool en)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ v4l2_dbg(1, debug, v4l2_dev, "%s: %sable, hpd_trigger_level:%d\n",
+ __func__, en ? "en" : "dis",
+ hdmirx_dev->hpd_trigger_level);
+ hdmirx_update_bits(hdmirx_dev, SCDC_CONFIG, HPDLOW, en ? 0 : HPDLOW);
+ en = hdmirx_dev->hpd_trigger_level ? en : !en;
+ hdmirx_writel(hdmirx_dev, CORE_CONFIG, en);
+}
+
+static int hdmirx_write_edid(struct snps_hdmirx_dev *hdmirx_dev,
+ struct v4l2_edid *edid, bool hpd_up)
+{
+ u32 edid_len = edid->blocks * EDID_BLOCK_SIZE;
+ char data[300];
+ u32 i;
+
+ memset(edid->reserved, 0, sizeof(edid->reserved));
+ if (edid->pad)
+ return -EINVAL;
+
+ if (edid->start_block)
+ return -EINVAL;
+
+ if (edid->blocks > EDID_NUM_BLOCKS_MAX) {
+ edid->blocks = EDID_NUM_BLOCKS_MAX;
+ return -E2BIG;
+ }
+
+ if (!edid->blocks) {
+ hdmirx_dev->edid_blocks_written = 0;
+ return 0;
+ }
+
+ memset(&hdmirx_dev->edid, 0, sizeof(hdmirx_dev->edid));
+ hdmirx_hpd_ctrl(hdmirx_dev, false);
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG11,
+ EDID_READ_EN_MASK |
+ EDID_WRITE_EN_MASK |
+ EDID_SLAVE_ADDR_MASK,
+ EDID_READ_EN(0) |
+ EDID_WRITE_EN(1) |
+ EDID_SLAVE_ADDR(0x50));
+ for (i = 0; i < edid_len; i++)
+ hdmirx_writel(hdmirx_dev, DMA_CONFIG10, edid->edid[i]);
+
+ /* read out for debug */
+ if (debug >= 2) {
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG11,
+ EDID_READ_EN_MASK |
+ EDID_WRITE_EN_MASK,
+ EDID_READ_EN(1) |
+ EDID_WRITE_EN(0));
+ edid_len = edid_len > sizeof(data) ? sizeof(data) : edid_len;
+ memset(data, 0, sizeof(data));
+ for (i = 0; i < edid_len; i++)
+ data[i] = hdmirx_readl(hdmirx_dev, DMA_STATUS14);
+
+ print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, data,
+ edid_len, false);
+ }
+
+ /*
+ * You must set EDID_READ_EN & EDID_WRITE_EN bit to 0,
+ * when the read/write edid operation is completed.Otherwise, it
+ * will affect the reading and writing of other registers
+ */
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG11,
+ EDID_READ_EN_MASK | EDID_WRITE_EN_MASK,
+ EDID_READ_EN(0) | EDID_WRITE_EN(0));
+
+ hdmirx_dev->edid_blocks_written = edid->blocks;
+ memcpy(&hdmirx_dev->edid, edid->edid, edid->blocks * EDID_BLOCK_SIZE);
+ if (hpd_up) {
+ if (tx_5v_power_present(hdmirx_dev))
+ hdmirx_hpd_ctrl(hdmirx_dev, true);
+ }
+
+ return 0;
+}
+
+/*
+ * Before clearing interrupt, we need to read the interrupt status.
+ */
+static inline void hdmirx_clear_interrupt(struct snps_hdmirx_dev *hdmirx_dev,
+ u32 reg, u32 val)
+{
+ /* (interrupt status register) = (interrupt clear register) - 0x8 */
+ hdmirx_readl(hdmirx_dev, reg - 0x8);
+ hdmirx_writel(hdmirx_dev, reg, val);
+}
+
+static void hdmirx_interrupts_setup(struct snps_hdmirx_dev *hdmirx_dev, bool en)
+{
+ v4l2_dbg(1, debug, &hdmirx_dev->v4l2_dev, "%s: %sable\n",
+ __func__, en ? "en" : "dis");
+
+ /* Note: In DVI mode, it needs to be written twice to take effect. */
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_0_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_0_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, AVPUNIT_0_INT_CLEAR, 0xffffffff);
+
+ if (en) {
+ hdmirx_update_bits(hdmirx_dev, MAINUNIT_0_INT_MASK_N,
+ TMDSQPCLK_OFF_CHG | TMDSQPCLK_LOCKED_CHG,
+ TMDSQPCLK_OFF_CHG | TMDSQPCLK_LOCKED_CHG);
+ hdmirx_update_bits(hdmirx_dev, MAINUNIT_2_INT_MASK_N,
+ TMDSVALID_STABLE_CHG, TMDSVALID_STABLE_CHG);
+ hdmirx_update_bits(hdmirx_dev, AVPUNIT_0_INT_MASK_N,
+ CED_DYN_CNT_CH2_IRQ |
+ CED_DYN_CNT_CH1_IRQ |
+ CED_DYN_CNT_CH0_IRQ,
+ CED_DYN_CNT_CH2_IRQ |
+ CED_DYN_CNT_CH1_IRQ |
+ CED_DYN_CNT_CH0_IRQ);
+ } else {
+ hdmirx_writel(hdmirx_dev, MAINUNIT_0_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, MAINUNIT_2_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, AVPUNIT_0_INT_MASK_N, 0);
+ }
+}
+
+static void hdmirx_plugout(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct arm_smccc_res res;
+
+ hdmirx_update_bits(hdmirx_dev, SCDC_CONFIG, POWERPROVIDED, 0);
+ hdmirx_interrupts_setup(hdmirx_dev, false);
+ hdmirx_hpd_ctrl(hdmirx_dev, false);
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG6, HDMIRX_DMA_EN, 0);
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG4,
+ LINE_FLAG_INT_EN |
+ HDMIRX_DMA_IDLE_INT |
+ HDMIRX_LOCK_DISABLE_INT |
+ LAST_FRAME_AXI_UNFINISH_INT_EN |
+ FIFO_OVERFLOW_INT_EN |
+ FIFO_UNDERFLOW_INT_EN |
+ HDMIRX_AXI_ERROR_INT_EN, 0);
+ hdmirx_reset_dma(hdmirx_dev);
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, HDMI_DISABLE | PHY_RESET |
+ PHY_PDDQ, HDMI_DISABLE);
+ hdmirx_writel(hdmirx_dev, PHYCREG_CONFIG0, 0x0);
+ cancel_delayed_work(&hdmirx_dev->delayed_work_res_change);
+ cancel_delayed_work_sync(&hdmirx_dev->delayed_work_heartbeat);
+ flush_work(&hdmirx_dev->work_wdt_config);
+ arm_smccc_smc(SIP_WDT_CFG, WDT_STOP, 0, 0, 0, 0, 0, 0, &res);
+}
+
+static int hdmirx_set_edid(struct file *file, void *fh, struct v4l2_edid *edid)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct arm_smccc_res res;
+ int ret;
+
+ disable_irq(hdmirx_dev->hdmi_irq);
+ disable_irq(hdmirx_dev->dma_irq);
+ arm_smccc_smc(RK_SIP_FIQ_CTRL, RK_SIP_FIQ_CTRL_FIQ_DIS,
+ RK_IRQ_HDMIRX_HDMI, 0, 0, 0, 0, 0, &res);
+
+ if (tx_5v_power_present(hdmirx_dev))
+ hdmirx_plugout(hdmirx_dev);
+ ret = hdmirx_write_edid(hdmirx_dev, edid, false);
+ if (ret)
+ return ret;
+ hdmirx_dev->edid_version = HDMIRX_EDID_USER;
+
+ enable_irq(hdmirx_dev->hdmi_irq);
+ enable_irq(hdmirx_dev->dma_irq);
+ arm_smccc_smc(RK_SIP_FIQ_CTRL, RK_SIP_FIQ_CTRL_FIQ_EN,
+ RK_IRQ_HDMIRX_HDMI, 0, 0, 0, 0, 0, &res);
+ queue_delayed_work(system_unbound_wq,
+ &hdmirx_dev->delayed_work_hotplug,
+ msecs_to_jiffies(500));
+ return 0;
+}
+
+static int hdmirx_get_edid(struct file *file, void *fh, struct v4l2_edid *edid)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ memset(edid->reserved, 0, sizeof(edid->reserved));
+
+ if (edid->pad)
+ return -EINVAL;
+
+ if (!edid->start_block && !edid->blocks) {
+ edid->blocks = hdmirx_dev->edid_blocks_written;
+ return 0;
+ }
+
+ if (!hdmirx_dev->edid_blocks_written)
+ return -ENODATA;
+
+ if (edid->start_block >= hdmirx_dev->edid_blocks_written || !edid->blocks)
+ return -EINVAL;
+
+ if (edid->start_block + edid->blocks > hdmirx_dev->edid_blocks_written)
+ edid->blocks = hdmirx_dev->edid_blocks_written - edid->start_block;
+
+ memcpy(edid->edid, &hdmirx_dev->edid, edid->blocks * EDID_BLOCK_SIZE);
+
+ v4l2_dbg(1, debug, v4l2_dev, "%s: read EDID:\n", __func__);
+ if (debug > 0)
+ print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1,
+ edid->edid, edid->blocks * EDID_BLOCK_SIZE, false);
+
+ return 0;
+}
+
+static int hdmirx_g_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *parm)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_fract fps;
+
+ if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ fps = v4l2_calc_timeperframe(&hdmirx_dev->timings);
+ parm->parm.capture.timeperframe.numerator = fps.numerator;
+ parm->parm.capture.timeperframe.denominator = fps.denominator;
+
+ return 0;
+}
+
+static int hdmirx_dv_timings_cap(struct file *file, void *fh,
+ struct v4l2_dv_timings_cap *cap)
+{
+ *cap = hdmirx_timings_cap;
+ return 0;
+}
+
+static int hdmirx_enum_dv_timings(struct file *file, void *_fh,
+ struct v4l2_enum_dv_timings *timings)
+{
+ return v4l2_enum_dv_timings_cap(timings, &hdmirx_timings_cap, NULL, NULL);
+}
+
+static void hdmirx_scdc_init(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ hdmirx_update_bits(hdmirx_dev, I2C_SLAVE_CONFIG1,
+ I2C_SDA_OUT_HOLD_VALUE_QST_MASK |
+ I2C_SDA_IN_HOLD_VALUE_QST_MASK,
+ I2C_SDA_OUT_HOLD_VALUE_QST(0x80) |
+ I2C_SDA_IN_HOLD_VALUE_QST(0x15));
+ hdmirx_update_bits(hdmirx_dev, SCDC_REGBANK_CONFIG0,
+ SCDC_SINKVERSION_QST_MASK,
+ SCDC_SINKVERSION_QST(1));
+}
+
+static int wait_reg_bit_status(struct snps_hdmirx_dev *hdmirx_dev, u32 reg,
+ u32 bit_mask, u32 expect_val, bool is_grf,
+ u32 ms)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ u32 i, val;
+
+ for (i = 0; i < ms; i++) {
+ if (is_grf)
+ regmap_read(hdmirx_dev->grf, reg, &val);
+ else
+ val = hdmirx_readl(hdmirx_dev, reg);
+
+ if ((val & bit_mask) == expect_val) {
+ v4l2_dbg(2, debug, v4l2_dev,
+ "%s: i:%d, time: %dms\n", __func__, i, ms);
+ break;
+ }
+ usleep_range(1000, 1010);
+ }
+
+ if (i == ms)
+ return -1;
+
+ return 0;
+}
+
+static int hdmirx_phy_register_write(struct snps_hdmirx_dev *hdmirx_dev,
+ u32 phy_reg, u32 val)
+{
+ struct device *dev = hdmirx_dev->dev;
+
+ reinit_completion(&hdmirx_dev->cr_write_done);
+ /* clear irq status */
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff);
+ /* en irq */
+ hdmirx_update_bits(hdmirx_dev, MAINUNIT_2_INT_MASK_N,
+ PHYCREG_CR_WRITE_DONE, PHYCREG_CR_WRITE_DONE);
+ /* write phy reg addr */
+ hdmirx_writel(hdmirx_dev, PHYCREG_CONFIG1, phy_reg);
+ /* write phy reg val */
+ hdmirx_writel(hdmirx_dev, PHYCREG_CONFIG2, val);
+ /* config write enable */
+ hdmirx_writel(hdmirx_dev, PHYCREG_CONTROL, PHYCREG_CR_PARA_WRITE_P);
+
+ if (!wait_for_completion_timeout(&hdmirx_dev->cr_write_done,
+ msecs_to_jiffies(20))) {
+ dev_err(dev, "%s wait cr write done failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void hdmirx_tmds_clk_ratio_config(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ u32 val;
+
+ val = hdmirx_readl(hdmirx_dev, SCDC_REGBANK_STATUS1);
+ v4l2_dbg(3, debug, v4l2_dev, "%s: scdc_regbank_st:%#x\n", __func__, val);
+ hdmirx_dev->tmds_clk_ratio = (val & SCDC_TMDSBITCLKRATIO) > 0;
+
+ if (hdmirx_dev->tmds_clk_ratio) {
+ v4l2_dbg(3, debug, v4l2_dev, "%s: HDMITX greater than 3.4Gbps\n", __func__);
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG,
+ TMDS_CLOCK_RATIO, TMDS_CLOCK_RATIO);
+ } else {
+ v4l2_dbg(3, debug, v4l2_dev, "%s: HDMITX less than 3.4Gbps\n", __func__);
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG,
+ TMDS_CLOCK_RATIO, 0);
+ }
+}
+
+static void hdmirx_phy_config(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct device *dev = hdmirx_dev->dev;
+
+ hdmirx_clear_interrupt(hdmirx_dev, SCDC_INT_CLEAR, 0xffffffff);
+ hdmirx_update_bits(hdmirx_dev, SCDC_INT_MASK_N, SCDCTMDSCCFG_CHG,
+ SCDCTMDSCCFG_CHG);
+ /* cr_para_clk 24M */
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, REFFREQ_SEL_MASK, REFFREQ_SEL(0));
+ /* rx data width 40bit valid */
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, RXDATA_WIDTH, RXDATA_WIDTH);
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, PHY_RESET, PHY_RESET);
+ usleep_range(100, 110);
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, PHY_RESET, 0);
+ usleep_range(100, 110);
+ /* select cr para interface */
+ hdmirx_writel(hdmirx_dev, PHYCREG_CONFIG0, 0x3);
+
+ if (wait_reg_bit_status(hdmirx_dev, SYS_GRF_SOC_STATUS1,
+ HDMIRXPHY_SRAM_INIT_DONE,
+ HDMIRXPHY_SRAM_INIT_DONE, true, 10))
+ dev_err(dev, "%s: phy SRAM init failed\n", __func__);
+
+ regmap_write(hdmirx_dev->grf, SYS_GRF_SOC_CON1,
+ (HDMIRXPHY_SRAM_EXT_LD_DONE << 16) |
+ HDMIRXPHY_SRAM_EXT_LD_DONE);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 2);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 3);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 2);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 2);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 3);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 2);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 0);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 1);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 0);
+ hdmirx_phy_register_write(hdmirx_dev, SUP_DIG_ANA_CREGS_SUP_ANA_NC, 0);
+
+ hdmirx_phy_register_write(hdmirx_dev,
+ HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_3_REG,
+ CDR_SETTING_BOUNDARY_3_DEFAULT);
+ hdmirx_phy_register_write(hdmirx_dev,
+ HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_4_REG,
+ CDR_SETTING_BOUNDARY_4_DEFAULT);
+ hdmirx_phy_register_write(hdmirx_dev,
+ HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_5_REG,
+ CDR_SETTING_BOUNDARY_5_DEFAULT);
+ hdmirx_phy_register_write(hdmirx_dev,
+ HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_6_REG,
+ CDR_SETTING_BOUNDARY_6_DEFAULT);
+ hdmirx_phy_register_write(hdmirx_dev,
+ HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_7_REG,
+ CDR_SETTING_BOUNDARY_7_DEFAULT);
+
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, PHY_PDDQ, 0);
+ if (wait_reg_bit_status(hdmirx_dev, PHY_STATUS, PDDQ_ACK, 0, false, 10))
+ dev_err(dev, "%s: wait pddq ack failed\n", __func__);
+
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, HDMI_DISABLE, 0);
+ if (wait_reg_bit_status(hdmirx_dev, PHY_STATUS, HDMI_DISABLE_ACK, 0,
+ false, 50))
+ dev_err(dev, "%s: wait hdmi disable ack failed\n", __func__);
+
+ hdmirx_tmds_clk_ratio_config(hdmirx_dev);
+}
+
+static void hdmirx_controller_init(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct device *dev = hdmirx_dev->dev;
+
+ reinit_completion(&hdmirx_dev->timer_base_lock);
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_0_INT_CLEAR, 0xffffffff);
+ /* en irq */
+ hdmirx_update_bits(hdmirx_dev, MAINUNIT_0_INT_MASK_N,
+ TIMER_BASE_LOCKED_IRQ, TIMER_BASE_LOCKED_IRQ);
+ /* write irefclk freq */
+ hdmirx_writel(hdmirx_dev, GLOBAL_TIMER_REF_BASE, IREF_CLK_FREQ_HZ);
+
+ if (!wait_for_completion_timeout(&hdmirx_dev->timer_base_lock,
+ msecs_to_jiffies(20)))
+ dev_err(dev, "%s wait timer base lock failed\n", __func__);
+
+ hdmirx_update_bits(hdmirx_dev, CMU_CONFIG0,
+ TMDSQPCLK_STABLE_FREQ_MARGIN_MASK |
+ AUDCLK_STABLE_FREQ_MARGIN_MASK,
+ TMDSQPCLK_STABLE_FREQ_MARGIN(2) |
+ AUDCLK_STABLE_FREQ_MARGIN(1));
+ hdmirx_update_bits(hdmirx_dev, DESCRAND_EN_CONTROL,
+ SCRAMB_EN_SEL_QST_MASK, SCRAMB_EN_SEL_QST(1));
+ hdmirx_update_bits(hdmirx_dev, CED_CONFIG,
+ CED_VIDDATACHECKEN_QST |
+ CED_DATAISCHECKEN_QST |
+ CED_GBCHECKEN_QST |
+ CED_CTRLCHECKEN_QST |
+ CED_CHLOCKMAXER_QST_MASK,
+ CED_VIDDATACHECKEN_QST |
+ CED_GBCHECKEN_QST |
+ CED_CTRLCHECKEN_QST |
+ CED_CHLOCKMAXER_QST(0x10));
+ hdmirx_update_bits(hdmirx_dev, DEFRAMER_CONFIG0,
+ VS_REMAPFILTER_EN_QST | VS_FILTER_ORDER_QST_MASK,
+ VS_REMAPFILTER_EN_QST | VS_FILTER_ORDER_QST(0x3));
+}
+
+static void hdmirx_set_negative_pol(struct snps_hdmirx_dev *hdmirx_dev, bool en)
+{
+ if (en) {
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG6,
+ VSYNC_TOGGLE_EN | HSYNC_TOGGLE_EN,
+ VSYNC_TOGGLE_EN | HSYNC_TOGGLE_EN);
+ hdmirx_update_bits(hdmirx_dev, VIDEO_CONFIG2,
+ VPROC_VSYNC_POL_OVR_VALUE |
+ VPROC_VSYNC_POL_OVR_EN |
+ VPROC_HSYNC_POL_OVR_VALUE |
+ VPROC_HSYNC_POL_OVR_EN,
+ VPROC_VSYNC_POL_OVR_EN |
+ VPROC_HSYNC_POL_OVR_EN);
+ return;
+ }
+
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG6,
+ VSYNC_TOGGLE_EN | HSYNC_TOGGLE_EN, 0);
+
+ hdmirx_update_bits(hdmirx_dev, VIDEO_CONFIG2,
+ VPROC_VSYNC_POL_OVR_VALUE |
+ VPROC_VSYNC_POL_OVR_EN |
+ VPROC_HSYNC_POL_OVR_VALUE |
+ VPROC_HSYNC_POL_OVR_EN, 0);
+}
+
+static int hdmirx_try_to_get_timings(struct snps_hdmirx_dev *hdmirx_dev,
+ struct v4l2_dv_timings *timings,
+ int try_cnt)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ int i, cnt = 0, fail_cnt = 0, ret = 0;
+ bool from_dma = false;
+
+ hdmirx_set_negative_pol(hdmirx_dev, false);
+ for (i = 0; i < try_cnt; i++) {
+ ret = hdmirx_get_detected_timings(hdmirx_dev, timings, from_dma);
+ if (ret) {
+ cnt = 0;
+ fail_cnt++;
+ if (fail_cnt > 3) {
+ hdmirx_set_negative_pol(hdmirx_dev, true);
+ from_dma = true;
+ }
+ } else {
+ cnt++;
+ }
+ if (cnt >= 5)
+ break;
+
+ usleep_range(10 * 1000, 10 * 1100);
+ }
+
+ if (try_cnt > 8 && cnt < 5)
+ v4l2_dbg(1, debug, v4l2_dev, "%s: res not stable\n", __func__);
+
+ return ret;
+}
+
+static void hdmirx_format_change(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct v4l2_dv_timings timings;
+ struct hdmirx_stream *stream = &hdmirx_dev->stream;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ const struct v4l2_event ev_src_chg = {
+ .type = V4L2_EVENT_SOURCE_CHANGE,
+ .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+ };
+
+ if (hdmirx_try_to_get_timings(hdmirx_dev, &timings, 20)) {
+ queue_delayed_work(system_unbound_wq,
+ &hdmirx_dev->delayed_work_hotplug,
+ msecs_to_jiffies(20));
+ return;
+ }
+
+ if (!v4l2_match_dv_timings(&hdmirx_dev->timings, &timings, 0, false)) {
+ /* automatically set timing rather than set by userspace */
+ hdmirx_dev->timings = timings;
+ v4l2_print_dv_timings(hdmirx_dev->v4l2_dev.name,
+ "New format: ", &timings, false);
+ }
+
+ hdmirx_dev->got_timing = true;
+ v4l2_dbg(1, debug, v4l2_dev, "%s: queue res_chg_event\n", __func__);
+ v4l2_event_queue(&stream->vdev, &ev_src_chg);
+}
+
+static void hdmirx_set_ddr_store_fmt(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ enum ddr_store_fmt store_fmt;
+ u32 dma_cfg1;
+
+ switch (hdmirx_dev->pix_fmt) {
+ case HDMIRX_RGB888:
+ store_fmt = STORE_RGB888;
+ break;
+ case HDMIRX_YUV444:
+ store_fmt = STORE_YUV444_8BIT;
+ break;
+ case HDMIRX_YUV422:
+ store_fmt = STORE_YUV422_8BIT;
+ break;
+ case HDMIRX_YUV420:
+ store_fmt = STORE_YUV420_8BIT;
+ break;
+ default:
+ store_fmt = STORE_RGB888;
+ break;
+ }
+
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG1,
+ DDR_STORE_FORMAT_MASK, DDR_STORE_FORMAT(store_fmt));
+ dma_cfg1 = hdmirx_readl(hdmirx_dev, DMA_CONFIG1);
+ v4l2_dbg(1, debug, v4l2_dev, "%s: pix_fmt: %s, DMA_CONFIG1:%#x\n",
+ __func__, pix_fmt_str[hdmirx_dev->pix_fmt], dma_cfg1);
+}
+
+static int hdmirx_wait_lock_and_get_timing(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ u32 mu_status, scdc_status, dma_st10, cmu_st;
+ u32 i;
+
+ for (i = 0; i < 300; i++) {
+ mu_status = hdmirx_readl(hdmirx_dev, MAINUNIT_STATUS);
+ scdc_status = hdmirx_readl(hdmirx_dev, SCDC_REGBANK_STATUS3);
+ dma_st10 = hdmirx_readl(hdmirx_dev, DMA_STATUS10);
+ cmu_st = hdmirx_readl(hdmirx_dev, CMU_STATUS);
+
+ if ((mu_status & TMDSVALID_STABLE_ST) &&
+ (dma_st10 & HDMIRX_LOCK) &&
+ (cmu_st & TMDSQPCLK_LOCKED_ST))
+ break;
+
+ if (!tx_5v_power_present(hdmirx_dev)) {
+ v4l2_err(v4l2_dev, "%s: HDMI pull out, return\n", __func__);
+ return -1;
+ }
+
+ hdmirx_tmds_clk_ratio_config(hdmirx_dev);
+ }
+
+ if (i == 300) {
+ v4l2_err(v4l2_dev, "%s: signal not lock, tmds_clk_ratio:%d\n",
+ __func__, hdmirx_dev->tmds_clk_ratio);
+ v4l2_err(v4l2_dev, "%s: mu_st:%#x, scdc_st:%#x, dma_st10:%#x\n",
+ __func__, mu_status, scdc_status, dma_st10);
+ return -1;
+ }
+
+ v4l2_info(v4l2_dev, "%s: signal lock ok, i:%d\n", __func__, i);
+ hdmirx_writel(hdmirx_dev, GLOBAL_SWRESET_REQUEST, DATAPATH_SWRESETREQ);
+
+ reinit_completion(&hdmirx_dev->avi_pkt_rcv);
+ hdmirx_clear_interrupt(hdmirx_dev, PKT_2_INT_CLEAR, 0xffffffff);
+ hdmirx_update_bits(hdmirx_dev, PKT_2_INT_MASK_N,
+ PKTDEC_AVIIF_RCV_IRQ, PKTDEC_AVIIF_RCV_IRQ);
+
+ if (!wait_for_completion_timeout(&hdmirx_dev->avi_pkt_rcv,
+ msecs_to_jiffies(300))) {
+ v4l2_err(v4l2_dev, "%s wait avi_pkt_rcv failed\n", __func__);
+ hdmirx_update_bits(hdmirx_dev, PKT_2_INT_MASK_N,
+ PKTDEC_AVIIF_RCV_IRQ, 0);
+ }
+
+ usleep_range(50 * 1000, 50 * 1010);
+ hdmirx_format_change(hdmirx_dev);
+
+ return 0;
+}
+
+static void hdmirx_dma_config(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ hdmirx_set_ddr_store_fmt(hdmirx_dev);
+
+ /* Note: uv_swap, rb can not swap, doc err*/
+ if (hdmirx_dev->cur_fmt_fourcc != V4L2_PIX_FMT_NV16)
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG6, RB_SWAP_EN, RB_SWAP_EN);
+ else
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG6, RB_SWAP_EN, 0);
+
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG7,
+ LOCK_FRAME_NUM_MASK,
+ LOCK_FRAME_NUM(2));
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG1,
+ UV_WID_MASK | Y_WID_MASK | ABANDON_EN,
+ UV_WID(1) | Y_WID(2) | ABANDON_EN);
+}
+
+static void hdmirx_submodule_init(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ /* Note: if not config HDCP2_CONFIG, there will be some errors; */
+ hdmirx_update_bits(hdmirx_dev, HDCP2_CONFIG,
+ HDCP2_SWITCH_OVR_VALUE |
+ HDCP2_SWITCH_OVR_EN,
+ HDCP2_SWITCH_OVR_EN);
+ hdmirx_scdc_init(hdmirx_dev);
+ hdmirx_controller_init(hdmirx_dev);
+}
+
+static int hdmirx_enum_input(struct file *file, void *priv,
+ struct v4l2_input *input)
+{
+ if (input->index > 0)
+ return -EINVAL;
+
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ input->std = 0;
+ strscpy(input->name, "hdmirx", sizeof(input->name));
+ input->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+
+ return 0;
+}
+
+static int hdmirx_get_input(struct file *file, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int hdmirx_set_input(struct file *file, void *priv, unsigned int i)
+{
+ if (i)
+ return -EINVAL;
+ return 0;
+}
+
+static int fcc_xysubs(u32 fcc, u32 *xsubs, u32 *ysubs)
+{
+ /* Note: cbcr plane bpp is 16 bit */
+ switch (fcc) {
+ case V4L2_PIX_FMT_NV24:
+ *xsubs = 1;
+ *ysubs = 1;
+ break;
+ case V4L2_PIX_FMT_NV16:
+ *xsubs = 2;
+ *ysubs = 1;
+ break;
+ case V4L2_PIX_FMT_NV12:
+ *xsubs = 2;
+ *ysubs = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static u32 hdmirx_align_bits_per_pixel(const struct hdmirx_output_fmt *fmt,
+ int plane_index)
+{
+ u32 bpp = 0;
+
+ if (fmt) {
+ switch (fmt->fourcc) {
+ case V4L2_PIX_FMT_NV24:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_BGR24:
+ bpp = fmt->bpp[plane_index];
+ break;
+ default:
+ pr_err("fourcc: %#x is not supported\n", fmt->fourcc);
+ break;
+ }
+ }
+
+ return bpp;
+}
+
+static const struct hdmirx_output_fmt *find_output_fmt(u32 pixelfmt)
+{
+ const struct hdmirx_output_fmt *fmt;
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(g_out_fmts); i++) {
+ fmt = &g_out_fmts[i];
+ if (fmt->fourcc == pixelfmt)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static void hdmirx_set_fmt(struct hdmirx_stream *stream,
+ struct v4l2_pix_format_mplane *pixm, bool try)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ struct v4l2_bt_timings *bt = &hdmirx_dev->timings.bt;
+ const struct hdmirx_output_fmt *fmt;
+ unsigned int imagesize = 0, planes;
+ u32 xsubs = 1, ysubs = 1, i;
+
+ memset(&pixm->plane_fmt[0], 0, sizeof(struct v4l2_plane_pix_format));
+ fmt = find_output_fmt(pixm->pixelformat);
+ if (!fmt) {
+ fmt = &g_out_fmts[0];
+ v4l2_err(v4l2_dev,
+ "%s: set_fmt:%#x not supported, use def_fmt:%x\n",
+ __func__, pixm->pixelformat, fmt->fourcc);
+ }
+
+ if (!bt->width || !bt->height)
+ v4l2_err(v4l2_dev, "%s: invalid resolution:%#xx%#x\n",
+ __func__, bt->width, bt->height);
+
+ pixm->pixelformat = fmt->fourcc;
+ pixm->width = bt->width;
+ pixm->height = bt->height;
+ pixm->num_planes = fmt->mplanes;
+ pixm->quantization = V4L2_QUANTIZATION_DEFAULT;
+ pixm->colorspace = V4L2_COLORSPACE_SRGB;
+ pixm->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+
+ if (bt->interlaced == V4L2_DV_INTERLACED)
+ pixm->field = V4L2_FIELD_INTERLACED_TB;
+ else
+ pixm->field = V4L2_FIELD_NONE;
+
+ memset(pixm->reserved, 0, sizeof(pixm->reserved));
+
+ /* calculate plane size and image size */
+ fcc_xysubs(fmt->fourcc, &xsubs, &ysubs);
+ planes = fmt->cplanes ? fmt->cplanes : fmt->mplanes;
+
+ for (i = 0; i < planes; i++) {
+ struct v4l2_plane_pix_format *plane_fmt;
+ int width, height, bpl, size, bpp;
+
+ if (!i) {
+ width = pixm->width;
+ height = pixm->height;
+ } else {
+ width = pixm->width / xsubs;
+ height = pixm->height / ysubs;
+ }
+
+ bpp = hdmirx_align_bits_per_pixel(fmt, i);
+ bpl = ALIGN(width * bpp / HDMIRX_STORED_BIT_WIDTH,
+ MEMORY_ALIGN_ROUND_UP_BYTES);
+ size = bpl * height;
+ imagesize += size;
+
+ if (fmt->mplanes > i) {
+ /* Set bpl and size for each mplane */
+ plane_fmt = pixm->plane_fmt + i;
+ plane_fmt->bytesperline = bpl;
+ plane_fmt->sizeimage = size;
+ }
+
+ v4l2_dbg(1, debug, v4l2_dev,
+ "C-Plane %i size: %d, Total imagesize: %d\n",
+ i, size, imagesize);
+ }
+
+ /* convert to non-MPLANE format.
+ * It's important since we want to unify non-MPLANE and MPLANE.
+ */
+ if (fmt->mplanes == 1)
+ pixm->plane_fmt[0].sizeimage = imagesize;
+
+ if (!try) {
+ stream->out_fmt = fmt;
+ stream->pixm = *pixm;
+
+ v4l2_dbg(1, debug, v4l2_dev,
+ "%s: req(%d, %d), out(%d, %d), fmt:%#x\n", __func__,
+ pixm->width, pixm->height, stream->pixm.width,
+ stream->pixm.height, fmt->fourcc);
+ }
+}
+
+static int hdmirx_enum_fmt_vid_cap_mplane(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+
+ if (f->index >= 1)
+ return -EINVAL;
+
+ f->pixelformat = hdmirx_dev->cur_fmt_fourcc;
+
+ return 0;
+}
+
+static int hdmirx_s_fmt_vid_cap_mplane(struct file *file,
+ void *priv, struct v4l2_format *f)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ if (vb2_is_busy(&stream->buf_queue)) {
+ v4l2_err(v4l2_dev, "%s: queue busy\n", __func__);
+ return -EBUSY;
+ }
+
+ hdmirx_set_fmt(stream, &f->fmt.pix_mp, false);
+
+ return 0;
+}
+
+static int hdmirx_g_fmt_vid_cap_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_pix_format_mplane pixm = {};
+
+ pixm.pixelformat = hdmirx_dev->cur_fmt_fourcc;
+ hdmirx_set_fmt(stream, &pixm, true);
+ f->fmt.pix_mp = pixm;
+
+ return 0;
+}
+
+static int hdmirx_g_dv_timings(struct file *file, void *_fh,
+ struct v4l2_dv_timings *timings)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ u32 dma_cfg1;
+
+ *timings = hdmirx_dev->timings;
+ dma_cfg1 = hdmirx_readl(hdmirx_dev, DMA_CONFIG1);
+ v4l2_dbg(1, debug, v4l2_dev, "%s: pix_fmt: %s, DMA_CONFIG1:%#x\n",
+ __func__, pix_fmt_str[hdmirx_dev->pix_fmt], dma_cfg1);
+
+ return 0;
+}
+
+static int hdmirx_s_dv_timings(struct file *file, void *_fh,
+ struct v4l2_dv_timings *timings)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ if (!timings)
+ return -EINVAL;
+
+ if (debug)
+ v4l2_print_dv_timings(hdmirx_dev->v4l2_dev.name,
+ "s_dv_timings: ", timings, false);
+
+ if (!v4l2_valid_dv_timings(timings, &hdmirx_timings_cap, NULL, NULL)) {
+ v4l2_dbg(1, debug, v4l2_dev,
+ "%s: timings out of range\n", __func__);
+ return -ERANGE;
+ }
+
+ /* Check if the timings are part of the CEA-861 timings. */
+ v4l2_find_dv_timings_cap(timings, &hdmirx_timings_cap, 0, NULL, NULL);
+
+ if (v4l2_match_dv_timings(&hdmirx_dev->timings, timings, 0, false)) {
+ v4l2_dbg(1, debug, v4l2_dev, "%s: no change\n", __func__);
+ return 0;
+ }
+
+ /*
+ * Changing the timings implies a format change, which is not allowed
+ * while buffers for use with streaming have already been allocated.
+ */
+ if (vb2_is_busy(&stream->buf_queue))
+ return -EBUSY;
+
+ hdmirx_dev->timings = *timings;
+ /* Update the internal format */
+ hdmirx_set_fmt(stream, &stream->pixm, false);
+
+ return 0;
+}
+
+static int hdmirx_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct hdmirx_stream *stream = video_drvdata(file);
+ struct device *dev = stream->hdmirx_dev->dev;
+
+ strscpy(cap->driver, dev->driver->name, sizeof(cap->driver));
+ strscpy(cap->card, dev->driver->name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform: snps_hdmirx");
+
+ return 0;
+}
+
+static int hdmirx_queue_setup(struct vb2_queue *queue,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_ctxs[])
+{
+ struct hdmirx_stream *stream = vb2_get_drv_priv(queue);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ const struct v4l2_pix_format_mplane *pixm = NULL;
+ const struct hdmirx_output_fmt *out_fmt;
+ u32 i, height;
+
+ pixm = &stream->pixm;
+ out_fmt = stream->out_fmt;
+
+ if (!num_planes || !out_fmt) {
+ v4l2_err(v4l2_dev, "%s: out_fmt not set\n", __func__);
+ return -EINVAL;
+ }
+
+ if (*num_planes) {
+ if (*num_planes != pixm->num_planes)
+ return -EINVAL;
+
+ for (i = 0; i < *num_planes; i++)
+ if (sizes[i] < pixm->plane_fmt[i].sizeimage)
+ return -EINVAL;
+ }
+
+ *num_planes = out_fmt->mplanes;
+ height = pixm->height;
+
+ for (i = 0; i < out_fmt->mplanes; i++) {
+ const struct v4l2_plane_pix_format *plane_fmt;
+ int h = height;
+
+ plane_fmt = &pixm->plane_fmt[i];
+ sizes[i] = plane_fmt->sizeimage / height * h;
+ }
+
+ v4l2_dbg(1, debug, v4l2_dev, "%s: count %d, size %d\n",
+ v4l2_type_names[queue->type], *num_buffers, sizes[0]);
+
+ return 0;
+}
+
+/*
+ * The vb2_buffer are stored in hdmirx_buffer, in order to unify
+ * mplane buffer and none-mplane buffer.
+ */
+static void hdmirx_buf_queue(struct vb2_buffer *vb)
+{
+ const struct hdmirx_output_fmt *out_fmt;
+ struct vb2_v4l2_buffer *vbuf;
+ struct hdmirx_buffer *hdmirx_buf;
+ struct vb2_queue *queue;
+ struct hdmirx_stream *stream;
+ const struct v4l2_pix_format_mplane *pixm;
+ unsigned long lock_flags = 0;
+ int i;
+
+ vbuf = to_vb2_v4l2_buffer(vb);
+ hdmirx_buf = container_of(vbuf, struct hdmirx_buffer, vb);
+ queue = vb->vb2_queue;
+ stream = vb2_get_drv_priv(queue);
+ pixm = &stream->pixm;
+ out_fmt = stream->out_fmt;
+
+ memset(hdmirx_buf->buff_addr, 0, sizeof(hdmirx_buf->buff_addr));
+ /*
+ * If mplanes > 1, every c-plane has its own m-plane,
+ * otherwise, multiple c-planes are in the same m-plane
+ */
+ for (i = 0; i < out_fmt->mplanes; i++)
+ hdmirx_buf->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i);
+
+ if (out_fmt->mplanes == 1) {
+ if (out_fmt->cplanes == 1) {
+ hdmirx_buf->buff_addr[HDMIRX_PLANE_CBCR] =
+ hdmirx_buf->buff_addr[HDMIRX_PLANE_Y];
+ } else {
+ for (i = 0; i < out_fmt->cplanes - 1; i++)
+ hdmirx_buf->buff_addr[i + 1] =
+ hdmirx_buf->buff_addr[i] +
+ pixm->plane_fmt[i].bytesperline *
+ pixm->height;
+ }
+ }
+
+ spin_lock_irqsave(&stream->vbq_lock, lock_flags);
+ list_add_tail(&hdmirx_buf->queue, &stream->buf_head);
+ spin_unlock_irqrestore(&stream->vbq_lock, lock_flags);
+}
+
+static void return_all_buffers(struct hdmirx_stream *stream,
+ enum vb2_buffer_state state)
+{
+ struct hdmirx_buffer *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stream->vbq_lock, flags);
+ if (stream->curr_buf)
+ list_add_tail(&stream->curr_buf->queue, &stream->buf_head);
+ if (stream->next_buf && stream->next_buf != stream->curr_buf)
+ list_add_tail(&stream->next_buf->queue, &stream->buf_head);
+ stream->curr_buf = NULL;
+ stream->next_buf = NULL;
+
+ while (!list_empty(&stream->buf_head)) {
+ buf = list_first_entry(&stream->buf_head,
+ struct hdmirx_buffer, queue);
+ list_del(&buf->queue);
+ spin_unlock_irqrestore(&stream->vbq_lock, flags);
+ vb2_buffer_done(&buf->vb.vb2_buf, state);
+ spin_lock_irqsave(&stream->vbq_lock, flags);
+ }
+ spin_unlock_irqrestore(&stream->vbq_lock, flags);
+}
+
+static void hdmirx_stop_streaming(struct vb2_queue *queue)
+{
+ struct hdmirx_stream *stream = vb2_get_drv_priv(queue);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ int ret;
+
+ v4l2_info(v4l2_dev, "stream start stopping\n");
+ mutex_lock(&hdmirx_dev->stream_lock);
+ WRITE_ONCE(stream->stopping, true);
+
+ /* wait last irq to return the buffer */
+ ret = wait_event_timeout(stream->wq_stopped, !stream->stopping,
+ msecs_to_jiffies(500));
+ if (!ret) {
+ v4l2_err(v4l2_dev, "%s: timeout waiting last irq\n",
+ __func__);
+ WRITE_ONCE(stream->stopping, false);
+ }
+
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG6, HDMIRX_DMA_EN, 0);
+ return_all_buffers(stream, VB2_BUF_STATE_ERROR);
+ mutex_unlock(&hdmirx_dev->stream_lock);
+ v4l2_info(v4l2_dev, "stream stopping finished\n");
+}
+
+static int hdmirx_start_streaming(struct vb2_queue *queue, unsigned int count)
+{
+ struct hdmirx_stream *stream = vb2_get_drv_priv(queue);
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ struct v4l2_dv_timings timings = hdmirx_dev->timings;
+ struct v4l2_bt_timings *bt = &timings.bt;
+ unsigned long lock_flags = 0;
+ int line_flag;
+
+ if (!hdmirx_dev->got_timing) {
+ v4l2_err(v4l2_dev, "timing is invalid\n");
+ return 0;
+ }
+
+ mutex_lock(&hdmirx_dev->stream_lock);
+ stream->frame_idx = 0;
+ stream->line_flag_int_cnt = 0;
+ stream->curr_buf = NULL;
+ stream->next_buf = NULL;
+ stream->irq_stat = 0;
+ WRITE_ONCE(stream->stopping, false);
+
+ spin_lock_irqsave(&stream->vbq_lock, lock_flags);
+ if (!stream->curr_buf) {
+ if (!list_empty(&stream->buf_head)) {
+ stream->curr_buf = list_first_entry(&stream->buf_head,
+ struct hdmirx_buffer,
+ queue);
+ list_del(&stream->curr_buf->queue);
+ } else {
+ stream->curr_buf = NULL;
+ }
+ }
+ spin_unlock_irqrestore(&stream->vbq_lock, lock_flags);
+
+ if (!stream->curr_buf) {
+ mutex_unlock(&hdmirx_dev->stream_lock);
+ return -ENOMEM;
+ }
+
+ v4l2_dbg(2, debug, v4l2_dev,
+ "%s: start_stream cur_buf y_addr:%#x, uv_addr:%#x\n",
+ __func__, stream->curr_buf->buff_addr[HDMIRX_PLANE_Y],
+ stream->curr_buf->buff_addr[HDMIRX_PLANE_CBCR]);
+ hdmirx_writel(hdmirx_dev, DMA_CONFIG2,
+ stream->curr_buf->buff_addr[HDMIRX_PLANE_Y]);
+ hdmirx_writel(hdmirx_dev, DMA_CONFIG3,
+ stream->curr_buf->buff_addr[HDMIRX_PLANE_CBCR]);
+
+ if (bt->height) {
+ if (bt->interlaced == V4L2_DV_INTERLACED)
+ line_flag = bt->height / 4;
+ else
+ line_flag = bt->height / 2;
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG7,
+ LINE_FLAG_NUM_MASK,
+ LINE_FLAG_NUM(line_flag));
+ } else {
+ v4l2_err(v4l2_dev, "height err: %d\n", bt->height);
+ }
+
+ hdmirx_writel(hdmirx_dev, DMA_CONFIG5, 0xffffffff);
+ hdmirx_writel(hdmirx_dev, CED_DYN_CONTROL, 0x1);
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG4,
+ LINE_FLAG_INT_EN |
+ HDMIRX_DMA_IDLE_INT |
+ HDMIRX_LOCK_DISABLE_INT |
+ LAST_FRAME_AXI_UNFINISH_INT_EN |
+ FIFO_OVERFLOW_INT_EN |
+ FIFO_UNDERFLOW_INT_EN |
+ HDMIRX_AXI_ERROR_INT_EN,
+ LINE_FLAG_INT_EN |
+ HDMIRX_DMA_IDLE_INT |
+ HDMIRX_LOCK_DISABLE_INT |
+ LAST_FRAME_AXI_UNFINISH_INT_EN |
+ FIFO_OVERFLOW_INT_EN |
+ FIFO_UNDERFLOW_INT_EN |
+ HDMIRX_AXI_ERROR_INT_EN);
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG6, HDMIRX_DMA_EN, HDMIRX_DMA_EN);
+ v4l2_dbg(1, debug, v4l2_dev, "%s: enable dma", __func__);
+ mutex_unlock(&hdmirx_dev->stream_lock);
+
+ return 0;
+}
+
+/* vb2 queue */
+static const struct vb2_ops hdmirx_vb2_ops = {
+ .queue_setup = hdmirx_queue_setup,
+ .buf_queue = hdmirx_buf_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .stop_streaming = hdmirx_stop_streaming,
+ .start_streaming = hdmirx_start_streaming,
+};
+
+static int hdmirx_init_vb2_queue(struct vb2_queue *q,
+ struct hdmirx_stream *stream,
+ enum v4l2_buf_type buf_type)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+
+ q->type = buf_type;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->drv_priv = stream;
+ q->ops = &hdmirx_vb2_ops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct hdmirx_buffer);
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &stream->vlock;
+ q->dev = hdmirx_dev->dev;
+ q->allow_cache_hints = 0;
+ q->bidirectional = 1;
+ q->dma_attrs = DMA_ATTR_FORCE_CONTIGUOUS;
+ return vb2_queue_init(q);
+}
+
+/* video device */
+static const struct v4l2_ioctl_ops hdmirx_v4l2_ioctl_ops = {
+ .vidioc_querycap = hdmirx_querycap,
+ .vidioc_try_fmt_vid_cap_mplane = hdmirx_g_fmt_vid_cap_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = hdmirx_s_fmt_vid_cap_mplane,
+ .vidioc_g_fmt_vid_cap_mplane = hdmirx_g_fmt_vid_cap_mplane,
+ .vidioc_enum_fmt_vid_cap = hdmirx_enum_fmt_vid_cap_mplane,
+
+ .vidioc_s_dv_timings = hdmirx_s_dv_timings,
+ .vidioc_g_dv_timings = hdmirx_g_dv_timings,
+ .vidioc_enum_dv_timings = hdmirx_enum_dv_timings,
+ .vidioc_query_dv_timings = hdmirx_query_dv_timings,
+ .vidioc_dv_timings_cap = hdmirx_dv_timings_cap,
+ .vidioc_enum_input = hdmirx_enum_input,
+ .vidioc_g_input = hdmirx_get_input,
+ .vidioc_s_input = hdmirx_set_input,
+ .vidioc_g_edid = hdmirx_get_edid,
+ .vidioc_s_edid = hdmirx_set_edid,
+ .vidioc_g_parm = hdmirx_g_parm,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = hdmirx_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations hdmirx_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .unlocked_ioctl = video_ioctl2,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+};
+
+static int hdmirx_register_stream_vdev(struct hdmirx_stream *stream)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = stream->hdmirx_dev;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ struct video_device *vdev = &stream->vdev;
+ char *vdev_name;
+ int ret = 0;
+
+ vdev_name = "stream_hdmirx";
+ strscpy(vdev->name, vdev_name, sizeof(vdev->name));
+ INIT_LIST_HEAD(&stream->buf_head);
+ spin_lock_init(&stream->vbq_lock);
+ mutex_init(&stream->vlock);
+ init_waitqueue_head(&stream->wq_stopped);
+ stream->curr_buf = NULL;
+ stream->next_buf = NULL;
+
+ vdev->ioctl_ops = &hdmirx_v4l2_ioctl_ops;
+ vdev->release = video_device_release_empty;
+ vdev->fops = &hdmirx_fops;
+ vdev->minor = -1;
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->lock = &stream->vlock;
+ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE |
+ V4L2_CAP_STREAMING;
+ video_set_drvdata(vdev, stream);
+ vdev->vfl_dir = VFL_DIR_RX;
+
+ hdmirx_init_vb2_queue(&stream->buf_queue, stream,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ vdev->queue = &stream->buf_queue;
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev, "video_register_device failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void process_signal_change(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG6, HDMIRX_DMA_EN, 0);
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG4,
+ LINE_FLAG_INT_EN |
+ HDMIRX_DMA_IDLE_INT |
+ HDMIRX_LOCK_DISABLE_INT |
+ LAST_FRAME_AXI_UNFINISH_INT_EN |
+ FIFO_OVERFLOW_INT_EN |
+ FIFO_UNDERFLOW_INT_EN |
+ HDMIRX_AXI_ERROR_INT_EN, 0);
+ hdmirx_reset_dma(hdmirx_dev);
+ hdmirx_dev->got_timing = false;
+ queue_delayed_work(system_unbound_wq,
+ &hdmirx_dev->delayed_work_res_change,
+ msecs_to_jiffies(50));
+}
+
+static void avpunit_0_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
+ int status, bool *handled)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ if (status & (CED_DYN_CNT_CH2_IRQ |
+ CED_DYN_CNT_CH1_IRQ |
+ CED_DYN_CNT_CH0_IRQ)) {
+ process_signal_change(hdmirx_dev);
+ v4l2_dbg(2, debug, v4l2_dev, "%s: avp0_st:%#x\n",
+ __func__, status);
+ *handled = true;
+ }
+
+ hdmirx_clear_interrupt(hdmirx_dev, AVPUNIT_0_INT_CLEAR, 0xffffffff);
+ hdmirx_writel(hdmirx_dev, AVPUNIT_0_INT_FORCE, 0x0);
+}
+
+static void avpunit_1_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
+ int status, bool *handled)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ if (status & DEFRAMER_VSYNC_THR_REACHED_IRQ) {
+ v4l2_info(v4l2_dev, "Vertical Sync threshold reached interrupt %#x", status);
+ hdmirx_update_bits(hdmirx_dev, AVPUNIT_1_INT_MASK_N,
+ DEFRAMER_VSYNC_THR_REACHED_MASK_N, 0);
+ *handled = true;
+ }
+}
+
+static void mainunit_0_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
+ int status, bool *handled)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ v4l2_dbg(2, debug, v4l2_dev, "mu0_st:%#x\n", status);
+ if (status & TIMER_BASE_LOCKED_IRQ) {
+ hdmirx_update_bits(hdmirx_dev, MAINUNIT_0_INT_MASK_N,
+ TIMER_BASE_LOCKED_IRQ, 0);
+ complete(&hdmirx_dev->timer_base_lock);
+ *handled = true;
+ }
+
+ if (status & TMDSQPCLK_OFF_CHG) {
+ process_signal_change(hdmirx_dev);
+ v4l2_dbg(2, debug, v4l2_dev, "%s: TMDSQPCLK_OFF_CHG\n", __func__);
+ *handled = true;
+ }
+
+ if (status & TMDSQPCLK_LOCKED_CHG) {
+ process_signal_change(hdmirx_dev);
+ v4l2_dbg(2, debug, v4l2_dev, "%s: TMDSQPCLK_LOCKED_CHG\n", __func__);
+ *handled = true;
+ }
+
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_0_INT_CLEAR, 0xffffffff);
+ hdmirx_writel(hdmirx_dev, MAINUNIT_0_INT_FORCE, 0x0);
+}
+
+static void mainunit_2_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
+ int status, bool *handled)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ v4l2_dbg(2, debug, v4l2_dev, "mu2_st:%#x\n", status);
+ if (status & PHYCREG_CR_WRITE_DONE) {
+ hdmirx_update_bits(hdmirx_dev, MAINUNIT_2_INT_MASK_N,
+ PHYCREG_CR_WRITE_DONE, 0);
+ complete(&hdmirx_dev->cr_write_done);
+ *handled = true;
+ }
+
+ if (status & TMDSVALID_STABLE_CHG) {
+ process_signal_change(hdmirx_dev);
+ v4l2_dbg(2, debug, v4l2_dev, "%s: TMDSVALID_STABLE_CHG\n", __func__);
+ *handled = true;
+ }
+
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff);
+ hdmirx_writel(hdmirx_dev, MAINUNIT_2_INT_FORCE, 0x0);
+}
+
+static void pkt_2_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
+ int status, bool *handled)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ v4l2_dbg(2, debug, v4l2_dev, "%s: pk2_st:%#x\n", __func__, status);
+ if (status & PKTDEC_AVIIF_RCV_IRQ) {
+ hdmirx_update_bits(hdmirx_dev, PKT_2_INT_MASK_N,
+ PKTDEC_AVIIF_RCV_IRQ, 0);
+ complete(&hdmirx_dev->avi_pkt_rcv);
+ v4l2_dbg(2, debug, v4l2_dev, "%s: AVIIF_RCV_IRQ\n", __func__);
+ *handled = true;
+ }
+
+ hdmirx_clear_interrupt(hdmirx_dev, PKT_2_INT_CLEAR, 0xffffffff);
+}
+
+static void scdc_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
+ int status, bool *handled)
+{
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ v4l2_dbg(2, debug, v4l2_dev, "%s: scdc_st:%#x\n", __func__, status);
+ if (status & SCDCTMDSCCFG_CHG) {
+ hdmirx_tmds_clk_ratio_config(hdmirx_dev);
+ *handled = true;
+ }
+
+ hdmirx_clear_interrupt(hdmirx_dev, SCDC_INT_CLEAR, 0xffffffff);
+}
+
+static irqreturn_t hdmirx_hdmi_irq_handler(int irq, void *dev_id)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = dev_id;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ struct arm_smccc_res res;
+ u32 mu0_st, mu2_st, pk2_st, scdc_st, avp1_st, avp0_st;
+ u32 mu0_mask, mu2_mask, pk2_mask, scdc_mask, avp1_msk, avp0_msk;
+ bool handled = false;
+
+ mu0_mask = hdmirx_readl(hdmirx_dev, MAINUNIT_0_INT_MASK_N);
+ mu2_mask = hdmirx_readl(hdmirx_dev, MAINUNIT_2_INT_MASK_N);
+ pk2_mask = hdmirx_readl(hdmirx_dev, PKT_2_INT_MASK_N);
+ scdc_mask = hdmirx_readl(hdmirx_dev, SCDC_INT_MASK_N);
+ mu0_st = hdmirx_readl(hdmirx_dev, MAINUNIT_0_INT_STATUS);
+ mu2_st = hdmirx_readl(hdmirx_dev, MAINUNIT_2_INT_STATUS);
+ pk2_st = hdmirx_readl(hdmirx_dev, PKT_2_INT_STATUS);
+ scdc_st = hdmirx_readl(hdmirx_dev, SCDC_INT_STATUS);
+ avp0_st = hdmirx_readl(hdmirx_dev, AVPUNIT_0_INT_STATUS);
+ avp1_st = hdmirx_readl(hdmirx_dev, AVPUNIT_1_INT_STATUS);
+ avp0_msk = hdmirx_readl(hdmirx_dev, AVPUNIT_0_INT_MASK_N);
+ avp1_msk = hdmirx_readl(hdmirx_dev, AVPUNIT_1_INT_MASK_N);
+ mu0_st &= mu0_mask;
+ mu2_st &= mu2_mask;
+ pk2_st &= pk2_mask;
+ avp1_st &= avp1_msk;
+ avp0_st &= avp0_msk;
+ scdc_st &= scdc_mask;
+
+ if (avp0_st)
+ avpunit_0_int_handler(hdmirx_dev, avp0_st, &handled);
+ if (avp1_st)
+ avpunit_1_int_handler(hdmirx_dev, avp1_st, &handled);
+ if (mu0_st)
+ mainunit_0_int_handler(hdmirx_dev, mu0_st, &handled);
+ if (mu2_st)
+ mainunit_2_int_handler(hdmirx_dev, mu2_st, &handled);
+ if (pk2_st)
+ pkt_2_int_handler(hdmirx_dev, pk2_st, &handled);
+ if (scdc_st)
+ scdc_int_handler(hdmirx_dev, scdc_st, &handled);
+
+ if (!handled) {
+ v4l2_dbg(2, debug, v4l2_dev, "%s: hdmi irq not handled", __func__);
+ v4l2_dbg(2, debug, v4l2_dev,
+ "avp0:%#x, avp1:%#x, mu0:%#x, mu2:%#x, pk2:%#x, scdc:%#x\n",
+ avp0_st, avp1_st, mu0_st, mu2_st, pk2_st, scdc_st);
+ }
+
+ v4l2_dbg(2, debug, v4l2_dev, "%s: en_fiq", __func__);
+ arm_smccc_smc(RK_SIP_FIQ_CTRL, RK_SIP_FIQ_CTRL_FIQ_EN,
+ RK_IRQ_HDMIRX_HDMI, 0, 0, 0, 0, 0, &res);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void hdmirx_vb_done(struct hdmirx_stream *stream,
+ struct vb2_v4l2_buffer *vb_done)
+{
+ const struct hdmirx_output_fmt *fmt = stream->out_fmt;
+ u32 i;
+
+ /* Dequeue a filled buffer */
+ for (i = 0; i < fmt->mplanes; i++) {
+ vb2_set_plane_payload(&vb_done->vb2_buf, i,
+ stream->pixm.plane_fmt[i].sizeimage);
+ }
+
+ vb_done->vb2_buf.timestamp = ktime_get_ns();
+ vb2_buffer_done(&vb_done->vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+static void dma_idle_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
+ bool *handled)
+{
+ struct hdmirx_stream *stream = &hdmirx_dev->stream;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ struct v4l2_dv_timings timings = hdmirx_dev->timings;
+ struct v4l2_bt_timings *bt = &timings.bt;
+ struct vb2_v4l2_buffer *vb_done = NULL;
+
+ if (!(stream->irq_stat) && !(stream->irq_stat & LINE_FLAG_INT_EN))
+ v4l2_dbg(1, debug, v4l2_dev,
+ "%s: last time have no line_flag_irq\n", __func__);
+
+ if (stream->line_flag_int_cnt <= FILTER_FRAME_CNT)
+ goto DMA_IDLE_OUT;
+
+ if (bt->interlaced != V4L2_DV_INTERLACED ||
+ !(stream->line_flag_int_cnt % 2)) {
+ if (stream->next_buf) {
+ if (stream->curr_buf)
+ vb_done = &stream->curr_buf->vb;
+
+ if (vb_done) {
+ vb_done->vb2_buf.timestamp = ktime_get_ns();
+ vb_done->sequence = stream->frame_idx;
+ hdmirx_vb_done(stream, vb_done);
+ stream->frame_idx++;
+ if (stream->frame_idx == 30)
+ v4l2_info(v4l2_dev, "rcv frames\n");
+ }
+
+ stream->curr_buf = NULL;
+ if (stream->next_buf) {
+ stream->curr_buf = stream->next_buf;
+ stream->next_buf = NULL;
+ }
+ } else {
+ v4l2_dbg(3, debug, v4l2_dev,
+ "%s: next_buf NULL, skip vb_done\n", __func__);
+ }
+ }
+
+DMA_IDLE_OUT:
+ *handled = true;
+}
+
+static void line_flag_int_handler(struct snps_hdmirx_dev *hdmirx_dev,
+ bool *handled)
+{
+ struct hdmirx_stream *stream = &hdmirx_dev->stream;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ struct v4l2_dv_timings timings = hdmirx_dev->timings;
+ struct v4l2_bt_timings *bt = &timings.bt;
+ u32 dma_cfg6;
+
+ stream->line_flag_int_cnt++;
+ if (!(stream->irq_stat) && !(stream->irq_stat & HDMIRX_DMA_IDLE_INT))
+ v4l2_dbg(1, debug, v4l2_dev,
+ "%s: last have no dma_idle_irq\n", __func__);
+ dma_cfg6 = hdmirx_readl(hdmirx_dev, DMA_CONFIG6);
+ if (!(dma_cfg6 & HDMIRX_DMA_EN)) {
+ v4l2_dbg(2, debug, v4l2_dev, "%s: dma not on\n", __func__);
+ goto LINE_FLAG_OUT;
+ }
+
+ if (stream->line_flag_int_cnt <= FILTER_FRAME_CNT)
+ goto LINE_FLAG_OUT;
+
+ if (bt->interlaced != V4L2_DV_INTERLACED ||
+ !(stream->line_flag_int_cnt % 2)) {
+ if (!stream->next_buf) {
+ spin_lock(&stream->vbq_lock);
+ if (!list_empty(&stream->buf_head)) {
+ stream->next_buf = list_first_entry(&stream->buf_head,
+ struct hdmirx_buffer,
+ queue);
+ list_del(&stream->next_buf->queue);
+ } else {
+ stream->next_buf = NULL;
+ }
+ spin_unlock(&stream->vbq_lock);
+
+ if (stream->next_buf) {
+ hdmirx_writel(hdmirx_dev, DMA_CONFIG2,
+ stream->next_buf->buff_addr[HDMIRX_PLANE_Y]);
+ hdmirx_writel(hdmirx_dev, DMA_CONFIG3,
+ stream->next_buf->buff_addr[HDMIRX_PLANE_CBCR]);
+ } else {
+ v4l2_dbg(3, debug, v4l2_dev,
+ "%s: no buffer is available\n", __func__);
+ }
+ }
+ } else {
+ v4l2_dbg(3, debug, v4l2_dev, "%s: interlace:%d, line_flag_int_cnt:%d\n",
+ __func__, bt->interlaced, stream->line_flag_int_cnt);
+ }
+
+LINE_FLAG_OUT:
+ *handled = true;
+}
+
+static irqreturn_t hdmirx_dma_irq_handler(int irq, void *dev_id)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = dev_id;
+ struct hdmirx_stream *stream = &hdmirx_dev->stream;
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ u32 dma_stat1, dma_stat13;
+ bool handled = false;
+
+ dma_stat1 = hdmirx_readl(hdmirx_dev, DMA_STATUS1);
+ dma_stat13 = hdmirx_readl(hdmirx_dev, DMA_STATUS13);
+ v4l2_dbg(3, debug, v4l2_dev, "dma_irq st1:%#x, st13:%d\n",
+ dma_stat1, dma_stat13);
+
+ if (READ_ONCE(stream->stopping)) {
+ v4l2_dbg(1, debug, v4l2_dev, "%s: stop stream\n", __func__);
+ hdmirx_writel(hdmirx_dev, DMA_CONFIG5, 0xffffffff);
+ hdmirx_update_bits(hdmirx_dev, DMA_CONFIG4,
+ LINE_FLAG_INT_EN |
+ HDMIRX_DMA_IDLE_INT |
+ HDMIRX_LOCK_DISABLE_INT |
+ LAST_FRAME_AXI_UNFINISH_INT_EN |
+ FIFO_OVERFLOW_INT_EN |
+ FIFO_UNDERFLOW_INT_EN |
+ HDMIRX_AXI_ERROR_INT_EN, 0);
+ WRITE_ONCE(stream->stopping, false);
+ wake_up(&stream->wq_stopped);
+ return IRQ_HANDLED;
+ }
+
+ if (dma_stat1 & HDMIRX_DMA_IDLE_INT)
+ dma_idle_int_handler(hdmirx_dev, &handled);
+
+ if (dma_stat1 & LINE_FLAG_INT_EN)
+ line_flag_int_handler(hdmirx_dev, &handled);
+
+ if (!handled)
+ v4l2_dbg(3, debug, v4l2_dev,
+ "%s: dma irq not handled, dma_stat1:%#x\n",
+ __func__, dma_stat1);
+
+ stream->irq_stat = dma_stat1;
+ hdmirx_writel(hdmirx_dev, DMA_CONFIG5, 0xffffffff);
+
+ return IRQ_HANDLED;
+}
+
+static void hdmirx_plugin(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct arm_smccc_res res;
+ int ret;
+
+ queue_delayed_work(system_unbound_wq,
+ &hdmirx_dev->delayed_work_heartbeat,
+ msecs_to_jiffies(10));
+ arm_smccc_smc(SIP_WDT_CFG, WDT_START, 0, 0, 0, 0, 0, 0, &res);
+ hdmirx_submodule_init(hdmirx_dev);
+ hdmirx_update_bits(hdmirx_dev, SCDC_CONFIG, POWERPROVIDED,
+ POWERPROVIDED);
+ hdmirx_hpd_ctrl(hdmirx_dev, true);
+ hdmirx_phy_config(hdmirx_dev);
+ ret = hdmirx_wait_lock_and_get_timing(hdmirx_dev);
+ if (ret) {
+ hdmirx_plugout(hdmirx_dev);
+ queue_delayed_work(system_unbound_wq,
+ &hdmirx_dev->delayed_work_hotplug,
+ msecs_to_jiffies(200));
+ return;
+ }
+ hdmirx_dma_config(hdmirx_dev);
+ hdmirx_interrupts_setup(hdmirx_dev, true);
+}
+
+static void hdmirx_delayed_work_hotplug(struct work_struct *work)
+{
+ struct snps_hdmirx_dev *hdmirx_dev;
+ bool plugin;
+
+ hdmirx_dev = container_of(work, struct snps_hdmirx_dev,
+ delayed_work_hotplug.work);
+
+ mutex_lock(&hdmirx_dev->work_lock);
+ hdmirx_dev->got_timing = false;
+ plugin = tx_5v_power_present(hdmirx_dev);
+ v4l2_ctrl_s_ctrl(hdmirx_dev->detect_tx_5v_ctrl, plugin);
+ v4l2_dbg(1, debug, &hdmirx_dev->v4l2_dev, "%s: plugin:%d\n",
+ __func__, plugin);
+
+ if (plugin)
+ hdmirx_plugin(hdmirx_dev);
+ else
+ hdmirx_plugout(hdmirx_dev);
+
+ mutex_unlock(&hdmirx_dev->work_lock);
+}
+
+static void hdmirx_delayed_work_res_change(struct work_struct *work)
+{
+ struct snps_hdmirx_dev *hdmirx_dev;
+ bool plugin;
+
+ hdmirx_dev = container_of(work, struct snps_hdmirx_dev,
+ delayed_work_res_change.work);
+
+ mutex_lock(&hdmirx_dev->work_lock);
+ plugin = tx_5v_power_present(hdmirx_dev);
+ v4l2_dbg(1, debug, &hdmirx_dev->v4l2_dev, "%s: plugin:%d\n",
+ __func__, plugin);
+ if (plugin) {
+ hdmirx_interrupts_setup(hdmirx_dev, false);
+ hdmirx_submodule_init(hdmirx_dev);
+ hdmirx_update_bits(hdmirx_dev, SCDC_CONFIG, POWERPROVIDED,
+ POWERPROVIDED);
+ hdmirx_hpd_ctrl(hdmirx_dev, true);
+ hdmirx_phy_config(hdmirx_dev);
+
+ if (hdmirx_wait_lock_and_get_timing(hdmirx_dev)) {
+ hdmirx_plugout(hdmirx_dev);
+ queue_delayed_work(system_unbound_wq,
+ &hdmirx_dev->delayed_work_hotplug,
+ msecs_to_jiffies(200));
+ } else {
+ hdmirx_dma_config(hdmirx_dev);
+ hdmirx_interrupts_setup(hdmirx_dev, true);
+ }
+ }
+ mutex_unlock(&hdmirx_dev->work_lock);
+}
+
+static void hdmirx_delayed_work_heartbeat(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct snps_hdmirx_dev *hdmirx_dev = container_of(dwork,
+ struct snps_hdmirx_dev,
+ delayed_work_heartbeat);
+
+ queue_work(system_highpri_wq, &hdmirx_dev->work_wdt_config);
+ queue_delayed_work(system_unbound_wq,
+ &hdmirx_dev->delayed_work_heartbeat, HZ);
+}
+
+static void hdmirx_work_wdt_config(struct work_struct *work)
+{
+ struct arm_smccc_res res;
+ struct snps_hdmirx_dev *hdmirx_dev = container_of(work,
+ struct snps_hdmirx_dev,
+ work_wdt_config);
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ arm_smccc_smc(SIP_WDT_CFG, WDT_PING, 0, 0, 0, 0, 0, 0, &res);
+ v4l2_dbg(3, debug, v4l2_dev, "hb\n");
+}
+
+static irqreturn_t hdmirx_5v_det_irq_handler(int irq, void *dev_id)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = dev_id;
+ u32 val;
+
+ val = gpiod_get_value(hdmirx_dev->detect_5v_gpio);
+ v4l2_dbg(3, debug, &hdmirx_dev->v4l2_dev, "%s: 5v:%d\n", __func__, val);
+
+ queue_delayed_work(system_unbound_wq,
+ &hdmirx_dev->delayed_work_hotplug,
+ msecs_to_jiffies(10));
+
+ return IRQ_HANDLED;
+}
+
+static const struct hdmirx_cec_ops hdmirx_cec_ops = {
+ .write = hdmirx_writel,
+ .read = hdmirx_readl,
+};
+
+static int hdmirx_parse_dt(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ struct device *dev = hdmirx_dev->dev;
+ int ret;
+
+ hdmirx_dev->num_clks = devm_clk_bulk_get_all(dev, &hdmirx_dev->clks);
+ if (hdmirx_dev->num_clks < 1)
+ return -ENODEV;
+
+ hdmirx_dev->resets[HDMIRX_RST_A].id = "rst_a";
+ hdmirx_dev->resets[HDMIRX_RST_P].id = "rst_p";
+ hdmirx_dev->resets[HDMIRX_RST_REF].id = "rst_ref";
+ hdmirx_dev->resets[HDMIRX_RST_BIU].id = "rst_biu";
+
+ ret = devm_reset_control_bulk_get_exclusive(dev, HDMIRX_NUM_RST,
+ hdmirx_dev->resets);
+ if (ret < 0) {
+ dev_err(dev, "failed to get reset controls\n");
+ return ret;
+ }
+
+ hdmirx_dev->detect_5v_gpio =
+ devm_gpiod_get_optional(dev, "hdmirx-5v-detection", GPIOD_IN);
+
+ if (IS_ERR(hdmirx_dev->detect_5v_gpio)) {
+ dev_err(dev, "failed to get hdmirx 5v detection gpio\n");
+ return PTR_ERR(hdmirx_dev->detect_5v_gpio);
+ }
+
+ hdmirx_dev->grf = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,grf");
+ if (IS_ERR(hdmirx_dev->grf)) {
+ dev_err(dev, "failed to get rockchip,grf\n");
+ return PTR_ERR(hdmirx_dev->grf);
+ }
+
+ hdmirx_dev->vo1_grf = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "rockchip,vo1_grf");
+ if (IS_ERR(hdmirx_dev->vo1_grf)) {
+ dev_err(dev, "failed to get rockchip,vo1_grf\n");
+ return PTR_ERR(hdmirx_dev->vo1_grf);
+ }
+
+ hdmirx_dev->hpd_trigger_level = !device_property_read_bool(dev, "hpd-is-active-low");
+
+ ret = of_reserved_mem_device_init(dev);
+ if (ret)
+ dev_warn(dev, "No reserved memory for HDMIRX, use default CMA\n");
+
+ return 0;
+}
+
+static void hdmirx_disable_all_interrupts(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ hdmirx_writel(hdmirx_dev, MAINUNIT_0_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, MAINUNIT_1_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, MAINUNIT_2_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, AVPUNIT_0_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, AVPUNIT_1_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, PKT_0_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, PKT_1_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, PKT_2_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, SCDC_INT_MASK_N, 0);
+ hdmirx_writel(hdmirx_dev, CEC_INT_MASK_N, 0);
+
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_0_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_1_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, MAINUNIT_2_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, AVPUNIT_0_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, AVPUNIT_1_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, PKT_0_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, PKT_1_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, PKT_2_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, SCDC_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, HDCP_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, HDCP_1_INT_CLEAR, 0xffffffff);
+ hdmirx_clear_interrupt(hdmirx_dev, CEC_INT_CLEAR, 0xffffffff);
+}
+
+static int hdmirx_init(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ hdmirx_update_bits(hdmirx_dev, PHY_CONFIG, PHY_RESET | PHY_PDDQ, 0);
+
+ regmap_write(hdmirx_dev->vo1_grf, VO1_GRF_VO1_CON2,
+ (HDMIRX_SDAIN_MSK | HDMIRX_SCLIN_MSK) |
+ ((HDMIRX_SDAIN_MSK | HDMIRX_SCLIN_MSK) << 16));
+ /*
+ * Some interrupts are enabled by default, so we disable
+ * all interrupts and clear interrupts status first.
+ */
+ hdmirx_disable_all_interrupts(hdmirx_dev);
+
+ return 0;
+}
+
+static void hdmirx_edid_init_config(struct snps_hdmirx_dev *hdmirx_dev)
+{
+ int ret;
+ struct v4l2_edid def_edid;
+
+ /* disable hpd and write edid */
+ def_edid.pad = 0;
+ def_edid.start_block = 0;
+ def_edid.blocks = EDID_NUM_BLOCKS_MAX;
+ if (hdmirx_dev->edid_version == HDMIRX_EDID_600M)
+ def_edid.edid = edid_init_data_600M;
+ else
+ def_edid.edid = edid_init_data_340M;
+ ret = hdmirx_write_edid(hdmirx_dev, &def_edid, false);
+ if (ret)
+ dev_err(hdmirx_dev->dev, "%s: write edid failed\n", __func__);
+}
+
+static void hdmirx_disable_irq(struct device *dev)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev);
+ struct arm_smccc_res res;
+
+ disable_irq(hdmirx_dev->hdmi_irq);
+ disable_irq(hdmirx_dev->dma_irq);
+ disable_irq(hdmirx_dev->det_irq);
+
+ arm_smccc_smc(RK_SIP_FIQ_CTRL, RK_SIP_FIQ_CTRL_FIQ_DIS,
+ RK_IRQ_HDMIRX_HDMI, 0, 0, 0, 0, 0, &res);
+
+ cancel_delayed_work_sync(&hdmirx_dev->delayed_work_hotplug);
+ cancel_delayed_work_sync(&hdmirx_dev->delayed_work_res_change);
+ cancel_delayed_work_sync(&hdmirx_dev->delayed_work_heartbeat);
+ flush_work(&hdmirx_dev->work_wdt_config);
+
+ arm_smccc_smc(SIP_WDT_CFG, WDT_STOP, 0, 0, 0, 0, 0, 0, &res);
+}
+
+static int hdmirx_disable(struct device *dev)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev);
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+
+ clk_bulk_disable_unprepare(hdmirx_dev->num_clks, hdmirx_dev->clks);
+
+ v4l2_dbg(2, debug, v4l2_dev, "%s: suspend\n", __func__);
+
+ return pinctrl_pm_select_sleep_state(dev);
+}
+
+static void hdmirx_enable_irq(struct device *dev)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev);
+ struct arm_smccc_res res;
+
+ enable_irq(hdmirx_dev->hdmi_irq);
+ enable_irq(hdmirx_dev->dma_irq);
+ enable_irq(hdmirx_dev->det_irq);
+
+ arm_smccc_smc(RK_SIP_FIQ_CTRL, RK_SIP_FIQ_CTRL_FIQ_EN,
+ RK_IRQ_HDMIRX_HDMI, 0, 0, 0, 0, 0, &res);
+
+ queue_delayed_work(system_unbound_wq, &hdmirx_dev->delayed_work_hotplug,
+ msecs_to_jiffies(20));
+}
+
+static int hdmirx_enable(struct device *dev)
+{
+ struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev);
+ struct v4l2_device *v4l2_dev = &hdmirx_dev->v4l2_dev;
+ int ret;
+
+ v4l2_dbg(2, debug, v4l2_dev, "%s: resume\n", __func__);
+ ret = pinctrl_pm_select_default_state(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_bulk_prepare_enable(hdmirx_dev->num_clks, hdmirx_dev->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable hdmirx bulk clks: %d\n", ret);
+ return ret;
+ }
+
+ reset_control_bulk_assert(HDMIRX_NUM_RST, hdmirx_dev->resets);
+ usleep_range(150, 160);
+ reset_control_bulk_deassert(HDMIRX_NUM_RST, hdmirx_dev->resets);
+ usleep_range(150, 160);
+
+ hdmirx_edid_init_config(hdmirx_dev);
+
+ return 0;
+}
+
+static int hdmirx_suspend(struct device *dev)
+{
+ hdmirx_disable_irq(dev);
+
+ return hdmirx_disable(dev);
+}
+
+static int hdmirx_resume(struct device *dev)
+{
+ int ret = hdmirx_enable(dev);
+
+ if (ret)
+ return ret;
+
+ hdmirx_enable_irq(dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops snps_hdmirx_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(hdmirx_suspend, hdmirx_resume)
+};
+
+static int hdmirx_setup_irq(struct snps_hdmirx_dev *hdmirx_dev,
+ struct platform_device *pdev)
+{
+ struct device *dev = hdmirx_dev->dev;
+ int ret, irq;
+
+ irq = platform_get_irq_byname(pdev, "hdmi");
+ if (irq < 0) {
+ dev_err_probe(dev, irq, "failed to get hdmi irq\n");
+ return irq;
+ }
+
+ irq_set_status_flags(irq, IRQ_NOAUTOEN);
+
+ hdmirx_dev->hdmi_irq = irq;
+ ret = devm_request_irq(dev, irq, hdmirx_hdmi_irq_handler, 0,
+ "rk_hdmirx-hdmi", hdmirx_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to request hdmi irq\n");
+ return ret;
+ }
+
+ irq = platform_get_irq_byname(pdev, "dma");
+ if (irq < 0) {
+ dev_err_probe(dev, irq, "failed to get dma irq\n");
+ return irq;
+ }
+
+ irq_set_status_flags(irq, IRQ_NOAUTOEN);
+
+ hdmirx_dev->dma_irq = irq;
+ ret = devm_request_threaded_irq(dev, irq, NULL, hdmirx_dma_irq_handler,
+ IRQF_ONESHOT, "rk_hdmirx-dma",
+ hdmirx_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to request dma irq\n");
+ return ret;
+ }
+
+ irq = gpiod_to_irq(hdmirx_dev->detect_5v_gpio);
+ if (irq < 0) {
+ dev_err_probe(dev, irq, "failed to get hdmirx-5v irq\n");
+ return irq;
+ }
+
+ irq_set_status_flags(irq, IRQ_NOAUTOEN);
+
+ hdmirx_dev->det_irq = irq;
+ ret = devm_request_irq(dev, irq, hdmirx_5v_det_irq_handler,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "rk_hdmirx-5v", hdmirx_dev);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to request hdmirx-5v irq\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int hdmirx_register_cec(struct snps_hdmirx_dev *hdmirx_dev,
+ struct platform_device *pdev)
+{
+ struct device *dev = hdmirx_dev->dev;
+ struct hdmirx_cec_data cec_data;
+ int irq;
+
+ irq = platform_get_irq_byname(pdev, "cec");
+ if (irq < 0) {
+ dev_err_probe(dev, irq, "failed to get cec irq\n");
+ return irq;
+ }
+
+ hdmirx_dev->cec_notifier = cec_notifier_conn_register(dev, NULL, NULL);
+ if (!hdmirx_dev->cec_notifier)
+ return -EINVAL;
+
+ cec_data.hdmirx = hdmirx_dev;
+ cec_data.dev = hdmirx_dev->dev;
+ cec_data.ops = &hdmirx_cec_ops;
+ cec_data.irq = irq;
+ cec_data.edid = edid_init_data_340M;
+
+ hdmirx_dev->cec = snps_hdmirx_cec_register(&cec_data);
+ if (!hdmirx_dev->cec) {
+ cec_notifier_conn_unregister(hdmirx_dev->cec_notifier);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hdmirx_probe(struct platform_device *pdev)
+{
+ struct snps_hdmirx_dev *hdmirx_dev;
+ struct device *dev = &pdev->dev;
+ struct v4l2_ctrl_handler *hdl;
+ struct hdmirx_stream *stream;
+ struct v4l2_device *v4l2_dev;
+ int ret;
+
+ hdmirx_dev = devm_kzalloc(dev, sizeof(*hdmirx_dev), GFP_KERNEL);
+ if (!hdmirx_dev)
+ return -ENOMEM;
+
+ ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ hdmirx_dev->dev = dev;
+ dev_set_drvdata(dev, hdmirx_dev);
+ hdmirx_dev->edid_version = HDMIRX_EDID_340M;
+
+ ret = hdmirx_parse_dt(hdmirx_dev);
+ if (ret)
+ return ret;
+
+ ret = hdmirx_setup_irq(hdmirx_dev, pdev);
+ if (ret)
+ return ret;
+
+ hdmirx_dev->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(hdmirx_dev->regs))
+ return dev_err_probe(dev, PTR_ERR(hdmirx_dev->regs),
+ "failed to remap regs resource\n");
+
+ mutex_init(&hdmirx_dev->stream_lock);
+ mutex_init(&hdmirx_dev->work_lock);
+ spin_lock_init(&hdmirx_dev->rst_lock);
+
+ init_completion(&hdmirx_dev->cr_write_done);
+ init_completion(&hdmirx_dev->timer_base_lock);
+ init_completion(&hdmirx_dev->avi_pkt_rcv);
+
+ INIT_WORK(&hdmirx_dev->work_wdt_config, hdmirx_work_wdt_config);
+ INIT_DELAYED_WORK(&hdmirx_dev->delayed_work_hotplug,
+ hdmirx_delayed_work_hotplug);
+ INIT_DELAYED_WORK(&hdmirx_dev->delayed_work_res_change,
+ hdmirx_delayed_work_res_change);
+ INIT_DELAYED_WORK(&hdmirx_dev->delayed_work_heartbeat,
+ hdmirx_delayed_work_heartbeat);
+
+ hdmirx_dev->cur_fmt_fourcc = V4L2_PIX_FMT_BGR24;
+ hdmirx_dev->timings = cea640x480;
+
+ hdmirx_enable(dev);
+ hdmirx_init(hdmirx_dev);
+
+ v4l2_dev = &hdmirx_dev->v4l2_dev;
+ strscpy(v4l2_dev->name, dev_name(dev), sizeof(v4l2_dev->name));
+
+ hdl = &hdmirx_dev->hdl;
+ v4l2_ctrl_handler_init(hdl, 1);
+ hdmirx_dev->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL,
+ V4L2_CID_DV_RX_POWER_PRESENT,
+ 0, 1, 0, 0);
+ if (hdl->error) {
+ dev_err(dev, "v4l2 ctrl handler init failed\n");
+ ret = hdl->error;
+ goto err_pm;
+ }
+ hdmirx_dev->v4l2_dev.ctrl_handler = hdl;
+
+ ret = v4l2_device_register(dev, &hdmirx_dev->v4l2_dev);
+ if (ret < 0) {
+ dev_err(dev, "register v4l2 device failed\n");
+ goto err_hdl;
+ }
+
+ stream = &hdmirx_dev->stream;
+ stream->hdmirx_dev = hdmirx_dev;
+ ret = hdmirx_register_stream_vdev(stream);
+ if (ret < 0) {
+ dev_err(dev, "register video device failed\n");
+ goto err_unreg_v4l2_dev;
+ }
+
+ ret = hdmirx_register_cec(hdmirx_dev, pdev);
+ if (ret)
+ goto err_unreg_video_dev;
+
+ hdmirx_enable_irq(dev);
+
+ return 0;
+
+err_unreg_video_dev:
+ video_unregister_device(&hdmirx_dev->stream.vdev);
+err_unreg_v4l2_dev:
+ v4l2_device_unregister(&hdmirx_dev->v4l2_dev);
+err_hdl:
+ v4l2_ctrl_handler_free(&hdmirx_dev->hdl);
+err_pm:
+ hdmirx_disable(dev);
+
+ return ret;
+}
+
+static int hdmirx_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct snps_hdmirx_dev *hdmirx_dev = dev_get_drvdata(dev);
+
+ snps_hdmirx_cec_unregister(hdmirx_dev->cec);
+ cec_notifier_conn_unregister(hdmirx_dev->cec_notifier);
+
+ hdmirx_disable_irq(dev);
+
+ video_unregister_device(&hdmirx_dev->stream.vdev);
+ v4l2_ctrl_handler_free(&hdmirx_dev->hdl);
+ v4l2_device_unregister(&hdmirx_dev->v4l2_dev);
+
+ hdmirx_disable(dev);
+
+ reset_control_bulk_assert(HDMIRX_NUM_RST, hdmirx_dev->resets);
+
+ of_reserved_mem_device_release(dev);
+
+ return 0;
+}
+
+static const struct of_device_id hdmirx_id[] = {
+ { .compatible = "rockchip,rk3588-hdmirx-ctrler" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, hdmirx_id);
+
+static struct platform_driver hdmirx_driver = {
+ .probe = hdmirx_probe,
+ .remove = hdmirx_remove,
+ .driver = {
+ .name = "snps_hdmirx",
+ .of_match_table = hdmirx_id,
+ .pm = &snps_hdmirx_pm_ops,
+ }
+};
+module_platform_driver(hdmirx_driver);
+
+MODULE_DESCRIPTION("Rockchip HDMI Receiver Driver");
+MODULE_AUTHOR("Dingxian Wen <shawn.wen@rock-chips.com>");
+MODULE_AUTHOR("Shreeya Patel <shreeya.patel@collabora.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h
new file mode 100644
index 000000000000..220ab99ca611
--- /dev/null
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.h
@@ -0,0 +1,394 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
+ *
+ * Author: Dingxian Wen <shawn.wen@rock-chips.com>
+ */
+
+#ifndef DW_HDMIRX_H
+#define DW_HDMIRX_H
+
+#include <linux/bitops.h>
+
+#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l)))
+#define HIWORD_UPDATE(v, h, l) (((v) << (l)) | (GENMASK((h), (l)) << 16))
+
+/* SYS_GRF */
+#define SYS_GRF_SOC_CON1 0x0304
+#define HDMIRXPHY_SRAM_EXT_LD_DONE BIT(1)
+#define HDMIRXPHY_SRAM_BYPASS BIT(0)
+#define SYS_GRF_SOC_STATUS1 0x0384
+#define HDMIRXPHY_SRAM_INIT_DONE BIT(10)
+#define SYS_GRF_CHIP_ID 0x0600
+
+/* VO1_GRF */
+#define VO1_GRF_VO1_CON2 0x0008
+#define HDMIRX_SDAIN_MSK BIT(2)
+#define HDMIRX_SCLIN_MSK BIT(1)
+
+/* HDMIRX PHY */
+#define SUP_DIG_ANA_CREGS_SUP_ANA_NC 0x004f
+
+#define LANE0_DIG_ASIC_RX_OVRD_OUT_0 0x100f
+#define LANE1_DIG_ASIC_RX_OVRD_OUT_0 0x110f
+#define LANE2_DIG_ASIC_RX_OVRD_OUT_0 0x120f
+#define LANE3_DIG_ASIC_RX_OVRD_OUT_0 0x130f
+#define ASIC_ACK_OVRD_EN BIT(1)
+#define ASIC_ACK BIT(0)
+
+#define LANE0_DIG_RX_VCOCAL_RX_VCO_CAL_CTRL_2 0x104a
+#define LANE1_DIG_RX_VCOCAL_RX_VCO_CAL_CTRL_2 0x114a
+#define LANE2_DIG_RX_VCOCAL_RX_VCO_CAL_CTRL_2 0x124a
+#define LANE3_DIG_RX_VCOCAL_RX_VCO_CAL_CTRL_2 0x134a
+#define FREQ_TUNE_START_VAL_MASK GENMASK(9, 0)
+#define FREQ_TUNE_START_VAL(x) UPDATE(x, 9, 0)
+
+#define HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_FSM_CONFIG 0x20c4
+#define HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_ADAPT_REF_FOM 0x20c7
+#define HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_3_REG 0x20e9
+#define CDR_SETTING_BOUNDARY_3_DEFAULT 0x52da
+#define HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_4_REG 0x20ea
+#define CDR_SETTING_BOUNDARY_4_DEFAULT 0x43cd
+#define HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_5_REG 0x20eb
+#define CDR_SETTING_BOUNDARY_5_DEFAULT 0x35b3
+#define HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_6_REG 0x20fb
+#define CDR_SETTING_BOUNDARY_6_DEFAULT 0x2799
+#define HDMIPCS_DIG_CTRL_PATH_MAIN_FSM_RATE_CALC_HDMI14_CDR_SETTING_7_REG 0x20fc
+#define CDR_SETTING_BOUNDARY_7_DEFAULT 0x1b65
+
+#define RAWLANE0_DIG_PCS_XF_RX_OVRD_OUT 0x300e
+#define RAWLANE1_DIG_PCS_XF_RX_OVRD_OUT 0x310e
+#define RAWLANE2_DIG_PCS_XF_RX_OVRD_OUT 0x320e
+#define RAWLANE3_DIG_PCS_XF_RX_OVRD_OUT 0x330e
+#define PCS_ACK_WRITE_SELECT BIT(14)
+#define PCS_EN_CTL BIT(1)
+#define PCS_ACK BIT(0)
+
+#define RAWLANE0_DIG_AON_FAST_FLAGS 0x305c
+#define RAWLANE1_DIG_AON_FAST_FLAGS 0x315c
+#define RAWLANE2_DIG_AON_FAST_FLAGS 0x325c
+#define RAWLANE3_DIG_AON_FAST_FLAGS 0x335c
+
+/* HDMIRX Ctrler */
+#define GLOBAL_SWRESET_REQUEST 0x0020
+#define DATAPATH_SWRESETREQ BIT(12)
+#define GLOBAL_SWENABLE 0x0024
+#define PHYCTRL_ENABLE BIT(21)
+#define CEC_ENABLE BIT(16)
+#define TMDS_ENABLE BIT(13)
+#define DATAPATH_ENABLE BIT(12)
+#define PKTFIFO_ENABLE BIT(11)
+#define AVPUNIT_ENABLE BIT(8)
+#define MAIN_ENABLE BIT(0)
+#define GLOBAL_TIMER_REF_BASE 0x0028
+#define CORE_CONFIG 0x0050
+#define CMU_CONFIG0 0x0060
+#define TMDSQPCLK_STABLE_FREQ_MARGIN_MASK GENMASK(30, 16)
+#define TMDSQPCLK_STABLE_FREQ_MARGIN(x) UPDATE(x, 30, 16)
+#define AUDCLK_STABLE_FREQ_MARGIN_MASK GENMASK(11, 9)
+#define AUDCLK_STABLE_FREQ_MARGIN(x) UPDATE(x, 11, 9)
+#define CMU_STATUS 0x007c
+#define TMDSQPCLK_LOCKED_ST BIT(4)
+#define CMU_TMDSQPCLK_FREQ 0x0084
+#define PHY_CONFIG 0x00c0
+#define LDO_AFE_PROG_MASK GENMASK(24, 23)
+#define LDO_AFE_PROG(x) UPDATE(x, 24, 23)
+#define LDO_PWRDN BIT(21)
+#define TMDS_CLOCK_RATIO BIT(16)
+#define RXDATA_WIDTH BIT(15)
+#define REFFREQ_SEL_MASK GENMASK(11, 9)
+#define REFFREQ_SEL(x) UPDATE(x, 11, 9)
+#define HDMI_DISABLE BIT(8)
+#define PHY_PDDQ BIT(1)
+#define PHY_RESET BIT(0)
+#define PHY_STATUS 0x00c8
+#define HDMI_DISABLE_ACK BIT(1)
+#define PDDQ_ACK BIT(0)
+#define PHYCREG_CONFIG0 0x00e0
+#define PHYCREG_CR_PARA_SELECTION_MODE_MASK GENMASK(1, 0)
+#define PHYCREG_CR_PARA_SELECTION_MODE(x) UPDATE(x, 1, 0)
+#define PHYCREG_CONFIG1 0x00e4
+#define PHYCREG_CONFIG2 0x00e8
+#define PHYCREG_CONFIG3 0x00ec
+#define PHYCREG_CONTROL 0x00f0
+#define PHYCREG_CR_PARA_WRITE_P BIT(1)
+#define PHYCREG_CR_PARA_READ_P BIT(0)
+#define PHYCREG_STATUS 0x00f4
+
+#define MAINUNIT_STATUS 0x0150
+#define TMDSVALID_STABLE_ST BIT(1)
+#define DESCRAND_EN_CONTROL 0x0210
+#define SCRAMB_EN_SEL_QST_MASK GENMASK(1, 0)
+#define SCRAMB_EN_SEL_QST(x) UPDATE(x, 1, 0)
+#define DESCRAND_SYNC_CONTROL 0x0214
+#define RECOVER_UNSYNC_STREAM_QST BIT(0)
+#define DESCRAND_SYNC_SEQ_CONFIG 0x022c
+#define DESCRAND_SYNC_SEQ_ERR_CNT_EN BIT(0)
+#define DESCRAND_SYNC_SEQ_STATUS 0x0234
+#define DEFRAMER_CONFIG0 0x0270
+#define VS_CNT_THR_QST_MASK GENMASK(27, 20)
+#define VS_CNT_THR_QST(x) UPDATE(x, 27, 20)
+#define HS_POL_QST_MASK GENMASK(19, 18)
+#define HS_POL_QST(x) UPDATE(x, 19, 18)
+#define VS_POL_QST_MASK GENMASK(17, 16)
+#define VS_POL_QST(x) UPDATE(x, 17, 16)
+#define VS_REMAPFILTER_EN_QST BIT(8)
+#define VS_FILTER_ORDER_QST_MASK GENMASK(1, 0)
+#define VS_FILTER_ORDER_QST(x) UPDATE(x, 1, 0)
+#define DEFRAMER_VSYNC_CNT_CLEAR 0x0278
+#define VSYNC_CNT_CLR_P BIT(0)
+#define DEFRAMER_STATUS 0x027c
+#define OPMODE_STS_MASK GENMASK(6, 4)
+#define I2C_SLAVE_CONFIG1 0x0164
+#define I2C_SDA_OUT_HOLD_VALUE_QST_MASK GENMASK(15, 8)
+#define I2C_SDA_OUT_HOLD_VALUE_QST(x) UPDATE(x, 15, 8)
+#define I2C_SDA_IN_HOLD_VALUE_QST_MASK GENMASK(7, 0)
+#define I2C_SDA_IN_HOLD_VALUE_QST(x) UPDATE(x, 7, 0)
+#define OPMODE_STS_MASK GENMASK(6, 4)
+#define REPEATER_QST BIT(28)
+#define FASTREAUTH_QST BIT(27)
+#define FEATURES_1DOT1_QST BIT(26)
+#define FASTI2C_QST BIT(25)
+#define EESS_CTL_THR_QST_MASK GENMASK(19, 16)
+#define EESS_CTL_THR_QST(x) UPDATE(x, 19, 16)
+#define OESS_CTL3_THR_QST_MASK GENMASK(11, 8)
+#define OESS_CTL3_THR_QST(x) UPDATE(x, 11, 8)
+#define EESS_OESS_SEL_QST_MASK GENMASK(5, 4)
+#define EESS_OESS_SEL_QST(x) UPDATE(x, 5, 4)
+#define KEY_DECRYPT_EN_QST BIT(0)
+#define KEY_DECRYPT_SEED_QST_MASK GENMASK(15, 0)
+#define KEY_DECRYPT_SEED_QST(x) UPDATE(x, 15, 0)
+#define HDCP_INT_CLEAR 0x50d8
+#define HDCP_1_INT_CLEAR 0x50e8
+#define HDCP2_CONFIG 0x02f0
+#define HDCP2_SWITCH_OVR_VALUE BIT(2)
+#define HDCP2_SWITCH_OVR_EN BIT(1)
+
+#define VIDEO_CONFIG2 0x042c
+#define VPROC_VSYNC_POL_OVR_VALUE BIT(19)
+#define VPROC_VSYNC_POL_OVR_EN BIT(18)
+#define VPROC_HSYNC_POL_OVR_VALUE BIT(17)
+#define VPROC_HSYNC_POL_OVR_EN BIT(16)
+#define VPROC_FMT_OVR_VALUE_MASK GENMASK(6, 4)
+#define VPROC_FMT_OVR_VALUE(x) UPDATE(x, 6, 4)
+#define VPROC_FMT_OVR_EN BIT(0)
+
+#define AFIFO_FILL_RESTART BIT(0)
+#define AFIFO_INIT_P BIT(0)
+#define AFIFO_THR_LOW_QST_MASK GENMASK(25, 16)
+#define AFIFO_THR_LOW_QST(x) UPDATE(x, 25, 16)
+#define AFIFO_THR_HIGH_QST_MASK GENMASK(9, 0)
+#define AFIFO_THR_HIGH_QST(x) UPDATE(x, 9, 0)
+#define AFIFO_THR_MUTE_LOW_QST_MASK GENMASK(25, 16)
+#define AFIFO_THR_MUTE_LOW_QST(x) UPDATE(x, 25, 16)
+#define AFIFO_THR_MUTE_HIGH_QST_MASK GENMASK(9, 0)
+#define AFIFO_THR_MUTE_HIGH_QST(x) UPDATE(x, 9, 0)
+
+#define AFIFO_UNDERFLOW_ST BIT(25)
+#define AFIFO_OVERFLOW_ST BIT(24)
+
+#define SPEAKER_ALLOC_OVR_EN BIT(16)
+#define I2S_BPCUV_EN BIT(4)
+#define SPDIF_EN BIT(2)
+#define I2S_EN BIT(1)
+#define AFIFO_THR_PASS_DEMUTEMASK_N BIT(24)
+#define AVMUTE_DEMUTEMASK_N BIT(16)
+#define AFIFO_THR_MUTE_LOW_MUTEMASK_N BIT(9)
+#define AFIFO_THR_MUTE_HIGH_MUTEMASK_N BIT(8)
+#define AVMUTE_MUTEMASK_N BIT(0)
+#define SCDC_CONFIG 0x0580
+#define HPDLOW BIT(1)
+#define POWERPROVIDED BIT(0)
+#define SCDC_REGBANK_STATUS1 0x058c
+#define SCDC_TMDSBITCLKRATIO BIT(1)
+#define SCDC_REGBANK_STATUS3 0x0594
+#define SCDC_REGBANK_CONFIG0 0x05c0
+#define SCDC_SINKVERSION_QST_MASK GENMASK(7, 0)
+#define SCDC_SINKVERSION_QST(x) UPDATE(x, 7, 0)
+#define AGEN_LAYOUT BIT(4)
+#define AGEN_SPEAKER_ALLOC GENMASK(15, 8)
+
+#define CED_CONFIG 0x0760
+#define CED_VIDDATACHECKEN_QST BIT(27)
+#define CED_DATAISCHECKEN_QST BIT(26)
+#define CED_GBCHECKEN_QST BIT(25)
+#define CED_CTRLCHECKEN_QST BIT(24)
+#define CED_CHLOCKMAXER_QST_MASK GENMASK(14, 0)
+#define CED_CHLOCKMAXER_QST(x) UPDATE(x, 14, 0)
+#define CED_DYN_CONFIG 0x0768
+#define CED_DYN_CONTROL 0x076c
+#define PKTEX_BCH_ERRFILT_CONFIG 0x07c4
+#define PKTEX_CHKSUM_ERRFILT_CONFIG 0x07c8
+
+#define PKTDEC_ACR_PH2_1 0x1100
+#define PKTDEC_ACR_PB3_0 0x1104
+#define PKTDEC_ACR_PB7_4 0x1108
+#define PKTDEC_AVIIF_PH2_1 0x1200
+#define PKTDEC_AVIIF_PB3_0 0x1204
+#define PKTDEC_AVIIF_PB7_4 0x1208
+#define VIC_VAL_MASK GENMASK(6, 0)
+#define PKTDEC_AVIIF_PB11_8 0x120c
+#define PKTDEC_AVIIF_PB15_12 0x1210
+#define PKTDEC_AVIIF_PB19_16 0x1214
+#define PKTDEC_AVIIF_PB23_20 0x1218
+#define PKTDEC_AVIIF_PB27_24 0x121c
+
+#define PKTFIFO_CONFIG 0x1500
+#define PKTFIFO_STORE_FILT_CONFIG 0x1504
+#define PKTFIFO_THR_CONFIG0 0x1508
+#define PKTFIFO_THR_CONFIG1 0x150c
+#define PKTFIFO_CONTROL 0x1510
+
+#define VMON_STATUS1 0x1580
+#define VMON_STATUS2 0x1584
+#define VMON_STATUS3 0x1588
+#define VMON_STATUS4 0x158c
+#define VMON_STATUS5 0x1590
+#define VMON_STATUS6 0x1594
+#define VMON_STATUS7 0x1598
+#define VMON_ILACE_DETECT BIT(4)
+
+#define CEC_TX_CONTROL 0x2000
+#define CEC_STATUS 0x2004
+#define CEC_CONFIG 0x2008
+#define RX_AUTO_DRIVE_ACKNOWLEDGE BIT(9)
+#define CEC_ADDR 0x200c
+#define CEC_TX_COUNT 0x2020
+#define CEC_TX_DATA3_0 0x2024
+#define CEC_RX_COUNT_STATUS 0x2040
+#define CEC_RX_DATA3_0 0x2044
+#define CEC_LOCK_CONTROL 0x2054
+#define CEC_RXQUAL_BITTIME_CONFIG 0x2060
+#define CEC_RX_BITTIME_CONFIG 0x2064
+#define CEC_TX_BITTIME_CONFIG 0x2068
+
+#define DMA_CONFIG1 0x4400
+#define UV_WID_MASK GENMASK(31, 28)
+#define UV_WID(x) UPDATE(x, 31, 28)
+#define Y_WID_MASK GENMASK(27, 24)
+#define Y_WID(x) UPDATE(x, 27, 24)
+#define DDR_STORE_FORMAT_MASK GENMASK(15, 12)
+#define DDR_STORE_FORMAT(x) UPDATE(x, 15, 12)
+#define ABANDON_EN BIT(0)
+#define DMA_CONFIG2 0x4404
+#define DMA_CONFIG3 0x4408
+#define DMA_CONFIG4 0x440c // dma irq en
+#define DMA_CONFIG5 0x4410 // dma irq clear status
+#define LINE_FLAG_INT_EN BIT(8)
+#define HDMIRX_DMA_IDLE_INT BIT(7)
+#define HDMIRX_LOCK_DISABLE_INT BIT(6)
+#define LAST_FRAME_AXI_UNFINISH_INT_EN BIT(5)
+#define FIFO_OVERFLOW_INT_EN BIT(2)
+#define FIFO_UNDERFLOW_INT_EN BIT(1)
+#define HDMIRX_AXI_ERROR_INT_EN BIT(0)
+#define DMA_CONFIG6 0x4414
+#define RB_SWAP_EN BIT(9)
+#define HSYNC_TOGGLE_EN BIT(5)
+#define VSYNC_TOGGLE_EN BIT(4)
+#define HDMIRX_DMA_EN BIT(1)
+#define DMA_CONFIG7 0x4418
+#define LINE_FLAG_NUM_MASK GENMASK(31, 16)
+#define LINE_FLAG_NUM(x) UPDATE(x, 31, 16)
+#define LOCK_FRAME_NUM_MASK GENMASK(11, 0)
+#define LOCK_FRAME_NUM(x) UPDATE(x, 11, 0)
+#define DMA_CONFIG8 0x441c
+#define REG_MIRROR_EN BIT(0)
+#define DMA_CONFIG9 0x4420
+#define DMA_CONFIG10 0x4424
+#define DMA_CONFIG11 0x4428
+#define EDID_READ_EN_MASK BIT(8)
+#define EDID_READ_EN(x) UPDATE(x, 8, 8)
+#define EDID_WRITE_EN_MASK BIT(7)
+#define EDID_WRITE_EN(x) UPDATE(x, 7, 7)
+#define EDID_SLAVE_ADDR_MASK GENMASK(6, 0)
+#define EDID_SLAVE_ADDR(x) UPDATE(x, 6, 0)
+#define DMA_STATUS1 0x4430 // dma irq status
+#define DMA_STATUS2 0x4434
+#define DMA_STATUS3 0x4438
+#define DMA_STATUS4 0x443c
+#define DMA_STATUS5 0x4440
+#define DMA_STATUS6 0x4444
+#define DMA_STATUS7 0x4448
+#define DMA_STATUS8 0x444c
+#define DMA_STATUS9 0x4450
+#define DMA_STATUS10 0x4454
+#define HDMIRX_LOCK BIT(3)
+#define DMA_STATUS11 0x4458
+#define HDMIRX_TYPE_MASK GENMASK(8, 7)
+#define HDMIRX_COLOR_DEPTH_MASK GENMASK(6, 3)
+#define HDMIRX_FORMAT_MASK GENMASK(2, 0)
+#define DMA_STATUS12 0x445c
+#define DMA_STATUS13 0x4460
+#define DMA_STATUS14 0x4464
+
+#define MAINUNIT_INTVEC_INDEX 0x5000
+#define MAINUNIT_0_INT_STATUS 0x5010
+#define CECRX_NOTIFY_ERR BIT(12)
+#define CECRX_EOM BIT(11)
+#define CECTX_DRIVE_ERR BIT(10)
+#define CECRX_BUSY BIT(9)
+#define CECTX_BUSY BIT(8)
+#define CECTX_FRAME_DISCARDED BIT(5)
+#define CECTX_NRETRANSMIT_FAIL BIT(4)
+#define CECTX_LINE_ERR BIT(3)
+#define CECTX_ARBLOST BIT(2)
+#define CECTX_NACK BIT(1)
+#define CECTX_DONE BIT(0)
+#define MAINUNIT_0_INT_MASK_N 0x5014
+#define MAINUNIT_0_INT_CLEAR 0x5018
+#define MAINUNIT_0_INT_FORCE 0x501c
+#define TIMER_BASE_LOCKED_IRQ BIT(26)
+#define TMDSQPCLK_OFF_CHG BIT(5)
+#define TMDSQPCLK_LOCKED_CHG BIT(4)
+#define MAINUNIT_1_INT_STATUS 0x5020
+#define MAINUNIT_1_INT_MASK_N 0x5024
+#define MAINUNIT_1_INT_CLEAR 0x5028
+#define MAINUNIT_1_INT_FORCE 0x502c
+#define MAINUNIT_2_INT_STATUS 0x5030
+#define MAINUNIT_2_INT_MASK_N 0x5034
+#define MAINUNIT_2_INT_CLEAR 0x5038
+#define MAINUNIT_2_INT_FORCE 0x503c
+#define PHYCREG_CR_READ_DONE BIT(11)
+#define PHYCREG_CR_WRITE_DONE BIT(10)
+#define TMDSVALID_STABLE_CHG BIT(1)
+
+#define AVPUNIT_0_INT_STATUS 0x5040
+#define AVPUNIT_0_INT_MASK_N 0x5044
+#define AVPUNIT_0_INT_CLEAR 0x5048
+#define AVPUNIT_0_INT_FORCE 0x504c
+#define CED_DYN_CNT_CH2_IRQ BIT(22)
+#define CED_DYN_CNT_CH1_IRQ BIT(21)
+#define CED_DYN_CNT_CH0_IRQ BIT(20)
+#define AVPUNIT_1_INT_STATUS 0x5050
+#define DEFRAMER_VSYNC_THR_REACHED_IRQ BIT(1)
+#define AVPUNIT_1_INT_MASK_N 0x5054
+#define DEFRAMER_VSYNC_THR_REACHED_MASK_N BIT(1)
+#define DEFRAMER_VSYNC_MASK_N BIT(0)
+#define AVPUNIT_1_INT_CLEAR 0x5058
+#define DEFRAMER_VSYNC_THR_REACHED_CLEAR BIT(1)
+#define PKT_0_INT_STATUS 0x5080
+#define PKTDEC_ACR_CHG_IRQ BIT(3)
+#define PKT_0_INT_MASK_N 0x5084
+#define PKTDEC_ACR_CHG_MASK_N BIT(3)
+#define PKT_0_INT_CLEAR 0x5088
+#define PKT_1_INT_STATUS 0x5090
+#define PKT_1_INT_MASK_N 0x5094
+#define PKT_1_INT_CLEAR 0x5098
+#define PKT_2_INT_STATUS 0x50a0
+#define PKTDEC_ACR_RCV_IRQ BIT(3)
+#define PKT_2_INT_MASK_N 0x50a4
+#define PKTDEC_AVIIF_RCV_IRQ BIT(11)
+#define PKTDEC_ACR_RCV_MASK_N BIT(3)
+#define PKT_2_INT_CLEAR 0x50a8
+#define PKTDEC_AVIIF_RCV_CLEAR BIT(11)
+#define PKTDEC_ACR_RCV_CLEAR BIT(3)
+#define SCDC_INT_STATUS 0x50c0
+#define SCDC_INT_MASK_N 0x50c4
+#define SCDC_INT_CLEAR 0x50c8
+#define SCDCTMDSCCFG_CHG BIT(2)
+
+#define CEC_INT_STATUS 0x5100
+#define CEC_INT_MASK_N 0x5104
+#define CEC_INT_CLEAR 0x5108
+
+#endif
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.c
new file mode 100644
index 000000000000..8554cbc4ccde
--- /dev/null
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
+ *
+ * Author: Shunqing Chen <csq@rock-chips.com>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <media/cec.h>
+#include <media/cec-notifier.h>
+
+#include "snps_hdmirx.h"
+#include "snps_hdmirx_cec.h"
+
+static void hdmirx_cec_write(struct hdmirx_cec *cec, int reg, u32 val)
+{
+ cec->ops->write(cec->hdmirx, reg, val);
+}
+
+static u32 hdmirx_cec_read(struct hdmirx_cec *cec, int reg)
+{
+ return cec->ops->read(cec->hdmirx, reg);
+}
+
+static void hdmirx_cec_update_bits(struct hdmirx_cec *cec, int reg, u32 mask,
+ u32 data)
+{
+ u32 val = hdmirx_cec_read(cec, reg) & ~mask;
+
+ val |= (data & mask);
+ hdmirx_cec_write(cec, reg, val);
+}
+
+static int hdmirx_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
+{
+ struct hdmirx_cec *cec = cec_get_drvdata(adap);
+
+ if (logical_addr == CEC_LOG_ADDR_INVALID)
+ cec->addresses = 0;
+ else
+ cec->addresses |= BIT(logical_addr) | BIT(15);
+
+ hdmirx_cec_write(cec, CEC_ADDR, cec->addresses);
+
+ return 0;
+}
+
+static int hdmirx_cec_transmit(struct cec_adapter *adap, u8 attempts,
+ u32 signal_free_time, struct cec_msg *msg)
+{
+ struct hdmirx_cec *cec = cec_get_drvdata(adap);
+ u32 data[4] = {0};
+ int i, data_len, msg_len;
+
+ msg_len = msg->len;
+ if (msg->len > 16)
+ msg_len = 16;
+ if (msg_len <= 0)
+ return 0;
+
+ hdmirx_cec_write(cec, CEC_TX_COUNT, msg_len - 1);
+ for (i = 0; i < msg_len; i++)
+ data[i / 4] |= msg->msg[i] << (i % 4) * 8;
+
+ data_len = msg_len / 4 + 1;
+ for (i = 0; i < data_len; i++)
+ hdmirx_cec_write(cec, CEC_TX_DATA3_0 + i * 4, data[i]);
+
+ hdmirx_cec_write(cec, CEC_TX_CONTROL, 0x1);
+
+ return 0;
+}
+
+static irqreturn_t hdmirx_cec_hardirq(int irq, void *data)
+{
+ struct cec_adapter *adap = data;
+ struct hdmirx_cec *cec = cec_get_drvdata(adap);
+ u32 stat = hdmirx_cec_read(cec, CEC_INT_STATUS);
+ irqreturn_t ret = IRQ_HANDLED;
+ u32 val;
+
+ if (!stat)
+ return IRQ_NONE;
+
+ hdmirx_cec_write(cec, CEC_INT_CLEAR, stat);
+
+ if (stat & CECTX_LINE_ERR) {
+ cec->tx_status = CEC_TX_STATUS_ERROR;
+ cec->tx_done = true;
+ ret = IRQ_WAKE_THREAD;
+ } else if (stat & CECTX_DONE) {
+ cec->tx_status = CEC_TX_STATUS_OK;
+ cec->tx_done = true;
+ ret = IRQ_WAKE_THREAD;
+ } else if (stat & CECTX_NACK) {
+ cec->tx_status = CEC_TX_STATUS_NACK;
+ cec->tx_done = true;
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ if (stat & CECRX_EOM) {
+ unsigned int len, i;
+
+ val = hdmirx_cec_read(cec, CEC_RX_COUNT_STATUS);
+ /* rxbuffer locked status */
+ if ((val & 0x80))
+ return ret;
+
+ len = (val & 0xf) + 1;
+ if (len > sizeof(cec->rx_msg.msg))
+ len = sizeof(cec->rx_msg.msg);
+
+ for (i = 0; i < len; i++) {
+ if (!(i % 4))
+ val = hdmirx_cec_read(cec, CEC_RX_DATA3_0 + i / 4 * 4);
+ cec->rx_msg.msg[i] = (val >> ((i % 4) * 8)) & 0xff;
+ }
+
+ cec->rx_msg.len = len;
+ smp_wmb(); /* receive RX msg */
+ cec->rx_done = true;
+ hdmirx_cec_write(cec, CEC_LOCK_CONTROL, 0x1);
+
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ return ret;
+}
+
+static irqreturn_t hdmirx_cec_thread(int irq, void *data)
+{
+ struct cec_adapter *adap = data;
+ struct hdmirx_cec *cec = cec_get_drvdata(adap);
+
+ if (cec->tx_done) {
+ cec->tx_done = false;
+ cec_transmit_attempt_done(adap, cec->tx_status);
+ }
+ if (cec->rx_done) {
+ cec->rx_done = false;
+ smp_rmb(); /* RX msg has been received */
+ cec_received_msg(adap, &cec->rx_msg);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int hdmirx_cec_enable(struct cec_adapter *adap, bool enable)
+{
+ struct hdmirx_cec *cec = cec_get_drvdata(adap);
+
+ if (!enable) {
+ hdmirx_cec_write(cec, CEC_INT_MASK_N, 0);
+ hdmirx_cec_write(cec, CEC_INT_CLEAR, 0);
+ if (cec->ops->disable)
+ cec->ops->disable(cec->hdmirx);
+ } else {
+ unsigned int irqs;
+
+ hdmirx_cec_log_addr(cec->adap, CEC_LOG_ADDR_INVALID);
+ if (cec->ops->enable)
+ cec->ops->enable(cec->hdmirx);
+ hdmirx_cec_update_bits(cec, GLOBAL_SWENABLE, CEC_ENABLE, CEC_ENABLE);
+
+ irqs = CECTX_LINE_ERR | CECTX_NACK | CECRX_EOM | CECTX_DONE;
+ hdmirx_cec_write(cec, CEC_INT_MASK_N, irqs);
+ }
+
+ return 0;
+}
+
+static const struct cec_adap_ops hdmirx_cec_ops = {
+ .adap_enable = hdmirx_cec_enable,
+ .adap_log_addr = hdmirx_cec_log_addr,
+ .adap_transmit = hdmirx_cec_transmit,
+};
+
+static void hdmirx_cec_del(void *data)
+{
+ struct hdmirx_cec *cec = data;
+
+ cec_delete_adapter(cec->adap);
+}
+
+struct hdmirx_cec *snps_hdmirx_cec_register(struct hdmirx_cec_data *data)
+{
+ struct hdmirx_cec *cec;
+ unsigned int irqs;
+ int ret;
+
+ if (!data)
+ return NULL;
+
+ /*
+ * Our device is just a convenience - we want to link to the real
+ * hardware device here, so that userspace can see the association
+ * between the HDMI hardware and its associated CEC chardev.
+ */
+ cec = devm_kzalloc(data->dev, sizeof(*cec), GFP_KERNEL);
+ if (!cec)
+ return NULL;
+
+ cec->dev = data->dev;
+ cec->irq = data->irq;
+ cec->ops = data->ops;
+ cec->hdmirx = data->hdmirx;
+ cec->edid = (struct edid *)data->edid;
+
+ hdmirx_cec_update_bits(cec, GLOBAL_SWENABLE, CEC_ENABLE, CEC_ENABLE);
+ hdmirx_cec_update_bits(cec, CEC_CONFIG, RX_AUTO_DRIVE_ACKNOWLEDGE,
+ RX_AUTO_DRIVE_ACKNOWLEDGE);
+
+ hdmirx_cec_write(cec, CEC_TX_COUNT, 0);
+ hdmirx_cec_write(cec, CEC_INT_MASK_N, 0);
+ hdmirx_cec_write(cec, CEC_INT_CLEAR, ~0);
+
+ cec->adap = cec_allocate_adapter(&hdmirx_cec_ops, cec, "rk-hdmirx",
+ CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT |
+ CEC_CAP_RC | CEC_CAP_PASSTHROUGH,
+ CEC_MAX_LOG_ADDRS);
+ if (IS_ERR(cec->adap)) {
+ dev_err(cec->dev, "cec adap allocate failed\n");
+ return NULL;
+ }
+
+ /* override the module pointer */
+ cec->adap->owner = THIS_MODULE;
+
+ ret = devm_add_action(cec->dev, hdmirx_cec_del, cec);
+ if (ret) {
+ cec_delete_adapter(cec->adap);
+ return NULL;
+ }
+
+ irq_set_status_flags(cec->irq, IRQ_NOAUTOEN);
+
+ ret = devm_request_threaded_irq(cec->dev, cec->irq,
+ hdmirx_cec_hardirq,
+ hdmirx_cec_thread, IRQF_ONESHOT,
+ "rk_hdmirx_cec", cec->adap);
+ if (ret) {
+ dev_err(cec->dev, "cec irq request failed\n");
+ return NULL;
+ }
+
+ cec->notify = cec_notifier_cec_adap_register(cec->dev,
+ NULL, cec->adap);
+ if (!cec->notify) {
+ dev_err(cec->dev, "cec notify register failed\n");
+ return NULL;
+ }
+
+ ret = cec_register_adapter(cec->adap, cec->dev);
+ if (ret < 0) {
+ dev_err(cec->dev, "cec register adapter failed\n");
+ cec_unregister_adapter(cec->adap);
+ return NULL;
+ }
+
+ cec_s_phys_addr_from_edid(cec->adap, cec->edid);
+
+ irqs = CECTX_LINE_ERR | CECTX_NACK | CECRX_EOM | CECTX_DONE;
+ hdmirx_cec_write(cec, CEC_INT_MASK_N, irqs);
+
+ /*
+ * CEC documentation says we must not call cec_delete_adapter
+ * after a successful call to cec_register_adapter().
+ */
+ devm_remove_action(cec->dev, hdmirx_cec_del, cec);
+
+ enable_irq(cec->irq);
+
+ return cec;
+}
+
+void snps_hdmirx_cec_unregister(struct hdmirx_cec *cec)
+{
+ if (!cec)
+ return;
+
+ disable_irq(cec->irq);
+
+ cec_unregister_adapter(cec->adap);
+}
diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.h b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.h
new file mode 100644
index 000000000000..ae43f74d471d
--- /dev/null
+++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx_cec.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
+ *
+ * Author: Shunqing Chen <csq@rock-chips.com>
+ */
+
+#ifndef DW_HDMI_RX_CEC_H
+#define DW_HDMI_RX_CEC_H
+
+struct snps_hdmirx_dev;
+
+struct hdmirx_cec_ops {
+ void (*write)(struct snps_hdmirx_dev *hdmirx_dev, int reg, u32 val);
+ u32 (*read)(struct snps_hdmirx_dev *hdmirx_dev, int reg);
+ void (*enable)(struct snps_hdmirx_dev *hdmirx);
+ void (*disable)(struct snps_hdmirx_dev *hdmirx);
+};
+
+struct hdmirx_cec_data {
+ struct snps_hdmirx_dev *hdmirx;
+ const struct hdmirx_cec_ops *ops;
+ struct device *dev;
+ int irq;
+ u8 *edid;
+};
+
+struct hdmirx_cec {
+ struct snps_hdmirx_dev *hdmirx;
+ struct device *dev;
+ const struct hdmirx_cec_ops *ops;
+ u32 addresses;
+ struct cec_adapter *adap;
+ struct cec_msg rx_msg;
+ unsigned int tx_status;
+ bool tx_done;
+ bool rx_done;
+ struct cec_notifier *notify;
+ int irq;
+ struct edid *edid;
+};
+
+struct hdmirx_cec *snps_hdmirx_cec_register(struct hdmirx_cec_data *data);
+void snps_hdmirx_cec_unregister(struct hdmirx_cec *cec);
+
+#endif /* DW_HDMI_RX_CEC_H */
--
2.44.1
From d3f4d04e3feba524c6e2fa0a2f8e25739a936396 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Wed, 15 May 2024 18:30:14 +0200
Subject: [PATCH 25/54] usb: typec: tcpm: print error on hard reset
A USB-C hard reset involves removing the voltage from VBUS for some
time. So basically it has the same effect as removing the USB-C plug
for a short moment. If the machine is powered from the USB-C port and
does not have a fallback supply (e.g. a battery), this will result in
a full machine reset due to power loss.
Ideally we want to avoid triggering a hard reset on these boards. A
non-working USB-C port is probably better than unplanned reboots. But
boards with a backup supply should do the hard reset to get everything
working again.
In theory it would be enough to check the self_powered property, but
it seems the property might not be configured consistently enough in
system firmwares.
So let's start with just printing an error message when a hard reset is
triggered on systems we expect to be affected. This at least makes
debugging issues on affected systems easier without impacting unaffected
systems too much.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/usb/typec/tcpm/tcpm.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 8a1af08f71b6..375bc84d14a2 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -5185,6 +5185,8 @@ static void run_state_machine(struct tcpm_port *port)
case HARD_RESET_SEND:
if (port->ams != NONE_AMS)
tcpm_ams_finish(port);
+ if (!port->self_powered && port->port_type == TYPEC_PORT_SNK)
+ dev_err(port->dev, "Initiating hard-reset, which might result in machine power-loss.\n");
/*
* State machine will be directed to HARD_RESET_START,
* thus set upcoming_state to INVALID_STATE.
--
2.44.1
From e394b2e700e3fe42e9240f1e1640b2e9c715f179 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel <sebastian.reichel@collabora.com>
Date: Tue, 14 May 2024 17:29:32 +0200
Subject: [PATCH 26/54] usb: typec: tcpm: avoid resets for missing source
capability messages
When the Linux Type-C controller drivers probe, they requests a soft
reset, which should result in the source restarting to send Source
Capability messages again independently of the previous state.
Unfortunately some USB PD sources do not follow the specification and
do not send them after a soft reset when they already negotiated a
specific contract before. The current way (and what is described in the
specificiation) to resolve this problem is triggering a hard reset.
But a hard reset is fatal on batteryless platforms powered via USB-C PD,
since that removes VBUS for some time. Since this is triggered at boot
time, the system will be stuck in a boot loop. Examples for platforms
affected by this are the Radxa Rock 5B or the Libre Computer Renegade
Elite ROC-RK3399-PC.
Instead of directly trying a hard reset when no Source Capability
message is send by the USB-PD source automatically, this changes the
state machine to try explicitly asking for the capabilities by sending
a Get Source Capability control message.
For me this solves issues with 2 different USB-PD sources - a RAVPower
powerbank and a Lemorele USB-C dock. Every other PD source I own
follows the specification and automatically sends the Source Capability
message after a soft reset, which works with or without this change.
I decided against making this extra step limited to devices not having
the self_powered flag set, since I don't see any huge drawbacks in this
approach and it keeps the logic simpler. The worst case scenario would
be a power source, which is really stuck. In that case the hard reset
is delayed by another 310ms.
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/usb/typec/tcpm/tcpm.c | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 375bc84d14a2..bac6866617c8 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -57,6 +57,7 @@
S(SNK_DISCOVERY_DEBOUNCE), \
S(SNK_DISCOVERY_DEBOUNCE_DONE), \
S(SNK_WAIT_CAPABILITIES), \
+ S(SNK_WAIT_CAPABILITIES_TIMEOUT), \
S(SNK_NEGOTIATE_CAPABILITIES), \
S(SNK_NEGOTIATE_PPS_CAPABILITIES), \
S(SNK_TRANSITION_SINK), \
@@ -3108,7 +3109,8 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
PD_MSG_CTRL_REJECT :
PD_MSG_CTRL_NOT_SUPP,
NONE_AMS);
- } else if (port->state == SNK_WAIT_CAPABILITIES) {
+ } else if (port->state == SNK_WAIT_CAPABILITIES ||
+ port->state == SNK_WAIT_CAPABILITIES_TIMEOUT) {
/*
* This message may be received even if VBUS is not
* present. This is quite unexpected; see USB PD
@@ -5039,10 +5041,31 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_state(port, SNK_SOFT_RESET,
PD_T_SINK_WAIT_CAP);
} else {
- tcpm_set_state(port, hard_reset_state(port),
+ tcpm_set_state(port, SNK_WAIT_CAPABILITIES_TIMEOUT,
PD_T_SINK_WAIT_CAP);
}
break;
+ case SNK_WAIT_CAPABILITIES_TIMEOUT:
+ /*
+ * There are some USB PD sources in the field, which do not
+ * properly implement the specification and fail to start
+ * sending Source Capability messages after a soft reset. The
+ * specification suggests to do a hard reset when no Source
+ * capability message is received within PD_T_SINK_WAIT_CAP,
+ * but that might effectively kil the machine's power source.
+ *
+ * This slightly diverges from the specification and tries to
+ * recover from this by explicitly asking for the capabilities
+ * using the Get_Source_Cap control message before falling back
+ * to a hard reset. The control message should also be supported
+ * and handled by all USB PD source and dual role devices
+ * according to the specification.
+ */
+ if (tcpm_pd_send_control(port, PD_CTRL_GET_SOURCE_CAP, TCPC_TX_SOP))
+ tcpm_set_state_cond(port, hard_reset_state(port), 0);
+ else
+ tcpm_set_state(port, hard_reset_state(port), PD_T_SINK_WAIT_CAP);
+ break;
case SNK_NEGOTIATE_CAPABILITIES:
port->pd_capable = true;
tcpm_set_partner_usb_comm_capable(port,
--
2.44.1
From d39959cd76c8f219df1de5ba951dec83cdb5c288 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Mon, 5 Feb 2024 01:38:48 +0200
Subject: [PATCH 27/54] [WIP] phy: phy-rockchip-samsung-hdptx: Add FRL & EARC
support
For upstreaming, this requires extending the standard PHY API to support
HDMI configuration options [1].
Currently, the bus_width PHY attribute is used to pass clock rate and
flags for 10-bit color depth, FRL and EARC. This is done by the HDMI
bridge driver via phy_set_bus_width().
[1]: https://lore.kernel.org/all/59d5595a24bbcca897e814440179fa2caf3dff38.1707040881.git.Sandor.yu@nxp.com/
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../phy/rockchip/phy-rockchip-samsung-hdptx.c | 434 +++++++++++++++++-
1 file changed, 431 insertions(+), 3 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 946c01210ac8..44acea3f86af 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -190,6 +190,12 @@
#define LN3_TX_SER_RATE_SEL_HBR2 BIT(3)
#define LN3_TX_SER_RATE_SEL_HBR3 BIT(2)
+#define HDMI20_MAX_RATE 600000000
+#define DATA_RATE_MASK 0xFFFFFFF
+#define COLOR_DEPTH_MASK BIT(31)
+#define HDMI_MODE_MASK BIT(30)
+#define HDMI_EARC_MASK BIT(29)
+
struct lcpll_config {
u32 bit_rate;
u8 lcvco_mode_en;
@@ -272,6 +278,25 @@ struct rk_hdptx_phy {
struct clk_bulk_data *clks;
int nr_clks;
struct reset_control_bulk_data rsts[RST_MAX];
+ bool earc_en;
+};
+
+static const struct lcpll_config lcpll_cfg[] = {
+ { 48000000, 1, 0, 0, 0x7d, 0x7d, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 2,
+ 0, 0x13, 0x18, 1, 0, 0x20, 0x0c, 1, 0, },
+ { 40000000, 1, 1, 0, 0x68, 0x68, 1, 1, 0, 0, 0, 1, 1, 1, 1, 9, 0, 1, 1,
+ 0, 2, 3, 1, 0, 0x20, 0x0c, 1, 0, },
+ { 32000000, 1, 1, 1, 0x6b, 0x6b, 1, 1, 0, 1, 2, 1, 1, 1, 1, 9, 1, 2, 1,
+ 0, 0x0d, 0x18, 1, 0, 0x20, 0x0c, 1, 1, },
+};
+
+static const struct ropll_config ropll_frl_cfg[] = {
+ { 24000000, 0x19, 0x19, 1, 1, 0, 1, 2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1,
+ 0, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+ { 18000000, 0x7d, 0x7d, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1,
+ 0, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
+ { 9000000, 0x7d, 0x7d, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1,
+ 0, 0, 0x20, 0x0c, 1, 0x0e, 0, 0, },
};
static const struct ropll_config ropll_tmds_cfg[] = {
@@ -449,6 +474,73 @@ static const struct reg_sequence rk_hdtpx_tmds_cmn_init_seq[] = {
REG_SEQ0(CMN_REG(009b), 0x00),
};
+static const struct reg_sequence rk_hdtpx_frl_cmn_init_seq[] = {
+ REG_SEQ0(CMN_REG(0011), 0x00),
+ REG_SEQ0(CMN_REG(0017), 0x00),
+ REG_SEQ0(CMN_REG(0026), 0x53),
+ REG_SEQ0(CMN_REG(0030), 0x00),
+ REG_SEQ0(CMN_REG(0031), 0x20),
+ REG_SEQ0(CMN_REG(0032), 0x30),
+ REG_SEQ0(CMN_REG(0033), 0x0b),
+ REG_SEQ0(CMN_REG(0034), 0x23),
+ REG_SEQ0(CMN_REG(0042), 0xb8),
+ REG_SEQ0(CMN_REG(004e), 0x14),
+ REG_SEQ0(CMN_REG(0074), 0x00),
+ REG_SEQ0(CMN_REG(0081), 0x09),
+ REG_SEQ0(CMN_REG(0086), 0x01),
+ REG_SEQ0(CMN_REG(0087), 0x0c),
+ REG_SEQ0(CMN_REG(009b), 0x10),
+};
+
+static const struct reg_sequence rk_hdtpx_frl_ropll_cmn_init_seq[] = {
+ REG_SEQ0(CMN_REG(0008), 0x00),
+ REG_SEQ0(CMN_REG(001e), 0x14),
+ REG_SEQ0(CMN_REG(0020), 0x00),
+ REG_SEQ0(CMN_REG(0021), 0x00),
+ REG_SEQ0(CMN_REG(0022), 0x11),
+ REG_SEQ0(CMN_REG(0023), 0x00),
+ REG_SEQ0(CMN_REG(0025), 0x00),
+ REG_SEQ0(CMN_REG(0027), 0x00),
+ REG_SEQ0(CMN_REG(0028), 0x00),
+ REG_SEQ0(CMN_REG(002a), 0x01),
+ REG_SEQ0(CMN_REG(002b), 0x00),
+ REG_SEQ0(CMN_REG(002c), 0x00),
+ REG_SEQ0(CMN_REG(002d), 0x00),
+ REG_SEQ0(CMN_REG(002e), 0x00),
+ REG_SEQ0(CMN_REG(002f), 0x04),
+ REG_SEQ0(CMN_REG(003d), 0x40),
+ REG_SEQ0(CMN_REG(005c), 0x25),
+ REG_SEQ0(CMN_REG(0089), 0x00),
+ REG_SEQ0(CMN_REG(0094), 0x00),
+ REG_SEQ0(CMN_REG(0097), 0x02),
+ REG_SEQ0(CMN_REG(0099), 0x04),
+};
+
+static const struct reg_sequence rk_hdtpx_frl_lcpll_cmn_init_seq[] = {
+ REG_SEQ0(CMN_REG(0025), 0x10),
+ REG_SEQ0(CMN_REG(0027), 0x01),
+ REG_SEQ0(CMN_REG(0028), 0x0d),
+ REG_SEQ0(CMN_REG(002e), 0x02),
+ REG_SEQ0(CMN_REG(002f), 0x0d),
+ REG_SEQ0(CMN_REG(003d), 0x00),
+ REG_SEQ0(CMN_REG(0051), 0x00),
+ REG_SEQ0(CMN_REG(0055), 0x00),
+ REG_SEQ0(CMN_REG(0059), 0x11),
+ REG_SEQ0(CMN_REG(005a), 0x03),
+ REG_SEQ0(CMN_REG(005c), 0x05),
+ REG_SEQ0(CMN_REG(005e), 0x07),
+ REG_SEQ0(CMN_REG(0060), 0x01),
+ REG_SEQ0(CMN_REG(0064), 0x07),
+ REG_SEQ0(CMN_REG(0065), 0x00),
+ REG_SEQ0(CMN_REG(0069), 0x00),
+ REG_SEQ0(CMN_REG(006c), 0x00),
+ REG_SEQ0(CMN_REG(0070), 0x01),
+ REG_SEQ0(CMN_REG(0089), 0x02),
+ REG_SEQ0(CMN_REG(0095), 0x00),
+ REG_SEQ0(CMN_REG(0097), 0x00),
+ REG_SEQ0(CMN_REG(0099), 0x00),
+};
+
static const struct reg_sequence rk_hdtpx_common_sb_init_seq[] = {
REG_SEQ0(SB_REG(0114), 0x00),
REG_SEQ0(SB_REG(0115), 0x00),
@@ -472,6 +564,17 @@ static const struct reg_sequence rk_hdtpx_tmds_lntop_lowbr_seq[] = {
REG_SEQ0(LNTOP_REG(0205), 0x1f),
};
+static const struct reg_sequence rk_hdtpx_frl_lntop_init_seq[] = {
+ REG_SEQ0(LNTOP_REG(0200), 0x04),
+ REG_SEQ0(LNTOP_REG(0201), 0x00),
+ REG_SEQ0(LNTOP_REG(0202), 0x00),
+ REG_SEQ0(LNTOP_REG(0203), 0xf0),
+ REG_SEQ0(LNTOP_REG(0204), 0xff),
+ REG_SEQ0(LNTOP_REG(0205), 0xff),
+ REG_SEQ0(LNTOP_REG(0206), 0x05),
+ REG_SEQ0(LNTOP_REG(0207), 0x0f),
+};
+
static const struct reg_sequence rk_hdtpx_common_lane_init_seq[] = {
REG_SEQ0(LANE_REG(0303), 0x0c),
REG_SEQ0(LANE_REG(0307), 0x20),
@@ -550,6 +653,40 @@ static const struct reg_sequence rk_hdtpx_tmds_lane_init_seq[] = {
REG_SEQ0(LANE_REG(0606), 0x1c),
};
+static const struct reg_sequence rk_hdtpx_frl_ropll_lane_init_seq[] = {
+ REG_SEQ0(LANE_REG(0312), 0x3c),
+ REG_SEQ0(LANE_REG(0412), 0x3c),
+ REG_SEQ0(LANE_REG(0512), 0x3c),
+ REG_SEQ0(LANE_REG(0612), 0x3c),
+};
+
+static const struct reg_sequence rk_hdtpx_frl_lcpll_lane_init_seq[] = {
+ REG_SEQ0(LANE_REG(0312), 0x3c),
+ REG_SEQ0(LANE_REG(0412), 0x3c),
+ REG_SEQ0(LANE_REG(0512), 0x3c),
+ REG_SEQ0(LANE_REG(0612), 0x3c),
+ REG_SEQ0(LANE_REG(0303), 0x2f),
+ REG_SEQ0(LANE_REG(0403), 0x2f),
+ REG_SEQ0(LANE_REG(0503), 0x2f),
+ REG_SEQ0(LANE_REG(0603), 0x2f),
+ REG_SEQ0(LANE_REG(0305), 0x03),
+ REG_SEQ0(LANE_REG(0405), 0x03),
+ REG_SEQ0(LANE_REG(0505), 0x03),
+ REG_SEQ0(LANE_REG(0605), 0x03),
+ REG_SEQ0(LANE_REG(0306), 0xfc),
+ REG_SEQ0(LANE_REG(0406), 0xfc),
+ REG_SEQ0(LANE_REG(0506), 0xfc),
+ REG_SEQ0(LANE_REG(0606), 0xfc),
+ REG_SEQ0(LANE_REG(0305), 0x4f),
+ REG_SEQ0(LANE_REG(0405), 0x4f),
+ REG_SEQ0(LANE_REG(0505), 0x4f),
+ REG_SEQ0(LANE_REG(0605), 0x4f),
+ REG_SEQ0(LANE_REG(0304), 0x14),
+ REG_SEQ0(LANE_REG(0404), 0x14),
+ REG_SEQ0(LANE_REG(0504), 0x14),
+ REG_SEQ0(LANE_REG(0604), 0x14),
+};
+
static bool rk_hdptx_phy_is_rw_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -651,6 +788,47 @@ static int rk_hdptx_post_enable_pll(struct rk_hdptx_phy *hdptx)
return 0;
}
+static int rk_hdptx_post_power_up(struct rk_hdptx_phy *hdptx)
+{
+ u32 val;
+ int ret;
+
+ val = (HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN) << 16 |
+ HDPTX_I_BIAS_EN | HDPTX_I_BGR_EN;
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+
+ usleep_range(10, 15);
+ reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
+
+ usleep_range(10, 15);
+ val = HDPTX_I_PLL_EN << 16 | HDPTX_I_PLL_EN;
+ regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
+
+ usleep_range(10, 15);
+ reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
+
+ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS, val,
+ val & HDPTX_O_PLL_LOCK_DONE, 20, 400);
+ if (ret) {
+ dev_err(hdptx->dev, "Failed to get PHY PLL lock: %d\n", ret);
+ return ret;
+ }
+
+ usleep_range(20, 30);
+ reset_control_deassert(hdptx->rsts[RST_LANE].rstc);
+
+ ret = regmap_read_poll_timeout(hdptx->grf, GRF_HDPTX_STATUS, val,
+ val & HDPTX_O_PHY_RDY, 100, 5000);
+ if (ret) {
+ dev_err(hdptx->dev, "Failed to get PHY ready: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(hdptx->dev, "PHY ready\n");
+
+ return 0;
+}
+
static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx)
{
u32 val;
@@ -680,6 +858,99 @@ static void rk_hdptx_phy_disable(struct rk_hdptx_phy *hdptx)
regmap_write(hdptx->grf, GRF_HDPTX_CON0, val);
}
+static void rk_hdptx_earc_config(struct rk_hdptx_phy *hdptx)
+{
+ regmap_update_bits(hdptx->regmap, SB_REG(0113), SB_RX_RCAL_OPT_CODE_MASK,
+ FIELD_PREP(SB_RX_RCAL_OPT_CODE_MASK, 1));
+ regmap_write(hdptx->regmap, SB_REG(011c), 0x04);
+ regmap_update_bits(hdptx->regmap, SB_REG(011b), SB_AFC_TOL_MASK,
+ FIELD_PREP(SB_AFC_TOL_MASK, 3));
+ regmap_write(hdptx->regmap, SB_REG(0109), 0x05);
+
+ regmap_update_bits(hdptx->regmap, SB_REG(0120),
+ SB_EARC_EN_MASK | SB_EARC_AFC_EN_MASK,
+ FIELD_PREP(SB_EARC_EN_MASK, 1) |
+ FIELD_PREP(SB_EARC_AFC_EN_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(011b), SB_EARC_SIG_DET_BYPASS_MASK,
+ FIELD_PREP(SB_EARC_SIG_DET_BYPASS_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(011f),
+ SB_PWM_AFC_CTRL_MASK | SB_RCAL_RSTN_MASK,
+ FIELD_PREP(SB_PWM_AFC_CTRL_MASK, 0xc) |
+ FIELD_PREP(SB_RCAL_RSTN_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0115), SB_READY_DELAY_TIME_MASK,
+ FIELD_PREP(SB_READY_DELAY_TIME_MASK, 2));
+ regmap_update_bits(hdptx->regmap, SB_REG(0113), SB_RX_RTERM_CTRL_MASK,
+ FIELD_PREP(SB_RX_RTERM_CTRL_MASK, 3));
+ regmap_update_bits(hdptx->regmap, SB_REG(0102), ANA_SB_RXTERM_OFFSP_MASK,
+ FIELD_PREP(ANA_SB_RXTERM_OFFSP_MASK, 3));
+ regmap_update_bits(hdptx->regmap, SB_REG(0103), ANA_SB_RXTERM_OFFSN_MASK,
+ FIELD_PREP(ANA_SB_RXTERM_OFFSN_MASK, 3));
+
+ regmap_write(hdptx->regmap, SB_REG(011a), 0x03);
+ regmap_write(hdptx->regmap, SB_REG(0118), 0x0a);
+ regmap_write(hdptx->regmap, SB_REG(011e), 0x6a);
+ regmap_write(hdptx->regmap, SB_REG(011d), 0x67);
+
+ regmap_update_bits(hdptx->regmap, SB_REG(0117), FAST_PULSE_TIME_MASK,
+ FIELD_PREP(FAST_PULSE_TIME_MASK, 4));
+ 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, 2) |
+ FIELD_PREP(SB_TG_RXTERM_EN_DELAY_TIME_MASK, 2));
+ regmap_update_bits(hdptx->regmap, SB_REG(0105), ANA_SB_TX_HLVL_PROG_MASK,
+ FIELD_PREP(ANA_SB_TX_HLVL_PROG_MASK, 7));
+ regmap_update_bits(hdptx->regmap, SB_REG(0106), ANA_SB_TX_LLVL_PROG_MASK,
+ FIELD_PREP(ANA_SB_TX_LLVL_PROG_MASK, 7));
+ regmap_update_bits(hdptx->regmap, SB_REG(010f), ANA_SB_VREG_GAIN_CTRL_MASK,
+ FIELD_PREP(ANA_SB_VREG_GAIN_CTRL_MASK, 0));
+ regmap_update_bits(hdptx->regmap, SB_REG(0110), ANA_SB_VREG_REF_SEL_MASK,
+ FIELD_PREP(ANA_SB_VREG_REF_SEL_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0115), SB_TG_OSC_EN_DELAY_TIME_MASK,
+ FIELD_PREP(SB_TG_OSC_EN_DELAY_TIME_MASK, 2));
+ regmap_update_bits(hdptx->regmap, SB_REG(0116), AFC_RSTN_DELAY_TIME_MASK,
+ FIELD_PREP(AFC_RSTN_DELAY_TIME_MASK, 2));
+ regmap_update_bits(hdptx->regmap, SB_REG(0109), ANA_SB_DMRX_AFC_DIV_RATIO_MASK,
+ FIELD_PREP(ANA_SB_DMRX_AFC_DIV_RATIO_MASK, 5));
+ regmap_update_bits(hdptx->regmap, SB_REG(0103), OVRD_SB_RX_RESCAL_DONE_MASK,
+ FIELD_PREP(OVRD_SB_RX_RESCAL_DONE_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0104), OVRD_SB_EN_MASK,
+ FIELD_PREP(OVRD_SB_EN_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0102), OVRD_SB_RXTERM_EN_MASK,
+ FIELD_PREP(OVRD_SB_RXTERM_EN_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0105), OVRD_SB_EARC_CMDC_EN_MASK,
+ FIELD_PREP(OVRD_SB_EARC_CMDC_EN_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(010f),
+ OVRD_SB_VREG_EN_MASK | OVRD_SB_VREG_LPF_BYPASS_MASK,
+ FIELD_PREP(OVRD_SB_VREG_EN_MASK, 1) |
+ FIELD_PREP(OVRD_SB_VREG_LPF_BYPASS_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(0123), OVRD_SB_READY_MASK,
+ FIELD_PREP(OVRD_SB_READY_MASK, 1));
+
+ usleep_range(1000, 1100);
+ regmap_update_bits(hdptx->regmap, SB_REG(0103), SB_RX_RESCAL_DONE_MASK,
+ FIELD_PREP(SB_RX_RESCAL_DONE_MASK, 1));
+ usleep_range(50, 60);
+ regmap_update_bits(hdptx->regmap, SB_REG(0104), SB_EN_MASK,
+ FIELD_PREP(SB_EN_MASK, 1));
+ usleep_range(50, 60);
+ regmap_update_bits(hdptx->regmap, SB_REG(0102), SB_RXTERM_EN_MASK,
+ FIELD_PREP(SB_RXTERM_EN_MASK, 1));
+ usleep_range(50, 60);
+ regmap_update_bits(hdptx->regmap, SB_REG(0105), SB_EARC_CMDC_EN_MASK,
+ FIELD_PREP(SB_EARC_CMDC_EN_MASK, 1));
+ regmap_update_bits(hdptx->regmap, SB_REG(010f), SB_VREG_EN_MASK,
+ FIELD_PREP(SB_VREG_EN_MASK, 1));
+ usleep_range(50, 60);
+ regmap_update_bits(hdptx->regmap, SB_REG(010f), OVRD_SB_VREG_LPF_BYPASS_MASK,
+ FIELD_PREP(OVRD_SB_VREG_LPF_BYPASS_MASK, 1));
+ usleep_range(250, 300);
+ regmap_update_bits(hdptx->regmap, SB_REG(010f), OVRD_SB_VREG_LPF_BYPASS_MASK,
+ FIELD_PREP(OVRD_SB_VREG_LPF_BYPASS_MASK, 0));
+ usleep_range(100, 120);
+ regmap_update_bits(hdptx->regmap, SB_REG(0123), SB_READY_MASK,
+ FIELD_PREP(SB_READY_MASK, 1));
+}
+
static bool rk_hdptx_phy_clk_pll_calc(unsigned int data_rate,
struct ropll_config *cfg)
{
@@ -755,9 +1026,13 @@ static bool rk_hdptx_phy_clk_pll_calc(unsigned int data_rate,
static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx,
unsigned int rate)
{
+ int i, bus_width = phy_get_bus_width(hdptx->phy);
+ u8 color_depth = (bus_width & COLOR_DEPTH_MASK) ? 1 : 0;
const struct ropll_config *cfg = NULL;
struct ropll_config rc = {0};
- int i;
+
+ if (color_depth)
+ rate = rate * 10 / 8;
for (i = 0; i < ARRAY_SIZE(ropll_tmds_cfg); i++)
if (rate == ropll_tmds_cfg[i].bit_rate) {
@@ -813,6 +1088,9 @@ static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx,
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, color_depth));
+
regmap_update_bits(hdptx->regmap, CMN_REG(0086), PLL_PCG_CLK_EN,
PLL_PCG_CLK_EN);
@@ -853,9 +1131,146 @@ static int rk_hdptx_ropll_tmds_mode_config(struct rk_hdptx_phy *hdptx,
rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_lane_init_seq);
rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_tmds_lane_init_seq);
+ if (hdptx->earc_en)
+ rk_hdptx_earc_config(hdptx);
+
return rk_hdptx_post_enable_lane(hdptx);
}
+static int rk_hdptx_ropll_frl_mode_config(struct rk_hdptx_phy *hdptx,
+ u32 bus_width)
+{
+ u32 bit_rate = bus_width & DATA_RATE_MASK;
+ u8 color_depth = (bus_width & COLOR_DEPTH_MASK) ? 1 : 0;
+ const struct ropll_config *cfg = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ropll_frl_cfg); i++)
+ if (bit_rate == ropll_frl_cfg[i].bit_rate) {
+ cfg = &ropll_frl_cfg[i];
+ break;
+ }
+
+ if (!cfg) {
+ dev_err(hdptx->dev, "%s cannot find pll cfg\n", __func__);
+ return -EINVAL;
+ }
+
+ rk_hdptx_pre_power_up(hdptx);
+
+ reset_control_assert(hdptx->rsts[RST_ROPLL].rstc);
+ usleep_range(10, 20);
+ 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_frl_cmn_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_frl_ropll_cmn_init_seq);
+
+ regmap_write(hdptx->regmap, CMN_REG(0051), cfg->pms_mdiv);
+ regmap_write(hdptx->regmap, CMN_REG(0055), cfg->pms_mdiv_afc);
+ regmap_write(hdptx->regmap, CMN_REG(0059),
+ (cfg->pms_pdiv << 4) | cfg->pms_refdiv);
+ regmap_write(hdptx->regmap, CMN_REG(005a), cfg->pms_sdiv << 4);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e), ROPLL_SDM_EN_MASK,
+ FIELD_PREP(ROPLL_SDM_EN_MASK, cfg->sdm_en));
+ if (!cfg->sdm_en)
+ regmap_update_bits(hdptx->regmap, CMN_REG(005e), 0xf, 0);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0064), ROPLL_SDM_NUM_SIGN_RBR_MASK,
+ FIELD_PREP(ROPLL_SDM_NUM_SIGN_RBR_MASK, cfg->sdm_num_sign));
+
+ regmap_write(hdptx->regmap, CMN_REG(0060), cfg->sdm_deno);
+ regmap_write(hdptx->regmap, CMN_REG(0065), cfg->sdm_num);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0069), ROPLL_SDC_N_RBR_MASK,
+ FIELD_PREP(ROPLL_SDC_N_RBR_MASK, cfg->sdc_n));
+
+ regmap_write(hdptx->regmap, CMN_REG(006c), cfg->sdc_num);
+ regmap_write(hdptx->regmap, CMN_REG(0070), cfg->sdc_deno);
+
+ 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, color_depth));
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_sb_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_frl_lntop_init_seq);
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_lane_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_frl_ropll_lane_init_seq);
+
+ if (hdptx->earc_en)
+ rk_hdptx_earc_config(hdptx);
+
+ return rk_hdptx_post_power_up(hdptx);
+}
+
+static int rk_hdptx_lcpll_frl_mode_config(struct rk_hdptx_phy *hdptx,
+ u32 bus_width)
+{
+ u32 bit_rate = bus_width & DATA_RATE_MASK;
+ u8 color_depth = (bus_width & COLOR_DEPTH_MASK) ? 1 : 0;
+ const struct lcpll_config *cfg = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(lcpll_cfg); i++)
+ if (bit_rate == lcpll_cfg[i].bit_rate) {
+ cfg = &lcpll_cfg[i];
+ break;
+ }
+
+ if (!cfg) {
+ dev_err(hdptx->dev, "%s cannot find pll cfg\n", __func__);
+ return -EINVAL;
+ }
+
+ rk_hdptx_pre_power_up(hdptx);
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_cmn_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_frl_cmn_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_frl_lcpll_cmn_init_seq);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(0008),
+ LCPLL_EN_MASK | LCPLL_LCVCO_MODE_EN_MASK,
+ FIELD_PREP(LCPLL_EN_MASK, 1) |
+ FIELD_PREP(LCPLL_LCVCO_MODE_EN_MASK, cfg->lcvco_mode_en));
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(001e),
+ LCPLL_PI_EN_MASK | LCPLL_100M_CLK_EN_MASK,
+ FIELD_PREP(LCPLL_PI_EN_MASK, cfg->pi_en) |
+ FIELD_PREP(LCPLL_100M_CLK_EN_MASK, cfg->clk_en_100m));
+
+ regmap_write(hdptx->regmap, CMN_REG(0020), cfg->pms_mdiv);
+ regmap_write(hdptx->regmap, CMN_REG(0021), cfg->pms_mdiv_afc);
+ regmap_write(hdptx->regmap, CMN_REG(0022),
+ (cfg->pms_pdiv << 4) | cfg->pms_refdiv);
+ regmap_write(hdptx->regmap, CMN_REG(0023),
+ (cfg->pms_sdiv << 4) | cfg->pms_sdiv);
+ regmap_write(hdptx->regmap, CMN_REG(002a), cfg->sdm_deno);
+ regmap_write(hdptx->regmap, CMN_REG(002b), cfg->sdm_num_sign);
+ regmap_write(hdptx->regmap, CMN_REG(002c), cfg->sdm_num);
+
+ regmap_update_bits(hdptx->regmap, CMN_REG(002d), LCPLL_SDC_N_MASK,
+ FIELD_PREP(LCPLL_SDC_N_MASK, cfg->sdc_n));
+
+ 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, color_depth));
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_sb_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_frl_lntop_init_seq);
+
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_common_lane_init_seq);
+ rk_hdptx_multi_reg_write(hdptx, rk_hdtpx_frl_lcpll_lane_init_seq);
+
+ if (hdptx->earc_en)
+ rk_hdptx_earc_config(hdptx);
+
+ return rk_hdptx_post_power_up(hdptx);
+}
+
static int rk_hdptx_phy_power_on(struct phy *phy)
{
struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
@@ -865,7 +1280,7 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
* from the HDMI bridge driver until phy_configure_opts_hdmi
* becomes available in the PHY API.
*/
- unsigned int rate = bus_width & 0xfffffff;
+ unsigned int rate = bus_width & DATA_RATE_MASK;
dev_dbg(hdptx->dev, "%s bus_width=%x rate=%u\n",
__func__, bus_width, rate);
@@ -876,7 +1291,20 @@ static int rk_hdptx_phy_power_on(struct phy *phy)
return ret;
}
- ret = rk_hdptx_ropll_tmds_mode_config(hdptx, rate);
+ if (bus_width & HDMI_EARC_MASK)
+ hdptx->earc_en = true;
+ else
+ hdptx->earc_en = false;
+
+ if (bus_width & HDMI_MODE_MASK) {
+ if (rate > 24000000)
+ ret = rk_hdptx_lcpll_frl_mode_config(hdptx, bus_width);
+ else
+ ret = rk_hdptx_ropll_frl_mode_config(hdptx, bus_width);
+ } else {
+ ret = rk_hdptx_ropll_tmds_mode_config(hdptx, rate);
+ }
+
if (ret)
pm_runtime_put(hdptx->dev);
--
2.44.1
From f3967b5c3262f3a19e84a3246e737ac39ec131e3 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Mon, 19 Feb 2024 21:53:24 +0200
Subject: [PATCH 28/54] [WIP] dt-bindings: phy: rockchip,rk3588-hdptx-phy: Add
#clock-cells
The PHY can be used as a clock provider on RK3588, hence add the missing
'#clock-cells' property.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../devicetree/bindings/phy/rockchip,rk3588-hdptx-phy.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/phy/rockchip,rk3588-hdptx-phy.yaml b/Documentation/devicetree/bindings/phy/rockchip,rk3588-hdptx-phy.yaml
index 54e822c715f3..84fe59dbcf48 100644
--- a/Documentation/devicetree/bindings/phy/rockchip,rk3588-hdptx-phy.yaml
+++ b/Documentation/devicetree/bindings/phy/rockchip,rk3588-hdptx-phy.yaml
@@ -27,6 +27,9 @@ properties:
- const: ref
- const: apb
+ "#clock-cells":
+ const: 0
+
"#phy-cells":
const: 0
--
2.44.1
From 3f89224fd82432d536350eb5291f906a7cc637e0 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Tue, 16 Jan 2024 19:27:40 +0200
Subject: [PATCH 29/54] [WIP] phy: phy-rockchip-samsung-hdptx: Add clock
provider
The HDMI PHY PLL can be used as an alternative dclk source to SoC CRU.
It provides more accurate clock rates required to properly support
various display modes, e.g. those relying on non-integer refresh rates.
Also note this only works for HDMI 2.0 or bellow, e.g. cannot be used to
support HDMI 2.1 4K@120Hz mode.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../phy/rockchip/phy-rockchip-samsung-hdptx.c | 148 +++++++++++++++++-
1 file changed, 143 insertions(+), 5 deletions(-)
diff --git a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
index 44acea3f86af..a3ac4f3835bc 100644
--- a/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
+++ b/drivers/phy/rockchip/phy-rockchip-samsung-hdptx.c
@@ -8,6 +8,7 @@
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
@@ -279,6 +280,12 @@ struct rk_hdptx_phy {
int nr_clks;
struct reset_control_bulk_data rsts[RST_MAX];
bool earc_en;
+
+ /* clk provider */
+ struct clk_hw hw;
+ unsigned long rate;
+ int id;
+ int count;
};
static const struct lcpll_config lcpll_cfg[] = {
@@ -1031,6 +1038,8 @@ static int rk_hdptx_ropll_tmds_cmn_config(struct rk_hdptx_phy *hdptx,
const struct ropll_config *cfg = NULL;
struct ropll_config rc = {0};
+ hdptx->rate = rate * 100;
+
if (color_depth)
rate = rate * 10 / 8;
@@ -1315,11 +1324,13 @@ static int rk_hdptx_phy_power_off(struct phy *phy)
{
struct rk_hdptx_phy *hdptx = phy_get_drvdata(phy);
u32 val;
- int ret;
+ int ret = 0;
- ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &val);
- if (ret == 0 && (val & HDPTX_O_PLL_LOCK_DONE))
- rk_hdptx_phy_disable(hdptx);
+ if (hdptx->count == 0) {
+ ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &val);
+ if (ret == 0 && (val & HDPTX_O_PLL_LOCK_DONE))
+ rk_hdptx_phy_disable(hdptx);
+ }
pm_runtime_put(hdptx->dev);
@@ -1332,6 +1343,129 @@ static const struct phy_ops rk_hdptx_phy_ops = {
.owner = THIS_MODULE,
};
+static struct rk_hdptx_phy *to_rk_hdptx_phy(struct clk_hw *hw)
+{
+ return container_of(hw, struct rk_hdptx_phy, hw);
+}
+
+static int rk_hdptx_phy_clk_prepare(struct clk_hw *hw)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(hdptx->dev);
+ if (ret) {
+ dev_err(hdptx->dev, "Failed to resume phy clk: %d\n", ret);
+ return ret;
+ }
+
+ if (!hdptx->count && hdptx->rate) {
+ ret = rk_hdptx_ropll_tmds_cmn_config(hdptx, hdptx->rate / 100);
+ if (ret < 0) {
+ dev_err(hdptx->dev, "Failed to init PHY PLL: %d\n", ret);
+ pm_runtime_put(hdptx->dev);
+ return ret;
+ }
+ }
+
+ hdptx->count++;
+
+ return 0;
+}
+
+static void rk_hdptx_phy_clk_unprepare(struct clk_hw *hw)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ if (hdptx->count == 1) {
+ u32 val;
+ int ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &val);
+ if (ret == 0 && (val & HDPTX_O_PLL_LOCK_DONE))
+ rk_hdptx_phy_disable(hdptx);
+ }
+
+ hdptx->count--;
+ pm_runtime_put(hdptx->dev);
+}
+
+static unsigned long rk_hdptx_phy_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+
+ return hdptx->rate;
+}
+
+static long rk_hdptx_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ const struct ropll_config *cfg = NULL;
+ 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) {
+ cfg = &ropll_tmds_cfg[i];
+ break;
+ }
+
+ if (!cfg && !rk_hdptx_phy_clk_pll_calc(bit_rate, NULL))
+ return -EINVAL;
+
+ return rate;
+}
+
+static int rk_hdptx_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct rk_hdptx_phy *hdptx = to_rk_hdptx_phy(hw);
+ u32 val;
+ int ret = regmap_read(hdptx->grf, GRF_HDPTX_STATUS, &val);
+ if (ret == 0 && (val & HDPTX_O_PLL_LOCK_DONE))
+ rk_hdptx_phy_disable(hdptx);
+
+ return rk_hdptx_ropll_tmds_cmn_config(hdptx, rate / 100);
+}
+
+static const struct clk_ops hdptx_phy_clk_ops = {
+ .prepare = rk_hdptx_phy_clk_prepare,
+ .unprepare = rk_hdptx_phy_clk_unprepare,
+ .recalc_rate = rk_hdptx_phy_clk_recalc_rate,
+ .round_rate = rk_hdptx_phy_clk_round_rate,
+ .set_rate = rk_hdptx_phy_clk_set_rate,
+};
+
+static int rk_hdptx_phy_clk_register(struct rk_hdptx_phy *hdptx)
+{
+ struct device *dev = hdptx->dev;
+ const char *name, *pname;
+ struct clk *refclk;
+ int ret;
+
+ refclk = devm_clk_get(dev, "ref");
+ if (IS_ERR(refclk))
+ return dev_err_probe(dev, PTR_ERR(refclk),
+ "Failed to get ref clock\n");
+
+ pname = __clk_get_name(refclk);
+ name = hdptx->id ? "clk_hdmiphy_pixel1" : "clk_hdmiphy_pixel0";
+ hdptx->hw.init = CLK_HW_INIT(name, pname, &hdptx_phy_clk_ops,
+ CLK_GET_RATE_NOCACHE);
+
+ ret = devm_clk_hw_register(dev, &hdptx->hw);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register clock\n");
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &hdptx->hw);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register clk provider\n");
+ return 0;
+}
+
static int rk_hdptx_phy_runtime_suspend(struct device *dev)
{
struct rk_hdptx_phy *hdptx = dev_get_drvdata(dev);
@@ -1367,6 +1501,10 @@ static int rk_hdptx_phy_probe(struct platform_device *pdev)
hdptx->dev = dev;
+ hdptx->id = of_alias_get_id(dev->of_node, "hdptxphy");
+ if (hdptx->id < 0)
+ hdptx->id = 0;
+
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return dev_err_probe(dev, PTR_ERR(regs),
@@ -1426,7 +1564,7 @@ static int rk_hdptx_phy_probe(struct platform_device *pdev)
reset_control_deassert(hdptx->rsts[RST_CMN].rstc);
reset_control_deassert(hdptx->rsts[RST_INIT].rstc);
- return 0;
+ return rk_hdptx_phy_clk_register(hdptx);
}
static const struct dev_pm_ops rk_hdptx_phy_pm_ops = {
--
2.44.1
From c7dbc888875e205bbfbf81d8174b6412642c429f Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Wed, 27 Mar 2024 20:36:15 +0200
Subject: [PATCH 30/54] [WIP] dt-bindings: display: rockchip-drm: Add optional
clocks property
Allow using the clock provided by HDMI0 PHY PLL to improve HDMI output
support on RK3588 SoC.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../bindings/display/rockchip/rockchip-drm.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip-drm.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip-drm.yaml
index a8d18a37cb23..9d000760dd6e 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip-drm.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip-drm.yaml
@@ -28,6 +28,14 @@ properties:
of vop devices. vop definitions as defined in
Documentation/devicetree/bindings/display/rockchip/rockchip-vop.yaml
+ clocks:
+ maxItems: 1
+ description: Optional clock provided by HDMI0 PLL
+
+ clock-names:
+ items:
+ - const: hdmi0_phy_pll
+
required:
- compatible
- ports
--
2.44.1
From 05c5f1c19d33c6c5404a68adb6d264387133d352 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Fri, 3 Nov 2023 19:58:02 +0200
Subject: [PATCH 31/54] [WIP] drm/rockchip: vop2: Improve display modes
handling on rk3588
The initial vop2 support for rk3588 in mainline is not able to handle
all display modes supported by connected displays, e.g.
2560x1440-75.00Hz, 2048x1152-60.00Hz, 1024x768-60.00Hz.
Additionally, it doesn't cope with non-integer refresh rates like 59.94,
29.97, 23.98, etc.
Improve HDMI0 clocking in order to support the additional display modes.
Fixes: 5a028e8f062f ("drm/rockchip: vop2: Add support for rk3588")
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 553 ++++++++++++++++++-
1 file changed, 552 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 62ebbdb16253..86d2c15af39f 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -5,6 +5,8 @@
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
#include <linux/component.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
@@ -212,6 +214,10 @@ struct vop2 {
struct clk *hclk;
struct clk *aclk;
struct clk *pclk;
+ // [CC:] hack to support additional display modes
+ struct clk *hdmi0_phy_pll;
+ /* list_head of internal clk */
+ struct list_head clk_list_head;
/* optional internal rgb encoder */
struct rockchip_rgb *rgb;
@@ -220,6 +226,19 @@ struct vop2 {
struct vop2_win win[];
};
+struct vop2_clk {
+ struct vop2 *vop2;
+ struct list_head list;
+ unsigned long rate;
+ struct clk_hw hw;
+ struct clk_divider div;
+ int div_val;
+ u8 parent_index;
+};
+
+#define to_vop2_clk(_hw) container_of(_hw, struct vop2_clk, hw)
+#define VOP2_MAX_DCLK_RATE 600000 /* kHz */
+
#define vop2_output_if_is_hdmi(x) ((x) == ROCKCHIP_VOP2_EP_HDMI0 || \
(x) == ROCKCHIP_VOP2_EP_HDMI1)
@@ -1476,9 +1495,30 @@ static bool vop2_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+ struct drm_crtc_state *new_crtc_state = container_of(mode, struct drm_crtc_state, mode);
drm_mode_set_crtcinfo(adj_mode, CRTC_INTERLACE_HALVE_V |
CRTC_STEREO_DOUBLE);
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+ adj_mode->crtc_clock *= 2;
+
+ drm_connector_list_iter_begin(crtc->dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ if ((new_crtc_state->connector_mask & drm_connector_mask(connector)) &&
+ ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ||
+ (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA))) {
+ drm_connector_list_iter_end(&conn_iter);
+ return true;
+ }
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+ if (adj_mode->crtc_clock <= VOP2_MAX_DCLK_RATE)
+ adj_mode->crtc_clock = DIV_ROUND_UP(clk_round_rate(vp->dclk,
+ adj_mode->crtc_clock * 1000), 1000);
return true;
}
@@ -1663,6 +1703,31 @@ static unsigned long rk3588_calc_dclk(unsigned long child_clk, unsigned long max
return 0;
}
+static struct vop2_clk *vop2_clk_get(struct vop2 *vop2, const char *name);
+
+static int vop2_cru_set_rate(struct vop2_clk *if_pixclk, struct vop2_clk *if_dclk)
+{
+ int ret = 0;
+
+ if (if_pixclk) {
+ ret = clk_set_rate(if_pixclk->hw.clk, if_pixclk->rate);
+ if (ret < 0) {
+ DRM_DEV_ERROR(if_pixclk->vop2->dev, "set %s to %ld failed: %d\n",
+ clk_hw_get_name(&if_pixclk->hw), if_pixclk->rate, ret);
+ return ret;
+ }
+ }
+
+ if (if_dclk) {
+ ret = clk_set_rate(if_dclk->hw.clk, if_dclk->rate);
+ if (ret < 0)
+ DRM_DEV_ERROR(if_dclk->vop2->dev, "set %s to %ld failed %d\n",
+ clk_hw_get_name(&if_dclk->hw), if_dclk->rate, ret);
+ }
+
+ return ret;
+}
+
/*
* 4 pixclk/cycle on rk3588
* RGB/eDP/HDMI: if_pixclk >= dclk_core
@@ -1686,6 +1751,72 @@ static unsigned long rk3588_calc_cru_cfg(struct vop2_video_port *vp, int id,
int K = 1;
if (vop2_output_if_is_hdmi(id)) {
+ if (vop2->data->soc_id == 3588 && id == ROCKCHIP_VOP2_EP_HDMI0 &&
+ vop2->hdmi0_phy_pll) {
+ const char *clk_src_name = "hdmi_edp0_clk_src";
+ const char *clk_parent_name = "dclk";
+ const char *pixclk_name = "hdmi_edp0_pixclk";
+ const char *dclk_name = "hdmi_edp0_dclk";
+ struct vop2_clk *if_clk_src, *if_clk_parent, *if_pixclk, *if_dclk, *dclk, *dclk_core, *dclk_out;
+ char clk_name[32];
+ int ret;
+
+ if_clk_src = vop2_clk_get(vop2, clk_src_name);
+ snprintf(clk_name, sizeof(clk_name), "%s%d", clk_parent_name, vp->id);
+ if_clk_parent = vop2_clk_get(vop2, clk_name);
+ if_pixclk = vop2_clk_get(vop2, pixclk_name);
+ if_dclk = vop2_clk_get(vop2, dclk_name);
+ if (!if_pixclk || !if_clk_parent) {
+ DRM_DEV_ERROR(vop2->dev, "failed to get connector interface clk\n");
+ return -ENODEV;
+ }
+
+ ret = clk_set_parent(if_clk_src->hw.clk, if_clk_parent->hw.clk);
+ if (ret < 0) {
+ DRM_DEV_ERROR(vop2->dev, "failed to set parent(%s) for %s: %d\n",
+ __clk_get_name(if_clk_parent->hw.clk),
+ __clk_get_name(if_clk_src->hw.clk), ret);
+ return ret;
+ }
+
+ if (output_mode == ROCKCHIP_OUT_MODE_YUV420)
+ K = 2;
+
+ if_pixclk->rate = (dclk_core_rate << 1) / K;
+ if_dclk->rate = dclk_core_rate / K;
+
+ snprintf(clk_name, sizeof(clk_name), "dclk_core%d", vp->id);
+ dclk_core = vop2_clk_get(vop2, clk_name);
+
+ snprintf(clk_name, sizeof(clk_name), "dclk_out%d", vp->id);
+ dclk_out = vop2_clk_get(vop2, clk_name);
+
+ snprintf(clk_name, sizeof(clk_name), "dclk%d", vp->id);
+ dclk = vop2_clk_get(vop2, clk_name);
+ if (v_pixclk <= (VOP2_MAX_DCLK_RATE * 1000)) {
+ if (output_mode == ROCKCHIP_OUT_MODE_YUV420)
+ v_pixclk = v_pixclk >> 1;
+ } else {
+ v_pixclk = v_pixclk >> 2;
+ }
+ clk_set_rate(dclk->hw.clk, v_pixclk);
+
+ if (dclk_core_rate > if_pixclk->rate) {
+ clk_set_rate(dclk_core->hw.clk, dclk_core_rate);
+ ret = vop2_cru_set_rate(if_pixclk, if_dclk);
+ } else {
+ ret = vop2_cru_set_rate(if_pixclk, if_dclk);
+ clk_set_rate(dclk_core->hw.clk, dclk_core_rate);
+ }
+
+ *dclk_core_div = dclk_core->div_val;
+ *dclk_out_div = dclk_out->div_val;
+ *if_pixclk_div = if_pixclk->div_val;
+ *if_dclk_div = if_dclk->div_val;
+
+ return dclk->rate;
+ }
+
/*
* K = 2: dclk_core = if_pixclk_rate > if_dclk_rate
* K = 1: dclk_core = hdmie_edp_dclk > if_pixclk_rate
@@ -1917,6 +2048,22 @@ static int us_to_vertical_line(struct drm_display_mode *mode, int us)
return us * mode->clock / mode->htotal / 1000;
}
+// [CC:] rework virtual clock
+static struct vop2_clk *vop2_clk_get(struct vop2 *vop2, const char *name)
+{
+ struct vop2_clk *clk, *n;
+
+ if (!name)
+ return NULL;
+
+ list_for_each_entry_safe(clk, n, &vop2->clk_list_head, list) {
+ if (!strcmp(clk_hw_get_name(&clk->hw), name))
+ return clk;
+ }
+
+ return NULL;
+}
+
static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
@@ -1944,6 +2091,8 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
u32 val, polflags;
int ret;
struct drm_encoder *encoder;
+ char clk_name[32];
+ struct vop2_clk *dclk;
drm_dbg(vop2->drm, "Update mode to %dx%d%s%d, type: %d for vp%d\n",
hdisplay, vdisplay, mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "p",
@@ -2044,11 +2193,38 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
dsp_ctrl |= RK3568_VP_DSP_CTRL__CORE_DCLK_DIV;
- clock *= 2;
+ // [CC:] done via mode_fixup
+ // clock *= 2;
}
vop2_vp_write(vp, RK3568_VP_MIPI_CTRL, 0);
+ snprintf(clk_name, sizeof(clk_name), "dclk%d", vp->id);
+ dclk = vop2_clk_get(vop2, clk_name);
+ if (dclk) {
+ /*
+ * use HDMI_PHY_PLL as dclk source under 4K@60 if it is available,
+ * otherwise use system cru as dclk source.
+ */
+ drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) {
+ struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
+
+ // [CC:] Using PHY PLL to handle all display modes
+ if (rkencoder->crtc_endpoint_id == ROCKCHIP_VOP2_EP_HDMI0) {
+ clk_get_rate(vop2->hdmi0_phy_pll);
+
+ if (mode->crtc_clock <= VOP2_MAX_DCLK_RATE) {
+ ret = clk_set_parent(vp->dclk, vop2->hdmi0_phy_pll);
+ if (ret < 0)
+ DRM_WARN("failed to set clock parent for %s\n",
+ __clk_get_name(vp->dclk));
+ }
+
+ clock = dclk->rate;
+ }
+ }
+ }
+
clk_set_rate(vp->dclk, clock);
vop2_post_config(crtc);
@@ -2504,7 +2680,43 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
spin_unlock_irq(&crtc->dev->event_lock);
}
+static enum drm_mode_status
+vop2_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct vop2 *vop2 = vp->vop2;
+ const struct vop2_data *vop2_data = vop2->data;
+ const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id];
+ int request_clock = mode->clock;
+ int clock;
+
+ if (mode->hdisplay > vp_data->max_output.width)
+ return MODE_BAD_HVALUE;
+
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+ request_clock *= 2;
+
+ if (request_clock <= VOP2_MAX_DCLK_RATE) {
+ clock = request_clock;
+ } else {
+ request_clock = request_clock >> 2;
+ clock = clk_round_rate(vp->dclk, request_clock * 1000) / 1000;
+ }
+
+ /*
+ * Hdmi or DisplayPort request a Accurate clock.
+ */
+ if (vcstate->output_type == DRM_MODE_CONNECTOR_HDMIA ||
+ vcstate->output_type == DRM_MODE_CONNECTOR_DisplayPort)
+ if (clock != request_clock)
+ return MODE_CLOCK_RANGE;
+
+ return MODE_OK;
+}
+
static const struct drm_crtc_helper_funcs vop2_crtc_helper_funcs = {
+ .mode_valid = vop2_crtc_mode_valid,
.mode_fixup = vop2_crtc_mode_fixup,
.atomic_check = vop2_crtc_atomic_check,
.atomic_begin = vop2_crtc_atomic_begin,
@@ -3074,6 +3286,336 @@ static const struct regmap_config vop2_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
+/*
+ * BEGIN virtual clock
+ */
+#define PLL_RATE_MIN 30000000
+
+#define cru_dbg(format, ...) do { \
+ if (cru_debug) \
+ pr_info("%s: " format, __func__, ## __VA_ARGS__); \
+ } while (0)
+
+#define PNAME(x) static const char *const x[]
+
+enum vop_clk_branch_type {
+ branch_mux,
+ branch_divider,
+ branch_factor,
+ branch_virtual,
+};
+
+#define VIR(cname) \
+ { \
+ .branch_type = branch_virtual, \
+ .name = cname, \
+ }
+
+
+#define MUX(cname, pnames, f) \
+ { \
+ .branch_type = branch_mux, \
+ .name = cname, \
+ .parent_names = pnames, \
+ .num_parents = ARRAY_SIZE(pnames), \
+ .flags = f, \
+ }
+
+#define FACTOR(cname, pname, f) \
+ { \
+ .branch_type = branch_factor, \
+ .name = cname, \
+ .parent_names = (const char *[]){ pname }, \
+ .num_parents = 1, \
+ .flags = f, \
+ }
+
+#define DIV(cname, pname, f, w) \
+ { \
+ .branch_type = branch_divider, \
+ .name = cname, \
+ .parent_names = (const char *[]){ pname }, \
+ .num_parents = 1, \
+ .flags = f, \
+ .div_width = w, \
+ }
+
+struct vop2_clk_branch {
+ enum vop_clk_branch_type branch_type;
+ const char *name;
+ const char *const *parent_names;
+ u8 num_parents;
+ unsigned long flags;
+ u8 div_shift;
+ u8 div_width;
+ u8 div_flags;
+};
+
+PNAME(mux_port0_dclk_src_p) = { "dclk0", "dclk1" };
+PNAME(mux_port2_dclk_src_p) = { "dclk2", "dclk1" };
+PNAME(mux_dp_pixclk_p) = { "dclk_out0", "dclk_out1", "dclk_out2" };
+PNAME(mux_hdmi_edp_clk_src_p) = { "dclk0", "dclk1", "dclk2" };
+PNAME(mux_mipi_clk_src_p) = { "dclk_out1", "dclk_out2", "dclk_out3" };
+PNAME(mux_dsc_8k_clk_src_p) = { "dclk0", "dclk1", "dclk2", "dclk3" };
+PNAME(mux_dsc_4k_clk_src_p) = { "dclk0", "dclk1", "dclk2", "dclk3" };
+
+/*
+ * We only use this clk driver calculate the div
+ * of dclk_core/dclk_out/if_pixclk/if_dclk and
+ * the rate of the dclk from the soc.
+ *
+ * We don't touch the cru in the vop here, as
+ * these registers has special read andy write
+ * limits.
+ */
+static struct vop2_clk_branch rk3588_vop_clk_branches[] = {
+ VIR("dclk0"),
+ VIR("dclk1"),
+ VIR("dclk2"),
+ VIR("dclk3"),
+
+ MUX("port0_dclk_src", mux_port0_dclk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("dclk_core0", "port0_dclk_src", CLK_SET_RATE_PARENT, 2),
+ DIV("dclk_out0", "port0_dclk_src", CLK_SET_RATE_PARENT, 2),
+
+ FACTOR("port1_dclk_src", "dclk1", CLK_SET_RATE_PARENT),
+ DIV("dclk_core1", "port1_dclk_src", CLK_SET_RATE_PARENT, 2),
+ DIV("dclk_out1", "port1_dclk_src", CLK_SET_RATE_PARENT, 2),
+
+ MUX("port2_dclk_src", mux_port2_dclk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("dclk_core2", "port2_dclk_src", CLK_SET_RATE_PARENT, 2),
+ DIV("dclk_out2", "port2_dclk_src", CLK_SET_RATE_PARENT, 2),
+
+ FACTOR("port3_dclk_src", "dclk3", CLK_SET_RATE_PARENT),
+ DIV("dclk_core3", "port3_dclk_src", CLK_SET_RATE_PARENT, 2),
+ DIV("dclk_out3", "port3_dclk_src", CLK_SET_RATE_PARENT, 2),
+
+ MUX("dp0_pixclk", mux_dp_pixclk_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ MUX("dp1_pixclk", mux_dp_pixclk_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+
+ MUX("hdmi_edp0_clk_src", mux_hdmi_edp_clk_src_p,
+ CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("hdmi_edp0_dclk", "hdmi_edp0_clk_src", 0, 2),
+ DIV("hdmi_edp0_pixclk", "hdmi_edp0_clk_src", CLK_SET_RATE_PARENT, 1),
+
+ MUX("hdmi_edp1_clk_src", mux_hdmi_edp_clk_src_p,
+ CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("hdmi_edp1_dclk", "hdmi_edp1_clk_src", 0, 2),
+ DIV("hdmi_edp1_pixclk", "hdmi_edp1_clk_src", CLK_SET_RATE_PARENT, 1),
+
+ MUX("mipi0_clk_src", mux_mipi_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("mipi0_pixclk", "mipi0_clk_src", CLK_SET_RATE_PARENT, 2),
+
+ MUX("mipi1_clk_src", mux_mipi_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("mipi1_pixclk", "mipi1_clk_src", CLK_SET_RATE_PARENT, 2),
+
+ FACTOR("rgb_pixclk", "port3_dclk_src", CLK_SET_RATE_PARENT),
+
+ MUX("dsc_8k_txp_clk_src", mux_dsc_8k_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("dsc_8k_txp_clk", "dsc_8k_txp_clk_src", 0, 2),
+ DIV("dsc_8k_pxl_clk", "dsc_8k_txp_clk_src", 0, 2),
+ DIV("dsc_8k_cds_clk", "dsc_8k_txp_clk_src", 0, 2),
+
+ MUX("dsc_4k_txp_clk_src", mux_dsc_4k_clk_src_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT),
+ DIV("dsc_4k_txp_clk", "dsc_4k_txp_clk_src", 0, 2),
+ DIV("dsc_4k_pxl_clk", "dsc_4k_txp_clk_src", 0, 2),
+ DIV("dsc_4k_cds_clk", "dsc_4k_txp_clk_src", 0, 2),
+};
+
+static unsigned long clk_virtual_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ return (unsigned long)vop2_clk->rate;
+}
+
+static long clk_virtual_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ vop2_clk->rate = rate;
+
+ return rate;
+}
+
+static int clk_virtual_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ return 0;
+}
+
+const struct clk_ops clk_virtual_ops = {
+ .round_rate = clk_virtual_round_rate,
+ .set_rate = clk_virtual_set_rate,
+ .recalc_rate = clk_virtual_recalc_rate,
+};
+
+static u8 vop2_mux_get_parent(struct clk_hw *hw)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ // cru_dbg("%s index: %d\n", clk_hw_get_name(hw), vop2_clk->parent_index);
+ return vop2_clk->parent_index;
+}
+
+static int vop2_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ vop2_clk->parent_index = index;
+
+ // cru_dbg("%s index: %d\n", clk_hw_get_name(hw), index);
+ return 0;
+}
+
+static int vop2_clk_mux_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ // cru_dbg("%s %ld(min: %ld max: %ld)\n",
+ // clk_hw_get_name(hw), req->rate, req->min_rate, req->max_rate);
+ return __clk_mux_determine_rate(hw, req);
+}
+
+static const struct clk_ops vop2_mux_clk_ops = {
+ .get_parent = vop2_mux_get_parent,
+ .set_parent = vop2_mux_set_parent,
+ .determine_rate = vop2_clk_mux_determine_rate,
+};
+
+#define div_mask(width) ((1 << (width)) - 1)
+
+static int vop2_div_get_val(unsigned long rate, unsigned long parent_rate)
+{
+ unsigned int div, value;
+
+ div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
+
+ value = ilog2(div);
+
+ return value;
+}
+
+static unsigned long vop2_clk_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+ unsigned long rate;
+ unsigned int div;
+
+ div = 1 << vop2_clk->div_val;
+ rate = parent_rate / div;
+
+ // cru_dbg("%s rate: %ld(prate: %ld)\n", clk_hw_get_name(hw), rate, parent_rate);
+ return rate;
+}
+
+static long vop2_clk_div_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+
+ if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
+ if (*prate < rate)
+ *prate = rate;
+ if ((*prate >> vop2_clk->div.width) > rate)
+ *prate = rate;
+
+ if ((*prate % rate))
+ *prate = rate;
+
+ /* SOC PLL can't output a too low pll freq */
+ if (*prate < PLL_RATE_MIN)
+ *prate = rate << vop2_clk->div.width;
+ }
+
+ // cru_dbg("%s rate: %ld(prate: %ld)\n", clk_hw_get_name(hw), rate, *prate);
+ return rate;
+}
+
+static int vop2_clk_div_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
+{
+ struct vop2_clk *vop2_clk = to_vop2_clk(hw);
+ int div_val;
+
+ div_val = vop2_div_get_val(rate, parent_rate);
+ vop2_clk->div_val = div_val;
+
+ // cru_dbg("%s prate: %ld rate: %ld div_val: %d\n",
+ // clk_hw_get_name(hw), parent_rate, rate, div_val);
+ return 0;
+}
+
+static const struct clk_ops vop2_div_clk_ops = {
+ .recalc_rate = vop2_clk_div_recalc_rate,
+ .round_rate = vop2_clk_div_round_rate,
+ .set_rate = vop2_clk_div_set_rate,
+};
+
+static struct clk *vop2_clk_register(struct vop2 *vop2, struct vop2_clk_branch *branch)
+{
+ struct clk_init_data init = {};
+ struct vop2_clk *vop2_clk;
+ struct clk *clk;
+
+ vop2_clk = devm_kzalloc(vop2->dev, sizeof(*vop2_clk), GFP_KERNEL);
+ if (!vop2_clk)
+ return ERR_PTR(-ENOMEM);
+
+ vop2_clk->vop2 = vop2;
+ vop2_clk->hw.init = &init;
+ vop2_clk->div.shift = branch->div_shift;
+ vop2_clk->div.width = branch->div_width;
+
+ init.name = branch->name;
+ init.flags = branch->flags;
+ init.num_parents = branch->num_parents;
+ init.parent_names = branch->parent_names;
+ if (branch->branch_type == branch_divider) {
+ init.ops = &vop2_div_clk_ops;
+ } else if (branch->branch_type == branch_virtual) {
+ init.ops = &clk_virtual_ops;
+ init.num_parents = 0;
+ init.parent_names = NULL;
+ } else {
+ init.ops = &vop2_mux_clk_ops;
+ }
+
+ clk = devm_clk_register(vop2->dev, &vop2_clk->hw);
+ if (!IS_ERR(clk))
+ list_add_tail(&vop2_clk->list, &vop2->clk_list_head);
+ else
+ DRM_DEV_ERROR(vop2->dev, "Register %s failed\n", branch->name);
+
+ return clk;
+}
+
+static int vop2_clk_init(struct vop2 *vop2)
+{
+ struct vop2_clk_branch *branch = rk3588_vop_clk_branches;
+ unsigned int nr_clk = ARRAY_SIZE(rk3588_vop_clk_branches);
+ unsigned int idx;
+ struct vop2_clk *clk, *n;
+
+ INIT_LIST_HEAD(&vop2->clk_list_head);
+
+ if (vop2->data->soc_id < 3588 || vop2->hdmi0_phy_pll == NULL)
+ return 0;
+
+ list_for_each_entry_safe(clk, n, &vop2->clk_list_head, list) {
+ list_del(&clk->list);
+ }
+
+ for (idx = 0; idx < nr_clk; idx++, branch++)
+ vop2_clk_register(vop2, branch);
+
+ return 0;
+}
+/*
+ * END virtual clock
+ */
+
static int vop2_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
@@ -3167,6 +3709,12 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
return PTR_ERR(vop2->pclk);
}
+ vop2->hdmi0_phy_pll = devm_clk_get_optional(vop2->drm->dev, "hdmi0_phy_pll");
+ if (IS_ERR(vop2->hdmi0_phy_pll)) {
+ DRM_DEV_ERROR(vop2->dev, "failed to get hdmi0_phy_pll source\n");
+ return PTR_ERR(vop2->hdmi0_phy_pll);
+ }
+
vop2->irq = platform_get_irq(pdev, 0);
if (vop2->irq < 0) {
drm_err(vop2->drm, "cannot find irq for vop2\n");
@@ -3183,6 +3731,9 @@ static int vop2_bind(struct device *dev, struct device *master, void *data)
if (ret)
return ret;
+ // [CC:] rework virtual clock
+ vop2_clk_init(vop2);
+
ret = vop2_find_rgb_encoder(vop2);
if (ret >= 0) {
vop2->rgb = rockchip_rgb_init(dev, &vop2->vps[ret].crtc,
--
2.44.1
From 63daa1b92854f3c1f7f1c46fac5af1bcf3c103e5 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Mon, 15 Jan 2024 22:47:41 +0200
Subject: [PATCH 32/54] arm64: dts: rockchip: Add HDMI0 bridge to rk3588
Add DT node for the HDMI0 bridge found on RK3588 SoC.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588s.dtsi | 46 +++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
index ae3ccafe6871..dd576b87354b 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
@@ -1501,6 +1501,52 @@ i2s9_8ch: i2s@fddfc000 {
status = "disabled";
};
+ hdmi0: hdmi@fde80000 {
+ compatible = "rockchip,rk3588-dw-hdmi";
+ reg = <0x0 0xfde80000 0x0 0x20000>;
+ interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 171 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 172 IRQ_TYPE_LEVEL_HIGH 0>,
+ <GIC_SPI 360 IRQ_TYPE_LEVEL_HIGH 0>;
+ clocks = <&cru PCLK_HDMITX0>,
+ <&cru CLK_HDMIHDP0>,
+ <&cru CLK_HDMITX0_EARC>,
+ <&cru CLK_HDMITX0_REF>,
+ <&cru MCLK_I2S5_8CH_TX>,
+ <&cru HCLK_VO1>;
+ clock-names = "pclk",
+ "hdp",
+ "earc",
+ "ref",
+ "aud",
+ "hclk_vo1";
+ resets = <&cru SRST_HDMITX0_REF>, <&cru SRST_HDMIHDP0>;
+ reset-names = "ref", "hdp";
+ power-domains = <&power RK3588_PD_VO1>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&hdmim0_tx0_cec &hdmim0_tx0_hpd
+ &hdmim0_tx0_scl &hdmim0_tx0_sda>;
+ rockchip,grf = <&sys_grf>;
+ rockchip,vo1_grf = <&vo1_grf>;
+ phys = <&hdptxphy_hdmi0>;
+ phy-names = "hdmi";
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ hdmi0_in: port@0 {
+ reg = <0>;
+ };
+
+ hdmi0_out: port@1 {
+ reg = <1>;
+ };
+ };
+ };
+
qos_gpu_m0: qos@fdf35000 {
compatible = "rockchip,rk3588-qos", "syscon";
reg = <0x0 0xfdf35000 0x0 0x20>;
--
2.44.1
From e2c386a032c2076f06d92da94805185f02252187 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Mon, 15 Jan 2024 22:51:17 +0200
Subject: [PATCH 33/54] arm64: dts: rockchip: Enable HDMI0 on rock-5b
Add the necessary DT changes to enable HDMI0 on Rock 5B.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../boot/dts/rockchip/rk3588-rock-5b.dts | 30 +++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
index f013d7841e89..cd3fd9cf9402 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
@@ -4,6 +4,7 @@
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
#include <dt-bindings/usb/pd.h>
#include "rk3588.dtsi"
@@ -196,6 +197,16 @@ &gpu {
status = "okay";
};
+&hdmi0 {
+ status = "okay";
+};
+
+&hdmi0_in {
+ hdmi0_in_vp0: endpoint {
+ remote-endpoint = <&vp0_out_hdmi0>;
+ };
+};
+
&hdmirx_cma {
status = "okay";
};
@@ -208,6 +219,10 @@ &hdmirx_ctrler {
memory-region = <&hdmirx_cma>;
};
+&hdptxphy_hdmi0 {
+ status = "okay";
+};
+
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0m2_xfer>;
@@ -969,3 +984,18 @@ &usb_host1_xhci {
&usb_host2_xhci {
status = "okay";
};
+
+&vop_mmu {
+ status = "okay";
+};
+
+&vop {
+ status = "okay";
+};
+
+&vp0 {
+ vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 {
+ reg = <ROCKCHIP_VOP2_EP_HDMI0>;
+ remote-endpoint = <&hdmi0_in_vp0>;
+ };
+};
--
2.44.1
From f6c1ee98ba1c9dd9f92428ada473c2119a66079b Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Wed, 17 Jan 2024 01:53:38 +0200
Subject: [PATCH 34/54] arm64: dts: rockchip: Enable HDMI0 on rk3588-evb1
Add the necessary DT changes to enable HDMI0 on Rockchip RK3588 EVB1.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../boot/dts/rockchip/rk3588-evb1-v10.dts | 30 +++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
index dd2fb2515900..e172647ba058 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
@@ -9,6 +9,7 @@
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/pinctrl/rockchip.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
#include <dt-bindings/usb/pd.h>
#include "rk3588.dtsi"
@@ -342,6 +343,20 @@ &gpu {
status = "okay";
};
+&hdmi0 {
+ status = "okay";
+};
+
+&hdmi0_in {
+ hdmi0_in_vp0: endpoint {
+ remote-endpoint = <&vp0_out_hdmi0>;
+ };
+};
+
+&hdptxphy_hdmi0 {
+ status = "okay";
+};
+
&i2c2 {
status = "okay";
@@ -1336,3 +1351,18 @@ &usb_host1_xhci {
dr_mode = "host";
status = "okay";
};
+
+&vop_mmu {
+ status = "okay";
+};
+
+&vop {
+ status = "okay";
+};
+
+&vp0 {
+ vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 {
+ reg = <ROCKCHIP_VOP2_EP_HDMI0>;
+ remote-endpoint = <&hdmi0_in_vp0>;
+ };
+};
--
2.44.1
From 15af3f84555104275cf534724431641a9763d172 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Tue, 16 Jan 2024 03:13:38 +0200
Subject: [PATCH 35/54] [WIP] arm64: dts: rockchip: Enable HDMI0 PHY clk
provider on rk3588
The HDMI0 PHY can be used as a clock provider on RK3588, hence add the
missing #clock-cells property.
---
arch/arm64/boot/dts/rockchip/rk3588s.dtsi | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
index dd576b87354b..eb1aa7d8e475 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
@@ -2954,6 +2954,7 @@ hdptxphy_hdmi0: phy@fed60000 {
reg = <0x0 0xfed60000 0x0 0x2000>;
clocks = <&cru CLK_USB2PHY_HDPTXRXPHY_REF>, <&cru PCLK_HDPTX0>;
clock-names = "ref", "apb";
+ #clock-cells = <0>;
#phy-cells = <0>;
resets = <&cru SRST_HDPTX0>, <&cru SRST_P_HDPTX0>,
<&cru SRST_HDPTX0_INIT>, <&cru SRST_HDPTX0_CMN>,
--
2.44.1
From c490fa08603e50bd609a6c47c7f1d0fa3b53b268 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Fri, 3 Nov 2023 20:05:05 +0200
Subject: [PATCH 36/54] [WIP] arm64: dts: rockchip: Make use of HDMI0 PHY PLL
on rock-5b
The initial vop2 support for rk3588 in mainline is not able to handle
all display modes supported by connected displays, e.g.
2560x1440-75.00Hz, 2048x1152-60.00Hz, 1024x768-60.00Hz.
Additionally, it doesn't cope with non-integer refresh rates like 59.94,
29.97, 23.98, etc.
Make use of the HDMI0 PHY PLL to support the additional display modes.
Note this requires commit "drm/rockchip: vop2: Improve display modes
handling on rk3588", which needs a rework to be upstreamable.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
index cd3fd9cf9402..c551b676860c 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-rock-5b.dts
@@ -192,6 +192,11 @@ &cpu_l3 {
cpu-supply = <&vdd_cpu_lit_s0>;
};
+&display_subsystem {
+ clocks = <&hdptxphy_hdmi0>;
+ clock-names = "hdmi0_phy_pll";
+};
+
&gpu {
mali-supply = <&vdd_gpu_s0>;
status = "okay";
--
2.44.1
From c7b836ef5634362651e8ce268962156c852cca26 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Wed, 17 Jan 2024 02:00:41 +0200
Subject: [PATCH 37/54] [WIP] arm64: dts: rockchip: Make use of HDMI0 PHY PLL
on rk3588-evb1
The initial vop2 support for rk3588 in mainline is not able to handle
all display modes supported by connected displays, e.g.
2560x1440-75.00Hz, 2048x1152-60.00Hz, 1024x768-60.00Hz.
Additionally, it doesn't cope with non-integer refresh rates like 59.94,
29.97, 23.98, etc.
Make use of the HDMI0 PHY PLL to support the additional display modes.
Note this requires commit "drm/rockchip: vop2: Improve display modes
handling on rk3588", which needs a rework to be upstreamable.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
index e172647ba058..cb3925d6c7ba 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3588-evb1-v10.dts
@@ -322,6 +322,11 @@ &cpu_l3 {
cpu-supply = <&vdd_cpu_lit_s0>;
};
+&display_subsystem {
+ clocks = <&hdptxphy_hdmi0>;
+ clock-names = "hdmi0_phy_pll";
+};
+
&gmac0 {
clock_in_out = "output";
phy-handle = <&rgmii_phy>;
--
2.44.1
From 3ca942418bedc7a8a28ed1ee8f4c92bdabf1e378 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Sat, 18 May 2024 02:49:30 +0300
Subject: [PATCH 38/54] drm/bridge: dw-hdmi: Simplify clock handling
Make use of devm_clk_get_enabled() to replace devm_clk_get() and
clk_prepare_enable() for isfr and iahb clocks, and drop the now
unnecessary calls to clk_disable_unprepare().
Similarly, use devm_clk_get_optional_enabled() helper for cec clock,
which additionally allows to remove the -ENOENT test.
Moreover, the clock related members of struct dw_hdmi are not required
anymore, hence drop them.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 66 ++++++-----------------
1 file changed, 16 insertions(+), 50 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 9f2bc932c371..0031f3c54882 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -138,9 +138,6 @@ struct dw_hdmi {
struct platform_device *audio;
struct platform_device *cec;
struct device *dev;
- struct clk *isfr_clk;
- struct clk *iahb_clk;
- struct clk *cec_clk;
struct dw_hdmi_i2c *i2c;
struct hdmi_data_info hdmi_data;
@@ -3326,6 +3323,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
struct device_node *ddc_node;
struct dw_hdmi_cec_data cec;
struct dw_hdmi *hdmi;
+ struct clk *clk;
struct resource *iores = NULL;
int irq;
int ret;
@@ -3405,50 +3403,27 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
hdmi->regm = plat_data->regm;
}
- hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr");
- if (IS_ERR(hdmi->isfr_clk)) {
- ret = PTR_ERR(hdmi->isfr_clk);
+ clk = devm_clk_get_enabled(hdmi->dev, "isfr");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
dev_err(hdmi->dev, "Unable to get HDMI isfr clk: %d\n", ret);
goto err_res;
}
- ret = clk_prepare_enable(hdmi->isfr_clk);
- if (ret) {
- dev_err(hdmi->dev, "Cannot enable HDMI isfr clock: %d\n", ret);
- goto err_res;
- }
-
- hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb");
- if (IS_ERR(hdmi->iahb_clk)) {
- ret = PTR_ERR(hdmi->iahb_clk);
+ clk = devm_clk_get_enabled(hdmi->dev, "iahb");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
dev_err(hdmi->dev, "Unable to get HDMI iahb clk: %d\n", ret);
- goto err_isfr;
- }
-
- ret = clk_prepare_enable(hdmi->iahb_clk);
- if (ret) {
- dev_err(hdmi->dev, "Cannot enable HDMI iahb clock: %d\n", ret);
- goto err_isfr;
+ goto err_res;
}
- hdmi->cec_clk = devm_clk_get(hdmi->dev, "cec");
- if (PTR_ERR(hdmi->cec_clk) == -ENOENT) {
- hdmi->cec_clk = NULL;
- } else if (IS_ERR(hdmi->cec_clk)) {
- ret = PTR_ERR(hdmi->cec_clk);
+ clk = devm_clk_get_optional_enabled(hdmi->dev, "cec");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
if (ret != -EPROBE_DEFER)
dev_err(hdmi->dev, "Cannot get HDMI cec clock: %d\n",
ret);
-
- hdmi->cec_clk = NULL;
- goto err_iahb;
- } else {
- ret = clk_prepare_enable(hdmi->cec_clk);
- if (ret) {
- dev_err(hdmi->dev, "Cannot enable HDMI cec clock: %d\n",
- ret);
- goto err_iahb;
- }
+ goto err_res;
}
/* Product and revision IDs */
@@ -3462,12 +3437,12 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
dev_err(dev, "Unsupported HDMI controller (%04x:%02x:%02x)\n",
hdmi->version, prod_id0, prod_id1);
ret = -ENODEV;
- goto err_iahb;
+ goto err_res;
}
ret = dw_hdmi_detect_phy(hdmi);
if (ret < 0)
- goto err_iahb;
+ goto err_res;
dev_info(dev, "Detected HDMI TX controller v%x.%03x %s HDCP (%s)\n",
hdmi->version >> 12, hdmi->version & 0xfff,
@@ -3479,14 +3454,14 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
- goto err_iahb;
+ goto err_res;
}
ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,
dw_hdmi_irq, IRQF_SHARED,
dev_name(dev), hdmi);
if (ret)
- goto err_iahb;
+ goto err_res;
/*
* To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator
@@ -3603,11 +3578,6 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
return hdmi;
-err_iahb:
- clk_disable_unprepare(hdmi->iahb_clk);
- clk_disable_unprepare(hdmi->cec_clk);
-err_isfr:
- clk_disable_unprepare(hdmi->isfr_clk);
err_res:
i2c_put_adapter(hdmi->ddc);
@@ -3627,10 +3597,6 @@ void dw_hdmi_remove(struct dw_hdmi *hdmi)
/* Disable all interrupts */
hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
- clk_disable_unprepare(hdmi->iahb_clk);
- clk_disable_unprepare(hdmi->isfr_clk);
- clk_disable_unprepare(hdmi->cec_clk);
-
if (hdmi->i2c)
i2c_del_adapter(&hdmi->i2c->adap);
else
--
2.44.1
From 03f4db37d31eb559828bec3f1bd5abcee19d843b Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Wed, 24 Apr 2024 14:35:06 +0300
Subject: [PATCH 39/54] drm/bridge: dw-hdmi: Move common data to separate
header
In preparation to add support for the HDMI 2.1 Quad-Pixel TX Controller,
move structs and common function declarations to a new dw-hdmi-common.h
header file.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../gpu/drm/bridge/synopsys/dw-hdmi-common.h | 166 +++++++++++++++++
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 170 +++---------------
2 files changed, 193 insertions(+), 143 deletions(-)
create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
new file mode 100644
index 000000000000..9182564278e4
--- /dev/null
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __DW_HDMI_COMMON_H__
+#define __DW_HDMI_COMMON_H__
+
+#include <linux/i2c.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/spinlock_types.h>
+
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_modes.h>
+
+#include <sound/hdmi-codec.h>
+
+struct cec_notifier;
+struct device;
+struct drm_bridge_state;
+struct drm_crtc_state;
+struct drm_edid;
+struct pinctrl;
+struct pinctrl_state;
+struct platform_device;
+struct regmap;
+
+#define DDC_CI_ADDR 0x37
+#define DDC_SEGMENT_ADDR 0x30
+
+#define HDMI_EDID_LEN 512
+
+/* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */
+#define SCDC_MIN_SOURCE_VERSION 0x1
+
+#define HDMI14_MAX_TMDSCLK 340000000
+
+struct hdmi_vmode {
+ bool mdataenablepolarity;
+
+ unsigned int mpixelclock;
+ unsigned int mpixelrepetitioninput;
+ unsigned int mpixelrepetitionoutput;
+ unsigned int mtmdsclock;
+};
+
+struct hdmi_data_info {
+ unsigned int enc_in_bus_format;
+ unsigned int enc_out_bus_format;
+ unsigned int enc_in_encoding;
+ unsigned int enc_out_encoding;
+ unsigned int pix_repet_factor;
+ unsigned int hdcp_enable;
+ struct hdmi_vmode video_mode;
+ bool rgb_limited_range;
+};
+
+struct dw_hdmi_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_phy_data {
+ enum dw_hdmi_phy_type type;
+ const char *name;
+ unsigned int gen;
+ bool has_svsret;
+ int (*configure)(struct dw_hdmi *hdmi,
+ const struct dw_hdmi_plat_data *pdata,
+ unsigned long mpixelclock);
+};
+
+struct dw_hdmi {
+ struct drm_connector connector;
+ struct drm_bridge bridge;
+ struct drm_bridge *next_bridge;
+
+ unsigned int version;
+
+ struct platform_device *audio;
+ struct platform_device *cec;
+ struct device *dev;
+ struct dw_hdmi_i2c *i2c;
+
+ struct hdmi_data_info hdmi_data;
+ const struct dw_hdmi_plat_data *plat_data;
+
+ int vic;
+
+ u8 edid[HDMI_EDID_LEN];
+
+ struct {
+ const struct dw_hdmi_phy_ops *ops;
+ const char *name;
+ void *data;
+ bool enabled;
+ } phy;
+
+ struct drm_display_mode previous_mode;
+
+ struct i2c_adapter *ddc;
+ void __iomem *regs;
+ bool sink_is_hdmi;
+ bool sink_has_audio;
+
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *default_state;
+ struct pinctrl_state *unwedge_state;
+
+ struct mutex mutex; /* for state below and previous_mode */
+ enum drm_connector_force force; /* mutex-protected force state */
+ struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */
+ bool disabled; /* DRM has disabled our bridge */
+ bool bridge_is_on; /* indicates the bridge is on */
+ bool rxsense; /* rxsense state */
+ u8 phy_mask; /* desired phy int mask settings */
+ u8 mc_clkdis; /* clock disable register */
+
+ spinlock_t audio_lock;
+ struct mutex audio_mutex;
+ unsigned int sample_non_pcm;
+ unsigned int sample_width;
+ unsigned int sample_rate;
+ unsigned int channels;
+ unsigned int audio_cts;
+ unsigned int audio_n;
+ bool audio_enable;
+
+ unsigned int reg_shift;
+ struct regmap *regm;
+ void (*enable_audio)(struct dw_hdmi *hdmi);
+ void (*disable_audio)(struct dw_hdmi *hdmi);
+
+ struct mutex cec_notifier_mutex;
+ struct cec_notifier *cec_notifier;
+
+ hdmi_codec_plugged_cb plugged_cb;
+ struct device *codec_dev;
+ enum drm_connector_status last_connector_result;
+};
+
+void dw_handle_plugged_change(struct dw_hdmi *hdmi, bool plugged);
+bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi,
+ const struct drm_display_info *display);
+
+enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector,
+ bool force);
+
+int dw_hdmi_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);
+void dw_hdmi_bridge_detach(struct drm_bridge *bridge);
+void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *orig_mode,
+ const struct drm_display_mode *mode);
+enum drm_connector_status dw_hdmi_bridge_detect(struct drm_bridge *bridge);
+const struct drm_edid *dw_hdmi_bridge_edid_read(struct drm_bridge *bridge,
+ struct drm_connector *connector);
+#endif /* __DW_HDMI_COMMON_H__ */
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 0031f3c54882..b66877771f56 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -10,10 +10,8 @@
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hdmi.h>
-#include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/module.h>
-#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/regmap.h>
@@ -25,12 +23,10 @@
#include <uapi/linux/media-bus-format.h>
#include <uapi/linux/videodev2.h>
-#include <drm/bridge/dw_hdmi.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_scdc_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
-#include <drm/drm_bridge.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
@@ -38,18 +34,9 @@
#include "dw-hdmi-audio.h"
#include "dw-hdmi-cec.h"
+#include "dw-hdmi-common.h"
#include "dw-hdmi.h"
-#define DDC_CI_ADDR 0x37
-#define DDC_SEGMENT_ADDR 0x30
-
-#define HDMI_EDID_LEN 512
-
-/* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */
-#define SCDC_MIN_SOURCE_VERSION 0x1
-
-#define HDMI14_MAX_TMDSCLK 340000000
-
static const u16 csc_coeff_default[3][4] = {
{ 0x2000, 0x0000, 0x0000, 0x0000 },
{ 0x0000, 0x2000, 0x0000, 0x0000 },
@@ -86,117 +73,6 @@ static const u16 csc_coeff_rgb_full_to_rgb_limited[3][4] = {
{ 0x0000, 0x0000, 0x1b7c, 0x0020 }
};
-struct hdmi_vmode {
- bool mdataenablepolarity;
-
- unsigned int mpixelclock;
- unsigned int mpixelrepetitioninput;
- unsigned int mpixelrepetitionoutput;
- unsigned int mtmdsclock;
-};
-
-struct hdmi_data_info {
- unsigned int enc_in_bus_format;
- unsigned int enc_out_bus_format;
- unsigned int enc_in_encoding;
- unsigned int enc_out_encoding;
- unsigned int pix_repet_factor;
- unsigned int hdcp_enable;
- struct hdmi_vmode video_mode;
- bool rgb_limited_range;
-};
-
-struct dw_hdmi_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_phy_data {
- enum dw_hdmi_phy_type type;
- const char *name;
- unsigned int gen;
- bool has_svsret;
- int (*configure)(struct dw_hdmi *hdmi,
- const struct dw_hdmi_plat_data *pdata,
- unsigned long mpixelclock);
-};
-
-struct dw_hdmi {
- struct drm_connector connector;
- struct drm_bridge bridge;
- struct drm_bridge *next_bridge;
-
- unsigned int version;
-
- struct platform_device *audio;
- struct platform_device *cec;
- struct device *dev;
- struct dw_hdmi_i2c *i2c;
-
- struct hdmi_data_info hdmi_data;
- const struct dw_hdmi_plat_data *plat_data;
-
- int vic;
-
- u8 edid[HDMI_EDID_LEN];
-
- struct {
- const struct dw_hdmi_phy_ops *ops;
- const char *name;
- void *data;
- bool enabled;
- } phy;
-
- struct drm_display_mode previous_mode;
-
- struct i2c_adapter *ddc;
- void __iomem *regs;
- bool sink_is_hdmi;
- bool sink_has_audio;
-
- struct pinctrl *pinctrl;
- struct pinctrl_state *default_state;
- struct pinctrl_state *unwedge_state;
-
- struct mutex mutex; /* for state below and previous_mode */
- enum drm_connector_force force; /* mutex-protected force state */
- struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */
- bool disabled; /* DRM has disabled our bridge */
- bool bridge_is_on; /* indicates the bridge is on */
- bool rxsense; /* rxsense state */
- u8 phy_mask; /* desired phy int mask settings */
- u8 mc_clkdis; /* clock disable register */
-
- spinlock_t audio_lock;
- struct mutex audio_mutex;
- unsigned int sample_non_pcm;
- unsigned int sample_width;
- unsigned int sample_rate;
- unsigned int channels;
- unsigned int audio_cts;
- unsigned int audio_n;
- bool audio_enable;
-
- unsigned int reg_shift;
- struct regmap *regm;
- void (*enable_audio)(struct dw_hdmi *hdmi);
- void (*disable_audio)(struct dw_hdmi *hdmi);
-
- struct mutex cec_notifier_mutex;
- struct cec_notifier *cec_notifier;
-
- hdmi_codec_plugged_cb plugged_cb;
- struct device *codec_dev;
- enum drm_connector_status last_connector_result;
-};
-
#define HDMI_IH_PHY_STAT0_RX_SENSE \
(HDMI_IH_PHY_STAT0_RX_SENSE0 | HDMI_IH_PHY_STAT0_RX_SENSE1 | \
HDMI_IH_PHY_STAT0_RX_SENSE2 | HDMI_IH_PHY_STAT0_RX_SENSE3)
@@ -219,11 +95,12 @@ static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset)
return val;
}
-static void handle_plugged_change(struct dw_hdmi *hdmi, bool plugged)
+void dw_handle_plugged_change(struct dw_hdmi *hdmi, bool plugged)
{
if (hdmi->plugged_cb && hdmi->codec_dev)
hdmi->plugged_cb(hdmi->codec_dev, plugged);
}
+EXPORT_SYMBOL_GPL(dw_handle_plugged_change);
int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
struct device *codec_dev)
@@ -234,7 +111,7 @@ int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
hdmi->plugged_cb = fn;
hdmi->codec_dev = codec_dev;
plugged = hdmi->last_connector_result == connector_status_connected;
- handle_plugged_change(hdmi, plugged);
+ dw_handle_plugged_change(hdmi, plugged);
mutex_unlock(&hdmi->mutex);
return 0;
@@ -1361,8 +1238,8 @@ void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write);
/* Filter out invalid setups to avoid configuring SCDC and scrambling */
-static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi,
- const struct drm_display_info *display)
+bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi,
+ const struct drm_display_info *display)
{
/* Completely disable SCDC support for older controllers */
if (hdmi->version < 0x200a)
@@ -1387,6 +1264,7 @@ static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi,
return true;
}
+EXPORT_SYMBOL_GPL(dw_hdmi_support_scdc);
/*
* HDMI2.0 Specifies the following procedure for High TMDS Bit Rates:
@@ -2486,13 +2364,14 @@ static const struct drm_edid *dw_hdmi_edid_read(struct dw_hdmi *hdmi,
* DRM Connector Operations
*/
-static enum drm_connector_status
+enum drm_connector_status
dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
return dw_hdmi_detect(hdmi);
}
+EXPORT_SYMBOL_GPL(dw_hdmi_connector_detect);
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
{
@@ -2868,10 +2747,10 @@ static u32 *dw_hdmi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
return input_fmts;
}
-static int dw_hdmi_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)
+int dw_hdmi_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 *hdmi = bridge->driver_private;
@@ -2887,6 +2766,7 @@ static int dw_hdmi_bridge_atomic_check(struct drm_bridge *bridge,
return 0;
}
+EXPORT_SYMBOL_GPL(dw_hdmi_bridge_atomic_check);
static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
@@ -2900,7 +2780,7 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
return dw_hdmi_connector_create(hdmi);
}
-static void dw_hdmi_bridge_detach(struct drm_bridge *bridge)
+void dw_hdmi_bridge_detach(struct drm_bridge *bridge)
{
struct dw_hdmi *hdmi = bridge->driver_private;
@@ -2909,6 +2789,7 @@ static void dw_hdmi_bridge_detach(struct drm_bridge *bridge)
hdmi->cec_notifier = NULL;
mutex_unlock(&hdmi->cec_notifier_mutex);
}
+EXPORT_SYMBOL_GPL(dw_hdmi_bridge_detach);
static enum drm_mode_status
dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
@@ -2930,9 +2811,9 @@ dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
return mode_status;
}
-static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
- const struct drm_display_mode *orig_mode,
- const struct drm_display_mode *mode)
+void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *orig_mode,
+ const struct drm_display_mode *mode)
{
struct dw_hdmi *hdmi = bridge->driver_private;
@@ -2943,6 +2824,7 @@ static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
mutex_unlock(&hdmi->mutex);
}
+EXPORT_SYMBOL_GPL(dw_hdmi_bridge_mode_set);
static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
struct drm_bridge_state *old_state)
@@ -2954,7 +2836,7 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
hdmi->curr_conn = NULL;
dw_hdmi_update_power(hdmi);
dw_hdmi_update_phy_mask(hdmi);
- handle_plugged_change(hdmi, false);
+ dw_handle_plugged_change(hdmi, false);
mutex_unlock(&hdmi->mutex);
}
@@ -2973,24 +2855,26 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
hdmi->curr_conn = connector;
dw_hdmi_update_power(hdmi);
dw_hdmi_update_phy_mask(hdmi);
- handle_plugged_change(hdmi, true);
+ dw_handle_plugged_change(hdmi, true);
mutex_unlock(&hdmi->mutex);
}
-static enum drm_connector_status dw_hdmi_bridge_detect(struct drm_bridge *bridge)
+enum drm_connector_status dw_hdmi_bridge_detect(struct drm_bridge *bridge)
{
struct dw_hdmi *hdmi = bridge->driver_private;
return dw_hdmi_detect(hdmi);
}
+EXPORT_SYMBOL_GPL(dw_hdmi_bridge_detect);
-static const struct drm_edid *dw_hdmi_bridge_edid_read(struct drm_bridge *bridge,
- struct drm_connector *connector)
+const struct drm_edid *dw_hdmi_bridge_edid_read(struct drm_bridge *bridge,
+ struct drm_connector *connector)
{
struct dw_hdmi *hdmi = bridge->driver_private;
return dw_hdmi_edid_read(hdmi, connector);
}
+EXPORT_SYMBOL_GPL(dw_hdmi_bridge_edid_read);
static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
--
2.44.1
From 69b642c3b37d51e9022633382f7834479d34e0e1 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Tue, 30 Apr 2024 12:48:34 +0300
Subject: [PATCH 40/54] drm/bridge: dw-hdmi: Commonize dw_hdmi_i2c_adapter()
In preparation to support DW HDMI QP variant, add a new parameter to
dw_hdmi_i2c_adapter() which allows using a different i2c_algorithm and
move the function to the common header.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h | 2 ++
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 8 +++++---
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
index 9182564278e4..a83c2873854c 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
@@ -146,6 +146,8 @@ struct dw_hdmi {
};
void dw_handle_plugged_change(struct dw_hdmi *hdmi, bool plugged);
+struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi,
+ const struct i2c_algorithm *algo);
bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi,
const struct drm_display_info *display);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index b66877771f56..5dd0e2bc080d 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -376,7 +376,8 @@ static const struct i2c_algorithm dw_hdmi_algorithm = {
.functionality = dw_hdmi_i2c_func,
};
-static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi)
+struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi,
+ const struct i2c_algorithm *algo)
{
struct i2c_adapter *adap;
struct dw_hdmi_i2c *i2c;
@@ -392,7 +393,7 @@ static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi)
adap = &i2c->adap;
adap->owner = THIS_MODULE;
adap->dev.parent = hdmi->dev;
- adap->algo = &dw_hdmi_algorithm;
+ adap->algo = algo ? algo : &dw_hdmi_algorithm;
strscpy(adap->name, "DesignWare HDMI", sizeof(adap->name));
i2c_set_adapdata(adap, hdmi);
@@ -409,6 +410,7 @@ static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi)
return adap;
}
+EXPORT_SYMBOL_GPL(dw_hdmi_i2c_adapter);
static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts,
unsigned int n)
@@ -3373,7 +3375,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
}
}
- hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
+ hdmi->ddc = dw_hdmi_i2c_adapter(hdmi, NULL);
if (IS_ERR(hdmi->ddc))
hdmi->ddc = NULL;
}
--
2.44.1
From 2a111c90c36baf2529bf90669a2fed0d33753a0b Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Tue, 30 Apr 2024 13:06:11 +0300
Subject: [PATCH 41/54] drm/bridge: dw-hdmi: Commonize AVI infoframe setup
In preparation to support DW HDMI QP variant and minimize code
duplication, split hdmi_config_AVI() into a common
dw_hdmi_prep_avi_infoframe() function to be shared by the two drivers.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../gpu/drm/bridge/synopsys/dw-hdmi-common.h | 4 ++
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 57 +++++++++++--------
2 files changed, 37 insertions(+), 24 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
index a83c2873854c..d34c979a48cd 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
@@ -150,6 +150,10 @@ struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi,
const struct i2c_algorithm *algo);
bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi,
const struct drm_display_info *display);
+void dw_hdmi_prep_avi_infoframe(struct hdmi_avi_infoframe *frame,
+ struct dw_hdmi *hdmi,
+ const struct drm_connector *connector,
+ const struct drm_display_mode *mode);
enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector,
bool force);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 5dd0e2bc080d..81d73fbcb2e6 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -1638,66 +1638,75 @@ static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi)
HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1);
}
-static void hdmi_config_AVI(struct dw_hdmi *hdmi,
- const struct drm_connector *connector,
- const struct drm_display_mode *mode)
+void dw_hdmi_prep_avi_infoframe(struct hdmi_avi_infoframe *frame,
+ struct dw_hdmi *hdmi,
+ const struct drm_connector *connector,
+ const struct drm_display_mode *mode)
{
- struct hdmi_avi_infoframe frame;
- u8 val;
-
/* Initialise info frame from DRM mode */
- drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, mode);
+ drm_hdmi_avi_infoframe_from_display_mode(frame, connector, mode);
if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) {
- drm_hdmi_avi_infoframe_quant_range(&frame, connector, mode,
+ drm_hdmi_avi_infoframe_quant_range(frame, connector, mode,
hdmi->hdmi_data.rgb_limited_range ?
HDMI_QUANTIZATION_RANGE_LIMITED :
HDMI_QUANTIZATION_RANGE_FULL);
} else {
- frame.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
- frame.ycc_quantization_range =
+ frame->quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
+ frame->ycc_quantization_range =
HDMI_YCC_QUANTIZATION_RANGE_LIMITED;
}
if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format))
- frame.colorspace = HDMI_COLORSPACE_YUV444;
+ frame->colorspace = HDMI_COLORSPACE_YUV444;
else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format))
- frame.colorspace = HDMI_COLORSPACE_YUV422;
+ frame->colorspace = HDMI_COLORSPACE_YUV422;
else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
- frame.colorspace = HDMI_COLORSPACE_YUV420;
+ frame->colorspace = HDMI_COLORSPACE_YUV420;
else
- frame.colorspace = HDMI_COLORSPACE_RGB;
+ frame->colorspace = HDMI_COLORSPACE_RGB;
/* Set up colorimetry */
if (!hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) {
switch (hdmi->hdmi_data.enc_out_encoding) {
case V4L2_YCBCR_ENC_601:
if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV601)
- frame.colorimetry = HDMI_COLORIMETRY_EXTENDED;
+ frame->colorimetry = HDMI_COLORIMETRY_EXTENDED;
else
- frame.colorimetry = HDMI_COLORIMETRY_ITU_601;
- frame.extended_colorimetry =
+ frame->colorimetry = HDMI_COLORIMETRY_ITU_601;
+ frame->extended_colorimetry =
HDMI_EXTENDED_COLORIMETRY_XV_YCC_601;
break;
case V4L2_YCBCR_ENC_709:
if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV709)
- frame.colorimetry = HDMI_COLORIMETRY_EXTENDED;
+ frame->colorimetry = HDMI_COLORIMETRY_EXTENDED;
else
- frame.colorimetry = HDMI_COLORIMETRY_ITU_709;
- frame.extended_colorimetry =
+ frame->colorimetry = HDMI_COLORIMETRY_ITU_709;
+ frame->extended_colorimetry =
HDMI_EXTENDED_COLORIMETRY_XV_YCC_709;
break;
default: /* Carries no data */
- frame.colorimetry = HDMI_COLORIMETRY_ITU_601;
- frame.extended_colorimetry =
+ frame->colorimetry = HDMI_COLORIMETRY_ITU_601;
+ frame->extended_colorimetry =
HDMI_EXTENDED_COLORIMETRY_XV_YCC_601;
break;
}
} else {
- frame.colorimetry = HDMI_COLORIMETRY_NONE;
- frame.extended_colorimetry =
+ frame->colorimetry = HDMI_COLORIMETRY_NONE;
+ frame->extended_colorimetry =
HDMI_EXTENDED_COLORIMETRY_XV_YCC_601;
}
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_prep_avi_infoframe);
+
+static void hdmi_config_AVI(struct dw_hdmi *hdmi,
+ const struct drm_connector *connector,
+ const struct drm_display_mode *mode)
+{
+ struct hdmi_avi_infoframe frame;
+ u8 val;
+
+ dw_hdmi_prep_avi_infoframe(&frame, hdmi, connector, mode);
/*
* The Designware IP uses a different byte format from standard
--
2.44.1
From ae857523d29c06985e85bed4e5b5477d5757ed6c Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Tue, 30 Apr 2024 21:47:36 +0300
Subject: [PATCH 42/54] drm/bridge: dw-hdmi: Commonize vmode setup
In preparation to support DW HDMI QP variant and minimize code
duplication, split hdmi_av_composer() into a common dw_hdmi_prep_vmode()
function to be shared by the two drivers.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../gpu/drm/bridge/synopsys/dw-hdmi-common.h | 2 ++
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 23 +++++++++++++------
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
index d34c979a48cd..b594be2c6337 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
@@ -154,6 +154,8 @@ void dw_hdmi_prep_avi_infoframe(struct hdmi_avi_infoframe *frame,
struct dw_hdmi *hdmi,
const struct drm_connector *connector,
const struct drm_display_mode *mode);
+struct hdmi_vmode *dw_hdmi_prep_vmode(struct dw_hdmi *hdmi,
+ const struct drm_display_mode *mode);
enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector,
bool force);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 81d73fbcb2e6..5cf929f68ae9 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -1864,15 +1864,10 @@ static void hdmi_config_drm_infoframe(struct dw_hdmi *hdmi,
HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN);
}
-static void hdmi_av_composer(struct dw_hdmi *hdmi,
- const struct drm_display_info *display,
- const struct drm_display_mode *mode)
+struct hdmi_vmode *dw_hdmi_prep_vmode(struct dw_hdmi *hdmi,
+ const struct drm_display_mode *mode)
{
- u8 inv_val, bytes;
- const struct drm_hdmi_info *hdmi_info = &display->hdmi;
struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
- int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len;
- unsigned int vdisplay, hdisplay;
vmode->mpixelclock = mode->clock * 1000;
@@ -1900,6 +1895,20 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
dev_dbg(hdmi->dev, "final tmdsclock = %d\n", vmode->mtmdsclock);
+ return vmode;
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_prep_vmode);
+
+static void hdmi_av_composer(struct dw_hdmi *hdmi,
+ const struct drm_display_info *display,
+ const struct drm_display_mode *mode)
+{
+ u8 inv_val, bytes;
+ const struct drm_hdmi_info *hdmi_info = &display->hdmi;
+ struct hdmi_vmode *vmode = dw_hdmi_prep_vmode(hdmi, mode);
+ int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len;
+ unsigned int vdisplay, hdisplay;
+
/* Set up HDMI_FC_INVIDCONF */
inv_val = (hdmi->hdmi_data.hdcp_enable ||
(dw_hdmi_support_scdc(hdmi, display) &&
--
2.44.1
From d26a864cd352b0fa251f5ef4298da27190e32b4c Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Tue, 30 Apr 2024 23:51:06 +0300
Subject: [PATCH 43/54] drm/bridge: dw-hdmi: Commonize hdmi_data_info setup
In preparation to support DW HDMI QP variant and minimize code
duplication, split dw_hdmi_setup() into a common dw_hdmi_prep_data()
function to be shared by the two drivers.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../gpu/drm/bridge/synopsys/dw-hdmi-common.h | 2 ++
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 22 +++++++++++++------
2 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
index b594be2c6337..b4ad71639ffa 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
@@ -156,6 +156,8 @@ void dw_hdmi_prep_avi_infoframe(struct hdmi_avi_infoframe *frame,
const struct drm_display_mode *mode);
struct hdmi_vmode *dw_hdmi_prep_vmode(struct dw_hdmi *hdmi,
const struct drm_display_mode *mode);
+void dw_hdmi_prep_data(struct dw_hdmi *hdmi,
+ const struct drm_display_mode *mode);
enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector,
bool force);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 5cf929f68ae9..a9053b0d5f54 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2133,14 +2133,9 @@ static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi)
HDMI_IH_MUTE_FC_STAT2);
}
-static int dw_hdmi_setup(struct dw_hdmi *hdmi,
- const struct drm_connector *connector,
- const struct drm_display_mode *mode)
+void dw_hdmi_prep_data(struct dw_hdmi *hdmi,
+ const struct drm_display_mode *mode)
{
- int ret;
-
- hdmi_disable_overflow_interrupts(hdmi);
-
hdmi->vic = drm_match_cea_mode(mode);
if (!hdmi->vic) {
@@ -2180,6 +2175,19 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi,
hdmi->hdmi_data.pix_repet_factor = 0;
hdmi->hdmi_data.hdcp_enable = 0;
hdmi->hdmi_data.video_mode.mdataenablepolarity = true;
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_prep_data);
+
+
+static int dw_hdmi_setup(struct dw_hdmi *hdmi,
+ const struct drm_connector *connector,
+ const struct drm_display_mode *mode)
+{
+ int ret;
+
+ hdmi_disable_overflow_interrupts(hdmi);
+
+ dw_hdmi_prep_data(hdmi, mode);
/* HDMI Initialization Step B.1 */
hdmi_av_composer(hdmi, &connector->display_info, mode);
--
2.44.1
From b41dc7e05fe0f33cffe74ffda9b1f980afa2849c Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Wed, 1 May 2024 01:46:51 +0300
Subject: [PATCH 44/54] drm/bridge: dw-hdmi: Commonize
dw_hdmi_connector_create()
In preparation to support DW HDMI QP variant, add a new parameter to
dw_hdmi_connector_create() which allows using a different
drm_connector_funcs structure and move the function to the common
header.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h | 2 ++
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 8 +++++---
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
index b4ad71639ffa..44e27ebdd238 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-common.h
@@ -161,6 +161,8 @@ void dw_hdmi_prep_data(struct dw_hdmi *hdmi,
enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector,
bool force);
+int dw_hdmi_connector_create(struct dw_hdmi *hdmi,
+ const struct drm_connector_funcs *funcs);
int dw_hdmi_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index a9053b0d5f54..39be3949c149 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2470,7 +2470,8 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs =
.atomic_check = dw_hdmi_connector_atomic_check,
};
-static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
+int dw_hdmi_connector_create(struct dw_hdmi *hdmi,
+ const struct drm_connector_funcs *funcs)
{
struct drm_connector *connector = &hdmi->connector;
struct cec_connector_info conn_info;
@@ -2488,7 +2489,7 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
drm_connector_helper_add(connector, &dw_hdmi_connector_helper_funcs);
drm_connector_init_with_ddc(hdmi->bridge.dev, connector,
- &dw_hdmi_connector_funcs,
+ funcs ? funcs : &dw_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA,
hdmi->ddc);
@@ -2517,6 +2518,7 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
return 0;
}
+EXPORT_SYMBOL_GPL(dw_hdmi_connector_create);
/* -----------------------------------------------------------------------------
* DRM Bridge Operations
@@ -2805,7 +2807,7 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
return drm_bridge_attach(bridge->encoder, hdmi->next_bridge,
bridge, flags);
- return dw_hdmi_connector_create(hdmi);
+ return dw_hdmi_connector_create(hdmi, NULL);
}
void dw_hdmi_bridge_detach(struct drm_bridge *bridge)
--
2.44.1
From f3b76b1900c8f8433dcdbeb4e11448c965b4fbe0 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Fri, 24 May 2024 23:46:26 +0300
Subject: [PATCH 45/54] drm/rockchip: dw_hdmi: Use modern drm_device based
logging
Prefer drm_{err|info|dbg}() over deprecated DRM_DEV_{ERROR|INFO|DEBUG}()
logging macros.
Conversion done with the help of the following semantic patch, followed
by a few minor indentation adjustments:
@@
identifier T;
@@
(
-DRM_DEV_ERROR(T->dev,
+drm_err(T,
...)
|
-DRM_DEV_INFO(T->dev,
+drm_info(T,
...)
|
-DRM_DEV_DEBUG(T->dev,
+drm_dbg(T,
...)
)
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 24 ++++++++++-----------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index fe33092abbe7..2509ce19313f 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -212,7 +212,7 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(hdmi->regmap)) {
- DRM_DEV_ERROR(hdmi->dev, "Unable to get rockchip,grf\n");
+ drm_err(hdmi, "Unable to get rockchip,grf\n");
return PTR_ERR(hdmi->regmap);
}
@@ -223,7 +223,7 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
if (PTR_ERR(hdmi->ref_clk) == -EPROBE_DEFER) {
return -EPROBE_DEFER;
} else if (IS_ERR(hdmi->ref_clk)) {
- DRM_DEV_ERROR(hdmi->dev, "failed to get reference clock\n");
+ drm_err(hdmi, "failed to get reference clock\n");
return PTR_ERR(hdmi->ref_clk);
}
@@ -233,7 +233,7 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
} else if (PTR_ERR(hdmi->grf_clk) == -EPROBE_DEFER) {
return -EPROBE_DEFER;
} else if (IS_ERR(hdmi->grf_clk)) {
- DRM_DEV_ERROR(hdmi->dev, "failed to get grf clock\n");
+ drm_err(hdmi, "failed to get grf clock\n");
return PTR_ERR(hdmi->grf_clk);
}
@@ -322,17 +322,16 @@ static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
ret = clk_prepare_enable(hdmi->grf_clk);
if (ret < 0) {
- DRM_DEV_ERROR(hdmi->dev, "failed to enable grfclk %d\n", ret);
+ drm_err(hdmi, "failed to enable grfclk %d\n", ret);
return;
}
ret = regmap_write(hdmi->regmap, hdmi->chip_data->lcdsel_grf_reg, val);
if (ret != 0)
- DRM_DEV_ERROR(hdmi->dev, "Could not write to GRF: %d\n", ret);
+ drm_err(hdmi, "Could not write to GRF: %d\n", ret);
clk_disable_unprepare(hdmi->grf_clk);
- DRM_DEV_DEBUG(hdmi->dev, "vop %s output to hdmi\n",
- ret ? "LIT" : "BIG");
+ drm_dbg(hdmi, "vop %s output to hdmi\n", ret ? "LIT" : "BIG");
}
static int
@@ -592,7 +591,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
ret = rockchip_hdmi_parse_dt(hdmi);
if (ret) {
if (ret != -EPROBE_DEFER)
- DRM_DEV_ERROR(hdmi->dev, "Unable to parse OF data\n");
+ drm_err(hdmi, "Unable to parse OF data\n");
return ret;
}
@@ -600,26 +599,25 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
if (IS_ERR(hdmi->phy)) {
ret = PTR_ERR(hdmi->phy);
if (ret != -EPROBE_DEFER)
- DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n");
+ drm_err(hdmi, "failed to get phy\n");
return ret;
}
ret = regulator_enable(hdmi->avdd_0v9);
if (ret) {
- DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd0v9: %d\n", ret);
+ drm_err(hdmi, "failed to enable avdd0v9: %d\n", ret);
goto err_avdd_0v9;
}
ret = regulator_enable(hdmi->avdd_1v8);
if (ret) {
- DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd1v8: %d\n", ret);
+ drm_err(hdmi, "failed to enable avdd1v8: %d\n", ret);
goto err_avdd_1v8;
}
ret = clk_prepare_enable(hdmi->ref_clk);
if (ret) {
- DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n",
- ret);
+ drm_err(hdmi, "Failed to enable HDMI reference clock: %d\n", ret);
goto err_clk;
}
--
2.44.1
From 14a35060796b9c44c63e0155cc777a59601e8fa1 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Tue, 7 May 2024 19:16:01 +0300
Subject: [PATCH 46/54] drm/rockchip: dw_hdmi: Simplify clock handling
Make use of devm_clk_get_optional_enabled() to replace devm_clk_get()
and clk_prepare_enable() for ref_clk and drop the now unnecessary calls
to clk_disable_unprepare().
Additionally, use devm_clk_get_optional() helper for grf_clk to replace
the open coding call to devm_clk_get() followed by the -ENOENT test.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 38 ++++++++-------------
1 file changed, 14 insertions(+), 24 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 2509ce19313f..7d07039ef096 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -209,6 +209,7 @@ static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
{
struct device_node *np = hdmi->dev->of_node;
+ int ret;
hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(hdmi->regmap)) {
@@ -216,25 +217,23 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
return PTR_ERR(hdmi->regmap);
}
- hdmi->ref_clk = devm_clk_get_optional(hdmi->dev, "ref");
+ hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "ref");
if (!hdmi->ref_clk)
- hdmi->ref_clk = devm_clk_get_optional(hdmi->dev, "vpll");
+ hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "vpll");
- if (PTR_ERR(hdmi->ref_clk) == -EPROBE_DEFER) {
- return -EPROBE_DEFER;
- } else if (IS_ERR(hdmi->ref_clk)) {
- drm_err(hdmi, "failed to get reference clock\n");
- return PTR_ERR(hdmi->ref_clk);
+ if (IS_ERR(hdmi->ref_clk)) {
+ ret = PTR_ERR(hdmi->ref_clk);
+ if (ret != -EPROBE_DEFER)
+ drm_err(hdmi, "failed to get reference clock\n");
+ return ret;
}
- hdmi->grf_clk = devm_clk_get(hdmi->dev, "grf");
- if (PTR_ERR(hdmi->grf_clk) == -ENOENT) {
- hdmi->grf_clk = NULL;
- } else if (PTR_ERR(hdmi->grf_clk) == -EPROBE_DEFER) {
- return -EPROBE_DEFER;
- } else if (IS_ERR(hdmi->grf_clk)) {
- drm_err(hdmi, "failed to get grf clock\n");
- return PTR_ERR(hdmi->grf_clk);
+ hdmi->grf_clk = devm_clk_get_optional(hdmi->dev, "grf");
+ if (IS_ERR(hdmi->grf_clk)) {
+ ret = PTR_ERR(hdmi->grf_clk);
+ if (ret != -EPROBE_DEFER)
+ drm_err(hdmi, "failed to get grf clock\n");
+ return ret;
}
hdmi->avdd_0v9 = devm_regulator_get(hdmi->dev, "avdd-0v9");
@@ -615,12 +614,6 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
goto err_avdd_1v8;
}
- ret = clk_prepare_enable(hdmi->ref_clk);
- if (ret) {
- drm_err(hdmi, "Failed to enable HDMI reference clock: %d\n", ret);
- goto err_clk;
- }
-
if (hdmi->chip_data == &rk3568_chip_data) {
regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1,
HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK |
@@ -649,8 +642,6 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
err_bind:
drm_encoder_cleanup(encoder);
- clk_disable_unprepare(hdmi->ref_clk);
-err_clk:
regulator_disable(hdmi->avdd_1v8);
err_avdd_1v8:
regulator_disable(hdmi->avdd_0v9);
@@ -665,7 +656,6 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
dw_hdmi_unbind(hdmi->hdmi);
drm_encoder_cleanup(&hdmi->encoder.encoder);
- clk_disable_unprepare(hdmi->ref_clk);
regulator_disable(hdmi->avdd_1v8);
regulator_disable(hdmi->avdd_0v9);
--
2.44.1
From 6ba21ebba361b107956768931a60367dab071fdd Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Wed, 15 May 2024 02:24:03 +0300
Subject: [PATCH 47/54] drm/rockchip: dw_hdmi: Use devm_regulator_get_enable()
The regulators are only enabled at bind() and disabled at unbind(),
hence replace the boilerplate code by making use of
devm_regulator_get_enable() helper.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 34 ++++-----------------
1 file changed, 6 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 7d07039ef096..edfd877c98fc 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -77,8 +77,6 @@ struct rockchip_hdmi {
struct clk *ref_clk;
struct clk *grf_clk;
struct dw_hdmi *hdmi;
- struct regulator *avdd_0v9;
- struct regulator *avdd_1v8;
struct phy *phy;
};
@@ -236,15 +234,13 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
return ret;
}
- hdmi->avdd_0v9 = devm_regulator_get(hdmi->dev, "avdd-0v9");
- if (IS_ERR(hdmi->avdd_0v9))
- return PTR_ERR(hdmi->avdd_0v9);
+ ret = devm_regulator_get_enable(hdmi->dev, "avdd-0v9");
+ if (ret)
+ return ret;
- hdmi->avdd_1v8 = devm_regulator_get(hdmi->dev, "avdd-1v8");
- if (IS_ERR(hdmi->avdd_1v8))
- return PTR_ERR(hdmi->avdd_1v8);
+ ret = devm_regulator_get_enable(hdmi->dev, "avdd-1v8");
- return 0;
+ return ret;
}
static enum drm_mode_status
@@ -602,18 +598,6 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
return ret;
}
- ret = regulator_enable(hdmi->avdd_0v9);
- if (ret) {
- drm_err(hdmi, "failed to enable avdd0v9: %d\n", ret);
- goto err_avdd_0v9;
- }
-
- ret = regulator_enable(hdmi->avdd_1v8);
- if (ret) {
- drm_err(hdmi, "failed to enable avdd1v8: %d\n", ret);
- goto err_avdd_1v8;
- }
-
if (hdmi->chip_data == &rk3568_chip_data) {
regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1,
HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK |
@@ -642,10 +626,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
err_bind:
drm_encoder_cleanup(encoder);
- regulator_disable(hdmi->avdd_1v8);
-err_avdd_1v8:
- regulator_disable(hdmi->avdd_0v9);
-err_avdd_0v9:
+
return ret;
}
@@ -656,9 +637,6 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
dw_hdmi_unbind(hdmi->hdmi);
drm_encoder_cleanup(&hdmi->encoder.encoder);
-
- regulator_disable(hdmi->avdd_1v8);
- regulator_disable(hdmi->avdd_0v9);
}
static const struct component_ops dw_hdmi_rockchip_ops = {
--
2.44.1
From 0de628fab9d6fa101e6b973599a89fdc84504f73 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Mon, 27 May 2024 22:38:38 +0300
Subject: [PATCH 48/54] drm/rockchip: dw_hdmi: Drop useless assignments of
mpll_cfg, cur_ctr, phy_config
The mpll_cfg, cur_ctr and phy_config members in struct dw_hdmi_plat_data
are only used to configure the Synopsys PHYs supported internally by DW
HDMI transmitter driver (gpu/drm/bridge/synopsys/dw-hdmi.c), via
hdmi_phy_configure_dwc_hdmi_3d_tx(), which is further invoked from
dw_hdmi_phy_init(). This is part of the internal
dw_hdmi_synopsys_phy_ops struct, setup in dw_hdmi_detect_phy().
To handle vendor PHYs, the DW HDMI driver doesn't use the internal PHY
ops, but expects the glue layers to provide the phy_ops and phy_name
members of struct dw_hdmi_plat_data.
Drop the unnecessary assignments of DW internal PHY related members from
structs rk3228_hdmi_drv_data and rk3328_hdmi_drv_data, since both set
the phy_force_vendor flag and provide the expected vendor PHY data.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 6 ------
1 file changed, 6 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index edfd877c98fc..ca6728a43159 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -444,9 +444,6 @@ static struct rockchip_hdmi_chip_data rk3228_chip_data = {
static const struct dw_hdmi_plat_data rk3228_hdmi_drv_data = {
.mode_valid = dw_hdmi_rockchip_mode_valid,
- .mpll_cfg = rockchip_mpll_cfg,
- .cur_ctr = rockchip_cur_ctr,
- .phy_config = rockchip_phy_config,
.phy_data = &rk3228_chip_data,
.phy_ops = &rk3228_hdmi_phy_ops,
.phy_name = "inno_dw_hdmi_phy2",
@@ -481,9 +478,6 @@ static struct rockchip_hdmi_chip_data rk3328_chip_data = {
static const struct dw_hdmi_plat_data rk3328_hdmi_drv_data = {
.mode_valid = dw_hdmi_rockchip_mode_valid,
- .mpll_cfg = rockchip_mpll_cfg,
- .cur_ctr = rockchip_cur_ctr,
- .phy_config = rockchip_phy_config,
.phy_data = &rk3328_chip_data,
.phy_ops = &rk3328_hdmi_phy_ops,
.phy_name = "inno_dw_hdmi_phy2",
--
2.44.1
From 73bf7e4a35984bca8b89f4acb277f45c0786f7b9 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Thu, 28 Mar 2024 00:47:03 +0200
Subject: [PATCH 49/54] dt-bindings: display: rockchip,dw-hdmi: Add compatible
for RK3588
Document the DW HDMI TX Controller found on Rockchip RK3588 SoC.
Since RK3588 uses different clocks than previous Rockchip SoCs and also
requires a couple of reset lines and some additional properties, provide
the required changes in the binding to be able to handle all variants.
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
.../display/rockchip/rockchip,dw-hdmi.yaml | 124 +++++++++++++-----
1 file changed, 89 insertions(+), 35 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
index 2aac62219ff6..651189de7af5 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
@@ -12,10 +12,8 @@ maintainers:
description: |
The HDMI transmitter is a Synopsys DesignWare HDMI 1.4 TX controller IP
with a companion PHY IP.
-
-allOf:
- - $ref: ../bridge/synopsys,dw-hdmi.yaml#
- - $ref: /schemas/sound/dai-common.yaml#
+ RK3588 SoC updated the TX controller IP to DW HDMI 2.1 Quad-Pixel (QP),
+ while making use of a HDMI/eDP TX Combo PHY based on a Samsung IP block.
properties:
compatible:
@@ -25,6 +23,7 @@ properties:
- rockchip,rk3328-dw-hdmi
- rockchip,rk3399-dw-hdmi
- rockchip,rk3568-dw-hdmi
+ - rockchip,rk3588-dw-hdmi
reg-io-width:
const: 4
@@ -40,36 +39,6 @@ properties:
A 1.8V supply that powers up the SoC internal circuitry. The pin name on the
SoC usually is HDMI_TX_AVDD_1V8.
- clocks:
- minItems: 2
- items:
- - {}
- - {}
- # The next three clocks are all optional, but shall be specified in this
- # order when present.
- - description: The HDMI CEC controller main clock
- - description: Power for GRF IO
- - description: External clock for some HDMI PHY (old clock name, deprecated)
- - description: External clock for some HDMI PHY (new name)
-
- clock-names:
- minItems: 2
- items:
- - {}
- - {}
- - enum:
- - cec
- - grf
- - vpll
- - ref
- - enum:
- - grf
- - vpll
- - ref
- - enum:
- - vpll
- - ref
-
ddc-i2c-bus:
$ref: /schemas/types.yaml#/definitions/phandle
description:
@@ -131,13 +100,98 @@ properties:
required:
- compatible
- reg
- - reg-io-width
- clocks
- clock-names
- interrupts
- ports
- rockchip,grf
+allOf:
+ - $ref: /schemas/sound/dai-common.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - rockchip,rk3588-dw-hdmi
+ then:
+ properties:
+ reg:
+ maxItems: 1
+
+ clocks:
+ minItems: 1
+ items:
+ - description: APB system interface clock
+ # The next clocks are optional, but shall be specified in this
+ # order when present.
+ - description: TMDS/FRL link clock
+ - description: EARC RX biphase clock
+ - description: Reference clock
+ - description: Audio interface clock
+ - description: Video datapath clock
+
+ clock-names:
+ minItems: 1
+ items:
+ - const: pclk
+ - enum: [hdp, earc, ref, aud, hclk_vo1]
+ - enum: [earc, ref, aud, hclk_vo1]
+ - enum: [ref, aud, hclk_vo1]
+ - enum: [aud, hclk_vo1]
+ - const: hclk_vo1
+
+ resets:
+ minItems: 2
+ maxItems: 2
+
+ reset-names:
+ items:
+ - const: ref
+ - const: hdp
+
+ interrupts:
+ minItems: 1
+ maxItems: 5
+
+ rockchip,vo1_grf:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ phandle to the VO1 GRF
+
+ required:
+ - resets
+ - reset-names
+ - rockchip,vo1_grf
+
+ else:
+ $ref: ../bridge/synopsys,dw-hdmi.yaml#
+
+ properties:
+ clocks:
+ minItems: 2
+ items:
+ - {}
+ - {}
+ # The next three clocks are all optional, but shall be specified in this
+ # order when present.
+ - description: The HDMI CEC controller main clock
+ - description: Power for GRF IO
+ - description: External clock for some HDMI PHY (old clock name, deprecated)
+ - description: External clock for some HDMI PHY (new name)
+
+ clock-names:
+ minItems: 2
+ items:
+ - {}
+ - {}
+ - enum: [cec, grf, vpll, ref]
+ - enum: [grf, vpll, ref]
+ - enum: [vpll, ref]
+
+ required:
+ - reg-io-width
+
unevaluatedProperties: false
examples:
--
2.44.1
From 3c174706f556aa659210015116982f06b34b8dda Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Mon, 20 May 2024 14:49:50 +0300
Subject: [PATCH 50/54] drm/bridge: synopsys: Add DW HDMI QP TX controller
driver
The Synopsys DesignWare HDMI 2.1 Quad-Pixel (QP) TX controller supports
the following features, among others:
* Fixed Rate Link (FRL)
* 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 driver to enable basic support, 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 <algea.cao@rock-chips.com>
Signed-off-by: Algea Cao <algea.cao@rock-chips.com>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/bridge/synopsys/Makefile | 2 +-
drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 796 ++++++++++++++++++
drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h | 831 +++++++++++++++++++
include/drm/bridge/dw_hdmi.h | 8 +
4 files changed, 1636 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile
index ce715562e9e5..8354e4879f70 100644
--- a/drivers/gpu/drm/bridge/synopsys/Makefile
+++ b/drivers/gpu/drm/bridge/synopsys/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
+obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o dw-hdmi-qp.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
new file mode 100644
index 000000000000..09a4bf7246d6
--- /dev/null
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c
@@ -0,0 +1,796 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
+ * Copyright (c) 2024 Collabora Ltd.
+ *
+ * Author: Algea Cao <algea.cao@rock-chips.com>
+ * Author: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
+ */
+#include <linux/hdmi.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include <drm/display/drm_hdmi_helper.h>
+#include <drm/display/drm_scdc_helper.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include <uapi/linux/media-bus-format.h>
+#include <uapi/linux/videodev2.h>
+
+#include "dw-hdmi-common.h"
+#include "dw-hdmi-qp.h"
+
+static void dw_hdmi_qp_write(struct dw_hdmi *hdmi, unsigned int val, int offset)
+{
+ regmap_write(hdmi->regm, offset, val);
+}
+
+static unsigned int dw_hdmi_qp_read(struct dw_hdmi *hdmi, int offset)
+{
+ unsigned int val = 0;
+
+ regmap_read(hdmi->regm, offset, &val);
+
+ return val;
+}
+
+static void dw_hdmi_qp_mod(struct dw_hdmi *hdmi, unsigned int data,
+ unsigned int mask, unsigned int reg)
+{
+ regmap_update_bits(hdmi->regm, reg, mask, data);
+}
+
+static void dw_hdmi_qp_i2c_init(struct dw_hdmi *hdmi)
+{
+ /* 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);
+}
+
+static int dw_hdmi_qp_i2c_read(struct dw_hdmi *hdmi,
+ unsigned char *buf, unsigned int length)
+{
+ struct dw_hdmi_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);
+
+ 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 *hdmi,
+ unsigned char *buf, unsigned int length)
+{
+ struct dw_hdmi_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 *hdmi = i2c_get_adapdata(adap);
+ struct dw_hdmi_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.
+ * TOFIX: 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;
+ }
+ }
+
+ mutex_lock(&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, 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);
+
+ mutex_unlock(&i2c->lock);
+
+ 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_algorithm = {
+ .master_xfer = dw_hdmi_qp_i2c_xfer,
+ .functionality = dw_hdmi_qp_i2c_func,
+};
+
+/* -----------------------------------------------------------------------------
+ * HDMI TX Setup
+ */
+
+static void hdmi_infoframe_set_checksum(u8 *ptr, int size)
+{
+ u8 csum = 0;
+ int i;
+
+ ptr[3] = 0;
+ /* compute checksum */
+ for (i = 0; i < size; i++)
+ csum += ptr[i];
+
+ ptr[3] = 256 - csum;
+}
+
+static void hdmi_config_AVI(struct dw_hdmi *hdmi,
+ const struct drm_connector *connector,
+ const struct drm_display_mode *mode)
+{
+ struct hdmi_avi_infoframe frame;
+ u32 val, i, j;
+ u8 buf[17];
+
+ dw_hdmi_prep_avi_infoframe(&frame, hdmi, connector, mode);
+
+ frame.scan_mode = HDMI_SCAN_MODE_NONE;
+ frame.video_code = hdmi->vic;
+
+ hdmi_avi_infoframe_pack_only(&frame, buf, 17);
+
+ /* mode which vic >= 128 must use avi version 3 */
+ if (hdmi->vic >= 128) {
+ frame.version = 3;
+ buf[1] = frame.version;
+ buf[4] &= 0x1f;
+ buf[4] |= ((frame.colorspace & 0x7) << 5);
+ buf[7] = frame.video_code;
+ hdmi_infoframe_set_checksum(buf, 17);
+ }
+
+ /*
+ * The Designware IP uses a different byte format from standard
+ * AVI info frames, though generally the bits are in the correct
+ * bytes.
+ */
+
+ val = (frame.version << 8) | (frame.length << 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 = buf[i * 4 + j + 3];
+ val |= buf[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);
+}
+
+static void hdmi_config_drm_infoframe(struct dw_hdmi *hdmi,
+ const struct drm_connector *connector)
+{
+ const struct drm_connector_state *conn_state = connector->state;
+ struct hdr_output_metadata *hdr_metadata;
+ struct hdmi_drm_infoframe frame;
+ u8 buffer[30];
+ ssize_t err;
+ int i;
+ u32 val;
+
+ if (!hdmi->plat_data->use_drm_infoframe)
+ return;
+
+ dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN);
+
+ if (!hdmi->connector.hdr_sink_metadata.hdmi_type1.eotf) {
+ dev_dbg(hdmi->dev, "No need to set HDR metadata in infoframe\n");
+ return;
+ }
+
+ if (!conn_state->hdr_output_metadata) {
+ dev_dbg(hdmi->dev, "source metadata not set yet\n");
+ return;
+ }
+
+ hdr_metadata = (struct hdr_output_metadata *)
+ conn_state->hdr_output_metadata->data;
+
+ if (!(hdmi->connector.hdr_sink_metadata.hdmi_type1.eotf &
+ BIT(hdr_metadata->hdmi_metadata_type1.eotf))) {
+ dev_err(hdmi->dev, "EOTF %d not supported\n",
+ hdr_metadata->hdmi_metadata_type1.eotf);
+ return;
+ }
+
+ err = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state);
+ if (err < 0)
+ return;
+
+ err = hdmi_drm_infoframe_pack(&frame, buffer, sizeof(buffer));
+ if (err < 0) {
+ dev_err(hdmi->dev, "Failed to pack drm infoframe: %zd\n", err);
+ return;
+ }
+
+ val = (frame.version << 8) | (frame.length << 16);
+ dw_hdmi_qp_write(hdmi, val, PKT_DRMI_CONTENTS0);
+
+ for (i = 0; i <= frame.length; i++) {
+ if (i % 4 == 0)
+ val = buffer[3 + i];
+ val |= buffer[3 + i] << ((i % 4) * 8);
+
+ if (i % 4 == 3 || (i == (frame.length)))
+ 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);
+}
+
+static int dw_hdmi_qp_setup(struct dw_hdmi *hdmi,
+ struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ u8 bytes = 0;
+ int ret;
+
+ dw_hdmi_prep_data(hdmi, mode);
+
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
+ hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1;
+ hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 1;
+ }
+
+ /*
+ * According to the dw-hdmi specification 6.4.2
+ * vp_pr_cd[3:0]:
+ * 0000b: No pixel repetition (pixel sent only once)
+ * 0001b: Pixel sent two times (pixel repeated once)
+ */
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+ hdmi->hdmi_data.pix_repet_factor = 1;
+
+ /* HDMI Initialization Step B.1 */
+ dw_hdmi_prep_vmode(hdmi, mode);
+
+ /* HDMI Initialization Step B.2 */
+ ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data,
+ &connector->display_info,
+ &hdmi->previous_mode);
+ if (ret)
+ return ret;
+ hdmi->phy.enabled = true;
+
+ /* not for DVI mode */
+ if (hdmi->sink_is_hdmi) {
+ dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__);
+
+ dw_hdmi_qp_mod(hdmi, 0, OPMODE_DVI, LINK_CONFIG0);
+ dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
+
+ if (hdmi->hdmi_data.video_mode.mtmdsclock > HDMI14_MAX_TMDSCLK) {
+ if (dw_hdmi_support_scdc(hdmi, &connector->display_info)) {
+ drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION, &bytes);
+ drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION,
+ min_t(u8, bytes, SCDC_MIN_SOURCE_VERSION));
+ drm_scdc_set_high_tmds_clock_ratio(connector, 1);
+ drm_scdc_set_scrambling(connector, 1);
+ }
+ dw_hdmi_qp_write(hdmi, 1, SCRAMB_CONFIG0);
+ } else {
+ if (dw_hdmi_support_scdc(hdmi, &connector->display_info)) {
+ drm_scdc_set_high_tmds_clock_ratio(connector, 0);
+ drm_scdc_set_scrambling(connector, 0);
+ }
+ dw_hdmi_qp_write(hdmi, 0, SCRAMB_CONFIG0);
+ }
+
+ /* HDMI Initialization Step F */
+ hdmi_config_AVI(hdmi, connector, mode);
+ hdmi_config_drm_infoframe(hdmi, connector);
+ } else {
+ dev_dbg(hdmi->dev, "%s DVI mode\n", __func__);
+
+ dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
+ dw_hdmi_qp_mod(hdmi, OPMODE_DVI, OPMODE_DVI, LINK_CONFIG0);
+ }
+
+ return 0;
+}
+
+static void dw_hdmi_qp_update_power(struct dw_hdmi *hdmi)
+{
+ int force = hdmi->force;
+
+ if (hdmi->disabled) {
+ force = DRM_FORCE_OFF;
+ } else if (force == DRM_FORCE_UNSPECIFIED) {
+ if (hdmi->rxsense)
+ force = DRM_FORCE_ON;
+ else
+ force = DRM_FORCE_OFF;
+ }
+
+ if (force == DRM_FORCE_OFF) {
+ if (hdmi->bridge_is_on) {
+ if (hdmi->phy.enabled) {
+ hdmi->phy.ops->disable(hdmi, hdmi->phy.data);
+ hdmi->phy.enabled = false;
+ }
+
+ hdmi->bridge_is_on = false;
+ }
+ } else {
+ if (!hdmi->bridge_is_on) {
+ hdmi->bridge_is_on = true;
+
+ /*
+ * The curr_conn field is guaranteed to be valid here, as this function
+ * is only be called when !hdmi->disabled.
+ */
+ dw_hdmi_qp_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode);
+ }
+ }
+}
+
+static void dw_hdmi_qp_connector_force(struct drm_connector *connector)
+{
+ struct dw_hdmi *hdmi =
+ container_of(connector, struct dw_hdmi, connector);
+
+ mutex_lock(&hdmi->mutex);
+ hdmi->force = connector->force;
+ dw_hdmi_qp_update_power(hdmi);
+ mutex_unlock(&hdmi->mutex);
+}
+
+static const struct drm_connector_funcs dw_hdmi_qp_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = dw_hdmi_connector_detect,
+ .destroy = drm_connector_cleanup,
+ .force = dw_hdmi_qp_connector_force,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int dw_hdmi_qp_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct dw_hdmi *hdmi = bridge->driver_private;
+
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
+ return drm_bridge_attach(bridge->encoder, hdmi->next_bridge,
+ bridge, flags);
+
+ return dw_hdmi_connector_create(hdmi, &dw_hdmi_qp_connector_funcs);
+}
+
+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 *hdmi = bridge->driver_private;
+ const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
+ enum drm_mode_status mode_status = MODE_OK;
+
+ if (pdata->mode_valid)
+ mode_status = pdata->mode_valid(hdmi, pdata->priv_data, info,
+ mode);
+
+ return mode_status;
+}
+
+static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_state)
+{
+ struct dw_hdmi *hdmi = bridge->driver_private;
+
+ mutex_lock(&hdmi->mutex);
+ hdmi->disabled = true;
+ hdmi->curr_conn = NULL;
+ dw_hdmi_qp_update_power(hdmi);
+ dw_handle_plugged_change(hdmi, false);
+ mutex_unlock(&hdmi->mutex);
+}
+
+static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_state)
+{
+ struct dw_hdmi *hdmi = bridge->driver_private;
+ struct drm_atomic_state *state = old_state->base.state;
+ struct drm_connector *connector;
+
+ connector = drm_atomic_get_new_connector_for_encoder(state,
+ bridge->encoder);
+
+ mutex_lock(&hdmi->mutex);
+ hdmi->disabled = false;
+ hdmi->curr_conn = connector;
+ dw_hdmi_qp_update_power(hdmi);
+ dw_handle_plugged_change(hdmi, true);
+ mutex_unlock(&hdmi->mutex);
+}
+
+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,
+ .attach = dw_hdmi_qp_bridge_attach,
+ .detach = dw_hdmi_bridge_detach,
+ .atomic_check = dw_hdmi_bridge_atomic_check,
+ .atomic_enable = dw_hdmi_qp_bridge_atomic_enable,
+ .atomic_disable = dw_hdmi_qp_bridge_atomic_disable,
+ .mode_set = dw_hdmi_bridge_mode_set,
+ .mode_valid = dw_hdmi_qp_bridge_mode_valid,
+ .detect = dw_hdmi_bridge_detect,
+ .edid_read = dw_hdmi_bridge_edid_read,
+};
+
+static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id)
+{
+ struct dw_hdmi *hdmi = dev_id;
+ struct dw_hdmi_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 int dw_hdmi_qp_detect_phy(struct dw_hdmi *hdmi)
+{
+ if (!hdmi->plat_data->phy_force_vendor) {
+ dev_err(hdmi->dev, "Internal HDMI PHY not supported\n");
+ return -ENODEV;
+ }
+
+ /* Vendor PHYs require support from the glue layer. */
+ if (!hdmi->plat_data->phy_ops || !hdmi->plat_data->phy_name) {
+ dev_err(hdmi->dev,
+ "Vendor HDMI PHY not supported by glue layer\n");
+ return -ENODEV;
+ }
+
+ hdmi->phy.ops = hdmi->plat_data->phy_ops;
+ hdmi->phy.data = hdmi->plat_data->phy_data;
+ hdmi->phy.name = hdmi->plat_data->phy_name;
+
+ return 0;
+}
+
+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 *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);
+
+ dw_hdmi_qp_i2c_init(hdmi);
+
+ if (hdmi->phy.ops->setup_hpd)
+ hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
+}
+
+static struct dw_hdmi *
+dw_hdmi_qp_probe(struct platform_device *pdev,
+ const struct dw_hdmi_plat_data *plat_data)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *ddc_node;
+ struct dw_hdmi *hdmi;
+ struct resource *iores = NULL;
+ int irq, ret;
+
+ hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+ if (!hdmi)
+ return ERR_PTR(-ENOMEM);
+
+ hdmi->plat_data = plat_data;
+ hdmi->dev = dev;
+ hdmi->disabled = true;
+ hdmi->rxsense = true;
+ hdmi->last_connector_result = connector_status_disconnected;
+
+ mutex_init(&hdmi->mutex);
+ mutex_init(&hdmi->audio_mutex);
+ mutex_init(&hdmi->cec_notifier_mutex);
+ spin_lock_init(&hdmi->audio_lock);
+
+ ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
+ if (ddc_node) {
+ hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node);
+ of_node_put(ddc_node);
+ if (!hdmi->ddc) {
+ dev_dbg(hdmi->dev, "failed to read ddc node\n");
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ } else {
+ dev_dbg(hdmi->dev, "no ddc property found\n");
+ }
+
+ if (!plat_data->regm) {
+ const struct regmap_config *reg_config;
+
+ reg_config = &dw_hdmi_qp_regmap_config;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hdmi->regs = devm_ioremap_resource(dev, iores);
+ if (IS_ERR(hdmi->regs)) {
+ ret = PTR_ERR(hdmi->regs);
+ goto err_res;
+ }
+
+ hdmi->regm = devm_regmap_init_mmio(dev, hdmi->regs, reg_config);
+ if (IS_ERR(hdmi->regm)) {
+ dev_err(dev, "Failed to configure regmap\n");
+ ret = PTR_ERR(hdmi->regm);
+ goto err_res;
+ }
+ } else {
+ hdmi->regm = plat_data->regm;
+ }
+
+ /* Allow SCDC advertising in dw_hdmi_support_scdc() */
+ hdmi->version = 0x200a;
+
+ ret = dw_hdmi_qp_detect_phy(hdmi);
+ if (ret < 0)
+ goto err_res;
+
+ dw_hdmi_qp_init_hw(hdmi);
+
+ if ((dw_hdmi_qp_read(hdmi, CMU_STATUS) & DISPLAY_CLK_MONITOR) ==
+ DISPLAY_CLK_LOCKED)
+ hdmi->disabled = false;
+
+ /* Not handled for now: IRQ0 (AVP), IRQ1 (CEC), IRQ2 (EARC) */
+ irq = platform_get_irq(pdev, 3);
+ if (irq < 0) {
+ ret = irq;
+ goto err_res;
+ }
+
+ ret = devm_request_threaded_irq(dev, irq,
+ dw_hdmi_qp_main_hardirq, NULL,
+ IRQF_SHARED, dev_name(dev), hdmi);
+ if (ret)
+ goto err_res;
+
+ /* If DDC bus is not specified, try to register HDMI I2C bus */
+ if (!hdmi->ddc) {
+ hdmi->ddc = dw_hdmi_i2c_adapter(hdmi, &dw_hdmi_algorithm);
+ if (IS_ERR(hdmi->ddc))
+ hdmi->ddc = NULL;
+ }
+
+ 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_HPD;
+ hdmi->bridge.ddc = hdmi->ddc;
+ hdmi->bridge.of_node = pdev->dev.of_node;
+ hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+
+ drm_bridge_add(&hdmi->bridge);
+
+ return hdmi;
+
+err_res:
+ i2c_put_adapter(hdmi->ddc);
+
+ return ERR_PTR(ret);
+}
+
+static void dw_hdmi_qp_remove(struct dw_hdmi *hdmi)
+{
+ drm_bridge_remove(&hdmi->bridge);
+
+ if (hdmi->audio && !IS_ERR(hdmi->audio))
+ platform_device_unregister(hdmi->audio);
+ if (!IS_ERR(hdmi->cec))
+ platform_device_unregister(hdmi->cec);
+
+ if (hdmi->i2c)
+ i2c_del_adapter(&hdmi->i2c->adap);
+ else
+ i2c_put_adapter(hdmi->ddc);
+}
+
+struct dw_hdmi *dw_hdmi_qp_bind(struct platform_device *pdev,
+ struct drm_encoder *encoder,
+ struct dw_hdmi_plat_data *plat_data)
+{
+ struct dw_hdmi *hdmi;
+ int ret;
+
+ hdmi = dw_hdmi_qp_probe(pdev, plat_data);
+ if (IS_ERR(hdmi))
+ return hdmi;
+
+ ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0);
+ if (ret) {
+ dw_hdmi_qp_remove(hdmi);
+ return ERR_PTR(ret);
+ }
+
+ return hdmi;
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind);
+
+void dw_hdmi_qp_unbind(struct dw_hdmi *hdmi)
+{
+ dw_hdmi_qp_remove(hdmi);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_qp_unbind);
+
+void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi *hdmi)
+{
+ dw_hdmi_qp_init_hw(hdmi);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_qp_resume);
+
+MODULE_AUTHOR("Algea Cao <algea.cao@rock-chips.com>");
+MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@collabora.com>");
+MODULE_DESCRIPTION("DW HDMI QP transmitter driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dw-hdmi-qp");
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
new file mode 100644
index 000000000000..4cac70f2d11d
--- /dev/null
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h
@@ -0,0 +1,831 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) Rockchip Electronics Co.Ltd
+ * Author:
+ * Algea Cao <algea.cao@rock-chips.com>
+ */
+#ifndef __DW_HDMI_QP_H__
+#define __DW_HDMI_QP_H__
+/* 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__ */
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index 6a46baa0737c..a9cc4604222a 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -131,6 +131,7 @@ struct dw_hdmi_plat_data {
unsigned long input_bus_encoding;
bool use_drm_infoframe;
bool ycbcr_420_allowed;
+ bool is_hdmi_qp;
/*
* Private data passed to all the .mode_valid() and .configure_phy()
@@ -162,6 +163,7 @@ struct dw_hdmi_plat_data {
unsigned long mpixelclock);
unsigned int disable_cec : 1;
+
};
struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
@@ -206,6 +208,12 @@ void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
bool force, bool disabled, bool rxsense);
void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data);
+void dw_hdmi_qp_unbind(struct dw_hdmi *hdmi);
+struct dw_hdmi *dw_hdmi_qp_bind(struct platform_device *pdev,
+ struct drm_encoder *encoder,
+ struct dw_hdmi_plat_data *plat_data);
+void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi *hdmi);
+
bool dw_hdmi_bus_fmt_is_420(struct dw_hdmi *hdmi);
#endif /* __IMX_HDMI_H__ */
--
2.44.1
From aad4f4cecc70fa0170fcad63933585f71c975164 Mon Sep 17 00:00:00 2001
From: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
Date: Mon, 20 May 2024 15:07:08 +0300
Subject: [PATCH 51/54] drm/rockchip: dw_hdmi: Add basic RK3588 support
RK3588 SoC updated the Synopsis DesignWare HDMI transmitter used in the
older SoCs to Quad-Pixel (QP) variant, which is HDMI 2.1 compliant,
while making use of 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@30Hz,
without audio, CEC or any of the HDMI 2.1 specific features.
Co-developed-by: Algea Cao <algea.cao@rock-chips.com>
Signed-off-by: Algea Cao <algea.cao@rock-chips.com>
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
---
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 261 +++++++++++++++++++-
1 file changed, 253 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index ca6728a43159..70f7006bde08 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -29,8 +29,8 @@
#define RK3288_GRF_SOC_CON6 0x025C
#define RK3288_HDMI_LCDC_SEL BIT(4)
-#define RK3328_GRF_SOC_CON2 0x0408
+#define RK3328_GRF_SOC_CON2 0x0408
#define RK3328_HDMI_SDAIN_MSK BIT(11)
#define RK3328_HDMI_SCLIN_MSK BIT(10)
#define RK3328_HDMI_HPD_IOE BIT(2)
@@ -54,6 +54,21 @@
#define RK3568_HDMI_SDAIN_MSK BIT(15)
#define RK3568_HDMI_SCLIN_MSK BIT(14)
+#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 (0x3 << 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)
/**
@@ -71,6 +86,7 @@ struct rockchip_hdmi_chip_data {
struct rockchip_hdmi {
struct device *dev;
struct regmap *regmap;
+ struct regmap *vo1_regmap;
struct rockchip_encoder encoder;
const struct rockchip_hdmi_chip_data *chip_data;
const struct dw_hdmi_plat_data *plat_data;
@@ -78,6 +94,10 @@ struct rockchip_hdmi {
struct clk *grf_clk;
struct dw_hdmi *hdmi;
struct phy *phy;
+
+ bool is_hdmi_qp;
+ struct gpio_desc *qp_enable_gpio;
+ struct delayed_work qp_hpd_work;
};
static struct rockchip_hdmi *to_rockchip_hdmi(struct drm_encoder *encoder)
@@ -206,8 +226,12 @@ static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
{
+ static const char * const qp_clk_names[] = {
+ "pclk", "hdp", "earc", "aud", "hclk_vo1",
+ };
struct device_node *np = hdmi->dev->of_node;
- int ret;
+ struct clk *qp_clk;
+ int ret, i;
hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(hdmi->regmap)) {
@@ -234,6 +258,34 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
return ret;
}
+ if (hdmi->is_hdmi_qp) {
+ hdmi->vo1_regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,vo1_grf");
+ if (IS_ERR(hdmi->vo1_regmap)) {
+ drm_err(hdmi, "Unable to get rockchip,vo1_grf\n");
+ return PTR_ERR(hdmi->vo1_regmap);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qp_clk_names); i++) {
+ qp_clk = devm_clk_get_optional_enabled(hdmi->dev, qp_clk_names[i]);
+
+ if (IS_ERR(qp_clk)) {
+ ret = PTR_ERR(qp_clk);
+ if (ret != -EPROBE_DEFER)
+ drm_err(hdmi, "failed to get %s clock: %d\n",
+ qp_clk_names[i], ret);
+ return ret;
+ }
+ }
+
+ hdmi->qp_enable_gpio = devm_gpiod_get_optional(hdmi->dev, "enable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(hdmi->qp_enable_gpio)) {
+ ret = PTR_ERR(hdmi->qp_enable_gpio);
+ drm_err(hdmi, "failed to request enable GPIO: %d\n", ret);
+ return ret;
+ }
+ }
+
ret = devm_regulator_get_enable(hdmi->dev, "avdd-0v9");
if (ret)
return ret;
@@ -303,8 +355,32 @@ static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
{
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
+ struct drm_crtc *crtc = encoder->crtc;
u32 val;
- int ret;
+ int ret, rate;
+
+ if (hdmi->is_hdmi_qp) {
+ /* Unconditionally switch to TMDS as FRL is not yet supported */
+ gpiod_set_value(hdmi->qp_enable_gpio, 1);
+
+ if (crtc && crtc->state) {
+ clk_set_rate(hdmi->ref_clk,
+ crtc->state->adjusted_mode.crtc_clock * 1000);
+ /*
+ * 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
+ */
+ if (hdmi->phy) {
+ rate = crtc->state->mode.clock * 10;
+ phy_set_bus_width(hdmi->phy, rate);
+ drm_dbg(hdmi, "%s set bus_width=%u\n",
+ __func__, rate);
+ }
+ }
+ }
if (hdmi->chip_data->lcdsel_grf_reg < 0)
return;
@@ -356,6 +432,9 @@ static int dw_hdmi_rockchip_genphy_init(struct dw_hdmi *dw_hdmi, void *data,
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
+ if (hdmi->is_hdmi_qp)
+ dw_hdmi_set_high_tmds_clock_ratio(dw_hdmi, display);
+
return phy_power_on(hdmi->phy);
}
@@ -430,6 +509,29 @@ static void dw_hdmi_rk3328_setup_hpd(struct dw_hdmi *dw_hdmi, void *data)
RK3328_HDMI_HPD_IOE));
}
+static enum drm_connector_status
+dw_hdmi_rk3588_read_hpd(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)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_rk3588_setup_hpd(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)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_phy_ops rk3228_hdmi_phy_ops = {
.init = dw_hdmi_rockchip_genphy_init,
.disable = dw_hdmi_rockchip_genphy_disable,
@@ -513,6 +615,82 @@ static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = {
.use_drm_infoframe = true,
};
+static const struct dw_hdmi_phy_ops rk3588_hdmi_phy_ops = {
+ .init = dw_hdmi_rockchip_genphy_init,
+ .disable = dw_hdmi_rockchip_genphy_disable,
+ .read_hpd = dw_hdmi_rk3588_read_hpd,
+ .setup_hpd = dw_hdmi_rk3588_setup_hpd,
+};
+
+struct rockchip_hdmi_chip_data rk3588_chip_data = {
+ .lcdsel_grf_reg = -1,
+};
+
+static const struct dw_hdmi_plat_data rk3588_hdmi_drv_data = {
+ .phy_data = &rk3588_chip_data,
+ .phy_ops = &rk3588_hdmi_phy_ops,
+ .phy_name = "samsung_hdptx_phy",
+ .phy_force_vendor = true,
+ .use_drm_infoframe = true,
+ .is_hdmi_qp = true,
+};
+
+static void dw_hdmi_rk3588_hpd_work(struct work_struct *p_work)
+{
+ struct rockchip_hdmi *hdmi = container_of(p_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_rk3588_hardirq(int irq, void *dev_id)
+{
+ struct rockchip_hdmi *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_rk3588_irq(int irq, void *dev_id)
+{
+ struct rockchip_hdmi *hdmi = dev_id;
+ u32 intr_stat, val;
+ int debounce_ms;
+
+ 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);
+
+ debounce_ms = intr_stat & RK3588_HDMI0_LEVEL_INT ? 150 : 20;
+ mod_delayed_work(system_wq, &hdmi->qp_hpd_work,
+ msecs_to_jiffies(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_rockchip_dt_ids[] = {
{ .compatible = "rockchip,rk3228-dw-hdmi",
.data = &rk3228_hdmi_drv_data
@@ -529,6 +707,9 @@ static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
{ .compatible = "rockchip,rk3568-dw-hdmi",
.data = &rk3568_hdmi_drv_data
},
+ { .compatible = "rockchip,rk3588-dw-hdmi",
+ .data = &rk3588_hdmi_drv_data
+ },
{},
};
MODULE_DEVICE_TABLE(of, dw_hdmi_rockchip_dt_ids);
@@ -542,7 +723,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
struct drm_device *drm = data;
struct drm_encoder *encoder;
struct rockchip_hdmi *hdmi;
- int ret;
+ int ret, irq;
+ u32 val;
if (!pdev->dev.of_node)
return -ENODEV;
@@ -553,13 +735,14 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
plat_data = devm_kmemdup(&pdev->dev, match->data,
- sizeof(*plat_data), GFP_KERNEL);
+ sizeof(*plat_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
hdmi->dev = &pdev->dev;
hdmi->plat_data = plat_data;
hdmi->chip_data = plat_data->phy_data;
+ hdmi->is_hdmi_qp = plat_data->is_hdmi_qp;
plat_data->phy_data = hdmi;
plat_data->priv_data = hdmi;
encoder = &hdmi->encoder.encoder;
@@ -598,6 +781,37 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
RK3568_HDMI_SCLIN_MSK,
RK3568_HDMI_SDAIN_MSK |
RK3568_HDMI_SCLIN_MSK));
+ } else if (hdmi->is_hdmi_qp) {
+ 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->vo1_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->vo1_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->qp_hpd_work, dw_hdmi_rk3588_hpd_work);
+
+ irq = platform_get_irq(pdev, 4);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(hdmi->dev, irq,
+ dw_hdmi_rk3588_hardirq,
+ dw_hdmi_rk3588_irq,
+ IRQF_SHARED, "dw-hdmi-qp-hpd",
+ hdmi);
+ if (ret)
+ return ret;
}
drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
@@ -605,7 +819,10 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
platform_set_drvdata(pdev, hdmi);
- hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
+ if (hdmi->is_hdmi_qp)
+ hdmi->hdmi = dw_hdmi_qp_bind(pdev, encoder, plat_data);
+ else
+ hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
/*
* If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
@@ -629,7 +846,13 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
{
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
- dw_hdmi_unbind(hdmi->hdmi);
+ if (hdmi->is_hdmi_qp) {
+ cancel_delayed_work_sync(&hdmi->qp_hpd_work);
+ dw_hdmi_qp_unbind(hdmi->hdmi);
+ } else {
+ dw_hdmi_unbind(hdmi->hdmi);
+ }
+
drm_encoder_cleanup(&hdmi->encoder.encoder);
}
@@ -651,8 +874,30 @@ static void dw_hdmi_rockchip_remove(struct platform_device *pdev)
static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev)
{
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
+ u32 val;
+
+ if (hdmi->is_hdmi_qp) {
+ 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->vo1_regmap, RK3588_GRF_VO1_CON3, val);
- dw_hdmi_resume(hdmi->hdmi);
+ 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->vo1_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);
+ } else {
+ dw_hdmi_resume(hdmi->hdmi);
+ }
return 0;
}
--
2.44.1
From 4682a4746176a2d0dbeb71e6085a12391aa3c5dd Mon Sep 17 00:00:00 2001
From: Detlev Casanova <detlev.casanova@collabora.com>
Date: Fri, 3 May 2024 14:27:39 -0400
Subject: [PATCH 52/54] vop2: Add clock resets support
At the end of initialization, each VP clock needs to be reset before
they can be used.
Failing to do so can put the VOP in an undefined state where the
generated HDMI signal is either lost or not matching the selected mode.
Signed-off-by: Detlev Casanova <detlev.casanova@collabora.com>
---
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 30 ++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
index 86d2c15af39f..6f626f2f198a 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -19,6 +19,7 @@
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
+#include <linux/reset.h>
#include <linux/swab.h>
#include <drm/drm.h>
@@ -159,6 +160,7 @@ struct vop2_win {
struct vop2_video_port {
struct drm_crtc crtc;
struct vop2 *vop2;
+ struct reset_control *dclk_rst;
struct clk *dclk;
unsigned int id;
const struct vop2_video_port_data *data;
@@ -2064,6 +2066,26 @@ static struct vop2_clk *vop2_clk_get(struct vop2 *vop2, const char *name)
return NULL;
}
+static int vop2_clk_reset(struct vop2_video_port *vp)
+{
+ struct reset_control *rstc = vp->dclk_rst;
+ struct vop2 *vop2 = vp->vop2;
+ int ret;
+
+ if (!rstc)
+ return 0;
+
+ ret = reset_control_assert(rstc);
+ if (ret < 0)
+ drm_warn(vop2->drm, "failed to assert reset\n");
+ udelay(10);
+ ret = reset_control_deassert(rstc);
+ if (ret < 0)
+ drm_warn(vop2->drm, "failed to deassert reset\n");
+
+ return ret;
+}
+
static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
@@ -2233,6 +2255,8 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl);
+ vop2_clk_reset(vp);
+
drm_crtc_vblank_on(crtc);
vop2_unlock(vop2);
@@ -2920,6 +2944,12 @@ static int vop2_create_crtcs(struct vop2 *vop2)
vp->data = vp_data;
snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", vp->id);
+ vp->dclk_rst = devm_reset_control_get_optional(vop2->dev, dclk_name);
+ if (IS_ERR(vp->dclk_rst)) {
+ drm_err(vop2->drm, "failed to get %s reset\n", dclk_name);
+ return PTR_ERR(vp->dclk_rst);
+ }
+
vp->dclk = devm_clk_get(vop2->dev, dclk_name);
if (IS_ERR(vp->dclk)) {
drm_err(vop2->drm, "failed to get %s\n", dclk_name);
--
2.44.1
From ec32c5f52b353b97633cc166e5b1e8342d2ae78e Mon Sep 17 00:00:00 2001
From: Detlev Casanova <detlev.casanova@collabora.com>
Date: Fri, 3 May 2024 14:28:12 -0400
Subject: [PATCH 53/54] arm64: dts: rockchip: Add VOP clock resets for rk3588s
This adds the needed clock resets for all rk3588(s) based SOCs.
Signed-off-by: Detlev Casanova <detlev.casanova@collabora.com>
---
arch/arm64/boot/dts/rockchip/rk3588s.dtsi | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
index eb1aa7d8e475..0fecbf46e127 100644
--- a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi
@@ -1403,6 +1403,14 @@ vop: vop@fdd90000 {
"pclk_vop";
iommus = <&vop_mmu>;
power-domains = <&power RK3588_PD_VOP>;
+ resets = <&cru SRST_D_VOP0>,
+ <&cru SRST_D_VOP1>,
+ <&cru SRST_D_VOP2>,
+ <&cru SRST_D_VOP3>;
+ reset-names = "dclk_vp0",
+ "dclk_vp1",
+ "dclk_vp2",
+ "dclk_vp3";
rockchip,grf = <&sys_grf>;
rockchip,vop-grf = <&vop_grf>;
rockchip,vo1-grf = <&vo1_grf>;
--
2.44.1
From dc492a647595f2866fb2e20ccf576bcaff42a109 Mon Sep 17 00:00:00 2001
From: Detlev Casanova <detlev.casanova@collabora.com>
Date: Mon, 6 May 2024 13:54:01 -0400
Subject: [PATCH 54/54] dt-bindings: display: vop2: Add VP clock resets
Add the documentation for VOP2 video ports reset clocks.
One reset can be set per video port.
Signed-off-by: Detlev Casanova <detlev.casanova@collabora.com>
---
.../display/rockchip/rockchip-vop2.yaml | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml
index 2531726af306..941fd059498d 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml
@@ -65,6 +65,22 @@ properties:
- const: dclk_vp3
- const: pclk_vop
+ resets:
+ minItems: 3
+ items:
+ - description: Pixel clock reset for video port 0.
+ - description: Pixel clock reset for video port 1.
+ - description: Pixel clock reset for video port 2.
+ - description: Pixel clock reset for video port 3.
+
+ reset-names:
+ minItems: 3
+ items:
+ - const: dclk_vp0
+ - const: dclk_vp1
+ - const: dclk_vp2
+ - const: dclk_vp3
+
rockchip,grf:
$ref: /schemas/types.yaml#/definitions/phandle
description:
@@ -128,6 +144,11 @@ allOf:
clock-names:
minItems: 7
+ resets:
+ minItems: 4
+ reset-names:
+ minItems: 4
+
ports:
required:
- port@0
@@ -183,6 +204,12 @@ examples:
"dclk_vp0",
"dclk_vp1",
"dclk_vp2";
+ resets = <&cru SRST_VOP0>,
+ <&cru SRST_VOP1>,
+ <&cru SRST_VOP2>;
+ reset-names = "dclk_vp0",
+ "dclk_vp1",
+ "dclk_vp2";
power-domains = <&power RK3568_PD_VO>;
iommus = <&vop_mmu>;
vop_out: ports {
--
2.44.1