* [PATCH 16/27] dt-bindings: display: rockchip: dw-hdmi: Add additional clock
From: Sascha Hauer @ 2022-01-26 14:55 UTC (permalink / raw)
To: dri-devel
Cc: linux-arm-kernel, linux-rockchip, devicetree, kernel, Andy Yan,
Benjamin Gaignard, Michael Riesch, Sandy Huang,
Heiko Stübner, Peter Geis, Sascha Hauer
In-Reply-To: <20220126145549.617165-1-s.hauer@pengutronix.de>
The rk3568 HDMI has an additional clock that needs to be enabled for the
HDMI controller to work. The purpose of that clock is not clear. It is
named "hclk" in the downstream driver, so use the same name.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
.../bindings/display/rockchip/rockchip,dw-hdmi.yaml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
index d7cb2b2be60e..7dd753630b46 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml
@@ -44,12 +44,13 @@ properties:
items:
- {}
- {}
- # The next three clocks are all optional, but shall be specified in this
+ # The next four 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)
+ - description: hclk
clock-names:
minItems: 2
@@ -61,13 +62,17 @@ properties:
- grf
- vpll
- ref
+ - hclk
- enum:
- grf
- vpll
- ref
+ - hclk
- enum:
- vpll
- ref
+ - hclk
+ - const: hclk
ddc-i2c-bus:
$ref: /schemas/types.yaml#/definitions/phandle
--
2.30.2
^ permalink raw reply related
* [PATCH 20/27] arm64: dts: rockchip: rk356x: Add VOP2 nodes
From: Sascha Hauer @ 2022-01-26 14:55 UTC (permalink / raw)
To: dri-devel
Cc: linux-arm-kernel, linux-rockchip, devicetree, kernel, Andy Yan,
Benjamin Gaignard, Michael Riesch, Sandy Huang,
Heiko Stübner, Peter Geis, Sascha Hauer
In-Reply-To: <20220126145549.617165-1-s.hauer@pengutronix.de>
The VOP2 is the display output controller on the RK3568. Add the node
for it to the dtsi file along with the required display-subsystem node
and the iommu node.
changes since v3:
- Bring back gamma_lut regs
- Drop redundant _vop suffix from clock names
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
arch/arm64/boot/dts/rockchip/rk3566.dtsi | 4 ++
arch/arm64/boot/dts/rockchip/rk3568.dtsi | 4 ++
arch/arm64/boot/dts/rockchip/rk356x.dtsi | 51 ++++++++++++++++++++++++
include/dt-bindings/soc/rockchip,vop2.h | 14 +++++++
4 files changed, 73 insertions(+)
create mode 100644 include/dt-bindings/soc/rockchip,vop2.h
diff --git a/arch/arm64/boot/dts/rockchip/rk3566.dtsi b/arch/arm64/boot/dts/rockchip/rk3566.dtsi
index 3839eef5e4f7..595fa2562cb8 100644
--- a/arch/arm64/boot/dts/rockchip/rk3566.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3566.dtsi
@@ -18,3 +18,7 @@ power-domain@RK3568_PD_PIPE {
#power-domain-cells = <0>;
};
};
+
+&vop {
+ compatible = "rockchip,rk3566-vop";
+};
diff --git a/arch/arm64/boot/dts/rockchip/rk3568.dtsi b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
index 2fd313a295f8..1e55efb6fcfd 100644
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
@@ -95,3 +95,7 @@ power-domain@RK3568_PD_PIPE {
#power-domain-cells = <0>;
};
};
+
+&vop {
+ compatible = "rockchip,rk3568-vop";
+};
diff --git a/arch/arm64/boot/dts/rockchip/rk356x.dtsi b/arch/arm64/boot/dts/rockchip/rk356x.dtsi
index a68033a23975..4008bd666d01 100644
--- a/arch/arm64/boot/dts/rockchip/rk356x.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x.dtsi
@@ -10,6 +10,7 @@
#include <dt-bindings/pinctrl/rockchip.h>
#include <dt-bindings/power/rk3568-power.h>
#include <dt-bindings/soc/rockchip,boot-mode.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
#include <dt-bindings/thermal/thermal.h>
/ {
@@ -129,6 +130,11 @@ opp-1800000000 {
};
};
+ display_subsystem: display-subsystem {
+ compatible = "rockchip,display-subsystem";
+ ports = <&vop_out>;
+ };
+
firmware {
scmi: scmi {
compatible = "arm,scmi-smc";
@@ -451,6 +457,51 @@ gmac1_mtl_tx_setup: tx-queues-config {
};
};
+ vop: vop@fe040000 {
+ reg = <0x0 0xfe040000 0x0 0x3000>, <0x0 0xfe044000 0x0 0x1000>;
+ reg-names = "regs", "gamma_lut";
+ interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_VOP>, <&cru HCLK_VOP>, <&cru DCLK_VOP0>, <&cru DCLK_VOP1>, <&cru DCLK_VOP2>;
+ clock-names = "aclk", "hclk", "dclk_vp0", "dclk_vp1", "dclk_vp2";
+ iommus = <&vop_mmu>;
+ power-domains = <&power RK3568_PD_VO>;
+ rockchip,grf = <&grf>;
+ status = "disabled";
+
+ vop_out: ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ vp0: port@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ vp1: port@1 {
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
+ vp2: port@2 {
+ reg = <2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+ };
+
+ vop_mmu: iommu@fe043e00 {
+ compatible = "rockchip,rk3568-iommu";
+ reg = <0x0 0xfe043e00 0x0 0x100>, <0x0 0xfe043f00 0x0 0x100>;
+ interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_VOP>, <&cru HCLK_VOP>;
+ clock-names = "aclk", "iface";
+ #iommu-cells = <0>;
+ status = "disabled";
+ };
+
qos_gpu: qos@fe128000 {
compatible = "rockchip,rk3568-qos", "syscon";
reg = <0x0 0xfe128000 0x0 0x20>;
diff --git a/include/dt-bindings/soc/rockchip,vop2.h b/include/dt-bindings/soc/rockchip,vop2.h
new file mode 100644
index 000000000000..0a87bc90564a
--- /dev/null
+++ b/include/dt-bindings/soc/rockchip,vop2.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */
+
+#ifndef __DT_BINDINGS_ROCKCHIP_VOP2_H
+#define __DT_BINDINGS_ROCKCHIP_VOP2_H
+
+#define RK3568_VOP2_EP_RGB 0
+#define RK3568_VOP2_EP_HDMI 1
+#define RK3568_VOP2_EP_EDP 2
+#define RK3568_VOP2_EP_MIPI0 3
+#define RK3568_VOP2_EP_LVDS0 4
+#define RK3568_VOP2_EP_MIPI1 5
+#define RK3568_VOP2_EP_LVDS1 6
+
+#endif /* __DT_BINDINGS_ROCKCHIP_VOP2_H */
--
2.30.2
^ permalink raw reply related
* [PATCH 22/27] arm64: dts: rockchip: rk3568-evb: Enable VOP2 and hdmi
From: Sascha Hauer @ 2022-01-26 14:55 UTC (permalink / raw)
To: dri-devel
Cc: linux-arm-kernel, linux-rockchip, devicetree, kernel, Andy Yan,
Benjamin Gaignard, Michael Riesch, Sandy Huang,
Heiko Stübner, Peter Geis, Sascha Hauer
In-Reply-To: <20220126145549.617165-1-s.hauer@pengutronix.de>
This enabled the VOP2 display controller along with hdmi and the
required port routes which is enough to get a picture out of the
hdmi port of the board.
Changes since v3:
- Fix HDMI connector type
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
.../boot/dts/rockchip/rk3568-evb1-v10.dts | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3568-evb1-v10.dts b/arch/arm64/boot/dts/rockchip/rk3568-evb1-v10.dts
index 184e2aa2416a..9a669240e49a 100644
--- a/arch/arm64/boot/dts/rockchip/rk3568-evb1-v10.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3568-evb1-v10.dts
@@ -7,6 +7,7 @@
/dts-v1/;
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/rockchip.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
#include "rk3568.dtsi"
/ {
@@ -33,6 +34,17 @@ dc_12v: dc-12v {
regulator-max-microvolt = <12000000>;
};
+ hdmi-con {
+ compatible = "hdmi-connector";
+ type = "a";
+
+ port {
+ hdmi_con_in: endpoint {
+ remote-endpoint = <&hdmi_out_con>;
+ };
+ };
+ };
+
vcc3v3_sys: vcc3v3-sys {
compatible = "regulator-fixed";
regulator-name = "vcc3v3_sys";
@@ -106,6 +118,12 @@ &gmac1m1_rgmii_clk
status = "okay";
};
+&hdmi {
+ avdd-0v9-supply = <&vdda0v9_image>;
+ avdd-1v8-supply = <&vcca1v8_image>;
+ status = "okay";
+};
+
&i2c0 {
status = "okay";
@@ -390,3 +408,33 @@ &sdmmc0 {
&uart2 {
status = "okay";
};
+
+&vop {
+ assigned-clocks = <&cru DCLK_VOP0>, <&cru DCLK_VOP1>;
+ assigned-clock-parents = <&pmucru PLL_HPLL>, <&cru PLL_VPLL>;
+ status = "okay";
+};
+
+&vop_mmu {
+ status = "okay";
+};
+
+&hdmi_in {
+ hdmi_in_vp0: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&vp0_out_hdmi>;
+ };
+};
+
+&hdmi_out {
+ hdmi_out_con: endpoint {
+ remote-endpoint = <&hdmi_con_in>;
+ };
+};
+
+&vp0 {
+ vp0_out_hdmi: endpoint@RK3568_VOP2_EP_HDMI {
+ reg = <RK3568_VOP2_EP_HDMI>;
+ remote-endpoint = <&hdmi_in_vp0>;
+ };
+};
--
2.30.2
^ permalink raw reply related
* [PATCH 24/27] clk: rk3568: drop CLK_SET_RATE_PARENT from dclk_vop*
From: Sascha Hauer @ 2022-01-26 14:55 UTC (permalink / raw)
To: dri-devel
Cc: linux-arm-kernel, linux-rockchip, devicetree, kernel, Andy Yan,
Benjamin Gaignard, Michael Riesch, Sandy Huang,
Heiko Stübner, Peter Geis, Sascha Hauer
In-Reply-To: <20220126145549.617165-1-s.hauer@pengutronix.de>
The pixel clocks dclk_vop[012] can be clocked from hpll, vpll, gpll or
cpll. gpll and cpll also drive many other clocks, so changing the
dclk_vop[012] clocks could change these other clocks as well. Drop
CLK_SET_RATE_PARENT to fix that. With this change the VOP2 driver can
only adjust the pixel clocks with the divider between the PLL and the
dclk_vop[012] which means the user may have to adjust the PLL clock to a
suitable rate using the assigned-clock-rate device tree property.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/clk/rockchip/clk-rk3568.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/rockchip/clk-rk3568.c b/drivers/clk/rockchip/clk-rk3568.c
index 9d889fc46811..7687c62d1fa8 100644
--- a/drivers/clk/rockchip/clk-rk3568.c
+++ b/drivers/clk/rockchip/clk-rk3568.c
@@ -1044,13 +1044,13 @@ static struct rockchip_clk_branch rk3568_clk_branches[] __initdata = {
RK3568_CLKGATE_CON(20), 8, GFLAGS),
GATE(HCLK_VOP, "hclk_vop", "hclk_vo", 0,
RK3568_CLKGATE_CON(20), 9, GFLAGS),
- COMPOSITE(DCLK_VOP0, "dclk_vop0", hpll_vpll_gpll_cpll_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT,
+ COMPOSITE(DCLK_VOP0, "dclk_vop0", hpll_vpll_gpll_cpll_p, CLK_SET_RATE_NO_REPARENT,
RK3568_CLKSEL_CON(39), 10, 2, MFLAGS, 0, 8, DFLAGS,
RK3568_CLKGATE_CON(20), 10, GFLAGS),
- COMPOSITE(DCLK_VOP1, "dclk_vop1", hpll_vpll_gpll_cpll_p, CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT,
+ COMPOSITE(DCLK_VOP1, "dclk_vop1", hpll_vpll_gpll_cpll_p, CLK_SET_RATE_NO_REPARENT,
RK3568_CLKSEL_CON(40), 10, 2, MFLAGS, 0, 8, DFLAGS,
RK3568_CLKGATE_CON(20), 11, GFLAGS),
- COMPOSITE(DCLK_VOP2, "dclk_vop2", hpll_vpll_gpll_cpll_p, 0,
+ COMPOSITE(DCLK_VOP2, "dclk_vop2", hpll_vpll_gpll_cpll_p, CLK_SET_RATE_NO_REPARENT,
RK3568_CLKSEL_CON(41), 10, 2, MFLAGS, 0, 8, DFLAGS,
RK3568_CLKGATE_CON(20), 12, GFLAGS),
GATE(CLK_VOP_PWM, "clk_vop_pwm", "xin24m", 0,
--
2.30.2
^ permalink raw reply related
* [PATCH 19/27] arm64: dts: rockchip: rk3399: rename HDMI ref clock to 'ref'
From: Sascha Hauer @ 2022-01-26 14:55 UTC (permalink / raw)
To: dri-devel
Cc: linux-arm-kernel, linux-rockchip, devicetree, kernel, Andy Yan,
Benjamin Gaignard, Michael Riesch, Sandy Huang,
Heiko Stübner, Peter Geis, Sascha Hauer
In-Reply-To: <20220126145549.617165-1-s.hauer@pengutronix.de>
The reference clock for the HDMI controller has been renamed to 'ref',
the previous 'vpll' name is only left for compatibility in the driver.
Rename the clock to the new name.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
arch/arm64/boot/dts/rockchip/rk3399.dtsi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3399.dtsi b/arch/arm64/boot/dts/rockchip/rk3399.dtsi
index 080457a68e3c..d0add619b0d2 100644
--- a/arch/arm64/boot/dts/rockchip/rk3399.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3399.dtsi
@@ -1884,7 +1884,7 @@ hdmi: hdmi@ff940000 {
<&cru SCLK_HDMI_CEC>,
<&cru PCLK_VIO_GRF>,
<&cru PLL_VPLL>;
- clock-names = "iahb", "isfr", "cec", "grf", "vpll";
+ clock-names = "iahb", "isfr", "cec", "grf", "ref";
power-domains = <&power RK3399_PD_HDCP>;
reg-io-width = <4>;
rockchip,grf = <&grf>;
--
2.30.2
^ permalink raw reply related
* [PATCH 27/27] drm: rockchip: Add VOP2 driver
From: Sascha Hauer @ 2022-01-26 14:55 UTC (permalink / raw)
To: dri-devel
Cc: linux-arm-kernel, linux-rockchip, devicetree, kernel, Andy Yan,
Benjamin Gaignard, Michael Riesch, Sandy Huang,
Heiko Stübner, Peter Geis, Sascha Hauer
In-Reply-To: <20220126145549.617165-1-s.hauer@pengutronix.de>
From: Andy Yan <andy.yan@rock-chips.com>
The VOP2 unit is found on Rockchip SoCs beginning with rk3566/rk3568.
It replaces the VOP unit found in the older Rockchip SoCs.
This driver has been derived from the downstream Rockchip Kernel and
heavily modified:
- All nonstandard DRM properties have been removed
- dropped struct vop2_plane_state and pass around less data between
functions
- Dropped all DRM_FORMAT_* not known on upstream
- rework register access to get rid of excessively used macros
- Drop all waiting for framesyncs
The driver is tested with HDMI and MIPI-DSI display on a RK3568-EVB
board. Overlay support is tested with the modetest utility. AFBC support
on the cluster windows is tested with weston-simple-dmabuf-egl on
weston using the (yet to be upstreamed) panfrost driver support.
Changes since v3:
- Sort includes
- fix typos
- Drop spinlock
- Use regmap_set_bits()/regmap_clear_bits()
- simplify vop2_scale_factor()
- simplify vop2_afbc_transform_offset()
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
drivers/gpu/drm/rockchip/Kconfig | 6 +
drivers/gpu/drm/rockchip/Makefile | 1 +
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 1 +
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 7 +-
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 2 +
drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 15 +
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 2665 ++++++++++++++++++
drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 480 ++++
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 285 ++
9 files changed, 3461 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
create mode 100644 drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index b9b156308460..4ff0043f0ee7 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -28,6 +28,12 @@ config ROCKCHIP_VOP
This selects support for the VOP driver. You should enable it
on all older SoCs up to RK3399.
+config ROCKCHIP_VOP2
+ bool "Rockchip VOP2 driver"
+ help
+ This selects support for the VOP2 driver. You should enable it
+ on all newer SoCs beginning form RK3568.
+
config ROCKCHIP_ANALOGIX_DP
bool "Rockchip specific extensions for Analogix DP driver"
depends on ROCKCHIP_VOP
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index dfc5512fdb9f..3ff7b21c0414 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -6,6 +6,7 @@
rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \
rockchip_drm_gem.o
+rockchipdrm-$(CONFIG_ROCKCHIP_VOP2) += rockchip_drm_vop2.o rockchip_vop2_reg.o
rockchipdrm-$(CONFIG_ROCKCHIP_VOP) += rockchip_drm_vop.o rockchip_vop_reg.o
rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o
rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
index 82c8faf1fb6b..95f6c5985fdd 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
@@ -459,6 +459,7 @@ static int __init rockchip_drm_init(void)
num_rockchip_sub_drivers = 0;
ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
+ ADD_ROCKCHIP_SUB_DRIVER(vop2_platform_driver, CONFIG_ROCKCHIP_VOP2);
ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver,
CONFIG_ROCKCHIP_LVDS);
ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
index 143a48330f84..6e1f97e1e4a6 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -18,7 +18,7 @@
#define ROCKCHIP_MAX_FB_BUFFER 3
#define ROCKCHIP_MAX_CONNECTOR 2
-#define ROCKCHIP_MAX_CRTC 2
+#define ROCKCHIP_MAX_CRTC 4
struct drm_device;
struct drm_connector;
@@ -31,6 +31,9 @@ struct rockchip_crtc_state {
int output_bpc;
int output_flags;
bool enable_afbc;
+ uint32_t bus_format;
+ u32 bus_flags;
+ int color_space;
};
#define to_rockchip_crtc_state(s) \
container_of(s, struct rockchip_crtc_state, base)
@@ -63,4 +66,6 @@ extern struct platform_driver rockchip_dp_driver;
extern struct platform_driver rockchip_lvds_driver;
extern struct platform_driver vop_platform_driver;
extern struct platform_driver rk3066_hdmi_driver;
+extern struct platform_driver vop2_platform_driver;
+
#endif /* _ROCKCHIP_DRM_DRV_H_ */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
index 3aa37e177667..0d2cb4f3922b 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -134,4 +134,6 @@ void rockchip_drm_mode_config_init(struct drm_device *dev)
dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
dev->mode_config.helper_private = &rockchip_mode_config_helpers;
+
+ dev->mode_config.normalize_zpos = true;
}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
index 857d97cdc67c..1e364d7b50e6 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h
@@ -54,9 +54,23 @@ struct vop_afbc {
struct vop_reg enable;
struct vop_reg win_sel;
struct vop_reg format;
+ struct vop_reg rb_swap;
+ struct vop_reg uv_swap;
+ struct vop_reg auto_gating_en;
+ struct vop_reg block_split_en;
+ struct vop_reg pic_vir_width;
+ struct vop_reg tile_num;
struct vop_reg hreg_block_split;
+ struct vop_reg pic_offset;
struct vop_reg pic_size;
+ struct vop_reg dsp_offset;
+ struct vop_reg transform_offset;
struct vop_reg hdr_ptr;
+ struct vop_reg half_block_en;
+ struct vop_reg xmirror;
+ struct vop_reg ymirror;
+ struct vop_reg rotate_270;
+ struct vop_reg rotate_90;
struct vop_reg rstn;
};
@@ -410,4 +424,5 @@ static inline int scl_vop_cal_lb_mode(int width, bool is_yuv)
}
extern const struct component_ops vop_component_ops;
+
#endif /* _ROCKCHIP_DRM_VOP_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
new file mode 100644
index 000000000000..9ceaae97ed0c
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
@@ -0,0 +1,2665 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2020 Rockchip Electronics Co., Ltd.
+ * Author: Andy Yan <andy.yan@rock-chips.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/swab.h>
+
+#include <drm/drm.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_flip_work.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include <uapi/linux/videodev2.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
+
+#include "rockchip_drm_drv.h"
+#include "rockchip_drm_gem.h"
+#include "rockchip_drm_fb.h"
+#include "rockchip_drm_vop2.h"
+
+/*
+ * VOP2 architecture
+ *
+ +----------+ +-------------+ +-----------+
+ | Cluster | | Sel 1 from 6| | 1 from 3 |
+ | window0 | | Layer0 | | RGB |
+ +----------+ +-------------+ +---------------+ +-------------+ +-----------+
+ +----------+ +-------------+ |N from 6 layers| | |
+ | Cluster | | Sel 1 from 6| | Overlay0 +--->| Video Port0 | +-----------+
+ | window1 | | Layer1 | | | | | | 1 from 3 |
+ +----------+ +-------------+ +---------------+ +-------------+ | LVDS |
+ +----------+ +-------------+ +-----------+
+ | Esmart | | Sel 1 from 6|
+ | window0 | | Layer2 | +---------------+ +-------------+ +-----------+
+ +----------+ +-------------+ |N from 6 Layers| | | +--> | 1 from 3 |
+ +----------+ +-------------+ --------> | Overlay1 +--->| Video Port1 | | MIPI |
+ | Esmart | | Sel 1 from 6| --------> | | | | +-----------+
+ | Window1 | | Layer3 | +---------------+ +-------------+
+ +----------+ +-------------+ +-----------+
+ +----------+ +-------------+ | 1 from 3 |
+ | Smart | | Sel 1 from 6| +---------------+ +-------------+ | HDMI |
+ | Window0 | | Layer4 | |N from 6 Layers| | | +-----------+
+ +----------+ +-------------+ | Overlay2 +--->| Video Port2 |
+ +----------+ +-------------+ | | | | +-----------+
+ | Smart | | Sel 1 from 6| +---------------+ +-------------+ | 1 from 3 |
+ | Window1 | | Layer5 | | eDP |
+ +----------+ +-------------+ +-----------+
+ *
+ */
+
+enum vop2_data_format {
+ VOP2_FMT_ARGB8888 = 0,
+ VOP2_FMT_RGB888,
+ VOP2_FMT_RGB565,
+ VOP2_FMT_XRGB101010,
+ VOP2_FMT_YUV420SP,
+ VOP2_FMT_YUV422SP,
+ VOP2_FMT_YUV444SP,
+ VOP2_FMT_YUYV422 = 8,
+ VOP2_FMT_YUYV420,
+ VOP2_FMT_VYUY422,
+ VOP2_FMT_VYUY420,
+ VOP2_FMT_YUV420SP_TILE_8x4 = 0x10,
+ VOP2_FMT_YUV420SP_TILE_16x2,
+ VOP2_FMT_YUV422SP_TILE_8x4,
+ VOP2_FMT_YUV422SP_TILE_16x2,
+ VOP2_FMT_YUV420SP_10,
+ VOP2_FMT_YUV422SP_10,
+ VOP2_FMT_YUV444SP_10,
+};
+
+enum vop2_afbc_format {
+ VOP2_AFBC_FMT_RGB565,
+ VOP2_AFBC_FMT_ARGB2101010 = 2,
+ VOP2_AFBC_FMT_YUV420_10BIT,
+ VOP2_AFBC_FMT_RGB888,
+ VOP2_AFBC_FMT_ARGB8888,
+ VOP2_AFBC_FMT_YUV420 = 9,
+ VOP2_AFBC_FMT_YUV422 = 0xb,
+ VOP2_AFBC_FMT_YUV422_10BIT = 0xe,
+ VOP2_AFBC_FMT_INVALID = -1,
+};
+
+union vop2_alpha_ctrl {
+ uint32_t val;
+ struct {
+ /* [0:1] */
+ uint32_t color_mode:1;
+ uint32_t alpha_mode:1;
+ /* [2:3] */
+ uint32_t blend_mode:2;
+ uint32_t alpha_cal_mode:1;
+ /* [5:7] */
+ uint32_t factor_mode:3;
+ /* [8:9] */
+ uint32_t alpha_en:1;
+ uint32_t src_dst_swap:1;
+ uint32_t reserved:6;
+ /* [16:23] */
+ uint32_t glb_alpha:8;
+ } bits;
+};
+
+struct vop2_alpha {
+ union vop2_alpha_ctrl src_color_ctrl;
+ union vop2_alpha_ctrl dst_color_ctrl;
+ union vop2_alpha_ctrl src_alpha_ctrl;
+ union vop2_alpha_ctrl dst_alpha_ctrl;
+};
+
+struct vop2_alpha_config {
+ bool src_premulti_en;
+ bool dst_premulti_en;
+ bool src_pixel_alpha_en;
+ bool dst_pixel_alpha_en;
+ uint16_t src_glb_alpha_value;
+ uint16_t dst_glb_alpha_value;
+};
+
+struct vop2_win {
+ struct vop2 *vop2;
+ struct drm_plane base;
+ const struct vop2_win_data *data;
+ struct regmap_field *reg[VOP2_WIN_MAX_REG];
+
+ /**
+ * @win_id: graphic window id, a cluster may be split into two
+ * graphics windows.
+ */
+ uint8_t win_id;
+
+ uint32_t offset;
+
+ uint8_t delay;
+ enum drm_plane_type type;
+};
+
+struct vop2_video_port {
+ struct drm_crtc crtc;
+ struct vop2 *vop2;
+ struct clk *dclk;
+ uint8_t id;
+ const struct vop2_video_port_regs *regs;
+ const struct vop2_video_port_data *data;
+
+ struct completion dsp_hold_completion;
+
+ /**
+ * @win_mask: Bitmask of windows attached to the video port;
+ */
+ uint32_t win_mask;
+
+ struct vop2_win *primary_plane;
+ struct drm_pending_vblank_event *event;
+
+ int nlayers;
+};
+
+struct vop2 {
+ struct device *dev;
+ struct drm_device *drm;
+ struct vop2_video_port vps[ROCKCHIP_MAX_CRTC];
+
+ const struct vop2_data *data;
+ /*
+ * Number of windows that are registered as plane, may be less than the
+ * total number of hardware windows.
+ */
+ uint32_t registered_num_wins;
+
+ void __iomem *regs;
+ struct regmap *map;
+
+ struct regmap *grf;
+
+ /* physical map length of vop2 register */
+ uint32_t len;
+
+ void __iomem *lut_regs;
+
+ /* protects crtc enable/disable */
+ struct mutex vop2_lock;
+
+ int irq;
+
+ /*
+ * Some global resources are shared between all video ports(crtcs), so
+ * we need a ref counter here.
+ */
+ unsigned int enable_count;
+ struct clk *hclk;
+ struct clk *aclk;
+
+ /* must be put at the end of the struct */
+ struct vop2_win win[];
+};
+
+static struct vop2_video_port *to_vop2_video_port(struct drm_crtc *crtc)
+{
+ return container_of(crtc, struct vop2_video_port, crtc);
+}
+
+static struct vop2_win *to_vop2_win(struct drm_plane *p)
+{
+ return container_of(p, struct vop2_win, base);
+}
+
+static void vop2_lock(struct vop2 *vop2)
+{
+ mutex_lock(&vop2->vop2_lock);
+}
+
+static void vop2_unlock(struct vop2 *vop2)
+{
+ mutex_unlock(&vop2->vop2_lock);
+}
+
+static void vop2_writel(struct vop2 *vop2, uint32_t offset, uint32_t v)
+{
+ regmap_write(vop2->map, offset, v);
+}
+
+static void vop2_vp_write(struct vop2_video_port *vp, uint32_t offset,
+ uint32_t v)
+{
+ regmap_write(vp->vop2->map, vp->data->offset + offset, v);
+}
+
+static uint32_t vop2_readl(struct vop2 *vop2, uint32_t offset)
+{
+ uint32_t val;
+
+ regmap_read(vop2->map, offset, &val);
+
+ return val;
+}
+
+static void vop2_win_write(const struct vop2_win *win, unsigned int reg,
+ uint32_t v)
+{
+ regmap_field_write(win->reg[reg], v);
+}
+
+static bool vop2_cluster_window(const struct vop2_win *win)
+{
+ return win->data->feature & WIN_FEATURE_CLUSTER;
+}
+
+static void vop2_cfg_done(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE,
+ BIT(vp->id) | RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN);
+}
+
+static void vop2_win_disable(struct vop2_win *win)
+{
+ vop2_win_write(win, VOP2_WIN_ENABLE, 0);
+
+ if (vop2_cluster_window(win))
+ vop2_win_write(win, VOP2_WIN_CLUSTER_ENABLE, 0);
+}
+
+static enum vop2_data_format vop2_convert_format(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ return VOP2_FMT_ARGB8888;
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ return VOP2_FMT_RGB888;
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ return VOP2_FMT_RGB565;
+ case DRM_FORMAT_NV12:
+ return VOP2_FMT_YUV420SP;
+ case DRM_FORMAT_NV16:
+ return VOP2_FMT_YUV422SP;
+ case DRM_FORMAT_NV24:
+ return VOP2_FMT_YUV444SP;
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_YVYU:
+ return VOP2_FMT_VYUY422;
+ case DRM_FORMAT_VYUY:
+ case DRM_FORMAT_UYVY:
+ return VOP2_FMT_YUYV422;
+ default:
+ DRM_ERROR("unsupported format[%08x]\n", format);
+ return -EINVAL;
+ }
+}
+
+static enum vop2_afbc_format vop2_convert_afbc_format(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ return VOP2_AFBC_FMT_ARGB8888;
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ return VOP2_AFBC_FMT_RGB888;
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ return VOP2_AFBC_FMT_RGB565;
+ case DRM_FORMAT_NV12:
+ return VOP2_AFBC_FMT_YUV420;
+ case DRM_FORMAT_NV16:
+ return VOP2_AFBC_FMT_YUV422;
+ default:
+ return VOP2_AFBC_FMT_INVALID;
+ }
+
+ return VOP2_AFBC_FMT_INVALID;
+}
+
+static bool vop2_win_rb_swap(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_BGR888:
+ case DRM_FORMAT_BGR565:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_afbc_rb_swap(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV24:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_afbc_uv_swap(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV16:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_win_uv_swap(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV24:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_win_dither_up(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_BGR565:
+ case DRM_FORMAT_RGB565:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool vop2_output_uv_swap(uint32_t bus_format, uint32_t output_mode)
+{
+ /*
+ * FIXME:
+ *
+ * There is no media type for YUV444 output,
+ * so when out_mode is AAAA or P888, assume output is YUV444 on
+ * yuv format.
+ *
+ * From H/W testing, YUV444 mode need a rb swap.
+ */
+ if (bus_format == MEDIA_BUS_FMT_YVYU8_1X16 ||
+ bus_format == MEDIA_BUS_FMT_VYUY8_1X16 ||
+ bus_format == MEDIA_BUS_FMT_YVYU8_2X8 ||
+ bus_format == MEDIA_BUS_FMT_VYUY8_2X8 ||
+ ((bus_format == MEDIA_BUS_FMT_YUV8_1X24 ||
+ bus_format == MEDIA_BUS_FMT_YUV10_1X30) &&
+ (output_mode == ROCKCHIP_OUT_MODE_AAAA ||
+ output_mode == ROCKCHIP_OUT_MODE_P888)))
+ return true;
+ else
+ return false;
+}
+
+static bool is_yuv_output(uint32_t bus_format)
+{
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ case MEDIA_BUS_FMT_YUV10_1X30:
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool rockchip_afbc(struct drm_plane *plane, u64 modifier)
+{
+ int i;
+
+ if (modifier == DRM_FORMAT_MOD_LINEAR)
+ return false;
+
+ for (i = 0 ; i < plane->modifier_count; i++)
+ if (plane->modifiers[i] == modifier)
+ return true;
+
+ return false;
+
+}
+
+static bool rockchip_vop2_mod_supported(struct drm_plane *plane, uint32_t format, u64 modifier)
+{
+ struct vop2_win *win = to_vop2_win(plane);
+ struct vop2 *vop2 = win->vop2;
+
+ if (modifier == DRM_FORMAT_MOD_INVALID)
+ return false;
+
+ if (modifier == DRM_FORMAT_MOD_LINEAR)
+ return true;
+
+ if (!rockchip_afbc(plane, modifier)) {
+ drm_err(vop2->drm, "Unsupported format modifier 0x%llx\n", modifier);
+
+ return false;
+ }
+
+ return vop2_convert_afbc_format(format) >= 0;
+}
+
+static uint32_t vop2_afbc_transform_offset(struct drm_plane_state *pstate,
+ bool afbc_half_block_en)
+{
+ struct drm_rect *src = &pstate->src;
+ struct drm_framebuffer *fb = pstate->fb;
+ uint32_t bpp = fb->format->cpp[0] * 8;
+ uint32_t vir_width = (fb->pitches[0] << 3) / bpp;
+ uint32_t width = drm_rect_width(src) >> 16;
+ uint32_t height = drm_rect_height(src) >> 16;
+ uint32_t act_xoffset = src->x1 >> 16;
+ uint32_t act_yoffset = src->y1 >> 16;
+ uint32_t align16_crop = 0;
+ uint32_t align64_crop = 0;
+ uint32_t height_tmp;
+ uint8_t tx, ty;
+ uint8_t bottom_crop_line_num = 0;
+
+ /* 16 pixel align */
+ if (height & 0xf)
+ align16_crop = 16 - (height & 0xf);
+
+ height_tmp = height + align16_crop;
+
+ /* 64 pixel align */
+ if (height_tmp & 0x3f)
+ align64_crop = 64 - (height_tmp & 0x3f);
+
+ bottom_crop_line_num = align16_crop + align64_crop;
+
+ switch (pstate->rotation &
+ (DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y |
+ DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270)) {
+ case DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y:
+ tx = 16 - ((act_xoffset + width) & 0xf);
+ ty = bottom_crop_line_num - act_yoffset;
+ break;
+ case DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90:
+ tx = bottom_crop_line_num - act_yoffset;
+ ty = vir_width - width - act_xoffset;
+ break;
+ case DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_270:
+ tx = act_yoffset;
+ ty = act_xoffset;
+ break;
+ case DRM_MODE_REFLECT_X:
+ tx = 16 - ((act_xoffset + width) & 0xf);
+ ty = act_yoffset;
+ break;
+ case DRM_MODE_REFLECT_Y:
+ tx = act_xoffset;
+ ty = bottom_crop_line_num - act_yoffset;
+ break;
+ case DRM_MODE_ROTATE_90:
+ tx = bottom_crop_line_num - act_yoffset;
+ ty = act_xoffset;
+ break;
+ case DRM_MODE_ROTATE_270:
+ tx = act_yoffset;
+ ty = vir_width - width - act_xoffset;
+ break;
+ case 0:
+ tx = act_xoffset;
+ ty = act_yoffset;
+ break;
+ }
+
+ if (afbc_half_block_en)
+ ty &= 0x7f;
+
+#define TRANSFORM_XOFFSET GENMASK(7, 0)
+#define TRANSFORM_YOFFSET GENMASK(23, 16)
+ return FIELD_PREP(TRANSFORM_XOFFSET, tx) |
+ FIELD_PREP(TRANSFORM_YOFFSET, ty);
+}
+
+/*
+ * A Cluster window has 2048 x 16 line buffer, which can
+ * works at 2048 x 16(Full) or 4096 x 8 (Half) mode.
+ * for Cluster_lb_mode register:
+ * 0: half mode, for plane input width range 2048 ~ 4096
+ * 1: half mode, for cluster work at 2 * 2048 plane mode
+ * 2: half mode, for rotate_90/270 mode
+ *
+ */
+static int vop2_get_cluster_lb_mode(struct vop2_win *win, struct drm_plane_state *pstate)
+{
+ if ((pstate->rotation & DRM_MODE_ROTATE_270) || (pstate->rotation & DRM_MODE_ROTATE_90))
+ return 2;
+ else
+ return 0;
+}
+
+static uint16_t vop2_scale_factor(uint32_t src, uint32_t dst)
+{
+ uint32_t fac;
+ int shift;
+
+ if (src == dst)
+ return 0;
+
+ if (dst < 2)
+ return U16_MAX;
+
+ if (src < 2)
+ return 0;
+
+ if (src > dst)
+ shift = 12;
+ else
+ shift = 16;
+
+ src--;
+ dst--;
+
+ fac = DIV_ROUND_UP(src << shift, dst) - 1;
+
+ if (fac > U16_MAX)
+ return U16_MAX;
+
+ return fac;
+}
+
+static void vop2_setup_scale(struct vop2 *vop2, const struct vop2_win *win,
+ uint32_t src_w, uint32_t src_h, uint32_t dst_w,
+ uint32_t dst_h, uint32_t pixel_format)
+{
+ const struct drm_format_info *info;
+ uint16_t hor_scl_mode, ver_scl_mode;
+ uint16_t hscl_filter_mode, vscl_filter_mode;
+ uint8_t gt2 = 0;
+ uint8_t gt4 = 0;
+ uint32_t val;
+
+ info = drm_format_info(pixel_format);
+
+ if (src_h >= (4 * dst_h)) {
+ gt4 = 1;
+ src_h >>= 2;
+ } else if (src_h >= (2 * dst_h)) {
+ gt2 = 1;
+ src_h >>= 1;
+ }
+
+ hor_scl_mode = scl_get_scl_mode(src_w, dst_w);
+ ver_scl_mode = scl_get_scl_mode(src_h, dst_h);
+
+ if (hor_scl_mode == SCALE_UP)
+ hscl_filter_mode = VOP2_SCALE_UP_BIC;
+ else
+ hscl_filter_mode = VOP2_SCALE_DOWN_BIL;
+
+ if (ver_scl_mode == SCALE_UP)
+ vscl_filter_mode = VOP2_SCALE_UP_BIL;
+ else
+ vscl_filter_mode = VOP2_SCALE_DOWN_BIL;
+
+ /*
+ * RK3568 VOP Esmart/Smart dsp_w should be even pixel
+ * at scale down mode
+ */
+ if (!(win->data->feature & WIN_FEATURE_AFBDC)) {
+ if ((hor_scl_mode == SCALE_DOWN) && (dst_w & 0x1)) {
+ drm_dbg(vop2->drm, "%s dst_w[%d] should align as 2 pixel\n",
+ win->data->name, dst_w);
+ dst_w++;
+ }
+ }
+
+ val = vop2_scale_factor(src_w, dst_w);
+ vop2_win_write(win, VOP2_WIN_SCALE_YRGB_X, val);
+ val = vop2_scale_factor(src_h, dst_h);
+ vop2_win_write(win, VOP2_WIN_SCALE_YRGB_Y, val);
+
+ vop2_win_write(win, VOP2_WIN_VSD_YRGB_GT4, gt4);
+ vop2_win_write(win, VOP2_WIN_VSD_YRGB_GT2, gt2);
+
+ vop2_win_write(win, VOP2_WIN_YRGB_HOR_SCL_MODE, hor_scl_mode);
+ vop2_win_write(win, VOP2_WIN_YRGB_VER_SCL_MODE, ver_scl_mode);
+
+ if (vop2_cluster_window(win))
+ return;
+
+ vop2_win_write(win, VOP2_WIN_YRGB_HSCL_FILTER_MODE, hscl_filter_mode);
+ vop2_win_write(win, VOP2_WIN_YRGB_VSCL_FILTER_MODE, vscl_filter_mode);
+
+ if (info->is_yuv) {
+ src_w /= info->hsub;
+ src_h /= info->vsub;
+
+ gt4 = gt2 = 0;
+
+ if (src_h >= (4 * dst_h)) {
+ gt4 = 1;
+ src_h >>= 2;
+ } else if (src_h >= (2 * dst_h)) {
+ gt2 = 1;
+ src_h >>= 1;
+ }
+
+ hor_scl_mode = scl_get_scl_mode(src_w, dst_w);
+ ver_scl_mode = scl_get_scl_mode(src_h, dst_h);
+
+ val = vop2_scale_factor(src_w, dst_w);
+ vop2_win_write(win, VOP2_WIN_SCALE_CBCR_X, val);
+
+ val = vop2_scale_factor(src_h, dst_h);
+ vop2_win_write(win, VOP2_WIN_SCALE_CBCR_Y, val);
+
+ vop2_win_write(win, VOP2_WIN_VSD_CBCR_GT4, gt4);
+ vop2_win_write(win, VOP2_WIN_VSD_CBCR_GT2, gt2);
+ vop2_win_write(win, VOP2_WIN_CBCR_HOR_SCL_MODE, hor_scl_mode);
+ vop2_win_write(win, VOP2_WIN_CBCR_VER_SCL_MODE, ver_scl_mode);
+ vop2_win_write(win, VOP2_WIN_CBCR_HSCL_FILTER_MODE, hscl_filter_mode);
+ vop2_win_write(win, VOP2_WIN_CBCR_VSCL_FILTER_MODE, vscl_filter_mode);
+ }
+}
+
+static int vop2_convert_csc_mode(int csc_mode)
+{
+ switch (csc_mode) {
+ case V4L2_COLORSPACE_SMPTE170M:
+ case V4L2_COLORSPACE_470_SYSTEM_M:
+ case V4L2_COLORSPACE_470_SYSTEM_BG:
+ return CSC_BT601L;
+ case V4L2_COLORSPACE_REC709:
+ case V4L2_COLORSPACE_SMPTE240M:
+ case V4L2_COLORSPACE_DEFAULT:
+ return CSC_BT709L;
+ case V4L2_COLORSPACE_JPEG:
+ return CSC_BT601F;
+ case V4L2_COLORSPACE_BT2020:
+ return CSC_BT2020;
+ default:
+ return CSC_BT709L;
+ }
+}
+
+/*
+ * colorspace path:
+ * Input Win csc Output
+ * 1. YUV(2020) --> Y2R->2020To709->R2Y --> YUV_OUTPUT(601/709)
+ * RGB --> R2Y __/
+ *
+ * 2. YUV(2020) --> bypasss --> YUV_OUTPUT(2020)
+ * RGB --> 709To2020->R2Y __/
+ *
+ * 3. YUV(2020) --> Y2R->2020To709 --> RGB_OUTPUT(709)
+ * RGB --> R2Y __/
+ *
+ * 4. YUV(601/709)-> Y2R->709To2020->R2Y --> YUV_OUTPUT(2020)
+ * RGB --> 709To2020->R2Y __/
+ *
+ * 5. YUV(601/709)-> bypass --> YUV_OUTPUT(709)
+ * RGB --> R2Y __/
+ *
+ * 6. YUV(601/709)-> bypass --> YUV_OUTPUT(601)
+ * RGB --> R2Y(601) __/
+ *
+ * 7. YUV --> Y2R(709) --> RGB_OUTPUT(709)
+ * RGB --> bypass __/
+ *
+ * 8. RGB --> 709To2020->R2Y --> YUV_OUTPUT(2020)
+ *
+ * 9. RGB --> R2Y(709) --> YUV_OUTPUT(709)
+ *
+ * 10. RGB --> R2Y(601) --> YUV_OUTPUT(601)
+ *
+ * 11. RGB --> bypass --> RGB_OUTPUT(709)
+ */
+
+static void vop2_setup_csc_mode(struct vop2_video_port *vp,
+ struct vop2_win *win,
+ struct drm_plane_state *pstate)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state);
+ int is_input_yuv = pstate->fb->format->is_yuv;
+ int is_output_yuv = is_yuv_output(vcstate->bus_format);
+ int input_csc = V4L2_COLORSPACE_DEFAULT;
+ int output_csc = vcstate->color_space;
+ bool r2y_en, y2r_en;
+ int csc_mode;
+
+ if (is_input_yuv && !is_output_yuv) {
+ y2r_en = true;
+ r2y_en = false;
+ csc_mode = vop2_convert_csc_mode(input_csc);
+ } else if (!is_input_yuv && is_output_yuv) {
+ y2r_en = false;
+ r2y_en = true;
+ csc_mode = vop2_convert_csc_mode(output_csc);
+ } else {
+ y2r_en = false;
+ r2y_en = false;
+ csc_mode = false;
+ }
+
+ vop2_win_write(win, VOP2_WIN_Y2R_EN, y2r_en);
+ vop2_win_write(win, VOP2_WIN_R2Y_EN, r2y_en);
+ vop2_win_write(win, VOP2_WIN_CSC_MODE, csc_mode);
+}
+
+static void vop2_crtc_enable_irq(struct vop2_video_port *vp, uint32_t irq)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ vop2_writel(vop2, RK3568_VP_INT_CLR(vp->id), irq << 16 | irq);
+ vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16 | irq);
+}
+
+static void vop2_crtc_disable_irq(struct vop2_video_port *vp, uint32_t irq)
+{
+ struct vop2 *vop2 = vp->vop2;
+
+ vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16);
+}
+
+static int vop2_core_clks_prepare_enable(struct vop2 *vop2)
+{
+ int ret;
+
+ ret = clk_prepare_enable(vop2->hclk);
+ if (ret < 0) {
+ drm_err(vop2->drm, "failed to enable hclk - %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(vop2->aclk);
+ if (ret < 0) {
+ drm_err(vop2->drm, "failed to enable aclk - %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+err:
+ clk_disable_unprepare(vop2->hclk);
+
+ return ret;
+}
+
+static void vop2_enable(struct vop2 *vop2)
+{
+ int ret;
+
+ ret = pm_runtime_get_sync(vop2->dev);
+ if (ret < 0) {
+ drm_err(vop2->drm, "failed to get pm runtime: %d\n", ret);
+ return;
+ }
+
+ ret = vop2_core_clks_prepare_enable(vop2);
+ if (ret) {
+ pm_runtime_put_sync(vop2->dev);
+ return;
+ }
+
+ if (vop2->data->soc_id == 3566)
+ vop2_writel(vop2, RK3568_OTP_WIN_EN, 1);
+
+ vop2_writel(vop2, RK3568_REG_CFG_DONE, RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN);
+
+ /*
+ * Disable auto gating, this is a workaround to
+ * avoid display image shift when a window enabled.
+ */
+ regmap_clear_bits(vop2->map, RK3568_SYS_AUTO_GATING_CTRL,
+ RK3568_SYS_AUTO_GATING_CTRL__AUTO_GATING_EN);
+
+ vop2_writel(vop2, RK3568_SYS0_INT_CLR,
+ VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR);
+ vop2_writel(vop2, RK3568_SYS0_INT_EN,
+ VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR);
+ vop2_writel(vop2, RK3568_SYS1_INT_CLR,
+ VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR);
+ vop2_writel(vop2, RK3568_SYS1_INT_EN,
+ VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR);
+}
+
+static void vop2_disable(struct vop2 *vop2)
+{
+ pm_runtime_put_sync(vop2->dev);
+
+ clk_disable_unprepare(vop2->aclk);
+ clk_disable_unprepare(vop2->hclk);
+}
+
+static void vop2_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct vop2 *vop2 = vp->vop2;
+ int ret;
+
+ vop2_lock(vop2);
+
+ drm_crtc_vblank_off(crtc);
+
+ /*
+ * Vop standby will take effect at end of current frame,
+ * if dsp hold valid irq happen, it means standby complete.
+ *
+ * we must wait standby complete when we want to disable aclk,
+ * if not, memory bus maybe dead.
+ */
+ reinit_completion(&vp->dsp_hold_completion);
+
+ vop2_crtc_enable_irq(vp, VP_INT_DSP_HOLD_VALID);
+
+ vop2_vp_write(vp, RK3568_VP_DSP_CTRL, RK3568_VP_DSP_CTRL__STANDBY);
+
+ ret = wait_for_completion_timeout(&vp->dsp_hold_completion, msecs_to_jiffies(50));
+ if (!ret)
+ drm_info(vop2->drm, "wait for vp%d dsp_hold timeout\n", vp->id);
+
+ vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID);
+
+ clk_disable_unprepare(vp->dclk);
+
+ vop2->enable_count--;
+
+ if (!vop2->enable_count)
+ vop2_disable(vop2);
+
+ vop2_unlock(vop2);
+
+ if (crtc->state->event && !crtc->state->active) {
+ spin_lock_irq(&crtc->dev->event_lock);
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+
+ crtc->state->event = NULL;
+ }
+}
+
+static int vop2_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *astate)
+{
+ struct drm_plane_state *pstate = drm_atomic_get_new_plane_state(astate, plane);
+ struct drm_framebuffer *fb = pstate->fb;
+ struct drm_crtc *crtc = pstate->crtc;
+ struct drm_crtc_state *cstate;
+ struct vop2_video_port *vp;
+ struct vop2 *vop2;
+ const struct vop2_data *vop2_data;
+ struct drm_rect *dest = &pstate->dst;
+ struct drm_rect *src = &pstate->src;
+ int min_scale = FRAC_16_16(1, 8);
+ int max_scale = FRAC_16_16(8, 1);
+ int format;
+ int ret;
+
+ if (!crtc)
+ return 0;
+
+ vp = to_vop2_video_port(crtc);
+ vop2 = vp->vop2;
+ vop2_data = vop2->data;
+
+ cstate = drm_atomic_get_existing_crtc_state(pstate->state, crtc);
+ if (WARN_ON(!cstate))
+ return -EINVAL;
+
+ ret = drm_atomic_helper_check_plane_state(pstate, cstate,
+ min_scale, max_scale,
+ true, true);
+ if (ret)
+ return ret;
+
+ if (!pstate->visible)
+ return 0;
+
+ format = vop2_convert_format(fb->format->format);
+ if (format < 0)
+ return format;
+
+ if (drm_rect_width(src) >> 16 < 4 || drm_rect_height(src) >> 16 < 4 ||
+ drm_rect_width(dest) < 4 || drm_rect_width(dest) < 4) {
+ drm_err(vop2->drm, "Invalid size: %dx%d->%dx%d, min size is 4x4\n",
+ drm_rect_width(src) >> 16, drm_rect_height(src) >> 16,
+ drm_rect_width(dest), drm_rect_height(dest));
+ pstate->visible = false;
+ return 0;
+ }
+
+ if (drm_rect_width(src) >> 16 > vop2_data->max_input.width ||
+ drm_rect_height(src) >> 16 > vop2_data->max_input.height) {
+ drm_err(vop2->drm, "Invalid source: %dx%d. max input: %dx%d\n",
+ drm_rect_width(src) >> 16,
+ drm_rect_height(src) >> 16,
+ vop2_data->max_input.width,
+ vop2_data->max_input.height);
+ return -EINVAL;
+ }
+
+ /*
+ * Src.x1 can be odd when do clip, but yuv plane start point
+ * need align with 2 pixel.
+ */
+ if (fb->format->is_yuv && ((pstate->src.x1 >> 16) % 2)) {
+ drm_err(vop2->drm, "Invalid Source: Yuv format not support odd xpos\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vop2_plane_atomic_disable(struct drm_plane *plane, struct drm_atomic_state *state)
+{
+ struct drm_plane_state *old_pstate = drm_atomic_get_old_plane_state(state, plane);
+ struct vop2_win *win = to_vop2_win(plane);
+ struct vop2 *vop2 = win->vop2;
+
+ drm_dbg(vop2->drm, "%s disable\n", win->data->name);
+
+ if (!old_pstate->crtc)
+ return;
+
+ vop2_win_disable(win);
+ vop2_win_write(win, VOP2_WIN_YUV_CLIP, 0);
+}
+
+/*
+ * The color key is 10 bit, so all format should
+ * convert to 10 bit here.
+ */
+static void vop2_plane_setup_color_key(struct drm_plane *plane, uint32_t color_key)
+{
+ struct drm_plane_state *pstate = plane->state;
+ struct drm_framebuffer *fb = pstate->fb;
+ struct vop2_win *win = to_vop2_win(plane);
+ uint32_t color_key_en = 0;
+ uint32_t r = 0;
+ uint32_t g = 0;
+ uint32_t b = 0;
+
+ if (!(color_key & VOP2_COLOR_KEY_MASK) || fb->format->is_yuv) {
+ vop2_win_write(win, VOP2_WIN_COLOR_KEY_EN, 0);
+ return;
+ }
+
+ switch (fb->format->format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ r = (color_key & 0xf800) >> 11;
+ g = (color_key & 0x7e0) >> 5;
+ b = (color_key & 0x1f);
+ r <<= 5;
+ g <<= 4;
+ b <<= 5;
+ color_key_en = 1;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_RGB888:
+ case DRM_FORMAT_BGR888:
+ r = (color_key & 0xff0000) >> 16;
+ g = (color_key & 0xff00) >> 8;
+ b = (color_key & 0xff);
+ r <<= 2;
+ g <<= 2;
+ b <<= 2;
+ color_key_en = 1;
+ break;
+ }
+
+ vop2_win_write(win, VOP2_WIN_COLOR_KEY_EN, color_key_en);
+ vop2_win_write(win, VOP2_WIN_COLOR_KEY, (r << 20) | (g << 10) | b);
+}
+
+static void vop2_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
+{
+ struct drm_plane_state *pstate = plane->state;
+ struct drm_crtc *crtc = pstate->crtc;
+ struct vop2_win *win = to_vop2_win(plane);
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode;
+ struct vop2 *vop2 = win->vop2;
+ struct drm_framebuffer *fb = pstate->fb;
+ uint32_t bpp = fb->format->cpp[0] * 8;
+ uint32_t actual_w, actual_h, dsp_w, dsp_h;
+ uint32_t act_info, dsp_info;
+ uint32_t format;
+ uint32_t afbc_format;
+ uint32_t rb_swap;
+ uint32_t uv_swap;
+ struct drm_rect *src = &pstate->src;
+ struct drm_rect *dest = &pstate->dst;
+ uint32_t afbc_tile_num;
+ uint32_t transform_offset;
+ bool dither_up;
+ bool xmirror = pstate->rotation & DRM_MODE_REFLECT_X;
+ bool ymirror = pstate->rotation & DRM_MODE_REFLECT_Y;
+ bool rotate_270 = pstate->rotation & DRM_MODE_ROTATE_270;
+ bool rotate_90 = pstate->rotation & DRM_MODE_ROTATE_90;
+ struct rockchip_gem_object *rk_obj;
+ unsigned long offset;
+ bool afbc_en;
+ dma_addr_t yrgb_mst;
+ dma_addr_t uv_mst;
+
+ /*
+ * can't update plane when vop2 is disabled.
+ */
+ if (WARN_ON(!crtc))
+ return;
+
+ if (!pstate->visible) {
+ vop2_plane_atomic_disable(plane, state);
+ return;
+ }
+
+ afbc_en = rockchip_afbc(plane, fb->modifier);
+
+ offset = (src->x1 >> 16) * fb->format->cpp[0];
+
+ /*
+ * AFBC HDR_PTR must set to the zero offset of the framebuffer.
+ */
+ if (afbc_en)
+ offset = 0;
+ else if (pstate->rotation & DRM_MODE_REFLECT_Y)
+ offset += ((src->y2 >> 16) - 1) * fb->pitches[0];
+ else
+ offset += (src->y1 >> 16) * fb->pitches[0];
+
+ rk_obj = to_rockchip_obj(fb->obj[0]);
+
+ yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0];
+ if (fb->format->is_yuv) {
+ int hsub = fb->format->hsub;
+ int vsub = fb->format->vsub;
+
+ offset = (src->x1 >> 16) * fb->format->cpp[1] / hsub;
+ offset += (src->y1 >> 16) * fb->pitches[1] / vsub;
+
+ if ((pstate->rotation & DRM_MODE_REFLECT_Y) && !afbc_en)
+ offset += fb->pitches[1] * ((pstate->src_h >> 16) - 2) / vsub;
+
+ rk_obj = to_rockchip_obj(fb->obj[0]);
+ uv_mst = rk_obj->dma_addr + offset + fb->offsets[1];
+ }
+
+ actual_w = drm_rect_width(src) >> 16;
+ actual_h = drm_rect_height(src) >> 16;
+ dsp_w = drm_rect_width(dest);
+
+ if (dest->x1 + dsp_w > adjusted_mode->hdisplay) {
+ drm_err(vop2->drm, "vp%d %s dest->x1[%d] + dsp_w[%d] exceed mode hdisplay[%d]\n",
+ vp->id, win->data->name, dest->x1, dsp_w, adjusted_mode->hdisplay);
+ dsp_w = adjusted_mode->hdisplay - dest->x1;
+ if (dsp_w < 4)
+ dsp_w = 4;
+ actual_w = dsp_w * actual_w / drm_rect_width(dest);
+ }
+
+ dsp_h = drm_rect_height(dest);
+
+ if (dest->y1 + dsp_h > adjusted_mode->vdisplay) {
+ drm_err(vop2->drm, "vp%d %s dest->y1[%d] + dsp_h[%d] exceed mode vdisplay[%d]\n",
+ vp->id, win->data->name, dest->y1, dsp_h, adjusted_mode->vdisplay);
+ dsp_h = adjusted_mode->vdisplay - dest->y1;
+ if (dsp_h < 4)
+ dsp_h = 4;
+ actual_h = dsp_h * actual_h / drm_rect_height(dest);
+ }
+
+ /*
+ * This is workaround solution for IC design:
+ * esmart can't support scale down when actual_w % 16 == 1.
+ */
+ if (!(win->data->feature & WIN_FEATURE_AFBDC)) {
+ if (actual_w > dsp_w && (actual_w & 0xf) == 1) {
+ drm_err(vop2->drm, "vp%d %s act_w[%d] MODE 16 == 1\n", vp->id, win->data->name, actual_w);
+ actual_w -= 1;
+ }
+ }
+
+ if (afbc_en && actual_w % 4) {
+ drm_err(vop2->drm, "vp%d %s actual_w[%d] should align as 4 pixel when enable afbc\n",
+ vp->id, win->data->name, actual_w);
+ actual_w = ALIGN_DOWN(actual_w, 4);
+ }
+
+ act_info = (actual_h - 1) << 16 | ((actual_w - 1) & 0xffff);
+ dsp_info = (dsp_h - 1) << 16 | ((dsp_w - 1) & 0xffff);
+
+ format = vop2_convert_format(fb->format->format);
+
+ drm_dbg(vop2->drm, "vp%d update %s[%dx%d->%dx%d@%dx%d] fmt[%p4cc_%s] addr[%pad]\n",
+ vp->id, win->data->name, actual_w, actual_h, dsp_w, dsp_h,
+ dest->x1, dest->y1,
+ &fb->format->format,
+ afbc_en ? "AFBC" : "", &yrgb_mst);
+
+ if (afbc_en) {
+ uint32_t stride;
+
+ /* the afbc superblock is 16 x 16 */
+ afbc_format = vop2_convert_afbc_format(fb->format->format);
+
+ /* Enable color transform for YTR */
+ if (fb->modifier & AFBC_FORMAT_MOD_YTR)
+ afbc_format |= (1 << 4);
+
+ afbc_tile_num = ALIGN(actual_w, 16) >> 4;
+
+ /*
+ * AFBC pic_vir_width is count by pixel, this is different
+ * with WIN_VIR_STRIDE.
+ */
+ stride = (fb->pitches[0] << 3) / bpp;
+ if ((stride & 0x3f) && (xmirror || rotate_90 || rotate_270))
+ drm_err(vop2->drm, "vp%d %s stride[%d] must align as 64 pixel when enable xmirror/rotate_90/rotate_270[0x%x]\n",
+ vp->id, win->data->name, stride, pstate->rotation);
+
+ rb_swap = vop2_afbc_rb_swap(fb->format->format);
+ uv_swap = vop2_afbc_uv_swap(fb->format->format);
+ /*
+ * This is a workaround for crazy IC design, Cluster
+ * and Esmart/Smart use different format configuration map:
+ * YUV420_10BIT: 0x10 for Cluster, 0x14 for Esmart/Smart.
+ *
+ * This is one thing we can make the convert simple:
+ * AFBCD decode all the YUV data to YUV444. So we just
+ * set all the yuv 10 bit to YUV444_10.
+ */
+ if (fb->format->is_yuv && (bpp == 10))
+ format = VOP2_CLUSTER_YUV444_10;
+
+ if (vop2_cluster_window(win))
+ vop2_win_write(win, VOP2_WIN_AFBC_ENABLE, 1);
+ vop2_win_write(win, VOP2_WIN_AFBC_FORMAT, afbc_format);
+ vop2_win_write(win, VOP2_WIN_AFBC_RB_SWAP, rb_swap);
+ vop2_win_write(win, VOP2_WIN_AFBC_UV_SWAP, uv_swap);
+ vop2_win_write(win, VOP2_WIN_AFBC_AUTO_GATING_EN, 0);
+ vop2_win_write(win, VOP2_WIN_AFBC_BLOCK_SPLIT_EN, 0);
+ if (pstate->rotation & (DRM_MODE_ROTATE_270 | DRM_MODE_ROTATE_90)) {
+ vop2_win_write(win, VOP2_WIN_AFBC_HALF_BLOCK_EN, 0);
+ transform_offset = vop2_afbc_transform_offset(pstate, false);
+ } else {
+ vop2_win_write(win, VOP2_WIN_AFBC_HALF_BLOCK_EN, 1);
+ transform_offset = vop2_afbc_transform_offset(pstate, true);
+ }
+ vop2_win_write(win, VOP2_WIN_AFBC_HDR_PTR, yrgb_mst);
+ vop2_win_write(win, VOP2_WIN_AFBC_PIC_SIZE, act_info);
+ vop2_win_write(win, VOP2_WIN_AFBC_TRANSFORM_OFFSET, transform_offset);
+ vop2_win_write(win, VOP2_WIN_AFBC_PIC_OFFSET, ((src->x1 >> 16) | src->y1));
+ vop2_win_write(win, VOP2_WIN_AFBC_DSP_OFFSET, (dest->x1 | (dest->y1 << 16)));
+ vop2_win_write(win, VOP2_WIN_AFBC_PIC_VIR_WIDTH, stride);
+ vop2_win_write(win, VOP2_WIN_AFBC_TILE_NUM, afbc_tile_num);
+ vop2_win_write(win, VOP2_WIN_XMIRROR, xmirror);
+ vop2_win_write(win, VOP2_WIN_AFBC_ROTATE_270, rotate_270);
+ vop2_win_write(win, VOP2_WIN_AFBC_ROTATE_90, rotate_90);
+ } else {
+ vop2_win_write(win, VOP2_WIN_YRGB_VIR, DIV_ROUND_UP(fb->pitches[0], 4));
+ }
+
+ vop2_win_write(win, VOP2_WIN_YMIRROR, ymirror);
+
+ if (rotate_90 || rotate_270) {
+ act_info = swahw32(act_info);
+ actual_w = drm_rect_height(src) >> 16;
+ actual_h = drm_rect_width(src) >> 16;
+ }
+
+ vop2_win_write(win, VOP2_WIN_FORMAT, format);
+ vop2_win_write(win, VOP2_WIN_YRGB_MST, yrgb_mst);
+
+ rb_swap = vop2_win_rb_swap(fb->format->format);
+ vop2_win_write(win, VOP2_WIN_RB_SWAP, rb_swap);
+ if (!vop2_cluster_window(win)) {
+ uv_swap = vop2_win_uv_swap(fb->format->format);
+ vop2_win_write(win, VOP2_WIN_UV_SWAP, uv_swap);
+ }
+
+ if (fb->format->is_yuv) {
+ vop2_win_write(win, VOP2_WIN_UV_VIR, DIV_ROUND_UP(fb->pitches[1], 4));
+ vop2_win_write(win, VOP2_WIN_UV_MST, uv_mst);
+ }
+
+ vop2_setup_scale(vop2, win, actual_w, actual_h, dsp_w, dsp_h, fb->format->format);
+ if (!vop2_cluster_window(win))
+ vop2_plane_setup_color_key(plane, 0);
+ vop2_win_write(win, VOP2_WIN_ACT_INFO, act_info);
+ vop2_win_write(win, VOP2_WIN_DSP_INFO, dsp_info);
+ vop2_win_write(win, VOP2_WIN_DSP_ST, dest->y1 << 16 | (dest->x1 & 0xffff));
+
+ vop2_setup_csc_mode(vp, win, pstate);
+
+ dither_up = vop2_win_dither_up(fb->format->format);
+ vop2_win_write(win, VOP2_WIN_DITHER_UP, dither_up);
+
+ vop2_win_write(win, VOP2_WIN_ENABLE, 1);
+
+ if (vop2_cluster_window(win)) {
+ int lb_mode = vop2_get_cluster_lb_mode(win, pstate);
+
+ vop2_win_write(win, VOP2_WIN_CLUSTER_LB_MODE, lb_mode);
+ vop2_win_write(win, VOP2_WIN_CLUSTER_ENABLE, 1);
+ }
+}
+
+static const struct drm_plane_helper_funcs vop2_plane_helper_funcs = {
+ .atomic_check = vop2_plane_atomic_check,
+ .atomic_update = vop2_plane_atomic_update,
+ .atomic_disable = vop2_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs vop2_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = drm_plane_cleanup,
+ .reset = drm_atomic_helper_plane_reset,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+ .format_mod_supported = rockchip_vop2_mod_supported,
+};
+
+static int vop2_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+
+ vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD);
+
+ return 0;
+}
+
+static void vop2_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+
+ vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD);
+}
+
+static bool vop2_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ drm_mode_set_crtcinfo(adj_mode, CRTC_INTERLACE_HALVE_V | CRTC_STEREO_DOUBLE);
+
+ return true;
+}
+
+static void vop2_dither_setup(struct drm_crtc *crtc, uint32_t *dsp_ctrl)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
+
+ switch (vcstate->bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ *dsp_ctrl |= RK3568_VP_DSP_CTRL__DITHER_DOWN_EN;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ *dsp_ctrl |= RK3568_VP_DSP_CTRL__DITHER_DOWN_EN;
+ *dsp_ctrl |= RGB888_TO_RGB666;
+ break;
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ *dsp_ctrl |= RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN;
+ break;
+ default:
+ break;
+ }
+
+ if (vcstate->output_mode != ROCKCHIP_OUT_MODE_AAAA)
+ *dsp_ctrl |= RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN;
+
+ *dsp_ctrl |= FIELD_PREP(RK3568_VP_DSP_CTRL__DITHER_DOWN_SEL,
+ DITHER_DOWN_ALLEGRO);
+}
+
+static void vop2_post_config(struct drm_crtc *crtc)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ uint16_t vtotal = mode->crtc_vtotal;
+ uint16_t hdisplay = mode->crtc_hdisplay;
+ uint16_t hact_st = mode->crtc_htotal - mode->crtc_hsync_start;
+ uint16_t vdisplay = mode->crtc_vdisplay;
+ uint16_t vact_st = mode->crtc_vtotal - mode->crtc_vsync_start;
+ uint32_t left_margin = 100, right_margin = 100, top_margin = 100, bottom_margin = 100;
+ uint16_t hsize = hdisplay * (left_margin + right_margin) / 200;
+ uint16_t vsize = vdisplay * (top_margin + bottom_margin) / 200;
+ uint16_t hact_end, vact_end;
+ uint32_t val;
+
+ vsize = rounddown(vsize, 2);
+ hsize = rounddown(hsize, 2);
+ hact_st += hdisplay * (100 - left_margin) / 200;
+ hact_end = hact_st + hsize;
+ val = hact_st << 16;
+ val |= hact_end;
+ vop2_vp_write(vp, RK3568_VP_POST_DSP_HACT_INFO, val);
+ vact_st += vdisplay * (100 - top_margin) / 200;
+ vact_end = vact_st + vsize;
+ val = vact_st << 16;
+ val |= vact_end;
+ vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO, val);
+ val = scl_cal_scale2(vdisplay, vsize) << 16;
+ val |= scl_cal_scale2(hdisplay, hsize);
+ vop2_vp_write(vp, RK3568_VP_POST_SCL_FACTOR_YRGB, val);
+
+ val = 0;
+ if (hdisplay != hsize)
+ val |= RK3568_VP_POST_SCL_CTRL__HSCALEDOWN;
+ if (vdisplay != vsize)
+ val |= RK3568_VP_POST_SCL_CTRL__VSCALEDOWN;
+ vop2_vp_write(vp, RK3568_VP_POST_SCL_CTRL, val);
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
+ uint16_t vact_st_f1 = vtotal + vact_st + 1;
+ uint16_t vact_end_f1 = vact_st_f1 + vsize;
+
+ val = vact_st_f1 << 16 | vact_end_f1;
+ vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO_F1, val);
+ }
+
+ vop2_vp_write(vp, RK3568_VP_DSP_BG, 0);
+}
+
+static void rk3568_set_intf_mux(struct vop2_video_port *vp, int id,
+ uint32_t polflags)
+{
+ struct vop2 *vop2 = vp->vop2;
+ uint32_t die, dip;
+
+ die = vop2_readl(vop2, RK3568_DSP_IF_EN);
+ dip = vop2_readl(vop2, RK3568_DSP_IF_POL);
+
+ switch (id) {
+ case RK3568_VOP2_EP_RGB:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_RGB_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_RGB |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_RGB_MUX, vp->id);
+ if (polflags & POLFLAG_DCLK_INV)
+ regmap_write(vop2->grf, RK3568_GRF_VO_CON1, BIT(3 + 16) | BIT(3));
+ else
+ regmap_write(vop2->grf, RK3568_GRF_VO_CON1, BIT(3 + 16));
+ break;
+ case RK3568_VOP2_EP_HDMI:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_HDMI_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_HDMI |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_HDMI_MUX, vp->id);
+ break;
+ case RK3568_VOP2_EP_EDP:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_EDP_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_EDP |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_EDP_MUX, vp->id);
+ break;
+ case RK3568_VOP2_EP_MIPI0:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_MIPI0 |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX, vp->id);
+ dip &= ~RK3568_DSP_IF_POL__MIPI_PIN_POL;
+ dip |= FIELD_PREP(RK3568_DSP_IF_POL__MIPI_PIN_POL, polflags);
+ break;
+ case RK3568_VOP2_EP_MIPI1:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_MIPI1 |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX, vp->id);
+ dip &= ~RK3568_DSP_IF_POL__MIPI_PIN_POL;
+ dip |= FIELD_PREP(RK3568_DSP_IF_POL__MIPI_PIN_POL, polflags);
+ break;
+ case RK3568_VOP2_EP_LVDS0:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_LVDS0 |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX, vp->id);
+ dip &= ~RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL;
+ dip |= FIELD_PREP(RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL, polflags);
+ break;
+ case RK3568_VOP2_EP_LVDS1:
+ die &= ~RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX;
+ die |= RK3568_SYS_DSP_INFACE_EN_LVDS1 |
+ FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX, vp->id);
+ dip &= ~RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL;
+ dip |= FIELD_PREP(RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL, polflags);
+ break;
+ default:
+ return;
+ };
+
+ dip |= RK3568_DSP_IF_POL__CFG_DONE_IMD;
+
+ vop2_writel(vop2, RK3568_DSP_IF_EN, die);
+ vop2_writel(vop2, RK3568_DSP_IF_POL, dip);
+}
+
+static int us_to_vertical_line(struct drm_display_mode *mode, int us)
+{
+ return us * mode->clock / mode->htotal / 1000;
+}
+
+static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *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];
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ unsigned long clock = mode->crtc_clock * 1000;
+ uint16_t hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
+ uint16_t hdisplay = mode->crtc_hdisplay;
+ uint16_t htotal = mode->crtc_htotal;
+ uint16_t hact_st = mode->crtc_htotal - mode->crtc_hsync_start;
+ uint16_t hact_end = hact_st + hdisplay;
+ uint16_t vdisplay = mode->crtc_vdisplay;
+ uint16_t vtotal = mode->crtc_vtotal;
+ uint16_t vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
+ uint16_t vact_st = mode->crtc_vtotal - mode->crtc_vsync_start;
+ uint16_t vact_end = vact_st + vdisplay;
+ uint8_t out_mode;
+ uint32_t dsp_ctrl = 0;
+ int act_end;
+ uint32_t val, polflags;
+ int ret;
+ struct drm_encoder *encoder;
+
+ 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",
+ drm_mode_vrefresh(mode), vcstate->output_type, vp->id);
+
+ vop2_lock(vop2);
+
+ ret = clk_prepare_enable(vp->dclk);
+ if (ret < 0) {
+ drm_err(vop2->drm, "failed to enable dclk for video port%d - %d\n",
+ vp->id, ret);
+ return;
+ }
+
+ if (!vop2->enable_count)
+ vop2_enable(vop2);
+
+ vop2->enable_count++;
+
+ vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY);
+
+ polflags = 0;
+ if (vcstate->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+ polflags |= POLFLAG_DCLK_INV;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ polflags |= BIT(HSYNC_POSITIVE);
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ polflags |= BIT(VSYNC_POSITIVE);
+
+ drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) {
+ struct device_node *node, *parent;
+
+ parent = of_get_parent(encoder->port);
+
+ for_each_endpoint_of_node(parent, node) {
+ struct device_node *crtc_port = of_graph_get_remote_port(node);
+ struct device_node *epn;
+ struct of_endpoint endpoint;
+
+ if (crtc->port != crtc_port) {
+ of_node_put(crtc_port);
+ continue;
+ }
+
+ of_node_put(crtc_port);
+
+ epn = of_graph_get_remote_endpoint(node);
+ of_graph_parse_endpoint(epn, &endpoint);
+ of_node_put(epn);
+
+ drm_dbg(vop2->drm, "vp%d is connected to %s, id %d\n",
+ vp->id, encoder->name, endpoint.id);
+ rk3568_set_intf_mux(vp, endpoint.id, polflags);
+ }
+ of_node_put(parent);
+ }
+
+ if (vcstate->output_mode == ROCKCHIP_OUT_MODE_AAAA &&
+ !(vp_data->feature & VOP_FEATURE_OUTPUT_10BIT))
+ out_mode = ROCKCHIP_OUT_MODE_P888;
+ else
+ out_mode = vcstate->output_mode;
+
+ dsp_ctrl |= FIELD_PREP(RK3568_VP_DSP_CTRL__OUT_MODE, out_mode);
+
+ if (vop2_output_uv_swap(vcstate->bus_format, vcstate->output_mode))
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_RB_SWAP;
+
+ if (is_yuv_output(vcstate->bus_format))
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__POST_DSP_OUT_R2Y;
+
+ vop2_dither_setup(crtc, &dsp_ctrl);
+
+ vop2_vp_write(vp, RK3568_VP_DSP_HTOTAL_HS_END, (htotal << 16) | hsync_len);
+ val = hact_st << 16;
+ val |= hact_end;
+ vop2_vp_write(vp, RK3568_VP_DSP_HACT_ST_END, val);
+
+ val = vact_st << 16;
+ val |= vact_end;
+ vop2_vp_write(vp, RK3568_VP_DSP_VACT_ST_END, val);
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
+ uint16_t vact_st_f1 = vtotal + vact_st + 1;
+ uint16_t vact_end_f1 = vact_st_f1 + vdisplay;
+
+ val = vact_st_f1 << 16 | vact_end_f1;
+ vop2_vp_write(vp, RK3568_VP_DSP_VACT_ST_END_F1, val);
+
+ val = vtotal << 16 | (vtotal + vsync_len);
+ vop2_vp_write(vp, RK3568_VP_DSP_VS_ST_END_F1, val);
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_INTERLACE;
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_FILED_POL;
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__P2I_EN;
+ vtotal += vtotal + 1;
+ act_end = vact_end_f1;
+ } else {
+ act_end = vact_end;
+ }
+
+ vop2_writel(vop2, RK3568_VP_LINE_FLAG(vp->id),
+ (act_end - us_to_vertical_line(mode, 0)) << 16 | act_end);
+
+ vop2_vp_write(vp, RK3568_VP_DSP_VTOTAL_VS_END, vtotal << 16 | vsync_len);
+
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
+ dsp_ctrl |= RK3568_VP_DSP_CTRL__CORE_DCLK_DIV;
+ clock *= 2;
+ }
+
+ vop2_vp_write(vp, RK3568_VP_MIPI_CTRL, 0);
+
+ clk_set_rate(vp->dclk, clock);
+
+ vop2_post_config(crtc);
+
+ vop2_cfg_done(vp);
+
+ vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl);
+
+ drm_crtc_vblank_on(crtc);
+
+ vop2_unlock(vop2);
+}
+
+static int vop2_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct drm_plane *plane;
+ int nplanes = 0;
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state)
+ nplanes++;
+
+ if (nplanes > vp->nlayers)
+ return -EINVAL;
+
+ return 0;
+}
+
+static bool is_opaque(uint16_t alpha)
+{
+ return (alpha >> 8) == 0xff;
+}
+
+static void vop2_parse_alpha(struct vop2_alpha_config *alpha_config,
+ struct vop2_alpha *alpha)
+{
+ int src_glb_alpha_en = is_opaque(alpha_config->src_glb_alpha_value) ? 0 : 1;
+ int dst_glb_alpha_en = is_opaque(alpha_config->dst_glb_alpha_value) ? 0 : 1;
+ int src_color_mode = alpha_config->src_premulti_en ? ALPHA_SRC_PRE_MUL : ALPHA_SRC_NO_PRE_MUL;
+ int dst_color_mode = alpha_config->dst_premulti_en ? ALPHA_SRC_PRE_MUL : ALPHA_SRC_NO_PRE_MUL;
+
+ alpha->src_color_ctrl.val = 0;
+ alpha->dst_color_ctrl.val = 0;
+ alpha->src_alpha_ctrl.val = 0;
+ alpha->dst_alpha_ctrl.val = 0;
+
+ if (!alpha_config->src_pixel_alpha_en)
+ alpha->src_color_ctrl.bits.blend_mode = ALPHA_GLOBAL;
+ else if (alpha_config->src_pixel_alpha_en && !src_glb_alpha_en)
+ alpha->src_color_ctrl.bits.blend_mode = ALPHA_PER_PIX;
+ else
+ alpha->src_color_ctrl.bits.blend_mode = ALPHA_PER_PIX_GLOBAL;
+
+ alpha->src_color_ctrl.bits.alpha_en = 1;
+
+ if (alpha->src_color_ctrl.bits.blend_mode == ALPHA_GLOBAL) {
+ alpha->src_color_ctrl.bits.color_mode = src_color_mode;
+ alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_SRC_GLOBAL;
+ } else if (alpha->src_color_ctrl.bits.blend_mode == ALPHA_PER_PIX) {
+ alpha->src_color_ctrl.bits.color_mode = src_color_mode;
+ alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_ONE;
+ } else {
+ alpha->src_color_ctrl.bits.color_mode = ALPHA_SRC_PRE_MUL;
+ alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_SRC_GLOBAL;
+ }
+ alpha->src_color_ctrl.bits.glb_alpha = alpha_config->src_glb_alpha_value >> 8;
+ alpha->src_color_ctrl.bits.alpha_mode = ALPHA_STRAIGHT;
+ alpha->src_color_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION;
+
+ alpha->dst_color_ctrl.bits.alpha_mode = ALPHA_STRAIGHT;
+ alpha->dst_color_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION;
+ alpha->dst_color_ctrl.bits.blend_mode = ALPHA_GLOBAL;
+ alpha->dst_color_ctrl.bits.glb_alpha = alpha_config->dst_glb_alpha_value >> 8;
+ alpha->dst_color_ctrl.bits.color_mode = dst_color_mode;
+ alpha->dst_color_ctrl.bits.factor_mode = ALPHA_SRC_INVERSE;
+
+ alpha->src_alpha_ctrl.bits.alpha_mode = ALPHA_STRAIGHT;
+ alpha->src_alpha_ctrl.bits.blend_mode = alpha->src_color_ctrl.bits.blend_mode;
+ alpha->src_alpha_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION;
+ alpha->src_alpha_ctrl.bits.factor_mode = ALPHA_ONE;
+
+ alpha->dst_alpha_ctrl.bits.alpha_mode = ALPHA_STRAIGHT;
+ if (alpha_config->dst_pixel_alpha_en && !dst_glb_alpha_en)
+ alpha->dst_alpha_ctrl.bits.blend_mode = ALPHA_PER_PIX;
+ else
+ alpha->dst_alpha_ctrl.bits.blend_mode = ALPHA_PER_PIX_GLOBAL;
+ alpha->dst_alpha_ctrl.bits.alpha_cal_mode = ALPHA_NO_SATURATION;
+ alpha->dst_alpha_ctrl.bits.factor_mode = ALPHA_SRC_INVERSE;
+}
+
+static int vop2_find_start_mixer_id_for_vp(struct vop2 *vop2, uint8_t port_id)
+{
+ struct vop2_video_port *vp;
+ int used_layer = 0;
+ int i;
+
+ for (i = 0; i < port_id; i++) {
+ vp = &vop2->vps[i];
+ used_layer += hweight32(vp->win_mask);
+ }
+
+ return used_layer;
+}
+
+static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_win)
+{
+ uint32_t offset = (main_win->data->phys_id * 0x10);
+ struct vop2_alpha_config alpha_config;
+ struct vop2_alpha alpha;
+ struct drm_plane_state *bottom_win_pstate;
+ bool src_pixel_alpha_en = false;
+ uint16_t src_glb_alpha_val, dst_glb_alpha_val;
+ bool premulti_en = false;
+ bool swap = false;
+
+ /* At one win mode, win0 is dst/bottom win, and win1 is a all zero src/top win */
+ bottom_win_pstate = main_win->base.state;
+ src_glb_alpha_val = 0;
+ dst_glb_alpha_val = main_win->base.state->alpha;
+
+ if (!bottom_win_pstate->fb)
+ return;
+
+ alpha_config.src_premulti_en = premulti_en;
+ alpha_config.dst_premulti_en = false;
+ alpha_config.src_pixel_alpha_en = src_pixel_alpha_en;
+ alpha_config.dst_pixel_alpha_en = true; /* alpha value need transfer to next mix */
+ alpha_config.src_glb_alpha_value = src_glb_alpha_val;
+ alpha_config.dst_glb_alpha_value = dst_glb_alpha_val;
+ vop2_parse_alpha(&alpha_config, &alpha);
+
+ alpha.src_color_ctrl.bits.src_dst_swap = swap;
+ vop2_writel(vop2, RK3568_CLUSTER0_MIX_SRC_COLOR_CTRL + offset,
+ alpha.src_color_ctrl.val);
+ vop2_writel(vop2, RK3568_CLUSTER0_MIX_DST_COLOR_CTRL + offset,
+ alpha.dst_color_ctrl.val);
+ vop2_writel(vop2, RK3568_CLUSTER0_MIX_SRC_ALPHA_CTRL + offset,
+ alpha.src_alpha_ctrl.val);
+ vop2_writel(vop2, RK3568_CLUSTER0_MIX_DST_ALPHA_CTRL + offset,
+ alpha.dst_alpha_ctrl.val);
+}
+
+static void vop2_setup_alpha(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+ struct drm_framebuffer *fb;
+ struct vop2_alpha_config alpha_config;
+ struct vop2_alpha alpha;
+ struct drm_plane *plane;
+ int pixel_alpha_en;
+ int premulti_en, gpremulti_en = 0;
+ int mixer_id;
+ uint32_t offset;
+ bool bottom_layer_alpha_en = false;
+ uint32_t dst_global_alpha = DRM_BLEND_ALPHA_OPAQUE;
+
+ mixer_id = vop2_find_start_mixer_id_for_vp(vop2, vp->id);
+ alpha_config.dst_pixel_alpha_en = true; /* alpha value need transfer to next mix */
+
+ drm_atomic_crtc_for_each_plane(plane, &vp->crtc) {
+ struct vop2_win *win = to_vop2_win(plane);
+
+ if (plane->state->normalized_zpos == 0 &&
+ !is_opaque(plane->state->alpha) &&
+ !vop2_cluster_window(win)) {
+ /*
+ * If bottom layer have global alpha effect [except cluster layer,
+ * because cluster have deal with bottom layer global alpha value
+ * at cluster mix], bottom layer mix need deal with global alpha.
+ */
+ bottom_layer_alpha_en = true;
+ dst_global_alpha = plane->state->alpha;
+ }
+ }
+
+ drm_atomic_crtc_for_each_plane(plane, &vp->crtc) {
+ struct vop2_win *win = to_vop2_win(plane);
+ int zpos = plane->state->normalized_zpos;
+
+ if (plane->state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI)
+ premulti_en = 1;
+ else
+ premulti_en = 0;
+
+ plane = &win->base;
+ fb = plane->state->fb;
+
+ pixel_alpha_en = fb->format->has_alpha;
+
+ alpha_config.src_premulti_en = premulti_en;
+
+ if (bottom_layer_alpha_en && zpos == 1) {
+ gpremulti_en = premulti_en;
+ /* Cd = Cs + (1 - As) * Cd * Agd */
+ alpha_config.dst_premulti_en = false;
+ alpha_config.src_pixel_alpha_en = pixel_alpha_en;
+ alpha_config.src_glb_alpha_value = plane->state->alpha;
+ alpha_config.dst_glb_alpha_value = dst_global_alpha;
+ } else if (vop2_cluster_window(win)) {
+ /* Mix output data only have pixel alpha */
+ alpha_config.dst_premulti_en = true;
+ alpha_config.src_pixel_alpha_en = true;
+ alpha_config.src_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ } else {
+ /* Cd = Cs + (1 - As) * Cd */
+ alpha_config.dst_premulti_en = true;
+ alpha_config.src_pixel_alpha_en = pixel_alpha_en;
+ alpha_config.src_glb_alpha_value = plane->state->alpha;
+ alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ }
+
+ vop2_parse_alpha(&alpha_config, &alpha);
+
+ offset = (mixer_id + zpos - 1) * 0x10;
+ vop2_writel(vop2, RK3568_MIX0_SRC_COLOR_CTRL + offset,
+ alpha.src_color_ctrl.val);
+ vop2_writel(vop2, RK3568_MIX0_DST_COLOR_CTRL + offset,
+ alpha.dst_color_ctrl.val);
+ vop2_writel(vop2, RK3568_MIX0_SRC_ALPHA_CTRL + offset,
+ alpha.src_alpha_ctrl.val);
+ vop2_writel(vop2, RK3568_MIX0_DST_ALPHA_CTRL + offset,
+ alpha.dst_alpha_ctrl.val);
+ }
+
+ if (vp->id == 0) {
+ if (bottom_layer_alpha_en) {
+ /* Transfer pixel alpha to hdr mix */
+ alpha_config.src_premulti_en = gpremulti_en;
+ alpha_config.dst_premulti_en = true;
+ alpha_config.src_pixel_alpha_en = true;
+ alpha_config.src_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE;
+ vop2_parse_alpha(&alpha_config, &alpha);
+
+ vop2_writel(vop2, RK3568_HDR0_SRC_COLOR_CTRL,
+ alpha.src_color_ctrl.val);
+ vop2_writel(vop2, RK3568_HDR0_DST_COLOR_CTRL,
+ alpha.dst_color_ctrl.val);
+ vop2_writel(vop2, RK3568_HDR0_SRC_ALPHA_CTRL,
+ alpha.src_alpha_ctrl.val);
+ vop2_writel(vop2, RK3568_HDR0_DST_ALPHA_CTRL,
+ alpha.dst_alpha_ctrl.val);
+ } else {
+ vop2_writel(vop2, RK3568_HDR0_SRC_COLOR_CTRL, 0);
+ }
+ }
+}
+
+#define NR_VPS 3
+#define NR_MIXERS 6
+
+static void vop2_setup_layer_mixer(struct vop2_video_port *vp)
+{
+ struct vop2 *vop2 = vp->vop2;
+ struct drm_plane *plane;
+ uint32_t layer_sel = 0;
+ uint32_t port_sel;
+ int nlayer, ofs;
+ struct drm_display_mode *adjusted_mode;
+ uint16_t hsync_len;
+ uint16_t hdisplay;
+ uint32_t bg_dly;
+ uint32_t pre_scan_dly;
+ int i;
+ struct vop2_video_port *vp0 = &vop2->vps[0];
+ struct vop2_video_port *vp1 = &vop2->vps[1];
+ struct vop2_video_port *vp2 = &vop2->vps[2];
+
+ adjusted_mode = &vp->crtc.state->adjusted_mode;
+ hsync_len = adjusted_mode->crtc_hsync_end - adjusted_mode->crtc_hsync_start;
+ hdisplay = adjusted_mode->crtc_hdisplay;
+
+ bg_dly = vp->data->pre_scan_max_dly[3];
+ vop2_writel(vop2, RK3568_VP_BG_MIX_CTRL(vp->id),
+ FIELD_PREP(RK3568_VP_BG_MIX_CTRL__BG_DLY, bg_dly));
+
+ pre_scan_dly = ((bg_dly + (hdisplay >> 1) - 1) << 16) | hsync_len;
+ vop2_vp_write(vp, RK3568_VP_PRE_SCAN_HTIMING, pre_scan_dly);
+
+ vop2_writel(vop2, RK3568_OVL_CTRL, 0);
+ port_sel = vop2_readl(vop2, RK3568_OVL_PORT_SEL);
+ port_sel &= RK3568_OVL_PORT_SEL__SEL_PORT;
+
+ if (vp0->nlayers)
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT0_MUX,
+ vp0->nlayers - 1);
+ else
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT0_MUX, 8);
+
+ if (vp1->nlayers)
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX,
+ (vp0->nlayers + vp1->nlayers - 1));
+ else
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX, 8);
+
+ if (vp2->nlayers)
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT2_MUX,
+ (vp2->nlayers + vp1->nlayers + vp0->nlayers - 1));
+ else
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX, 8);
+
+ layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL);
+
+ ofs = 0;
+ for (i = 0; i < vp->id; i++)
+ ofs += vop2->vps[i].nlayers;
+
+ nlayer = 0;
+ drm_atomic_crtc_for_each_plane(plane, &vp->crtc) {
+ struct vop2_win *win = to_vop2_win(plane);
+
+ switch (win->data->phys_id) {
+ case ROCKCHIP_VOP2_CLUSTER0:
+ port_sel &= ~RK3568_OVL_PORT_SEL__CLUSTER0;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__CLUSTER0, vp->id);
+ break;
+ case ROCKCHIP_VOP2_CLUSTER1:
+ port_sel &= ~RK3568_OVL_PORT_SEL__CLUSTER1;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__CLUSTER1, vp->id);
+ break;
+ case ROCKCHIP_VOP2_ESMART0:
+ port_sel &= ~RK3568_OVL_PORT_SEL__ESMART0;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__ESMART0, vp->id);
+ break;
+ case ROCKCHIP_VOP2_ESMART1:
+ port_sel &= ~RK3568_OVL_PORT_SEL__ESMART1;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__ESMART1, vp->id);
+ break;
+ case ROCKCHIP_VOP2_SMART0:
+ port_sel &= ~RK3568_OVL_PORT_SEL__SMART0;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__SMART0, vp->id);
+ break;
+ case ROCKCHIP_VOP2_SMART1:
+ port_sel &= ~RK3568_OVL_PORT_SEL__SMART1;
+ port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__SMART1, vp->id);
+ break;
+ }
+
+ layer_sel &= ~RK3568_OVL_LAYER_SEL__LAYER(nlayer + ofs, 0x7);
+ layer_sel |= RK3568_OVL_LAYER_SEL__LAYER(nlayer + ofs, win->data->layer_sel_id);
+ nlayer++;
+ }
+
+ /* configure unused layers to 0x5 (reserved) */
+ for (; nlayer < 3; nlayer++) {
+ layer_sel &= ~RK3568_OVL_LAYER_SEL__LAYER(nlayer + ofs, 0x7);
+ layer_sel |= RK3568_OVL_LAYER_SEL__LAYER(nlayer + ofs, 5);
+ }
+
+ vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel);
+ vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel);
+ vop2_writel(vop2, RK3568_OVL_CTRL, RK3568_OVL_CTRL__LAYERSEL_REGDONE_IMD);
+}
+
+static void vop2_setup_dly_for_windows(struct vop2 *vop2)
+{
+ struct vop2_win *win;
+ int i = 0;
+ uint32_t cdly = 0, sdly = 0;
+
+ for (i = 0; i < vop2->data->win_size; i++) {
+ uint32_t dly;
+
+ win = &vop2->win[i];
+ dly = win->delay;
+
+ switch (win->data->phys_id) {
+ case ROCKCHIP_VOP2_CLUSTER0:
+ cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER0_0, dly);
+ cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER0_1, dly);
+ break;
+ case ROCKCHIP_VOP2_CLUSTER1:
+ cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER1_0, dly);
+ cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER1_1, dly);
+ break;
+ case ROCKCHIP_VOP2_ESMART0:
+ sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__ESMART0, dly);
+ break;
+ case ROCKCHIP_VOP2_ESMART1:
+ sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__ESMART1, dly);
+ break;
+ case ROCKCHIP_VOP2_SMART0:
+ sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__SMART1, dly);
+ break;
+ case ROCKCHIP_VOP2_SMART1:
+ sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__SMART0, dly);
+ break;
+ }
+ }
+
+ vop2_writel(vop2, RK3568_CLUSTER_DLY_NUM, cdly);
+ vop2_writel(vop2, RK3568_SMART_DLY_NUM, sdly);
+}
+
+static void vop2_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+ struct vop2 *vop2 = vp->vop2;
+ struct drm_plane *plane;
+
+ vp->win_mask = 0;
+
+ drm_atomic_crtc_for_each_plane(plane, crtc) {
+ struct vop2_win *win = to_vop2_win(plane);
+
+ win->delay = win->data->dly[VOP2_DLY_MODE_DEFAULT];
+
+ vp->win_mask |= BIT(win->data->phys_id);
+
+ if (vop2_cluster_window(win))
+ vop2_setup_cluster_alpha(vop2, win);
+ }
+
+ if (!vp->win_mask)
+ return;
+
+ vop2_setup_layer_mixer(vp);
+ vop2_setup_alpha(vp);
+ vop2_setup_dly_for_windows(vop2);
+}
+
+static void vop2_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+ struct vop2_video_port *vp = to_vop2_video_port(crtc);
+
+ vop2_post_config(crtc);
+
+ vop2_cfg_done(vp);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc));
+ vp->event = crtc->state->event;
+ crtc->state->event = NULL;
+ }
+
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static const struct drm_crtc_helper_funcs vop2_crtc_helper_funcs = {
+ .mode_fixup = vop2_crtc_mode_fixup,
+ .atomic_check = vop2_crtc_atomic_check,
+ .atomic_begin = vop2_crtc_atomic_begin,
+ .atomic_flush = vop2_crtc_atomic_flush,
+ .atomic_enable = vop2_crtc_atomic_enable,
+ .atomic_disable = vop2_crtc_atomic_disable,
+};
+
+static void vop2_crtc_reset(struct drm_crtc *crtc)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
+
+ if (crtc->state) {
+ __drm_atomic_helper_crtc_destroy_state(crtc->state);
+ kfree(vcstate);
+ }
+
+ vcstate = kzalloc(sizeof(*vcstate), GFP_KERNEL);
+ if (!vcstate)
+ return;
+
+ crtc->state = &vcstate->base;
+ crtc->state->crtc = crtc;
+}
+
+static struct drm_crtc_state *vop2_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+ struct rockchip_crtc_state *vcstate, *old_vcstate;
+
+ old_vcstate = to_rockchip_crtc_state(crtc->state);
+
+ vcstate = kmemdup(old_vcstate, sizeof(*old_vcstate), GFP_KERNEL);
+ if (!vcstate)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &vcstate->base);
+
+ return &vcstate->base;
+}
+
+static void vop2_crtc_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(state);
+
+ __drm_atomic_helper_crtc_destroy_state(&vcstate->base);
+ kfree(vcstate);
+}
+
+static const struct drm_crtc_funcs vop2_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .destroy = drm_crtc_cleanup,
+ .reset = vop2_crtc_reset,
+ .atomic_duplicate_state = vop2_crtc_duplicate_state,
+ .atomic_destroy_state = vop2_crtc_destroy_state,
+ .enable_vblank = vop2_crtc_enable_vblank,
+ .disable_vblank = vop2_crtc_disable_vblank,
+};
+
+static irqreturn_t vop2_isr(int irq, void *data)
+{
+ struct vop2 *vop2 = data;
+ const struct vop2_data *vop2_data = vop2->data;
+ uint32_t axi_irqs[VOP2_SYS_AXI_BUS_NUM];
+ int ret = IRQ_NONE;
+ int i;
+
+ /*
+ * The irq is shared with the iommu. If the runtime-pm state of the
+ * vop2-device is disabled the irq has to be targeted at the iommu.
+ */
+ if (!pm_runtime_get_if_in_use(vop2->dev))
+ return IRQ_NONE;
+
+ for (i = 0; i < vop2_data->nr_vps; i++) {
+ struct vop2_video_port *vp = &vop2->vps[i];
+ struct drm_crtc *crtc = &vp->crtc;
+ uint32_t irqs;
+
+ irqs = vop2_readl(vop2, RK3568_VP_INT_STATUS(vp->id));
+ vop2_writel(vop2, RK3568_VP_INT_CLR(vp->id), irqs << 16 | irqs);
+
+ if (irqs & VP_INT_DSP_HOLD_VALID) {
+ complete(&vp->dsp_hold_completion);
+ ret = IRQ_HANDLED;
+ }
+
+ if (irqs & VP_INT_FS_FIELD) {
+ unsigned long flags;
+
+ drm_crtc_handle_vblank(crtc);
+ spin_lock_irqsave(&crtc->dev->event_lock, flags);
+ if (vp->event) {
+ uint32_t val = vop2_readl(vop2, RK3568_REG_CFG_DONE);
+ if (!(val & BIT(vp->id))) {
+ drm_crtc_send_vblank_event(crtc, vp->event);
+ vp->event = NULL;
+ drm_crtc_vblank_put(crtc);
+ }
+ }
+ spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+
+ ret = IRQ_HANDLED;
+ }
+
+ if (irqs & VP_INT_POST_BUF_EMPTY) {
+ drm_err_ratelimited(vop2->drm,
+ "POST_BUF_EMPTY irq err at vp%d\n",
+ vp->id);
+ ret = IRQ_HANDLED;
+ }
+ }
+
+ axi_irqs[0] = vop2_readl(vop2, RK3568_SYS0_INT_STATUS);
+ vop2_writel(vop2, RK3568_SYS0_INT_CLR, axi_irqs[0] << 16 | axi_irqs[0]);
+ axi_irqs[1] = vop2_readl(vop2, RK3568_SYS1_INT_STATUS);
+ vop2_writel(vop2, RK3568_SYS1_INT_CLR, axi_irqs[1] << 16 | axi_irqs[1]);
+
+ for (i = 0; i < ARRAY_SIZE(axi_irqs); i++) {
+ if (axi_irqs[i] & VOP2_INT_BUS_ERRPR) {
+ drm_err_ratelimited(vop2->drm, "BUS_ERROR irq err\n");
+ ret = IRQ_HANDLED;
+ }
+ }
+
+ pm_runtime_put(vop2->dev);
+
+ return ret;
+}
+
+static int vop2_plane_init(struct vop2 *vop2, struct vop2_win *win, unsigned long possible_crtcs)
+{
+ const struct vop2_win_data *win_data = win->data;
+ unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) | BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE);
+ int ret;
+
+ ret = drm_universal_plane_init(vop2->drm, &win->base, possible_crtcs,
+ &vop2_plane_funcs, win_data->formats, win_data->nformats,
+ win_data->format_modifiers, win->type, win_data->name);
+ if (ret) {
+ drm_err(vop2->drm, "failed to initialize plane %d\n", ret);
+ return ret;
+ }
+
+ drm_plane_helper_add(&win->base, &vop2_plane_helper_funcs);
+
+ if (win->data->supported_rotations)
+ drm_plane_create_rotation_property(&win->base, DRM_MODE_ROTATE_0,
+ DRM_MODE_ROTATE_0 |
+ win->data->supported_rotations);
+ drm_plane_create_alpha_property(&win->base);
+ drm_plane_create_blend_mode_property(&win->base, blend_caps);
+ drm_plane_create_zpos_property(&win->base, win->win_id, 0,
+ vop2->registered_num_wins - 1);
+
+ return 0;
+}
+
+static struct vop2_video_port *get_activated_vp(struct vop2 *vop2, int n)
+{
+ int i, id = 0;
+
+ for (i = 0; i < vop2->data->nr_vps; i++) {
+ struct vop2_video_port *vp = &vop2->vps[i];
+
+ if (!vp->crtc.port)
+ continue;
+
+ if (n == id)
+ return vp;
+ id++;
+ }
+
+ return NULL;
+}
+
+static int vop2_create_crtc(struct vop2 *vop2)
+{
+ const struct vop2_data *vop2_data = vop2->data;
+ struct drm_device *drm = vop2->drm;
+ struct device *dev = vop2->dev;
+ struct drm_plane *plane;
+ struct device_node *port;
+ struct vop2_video_port *vp;
+ uint32_t possible_crtcs;
+ int i, nvp, nvps = 0;
+ int ret;
+
+ for (i = 0; i < vop2_data->nr_vps; i++) {
+ const struct vop2_video_port_data *vp_data;
+ struct device_node *np;
+ char dclk_name[9];
+
+ vp_data = &vop2_data->vp[i];
+ vp = &vop2->vps[i];
+ vp->vop2 = vop2;
+ vp->id = vp_data->id;
+ vp->regs = vp_data->regs;
+ vp->data = vp_data;
+
+ snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", vp->id);
+ 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);
+ return PTR_ERR(vp->dclk);
+ }
+
+ np = of_graph_get_remote_node(dev->of_node, i, -1);
+ if (!np) {
+ printk("%s: No remote for vp%d\n", __func__, i);
+ continue;
+ }
+ of_node_put(np);
+
+ port = of_graph_get_port_by_id(dev->of_node, i);
+ if (!port) {
+ drm_err(vop2->drm, "no port node found for video_port%d\n", i);
+ return -ENOENT;
+ }
+
+ vp->crtc.port = port;
+ nvps++;
+ }
+
+ nvp = 0;
+ for (i = 0; i < vop2->registered_num_wins; i++) {
+ struct vop2_win *win = &vop2->win[i];
+
+ if (win->type == DRM_PLANE_TYPE_PRIMARY) {
+ vp = get_activated_vp(vop2, nvp);
+
+ if (vp) {
+ possible_crtcs = BIT(nvp);
+ vp->primary_plane = win;
+
+ nvp++;
+ } else {
+ /* change the unused primary window to overlay window */
+ win->type = DRM_PLANE_TYPE_OVERLAY;
+ }
+ }
+
+ if (win->type == DRM_PLANE_TYPE_OVERLAY)
+ possible_crtcs = (1 << vop2_data->nr_vps) - 1;
+
+ ret = vop2_plane_init(vop2, win, possible_crtcs);
+
+ if (ret) {
+ drm_err(vop2->drm, "failed to init plane %s: %d\n", win->data->name, ret);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < vop2_data->nr_vps; i++) {
+ vp = &vop2->vps[i];
+
+ if (!vp->crtc.port)
+ continue;
+
+ plane = &vp->primary_plane->base;
+
+ ret = drm_crtc_init_with_planes(drm, &vp->crtc, plane, NULL, &vop2_crtc_funcs,
+ "video_port%d", vp->id);
+ if (ret) {
+ drm_err(vop2->drm, "crtc init for video_port%d failed\n", i);
+ return ret;
+ }
+
+ drm_crtc_helper_add(&vp->crtc, &vop2_crtc_helper_funcs);
+
+ init_completion(&vp->dsp_hold_completion);
+ }
+
+ for (i = 0; i < vop2->data->nr_vps; i++) {
+ struct vop2_video_port *vp = &vop2->vps[i];
+ if (vp->crtc.port)
+ vp->nlayers = NR_MIXERS / nvps;
+ }
+
+ return 0;
+}
+
+static void vop2_destroy_crtc(struct drm_crtc *crtc)
+{
+ of_node_put(crtc->port);
+
+ /*
+ * Destroy CRTC after vop2_plane_destroy() since vop2_disable_plane()
+ * references the CRTC.
+ */
+ drm_crtc_cleanup(crtc);
+}
+
+static int vop2_cluster_init(struct vop2_win *win)
+{
+ struct vop2 *vop2 = win->vop2;
+ int i;
+ struct reg_field vop2_cluster_regs[VOP2_WIN_MAX_REG] = {
+ [VOP2_WIN_ENABLE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 0, 0),
+ [VOP2_WIN_FORMAT] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 1, 5),
+ [VOP2_WIN_RB_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 14, 14),
+ [VOP2_WIN_DITHER_UP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 18, 18),
+ [VOP2_WIN_ACT_INFO] = REG_FIELD(RK3568_CLUSTER_WIN_ACT_INFO, 0, 31),
+ [VOP2_WIN_DSP_INFO] = REG_FIELD(RK3568_CLUSTER_WIN_DSP_INFO, 0, 31),
+ [VOP2_WIN_DSP_ST] = REG_FIELD(RK3568_CLUSTER_WIN_DSP_ST, 0, 31),
+ [VOP2_WIN_YRGB_MST] = REG_FIELD(RK3568_CLUSTER_WIN_YRGB_MST, 0, 31),
+ [VOP2_WIN_UV_MST] = REG_FIELD(RK3568_CLUSTER_WIN_CBR_MST, 0, 31),
+ [VOP2_WIN_YUV_CLIP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 19, 19),
+ [VOP2_WIN_YRGB_VIR] = REG_FIELD(RK3568_CLUSTER_WIN_VIR, 0, 15),
+ [VOP2_WIN_UV_VIR] = REG_FIELD(RK3568_CLUSTER_WIN_VIR, 16, 31),
+ [VOP2_WIN_Y2R_EN] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 8, 8),
+ [VOP2_WIN_R2Y_EN] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 9, 9),
+ [VOP2_WIN_CSC_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 10, 11),
+
+ /* Scale */
+ [VOP2_WIN_SCALE_YRGB_X] = REG_FIELD(RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB, 0, 15),
+ [VOP2_WIN_SCALE_YRGB_Y] = REG_FIELD(RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB, 16, 31),
+ [VOP2_WIN_YRGB_VER_SCL_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 14, 15),
+ [VOP2_WIN_YRGB_HOR_SCL_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 12, 13),
+ [VOP2_WIN_BIC_COE_SEL] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 2, 3),
+ [VOP2_WIN_VSD_YRGB_GT2] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 28, 28),
+ [VOP2_WIN_VSD_YRGB_GT4] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 29, 29),
+
+ /* cluster regs */
+ [VOP2_WIN_AFBC_ENABLE] = REG_FIELD(RK3568_CLUSTER_CTRL, 1, 1),
+ [VOP2_WIN_CLUSTER_ENABLE] = REG_FIELD(RK3568_CLUSTER_CTRL, 0, 0),
+ [VOP2_WIN_CLUSTER_LB_MODE] = REG_FIELD(RK3568_CLUSTER_CTRL, 4, 7),
+
+ /* afbc regs */
+ [VOP2_WIN_AFBC_FORMAT] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 2, 6),
+ [VOP2_WIN_AFBC_RB_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 9, 9),
+ [VOP2_WIN_AFBC_UV_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 10, 10),
+ [VOP2_WIN_AFBC_AUTO_GATING_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_OUTPUT_CTRL, 4, 4),
+ [VOP2_WIN_AFBC_HALF_BLOCK_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 7, 7),
+ [VOP2_WIN_AFBC_BLOCK_SPLIT_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 8, 8),
+ [VOP2_WIN_AFBC_HDR_PTR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_HDR_PTR, 0, 31),
+ [VOP2_WIN_AFBC_PIC_SIZE] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_PIC_SIZE, 0, 31),
+ [VOP2_WIN_AFBC_PIC_VIR_WIDTH] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH, 0, 15),
+ [VOP2_WIN_AFBC_TILE_NUM] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH, 16, 31),
+ [VOP2_WIN_AFBC_PIC_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_PIC_OFFSET, 0, 31),
+ [VOP2_WIN_AFBC_DSP_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_DSP_OFFSET, 0, 31),
+ [VOP2_WIN_AFBC_TRANSFORM_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_TRANSFORM_OFFSET, 0, 31),
+ [VOP2_WIN_AFBC_ROTATE_90] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 0, 0),
+ [VOP2_WIN_AFBC_ROTATE_270] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 1, 1),
+ [VOP2_WIN_XMIRROR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 2, 2),
+ [VOP2_WIN_YMIRROR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 3, 3),
+ [VOP2_WIN_UV_SWAP] = { .reg = 0xffffffff },
+ [VOP2_WIN_COLOR_KEY] = { .reg = 0xffffffff },
+ [VOP2_WIN_COLOR_KEY_EN] = { .reg = 0xffffffff },
+ [VOP2_WIN_SCALE_CBCR_X] = { .reg = 0xffffffff },
+ [VOP2_WIN_SCALE_CBCR_Y] = { .reg = 0xffffffff },
+ [VOP2_WIN_YRGB_HSCL_FILTER_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_YRGB_VSCL_FILTER_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CBCR_VER_SCL_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CBCR_HSCL_FILTER_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CBCR_HOR_SCL_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CBCR_VSCL_FILTER_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_VSD_CBCR_GT2] = { .reg = 0xffffffff },
+ [VOP2_WIN_VSD_CBCR_GT4] = { .reg = 0xffffffff },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(vop2_cluster_regs); i++)
+ vop2_cluster_regs[i].reg += win->offset;
+
+ return devm_regmap_field_bulk_alloc(vop2->dev, vop2->map, win->reg,
+ vop2_cluster_regs,
+ ARRAY_SIZE(vop2_cluster_regs));
+};
+
+static int vop2_esmart_init(struct vop2_win *win)
+{
+ struct vop2 *vop2 = win->vop2;
+ int i;
+ struct reg_field vop2_esmart_regs[VOP2_WIN_MAX_REG] = {
+ [VOP2_WIN_ENABLE] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 0, 0),
+ [VOP2_WIN_FORMAT] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 1, 5),
+ [VOP2_WIN_DITHER_UP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 12, 12),
+ [VOP2_WIN_RB_SWAP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 14, 14),
+ [VOP2_WIN_UV_SWAP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 16, 16),
+ [VOP2_WIN_ACT_INFO] = REG_FIELD(RK3568_SMART_REGION0_ACT_INFO, 0, 31),
+ [VOP2_WIN_DSP_INFO] = REG_FIELD(RK3568_SMART_REGION0_DSP_INFO, 0, 31),
+ [VOP2_WIN_DSP_ST] = REG_FIELD(RK3568_SMART_REGION0_DSP_ST, 0, 28),
+ [VOP2_WIN_YRGB_MST] = REG_FIELD(RK3568_SMART_REGION0_YRGB_MST, 0, 31),
+ [VOP2_WIN_UV_MST] = REG_FIELD(RK3568_SMART_REGION0_CBR_MST, 0, 31),
+ [VOP2_WIN_YUV_CLIP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 17, 17),
+ [VOP2_WIN_YRGB_VIR] = REG_FIELD(RK3568_SMART_REGION0_VIR, 0, 15),
+ [VOP2_WIN_UV_VIR] = REG_FIELD(RK3568_SMART_REGION0_VIR, 16, 31),
+ [VOP2_WIN_Y2R_EN] = REG_FIELD(RK3568_SMART_CTRL0, 0, 0),
+ [VOP2_WIN_R2Y_EN] = REG_FIELD(RK3568_SMART_CTRL0, 1, 1),
+ [VOP2_WIN_CSC_MODE] = REG_FIELD(RK3568_SMART_CTRL0, 2, 3),
+ [VOP2_WIN_YMIRROR] = REG_FIELD(RK3568_SMART_CTRL1, 31, 31),
+ [VOP2_WIN_COLOR_KEY] = REG_FIELD(RK3568_SMART_COLOR_KEY_CTRL, 0, 29),
+ [VOP2_WIN_COLOR_KEY_EN] = REG_FIELD(RK3568_SMART_COLOR_KEY_CTRL, 31, 31),
+
+ /* Scale */
+ [VOP2_WIN_SCALE_YRGB_X] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_YRGB, 0, 15),
+ [VOP2_WIN_SCALE_YRGB_Y] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_YRGB, 16, 31),
+ [VOP2_WIN_SCALE_CBCR_X] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_CBR, 0, 15),
+ [VOP2_WIN_SCALE_CBCR_Y] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_CBR, 16, 31),
+ [VOP2_WIN_YRGB_HOR_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 0, 1),
+ [VOP2_WIN_YRGB_HSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 2, 3),
+ [VOP2_WIN_YRGB_VER_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 4, 5),
+ [VOP2_WIN_YRGB_VSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 6, 7),
+ [VOP2_WIN_CBCR_HOR_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 8, 9),
+ [VOP2_WIN_CBCR_HSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 10, 11),
+ [VOP2_WIN_CBCR_VER_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 12, 13),
+ [VOP2_WIN_CBCR_VSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 14, 15),
+ [VOP2_WIN_BIC_COE_SEL] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 16, 17),
+ [VOP2_WIN_VSD_YRGB_GT2] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 8, 8),
+ [VOP2_WIN_VSD_YRGB_GT4] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 9, 9),
+ [VOP2_WIN_VSD_CBCR_GT2] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 10, 10),
+ [VOP2_WIN_VSD_CBCR_GT4] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 11, 11),
+ [VOP2_WIN_XMIRROR] = { .reg = 0xffffffff },
+ [VOP2_WIN_CLUSTER_ENABLE] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_ENABLE] = { .reg = 0xffffffff },
+ [VOP2_WIN_CLUSTER_LB_MODE] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_FORMAT] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_RB_SWAP] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_UV_SWAP] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_AUTO_GATING_EN] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_BLOCK_SPLIT_EN] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_PIC_VIR_WIDTH] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_TILE_NUM] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_PIC_OFFSET] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_PIC_SIZE] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_DSP_OFFSET] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_TRANSFORM_OFFSET] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_HDR_PTR] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_HALF_BLOCK_EN] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_ROTATE_270] = { .reg = 0xffffffff },
+ [VOP2_WIN_AFBC_ROTATE_90] = { .reg = 0xffffffff },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(vop2_esmart_regs); i++)
+ vop2_esmart_regs[i].reg += win->offset;
+
+ return devm_regmap_field_bulk_alloc(vop2->dev, vop2->map, win->reg,
+ vop2_esmart_regs,
+ ARRAY_SIZE(vop2_esmart_regs));
+};
+
+static int vop2_win_init(struct vop2 *vop2)
+{
+ const struct vop2_data *vop2_data = vop2->data;
+ struct vop2_win *win;
+ int i, ret;
+
+ for (i = 0; i < vop2_data->win_size; i++) {
+ const struct vop2_win_data *win_data = &vop2_data->win[i];
+
+ win = &vop2->win[i];
+ win->data = win_data;
+ win->type = win_data->type;
+ win->offset = win_data->base;
+ win->win_id = i;
+ win->vop2 = vop2;
+ if (vop2_cluster_window(win))
+ ret = vop2_cluster_init(win);
+ else
+ ret = vop2_esmart_init(win);
+ if (ret)
+ return ret;
+ }
+
+ vop2->registered_num_wins = vop2_data->win_size;
+
+ return 0;
+}
+
+/*
+ * The window registers are only updated when config done is written.
+ * Until that they read back the old value. As we read-modify-write
+ * these registers mark them as non-volatile. This makes sure we read
+ * the new values from the regmap register cache.
+ */
+static const struct regmap_range vop2_nonvolatile_range[] = {
+ regmap_reg_range(0x1000, 0x23ff),
+};
+
+static const struct regmap_access_table vop2_volatile_table = {
+ .no_ranges = vop2_nonvolatile_range,
+ .n_no_ranges = ARRAY_SIZE(vop2_nonvolatile_range),
+};
+
+static const struct regmap_config vop2_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x3000,
+ .name = "vop2",
+ .volatile_table = &vop2_volatile_table,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int vop2_bind(struct device *dev, struct device *master, void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ const struct vop2_data *vop2_data;
+ struct drm_device *drm = data;
+ struct vop2 *vop2;
+ struct resource *res;
+ size_t alloc_size;
+ int ret;
+
+ vop2_data = of_device_get_match_data(dev);
+ if (!vop2_data)
+ return -ENODEV;
+
+ /* Allocate vop2 struct and its vop2_win array */
+ alloc_size = sizeof(*vop2) + sizeof(*vop2->win) * vop2_data->win_size;
+ vop2 = devm_kzalloc(dev, alloc_size, GFP_KERNEL);
+ if (!vop2)
+ return -ENOMEM;
+
+ vop2->dev = dev;
+ vop2->data = vop2_data;
+ vop2->drm = drm;
+
+ dev_set_drvdata(dev, vop2);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+ if (!res) {
+ drm_err(vop2->drm, "failed to get vop2 register byname\n");
+ return -EINVAL;
+ }
+
+ vop2->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(vop2->regs))
+ return PTR_ERR(vop2->regs);
+ vop2->len = resource_size(res);
+
+ vop2->map = devm_regmap_init_mmio(dev, vop2->regs, &vop2_regmap_config);
+
+ ret = vop2_win_init(vop2);
+ if (ret)
+ return ret;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gamma_lut");
+ if (res) {
+ vop2->lut_regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(vop2->lut_regs))
+ return PTR_ERR(vop2->lut_regs);
+ }
+
+ vop2->grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf");
+
+ vop2->hclk = devm_clk_get(vop2->dev, "hclk");
+ if (IS_ERR(vop2->hclk)) {
+ drm_err(vop2->drm, "failed to get hclk source\n");
+ return PTR_ERR(vop2->hclk);
+ }
+
+ vop2->aclk = devm_clk_get(vop2->dev, "aclk");
+ if (IS_ERR(vop2->aclk)) {
+ drm_err(vop2->drm, "failed to get aclk source\n");
+ return PTR_ERR(vop2->aclk);
+ }
+
+ vop2->irq = platform_get_irq(pdev, 0);
+ if (vop2->irq < 0) {
+ drm_err(vop2->drm, "cannot find irq for vop2\n");
+ return vop2->irq;
+ }
+
+ mutex_init(&vop2->vop2_lock);
+
+ ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2);
+ if (ret)
+ return ret;
+
+ ret = rockchip_drm_dma_attach_device(vop2->drm, vop2->dev);
+ if (ret) {
+ drm_err(vop2->drm, "failed to attach dma mapping, %d\n", ret);
+ return ret;
+ }
+
+ ret = vop2_create_crtc(vop2);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static void vop2_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct vop2 *vop2 = dev_get_drvdata(dev);
+ struct drm_device *drm = vop2->drm;
+ struct list_head *plane_list = &drm->mode_config.plane_list;
+ struct list_head *crtc_list = &drm->mode_config.crtc_list;
+ struct drm_crtc *crtc, *tmpc;
+ struct drm_plane *plane, *tmpp;
+
+ rockchip_drm_dma_detach_device(vop2->drm, vop2->dev);
+
+ pm_runtime_disable(dev);
+
+ list_for_each_entry_safe(plane, tmpp, plane_list, head)
+ drm_plane_cleanup(plane);
+
+ list_for_each_entry_safe(crtc, tmpc, crtc_list, head)
+ vop2_destroy_crtc(crtc);
+}
+
+const struct component_ops vop2_component_ops = {
+ .bind = vop2_bind,
+ .unbind = vop2_unbind,
+};
+EXPORT_SYMBOL_GPL(vop2_component_ops);
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
new file mode 100644
index 000000000000..bb5677ff00e9
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h
@@ -0,0 +1,480 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author:Mark Yao <mark.yao@rock-chips.com>
+ */
+
+#ifndef _ROCKCHIP_DRM_VOP2_H
+#define _ROCKCHIP_DRM_VOP2_H
+
+#include "rockchip_drm_vop.h"
+
+#include <linux/regmap.h>
+#include <drm/drm_modes.h>
+
+#define VOP_FEATURE_OUTPUT_10BIT BIT(0)
+
+#define WIN_FEATURE_AFBDC BIT(0)
+#define WIN_FEATURE_CLUSTER BIT(1)
+
+/*
+ * the delay number of a window in different mode.
+ */
+enum win_dly_mode {
+ VOP2_DLY_MODE_DEFAULT, /**< default mode */
+ VOP2_DLY_MODE_HISO_S, /** HDR in SDR out mode, as a SDR window */
+ VOP2_DLY_MODE_HIHO_H, /** HDR in HDR out mode, as a HDR window */
+ VOP2_DLY_MODE_MAX,
+};
+
+struct vop_rect {
+ int width;
+ int height;
+};
+
+enum vop2_scale_up_mode {
+ VOP2_SCALE_UP_NRST_NBOR,
+ VOP2_SCALE_UP_BIL,
+ VOP2_SCALE_UP_BIC,
+};
+
+enum vop2_scale_down_mode {
+ VOP2_SCALE_DOWN_NRST_NBOR,
+ VOP2_SCALE_DOWN_BIL,
+ VOP2_SCALE_DOWN_AVG,
+};
+
+enum vop2_win_regs {
+ VOP2_WIN_ENABLE,
+ VOP2_WIN_FORMAT,
+ VOP2_WIN_CSC_MODE,
+ VOP2_WIN_XMIRROR,
+ VOP2_WIN_YMIRROR,
+ VOP2_WIN_RB_SWAP,
+ VOP2_WIN_UV_SWAP,
+ VOP2_WIN_ACT_INFO,
+ VOP2_WIN_DSP_INFO,
+ VOP2_WIN_DSP_ST,
+ VOP2_WIN_YRGB_MST,
+ VOP2_WIN_UV_MST,
+ VOP2_WIN_YRGB_VIR,
+ VOP2_WIN_UV_VIR,
+ VOP2_WIN_YUV_CLIP,
+ VOP2_WIN_Y2R_EN,
+ VOP2_WIN_R2Y_EN,
+ VOP2_WIN_COLOR_KEY,
+ VOP2_WIN_COLOR_KEY_EN,
+ VOP2_WIN_DITHER_UP,
+
+ /* scale regs */
+ VOP2_WIN_SCALE_YRGB_X,
+ VOP2_WIN_SCALE_YRGB_Y,
+ VOP2_WIN_SCALE_CBCR_X,
+ VOP2_WIN_SCALE_CBCR_Y,
+ VOP2_WIN_YRGB_HOR_SCL_MODE,
+ VOP2_WIN_YRGB_HSCL_FILTER_MODE,
+ VOP2_WIN_YRGB_VER_SCL_MODE,
+ VOP2_WIN_YRGB_VSCL_FILTER_MODE,
+ VOP2_WIN_CBCR_VER_SCL_MODE,
+ VOP2_WIN_CBCR_HSCL_FILTER_MODE,
+ VOP2_WIN_CBCR_HOR_SCL_MODE,
+ VOP2_WIN_CBCR_VSCL_FILTER_MODE,
+ VOP2_WIN_VSD_CBCR_GT2,
+ VOP2_WIN_VSD_CBCR_GT4,
+ VOP2_WIN_VSD_YRGB_GT2,
+ VOP2_WIN_VSD_YRGB_GT4,
+ VOP2_WIN_BIC_COE_SEL,
+
+ /* cluster regs */
+ VOP2_WIN_CLUSTER_ENABLE,
+ VOP2_WIN_AFBC_ENABLE,
+ VOP2_WIN_CLUSTER_LB_MODE,
+
+ /* afbc regs */
+ VOP2_WIN_AFBC_FORMAT,
+ VOP2_WIN_AFBC_RB_SWAP,
+ VOP2_WIN_AFBC_UV_SWAP,
+ VOP2_WIN_AFBC_AUTO_GATING_EN,
+ VOP2_WIN_AFBC_BLOCK_SPLIT_EN,
+ VOP2_WIN_AFBC_PIC_VIR_WIDTH,
+ VOP2_WIN_AFBC_TILE_NUM,
+ VOP2_WIN_AFBC_PIC_OFFSET,
+ VOP2_WIN_AFBC_PIC_SIZE,
+ VOP2_WIN_AFBC_DSP_OFFSET,
+ VOP2_WIN_AFBC_TRANSFORM_OFFSET,
+ VOP2_WIN_AFBC_HDR_PTR,
+ VOP2_WIN_AFBC_HALF_BLOCK_EN,
+ VOP2_WIN_AFBC_ROTATE_270,
+ VOP2_WIN_AFBC_ROTATE_90,
+ VOP2_WIN_MAX_REG,
+};
+
+struct vop2_win_data {
+ const char *name;
+ uint8_t phys_id;
+
+ uint32_t base;
+ enum drm_plane_type type;
+
+ uint32_t nformats;
+ const uint32_t *formats;
+ const uint64_t *format_modifiers;
+ const unsigned int supported_rotations;
+
+ /**
+ * @layer_sel_id: defined by register OVERLAY_LAYER_SEL of VOP2
+ */
+ int layer_sel_id;
+ uint64_t feature;
+
+ unsigned int max_upscale_factor;
+ unsigned int max_downscale_factor;
+ const uint8_t dly[VOP2_DLY_MODE_MAX];
+};
+
+struct vop2_video_port_data {
+ char id;
+ uint32_t feature;
+ uint16_t gamma_lut_len;
+ uint16_t cubic_lut_len;
+ struct vop_rect max_output;
+ const u8 pre_scan_max_dly[4];
+ const struct vop2_video_port_regs *regs;
+ int offset;
+};
+
+struct vop2_data {
+ uint8_t nr_vps;
+ uint8_t nr_mixers;
+ uint8_t nr_layers;
+ uint8_t nr_gammas;
+ const struct vop2_ctrl *ctrl;
+ const struct vop2_win_data *win;
+ const struct vop2_video_port_data *vp;
+ const struct vop_csc_table *csc_table;
+ struct vop_rect max_input;
+ struct vop_rect max_output;
+
+ unsigned int win_size;
+ unsigned int soc_id;
+};
+
+/* interrupt define */
+#define FS_NEW_INTR BIT(4)
+#define ADDR_SAME_INTR BIT(5)
+#define LINE_FLAG1_INTR BIT(6)
+#define WIN0_EMPTY_INTR BIT(7)
+#define WIN1_EMPTY_INTR BIT(8)
+#define WIN2_EMPTY_INTR BIT(9)
+#define WIN3_EMPTY_INTR BIT(10)
+#define HWC_EMPTY_INTR BIT(11)
+#define POST_BUF_EMPTY_INTR BIT(12)
+#define PWM_GEN_INTR BIT(13)
+#define DMA_FINISH_INTR BIT(14)
+#define FS_FIELD_INTR BIT(15)
+#define FE_INTR BIT(16)
+#define WB_UV_FIFO_FULL_INTR BIT(17)
+#define WB_YRGB_FIFO_FULL_INTR BIT(18)
+#define WB_COMPLETE_INTR BIT(19)
+
+/*
+ * display output interface supported by rockchip lcdc
+ */
+#define ROCKCHIP_OUT_MODE_P888 0
+#define ROCKCHIP_OUT_MODE_BT1120 0
+#define ROCKCHIP_OUT_MODE_P666 1
+#define ROCKCHIP_OUT_MODE_P565 2
+#define ROCKCHIP_OUT_MODE_BT656 5
+#define ROCKCHIP_OUT_MODE_S888 8
+#define ROCKCHIP_OUT_MODE_S888_DUMMY 12
+#define ROCKCHIP_OUT_MODE_YUV420 14
+/* for use special outface */
+#define ROCKCHIP_OUT_MODE_AAAA 15
+
+enum vop_csc_format {
+ CSC_BT601L,
+ CSC_BT709L,
+ CSC_BT601F,
+ CSC_BT2020,
+};
+
+enum src_factor_mode {
+ SRC_FAC_ALPHA_ZERO,
+ SRC_FAC_ALPHA_ONE,
+ SRC_FAC_ALPHA_DST,
+ SRC_FAC_ALPHA_DST_INVERSE,
+ SRC_FAC_ALPHA_SRC,
+ SRC_FAC_ALPHA_SRC_GLOBAL,
+};
+
+enum dst_factor_mode {
+ DST_FAC_ALPHA_ZERO,
+ DST_FAC_ALPHA_ONE,
+ DST_FAC_ALPHA_SRC,
+ DST_FAC_ALPHA_SRC_INVERSE,
+ DST_FAC_ALPHA_DST,
+ DST_FAC_ALPHA_DST_GLOBAL,
+};
+
+#define RK3568_GRF_VO_CON1 0x0364
+/* System registers definition */
+#define RK3568_REG_CFG_DONE 0x000
+#define RK3568_VERSION_INFO 0x004
+#define RK3568_SYS_AUTO_GATING_CTRL 0x008
+#define RK3568_SYS_AXI_LUT_CTRL 0x024
+#define RK3568_DSP_IF_EN 0x028
+#define RK3568_DSP_IF_CTRL 0x02c
+#define RK3568_DSP_IF_POL 0x030
+#define RK3568_WB_CTRL 0x40
+#define RK3568_WB_XSCAL_FACTOR 0x44
+#define RK3568_WB_YRGB_MST 0x48
+#define RK3568_WB_CBR_MST 0x4C
+#define RK3568_OTP_WIN_EN 0x050
+#define RK3568_LUT_PORT_SEL 0x058
+#define RK3568_SYS_STATUS0 0x060
+#define RK3568_VP_LINE_FLAG(vp) (0x70 + (vp) * 0x4)
+#define RK3568_SYS0_INT_EN 0x80
+#define RK3568_SYS0_INT_CLR 0x84
+#define RK3568_SYS0_INT_STATUS 0x88
+#define RK3568_SYS1_INT_EN 0x90
+#define RK3568_SYS1_INT_CLR 0x94
+#define RK3568_SYS1_INT_STATUS 0x98
+#define RK3568_VP_INT_EN(vp) (0xA0 + (vp) * 0x10)
+#define RK3568_VP_INT_CLR(vp) (0xA4 + (vp) * 0x10)
+#define RK3568_VP_INT_STATUS(vp) (0xA8 + (vp) * 0x10)
+#define RK3568_VP_INT_RAW_STATUS(vp) (0xAC + (vp) * 0x10)
+
+/* Video Port registers definition */
+#define RK3568_VP_DSP_CTRL 0x00
+#define RK3568_VP_MIPI_CTRL 0x04
+#define RK3568_VP_COLOR_BAR_CTRL 0x08
+#define RK3568_VP_3D_LUT_CTRL 0x10
+#define RK3568_VP_3D_LUT_MST 0x20
+#define RK3568_VP_DSP_BG 0x2C
+#define RK3568_VP_PRE_SCAN_HTIMING 0x30
+#define RK3568_VP_POST_DSP_HACT_INFO 0x34
+#define RK3568_VP_POST_DSP_VACT_INFO 0x38
+#define RK3568_VP_POST_SCL_FACTOR_YRGB 0x3C
+#define RK3568_VP_POST_SCL_CTRL 0x40
+#define RK3568_VP_POST_DSP_VACT_INFO_F1 0x44
+#define RK3568_VP_DSP_HTOTAL_HS_END 0x48
+#define RK3568_VP_DSP_HACT_ST_END 0x4C
+#define RK3568_VP_DSP_VTOTAL_VS_END 0x50
+#define RK3568_VP_DSP_VACT_ST_END 0x54
+#define RK3568_VP_DSP_VS_ST_END_F1 0x58
+#define RK3568_VP_DSP_VACT_ST_END_F1 0x5C
+#define RK3568_VP_BCSH_CTRL 0x60
+#define RK3568_VP_BCSH_BCS 0x64
+#define RK3568_VP_BCSH_H 0x68
+#define RK3568_VP_BCSH_COLOR_BAR 0x6C
+
+/* Overlay registers definition */
+#define RK3568_OVL_CTRL 0x600
+#define RK3568_OVL_LAYER_SEL 0x604
+#define RK3568_OVL_PORT_SEL 0x608
+#define RK3568_CLUSTER0_MIX_SRC_COLOR_CTRL 0x610
+#define RK3568_CLUSTER0_MIX_DST_COLOR_CTRL 0x614
+#define RK3568_CLUSTER0_MIX_SRC_ALPHA_CTRL 0x618
+#define RK3568_CLUSTER0_MIX_DST_ALPHA_CTRL 0x61C
+#define RK3568_MIX0_SRC_COLOR_CTRL 0x650
+#define RK3568_MIX0_DST_COLOR_CTRL 0x654
+#define RK3568_MIX0_SRC_ALPHA_CTRL 0x658
+#define RK3568_MIX0_DST_ALPHA_CTRL 0x65C
+#define RK3568_HDR0_SRC_COLOR_CTRL 0x6C0
+#define RK3568_HDR0_DST_COLOR_CTRL 0x6C4
+#define RK3568_HDR0_SRC_ALPHA_CTRL 0x6C8
+#define RK3568_HDR0_DST_ALPHA_CTRL 0x6CC
+#define RK3568_VP_BG_MIX_CTRL(vp) (0x6E0 + (vp) * 4)
+#define RK3568_CLUSTER_DLY_NUM 0x6F0
+#define RK3568_SMART_DLY_NUM 0x6F8
+
+/* Cluster register definition, offset relative to window base */
+#define RK3568_CLUSTER_WIN_CTRL0 0x00
+#define RK3568_CLUSTER_WIN_CTRL1 0x04
+#define RK3568_CLUSTER_WIN_YRGB_MST 0x10
+#define RK3568_CLUSTER_WIN_CBR_MST 0x14
+#define RK3568_CLUSTER_WIN_VIR 0x18
+#define RK3568_CLUSTER_WIN_ACT_INFO 0x20
+#define RK3568_CLUSTER_WIN_DSP_INFO 0x24
+#define RK3568_CLUSTER_WIN_DSP_ST 0x28
+#define RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB 0x30
+#define RK3568_CLUSTER_WIN_AFBCD_TRANSFORM_OFFSET 0x3C
+#define RK3568_CLUSTER_WIN_AFBCD_OUTPUT_CTRL 0x50
+#define RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE 0x54
+#define RK3568_CLUSTER_WIN_AFBCD_HDR_PTR 0x58
+#define RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH 0x5C
+#define RK3568_CLUSTER_WIN_AFBCD_PIC_SIZE 0x60
+#define RK3568_CLUSTER_WIN_AFBCD_PIC_OFFSET 0x64
+#define RK3568_CLUSTER_WIN_AFBCD_DSP_OFFSET 0x68
+#define RK3568_CLUSTER_WIN_AFBCD_CTRL 0x6C
+
+#define RK3568_CLUSTER_CTRL 0x100
+
+/* (E)smart register definition, offset relative to window base */
+#define RK3568_SMART_CTRL0 0x00
+#define RK3568_SMART_CTRL1 0x04
+#define RK3568_SMART_REGION0_CTRL 0x10
+#define RK3568_SMART_REGION0_YRGB_MST 0x14
+#define RK3568_SMART_REGION0_CBR_MST 0x18
+#define RK3568_SMART_REGION0_VIR 0x1C
+#define RK3568_SMART_REGION0_ACT_INFO 0x20
+#define RK3568_SMART_REGION0_DSP_INFO 0x24
+#define RK3568_SMART_REGION0_DSP_ST 0x28
+#define RK3568_SMART_REGION0_SCL_CTRL 0x30
+#define RK3568_SMART_REGION0_SCL_FACTOR_YRGB 0x34
+#define RK3568_SMART_REGION0_SCL_FACTOR_CBR 0x38
+#define RK3568_SMART_REGION0_SCL_OFFSET 0x3C
+#define RK3568_SMART_REGION1_CTRL 0x40
+#define RK3568_SMART_REGION1_YRGB_MST 0x44
+#define RK3568_SMART_REGION1_CBR_MST 0x48
+#define RK3568_SMART_REGION1_VIR 0x4C
+#define RK3568_SMART_REGION1_ACT_INFO 0x50
+#define RK3568_SMART_REGION1_DSP_INFO 0x54
+#define RK3568_SMART_REGION1_DSP_ST 0x58
+#define RK3568_SMART_REGION1_SCL_CTRL 0x60
+#define RK3568_SMART_REGION1_SCL_FACTOR_YRGB 0x64
+#define RK3568_SMART_REGION1_SCL_FACTOR_CBR 0x68
+#define RK3568_SMART_REGION1_SCL_OFFSET 0x6C
+#define RK3568_SMART_REGION2_CTRL 0x70
+#define RK3568_SMART_REGION2_YRGB_MST 0x74
+#define RK3568_SMART_REGION2_CBR_MST 0x78
+#define RK3568_SMART_REGION2_VIR 0x7C
+#define RK3568_SMART_REGION2_ACT_INFO 0x80
+#define RK3568_SMART_REGION2_DSP_INFO 0x84
+#define RK3568_SMART_REGION2_DSP_ST 0x88
+#define RK3568_SMART_REGION2_SCL_CTRL 0x90
+#define RK3568_SMART_REGION2_SCL_FACTOR_YRGB 0x94
+#define RK3568_SMART_REGION2_SCL_FACTOR_CBR 0x98
+#define RK3568_SMART_REGION2_SCL_OFFSET 0x9C
+#define RK3568_SMART_REGION3_CTRL 0xA0
+#define RK3568_SMART_REGION3_YRGB_MST 0xA4
+#define RK3568_SMART_REGION3_CBR_MST 0xA8
+#define RK3568_SMART_REGION3_VIR 0xAC
+#define RK3568_SMART_REGION3_ACT_INFO 0xB0
+#define RK3568_SMART_REGION3_DSP_INFO 0xB4
+#define RK3568_SMART_REGION3_DSP_ST 0xB8
+#define RK3568_SMART_REGION3_SCL_CTRL 0xC0
+#define RK3568_SMART_REGION3_SCL_FACTOR_YRGB 0xC4
+#define RK3568_SMART_REGION3_SCL_FACTOR_CBR 0xC8
+#define RK3568_SMART_REGION3_SCL_OFFSET 0xCC
+#define RK3568_SMART_COLOR_KEY_CTRL 0xD0
+
+/* HDR register definition */
+#define RK3568_HDR_LUT_CTRL 0x2000
+#define RK3568_HDR_LUT_MST 0x2004
+#define RK3568_SDR2HDR_CTRL 0x2010
+#define RK3568_HDR2SDR_CTRL 0x2020
+#define RK3568_HDR2SDR_SRC_RANGE 0x2024
+#define RK3568_HDR2SDR_NORMFACEETF 0x2028
+#define RK3568_HDR2SDR_DST_RANGE 0x202C
+#define RK3568_HDR2SDR_NORMFACCGAMMA 0x2030
+#define RK3568_HDR_EETF_OETF_Y0 0x203C
+#define RK3568_HDR_SAT_Y0 0x20C0
+#define RK3568_HDR_EOTF_OETF_Y0 0x20F0
+#define RK3568_HDR_OETF_DX_POW1 0x2200
+#define RK3568_HDR_OETF_XN1 0x2300
+
+#define RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN BIT(15)
+
+#define RK3568_VP_DSP_CTRL__STANDBY BIT(31)
+#define RK3568_VP_DSP_CTRL__DITHER_DOWN_MODE BIT(20)
+#define RK3568_VP_DSP_CTRL__DITHER_DOWN_SEL GENMASK(19, 18)
+#define RK3568_VP_DSP_CTRL__DITHER_DOWN_EN BIT(17)
+#define RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN BIT(16)
+#define RK3568_VP_DSP_CTRL__POST_DSP_OUT_R2Y BIT(15)
+#define RK3568_VP_DSP_CTRL__DSP_RB_SWAP BIT(9)
+#define RK3568_VP_DSP_CTRL__DSP_INTERLACE BIT(7)
+#define RK3568_VP_DSP_CTRL__DSP_FILED_POL BIT(6)
+#define RK3568_VP_DSP_CTRL__P2I_EN BIT(5)
+#define RK3568_VP_DSP_CTRL__CORE_DCLK_DIV BIT(4)
+#define RK3568_VP_DSP_CTRL__OUT_MODE GENMASK(3, 0)
+
+#define RK3568_VP_POST_SCL_CTRL__VSCALEDOWN BIT(1)
+#define RK3568_VP_POST_SCL_CTRL__HSCALEDOWN BIT(0)
+
+#define RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX GENMASK(26, 25)
+#define RK3568_SYS_DSP_INFACE_EN_LVDS1 BIT(24)
+#define RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX GENMASK(22, 21)
+#define RK3568_SYS_DSP_INFACE_EN_MIPI1 BIT(20)
+#define RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX GENMASK(19, 18)
+#define RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX GENMASK(17, 16)
+#define RK3568_SYS_DSP_INFACE_EN_EDP_MUX GENMASK(15, 14)
+#define RK3568_SYS_DSP_INFACE_EN_HDMI_MUX GENMASK(11, 10)
+#define RK3568_SYS_DSP_INFACE_EN_RGB_MUX GENMASK(9, 8)
+#define RK3568_SYS_DSP_INFACE_EN_LVDS0 BIT(5)
+#define RK3568_SYS_DSP_INFACE_EN_MIPI0 BIT(4)
+#define RK3568_SYS_DSP_INFACE_EN_EDP BIT(3)
+#define RK3568_SYS_DSP_INFACE_EN_HDMI BIT(1)
+#define RK3568_SYS_DSP_INFACE_EN_RGB BIT(0)
+
+#define RK3568_DSP_IF_POL__MIPI_PIN_POL GENMASK(19, 16)
+#define RK3568_DSP_IF_POL__EDP_PIN_POL GENMASK(15, 12)
+#define RK3568_DSP_IF_POL__HDMI_PIN_POL GENMASK(7, 4)
+#define RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL GENMASK(3, 0)
+
+#define RK3568_VP0_MIPI_CTRL__DCLK_DIV2_PHASE_LOCK BIT(5)
+#define RK3568_VP0_MIPI_CTRL__DCLK_DIV2 BIT(4)
+
+#define RK3568_SYS_AUTO_GATING_CTRL__AUTO_GATING_EN BIT(31)
+
+#define RK3568_DSP_IF_POL__CFG_DONE_IMD BIT(28)
+
+#define VOP2_SYS_AXI_BUS_NUM 2
+
+#define VOP2_CLUSTER_YUV444_10 0x12
+
+#define VOP2_COLOR_KEY_MASK BIT(31)
+
+#define RK3568_OVL_CTRL__LAYERSEL_REGDONE_IMD BIT(28)
+
+#define RK3568_VP_BG_MIX_CTRL__BG_DLY GENMASK(31, 24)
+
+#define RK3568_OVL_PORT_SEL__SEL_PORT GENMASK(31, 16)
+#define RK3568_OVL_PORT_SEL__SMART1 GENMASK(31, 30)
+#define RK3568_OVL_PORT_SEL__SMART0 GENMASK(29, 28)
+#define RK3568_OVL_PORT_SEL__ESMART1 GENMASK(27, 26)
+#define RK3568_OVL_PORT_SEL__ESMART0 GENMASK(25, 24)
+#define RK3568_OVL_PORT_SEL__CLUSTER1 GENMASK(19, 18)
+#define RK3568_OVL_PORT_SEL__CLUSTER0 GENMASK(17, 16)
+#define RK3568_OVL_PORT_SET__PORT2_MUX GENMASK(11, 8)
+#define RK3568_OVL_PORT_SET__PORT1_MUX GENMASK(7, 4)
+#define RK3568_OVL_PORT_SET__PORT0_MUX GENMASK(3, 0)
+#define RK3568_OVL_LAYER_SEL__LAYER(layer, x) ((x) << ((layer) * 4))
+
+#define RK3568_CLUSTER_DLY_NUM__CLUSTER1_1 GENMASK(31, 24)
+#define RK3568_CLUSTER_DLY_NUM__CLUSTER1_0 GENMASK(23, 16)
+#define RK3568_CLUSTER_DLY_NUM__CLUSTER0_1 GENMASK(15, 8)
+#define RK3568_CLUSTER_DLY_NUM__CLUSTER0_0 GENMASK(7, 0)
+
+#define RK3568_SMART_DLY_NUM__SMART1 GENMASK(31, 24)
+#define RK3568_SMART_DLY_NUM__SMART0 GENMASK(23, 16)
+#define RK3568_SMART_DLY_NUM__ESMART1 GENMASK(15, 8)
+#define RK3568_SMART_DLY_NUM__ESMART0 GENMASK(7, 0)
+
+#define VP_INT_DSP_HOLD_VALID BIT(6)
+#define VP_INT_FS_FIELD BIT(5)
+#define VP_INT_POST_BUF_EMPTY BIT(4)
+#define VP_INT_LINE_FLAG1 BIT(3)
+#define VP_INT_LINE_FLAG0 BIT(2)
+#define VOP2_INT_BUS_ERRPR BIT(1)
+#define VP_INT_FS BIT(0)
+
+#define POLFLAG_DCLK_INV BIT(3)
+
+enum vop2_layer_phy_id {
+ ROCKCHIP_VOP2_CLUSTER0 = 0,
+ ROCKCHIP_VOP2_CLUSTER1,
+ ROCKCHIP_VOP2_ESMART0,
+ ROCKCHIP_VOP2_ESMART1,
+ ROCKCHIP_VOP2_SMART0,
+ ROCKCHIP_VOP2_SMART1,
+ ROCKCHIP_VOP2_CLUSTER2,
+ ROCKCHIP_VOP2_CLUSTER3,
+ ROCKCHIP_VOP2_ESMART2,
+ ROCKCHIP_VOP2_ESMART3,
+ ROCKCHIP_VOP2_PHY_ID_INVALID = -1,
+};
+
+extern const struct component_ops vop2_component_ops;
+
+#endif /* _ROCKCHIP_DRM_VOP2_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
new file mode 100644
index 000000000000..2376e63d9478
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Rockchip Electronics Co.Ltd
+ * Author: Andy Yan <andy.yan@rock-chips.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "rockchip_drm_vop2.h"
+
+static const uint32_t formats_win_full_10bit[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV24,
+};
+
+static const uint32_t formats_win_full_10bit_yuyv[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV24,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_VYUY,
+};
+
+static const uint32_t formats_win_lite[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+};
+
+static const uint64_t format_modifiers[] = {
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static const uint64_t format_modifiers_afbc[] = {
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_SPARSE),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_CBR),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR |
+ AFBC_FORMAT_MOD_SPARSE),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_CBR |
+ AFBC_FORMAT_MOD_SPARSE),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR |
+ AFBC_FORMAT_MOD_CBR),
+
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR |
+ AFBC_FORMAT_MOD_CBR |
+ AFBC_FORMAT_MOD_SPARSE),
+
+ /* SPLIT mandates SPARSE, RGB modes mandates YTR */
+ DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 |
+ AFBC_FORMAT_MOD_YTR |
+ AFBC_FORMAT_MOD_SPARSE |
+ AFBC_FORMAT_MOD_SPLIT),
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static const struct vop2_video_port_data rk3568_vop_video_ports[] = {
+ {
+ .id = 0,
+ .feature = VOP_FEATURE_OUTPUT_10BIT,
+ .gamma_lut_len = 1024,
+ .cubic_lut_len = 9 * 9 * 9,
+ .max_output = { 4096, 2304 },
+ .pre_scan_max_dly = { 69, 53, 53, 42 },
+ .offset = 0xc00,
+ }, {
+ .id = 1,
+ .gamma_lut_len = 1024,
+ .max_output = { 2048, 1536 },
+ .pre_scan_max_dly = { 40, 40, 40, 40 },
+ .offset = 0xd00,
+ }, {
+ .id = 2,
+ .gamma_lut_len = 1024,
+ .max_output = { 1920, 1080 },
+ .pre_scan_max_dly = { 40, 40, 40, 40 },
+ .offset = 0xe00,
+ },
+};
+
+/*
+ * rk3568 vop with 2 cluster, 2 esmart win, 2 smart win.
+ * Every cluster can work as 4K win or split into two win.
+ * All win in cluster support AFBCD.
+ *
+ * Every esmart win and smart win support 4 Multi-region.
+ *
+ * Scale filter mode:
+ *
+ * * Cluster: bicubic for horizontal scale up, others use bilinear
+ * * ESmart:
+ * * nearest-neighbor/bilinear/bicubic for scale up
+ * * nearest-neighbor/bilinear/average for scale down
+ *
+ *
+ * @TODO describe the wind like cpu-map dt nodes;
+ */
+static const struct vop2_win_data rk3568_vop_win_data[] = {
+ {
+ .name = "Smart0-win0",
+ .phys_id = ROCKCHIP_VOP2_SMART0,
+ .base = 0x1c00,
+ .formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers,
+ .layer_sel_id = 3,
+ .supported_rotations = DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .max_upscale_factor = 8,
+ .max_downscale_factor = 8,
+ .dly = { 20, 47, 41 },
+ }, {
+ .name = "Smart1-win0",
+ .phys_id = ROCKCHIP_VOP2_SMART1,
+ .formats = formats_win_lite,
+ .nformats = ARRAY_SIZE(formats_win_lite),
+ .format_modifiers = format_modifiers,
+ .base = 0x1e00,
+ .layer_sel_id = 7,
+ .supported_rotations = DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .max_upscale_factor = 8,
+ .max_downscale_factor = 8,
+ .dly = { 20, 47, 41 },
+ }, {
+ .name = "Esmart1-win0",
+ .phys_id = ROCKCHIP_VOP2_ESMART1,
+ .formats = formats_win_full_10bit_yuyv,
+ .nformats = ARRAY_SIZE(formats_win_full_10bit_yuyv),
+ .format_modifiers = format_modifiers,
+ .base = 0x1a00,
+ .layer_sel_id = 6,
+ .supported_rotations = DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .max_upscale_factor = 8,
+ .max_downscale_factor = 8,
+ .dly = { 20, 47, 41 },
+ }, {
+ .name = "Esmart0-win0",
+ .phys_id = ROCKCHIP_VOP2_ESMART0,
+ .formats = formats_win_full_10bit_yuyv,
+ .nformats = ARRAY_SIZE(formats_win_full_10bit_yuyv),
+ .format_modifiers = format_modifiers,
+ .base = 0x1800,
+ .layer_sel_id = 2,
+ .supported_rotations = DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .max_upscale_factor = 8,
+ .max_downscale_factor = 8,
+ .dly = { 20, 47, 41 },
+ }, {
+ .name = "Cluster0-win0",
+ .phys_id = ROCKCHIP_VOP2_CLUSTER0,
+ .base = 0x1000,
+ .formats = formats_win_full_10bit,
+ .nformats = ARRAY_SIZE(formats_win_full_10bit),
+ .format_modifiers = format_modifiers_afbc,
+ .layer_sel_id = 0,
+ .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y,
+ .max_upscale_factor = 4,
+ .max_downscale_factor = 4,
+ .dly = { 0, 27, 21 },
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER,
+ }, {
+ .name = "Cluster1-win0",
+ .phys_id = ROCKCHIP_VOP2_CLUSTER1,
+ .base = 0x1200,
+ .formats = formats_win_full_10bit,
+ .nformats = ARRAY_SIZE(formats_win_full_10bit),
+ .format_modifiers = format_modifiers_afbc,
+ .layer_sel_id = 1,
+ .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .max_upscale_factor = 4,
+ .max_downscale_factor = 4,
+ .dly = { 0, 27, 21 },
+ .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER,
+ },
+};
+
+static const struct vop2_data rk3566_vop = {
+ .nr_vps = 3,
+ .nr_mixers = 5,
+ .nr_gammas = 1,
+ .max_input = { 4096, 2304 },
+ .max_output = { 4096, 2304 },
+ .vp = rk3568_vop_video_ports,
+ .win = rk3568_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3568_vop_win_data),
+ .soc_id = 3566,
+};
+
+static const struct vop2_data rk3568_vop = {
+ .nr_vps = 3,
+ .nr_mixers = 5,
+ .nr_gammas = 1,
+ .max_input = { 4096, 2304 },
+ .max_output = { 4096, 2304 },
+ .vp = rk3568_vop_video_ports,
+ .win = rk3568_vop_win_data,
+ .win_size = ARRAY_SIZE(rk3568_vop_win_data),
+ .soc_id = 3568,
+};
+
+static const struct of_device_id vop2_dt_match[] = {
+ {
+ .compatible = "rockchip,rk3566-vop",
+ .data = &rk3566_vop,
+ }, {
+ .compatible = "rockchip,rk3568-vop",
+ .data = &rk3568_vop,
+ }, {
+ },
+};
+MODULE_DEVICE_TABLE(of, vop2_dt_match);
+
+static int vop2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ return component_add(dev, &vop2_component_ops);
+}
+
+static int vop2_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &vop2_component_ops);
+
+ return 0;
+}
+
+struct platform_driver vop2_platform_driver = {
+ .probe = vop2_probe,
+ .remove = vop2_remove,
+ .driver = {
+ .name = "rockchip-vop2",
+ .of_match_table = of_match_ptr(vop2_dt_match),
+ },
+};
--
2.30.2
^ permalink raw reply related
* Re: [PATCH] iio: st-sensors: Update ST Sensor bindings
From: Geert Uytterhoeven @ 2022-01-26 14:59 UTC (permalink / raw)
To: Linus Walleij
Cc: Jonathan Cameron, linux-iio, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Rob Herring
In-Reply-To: <20210412122331.1631643-1-linus.walleij@linaro.org>
Hi Linus,
On Mon, Apr 12, 2021 at 2:24 PM Linus Walleij <linus.walleij@linaro.org> wrote:
> This adjusts the ST Sensor bindings with the more fine-grained
> syntax checks that were proposed late in the last kernel cycle
> and colliding with parallel work.
>
> Cc: devicetree@vger.kernel.org
> Reviewed-by: Rob Herring <robh@kernel.org>
> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Thanks for your patch, which is now commit 0cd71145803dc2b8
("iio: st-sensors: Update ST Sensor bindings") in v5.14.
> --- a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> +++ b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> interrupts:
> + description: interrupt line(s) connected to the DRDY line(s) and/or the
> + Intertial interrupt lines INT1 and INT2 if these exist. This means up to
> + three interrupts, and the DRDY must be the first one if it exists on
So this says three (the LSM9DS0 datasheet agrees)...
> + the package. The trigger edge of the interrupts is sometimes software
> + configurable in the hardware so the operating system should parse this
> + flag and set up the trigger edge as indicated in the device tree.
> minItems: 1
> + maxItems: 2
... while this says two?
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Aw: [PATCH 01/27] drm/encoder: Add of_graph port to struct drm_encoder
From: Frank Wunderlich @ 2022-01-26 15:04 UTC (permalink / raw)
To: Sascha Hauer
Cc: dri-devel, linux-arm-kernel, linux-rockchip, devicetree, kernel
In-Reply-To: <20220126145549.617165-2-s.hauer@pengutronix.de>
Hi
> Gesendet: Mittwoch, 26. Januar 2022 um 15:55 Uhr
> Von: "Sascha Hauer" <s.hauer@pengutronix.de>
> Betreff: [PATCH 01/27] drm/encoder: Add of_graph port to struct drm_encoder
Please use "git format-patch -vX ..." when creating your patches to make sure the version is set in every patch and not only the coverletter. That makes it easier to find Patch from specific version and shows version also in Patchwork where the coverletter is hidden
regards Frank
^ permalink raw reply
* Re: [PATCH] arm64: dts: renesas: ulcb-kf: add 9-asix sensor device
From: Geert Uytterhoeven @ 2022-01-26 15:04 UTC (permalink / raw)
To: Nikita Yushchenko
Cc: Magnus Damm, Rob Herring, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Linux Kernel Mailing List
In-Reply-To: <20220112205205.4082026-1-nikita.yoush@cogentembedded.com>
Hi Nikita,
On Wed, Jan 12, 2022 at 9:52 PM Nikita Yushchenko
<nikita.yoush@cogentembedded.com> wrote:
> This adds nodes for lsm9ds0 sensor installed on the KF board.
>
> With this patch, the sensor data becomes available over iio sysfs
> interface.
>
> Interrupt definition is not added yet, because the interrupt lines of
> lsm9ds0 are pulled to VCC on the board, which implies need for
> active-low configuration. But st_sensors drivers currently can't work
> with active-low interrupts on this chip.
>
> Signed-off-by: Nikita Yushchenko <nikita.yoush@cogentembedded.com>
Forgot something...
> --- a/arch/arm64/boot/dts/renesas/ulcb-kf.dtsi
> +++ b/arch/arm64/boot/dts/renesas/ulcb-kf.dtsi
> @@ -66,6 +66,13 @@ hdmi_3v3: regulator-hdmi-3v3 {
> regulator-max-microvolt = <3300000>;
> };
>
> + accel_3v3: regulator-acc-3v3 {
Please move up, to preserve sort order.
> + compatible = "regulator-fixed";
> + regulator-name = "accel-3v3";
> + regulator-min-microvolt = <3300000>;
> + regulator-max-microvolt = <3300000>;
> + };
> +
> hdmi1-out {
> compatible = "hdmi-connector";
> type = "a";
> @@ -208,6 +215,22 @@ pcm3168a_endpoint_c: endpoint {
> };
> };
> };
> +
> + lsm9ds0_acc_mag@1d {
Please move up, to preserve sort order.
> + compatible = "st,lsm9ds0-imu";
> + reg = <0x1d>;
> +
> + vdd-supply = <&accel_3v3>;
> + vddio-supply = <&accel_3v3>;
> + };
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [RFC PATCH v2 4/7] media: bcm2835-unicam: Add support for for CCP2/CSI2 camera interface
From: Maxime Ripard @ 2022-01-26 15:03 UTC (permalink / raw)
To: Jean-Michel Hautbois
Cc: Laurent Pinchart, dave.stevenson, devicetree, kernel-list,
linux-arm-kernel, linux-kernel, linux-media, linux-rpi-kernel,
lukasz, mchehab, naush, robh, tomi.valkeinen
In-Reply-To: <0212af6d-a5de-05bb-161b-4271692dee59@ideasonboard.com>
[-- Attachment #1: Type: text/plain, Size: 2200 bytes --]
Hi,
On Wed, Jan 26, 2022 at 03:21:40PM +0100, Jean-Michel Hautbois wrote:
> On 23/01/2022 00:26, Laurent Pinchart wrote:
> > > +struct unicam_device {
> > > + struct kref kref;
> > > +
> > > + /* V4l2 specific parameters */
> > > + struct v4l2_async_subdev asd;
> > > +
> > > + /* peripheral base address */
> > > + void __iomem *base;
> > > + /* clock gating base address */
> > > + void __iomem *clk_gate_base;
> > > + /* lp clock handle */
> > > + struct clk *clock;
> > > + /* vpu clock handle */
> > > + struct clk *vpu_clock;
> > > + /* vpu clock request */
> > > + struct clk_request *vpu_req;
> >
> > Not used (and that may be a problem).
>
> In the original linux-rpi tree, there is this portion of code in
> unicam_start_streaming:
>
> dev->vpu_req = clk_request_start(dev->vpu_clock, MIN_VPU_CLOCK_RATE);
> if (!dev->vpu_req) {
> unicam_err(dev, "failed to set up VPU clock\n");
> goto error_pipeline;
> }
>
> ret = clk_prepare_enable(dev->vpu_clock);
> if (ret) {
> unicam_err(dev, "Failed to enable VPU clock: %d\n", ret);
> goto error_pipeline;
> }
>
> And this is used as this because it depends on the non-merged series "clk:
> [PATCH v2 0/3] clk: Implement a clock request API" [1]
>
> [1]: https://lore.kernel.org/all/20210914093515.260031-1-maxime@cerno.tech/
>
> That's why I modified the code and called:
> clk_set_min_rate(dev->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
I assume this would depend on the framerate and resolution though?
> Dave, is it ok or do we need absolutely the clock request API ?
The main issue is that clk_set_min_rate will never scale the clock back
if you (or the HVS) don't have those constraints anymore. So you
eventually make the clock run at the maximum you'll ever need all the
time (which would be around 500MHz in our case).
The clock request API was an attempt at making the clock scale back to
its minimum when we no longer needed it. The current discussion points
towards changing the behavior of clk_set_min_rate:
https://lore.kernel.org/linux-clk/20220125141549.747889-1-maxime@cerno.tech/
So it looks like we won't need the clk_request API after all.
Maxime
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v20 1/5] arm64: Use insert_resource() to simplify code
From: john.p.donnelly @ 2022-01-26 15:16 UTC (permalink / raw)
To: Zhen Lei, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86,
H . Peter Anvin, linux-kernel, Dave Young, Baoquan He,
Vivek Goyal, Eric Biederman, kexec, Catalin Marinas, Will Deacon,
linux-arm-kernel, Rob Herring, Frank Rowand, devicetree,
Jonathan Corbet, linux-doc
Cc: Randy Dunlap, Feng Zhou, Kefeng Wang, Chen Zhou, Dave Kleikamp,
John Donnelly
In-Reply-To: <20220124084708.683-2-thunder.leizhen@huawei.com>
On 1/24/22 2:47 AM, Zhen Lei wrote:
> insert_resource() traverses the subtree layer by layer from the root node
> until a proper location is found. Compared with request_resource(), the
> parent node does not need to be determined in advance.
>
> In addition, move the insertion of node 'crashk_res' into function
> reserve_crashkernel() to make the associated code close together.
>
> Signed-off-by: Zhen Lei <thunder.leizhen@huawei.com>
Acked-by: John Donnelly <john.p.donnelly@oracle.com>
> ---
> arch/arm64/kernel/setup.c | 17 +++--------------
> arch/arm64/mm/init.c | 1 +
> 2 files changed, 4 insertions(+), 14 deletions(-)
>
> diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c
> index f70573928f1bff0..a81efcc359e4e78 100644
> --- a/arch/arm64/kernel/setup.c
> +++ b/arch/arm64/kernel/setup.c
> @@ -225,6 +225,8 @@ static void __init request_standard_resources(void)
> kernel_code.end = __pa_symbol(__init_begin - 1);
> kernel_data.start = __pa_symbol(_sdata);
> kernel_data.end = __pa_symbol(_end - 1);
> + insert_resource(&iomem_resource, &kernel_code);
> + insert_resource(&iomem_resource, &kernel_data);
>
> num_standard_resources = memblock.memory.cnt;
> res_size = num_standard_resources * sizeof(*standard_resources);
> @@ -246,20 +248,7 @@ static void __init request_standard_resources(void)
> res->end = __pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;
> }
>
> - request_resource(&iomem_resource, res);
> -
> - if (kernel_code.start >= res->start &&
> - kernel_code.end <= res->end)
> - request_resource(res, &kernel_code);
> - if (kernel_data.start >= res->start &&
> - kernel_data.end <= res->end)
> - request_resource(res, &kernel_data);
> -#ifdef CONFIG_KEXEC_CORE
> - /* Userspace will find "Crash kernel" region in /proc/iomem. */
> - if (crashk_res.end && crashk_res.start >= res->start &&
> - crashk_res.end <= res->end)
> - request_resource(res, &crashk_res);
> -#endif
> + insert_resource(&iomem_resource, res);
> }
> }
>
> diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
> index db63cc885771a52..90f276d46b93bc6 100644
> --- a/arch/arm64/mm/init.c
> +++ b/arch/arm64/mm/init.c
> @@ -109,6 +109,7 @@ static void __init reserve_crashkernel(void)
> kmemleak_ignore_phys(crash_base);
> crashk_res.start = crash_base;
> crashk_res.end = crash_base + crash_size - 1;
> + insert_resource(&iomem_resource, &crashk_res);
> }
> #else
> static void __init reserve_crashkernel(void)
^ permalink raw reply
* Re: [PATCH v20 2/5] arm64: kdump: introduce some macros for crash kernel reservation
From: john.p.donnelly @ 2022-01-26 15:17 UTC (permalink / raw)
To: Zhen Lei, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86,
H . Peter Anvin, linux-kernel, Dave Young, Baoquan He,
Vivek Goyal, Eric Biederman, kexec, Catalin Marinas, Will Deacon,
linux-arm-kernel, Rob Herring, Frank Rowand, devicetree,
Jonathan Corbet, linux-doc
Cc: Randy Dunlap, Feng Zhou, Kefeng Wang, Chen Zhou, Dave Kleikamp
In-Reply-To: <20220124084708.683-3-thunder.leizhen@huawei.com>
On 1/24/22 2:47 AM, Zhen Lei wrote:
> From: Chen Zhou <chenzhou10@huawei.com>
>
> Introduce macro CRASH_ALIGN for alignment, macro CRASH_ADDR_LOW_MAX
> for upper bound of low crash memory, macro CRASH_ADDR_HIGH_MAX for
> upper bound of high crash memory, use macros instead.
>
> Signed-off-by: Chen Zhou <chenzhou10@huawei.com>
> Signed-off-by: Zhen Lei <thunder.leizhen@huawei.com>
> Tested-by: John Donnelly <John.p.donnelly@oracle.com>
> Tested-by: Dave Kleikamp <dave.kleikamp@oracle.com>
Acked-by: John Donnelly <john.p.donnelly@oracle.com>
> ---
> arch/arm64/mm/init.c | 11 ++++++++---
> 1 file changed, 8 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
> index 90f276d46b93bc6..6c653a2c7cff052 100644
> --- a/arch/arm64/mm/init.c
> +++ b/arch/arm64/mm/init.c
> @@ -65,6 +65,12 @@ EXPORT_SYMBOL(memstart_addr);
> phys_addr_t arm64_dma_phys_limit __ro_after_init;
>
> #ifdef CONFIG_KEXEC_CORE
> +/* Current arm64 boot protocol requires 2MB alignment */
> +#define CRASH_ALIGN SZ_2M
> +
> +#define CRASH_ADDR_LOW_MAX arm64_dma_phys_limit
> +#define CRASH_ADDR_HIGH_MAX MEMBLOCK_ALLOC_ACCESSIBLE
> +
> /*
> * reserve_crashkernel() - reserves memory for crash kernel
> *
> @@ -75,7 +81,7 @@ phys_addr_t arm64_dma_phys_limit __ro_after_init;
> static void __init reserve_crashkernel(void)
> {
> unsigned long long crash_base, crash_size;
> - unsigned long long crash_max = arm64_dma_phys_limit;
> + unsigned long long crash_max = CRASH_ADDR_LOW_MAX;
> int ret;
>
> ret = parse_crashkernel(boot_command_line, memblock_phys_mem_size(),
> @@ -90,8 +96,7 @@ static void __init reserve_crashkernel(void)
> if (crash_base)
> crash_max = crash_base + crash_size;
>
> - /* Current arm64 boot protocol requires 2MB alignment */
> - crash_base = memblock_phys_alloc_range(crash_size, SZ_2M,
> + crash_base = memblock_phys_alloc_range(crash_size, CRASH_ALIGN,
> crash_base, crash_max);
> if (!crash_base) {
> pr_warn("cannot allocate crashkernel (size:0x%llx)\n",
^ permalink raw reply
* Re: [PATCH v20 3/5] arm64: kdump: reimplement crashkernel=X
From: john.p.donnelly @ 2022-01-26 15:18 UTC (permalink / raw)
To: Zhen Lei, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86,
H . Peter Anvin, linux-kernel, Dave Young, Baoquan He,
Vivek Goyal, Eric Biederman, kexec, Catalin Marinas, Will Deacon,
linux-arm-kernel, Rob Herring, Frank Rowand, devicetree,
Jonathan Corbet, linux-doc
Cc: Randy Dunlap, Feng Zhou, Kefeng Wang, Chen Zhou, Dave Kleikamp
In-Reply-To: <20220124084708.683-4-thunder.leizhen@huawei.com>
On 1/24/22 2:47 AM, Zhen Lei wrote:
> From: Chen Zhou <chenzhou10@huawei.com>
>
> There are following issues in arm64 kdump:
> 1. We use crashkernel=X to reserve crashkernel below 4G, which
> will fail when there is no enough low memory.
> 2. If reserving crashkernel above 4G, in this case, crash dump
> kernel will boot failure because there is no low memory available
> for allocation.
>
> To solve these issues, change the behavior of crashkernel=X and
> introduce crashkernel=X,[high,low]. crashkernel=X tries low allocation
> in DMA zone, and fall back to high allocation if it fails.
> We can also use "crashkernel=X,high" to select a region above DMA zone,
> which also tries to allocate at least 256M in DMA zone automatically.
> "crashkernel=Y,low" can be used to allocate specified size low memory.
>
> Signed-off-by: Chen Zhou <chenzhou10@huawei.com>
> Co-developed-by: Zhen Lei <thunder.leizhen@huawei.com>
> Signed-off-by: Zhen Lei <thunder.leizhen@huawei.com>
Acked-by: John Donnelly <john.p.donnelly@oracle.com>
> ---
> arch/arm64/kernel/machine_kexec.c | 9 +++-
> arch/arm64/kernel/machine_kexec_file.c | 12 ++++-
> arch/arm64/mm/init.c | 68 ++++++++++++++++++++++++--
> 3 files changed, 81 insertions(+), 8 deletions(-)
>
> diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c
> index e16b248699d5c3c..19c2d487cb08feb 100644
> --- a/arch/arm64/kernel/machine_kexec.c
> +++ b/arch/arm64/kernel/machine_kexec.c
> @@ -329,8 +329,13 @@ bool crash_is_nosave(unsigned long pfn)
>
> /* in reserved memory? */
> addr = __pfn_to_phys(pfn);
> - if ((addr < crashk_res.start) || (crashk_res.end < addr))
> - return false;
> + if ((addr < crashk_res.start) || (crashk_res.end < addr)) {
> + if (!crashk_low_res.end)
> + return false;
> +
> + if ((addr < crashk_low_res.start) || (crashk_low_res.end < addr))
> + return false;
> + }
>
> if (!kexec_crash_image)
> return true;
> diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
> index 59c648d51848886..889951291cc0f9c 100644
> --- a/arch/arm64/kernel/machine_kexec_file.c
> +++ b/arch/arm64/kernel/machine_kexec_file.c
> @@ -65,10 +65,18 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
>
> /* Exclude crashkernel region */
> ret = crash_exclude_mem_range(cmem, crashk_res.start, crashk_res.end);
> + if (ret)
> + goto out;
> +
> + if (crashk_low_res.end) {
> + ret = crash_exclude_mem_range(cmem, crashk_low_res.start, crashk_low_res.end);
> + if (ret)
> + goto out;
> + }
>
> - if (!ret)
> - ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
> + ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
>
> +out:
> kfree(cmem);
> return ret;
> }
> diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
> index 6c653a2c7cff052..a5d43feac0d7d96 100644
> --- a/arch/arm64/mm/init.c
> +++ b/arch/arm64/mm/init.c
> @@ -71,6 +71,30 @@ phys_addr_t arm64_dma_phys_limit __ro_after_init;
> #define CRASH_ADDR_LOW_MAX arm64_dma_phys_limit
> #define CRASH_ADDR_HIGH_MAX MEMBLOCK_ALLOC_ACCESSIBLE
>
> +static int __init reserve_crashkernel_low(unsigned long long low_size)
> +{
> + unsigned long long low_base;
> +
> + /* passed with crashkernel=0,low ? */
> + if (!low_size)
> + return 0;
> +
> + low_base = memblock_phys_alloc_range(low_size, CRASH_ALIGN, 0, CRASH_ADDR_LOW_MAX);
> + if (!low_base) {
> + pr_err("cannot allocate crashkernel low memory (size:0x%llx).\n", low_size);
> + return -ENOMEM;
> + }
> +
> + pr_info("crashkernel low memory reserved: 0x%llx - 0x%llx (%lld MB)\n",
> + low_base, low_base + low_size, low_size >> 20);
> +
> + crashk_low_res.start = low_base;
> + crashk_low_res.end = low_base + low_size - 1;
> + insert_resource(&iomem_resource, &crashk_low_res);
> +
> + return 0;
> +}
> +
> /*
> * reserve_crashkernel() - reserves memory for crash kernel
> *
> @@ -81,29 +105,62 @@ phys_addr_t arm64_dma_phys_limit __ro_after_init;
> static void __init reserve_crashkernel(void)
> {
> unsigned long long crash_base, crash_size;
> + unsigned long long crash_low_size = SZ_256M;
> unsigned long long crash_max = CRASH_ADDR_LOW_MAX;
> int ret;
> + bool fixed_base;
> + char *cmdline = boot_command_line;
>
> - ret = parse_crashkernel(boot_command_line, memblock_phys_mem_size(),
> + /* crashkernel=X[@offset] */
> + ret = parse_crashkernel(cmdline, memblock_phys_mem_size(),
> &crash_size, &crash_base);
> - /* no crashkernel= or invalid value specified */
> - if (ret || !crash_size)
> - return;
> + if (ret || !crash_size) {
> + unsigned long long low_size;
>
> + /* crashkernel=X,high */
> + ret = parse_crashkernel_high(cmdline, 0, &crash_size, &crash_base);
> + if (ret || !crash_size)
> + return;
> +
> + /* crashkernel=X,low */
> + ret = parse_crashkernel_low(cmdline, 0, &low_size, &crash_base);
> + if (!ret)
> + crash_low_size = low_size;
> +
> + crash_max = CRASH_ADDR_HIGH_MAX;
> + }
> +
> + fixed_base = !!crash_base;
> crash_size = PAGE_ALIGN(crash_size);
>
> /* User specifies base address explicitly. */
> if (crash_base)
> crash_max = crash_base + crash_size;
>
> +retry:
> crash_base = memblock_phys_alloc_range(crash_size, CRASH_ALIGN,
> crash_base, crash_max);
> if (!crash_base) {
> + /*
> + * Attempt to fully allocate low memory failed, fall back
> + * to high memory, the minimum required low memory will be
> + * reserved later.
> + */
> + if (!fixed_base && (crash_max == CRASH_ADDR_LOW_MAX)) {
> + crash_max = CRASH_ADDR_HIGH_MAX;
> + goto retry;
> + }
> +
> pr_warn("cannot allocate crashkernel (size:0x%llx)\n",
> crash_size);
> return;
> }
>
> + if (crash_base >= SZ_4G && reserve_crashkernel_low(crash_low_size)) {
> + memblock_phys_free(crash_base, crash_size);
> + return;
> + }
> +
> pr_info("crashkernel reserved: 0x%016llx - 0x%016llx (%lld MB)\n",
> crash_base, crash_base + crash_size, crash_size >> 20);
>
> @@ -112,6 +169,9 @@ static void __init reserve_crashkernel(void)
> * map. Inform kmemleak so that it won't try to access it.
> */
> kmemleak_ignore_phys(crash_base);
> + if (crashk_low_res.end)
> + kmemleak_ignore_phys(crashk_low_res.start);
> +
> crashk_res.start = crash_base;
> crashk_res.end = crash_base + crash_size - 1;
> insert_resource(&iomem_resource, &crashk_res);
^ permalink raw reply
* Re: [PATCH v20 4/5] of: fdt: Add memory for devices by DT property "linux,usable-memory-range"
From: john.p.donnelly @ 2022-01-26 15:19 UTC (permalink / raw)
To: Zhen Lei, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86,
H . Peter Anvin, linux-kernel, Dave Young, Baoquan He,
Vivek Goyal, Eric Biederman, kexec, Catalin Marinas, Will Deacon,
linux-arm-kernel, Rob Herring, Frank Rowand, devicetree,
Jonathan Corbet, linux-doc
Cc: Randy Dunlap, Feng Zhou, Kefeng Wang, Chen Zhou, Dave Kleikamp
In-Reply-To: <20220124084708.683-5-thunder.leizhen@huawei.com>
On 1/24/22 2:47 AM, Zhen Lei wrote:
> From: Chen Zhou <chenzhou10@huawei.com>
>
> When reserving crashkernel in high memory, some low memory is reserved
> for crash dump kernel devices and never mapped by the first kernel.
> This memory range is advertised to crash dump kernel via DT property
> under /chosen,
> linux,usable-memory-range = <BASE1 SIZE1 [BASE2 SIZE2]>
>
> We reused the DT property linux,usable-memory-range and made the low
> memory region as the second range "BASE2 SIZE2", which keeps compatibility
> with existing user-space and older kdump kernels.
>
> Crash dump kernel reads this property at boot time and call memblock_add()
> to add the low memory region after memblock_cap_memory_range() has been
> called.
>
> Signed-off-by: Chen Zhou <chenzhou10@huawei.com>
> Co-developed-by: Zhen Lei <thunder.leizhen@huawei.com>
> Signed-off-by: Zhen Lei <thunder.leizhen@huawei.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Tested-by: Dave Kleikamp <dave.kleikamp@oracle.com>
Acked-by: John Donnelly <john.p.donnelly@oracle.com>
> ---
> drivers/of/fdt.c | 33 +++++++++++++++++++++++----------
> 1 file changed, 23 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c
> index ad85ff6474ff139..df4b9d2418a13d4 100644
> --- a/drivers/of/fdt.c
> +++ b/drivers/of/fdt.c
> @@ -973,16 +973,24 @@ static void __init early_init_dt_check_for_elfcorehdr(unsigned long node)
>
> static unsigned long chosen_node_offset = -FDT_ERR_NOTFOUND;
>
> +/*
> + * The main usage of linux,usable-memory-range is for crash dump kernel.
> + * Originally, the number of usable-memory regions is one. Now there may
> + * be two regions, low region and high region.
> + * To make compatibility with existing user-space and older kdump, the low
> + * region is always the last range of linux,usable-memory-range if exist.
> + */
> +#define MAX_USABLE_RANGES 2
> +
> /**
> * early_init_dt_check_for_usable_mem_range - Decode usable memory range
> * location from flat tree
> */
> void __init early_init_dt_check_for_usable_mem_range(void)
> {
> - const __be32 *prop;
> - int len;
> - phys_addr_t cap_mem_addr;
> - phys_addr_t cap_mem_size;
> + struct memblock_region rgn[MAX_USABLE_RANGES] = {0};
> + const __be32 *prop, *endp;
> + int len, i;
> unsigned long node = chosen_node_offset;
>
> if ((long)node < 0)
> @@ -991,16 +999,21 @@ void __init early_init_dt_check_for_usable_mem_range(void)
> pr_debug("Looking for usable-memory-range property... ");
>
> prop = of_get_flat_dt_prop(node, "linux,usable-memory-range", &len);
> - if (!prop || (len < (dt_root_addr_cells + dt_root_size_cells)))
> + if (!prop || (len % (dt_root_addr_cells + dt_root_size_cells)))
> return;
>
> - cap_mem_addr = dt_mem_next_cell(dt_root_addr_cells, &prop);
> - cap_mem_size = dt_mem_next_cell(dt_root_size_cells, &prop);
> + endp = prop + (len / sizeof(__be32));
> + for (i = 0; i < MAX_USABLE_RANGES && prop < endp; i++) {
> + rgn[i].base = dt_mem_next_cell(dt_root_addr_cells, &prop);
> + rgn[i].size = dt_mem_next_cell(dt_root_size_cells, &prop);
>
> - pr_debug("cap_mem_start=%pa cap_mem_size=%pa\n", &cap_mem_addr,
> - &cap_mem_size);
> + pr_debug("cap_mem_regions[%d]: base=%pa, size=%pa\n",
> + i, &rgn[i].base, &rgn[i].size);
> + }
>
> - memblock_cap_memory_range(cap_mem_addr, cap_mem_size);
> + memblock_cap_memory_range(rgn[0].base, rgn[0].size);
> + for (i = 1; i < MAX_USABLE_RANGES && rgn[i].size; i++)
> + memblock_add(rgn[i].base, rgn[i].size);
> }
>
> #ifdef CONFIG_SERIAL_EARLYCON
^ permalink raw reply
* Re: [PATCH v20 5/5] kdump: update Documentation about crashkernel
From: john.p.donnelly @ 2022-01-26 15:19 UTC (permalink / raw)
To: Zhen Lei, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86,
H . Peter Anvin, linux-kernel, Dave Young, Baoquan He,
Vivek Goyal, Eric Biederman, kexec, Catalin Marinas, Will Deacon,
linux-arm-kernel, Rob Herring, Frank Rowand, devicetree,
Jonathan Corbet, linux-doc
Cc: Randy Dunlap, Feng Zhou, Kefeng Wang, Chen Zhou, Dave Kleikamp
In-Reply-To: <20220124084708.683-6-thunder.leizhen@huawei.com>
On 1/24/22 2:47 AM, Zhen Lei wrote:
> From: Chen Zhou <chenzhou10@huawei.com>
>
> For arm64, the behavior of crashkernel=X has been changed, which
> tries low allocation in DMA zone and fall back to high allocation
> if it fails.
>
> We can also use "crashkernel=X,high" to select a high region above
> DMA zone, which also tries to allocate at least 256M low memory in
> DMA zone automatically and "crashkernel=Y,low" can be used to allocate
> specified size low memory.
>
> So update the Documentation.
>
> Signed-off-by: Chen Zhou <chenzhou10@huawei.com>
> Signed-off-by: Zhen Lei <thunder.leizhen@huawei.com>
Acked-by: John Donnelly <john.p.donnelly@oracle.com>
> ---
> Documentation/admin-guide/kdump/kdump.rst | 11 +++++++++--
> Documentation/admin-guide/kernel-parameters.txt | 11 +++++++++--
> 2 files changed, 18 insertions(+), 4 deletions(-)
>
> diff --git a/Documentation/admin-guide/kdump/kdump.rst b/Documentation/admin-guide/kdump/kdump.rst
> index cb30ca3df27c9b2..d4c287044be0c70 100644
> --- a/Documentation/admin-guide/kdump/kdump.rst
> +++ b/Documentation/admin-guide/kdump/kdump.rst
> @@ -361,8 +361,15 @@ Boot into System Kernel
> kernel will automatically locate the crash kernel image within the
> first 512MB of RAM if X is not given.
>
> - On arm64, use "crashkernel=Y[@X]". Note that the start address of
> - the kernel, X if explicitly specified, must be aligned to 2MiB (0x200000).
> + On arm64, use "crashkernel=X" to try low allocation in DMA zone and
> + fall back to high allocation if it fails.
> + We can also use "crashkernel=X,high" to select a high region above
> + DMA zone, which also tries to allocate at least 256M low memory in
> + DMA zone automatically.
> + "crashkernel=Y,low" can be used to allocate specified size low memory.
> + Use "crashkernel=Y@X" if you really have to reserve memory from
> + specified start address X. Note that the start address of the kernel,
> + X if explicitly specified, must be aligned to 2MiB (0x200000).
>
> Load the Dump-capture Kernel
> ============================
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index f5a27f067db9ed9..65780c2ca830be0 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -792,6 +792,9 @@
> [KNL, X86-64] Select a region under 4G first, and
> fall back to reserve region above 4G when '@offset'
> hasn't been specified.
> + [KNL, ARM64] Try low allocation in DMA zone and fall back
> + to high allocation if it fails when '@offset' hasn't been
> + specified.
> See Documentation/admin-guide/kdump/kdump.rst for further details.
>
> crashkernel=range1:size1[,range2:size2,...][@offset]
> @@ -808,6 +811,8 @@
> Otherwise memory region will be allocated below 4G, if
> available.
> It will be ignored if crashkernel=X is specified.
> + [KNL, ARM64] range in high memory.
> + Allow kernel to allocate physical memory region from top.
> crashkernel=size[KMG],low
> [KNL, X86-64] range under 4G. When crashkernel=X,high
> is passed, kernel could allocate physical memory region
> @@ -816,13 +821,15 @@
> requires at least 64M+32K low memory, also enough extra
> low memory is needed to make sure DMA buffers for 32-bit
> devices won't run out. Kernel would try to allocate at
> - at least 256M below 4G automatically.
> + least 256M below 4G automatically.
> This one let user to specify own low range under 4G
> for second kernel instead.
> 0: to disable low allocation.
> It will be ignored when crashkernel=X,high is not used
> or memory reserved is below 4G.
> -
> + [KNL, ARM64] range in low memory.
> + This one let user to specify a low range in DMA zone for
> + crash dump kernel.
> cryptomgr.notests
> [KNL] Disable crypto self-tests
>
^ permalink raw reply
* Re: [PATCH] arm64: dts: renesas: ulcb-kf: add 9-asix sensor device
From: Nikita Yushchenko @ 2022-01-26 15:27 UTC (permalink / raw)
To: Geert Uytterhoeven
Cc: Magnus Damm, Rob Herring, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Linux Kernel Mailing List
In-Reply-To: <CAMuHMdU0h+VaAbwOqJE5YRK2f69H8JrVgtXsoYNwvNgGJKH7mQ@mail.gmail.com>
>> Interrupt definition is not added yet, because the interrupt lines of
>> lsm9ds0 are pulled to VCC on the board, which implies need for
>> active-low configuration. But st_sensors drivers currently can't work
>> with active-low interrupts on this chip.
>
> That's unfortunate, as DT describes hardware, not limitations of the
> software stack.
Unfortunately, if interrupt definition is added, driver does wrong things and causes board hang.
>> + vdd-supply = <&accel_3v3>;
>> + vddio-supply = <&accel_3v3>;
>
> According to the bindings, the supplies are not required, so you can
> leave them out? Or are the bindings wrong?
If supplies are not defined, warning messages about dummy regulator are logged.
> (The bindings also say "interrupts: maxItems 2", while the "interrupts:
> description" says up to three interrupts, doh...)
Chip has 3 interrupt outputs. On KF board, all those are ANDed together and result connected to SoC's
gpio that is expected to be used as a shared active-low interrupt. Driver currently claims that this
chip does not support active-low interrupts. Per datasheet, this is not true. But driver's way to set up
interrupt registers does not scale to the case when interrupts have to be configured by different bits
in several registers, that part of the driver has to be somehow rewritten. I guess nobody has ever tried
to make these drivers (st_*) to drive a compound device (accel+gyro) with interrupts.
At the same time, the device is perfectly useful without interrupts, and that is how it is enabled in
the vendor BSP.
Nikita
^ permalink raw reply
* [PATCH RFC RFT] mips: bmips: bcm63268: add TWD block binding
From: Rafał Miłecki @ 2022-01-26 15:30 UTC (permalink / raw)
To: Florian Fainelli, Rob Herring
Cc: Álvaro Fernández Rojas, Thomas Bogendoerfer,
bcm-kernel-feedback-list, linux-mips, devicetree,
Rafał Miłecki
From: Rafał Miłecki <rafal@milecki.pl>
TWD is an MFD block that contains timers, watchdog & some clocks / reset
controller.
Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
---
arch/mips/boot/dts/brcm/bcm63268.dtsi | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/arch/mips/boot/dts/brcm/bcm63268.dtsi b/arch/mips/boot/dts/brcm/bcm63268.dtsi
index c3ce49ec675f..8926417a8fbc 100644
--- a/arch/mips/boot/dts/brcm/bcm63268.dtsi
+++ b/arch/mips/boot/dts/brcm/bcm63268.dtsi
@@ -105,14 +105,20 @@ periph_intc: interrupt-controller@10000020 {
interrupts = <2>, <3>;
};
- wdt: watchdog@1000009c {
- compatible = "brcm,bcm7038-wdt";
- reg = <0x1000009c 0xc>;
+ timer-mfd@10000080 {
+ compatible = "brcm,bcm7038-twd", "simple-mfd", "syscon";
+ reg = <0x10000080 0x30>;
+ ranges = <0x0 0x10000080 0x30>;
- clocks = <&periph_osc>;
- clock-names = "refclk";
+ wdt: watchdog@1c {
+ compatible = "brcm,bcm7038-wdt";
+ reg = <0x1c 0xc>;
- timeout-sec = <30>;
+ clocks = <&periph_osc>;
+ clock-names = "refclk";
+
+ timeout-sec = <30>;
+ };
};
uart0: serial@10000180 {
--
2.31.1
^ permalink raw reply related
* [PATCH v19 0/3] Add matrix keypad driver support for Mediatek SoCs
From: Mattijs Korpershoek @ 2022-01-26 15:35 UTC (permalink / raw)
To: Dmitry Torokhov, Andy Shevchenko, Marco Felsch, Rob Herring,
Matthias Brugger, Fengping Yu, Yingjoe Chen
Cc: Mattijs Korpershoek, Fabien Parent, Kevin Hilman, linux-input,
devicetree, linux-arm-kernel, linux-mediatek, linux-kernel
Dear all,
This is a follow-up on an abandoned series, see [1]
Since Dmitry seemed generally happy with the driver, I applied his rename
recommendations.
I also did a couple of binding fixes which were needed to pass "make dt_binding_check".
Thus, I have made the following:
* All Reviewed-By: tags were kept
* Applied Marco's reviewed by on the bindings (since he approved v10)
* Fengping is still the maintainer since he is the original author of this driver
* The binding license is not changed even if checkpatch recommended it:
> WARNING: DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)
Please tell me if you would rather have me do things differently.
[1] https://lore.kernel.org/all/20200909072159.14888-1-fengping.yu@mediatek.com/
v18 -> v19:
- bindings: fix maintainer -> maintainers keyword
- bindings: add matrix-keymap.yaml ref and remove included properties
- bindings: fix compatible strings (reported by make dt_binding_check)
- bindings: reorder properties to match the required ones
- bindings: remove un-necessary descriptions
- bindings: fix multiline description for mediatek,debounce-us
- bindings: fix example to pass make dt_binding_check
- bindings: fixed licence to GPL-2.0 only (checkpatch)
- bindings: rename to mediatek,mt6779-keypad.yaml to match driver
- rename driver to mt6779-keypad.c
- prefix function names with mt6779_keypad_
- prefix globals with mt6779_keypad_
- rename CONFIG_KEYBOARD_MTK_KPD to CONFIG_KEYBOARD_MT6779
- rename module name to mt6779-keypad
- update copyright year to 2022
fengping.yu (3):
dt-bindings: input: Add bindings for Mediatek matrix keypad
Input: mt6779-keypad - Add MediaTek keypad driver
arm64: defconfig: Add CONFIG_KEYBOARD_MT6779=m
.../input/mediatek,mt6779-keypad.yaml | 80 +++++++
arch/arm64/configs/defconfig | 1 +
drivers/input/keyboard/Kconfig | 12 +
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/mt6779-keypad.c | 218 ++++++++++++++++++
5 files changed, 312 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml
create mode 100644 drivers/input/keyboard/mt6779-keypad.c
base-commit: 87a0b2fafc09766d8c55461a18345a1cfb10a7fe
--
2.32.0
^ permalink raw reply
* [PATCH v19 1/3] dt-bindings: input: Add bindings for Mediatek matrix keypad
From: Mattijs Korpershoek @ 2022-01-26 15:35 UTC (permalink / raw)
To: Dmitry Torokhov, Andy Shevchenko, Marco Felsch, Rob Herring,
Matthias Brugger, Fengping Yu, Yingjoe Chen
Cc: Mattijs Korpershoek, Fabien Parent, Kevin Hilman, linux-input,
devicetree, linux-arm-kernel, linux-mediatek, linux-kernel
In-Reply-To: <20220126153519.3637496-1-mkorpershoek@baylibre.com>
From: "fengping.yu" <fengping.yu@mediatek.com>
This patch add devicetree bindings for Mediatek matrix keypad driver.
Signed-off-by: fengping.yu <fengping.yu@mediatek.com>
Reviewed-by: Marco Felsch <m.felsch@pengutronix.de>
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
.../input/mediatek,mt6779-keypad.yaml | 80 +++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml
diff --git a/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml b/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml
new file mode 100644
index 000000000000..2c76029224a0
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/mediatek,mt6779-keypad.yaml
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/mediatek,mt6779-keypad.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Mediatek's Keypad Controller device tree bindings
+
+maintainers:
+ - Fengping Yu <fengping.yu@mediatek.com>
+
+allOf:
+ - $ref: "/schemas/input/matrix-keymap.yaml#"
+
+description: |
+ Mediatek's Keypad controller is used to interface a SoC with a matrix-type
+ keypad device. The keypad controller supports multiple row and column lines.
+ A key can be placed at each intersection of a unique row and a unique column.
+ The keypad controller can sense a key-press and key-release and report the
+ event using a interrupt to the cpu.
+
+properties:
+ compatible:
+ oneOf:
+ - const: mediatek,mt6779-keypad
+ - items:
+ - enum:
+ - mediatek,mt6873-keypad
+ - const: mediatek,mt6779-keypad
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ description: Names of the clocks listed in clocks property in the same order
+ items:
+ - const: kpd
+
+ wakeup-source:
+ description: use any event on keypad as wakeup event
+ type: boolean
+
+ mediatek,debounce-us:
+ description: |
+ Debounce interval in microseconds, if not specified, the default
+ value is 16000
+ maximum: 256000
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/input/input.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ kp@10010000 {
+ compatible = "mediatek,mt6779-keypad";
+ reg = <0 0x10010000 0 0x1000>;
+ interrupts = <GIC_SPI 75 IRQ_TYPE_EDGE_FALLING>;
+ clocks = <&clk26m>;
+ clock-names = "kpd";
+ };
+ };
--
2.32.0
^ permalink raw reply related
* Re: [PATCH] arm64: dts: renesas: ulcb-kf: add 9-asix sensor device
From: Geert Uytterhoeven @ 2022-01-26 15:35 UTC (permalink / raw)
To: Nikita Yushchenko
Cc: Magnus Damm, Rob Herring, Linux-Renesas,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Linux Kernel Mailing List
In-Reply-To: <a30c55ad-ec14-6cb8-0384-b07b79212351@cogentembedded.com>
Hi Nikita,
On Wed, Jan 26, 2022 at 4:28 PM Nikita Yushchenko
<nikita.yoush@cogentembedded.com> wrote:
> >> Interrupt definition is not added yet, because the interrupt lines of
> >> lsm9ds0 are pulled to VCC on the board, which implies need for
> >> active-low configuration. But st_sensors drivers currently can't work
> >> with active-low interrupts on this chip.
> >
> > That's unfortunate, as DT describes hardware, not limitations of the
> > software stack.
>
> Unfortunately, if interrupt definition is added, driver does wrong things and causes board hang.
OK.
> >> + vdd-supply = <&accel_3v3>;
> >> + vddio-supply = <&accel_3v3>;
> >
> > According to the bindings, the supplies are not required, so you can
> > leave them out? Or are the bindings wrong?
>
> If supplies are not defined, warning messages about dummy regulator are logged.
OK.
> > (The bindings also say "interrupts: maxItems 2", while the "interrupts:
> > description" says up to three interrupts, doh...)
>
> Chip has 3 interrupt outputs. On KF board, all those are ANDed together and result connected to SoC's
> gpio that is expected to be used as a shared active-low interrupt. Driver currently claims that this
> chip does not support active-low interrupts. Per datasheet, this is not true. But driver's way to set up
> interrupt registers does not scale to the case when interrupts have to be configured by different bits
> in several registers, that part of the driver has to be somehow rewritten. I guess nobody has ever tried
> to make these drivers (st_*) to drive a compound device (accel+gyro) with interrupts.
>
> At the same time, the device is perfectly useful without interrupts, and that is how it is enabled in
> the vendor BSP.
OK, will queue in renesas-devel for v5.18, with the low-hanging fruits
(node names, order) fixed.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* [PATCH v19 2/3] Input: mt6779-keypad - Add MediaTek keypad driver
From: Mattijs Korpershoek @ 2022-01-26 15:35 UTC (permalink / raw)
To: Dmitry Torokhov, Andy Shevchenko, Marco Felsch, Rob Herring,
Matthias Brugger, Fengping Yu, Yingjoe Chen
Cc: Mattijs Korpershoek, Fabien Parent, Kevin Hilman, linux-input,
devicetree, linux-arm-kernel, linux-mediatek, linux-kernel
In-Reply-To: <20220126153519.3637496-1-mkorpershoek@baylibre.com>
From: "fengping.yu" <fengping.yu@mediatek.com>
This patch adds matrix keypad support for Mediatek SoCs.
Signed-off-by: fengping.yu <fengping.yu@mediatek.com>
Reviewed-by: Marco Felsch <m.felsch@pengutronix.de>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Reviewed-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
---
drivers/input/keyboard/Kconfig | 12 ++
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/mt6779-keypad.c | 218 +++++++++++++++++++++++++
3 files changed, 231 insertions(+)
create mode 100644 drivers/input/keyboard/mt6779-keypad.c
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 0c607da9ee10..03a9530f620e 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -779,6 +779,18 @@ config KEYBOARD_BCM
To compile this driver as a module, choose M here: the
module will be called bcm-keypad.
+config KEYBOARD_MT6779
+ tristate "MediaTek Keypad Support"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ select REGMAP_MMIO
+ select INPUT_MATRIXKMAP
+ help
+ Say Y here if you want to use the keypad on MediaTek SoCs.
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mt6779-keypad.
+
config KEYBOARD_MTK_PMIC
tristate "MediaTek PMIC keys support"
depends on MFD_MT6397
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index e3c8648f834e..721936e90290 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o
obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o
obj-$(CONFIG_KEYBOARD_MCS) += mcs_touchkey.o
obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o
+obj-$(CONFIG_KEYBOARD_MT6779) += mt6779-keypad.o
obj-$(CONFIG_KEYBOARD_MTK_PMIC) += mtk-pmic-keys.o
obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o
obj-$(CONFIG_KEYBOARD_NOMADIK) += nomadik-ske-keypad.o
diff --git a/drivers/input/keyboard/mt6779-keypad.c b/drivers/input/keyboard/mt6779-keypad.c
new file mode 100644
index 000000000000..1564ed529e09
--- /dev/null
+++ b/drivers/input/keyboard/mt6779-keypad.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc.
+ * Author Fengping Yu <fengping.yu@mediatek.com>
+ */
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MTK_KPD_NAME "mt6779-keypad"
+#define MTK_KPD_MEM 0x0004
+#define MTK_KPD_DEBOUNCE 0x0018
+#define MTK_KPD_DEBOUNCE_MASK GENMASK(13, 0)
+#define MTK_KPD_DEBOUNCE_MAX_US 256000
+#define MTK_KPD_NUM_MEMS 5
+#define MTK_KPD_NUM_BITS 136 /* 4*32+8 MEM5 only use 8 BITS */
+
+struct mt6779_keypad {
+ struct regmap *regmap;
+ struct input_dev *input_dev;
+ struct clk *clk;
+ void __iomem *base;
+ u32 n_rows;
+ u32 n_cols;
+ DECLARE_BITMAP(keymap_state, MTK_KPD_NUM_BITS);
+};
+
+static const struct regmap_config mt6779_keypad_regmap_cfg = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = sizeof(u32),
+ .max_register = 36,
+};
+
+static irqreturn_t mt6779_keypad_irq_handler(int irq, void *dev_id)
+{
+ struct mt6779_keypad *keypad = dev_id;
+ unsigned short *keycode = keypad->input_dev->keycode;
+ DECLARE_BITMAP(new_state, MTK_KPD_NUM_BITS);
+ DECLARE_BITMAP(change, MTK_KPD_NUM_BITS);
+ int bit_nr;
+ int pressed;
+ unsigned short code;
+ int row, col;
+ int row_shift = get_count_order(keypad->n_cols);
+
+ regmap_bulk_read(keypad->regmap, MTK_KPD_MEM,
+ new_state, MTK_KPD_NUM_MEMS);
+
+ bitmap_xor(change, new_state, keypad->keymap_state, MTK_KPD_NUM_BITS);
+
+ for_each_set_bit(bit_nr, change, MTK_KPD_NUM_BITS) {
+ /* For 32bits register, only bits [15:0] use to indicate key status */
+ if (bit_nr % 32 >= 16)
+ continue;
+
+ /* 1: not pressed, 0: pressed */
+ pressed = !test_bit(bit_nr, new_state);
+ dev_dbg(&keypad->input_dev->dev, "%s",
+ pressed ? "pressed" : "released");
+
+ row = bit_nr / 32;
+ col = bit_nr % 32;
+
+ code = keycode[MATRIX_SCAN_CODE(row, col, row_shift)];
+
+ input_report_key(keypad->input_dev, code, pressed);
+ input_sync(keypad->input_dev);
+
+ dev_dbg(&keypad->input_dev->dev,
+ "report Linux keycode = %d\n", code);
+ }
+
+ bitmap_copy(keypad->keymap_state, new_state, MTK_KPD_NUM_BITS);
+
+ return IRQ_HANDLED;
+}
+
+static void mt6779_keypad_clk_disable(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int mt6779_keypad_pdrv_probe(struct platform_device *pdev)
+{
+ struct mt6779_keypad *keypad;
+ unsigned int irq;
+ u32 debounce;
+ bool wakeup;
+ int error;
+
+ keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL);
+ if (!keypad)
+ return -ENOMEM;
+
+ keypad->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(keypad->base))
+ return PTR_ERR(keypad->base);
+
+ keypad->regmap = devm_regmap_init_mmio(&pdev->dev,
+ keypad->base,
+ &mt6779_keypad_regmap_cfg);
+ if (IS_ERR(keypad->regmap)) {
+ dev_err(&pdev->dev,
+ "regmap init failed:%pe\n", keypad->regmap);
+ return PTR_ERR(keypad->regmap);
+ }
+
+ bitmap_fill(keypad->keymap_state, MTK_KPD_NUM_BITS);
+
+ keypad->input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!keypad->input_dev) {
+ dev_err(&pdev->dev, "Failed to allocate input dev\n");
+ return -ENOMEM;
+ }
+
+ keypad->input_dev->name = MTK_KPD_NAME;
+ keypad->input_dev->id.bustype = BUS_HOST;
+
+ error = matrix_keypad_parse_properties(&pdev->dev, &keypad->n_rows,
+ &keypad->n_cols);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to parse keypad params\n");
+ return error;
+ }
+
+ if (device_property_read_u32(&pdev->dev, "mediatek,debounce-us",
+ &debounce))
+ debounce = 16000;
+
+ if (debounce > MTK_KPD_DEBOUNCE_MAX_US) {
+ dev_err(&pdev->dev, "Debounce time exceeds the maximum allowed time %dus\n",
+ MTK_KPD_DEBOUNCE_MAX_US);
+ return -EINVAL;
+ }
+
+ wakeup = device_property_read_bool(&pdev->dev, "wakeup-source");
+
+ dev_dbg(&pdev->dev, "n_row=%d n_col=%d debounce=%d\n",
+ keypad->n_rows, keypad->n_cols, debounce);
+
+ error = matrix_keypad_build_keymap(NULL, NULL,
+ keypad->n_rows,
+ keypad->n_cols,
+ NULL,
+ keypad->input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to build keymap\n");
+ return error;
+ }
+
+ regmap_write(keypad->regmap, MTK_KPD_DEBOUNCE,
+ debounce * 32 / 1000 & MTK_KPD_DEBOUNCE_MASK);
+
+ keypad->clk = devm_clk_get(&pdev->dev, "kpd");
+ if (IS_ERR(keypad->clk))
+ return PTR_ERR(keypad->clk);
+
+ error = clk_prepare_enable(keypad->clk);
+ if (error) {
+ dev_err(&pdev->dev, "cannot prepare/enable keypad clock\n");
+ return error;
+ }
+
+ error = devm_add_action_or_reset(&pdev->dev, mt6779_keypad_clk_disable, keypad->clk);
+ if (error)
+ return error;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ error = devm_request_threaded_irq(&pdev->dev, irq,
+ NULL, mt6779_keypad_irq_handler,
+ IRQF_ONESHOT,
+ MTK_KPD_NAME, keypad);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to request IRQ#%d:%d\n",
+ irq, error);
+ return error;
+ }
+
+ error = input_register_device(keypad->input_dev);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to register device\n");
+ return error;
+ }
+
+ error = device_init_wakeup(&pdev->dev, wakeup);
+ if (error)
+ dev_warn(&pdev->dev, "device_init_wakeup fail\n");
+
+ return 0;
+}
+
+static const struct of_device_id mt6779_keypad_of_match[] = {
+ { .compatible = "mediatek,mt6779-keypad" },
+ { .compatible = "mediatek,mt6873-keypad" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver mt6779_keypad_pdrv = {
+ .probe = mt6779_keypad_pdrv_probe,
+ .driver = {
+ .name = MTK_KPD_NAME,
+ .of_match_table = mt6779_keypad_of_match,
+ },
+};
+module_platform_driver(mt6779_keypad_pdrv);
+
+MODULE_AUTHOR("Mediatek Corporation");
+MODULE_DESCRIPTION("MTK Keypad (KPD) Driver");
+MODULE_LICENSE("GPL");
--
2.32.0
^ permalink raw reply related
* [PATCH v19 3/3] arm64: defconfig: Add CONFIG_KEYBOARD_MT6779=m
From: Mattijs Korpershoek @ 2022-01-26 15:35 UTC (permalink / raw)
To: Dmitry Torokhov, Andy Shevchenko, Marco Felsch, Rob Herring,
Matthias Brugger, Fengping Yu, Yingjoe Chen
Cc: Mattijs Korpershoek, Fabien Parent, Kevin Hilman, linux-input,
devicetree, linux-arm-kernel, linux-mediatek, linux-kernel
In-Reply-To: <20220126153519.3637496-1-mkorpershoek@baylibre.com>
From: "fengping.yu" <fengping.yu@mediatek.com>
Add Mediatek matrix keypad support in defconfig.
Signed-off-by: fengping.yu <fengping.yu@mediatek.com>
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
Reviewed-by: Marco Felsch <m.felsch@pengutronix.de>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index f2e2b9bdd702..099a9e68711c 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -390,6 +390,7 @@ CONFIG_KEYBOARD_GPIO=y
CONFIG_KEYBOARD_SNVS_PWRKEY=m
CONFIG_KEYBOARD_IMX_SC_KEY=m
CONFIG_KEYBOARD_CROS_EC=y
+CONFIG_KEYBOARD_MT6779=m
CONFIG_INPUT_TOUCHSCREEN=y
CONFIG_TOUCHSCREEN_ATMEL_MXT=m
CONFIG_TOUCHSCREEN_GOODIX=m
--
2.32.0
^ permalink raw reply related
* Re: [PATCH v4 2/2] pwm: Add clock based PWM output driver
From: Uwe Kleine-König @ 2022-01-26 15:35 UTC (permalink / raw)
To: Nikita Travkin
Cc: Krzysztof Kozlowski, thierry.reding, lee.jones, robh+dt, sboyd,
linus.walleij, masneyb, sean.anderson, linux-pwm, devicetree,
linux-kernel, ~postmarketos/upstreaming
In-Reply-To: <72e076e7d0612667d503cb1196abb3c7@trvn.ru>
[-- Attachment #1: Type: text/plain, Size: 7080 bytes --]
On Wed, Jan 26, 2022 at 06:35:15PM +0500, Nikita Travkin wrote:
> Krzysztof Kozlowski писал(а) 26.01.2022 18:18:
> > On 26/01/2022 13:58, Nikita Travkin wrote:
> >> Some systems have clocks exposed to external devices. If the clock
> >> controller supports duty-cycle configuration, such clocks can be used as
> >> pwm outputs. In fact PWM and CLK subsystems are interfaced with in a
> >> similar way and an "opposite" driver already exists (clk-pwm). Add a
> >> driver that would enable pwm devices to be used via clk subsystem.
> >>
> >> Signed-off-by: Nikita Travkin <nikita@trvn.ru>
> >> --
> >>
> >> Changes in v2:
> >> - Address Uwe's review comments:
> >> - Round set clk rate up
> >> - Add a description with limitations of the driver
> >> - Disable and unprepare clock before removing pwmchip
> >> Changes in v3:
> >> - Use 64bit version of div round up
> >> - Address Uwe's review comments:
> >> - Reword the limitations to avoid incorrect claims
> >> - Move the clk_enabled flag assignment
> >> - Drop unnecessary statements
> >> ---
> >> drivers/pwm/Kconfig | 10 +++
> >> drivers/pwm/Makefile | 1 +
> >> drivers/pwm/pwm-clk.c | 139 ++++++++++++++++++++++++++++++++++++++++++
> >> 3 files changed, 150 insertions(+)
> >> create mode 100644 drivers/pwm/pwm-clk.c
> >>
> >> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> >> index 21e3b05a5153..daa2491a4054 100644
> >> --- a/drivers/pwm/Kconfig
> >> +++ b/drivers/pwm/Kconfig
> >> @@ -140,6 +140,16 @@ config PWM_BRCMSTB
> >> To compile this driver as a module, choose M Here: the module
> >> will be called pwm-brcmstb.c.
> >>
> >> +config PWM_CLK
> >> + tristate "Clock based PWM support"
> >> + depends on HAVE_CLK || COMPILE_TEST
> >> + help
> >> + Generic PWM framework driver for outputs that can be
> >> + muxed to clocks.
> >> +
> >> + To compile this driver as a module, choose M here: the module
> >> + will be called pwm-clk.
> >> +
> >> config PWM_CLPS711X
> >> tristate "CLPS711X PWM support"
> >> depends on ARCH_CLPS711X || COMPILE_TEST
> >> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> >> index 708840b7fba8..4a860103c470 100644
> >> --- a/drivers/pwm/Makefile
> >> +++ b/drivers/pwm/Makefile
> >> @@ -10,6 +10,7 @@ obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o
> >> obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o
> >> obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o
> >> obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o
> >> +obj-$(CONFIG_PWM_CLK) += pwm-clk.o
> >> obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
> >> obj-$(CONFIG_PWM_CRC) += pwm-crc.o
> >> obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o
> >> diff --git a/drivers/pwm/pwm-clk.c b/drivers/pwm/pwm-clk.c
> >> new file mode 100644
> >> index 000000000000..b3bfa12a0e73
> >> --- /dev/null
> >> +++ b/drivers/pwm/pwm-clk.c
> >> @@ -0,0 +1,139 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/*
> >> + * Clock based PWM controller
> >> + *
> >> + * Copyright (c) 2021 Nikita Travkin <nikita@trvn.ru>
> >> + *
> >> + * This is an "adapter" driver that allows PWM consumers to use
> >> + * system clocks with duty cycle control as PWM outputs.
> >> + *
> >> + * Limitations:
> >> + * - Glitches are possible when new pwm state is applied.
> >> + * - Due to the fact that exact behavior depends on the underlying
> >> + * clock driver, various limitations are possible.
> >> + * - Period depends on the clock and, in general, not guaranteed.
> >> + * - Underlying clock may not be able to give 0% or 100% duty cycle
> >> + * (constant off or on), exact behavior will depend on the clock.
> >> + * - When the PWM is disabled, the clock will be disabled as well,
> >> + * line state will depend on the clock.
> >> + */
> >> +
> >> +#include <linux/kernel.h>
> >> +#include <linux/math64.h>
> >> +#include <linux/err.h>
> >> +#include <linux/module.h>
> >> +#include <linux/of.h>
> >> +#include <linux/platform_device.h>
> >> +#include <linux/clk.h>
> >> +#include <linux/pwm.h>
> >> +
> >> +struct pwm_clk_chip {
> >> + struct pwm_chip chip;
> >> + struct clk *clk;
> >> + bool clk_enabled;
> >> +};
> >> +
> >> +#define to_pwm_clk_chip(_chip) container_of(_chip, struct pwm_clk_chip, chip)
> >> +
> >> +static int pwm_clk_apply(struct pwm_chip *pwm_chip, struct pwm_device *pwm,
> >> + const struct pwm_state *state)
> >> +{
> >> + struct pwm_clk_chip *chip = to_pwm_clk_chip(pwm_chip);
> >> + int ret;
> >> + u32 rate;
> >> + u64 period = state->period;
> >> + u64 duty_cycle = state->duty_cycle;
> >> +
> >> + if (!state->enabled) {
> >> + if (pwm->state.enabled) {
> >> + clk_disable(chip->clk);
> >> + chip->clk_enabled = false;
> >> + }
> >> + return 0;
> >> + } else if (!pwm->state.enabled) {
> >> + ret = clk_enable(chip->clk);
> >> + if (ret)
> >> + return ret;
> >> + chip->clk_enabled = true;
> >> + }
> >> +
> >> + rate = DIV64_U64_ROUND_UP(NSEC_PER_SEC, period);
> >> + ret = clk_set_rate(chip->clk, rate);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + if (state->polarity == PWM_POLARITY_INVERSED)
> >> + duty_cycle = period - duty_cycle;
> >> +
> >> + return clk_set_duty_cycle(chip->clk, duty_cycle, period);
> >> +}
> >> +
> >> +static const struct pwm_ops pwm_clk_ops = {
> >> + .apply = pwm_clk_apply,
> >> + .owner = THIS_MODULE,
> >> +};
> >> +
> >> +static int pwm_clk_probe(struct platform_device *pdev)
> >> +{
> >> + struct pwm_clk_chip *chip;
> >> + int ret;
> >> +
> >> + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
> >> + if (!chip)
> >> + return -ENOMEM;
> >> +
> >> + chip->clk = devm_clk_get(&pdev->dev, NULL);
> >> + if (IS_ERR(chip->clk))
> >> + return dev_err_probe(&pdev->dev, PTR_ERR(chip->clk),
> >> + "Failed to get clock\n");
> >> +
> >> + chip->chip.dev = &pdev->dev;
> >> + chip->chip.ops = &pwm_clk_ops;
> >> + chip->chip.npwm = 1;
> >> +
> >> + ret = clk_prepare(chip->clk);
> >> + if (ret < 0)
> >> + dev_err_probe(&pdev->dev, ret, "Failed to prepare clock\n");
> >> +
> >> + ret = pwmchip_add(&chip->chip);
> >> + if (ret < 0)
> >> + dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n");
> >> +
> >
> > What is the point of probing the driver if pwmchip_add() fails? This
> > should be rather fatal error.
> >
> > The same with clock. If preparing clock fails, there is little point in
> > enabling/disabling it later.
> >
>
> Uh oh... Seems like I forgot a return in both cases... For some reason
> I had an incorrect assumption in my mind that dev_err_probe is a macro
> that does the return on it's own, yet I used it correctly just a couple
> of lines earlier...
dev_err_probe returns the error code, so
return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n");
is the right magic.
Best regards
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | https://www.pengutronix.de/ |
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* [PATCH v10 0/1] Add clkcfg driver for Microchip PolarFire SoC
From: conor.dooley @ 2022-01-26 15:40 UTC (permalink / raw)
To: mturquette, sboyd, linux-clk, robh+dt, devicetree
Cc: krzysztof.kozlowski, geert, david.abdurachmanov, palmer,
daire.mcnamara, cyril.jean, conor.dooley
From: Conor Dooley <conor.dooley@microchip.com>
This patch adds support for the clkcfg hardware block on the Microchip
PolarFire SoC
Changes since v9:
* removed devm_clk_get, replaced by index 0 clk_parent_data
* moved CLK_HW_INIT_x out of register functions
* dropped no longer needed name/flags/parent struct members
* added CLK_DIVIDER_ONE_BASED flags
* added rationale for critical clocks
* use lock directly
* dropped mpfs_periph_clk_recalc_rate
* dropped manual devm_ _unregisters
* dropped goto err in probe function
* dropped dt-binding
Changes since v8:
* Use devm_clk_hw_unregister directly
* Get parent clk in probe rather than register function
Changes since v7:
* Dropped clock-output-names again (oops)
Changes since v6:
* Dropped clock-output-names *as a required property*
* Dropped if(lock) check on spinlocks, added spinlocks to all
read-modify-write register access
* Removed kfree()s on non-dynamically allocated variables
* Use devm_clk_get to get the reference clock
* Account for reserved clock when calculating the size of num_clks
Changes since v5:
* Dropped clock-output-names property
Major changes since v4:
* Adjusted license for microchip,mpfs-clock.h to match microchip,mpfs.yaml
* Corrected the number of clocks to 33 from 32
Major changes since v3:
* Patch reformatted so microchip,mpfs-clock.h is part of device-tree patch
Major changes since v2:
* In mpfs_cfg_clk_set_rate, return immediately if divider_get_val
returns <0
* rebased to v5.12-rc1
Major changes since v1:
* Dependency on SOC_MICROCHIP_POLARFIRE
* All references to PFSOC/pfsoc changed to MPFS/mpfs
* Cleaned error handling in _probe
* Re-ordered code to place structs et al at top
Daire McNamara (1):
clk: microchip: Add driver for Microchip PolarFire SoC
drivers/clk/Kconfig | 4 +-
drivers/clk/Makefile | 2 +-
drivers/clk/microchip/Kconfig | 10 +
drivers/clk/microchip/Makefile | 1 +
drivers/clk/microchip/clk-mpfs.c | 382 +++++++++++++++++++++++++++++++
5 files changed, 395 insertions(+), 4 deletions(-)
create mode 100644 drivers/clk/microchip/Kconfig
create mode 100644 drivers/clk/microchip/clk-mpfs.c
--
2.32.0
^ permalink raw reply
* [PATCH v10 1/1] clk: microchip: Add driver for Microchip PolarFire SoC
From: conor.dooley @ 2022-01-26 15:40 UTC (permalink / raw)
To: mturquette, sboyd, linux-clk, robh+dt, devicetree
Cc: krzysztof.kozlowski, geert, david.abdurachmanov, palmer,
daire.mcnamara, cyril.jean, conor.dooley, Padmarao Bengari
In-Reply-To: <20220126154003.3797323-1-conor.dooley@microchip.com>
From: Daire McNamara <daire.mcnamara@microchip.com>
Add support for clock configuration on Microchip PolarFire SoC
Reviewed-by: Geert Uytterhoeven <geert@linux-m68k.org>
Tested-by: Geert Uytterhoeven <geert@linux-m68k.org>
Co-developed-by: Padmarao Bengari <padmarao.begari@microchip.com>
Signed-off-by: Padmarao Bengari <padmarao.begari@microchip.com>
Signed-off-by: Daire McNamara <daire.mcnamara@microchip.com>
Co-developed-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
---
drivers/clk/Kconfig | 4 +-
drivers/clk/Makefile | 2 +-
drivers/clk/microchip/Kconfig | 10 +
drivers/clk/microchip/Makefile | 1 +
drivers/clk/microchip/clk-mpfs.c | 382 +++++++++++++++++++++++++++++++
5 files changed, 395 insertions(+), 4 deletions(-)
create mode 100644 drivers/clk/microchip/Kconfig
create mode 100644 drivers/clk/microchip/clk-mpfs.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index ad4256d54361..678a865021e2 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -330,9 +330,6 @@ config COMMON_CLK_PXA
help
Support for the Marvell PXA SoC.
-config COMMON_CLK_PIC32
- def_bool COMMON_CLK && MACH_PIC32
-
config COMMON_CLK_OXNAS
bool "Clock driver for the OXNAS SoC Family"
depends on ARCH_OXNAS || COMPILE_TEST
@@ -407,6 +404,7 @@ source "drivers/clk/keystone/Kconfig"
source "drivers/clk/mediatek/Kconfig"
source "drivers/clk/meson/Kconfig"
source "drivers/clk/mstar/Kconfig"
+source "drivers/clk/microchip/Kconfig"
source "drivers/clk/mvebu/Kconfig"
source "drivers/clk/pistachio/Kconfig"
source "drivers/clk/qcom/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 16e588630472..61271926b16b 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -91,7 +91,7 @@ obj-$(CONFIG_ARCH_KEYSTONE) += keystone/
obj-$(CONFIG_MACH_LOONGSON32) += loongson1/
obj-y += mediatek/
obj-$(CONFIG_ARCH_MESON) += meson/
-obj-$(CONFIG_MACH_PIC32) += microchip/
+obj-y += microchip/
ifeq ($(CONFIG_COMMON_CLK), y)
obj-$(CONFIG_ARCH_MMP) += mmp/
endif
diff --git a/drivers/clk/microchip/Kconfig b/drivers/clk/microchip/Kconfig
new file mode 100644
index 000000000000..e1af380d8c9c
--- /dev/null
+++ b/drivers/clk/microchip/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config COMMON_CLK_PIC32
+ def_bool COMMON_CLK && MACH_PIC32
+
+config MCHP_CLK_MPFS
+ bool "Clk driver for PolarFire SoC"
+ depends on (RISCV && SOC_MICROCHIP_POLARFIRE) || COMPILE_TEST
+ help
+ Supports Clock Configuration for PolarFire SoC
\ No newline at end of file
diff --git a/drivers/clk/microchip/Makefile b/drivers/clk/microchip/Makefile
index f34b247e870f..5fa6dcf30a9a 100644
--- a/drivers/clk/microchip/Makefile
+++ b/drivers/clk/microchip/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_COMMON_CLK_PIC32) += clk-core.o
obj-$(CONFIG_PIC32MZDA) += clk-pic32mzda.o
+obj-$(CONFIG_MCHP_CLK_MPFS) += clk-mpfs.o
diff --git a/drivers/clk/microchip/clk-mpfs.c b/drivers/clk/microchip/clk-mpfs.c
new file mode 100644
index 000000000000..eec461b906d8
--- /dev/null
+++ b/drivers/clk/microchip/clk-mpfs.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Daire McNamara,<daire.mcnamara@microchip.com>
+ * Copyright (C) 2020 Microchip Technology Inc. All rights reserved.
+ */
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <dt-bindings/clock/microchip,mpfs-clock.h>
+
+/* address offset of control registers */
+#define REG_CLOCK_CONFIG_CR 0x08u
+#define REG_SUBBLK_CLOCK_CR 0x84u
+#define REG_SUBBLK_RESET_CR 0x88u
+
+struct mpfs_clock_data {
+ void __iomem *base;
+ struct clk_hw_onecell_data hw_data;
+};
+
+struct mpfs_cfg_clock {
+ const struct clk_div_table *table;
+ unsigned int id;
+ u8 shift;
+ u8 width;
+};
+
+struct mpfs_cfg_hw_clock {
+ struct mpfs_cfg_clock cfg;
+ void __iomem *sys_base;
+ struct clk_hw hw;
+ struct clk_init_data init;
+};
+
+#define to_mpfs_cfg_clk(_hw) container_of(_hw, struct mpfs_cfg_hw_clock, hw)
+
+struct mpfs_periph_clock {
+ unsigned int id;
+ u8 shift;
+};
+
+struct mpfs_periph_hw_clock {
+ struct mpfs_periph_clock periph;
+ void __iomem *sys_base;
+ struct clk_hw hw;
+};
+
+#define to_mpfs_periph_clk(_hw) container_of(_hw, struct mpfs_periph_hw_clock, hw)
+
+/*
+ * mpfs_clk_lock prevents anything else from writing to the
+ * mpfs clk block while a software locked register is being written.
+ */
+static DEFINE_SPINLOCK(mpfs_clk_lock);
+
+static const struct clk_parent_data mpfs_cfg_parent[] = {
+ { .index = 0 },
+};
+
+static const struct clk_div_table mpfs_div_cpu_axi_table[] = {
+ { 0, 1 }, { 1, 2 }, { 2, 4 }, { 3, 8 },
+ { 0, 0 }
+};
+
+static const struct clk_div_table mpfs_div_ahb_table[] = {
+ { 1, 2 }, { 2, 4}, { 3, 8 },
+ { 0, 0 }
+};
+
+static unsigned long mpfs_cfg_clk_recalc_rate(struct clk_hw *hw, unsigned long prate)
+{
+ struct mpfs_cfg_hw_clock *cfg_hw = to_mpfs_cfg_clk(hw);
+ struct mpfs_cfg_clock *cfg = &cfg_hw->cfg;
+ void __iomem *base_addr = cfg_hw->sys_base;
+ u32 val;
+
+ val = readl_relaxed(base_addr + REG_CLOCK_CONFIG_CR) >> cfg->shift;
+ val &= clk_div_mask(cfg->width);
+
+ return prate / (1u << val);
+}
+
+static long mpfs_cfg_clk_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
+{
+ struct mpfs_cfg_hw_clock *cfg_hw = to_mpfs_cfg_clk(hw);
+ struct mpfs_cfg_clock *cfg = &cfg_hw->cfg;
+
+ return divider_round_rate(hw, rate, prate, cfg->table, cfg->width, CLK_DIVIDER_ONE_BASED);
+}
+
+static int mpfs_cfg_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long prate)
+{
+ struct mpfs_cfg_hw_clock *cfg_hw = to_mpfs_cfg_clk(hw);
+ struct mpfs_cfg_clock *cfg = &cfg_hw->cfg;
+ void __iomem *base_addr = cfg_hw->sys_base;
+ unsigned long flags;
+ u32 val;
+ int divider_setting;
+
+ divider_setting = divider_get_val(rate, prate, cfg->table, cfg->width,
+ CLK_DIVIDER_ONE_BASED);
+
+ if (divider_setting < 0)
+ return divider_setting;
+
+ spin_lock_irqsave(&mpfs_clk_lock, flags);
+
+ val = readl_relaxed(base_addr + REG_CLOCK_CONFIG_CR);
+ val &= ~(clk_div_mask(cfg->width) << cfg_hw->cfg.shift);
+ val |= divider_setting << cfg->shift;
+ writel_relaxed(val, base_addr + REG_CLOCK_CONFIG_CR);
+
+ spin_unlock_irqrestore(&mpfs_clk_lock, flags);
+
+ return 0;
+}
+
+static const struct clk_ops mpfs_clk_cfg_ops = {
+ .recalc_rate = mpfs_cfg_clk_recalc_rate,
+ .round_rate = mpfs_cfg_clk_round_rate,
+ .set_rate = mpfs_cfg_clk_set_rate,
+};
+
+#define CLK_CFG(_id, _name, _parent, _shift, _width, _table, _flags) { \
+ .cfg.id = _id, \
+ .cfg.shift = _shift, \
+ .cfg.width = _width, \
+ .cfg.table = _table, \
+ .hw.init = CLK_HW_INIT_PARENTS_DATA(_name, _parent, &mpfs_clk_cfg_ops, \
+ _flags), \
+}
+
+static struct mpfs_cfg_hw_clock mpfs_cfg_clks[] = {
+ CLK_CFG(CLK_CPU, "clk_cpu", mpfs_cfg_parent, 0, 2, mpfs_div_cpu_axi_table, 0),
+ CLK_CFG(CLK_AXI, "clk_axi", mpfs_cfg_parent, 2, 2, mpfs_div_cpu_axi_table, 0),
+ CLK_CFG(CLK_AHB, "clk_ahb", mpfs_cfg_parent, 4, 2, mpfs_div_ahb_table, 0),
+};
+
+static int mpfs_clk_register_cfg(struct device *dev, struct mpfs_cfg_hw_clock *cfg_hw,
+ void __iomem *sys_base)
+{
+ cfg_hw->sys_base = sys_base;
+
+ return devm_clk_hw_register(dev, &cfg_hw->hw);
+}
+
+static int mpfs_clk_register_cfgs(struct device *dev, struct mpfs_cfg_hw_clock *cfg_hws,
+ unsigned int num_clks, struct mpfs_clock_data *data)
+{
+ void __iomem *sys_base = data->base;
+ unsigned int i, id;
+ int ret;
+
+ for (i = 0; i < num_clks; i++) {
+ struct mpfs_cfg_hw_clock *cfg_hw = &cfg_hws[i];
+
+ ret = mpfs_clk_register_cfg(dev, cfg_hw, sys_base);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register clock id: %d\n",
+ cfg_hw->cfg.id);
+
+ id = cfg_hws[i].cfg.id;
+ data->hw_data.hws[id] = &cfg_hw->hw;
+ }
+
+ return 0;
+}
+
+static int mpfs_periph_clk_enable(struct clk_hw *hw)
+{
+ struct mpfs_periph_hw_clock *periph_hw = to_mpfs_periph_clk(hw);
+ struct mpfs_periph_clock *periph = &periph_hw->periph;
+ void __iomem *base_addr = periph_hw->sys_base;
+ u32 reg, val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mpfs_clk_lock, flags);
+
+ reg = readl_relaxed(base_addr + REG_SUBBLK_RESET_CR);
+ val = reg & ~(1u << periph->shift);
+ writel_relaxed(val, base_addr + REG_SUBBLK_RESET_CR);
+
+ reg = readl_relaxed(base_addr + REG_SUBBLK_CLOCK_CR);
+ val = reg | (1u << periph->shift);
+ writel_relaxed(val, base_addr + REG_SUBBLK_CLOCK_CR);
+
+ spin_unlock_irqrestore(&mpfs_clk_lock, flags);
+
+ return 0;
+}
+
+static void mpfs_periph_clk_disable(struct clk_hw *hw)
+{
+ struct mpfs_periph_hw_clock *periph_hw = to_mpfs_periph_clk(hw);
+ struct mpfs_periph_clock *periph = &periph_hw->periph;
+ void __iomem *base_addr = periph_hw->sys_base;
+ u32 reg, val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mpfs_clk_lock, flags);
+
+ reg = readl_relaxed(base_addr + REG_SUBBLK_RESET_CR);
+ val = reg | (1u << periph->shift);
+ writel_relaxed(val, base_addr + REG_SUBBLK_RESET_CR);
+
+ reg = readl_relaxed(base_addr + REG_SUBBLK_CLOCK_CR);
+ val = reg & ~(1u << periph->shift);
+ writel_relaxed(val, base_addr + REG_SUBBLK_CLOCK_CR);
+
+ spin_unlock_irqrestore(&mpfs_clk_lock, flags);
+}
+
+static int mpfs_periph_clk_is_enabled(struct clk_hw *hw)
+{
+ struct mpfs_periph_hw_clock *periph_hw = to_mpfs_periph_clk(hw);
+ struct mpfs_periph_clock *periph = &periph_hw->periph;
+ void __iomem *base_addr = periph_hw->sys_base;
+ u32 reg;
+
+ reg = readl_relaxed(base_addr + REG_SUBBLK_RESET_CR);
+ if ((reg & (1u << periph->shift)) == 0u) {
+ reg = readl_relaxed(base_addr + REG_SUBBLK_CLOCK_CR);
+ if (reg & (1u << periph->shift))
+ return 1;
+ }
+
+ return 0;
+}
+
+static const struct clk_ops mpfs_periph_clk_ops = {
+ .enable = mpfs_periph_clk_enable,
+ .disable = mpfs_periph_clk_disable,
+ .is_enabled = mpfs_periph_clk_is_enabled,
+};
+
+#define CLK_PERIPH(_id, _name, _parent, _shift, _flags) { \
+ .periph.id = _id, \
+ .periph.shift = _shift, \
+ .hw.init = CLK_HW_INIT_HW(_name, _parent, &mpfs_periph_clk_ops, \
+ _flags), \
+}
+
+#define PARENT_CLK(PARENT) (&mpfs_cfg_clks[CLK_##PARENT].hw)
+
+/*
+ * Critical clocks:
+ * - CLK_ENVM: reserved by hart software services (hss) superloop monitor/m mode interrupt
+ * trap handler
+ * - CLK_MMUART0: reserved by the hss
+ * - CLK_DDRC: provides clock to the ddr subsystem
+ * - CLK_FICx: these provide clocks for sections of the fpga fabric, disabling them would
+ * cause the fabric to go into reset
+ */
+
+static struct mpfs_periph_hw_clock mpfs_periph_clks[] = {
+ CLK_PERIPH(CLK_ENVM, "clk_periph_envm", PARENT_CLK(AHB), 0, CLK_IS_CRITICAL),
+ CLK_PERIPH(CLK_MAC0, "clk_periph_mac0", PARENT_CLK(AHB), 1, 0),
+ CLK_PERIPH(CLK_MAC1, "clk_periph_mac1", PARENT_CLK(AHB), 2, 0),
+ CLK_PERIPH(CLK_MMC, "clk_periph_mmc", PARENT_CLK(AHB), 3, 0),
+ CLK_PERIPH(CLK_TIMER, "clk_periph_timer", PARENT_CLK(AHB), 4, 0),
+ CLK_PERIPH(CLK_MMUART0, "clk_periph_mmuart0", PARENT_CLK(AHB), 5, CLK_IS_CRITICAL),
+ CLK_PERIPH(CLK_MMUART1, "clk_periph_mmuart1", PARENT_CLK(AHB), 6, 0),
+ CLK_PERIPH(CLK_MMUART2, "clk_periph_mmuart2", PARENT_CLK(AHB), 7, 0),
+ CLK_PERIPH(CLK_MMUART3, "clk_periph_mmuart3", PARENT_CLK(AHB), 8, 0),
+ CLK_PERIPH(CLK_MMUART4, "clk_periph_mmuart4", PARENT_CLK(AHB), 9, 0),
+ CLK_PERIPH(CLK_SPI0, "clk_periph_spi0", PARENT_CLK(AHB), 10, 0),
+ CLK_PERIPH(CLK_SPI1, "clk_periph_spi1", PARENT_CLK(AHB), 11, 0),
+ CLK_PERIPH(CLK_I2C0, "clk_periph_i2c0", PARENT_CLK(AHB), 12, 0),
+ CLK_PERIPH(CLK_I2C1, "clk_periph_i2c1", PARENT_CLK(AHB), 13, 0),
+ CLK_PERIPH(CLK_CAN0, "clk_periph_can0", PARENT_CLK(AHB), 14, 0),
+ CLK_PERIPH(CLK_CAN1, "clk_periph_can1", PARENT_CLK(AHB), 15, 0),
+ CLK_PERIPH(CLK_USB, "clk_periph_usb", PARENT_CLK(AHB), 16, 0),
+ CLK_PERIPH(CLK_RTC, "clk_periph_rtc", PARENT_CLK(AHB), 18, 0),
+ CLK_PERIPH(CLK_QSPI, "clk_periph_qspi", PARENT_CLK(AHB), 19, 0),
+ CLK_PERIPH(CLK_GPIO0, "clk_periph_gpio0", PARENT_CLK(AHB), 20, 0),
+ CLK_PERIPH(CLK_GPIO1, "clk_periph_gpio1", PARENT_CLK(AHB), 21, 0),
+ CLK_PERIPH(CLK_GPIO2, "clk_periph_gpio2", PARENT_CLK(AHB), 22, 0),
+ CLK_PERIPH(CLK_DDRC, "clk_periph_ddrc", PARENT_CLK(AHB), 23, CLK_IS_CRITICAL),
+ CLK_PERIPH(CLK_FIC0, "clk_periph_fic0", PARENT_CLK(AHB), 24, CLK_IS_CRITICAL),
+ CLK_PERIPH(CLK_FIC1, "clk_periph_fic1", PARENT_CLK(AHB), 25, CLK_IS_CRITICAL),
+ CLK_PERIPH(CLK_FIC2, "clk_periph_fic2", PARENT_CLK(AHB), 26, CLK_IS_CRITICAL),
+ CLK_PERIPH(CLK_FIC3, "clk_periph_fic3", PARENT_CLK(AHB), 27, CLK_IS_CRITICAL),
+ CLK_PERIPH(CLK_ATHENA, "clk_periph_athena", PARENT_CLK(AHB), 28, 0),
+ CLK_PERIPH(CLK_CFM, "clk_periph_cfm", PARENT_CLK(AHB), 29, 0),
+};
+
+static int mpfs_clk_register_periph(struct device *dev, struct mpfs_periph_hw_clock *periph_hw,
+ void __iomem *sys_base)
+{
+ periph_hw->sys_base = sys_base;
+
+ return devm_clk_hw_register(dev, &periph_hw->hw);
+}
+
+static int mpfs_clk_register_periphs(struct device *dev, struct mpfs_periph_hw_clock *periph_hws,
+ int num_clks, struct mpfs_clock_data *data)
+{
+ void __iomem *sys_base = data->base;
+ unsigned int i, id;
+ int ret;
+
+ for (i = 0; i < num_clks; i++) {
+ struct mpfs_periph_hw_clock *periph_hw = &periph_hws[i];
+
+ ret = mpfs_clk_register_periph(dev, periph_hw, sys_base);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register clock id: %d\n",
+ periph_hw->periph.id);
+
+ id = periph_hws[i].periph.id;
+ data->hw_data.hws[id] = &periph_hw->hw;
+ }
+
+ return 0;
+}
+
+static int mpfs_clk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mpfs_clock_data *clk_data;
+ unsigned int num_clks;
+ int ret;
+
+ /* CLK_RESERVED is not part of cfg_clks nor periph_clks, so add 1 */
+ num_clks = ARRAY_SIZE(mpfs_cfg_clks) + ARRAY_SIZE(mpfs_periph_clks) + 1;
+
+ clk_data = devm_kzalloc(dev, struct_size(clk_data, hw_data.hws, num_clks), GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(clk_data->base))
+ return PTR_ERR(clk_data->base);
+
+ clk_data->hw_data.num = num_clks;
+
+ ret = mpfs_clk_register_cfgs(dev, mpfs_cfg_clks, ARRAY_SIZE(mpfs_cfg_clks), clk_data);
+ if (ret)
+ return ret;
+
+ ret = mpfs_clk_register_periphs(dev, mpfs_periph_clks, ARRAY_SIZE(mpfs_periph_clks),
+ clk_data);
+ if (ret)
+ return ret;
+
+ ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, &clk_data->hw_data);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static const struct of_device_id mpfs_clk_of_match_table[] = {
+ { .compatible = "microchip,mpfs-clkcfg", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mpfs_clk_match_table);
+
+static struct platform_driver mpfs_clk_driver = {
+ .probe = mpfs_clk_probe,
+ .driver = {
+ .name = "microchip-mpfs-clkcfg",
+ .of_match_table = mpfs_clk_of_match_table,
+ },
+};
+
+static int __init clk_mpfs_init(void)
+{
+ return platform_driver_register(&mpfs_clk_driver);
+}
+core_initcall(clk_mpfs_init);
+
+static void __exit clk_mpfs_exit(void)
+{
+ platform_driver_unregister(&mpfs_clk_driver);
+}
+module_exit(clk_mpfs_exit);
+
+MODULE_DESCRIPTION("Microchip PolarFire SoC Clock Driver");
+MODULE_LICENSE("GPL v2");
--
2.32.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox